音乐播放器
HMF-have much fun Site
 
文章 标签
16

Powered by Gridea | Theme: Fog
载入天数...
载入时分秒...
总访问量:  |   访问人数:

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

  热度: loading...

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

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

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

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

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

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

llllrrrr 都从这个区间的右端点出发,首先rrrr从上一个询问的右端点走向这一个询问的右端点,然后llll从块的右端点出发,向左走统计左边的贡献
,注意 llll 统计的贡献是临时的,因此统计完之后要让llll从此询问的左端点回归到块的右端点,同时删除贡献。

以下是需要注意的几点

  1. 需要有一个不会归零的量记录rrrr统计到的贡献
  2. 左右端点在同一个块内的询问可以直接暴力,反正也就O(lenlen)的复杂度

来考虑复杂度,对于一个区块内的询问,指针 rrrr 最多从这个区块右端点移动的序列右端点,最多移动 nn 次,最多有nlen\lceil {\frac{n}{len}} \rceil个区间,所以复杂度最多为O(nlennlen)。

对于单个询问,指针 llll 在这个块内移动,每个询问最多移动lenlen 次,对于所有的 mm 个询问,最多移动O(mlenmlen)次。

因为 mmnn同级,可以证明算法的复杂度为O(nlennlen),大多数情况复杂度为O(nnn\sqrt n

回滚莫队有一个好处,即只需要增操作,不需要减操作,对与某些无法实现减操作(比如查询最大值)的题目尤其好用

例题:P3380【模板】回滚莫队&不删除莫队

#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歴史の研究

#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:只删回滚莫队

与前面提到的只加回滚莫队相反,只删回滚莫队通过将询问左端点按块递增排序,将左端点在同一个块内的询问按右端点降序排序,ll 指针复位时回到块的最左端而不是最右端, rr 指针从右往左扫来实现只有删除操作的效果。

例题:P8078 [WC2022] 秃子酋长

这种莫队的例题比较稀少,目前也只找到一道。