[题解]POI2009 LYZ-Ice Skates

洛谷P3488

简要题意:

  • 共有 \(n\) 种号码的鞋子,每种号码的鞋子都有 \(k\) 双,\(x\) 号脚的人可以穿 \(x,x+1,\dots\,x + d\) 号码的鞋子
  • \(m\) 操作,每次会有人到来或离开,每次操作后,判断每个人是否都能匹配到鞋子
  • \(n \le 2 \times 10^5,m \le 5 \times 10^5,1\le k \le 10^9\)

做法:

\(s_i\)\(i\) 号脚的人的个数,那么显然对于任意一段区间 \([l,r]\),都要满足 \(\sum_{i=l}^r s_i \le k \times (r+d-l+1)\)

将右边拆开可得 \(\sum_{i=l}^r s_i \le k \times (r-l+1) + k \times d\)

移项得 \(\sum_{i=l}^r s_i - k \le k \times d\)

要使不等式成立,只需 \(\sum_{i=l}^r s_i-k\) 的最大值小于等于 \(k \times d\) 即可。

于是我们需要维护序列 \(\{s_i-k\}\) 的最大子段和。

如果没有修改操作,这就是一个很简单的DP题

\(f_i\) 为以\(i\)结尾的最大字段和, \(g_i\) 为前 \(i\) 个数的最大字段和。

那么 \(f_i=f_{i-1}+s_i,g_i=max(g_{i-1},f_i)\),同时 \(g_n\) 就是这个序列的最大字段和。

然而现在我们需要修改序列中的值,于是我们要从修改的位置向后重新递推。然而时间复杂度 \(O(nm)\)显然无法解决问题,所以考虑使用动态DP。

考虑如何优化修改后的递推过程。显然 \(f_i\) 的转移方程是一个线性递推式可以使用矩阵加速,但 \(g_i\) 的转移方程中有一个取max的操作不能用矩阵来转移,于是我们需要重新定义矩阵乘法。

众所周知,传统的矩阵乘法是 \(C_{i,j}=\sum A_{i,k} \times B_{k,j}\),由于矩阵乘法满足结合律所以我们可以通过快速幂来加速运算。

尝试将新的矩阵乘法定义为 \(C_{i,j}=max \{ A_{i,k} + B_{k,j} \}\),可以发现,新的乘法运算同样满足结合律。

解决的转移的问题后我们就可以构造出矩阵:

\(\begin{bmatrix}s_i&-inf&s_i\\s_i&0&s_i\\-inf&-inf&0\end{bmatrix} \times \begin{bmatrix}f_{i-1}\\g_{i-1}\\0\end{bmatrix} = \begin{bmatrix}f_i\\g_i\\0\end{bmatrix}\)

然而仅有矩阵还不够,我们要快速求出一个区间的乘积才能得到答案,但每个位置的转移矩阵都不相同所以我们不能用快速幂来求。

要支持单点修改和查询区间乘积,显然想到用线段树来维护。我们可以在线段树的每个节点放一个矩阵来维护这个区间的乘积,每次修改只需改对应叶子节点的矩阵,然后向上更新父亲即可。

代码:

#include <cstdio>
#include <algorithm>

using namespace std;

#define il inline
#define re register
#define file(s) freopen(#s".in","r",stdin),freopen(#s".out","w",stdout)

typedef long long ll;

const int N=5e5+10;
const ll inf=0x3f3f3f3f3f3f3f3f;

namespace FastIO
{
char buf[1<<21],buf2[1<<21],*p1=buf,*p2=buf;
int p4,p3=-1;
il int getc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
il void Flush(){fwrite(buf2,1,p3+1,stdout),p3=-1;}
#define isdigit(ch) (ch>=48&&ch<=57)
template <typename T>
il void read(T &x)
{
	re int f=0;x=0;re char ch=getc();
	while(!isdigit(ch)) f|=(ch=='-'),ch=getc();
	while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getc();
	x=f?-x:x;
}
template <typename T>
il void print(T x)
{
	if(p3>(1<<20)) Flush();
	if(x<0) buf2[++p3]=45,x=-x;
	re int a[50]={};
	do{a[++p4]=x%10+48;}while(x/=10);
	do{buf2[++p3]=a[p4];}while(--p4);
}
}
using namespace FastIO;

/*矩阵部分*/
struct mat{ll a[3][3];};
il void init(mat &x)
{
	for(re int i=0;i<3;++i)
		for(re int j=0;j<3;++j)
			x.a[i][j]=-inf;
	return;
}
il mat mul(const mat &x,const mat &y)
{
	re mat z;init(z);
	for(re int i=0;i<3;++i)
		for(re int j=0;j<3;++j)
			for(re int k=0;k<3;++k)
				z.a[i][j]=max(z.a[i][j],x.a[i][k]+y.a[k][j]);
	return z;
}
//新的矩阵乘法

int n,m;
ll k,d,s[N];

/*线段树部分*/
int L[N<<2],R[N<<2];
mat val[N<<2];
#define ls(i) (i<<1)
#define rs(i) (i<<1|1)
#define pushup(i) (val[i]=mul(val[ls(i)],val[rs(i)]))
il void build(int i,int l,int r)
{
	L[i]=l;R[i]=r;
	if(l==r)
	{
		val[i].a[0][0]=s[L[i]]; val[i].a[0][1]=-inf; val[i].a[0][2]=s[L[i]];
		val[i].a[1][0]=s[L[i]]; val[i].a[1][1]=0;    val[i].a[1][2]=s[L[i]];
		val[i].a[2][0]=-inf;    val[i].a[2][1]=-inf; val[i].a[2][2]=0;
		return;
	}
	re int mid=(l+r)>>1;
	build(ls(i),l,mid);build(rs(i),mid+1,r);
	pushup(i);return;
}//建树
il void modify(int i,int p)
{
	if(L[i]==R[i])
	{
		val[i].a[0][0]=s[L[i]]; val[i].a[0][1]=-inf; val[i].a[0][2]=s[L[i]];
		val[i].a[1][0]=s[L[i]]; val[i].a[1][1]=0;    val[i].a[1][2]=s[L[i]];
		val[i].a[2][0]=-inf;    val[i].a[2][1]=-inf; val[i].a[2][2]=0;
		return;//重置叶子节点的矩阵
	}
	re int mid=(L[i]+R[i])>>1;
	modify(p>mid?rs(i):ls(i),p);
	pushup(i);return;
}//单点修改

int main()
{
	read(n);read(m);read(k);read(d);
	for(re int i=1;i<=n;++i) s[i]=-k;
	build(1,1,n);
	for(re int i=1,r,x;i<=m;++i)
	{
		read(r),read(x);s[r]+=x;modify(1,r);
		puts(val[1].a[1][2]<=k*d?"TAK":"NIE");
        //根节点的矩阵即为整个序列的答案
	}
	Flush();return 0;
}
posted @ 2020-12-04 15:29  watermonster1y1  阅读(51)  评论(0编辑  收藏  举报