P5113-Sabbat of the witch【分块,基数排序】
正题
题目链接:https://www.luogu.com.cn/problem/P5113
题目大意
一个长度为\(n\)的序列\(a\),\(m\)次要求支持以下操作
- 将区间\([l,r]\)都变为\(x\)。
- 询问区间\([l,r]\)的和。
- 将第\(x\)次操作\(1\)撤销。
强制在线
\(1\leq n,m\leq 10^5,1\leq a_i,x\leq 10^{9}\)
操作\(1\)的个数不超过\(65000\)
解题思路
考虑分块,那么我们需要解决散块和整块的问题。
考虑使用领接表(单向链表)来维护每个数字和块的历史状态,那么对于散块来说我们直接添加新节点连接,然后撤回时给对应操作打上标记,再在邻接表上一直跳到没有打标记的节点即可。
在散块的个体处理完后我们需要重构维护整块信息。
现在主要是整块如何处理的问题,一般的我们会有一个\(ans_i\)表示第\(i\)块的和,和同上面个体一样的,每个块都有一个领接表来表明它的历史状态。
对于一个块来说,不是整块修改产生贡献的部分个体的修改时间比整块的修改时间要晚,而且撤回操作只会让整个块的修改时间变早,而修改操作一定会让整个块的修改时间变成最晚的。
结合上面的性质,我们大概能得到一个做法。对于每个块,我们维护一个每个数散块的修改时间升序排序的序列,这样越在前面的数越容易被整块的修改影响。也就是必定是影响一个前缀,假设这个前缀的位置为\(x\),那么撤回操作只会让\(x\)向前移动,而整块修改只会让\(x\)直接跳到\(R\),这两种情况分开维护的话就会发现对于每次重构后的块,\(x\)最多移动\(\sqrt n\)次,也就是这个移动的复杂度均摊进了重构中。
而这个升序序列只需要在散块修改的时候才需要重构,需要使用到基数排序。
需要注意的细节是开始所有数都没有被修改但是有值,所以块内的初值很重要。
时间复杂度:\(O(n\sqrt n)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const ll N=1e5+10,Q=320,P=256;
ll n,m,tot,cnt,qnt,a[N],ls[N];
ll b[Q],rk[N],rks[N],res[N],xs[N];
ll w[N],ql[N],qr[N],val[N*Q],nxt[N*Q];
ll L[Q],R[Q],lst[Q],ans[Q],lp[Q],pos[N];
bool flag[N];
void Addl(ll &x,ll w)
{val[++tot]=w;nxt[tot]=x;x=tot;return;}
void PushUp(ll &x)
{while(flag[val[x]])x=nxt[x];return;}
void Rebuild(ll x){
for(ll i=0;i<P;i++)b[i]=0;
for(ll i=L[x];i<=R[x];i++)b[val[ls[i]]%P]++;
for(ll i=1;i<P;i++)b[i]+=b[i-1];
for(ll i=L[x];i<=R[x];i++)
b[val[ls[i]]%P]--,rk[L[x]+b[val[ls[i]]%P]]=i;
for(ll i=0;i<P;i++)b[i]=0;
for(ll i=L[x];i<=R[x];i++)b[val[ls[i]]/P]++;
for(ll i=1;i<P;i++)b[i]+=b[i-1];
for(ll i=R[x];i>=L[x];i--)
b[val[ls[rk[i]]]/P]--,rks[L[x]+b[val[ls[rk[i]]]/P]]=rk[i];
for(ll i=L[x];i<=R[x];i++)xs[i]=val[ls[rks[i]]];
res[R[x]]=xs[R[x]]?w[xs[R[x]]]:a[rks[R[x]]];
for(ll i=R[x]-1;i>=L[x];i--)
res[i]=res[i+1]+(xs[i]?w[xs[i]]:a[rks[i]]);
lp[x]=L[x];
while(lp[x]<=R[x]&&xs[lp[x]]<val[lst[x]])lp[x]++;
ans[x]=(lp[x]<=R[x])?res[lp[x]]:0;
ans[x]+=(lp[x]-L[x])*w[val[lst[x]]];
return;
}
void Change(ll l,ll r,ll val){
ll x=pos[l],y=pos[r];
++qnt;w[qnt]=val;
ql[qnt]=l;qr[qnt]=r;
if(x==y){
for(ll i=l;i<=r;i++)
Addl(ls[i],qnt);
Rebuild(x);
return;
}
for(ll i=x+1;i<y;i++)
Addl(lst[i],qnt),ans[i]=val*(R[i]-L[i]+1);
for(ll i=l;i<=R[x];i++)Addl(ls[i],qnt);
for(ll i=L[y];i<=r;i++)Addl(ls[i],qnt);
Rebuild(x);Rebuild(y);
return;
}
ll GetI(ll p){
if(!ls[p]&&!lst[pos[p]])return a[p];
return w[max(val[lst[pos[p]]],val[ls[p]])];
}
ll Ask(ll l,ll r){
ll x=pos[l],y=pos[r],sum=0;
if(x==y){
for(ll i=l;i<=r;i++)sum+=GetI(i);
return sum;
}
for(ll i=x+1;i<y;i++)sum+=ans[i];
for(ll i=l;i<=R[x];i++)sum+=GetI(i);
for(ll i=L[y];i<=r;i++)sum+=GetI(i);
return sum;
}
void Remake(ll x){
if(!flag[val[lst[x]]])return;
PushUp(lst[x]);
if(xs[R[x]]<val[lst[x]]){
ans[x]=(R[x]-L[x]+1)*w[val[lst[x]]];
return;
}
lp[x]--;
while(lp[x]>=L[x]&&xs[lp[x]]>=val[lst[x]])lp[x]--;
lp[x]++;ans[x]=(lp[x]<=R[x])?res[lp[x]]:0;
ans[x]+=(lp[x]-L[x])*w[val[lst[x]]];
return;
}
void Withdraw(ll p){
ll l=ql[p],r=qr[p];flag[p]=1;
ll x=pos[l],y=pos[r];
if(x==y){
for(ll i=l;i<=r;i++)PushUp(ls[i]);
Rebuild(x);return;
}
for(ll i=x+1;i<y;i++)Remake(i);
for(ll i=l;i<=R[x];i++)PushUp(ls[i]);
for(ll i=L[y];i<=r;i++)PushUp(ls[i]);
Rebuild(x);Rebuild(y);
return;
}
signed main()
{
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
scanf("%lld%lld",&n,&m);
ll T=sqrt(n);
for(ll i=1;i<=n/T;i++)L[i]=R[i-1]+1,R[i]=i*T;
cnt=n/T;if(R[cnt]!=n)++cnt,L[cnt]=R[cnt-1]+1,R[cnt]=n;
for(ll i=1;i<=cnt;i++)
for(ll j=L[i];j<=R[i];j++)pos[j]=i;
for(ll i=1;i<=n;i++)scanf("%lld",&a[i]),ans[pos[i]]+=a[i];
for(ll i=1;i<=cnt;i++)Rebuild(i);
ll las=0;
while(m--){
ll op;scanf("%lld",&op);
if(op==1){
ll l,r,w;
scanf("%lld%lld%lld",&l,&r,&w);
l^=las;r^=las;Change(l,r,w);
}
else if(op==2){
ll l,r;
scanf("%lld%lld",&l,&r);
l^=las;r^=las;
printf("%lld\n",las=Ask(l,r));
}
else{
ll x;scanf("%lld",&x);
x^=las;Withdraw(x);
}
}
return 0;
}