【学习笔记】回滚莫队初步总结

· · 个人记录

什么是回滚莫队啊(战术后仰)

首先确定一个常数len(一般而言取 \sqrt{n} ,n^{\frac{2}{3}}),将数组分为\lceil {\frac{n}{len}} \rceil个块 对于m个区间询问,将其按照左端点l所属的块从小到大排序,然后对于左端点在同一个块内的数据吗,将右端点r按从小到大排序

然后枚举每个块,让我们来处理所有左端点在这个块内的询问

我们知道一个性质,左端点在同一个块内的询问,右端点从左到右排序,所以右端点是单调的,我们用一个不会回退的指针rr来统计右边的结果

现在来考虑左端点,虽然左端点是无序的,但是由于它们都在同一个块内,所以它们离这个块的右端点的距离都不会超过len,所以我们用一个可以回退的指针 ll 来统计左边的答案

由于同一个块内的询问的右端点是递增的,因此 rr 产生的贡献在统计这个块内询问时是永久的,不用删除,而左端点是无序的,产生的贡献需要删除。

,注意 $ll$ 统计的贡献是临时的,因此统计完之后要让$ll$从此询问的左端点回归到块的右端点,同时删除贡献。 以下是需要注意的几点 1. 需要有一个不会归零的量记录$rr$统计到的贡献 2. 左右端点在同一个块内的询问可以直接暴力,反正也就O($len$)的复杂度 来考虑复杂度,对于一个区块内的询问,指针 $rr$ 最多从这个区块右端点移动的序列右端点,最多移动 $n$ 次,最多有$\lceil {\frac{n}{len}} \rceil$个区间,所以复杂度最多为O($nlen$)。 对于单个询问,指针 $ll$ 在这个块内移动,每个询问最多移动$len$ 次,对于所有的 $m$ 个询问,最多移动O($mlen$)次。 因为 $m$ , $n$同级,可以证明算法的复杂度为O($nlen$),大多数情况复杂度为O($n\sqrt n$) 回滚莫队有一个好处,即只需要增操作,不需要减操作,对与某些无法实现减操作(比如查询最大值)的题目尤其好用 例题:[P3380【模板】回滚莫队&不删除莫队](https://www.luogu.com.cn/problem/P5906) ```cpp #include<bits/stdc++.h> #define N 200010 using namespace std; int n,m,a[N],las[N],tmp[N],qlen,qn,bel[N],mr[N],ans[N],st[N],ed[N],num[N],cnt; struct node{ int l,r,id; }qt[200010]; bool cmp(node a1,node a2) {return (bel[a1.l]==bel[a2.l])?(a1.r<a2.r):(bel[a1.l]<bel[a2.l]);} int calc(int l,int r) { int res=0; for(int i=l; i<=r; i++) las[a[i]]=0; for(int i=l; i<=r; i++) (las[a[i]]==0)?(las[a[i]]=i):(res=max(res,i-las[a[i]])); return res; } int main() { cin>>n; qlen=sqrt(n); for(int i=1; i<=n; i++) { cin>>a[i]; tmp[i]=a[i],bel[i]=(i-1)/qlen+1; mr[bel[i]]=i; } qn=bel[n],mr[bel[n]]=n; sort(tmp+1,tmp+n+1); int tn=unique(tmp+1,tmp+n+1)-tmp-1; for(int i=1; i<=n; i++) a[i]=lower_bound(tmp+1,tmp+tn+1,a[i])-tmp; cin>>m; for(int i=1; i<=m; i++) { cin>>qt[i].l>>qt[i].r; qt[i].id=i; } sort(qt+1,qt+m+1,cmp); int now=1; for(int i=1; i<=qn; i++) { int l=mr[i]+1,r=l-1,mx=0; cnt=0; while(bel[qt[now].l]==i) { if(bel[qt[now].r]==i) { ans[qt[now].id]=calc(qt[now].l,qt[now].r),now++; continue; } while(r<qt[now].r) { r++; ed[a[r]]=r; if(!st[a[r]]) st[a[r]]=r,num[++cnt]=a[r]; mx=max(mx,r-st[a[r]]); } int t1=mx; while(l>qt[now].l) { l--; if(!ed[a[l]]) ed[a[l]]=l; else mx=max(mx,ed[a[l]]-l); } ans[qt[now].id]=mx; while(l<=mr[i]) { if(ed[a[l]]==l) ed[a[l]]=0; l++; } mx=t1; now++; } for(int j=1; j<=cnt; j++) st[num[j]]=ed[num[j]]=0; } for(int i=1; i<=m; i++) cout<<ans[i]<<endl; return 0; } ``` [AT1219歴史の研究](https://www.luogu.com.cn/problem/AT1219) ```cpp #include<bits/stdc++.h> #define N 500010 using namespace std; long long n,q,a[N],bel[N],qlen,mr[N],ans[N],bac[N],num[N],cnt,bac2[N],tmp[N],dui[N]; struct node{ long long l,r,id; }pt[100010]; bool cmp(node a1,node a2) { return (bel[a1.l]==bel[a2.l])?(a1.r<a2.r):(bel[a1.l]<bel[a2.l]); } long long calc(long long l,long long r) { long long res=0; for(long long i=l; i<=r; i++) bac2[a[i]]=0; for(long long i=l; i<=r; i++) bac2[a[i]]+=dui[a[i]],res=max(res,bac2[a[i]]); return res; } int main() { cin>>n>>q; qlen=sqrt(n); for(long long i=1; i<=n; i++) { cin>>a[i];tmp[i]=a[i]; bel[i]=(i-1)/qlen+1,mr[bel[i]]=i; } sort(tmp+1,tmp+n+1); int tn=unique(tmp+1,tmp+n+1)-tmp-1; for(int i=1; i<=n; i++) { int tp=a[i]; a[i]=lower_bound(tmp+1,tmp+tn+1,a[i])-tmp; dui[a[i]]=tp; } for(long long i=1; i<=q; i++) { cin>>pt[i].l>>pt[i].r; pt[i].id=i; } sort(pt+1,pt+q+1,cmp); long long now=1; for(long long i=1; i<=bel[n]; i++) { long long l=mr[i]+1,r=l-1,mx=0; cnt=0; while(bel[pt[now].l]==i) { if(bel[pt[now].r]==i) { ans[pt[now].id]=calc(pt[now].l,pt[now].r); now++;continue; } while(r<pt[now].r) r++,bac[a[r]]+=dui[a[r]],mx=max(mx,bac[a[r]]),num[++cnt]=a[r]; long long tp=mx; while(l>pt[now].l) l--,bac[a[l]]+=dui[a[l]],mx=max(mx,bac[a[l]]); ans[pt[now].id]=mx; while(l<=mr[i]) bac[a[l]]-=dui[a[l]],l++; now++,mx=tp; } for(long long j=1; j<=cnt; j++) bac[num[j]]=0; } for(long long i=1; i<=q; i++) cout<<ans[i]<<endl; return 0; } ``` _upd2022/1/20:只删回滚莫队_ 与前面提到的只加回滚莫队相反,只删回滚莫队通过将询问左端点按块递增排序,将左端点在同一个块内的询问按右端点降序排序,$l$ 指针复位时回到块的最左端而不是最右端, $r$ 指针从右往左扫来实现只有删除操作的效果。 例题:[P8078 [WC2022] 秃子酋长](https://www.luogu.com.cn/problem/P8078) 这种莫队的例题比较稀少,目前也只找到一道。