another莫队之回滚莫队

本文介绍了回滚莫队算法,包括其基本思想和实现细节,并提供了两个应用实例:LOJ#6285和BZOJ4241。在LOJ问题中,我们寻找区间最小众数;在BZOJ问题中,计算事件种类的最大重要度。通过以块编号和右端点位置排序,回滚莫队能保证n√n的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

就找了两三道题,不过回滚莫队思路也好理解,代码实现也不难:

1、我们以块编号为第一关键字排序,右端点位置为第二关键字排序

2、询问时依次枚举区间,我们保留右端点的移量(右边单增),左端点则每次在这一个块中来回移动

3、下一个块时,清空统计答案重做

所以对于每一个块:左端点每次操作√n,右端点总共移n,均摊√n,因此时间复杂度保证了n√n

1)loj#6285

题目描述

给出一个长为 n 的数列,以及 n 个操作,操作涉及询问区间的最小众数。

输入格式

第一行输入一个数字 n。

第二行输入 n个数字,第 i 个数字为 ai,以空格隔开。

接下来输入 n 行询问,每行输入两个数字 l、r以空格隔开。

表示查询位于[l,r]的数字的众数。

输出格式

对于每次询问,输出一行一个数字表示答案。

样例

样例输入

4

1 2 2 4

1 2

1 4

2 4

3 4

样例输出

1

2

2

2

#include<bits/stdc++.h>
using namespace std;
#define Inc(i,L,r) for(register int i=(L);i<=(r);++i)
const int N = 1e5;
int n,siz,k[N+10],bl[N+10];
struct Que{
	int L,r,idx;
}q[N+10];
bool cmp(const Que&A,const Que&B){
	return bl[A.L]^bl[B.L]?A.L<B.L:A.r<B.r;
}
inline void init(){
	scanf("%d",&n);
	Inc(i,1,n)scanf("%d",k+i);
	Inc(i,1,n){
		int x,y;scanf("%d%d",&x,&y);
		q[i]=(Que){x,y,i};
	}
	siz=sqrt(n);
	Inc(i,1,n)bl[i]=(i-1)/siz+1;
	sort(q+1,q+1+n,cmp);
}
int rp[N+10];
inline void disc(){
	int tmp[N+10];
	Inc(i,1,n)tmp[i]=k[i];
	sort(tmp+1,tmp+1+n);
	int len=unique(tmp+1,tmp+1+n)-tmp-1;
	Inc(i,1,n)rp[i]=lower_bound(tmp+1,tmp+1+len,k[i])-tmp;
}
int cur_ans,cur_num,Ans[N+10],num[N+10];
inline void addr(int x){
	++num[rp[x]];
	if(num[rp[x]]>=cur_num){
		if(num[rp[x]]==cur_num&&k[x]<cur_ans)cur_ans=k[x];
		else if(num[rp[x]]>cur_num)cur_ans=k[x],cur_num=num[rp[x]];
	}
}
inline void addl(int x,int &ans,int &Num){
	++num[rp[x]];
	if(num[rp[x]]>=Num){
		if(num[rp[x]]==Num&&k[x]<ans)ans=k[x];
		else if(num[rp[x]]>Num)ans=k[x],Num=num[rp[x]];
	}
}
inline void remove(int x){
	--num[rp[x]];
}
inline void solv(){
	int L,r,lim;
	Inc(i,1,n){
		if(bl[q[i].L]^bl[q[i-1].L]){//下一个块,初始化 
			memset(num,0,sizeof(num));
			L=lim=bl[q[i].L]*siz+1;
			r=bl[q[i].L]*siz;
			cur_ans=cur_num=0;
		}
		if(bl[q[i].L]==bl[q[i].r]){//不用移动r,直接暴力扫√n ,也是分块散块的处理思想呢 
			int ans,nownum=0;
			Inc(j,q[i].L,q[i].r)++num[rp[j]];
			Inc(j,q[i].L,q[i].r)if(num[rp[j]]>=nownum){
				if(num[rp[j]]==nownum&&k[j]<ans)ans=k[j];
				else if(num[rp[j]]>nownum)ans=k[j],nownum=num[rp[j]];
			}
			Inc(j,q[i].L,q[i].r)--num[rp[j]];
			Ans[q[i].idx]=ans;
			continue;
		}
		while(r<q[i].r)addr(++r);
		int now=cur_ans,nownum=cur_num;//值得注意的是我们不能让左边影响右边已经统计好的答案 
		while(L>q[i].L)addl(--L,now,nownum);
		Ans[q[i].idx]=now;
		while(L<lim)remove(L++);
	}
	Inc(i,1,n)cout<<Ans[i]<<"\n";
}
int main(){
	init();
	disc();
	solv();
	return 0;
} 

2)历史研究 #BZOJ4241

Description

IOI国历史研究的第一人——JOI教授,最近获得了一份被认为是古代IOI国的住民写下的日记。JOI教授为了通过这份日记来研究古代IOI国的生活,开始着手调查日记中记载的事件。
  日记中记录了连续N天发生的时间,大约每天发生一件。
  事件有种类之分。第i天(1<=i<=N)发生的事件的种类用一个整数Xi表示,Xi越大,事件的规模就越大。
JOI教授决定用如下的方法分析这些日记:
1.选择日记中连续的一些天作为分析的时间段
2.事件种类t的重要度为t*(这段时间内重要度为t的事件数)
3.计算出所有事件种类的重要度,输出其中的最大值
  现在你被要求制作一个帮助教授分析的程序,每次给出分析的区间,你需要输出重要度的最大值。

Input

  第一行两个空格分隔的整数N和Q,表示日记一共记录了N天,询问有Q次。
  接下来一行N个空格分隔的整数X1...XN,Xi表示第i天发生的事件的种类
  接下来Q行,第i行(1<=i<=Q)有两个空格分隔整数Ai和Bi,表示第i次询问的区间为[Ai,Bi]。

Output

  输出Q行,第i行(1<=i<=Q)一个整数,表示第i次询问的最大重要度

Sample Input

5 5

9 8 7 8 9

1 2

3 4

4 4

1 4

2 4

Sample Output

9

8

8

16

16

Hint

1<=N<=10^5
1<=Q<=10^5
1<=Xi<=10^9 (1<=i<=N)

 

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define N 100010
#define Inc(i,L,r) for(register int i=(L);i<=(r);++i)
#define Red(i,r,L) for(register int i=(r);i>=(L);--i)
int n,q,siz,v[N],rp[N],bl[N],imp[N];
struct Sec{
	int L,r,idx;
}a[N];
bool cmp(const Sec&A,const Sec&B){
	return bl[A.L]^bl[B.L]?A.L<B.L:A.r<B.r;
}
inline void disc(){
	int tmp[N];
	Inc(i,1,n)tmp[i]=v[i];
	sort(tmp+1,tmp+1+n);
	int len=unique(tmp+1,tmp+1+n)-tmp-1;
	Inc(i,1,n)rp[i]=lower_bound(tmp+1,tmp+1+len,v[i])-tmp;
}
inline void prep(){
	siz=sqrt(n);
	Inc(i,1,n)bl[i]=(i-1)/siz+1;
	Inc(i,1,q){
		int L,r;scanf("%lld%lld",&L,&r);
		a[i]=(Sec){L,r,i};
	}
//	cout<<q<<"\n";
	sort(a+1,a+1+q,cmp);
}
inline void init(){
	scanf("%lld%lld",&n,&q);
	Inc(i,1,n)scanf("%lld",v+i);
	disc();//离散化
	prep();//分块
}
int L,r,ans1,sum[N],ans[N];
int tmp[N];
inline void add(int x){
	sum[rp[x]]+=v[x];
}
inline void remove(int x){
	sum[rp[x]]-=v[x];
}
inline void Momo(){
	Inc(i,1,q){
		if(bl[a[i].L]^bl[a[i-1].L]){//下一个块 
			memset(sum,0,sizeof(sum));
			L=bl[a[i].L]*siz+1;
			r=bl[a[i].L]*siz;
			ans1=0;
		}
		if(bl[a[i].r]==bl[a[i].L]){//同一个块内,暴力处理根号n 
			int tmpans=0;
			Inc(j,a[i].L,a[i].r)tmp[rp[j]]+=v[j];
//			Inc(j,a[i].L,a[i].r)cout<<v[j]<<" ";cout<<"\n";
			Inc(j,a[i].L,a[i].r)tmpans=max(tmpans,tmp[rp[j]]);
			Inc(j,a[i].L,a[i].r)tmp[rp[j]]-=v[j];
			ans[a[i].idx]=tmpans;
			continue;
		}int ed=min(bl[a[i].L]*siz+1,n);
		while(r<a[i].r)add(++r),ans1=max(ans1,sum[rp[r]]);
		int las=ans1;
		while(L>a[i].L)add(--L),ans1=max(ans1,sum[rp[L]]);
		while(L<ed)remove(L++);
		ans[a[i].idx]=ans1;
		ans1=las;
	}
	Inc(i,1,q)cout<<ans[i]<<"\n";
}
main(){
//	freopen("yanjiu.in","r",stdin);
//	freopen("yanjiu.out","w",stdout);
	init();
	Momo();
}

3)Chef and Problems(from Code-Chef FNCS)

给定序列,求[l,r]区间内数字相同的数的最远距离。

题目传送门

#include<bits/stdc++.h>
using namespace std;
const int Maxn=100005;
int n,m,k,siz,a[Maxn],bl[Maxn];
int t[Maxn],mnl[Maxn],mxr[Maxn];
int ret,ans[Maxn];
struct seq{
	int l,r,idx;
}q[Maxn];
bool cmp(const seq&A,const seq&B){
	return bl[A.l]!=bl[B.l]?A.l<B.l:A.r<B.r;
}
void add(int x){
	mnl[a[x]]=min(mnl[a[x]],x);
	mxr[a[x]]=max(mxr[a[x]],x);
	ret=max(ret,mxr[a[x]]-mnl[a[x]]);
}
int main(){
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for(int i=1;i<=k;++i){
		int l,r;scanf("%d%d",&l,&r);
		q[i]=(seq){l,r,i};
	}
	siz=sqrt(n);
	for(int i=1;i<=n;++i)
		bl[i]=(i-1)/siz+1;
	sort(q+1,q+k+1,cmp);
	int l,r;
	for(int i=1;i<=k;++i){
		if(bl[q[i].l]!=bl[q[i-1].l]){
			memset(mxr,-63,sizeof(mxr));
			memset(mnl,63,sizeof(mnl));
			l=r=min(n,bl[q[i].l]*siz);
			ret=0;
		}
		if(bl[q[i].l]==bl[q[i].r]){
			int mx=0;
			for(int j=q[i].l;j<=q[i].r;++j)
				t[a[j]]=0x3f3f3f3f;			
			for(int j=q[i].l;j<=q[i].r;++j){
				t[a[j]]=min(t[a[j]],j);
				mx=max(mx,j-t[a[j]]);
			}
			ans[q[i].idx]=mx;
			continue;
		}
		while(r<q[i].r)add(++r);
		int tmp=0;
		for(int j=q[i].l;j<=l;++j)//手残写成j<=q[i].r= =半天没调出来 
			t[a[j]]=0x3f3f3f3f;		
		for(int j=q[i].l;j<=l;++j){
			t[a[j]]=min(t[a[j]],j);
			tmp=max(tmp,max(mxr[a[j]]-j,j-t[a[j]]));//注意后面可能没有更新过这个颜色 
		}
		ans[q[i].idx]=max(ret,tmp);
	}
	for(int i=1;i<=k;++i)
		cout<<ans[i]<<"\n";
	return 0;
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值