回滚莫队

我们发现有的时候我们莫队不方便维护加或减中的一个,只方便维护另一个,我们就要考虑另外一种更加针对化的莫队。

回滚莫队

先开一个例题 「JOISC 2014 Day1」历史研究

题目大意:一个序列,多个询问。每次询问一段区间 [l,r] ,定义重要度为一个数出现次数与其权值的乘积,求区间中的重要度的最大值。

我们发现这道题中我们并不方便维护删除。因为我们如果加入一个数,那么显然只要维护当前最大值与加入的数

的重要度的较大者即可,这是 O(1) 的。而删除我们并不知道当最大值被删除后我们应选择谁,所以难以 O(1) 维护。那么我们怎么考虑不维护删除操作呢?

我们先按照左端点所属块从小到大排序,右端点第二关键字从小到大排序。我们对于所有左端点在同一个块内的,我们将左端点控制在当前块的右边界 +1 的位置,右端点为当前块的左边界。这样我们维护了一个空集。当然网上还有一种写法是右端点位于右边界,然后对于左右询问端点同块的直接跑暴力,这样也是可以的,其实区别不大。

这样设置好之后,我们先把右端点向着询问靠拢,更新答案。之后我们临时创建 l2,res2 ,初始值为 l,res ,然后我们把 l2 向询问靠拢同时更新 res2。当前询问的答案就是 res2 ,之后我们恢复l2 加入的部分。(注意并未真正意义上的删除)为什么我们这样做?因为我们的排序方法虽然保证了右端点的单调性,但是没法保证左端点,因此我们每次都刷一遍来维护左端点。

关于复杂度是 O(nn)。证明一下:首先对于每个块内,右端点单调增,总共一整个块的复杂度是 O(n) ,对于所有块就是 O(nn) 。左端点每次询问最多跑 O(n) ,总共算起来就是 O(nn) 。每次切换左端点的块,右端点最多跑 O(n) ,总共 O(nn)。所以总复杂度是 O(nn)

参考代码

#include<bits/stdc++.h>
#define ll long long
#define db double
#define filein(a) freopen(#a".in","r",stdin)
#define fileot(a) freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define gc getchar
#define pc putchar
namespace IO{
	template<class T>
	inline void read(T &s){
		s=0;char ch=gc();bool f=0;
		while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
		while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
		if(ch=='.'){
			db p=0.1;ch=gc();
			while('0'<=ch&&ch<='9') {s=s+p*(ch^48);ch=gc();}
		}
		s=f?-s:s;
	}
	template<class T,class ...A>
	inline void read(T &s,A &...a){
		read(s);read(a...);
	}
	inline bool blank(char c){
		return c==' ' or c=='\t' or c=='\n' or c=='\r' or c==EOF;
	}
	inline void gs(std::string &s){
		s+='#';char c=gc();
		while(blank(c) ) c=gc();
		while(!blank(c) ){
			s+=c;c=gc();
		}
	}
};
using IO::read;
using IO::gs;
const int N=1e5+3;
const int BLK=317;
int n,Q,num;
int a[N],c[N];
int st[BLK],ed[BLK];
int belong[N];
struct ques{
	int id,l,r;
}qu[N];
namespace Discrete{
	int id[N];
	inline void work(){
		for(int i=1;i<=n;++i){
			id[i]=i;
		}
		std::sort(id+1,id+1+n,[](int x,int y){
			return a[x]<a[y];
		});
		int top=1,last=a[id[1] ];
		a[id[1] ]=1;
		for(int i=2;i<=n;++i){
			if(a[id[i] ]!=last) ++top;
			last=a[id[i] ];
			c[top]=a[id[i] ];a[id[i] ]=top;
		}
	}
};
ll ans[N];
int cnt[N],cnt2[N];
inline void add(int x,ll &res){
	++cnt[a[x] ];
	res=std::max(res,1ll*cnt[a[x] ]*c[a[x] ]);
}
inline void del(int x){
	--cnt[a[x] ];
}
int blk;
int main(){
	//filein(a);fileot(a);
	read(n,Q);
	blk=sqrt(n);
	for(int i=1;i<=n;++i){
		read(a[i]);
	}
	//分块
	num=n/blk;
	for(int i=1;i<=num;++i){
		st[i]=(i-1)*blk+1;
		ed[i]=i*blk;
	}
	if(ed[num]!=n){
		++num;
		st[num]=ed[num-1]+1;
		ed[num]=n;
	}
	for(int i=1;i<=num;++i){
		for(int l=st[i];l<=ed[i];++l){
			belong[l]=i;
		}
	}
	for(int i=1;i<=Q;++i){
		int l,r;
		read(l,r);
		qu[i]={i,l,r};
	}
	std::sort(qu+1,qu+1+Q,[](ques x,ques y){
		if(belong[x.l]==belong[y.l])
			return x.r<y.r;
		return belong[x.l]<belong[y.l];
	});
	//离散化
	Discrete::work();
	//回滚莫队
	int lab=0;
	int l=1,r=0;ll mx=0;
	for(int i=1;i<=Q;++i){
		int ql=qu[i].l,qr=qu[i].r;
		if(lab!=belong[ql]){
			while(r>st[belong[ql] ]) del(r--);
			while(l<ed[belong[ql] ]+1) del(l++);
			lab=belong[ql];
			mx=0;
		}
		while(r<qr) add(++r,mx);
		int l2=l;ll mx2=mx;
		while(ql<l2) add(--l2,mx2);
		ans[qu[i].id]=mx2;
		while(l2<l) del(l2++);
	}
	for(int i=1;i<=Q;++i){
		printf("%lld\n",ans[i]);
	}
	return 0;
}

Rmq Problem / mex

题目大意:一个序列,多个询问。每次询问一段区间 [l,r] ,求区间 mex ,也即区间内最小没出现过的自然数。

这次我们发现删除要更加容易维护,所以考虑不做加入操作。我们先按照左端点所在块从小到大排序,再以右端点为第二关键字从大到小排序。

对于每个块,我们考虑将左右端点分别放在询问左端点块的左边界和询问右端点的右边界。这样我们属于是预处理了当前块内询问的完全覆盖集,最后我们向中间删即可。但是由于左端点仍然没有单调性,于是我们还是临时的删除,当前询问结束就恢复。

具体来说,我们对于每个块的询问先预处理出最大集和答案,然后随着删除的时候更新即可。预处理的答案我们暴力求解,然后删除的时候如果一个数被删空了,就可以和答案取较小者。

复杂度证明:

所有的东西都和上面那个题一样,只需要证暴力预处理答案不会寄即可。

发现暴力处理是 O(n) 的,但是换块只会进行 O(n) ,因此总复杂度还是 O(nn).

参考代码

#include<bits/stdc++.h>
#define ll long long
#define db double
#define filein(a) freopen(#a".in","r",stdin)
#define fileot(a) freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define gc getchar
#define pc putchar
namespace IO{
	template<class T>
	inline void read(T &s){
		s=0;char ch=gc();bool f=0;
		while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
		while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
		if(ch=='.'){
			db p=0.1;ch=gc();
			while('0'<=ch&&ch<='9') {s=s+p*(ch^48);ch=gc();}
		}
		s=f?-s:s;
	}
	template<class T,class ...A>
	inline void read(T &s,A &...a){
		read(s);read(a...);
	}
	inline bool blank(char c){
		return c==' ' or c=='\t' or c=='\n' or c=='\r' or c==EOF;
	}
	inline void gs(std::string &s){
		s+='#';char c=gc();
		while(blank(c) ) c=gc();
		while(!blank(c) ){
			s+=c;c=gc();
		}
	}
};
using IO::read;
using IO::gs;
const int N=2e5+3;
const int BLK=448+3;
const int inf=1e9;
int n,m;
int a[N];
struct ques{
	int id,l,r;
}qu[N];
int cnt[N],cnt2[N];
int blk,num;
int st[BLK],ed[BLK];
int belong[N];
inline void add(int x){
	++cnt[a[x] ];
}
inline void del(int x){
	--cnt[a[x] ];
}
inline void del(int x,int &mex){
	--cnt[a[x] ];
	if(!cnt[a[x] ]){
		mex=std::min(mex,a[x]);
	}
}
int ans[N];
int main(){
	//filein(a);fileot(a);
	read(n,m);
	for(int i=1;i<=n;++i){
		read(a[i]);
	}
	blk=sqrt(n);
	num=n/blk;
	for(int i=1;i<=num;++i){
		st[i]=ed[i-1]+1;
		ed[i]=i*blk;
	}
	if(ed[num]!=n){
		++num;
		st[num]=ed[num-1]+1;
		ed[num]=n;
	}
	for(int i=1;i<=num;++i){
		for(int l=st[i];l<=ed[i];++l){
			belong[l]=i;
		}
	}
	for(int i=1;i<=m;++i){
		int l,r;
		read(l,r);
		qu[i]={i,l,r};
	}
	std::sort(qu+1,qu+1+m,[](ques x,ques y){
		if(belong[x.l]==belong[y.l]){
			return x.r>y.r;
		}
		return belong[x.l]<belong[y.l];
	});
	int l=1,r=0;
	int mex=0;
	int lab=0;
	for(int i=1;i<=m;++i){
		int ql=qu[i].l,qr=qu[i].r;
		if(lab!=belong[ql]){
			while(r<ed[belong[qr] ]) add(++r);
			while(l<st[belong[ql] ]) del(l++);
			mex=0;
			while(cnt[mex]) ++mex;
			lab=belong[ql];
		}
		while(qr<r){
			del(r--,mex);
		}
		int l2=l,mex2=mex;
		while(l2<ql){
			del(l2++,mex2);
		}
		ans[qu[i].id]=mex2;
		while(l2>l){
			add(--l2);
		} 
	}
	for(int i=1;i<=m;++i){
		printf("%d\n",ans[i]);
	}
	//fprintf(stderr,"%dms\n",clock() );
	return 0;
}

[WC2022] 秃子酋长

题目大意:给一个排列。每次询问给出一个区间 [l,r] ,问这个区间排序之后,求相邻两个数的在原排列上位置之差的和。

题解

考场上没想到的。当时想法是莫队加set,复杂度是 O(nnlogn) 的。

实际上可以发现,排序后相邻两个数原排列上位置差的和,这个东西把值放到链表顺序连接的上,直接就弄出前驱后继了,继而可以发现我们可以做到 O(1) 删除并更新答案,O(1) 按顺序撤回,以及 O(n) 加点并更新答案。

于是考虑回滚莫队,我们把加入直接滚掉,维护只删不加的莫队。由于不知道到底要用到多少的链表,我们直接对于每块的询问把所有右端点删的边全给恢复了。

给出代码:

#include<bits/stdc++.h>
#define ll long long
#define db double
#define filein(a) freopen(#a".in","r",stdin)
#define fileot(a) freopen(#a".out","w",stdout)
#define sky fflush(stdout)
#define gc getchar
#define pc putchar
namespace IO{
	template<class T>
	inline void read(T &s){
		s=0;char ch=gc();bool f=0;
		while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=gc();}
		while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=gc();}
		if(ch=='.'){
			db p=0.1;ch=gc();
			while('0'<=ch&&ch<='9') {s=s+p*(ch^48);ch=gc();}
		}
		s=f?-s:s;
	}
	template<class T,class ...A>
	inline void read(T &s,A &...a){
		read(s);read(a...);
	}
	inline bool blank(char c){
		return c==' ' or c=='\t' or c=='\n' or c=='\r' or c==EOF;
	}
	inline void gs(std::string &s){
		s+='#';char c=gc();
		while(blank(c) ) c=gc();
		while(!blank(c) ){
			s+=c;c=gc();
		}
	}
};
using IO::read;
using IO::gs;
const int N=5e5+3;
int n,m;
int a[N];
struct ques{
	int id,l,r;
}qu[N];
ll ans[N];
const int BLK=708;
int blk,num;
int st[BLK],ed[BLK];
int belong[N];
int val[N];
struct list{
	struct node{
		int l,r;
	}p[N];
	int re[N],top;
	inline void build(){
		for(int i=1;i<=n;++i){
			if(i!=1) p[i].l=i-1;
			if(i!=n) p[i].r=i+1;
		}
	}
	inline void resume(){
		if(!top) return;
		int x=re[top--];
		p[p[x].l].r=x;
		p[p[x].r].l=x;
	}
	inline void del(int x,ll &res){
		x=a[x];
		int p1=p[x].l,p2=p[x].r;
		if(p1 and p2) res+=abs(val[p1]-val[p2]);
		if(p1) res-=abs(val[p1]-val[x]);
		if(p2) res-=abs(val[x]-val[p2]);
		re[++top]=x;
		p[p1].r=p2;p[p2].l=p1;
	}
}t;
int main(){
	//filein(a);fileot(a);
	read(n,m);
	blk=sqrt(n);
	num=n/blk;
	for(int i=1;i<=n;++i){
		read(a[i]);
		val[a[i] ]=i;
	}
	for(int i=1;i<=m;++i){
		int l,r;read(l,r);
		qu[i]={i,l,r};
	}
	for(int i=1;i<=num;++i){
		st[i]=ed[i-1]+1;
		ed[i]=blk*i;
	}
	if(ed[num]!=n){
		++num;
		st[num]=ed[num-1]+1;
		ed[num]=n;
	}
	for(int i=1;i<=num;++i){
		for(int l=st[i];l<=ed[i];++l){
			belong[l]=i;
		}
	}
	std::sort(qu+1,qu+1+m,[](ques x,ques y){
		if(belong[x.l]==belong[y.l]){
			return x.r>y.r;
		}
		return belong[x.l]<belong[y.l];
	});
	int l=1,r=n,lab=0;
	ll res1=0,res=0;
	t.build();
	for(int i=1;i<n;++i){
		res+=abs(val[i+1]-val[i]);
	}
	res1=res;
	for(int i=1;i<=m;++i){
		int ql=qu[i].l,qr=qu[i].r;
		if(lab!=belong[ql]){
			while(t.top) t.resume();r=n;
			while(l<st[belong[ql] ]) t.del(l++,res1);
			res=res1;lab=belong[ql];t.top=0;
		}
		while(r>qr) t.del(r--,res);
		int l2=l;ll res2=res;
		while(l2<ql) t.del(l2++,res2);
		ans[qu[i].id]=res2;
		while(l<l2) t.resume(),--l2;
	}
	for(int i=1;i<=m;++i){
		printf("%lld\n",ans[i]);
	}
	fprintf(stderr,"%dms\n",clock() );
	return 0;
}

posted @   cbdsopa  阅读(70)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示