[题解]POI2009 LYZ-Ice Skates
简要题意:
- 共有 \(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;
}