回滚莫队捏

解释摆了大家都做数学了我还搁这里莫队()

写cpp里了

使用博客园阅读观感更佳


【模板】回滚莫队&不删除莫队


###题目链接


题目背景

这是一道模板题。

题目描述

给定一个序列,多次询问一段区间 [ l , r ] [l,r] [l,r],求区间中相同的数的最远间隔距离

序列中两个元素的间隔距离指的是两个元素下标差的绝对值

输入格式

第一行一个整数 n n n,表示序列长度。

第二行 n n n 个整数,描述这个序列。

第三行一个整数 m m m,表示询问个数。

之后 m m m 行,每行两个整数 l , r l,r l,r 表示询问区间。

输出格式

m m m 行,每行一个整数表示答案。如果区间内不存在两个数相同,则输出 0 0 0

样例 #1

样例输入 #1

8
1 6 2 2 3 3 1 6
5
1 4
2 5
2 8
5 6
1 7

样例输出 #1

1
1
6
1
6

提示

a i a_i ai 表示序列元素。

对于 40 % 40\% 40% 的数据,满足 1 ≤ a i ≤ 400 1\leq a_i \leq 400 1ai400 1 ≤ n , m ≤ 60000 1\leq n,m\leq 60000 1n,m60000

对于 100 % 100\% 100% 的数据,满足 1 ≤ n , m ≤ 2 ⋅ 1 0 5 1\leq n,m\leq 2\cdot 10^5 1n,m2105 1 ≤ a i ≤ 2 ⋅ 1 0 9 1\leq a_i\leq 2\cdot 10^9 1ai2109


完整代码

Miku's Code
#include<bits/stdc++.h>
using namespace std;
/*
回滚莫队(×)
能不删不删莫队(√)
我们的普通莫队,删除操作就要更改答案。
但是回滚莫队的思想就是让我们的删除操作更少。
那么如何实现?
我们的普通莫队l\r指针反复横跳
那么我们的回滚莫队就是不让它瞎跳:
我们找到几个询问作为一组,在这一组询问里找一个公共线段,使这个公共线段的l\r不断移动达到与查询区间左右端点重合的目的
而l\r的移动,我们让它有序,也就是说让它的这组询问l/r不回撤
如何处理?
我们设左端点在一个块内所有的询问构成一组询问。
莫队排序后一组询问的右端点有序、
可知左端点是无序的,但是他们在一个分块里。
我们使l停留在这组询问左端点块的最右方,r不断向右移动处理询问。
对于每个询问的左端点定义一个临时指针p和一个临时的数组to2从L开始向左处理询问,L不变,p查完回撤,清空to2
而下一组我们直接清空数组重新设定l/r即可 
*/ 
const int maxn=2e5+50;

int n,m,sqn,temp[maxn];						//temp是离散化后的x序列 
int bl[maxn],L[maxn],R[maxn];				//分块
int ans[maxn];								//离线答案 
int st[maxn],to1[maxn],to2[maxn];
/*
在一定范围内:
st表示元素出现的第一个位置
to1表示元素出现最后一个位置
to2表示每组询问的公共起点(某个块的右端)前的一段中,元素出现的最后一个位置
*/
struct lsh{
	int id,x;
};lsh s[maxn];	//序列 
struct MO{
	int l,r,id;
};MO q[maxn];	//查询 

bool comp(MO m1,MO m2){
	if(bl[m1.l]==bl[m2.l]){
		return m1.r<m2.r;
	}
	return m1.l<m2.l;
}

bool scomp(lsh l1,lsh l2){		//离散化使用 
	return l1.x<l2.x;
}

void input(){
	scanf("%d",&n);
	sqn=sqrt(n);
	for(int i=1;i<=n;++i){
		scanf("%d",&s[i].x);
		s[i].id=i;
		bl[i]=(i-1)/sqn+1;
		if(bl[i]!=bl[i-1])	L[bl[i]]=i,R[bl[i-1]]=i-1;
	} 
	R[bl[n]]=n;
	scanf("%d",&m);
	for(int i=1;i<=m;++i){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
}

void pre(){						//离散化 
	sort(s+1,s+1+n,scomp);
	int pre=-1,cnt=0;
	for(int i=1;i<=n;++i){		//不知道结构体咋用lower_bound和unique所以摆了 
		if(s[i].x!=pre)	++cnt;
		temp[s[i].id]=cnt;
		pre=s[i].x;		
	}
}

void work(){
	int bll=0,ss=0,l=0,r=0;
	//bll表示上次询问左端点所在分块,ss是上次在L-R区间里查询到的答案
	//l是公共的,r是上次询问的 
	for(int i=1;i<=m;++i){
		if(bl[q[i].l]==bl[q[i].r]){		//在一个分块里暴力就好 
			ss=0;
			for(int j=q[i].l;j<=q[i].r;++j){
				st[temp[j]]=0;
			}
			for(int j=q[i].l;j<=q[i].r;++j){
				if(!st[temp[j]])	st[temp[j]]=j;
				ss=max(ss,j-st[temp[j]]);
			}
			for(int j=q[i].l;j<=q[i].r;++j){
				st[temp[j]]=0;
			}
			ans[q[i].id]=ss;
			continue;
		}
		if(bll!=bl[q[i].l]){
			//左端点块发生变化我们把之前求解的东西全都不要了。
			ss=0;
			for(int j=l;j<=r;++j)	st[temp[j]]=to1[temp[j]]=0;
			l=R[bl[q[i].l]];			//l变成现在的查询左端点区块的右端点 
			r=l-1;						//莫队初始化 
			bll=bl[q[i].l];				//更新分块 
		}
		while(r<q[i].r){				//计算右端点后一小节 
			++r;
			if(!st[temp[r]])	st[temp[r]]=r;
			to1[temp[r]]=r;				//元素最后出现的位置要一直更新啊 
			ss=max(ss,r-st[temp[r]]);
		}
		int p=l,ss2=0; 
		while(q[i].l<p){				//计算左端点前一小节 
			--p;
			if(!to2[temp[p]])	to2[temp[p]]=p;
			//我们从后向前开始移动,最后出现的位置只需要更新一次哦 
			ss2=max(ss2,max(to1[temp[p]],to2[temp[p]])-p);
			//为什么要取max?因为我们的固定的左端点右边也可能有这个元素啊 
		}
		while(p<l){						//回撤 
			to2[temp[p]]=0;
			++p; 
		}
		ans[q[i].id]=max(ss2,ss);
	} 
} 

int main(){
	input();
	sort(q+1,q+1+m,comp);
	pre();
	work();
	for(int i=1;i<=m;++i){
		printf("%d\n",ans[i]);
	}
	return 0;
} 

相似题目:


permu


题目链接


题目背景

[无]

题目描述

给出一个长度为 n n n的排列 P ( P 1 , P 2 , . . . P n ) P(P1,P2,...Pn) P(P1,P2,...Pn),以及 m m m个询问。每次询问某个区间 [ l , r ] [l,r] [l,r]中,最长的值域 连续段长度。

输入格式

第一行两个整数 n , m n,m n,m

接下来一行 n n n个整数,描述 P P P

接下来 m m m行,每行两个整数 l , r l,r l,r,描述一组询问。

输出格式

对于每组询问,输出一行一个整数,描述答案。

样例 #1

样例输入 #1

8 3
3 1 7 2 5 8 6 4
1 4
5 8
1 7

样例输出 #1

3
3
4

提示

对于询问 [ 1 , 4 ] [1,4] [1,4] P 2 , P 4 , P 1 P2,P4,P1 P2,P4,P1组成最长的值域连续段 [ 1 , 3 ] [1,3] [1,3]

对于询问 [ 5 , 8 ] [5,8] [5,8] P 8 , P 5 , P 7 P8,P5,P7 P8,P5,P7组成最长的值域连续段 [ 4 , 6 ] [4,6] [4,6]

对于询问 [ 1 , 7 ] [1,7] [1,7] P 5 , P 7 , P 3 , P 6 P5,P7,P3,P6 P5,P7,P3,P6组成最长的值域连续段 [ 5 , 8 ] [5,8] [5,8]

1 < = n , m < = 50000 1<=n,m<=50000 1<=n,m<=50000


完整代码

来点仿照上一道题的WA0
#include<bits/stdc++.h>
#include<bits/stdc++.h>
using namespace std;


const int maxn=2e5+50;

int n,m,sqn,a[maxn]; 
int bl[maxn],L[maxn],R[maxn];
int ans[maxn];
int lb[maxn],rb[maxn],slb[maxn],srb[maxn];
//lb[v]表示权值v左侧的连续段个数,rb[v]表示右侧的连续段个数
//只有增加值落在在段边界上的时候更新答案 
struct MO{
	int l,r,id;
};MO q[maxn];	//查询 

bool comp(MO m1,MO m2){
	if(bl[m1.l]==bl[m2.l]){
		return m1.r<m2.r;
	}
	return m1.l<m2.l;
}

void input(){
	scanf("%d%d",&n,&m);
	sqn=sqrt(n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		bl[i]=(i-1)/sqn+1;
		if(bl[i]!=bl[i-1])	L[bl[i]]=i,R[bl[i-1]]=i-1;
	} 
	R[bl[n]]=n;
	for(int i=1;i<=m;++i){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
}

void work(){
	int bll=0,ss=0,l=0,r=0;
	//bll表示上次询问左端点所在分块,ss是上次在L-R区间里查询到的答案
	//l是公共的,r是上次询问的 
	for(int i=1;i<=m;++i){
		if(bl[q[i].l]==bl[q[i].r]){		//在一个分块里暴力就好 
			ss=0;
			for(int j=q[i].l;j<=q[i].r;++j){
				lb[a[j]]=rb[a[j]]=0;
			}
			for(int j=q[i].l;j<=q[i].r;++j){
				lb[a[j]]=lb[a[j]-1]+1;
				rb[a[j]]=rb[a[j]+1]-1;
				int tmp=lb[a[j]]+rb[a[j]]-1;
				ss=max(ss,tmp);
				lb[a[j]+rb[a[j]]-1]=tmp;
				rb[a[j]-lb[a[j]]+1]=tmp;
			}
			for(int j=q[i].l;j<=q[i].r;++j){
				lb[a[j]]=rb[a[j]]=0;
			}
			ans[q[i].id]=ss;
			continue;
		}
		if(bll!=bl[q[i].l]){
			//cout<<"###"<<i<<endl; 
			ss=0;
			for(int j=l;j<=r;++j){
				lb[a[j]]=rb[a[j]]=0;
			}
			l=R[bl[q[i].l]];
			r=l-1;
			bll=bl[q[i].l];
		}
		while(r<q[i].r){				//计算右端点后一小节 
			++r;
			lb[a[r]]=lb[a[r]-1]+1;
			rb[a[r]]=rb[a[r]+1]+1;
			int tmp=lb[a[r]]+rb[a[r]]-1;
			ss=max(tmp,ss);
			lb[a[r]+rb[a[r]]-1]=tmp;
			rb[a[r]-lb[a[r]]+1]=tmp; 
		}
		int p=l,ss2=ss;
		if(q[i].l<p){
			for(int j=l;j<=r;++j){
				slb[a[j]]=lb[a[j]];
				srb[a[j]]=rb[a[j]];
			}
		}
		while(q[i].l<p){				//计算左端点前一小节 
			--p;
			slb[a[p]]=slb[a[p]-1]+1;
			srb[a[p]]=srb[a[p]+1]+1;
			int tmp=slb[a[p]]+srb[a[p]]-1;
			ss2=max(ss2,tmp);
			slb[a[p]+rb[a[p]]-1]=tmp;
			srb[a[p]-lb[a[p]]-1]=tmp;
		}
		for(int j=p;j<=r;++j){			//回撤 
			slb[a[j]]=srb[a[j]]=0;
		}
		p=l; 
		ans[q[i].id]=max(ss2,ss);
	} 
} 

int main(){
	input();
	sort(q+1,q+1+m,comp);
	work();
	for(int i=1;i<=m;++i){
		printf("%d\n",ans[i]);
	}
	return 0;
} 
//可以看到这一道题又T又WA,F了
//真不会改,就算WA能该对T怎么改啊aaaaa

Miku's Code
#include<bits/stdc++.h>
using namespace std;


const int maxn=5e4+50;

int n,m,sqn,a[maxn]; 
int bl[maxn],L[maxn],R[maxn];
int ans[maxn];
int lb[maxn],rb[maxn],slb[maxn],srb[maxn];
//lb[v]表示权值v左侧的连续段个数,rb[v]表示右侧的连续段个数
//只有增加值落在在段边界上的时候更新答案 
struct MO{
	int l,r,id;
};MO q[maxn];	//查询 
struct SAVE{
	int type;	//记录修改了哪个数组,1=lb,2=rb
	int pos,val;
};SAVE save[maxn];

bool comp(MO m1,MO m2){
	if(bl[m1.l]==bl[m2.l]){
		return m1.r<m2.r;
	}
	return m1.l<m2.l;
}

void input(){
	scanf("%d%d",&n,&m);
	sqn=sqrt(n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		bl[i]=(i-1)/sqn+1;
		if(bl[i]!=bl[i-1])	L[bl[i]]=i,R[bl[i-1]]=i-1;
	} 
	R[bl[n]]=n;
	for(int i=1;i<=m;++i){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
}

void work(){
	int bll=0,ss=0,r=0,ss2=0;
	//bll表示上次询问左端点所在分块,ss是上次在L-R区间里查询到的答案
	//l是公共的,r是上次询问的 
	for(int i=1;i<=m;++i){
		if(bll!=bl[q[i].l]){
			ss=0;
			for(int j=1;j<=n;++j){
				lb[a[j]]=rb[a[j]]=0;
			}
			//l=R[bl[q[i].l]];
			r=bl[q[i].l]*sqn;
			bll=bl[q[i].l];
		}
		while(r<q[i].r){				//计算右端点后一小节 
			++r;
			lb[a[r]]=lb[a[r]-1]+1;
			rb[a[r]]=rb[a[r]+1]+1;
			int tmp=lb[a[r]]+rb[a[r]]-1;
			ss=max(tmp,ss);
			lb[a[r]+rb[a[r]]-1]=tmp;
			rb[a[r]-lb[a[r]]+1]=tmp; 
		}
		ss2=ss;							//还是不对ss直接修改
		int p=0;
		for(int j=q[i].l;j<=min(q[i].r,R[bl[q[i].l]]);++j){
			lb[a[j]]=lb[a[j]-1]+1;
			rb[a[j]]=rb[a[j]+1]+1;
			int tmp=lb[a[j]]+rb[a[j]]-1;
			save[++p].type=1; save[p].pos=a[j]+rb[a[j]]-1; save[p].val=lb[a[j]+rb[a[j]]-1];
			save[++p].type=2; save[p].pos=a[j]-lb[a[j]]+1; save[p].val=rb[a[j]-lb[a[j]]+1];
			//记录修改之前的值 
			lb[a[j]+rb[a[j]]-1]=tmp;
			rb[a[j]-lb[a[j]]+1]=tmp;
			ss2=max(ss2,tmp);
		}
		for(int j=p;j>=1;--j){								//回撤 
			if(save[j].type==1)	lb[save[j].pos]=save[j].val;
			else	rb[save[j].pos]=save[j].val;
		}
		for(int j=q[i].l;j<=min(q[i].r,R[bl[q[i].l]]);++j){	//回撤 
			lb[a[j]]=rb[a[j]]=0;
		}
		ans[q[i].id]=ss2;
	}
} 

int main(){
	input();
	sort(q+1,q+1+m,comp);
	work();
	for(int i=1;i<=m;++i){
		printf("%d\n",ans[i]);
	}
	return 0;
} 
@[toc] # 解释摆了大家都做数学了我还搁这里莫队() 写cpp里了

【模板】回滚莫队&不删除莫队


###题目链接


题目背景

这是一道模板题。

题目描述

给定一个序列,多次询问一段区间 [ l , r ] [l,r] [l,r],求区间中相同的数的最远间隔距离

序列中两个元素的间隔距离指的是两个元素下标差的绝对值

输入格式

第一行一个整数 n n n,表示序列长度。

第二行 n n n 个整数,描述这个序列。

第三行一个整数 m m m,表示询问个数。

之后 m m m 行,每行两个整数 l , r l,r l,r 表示询问区间。

输出格式

m m m 行,每行一个整数表示答案。如果区间内不存在两个数相同,则输出 0 0 0

样例 #1

样例输入 #1

8
1 6 2 2 3 3 1 6
5
1 4
2 5
2 8
5 6
1 7

样例输出 #1

1
1
6
1
6

提示

a i a_i ai 表示序列元素。

对于 40 % 40\% 40% 的数据,满足 1 ≤ a i ≤ 400 1\leq a_i \leq 400 1ai400 1 ≤ n , m ≤ 60000 1\leq n,m\leq 60000 1n,m60000

对于 100 % 100\% 100% 的数据,满足 1 ≤ n , m ≤ 2 ⋅ 1 0 5 1\leq n,m\leq 2\cdot 10^5 1n,m2105 1 ≤ a i ≤ 2 ⋅ 1 0 9 1\leq a_i\leq 2\cdot 10^9 1ai2109


完整代码

Miku's Code
#include<bits/stdc++.h>
using namespace std;
/*
回滚莫队(×)
能不删不删莫队(√)
我们的普通莫队,删除操作就要更改答案。
但是回滚莫队的思想就是让我们的删除操作更少。
那么如何实现?
我们的普通莫队l\r指针反复横跳
那么我们的回滚莫队就是不让它瞎跳:
我们找到几个询问作为一组,在这一组询问里找一个公共线段,使这个公共线段的l\r不断移动达到与查询区间左右端点重合的目的
而l\r的移动,我们让它有序,也就是说让它的这组询问l/r不回撤
如何处理?
我们设左端点在一个块内所有的询问构成一组询问。
莫队排序后一组询问的右端点有序、
可知左端点是无序的,但是他们在一个分块里。
我们使l停留在这组询问左端点块的最右方,r不断向右移动处理询问。
对于每个询问的左端点定义一个临时指针p和一个临时的数组to2从L开始向左处理询问,L不变,p查完回撤,清空to2
而下一组我们直接清空数组重新设定l/r即可 
*/ 
const int maxn=2e5+50;

int n,m,sqn,temp[maxn];						//temp是离散化后的x序列 
int bl[maxn],L[maxn],R[maxn];				//分块
int ans[maxn];								//离线答案 
int st[maxn],to1[maxn],to2[maxn];
/*
在一定范围内:
st表示元素出现的第一个位置
to1表示元素出现最后一个位置
to2表示每组询问的公共起点(某个块的右端)前的一段中,元素出现的最后一个位置
*/
struct lsh{
	int id,x;
};lsh s[maxn];	//序列 
struct MO{
	int l,r,id;
};MO q[maxn];	//查询 

bool comp(MO m1,MO m2){
	if(bl[m1.l]==bl[m2.l]){
		return m1.r<m2.r;
	}
	return m1.l<m2.l;
}

bool scomp(lsh l1,lsh l2){		//离散化使用 
	return l1.x<l2.x;
}

void input(){
	scanf("%d",&n);
	sqn=sqrt(n);
	for(int i=1;i<=n;++i){
		scanf("%d",&s[i].x);
		s[i].id=i;
		bl[i]=(i-1)/sqn+1;
		if(bl[i]!=bl[i-1])	L[bl[i]]=i,R[bl[i-1]]=i-1;
	} 
	R[bl[n]]=n;
	scanf("%d",&m);
	for(int i=1;i<=m;++i){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
}

void pre(){						//离散化 
	sort(s+1,s+1+n,scomp);
	int pre=-1,cnt=0;
	for(int i=1;i<=n;++i){		//不知道结构体咋用lower_bound和unique所以摆了 
		if(s[i].x!=pre)	++cnt;
		temp[s[i].id]=cnt;
		pre=s[i].x;		
	}
}

void work(){
	int bll=0,ss=0,l=0,r=0;
	//bll表示上次询问左端点所在分块,ss是上次在L-R区间里查询到的答案
	//l是公共的,r是上次询问的 
	for(int i=1;i<=m;++i){
		if(bl[q[i].l]==bl[q[i].r]){		//在一个分块里暴力就好 
			ss=0;
			for(int j=q[i].l;j<=q[i].r;++j){
				st[temp[j]]=0;
			}
			for(int j=q[i].l;j<=q[i].r;++j){
				if(!st[temp[j]])	st[temp[j]]=j;
				ss=max(ss,j-st[temp[j]]);
			}
			for(int j=q[i].l;j<=q[i].r;++j){
				st[temp[j]]=0;
			}
			ans[q[i].id]=ss;
			continue;
		}
		if(bll!=bl[q[i].l]){
			//左端点块发生变化我们把之前求解的东西全都不要了。
			ss=0;
			for(int j=l;j<=r;++j)	st[temp[j]]=to1[temp[j]]=0;
			l=R[bl[q[i].l]];			//l变成现在的查询左端点区块的右端点 
			r=l-1;						//莫队初始化 
			bll=bl[q[i].l];				//更新分块 
		}
		while(r<q[i].r){				//计算右端点后一小节 
			++r;
			if(!st[temp[r]])	st[temp[r]]=r;
			to1[temp[r]]=r;				//元素最后出现的位置要一直更新啊 
			ss=max(ss,r-st[temp[r]]);
		}
		int p=l,ss2=0; 
		while(q[i].l<p){				//计算左端点前一小节 
			--p;
			if(!to2[temp[p]])	to2[temp[p]]=p;
			//我们从后向前开始移动,最后出现的位置只需要更新一次哦 
			ss2=max(ss2,max(to1[temp[p]],to2[temp[p]])-p);
			//为什么要取max?因为我们的固定的左端点右边也可能有这个元素啊 
		}
		while(p<l){						//回撤 
			to2[temp[p]]=0;
			++p; 
		}
		ans[q[i].id]=max(ss2,ss);
	} 
} 

int main(){
	input();
	sort(q+1,q+1+m,comp);
	pre();
	work();
	for(int i=1;i<=m;++i){
		printf("%d\n",ans[i]);
	}
	return 0;
} 

相似题目:


permu


题目链接


题目背景

[无]

题目描述

给出一个长度为 n n n的排列 P ( P 1 , P 2 , . . . P n ) P(P1,P2,...Pn) P(P1,P2,...Pn),以及 m m m个询问。每次询问某个区间 [ l , r ] [l,r] [l,r]中,最长的值域 连续段长度。

输入格式

第一行两个整数 n , m n,m n,m

接下来一行 n n n个整数,描述 P P P

接下来 m m m行,每行两个整数 l , r l,r l,r,描述一组询问。

输出格式

对于每组询问,输出一行一个整数,描述答案。

样例 #1

样例输入 #1

8 3
3 1 7 2 5 8 6 4
1 4
5 8
1 7

样例输出 #1

3
3
4

提示

对于询问 [ 1 , 4 ] [1,4] [1,4] P 2 , P 4 , P 1 P2,P4,P1 P2,P4,P1组成最长的值域连续段 [ 1 , 3 ] [1,3] [1,3]

对于询问 [ 5 , 8 ] [5,8] [5,8] P 8 , P 5 , P 7 P8,P5,P7 P8,P5,P7组成最长的值域连续段 [ 4 , 6 ] [4,6] [4,6]

对于询问 [ 1 , 7 ] [1,7] [1,7] P 5 , P 7 , P 3 , P 6 P5,P7,P3,P6 P5,P7,P3,P6组成最长的值域连续段 [ 5 , 8 ] [5,8] [5,8]

1 < = n , m < = 50000 1<=n,m<=50000 1<=n,m<=50000


完整代码

来点仿照上一道题的WA0
#include<bits/stdc++.h>
#include<bits/stdc++.h>
using namespace std;


const int maxn=2e5+50;

int n,m,sqn,a[maxn]; 
int bl[maxn],L[maxn],R[maxn];
int ans[maxn];
int lb[maxn],rb[maxn],slb[maxn],srb[maxn];
//lb[v]表示权值v左侧的连续段个数,rb[v]表示右侧的连续段个数
//只有增加值落在在段边界上的时候更新答案 
struct MO{
	int l,r,id;
};MO q[maxn];	//查询 

bool comp(MO m1,MO m2){
	if(bl[m1.l]==bl[m2.l]){
		return m1.r<m2.r;
	}
	return m1.l<m2.l;
}

void input(){
	scanf("%d%d",&n,&m);
	sqn=sqrt(n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		bl[i]=(i-1)/sqn+1;
		if(bl[i]!=bl[i-1])	L[bl[i]]=i,R[bl[i-1]]=i-1;
	} 
	R[bl[n]]=n;
	for(int i=1;i<=m;++i){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
}

void work(){
	int bll=0,ss=0,l=0,r=0;
	//bll表示上次询问左端点所在分块,ss是上次在L-R区间里查询到的答案
	//l是公共的,r是上次询问的 
	for(int i=1;i<=m;++i){
		if(bl[q[i].l]==bl[q[i].r]){		//在一个分块里暴力就好 
			ss=0;
			for(int j=q[i].l;j<=q[i].r;++j){
				lb[a[j]]=rb[a[j]]=0;
			}
			for(int j=q[i].l;j<=q[i].r;++j){
				lb[a[j]]=lb[a[j]-1]+1;
				rb[a[j]]=rb[a[j]+1]-1;
				int tmp=lb[a[j]]+rb[a[j]]-1;
				ss=max(ss,tmp);
				lb[a[j]+rb[a[j]]-1]=tmp;
				rb[a[j]-lb[a[j]]+1]=tmp;
			}
			for(int j=q[i].l;j<=q[i].r;++j){
				lb[a[j]]=rb[a[j]]=0;
			}
			ans[q[i].id]=ss;
			continue;
		}
		if(bll!=bl[q[i].l]){
			//cout<<"###"<<i<<endl; 
			ss=0;
			for(int j=l;j<=r;++j){
				lb[a[j]]=rb[a[j]]=0;
			}
			l=R[bl[q[i].l]];
			r=l-1;
			bll=bl[q[i].l];
		}
		while(r<q[i].r){				//计算右端点后一小节 
			++r;
			lb[a[r]]=lb[a[r]-1]+1;
			rb[a[r]]=rb[a[r]+1]+1;
			int tmp=lb[a[r]]+rb[a[r]]-1;
			ss=max(tmp,ss);
			lb[a[r]+rb[a[r]]-1]=tmp;
			rb[a[r]-lb[a[r]]+1]=tmp; 
		}
		int p=l,ss2=ss;
		if(q[i].l<p){
			for(int j=l;j<=r;++j){
				slb[a[j]]=lb[a[j]];
				srb[a[j]]=rb[a[j]];
			}
		}
		while(q[i].l<p){				//计算左端点前一小节 
			--p;
			slb[a[p]]=slb[a[p]-1]+1;
			srb[a[p]]=srb[a[p]+1]+1;
			int tmp=slb[a[p]]+srb[a[p]]-1;
			ss2=max(ss2,tmp);
			slb[a[p]+rb[a[p]]-1]=tmp;
			srb[a[p]-lb[a[p]]-1]=tmp;
		}
		for(int j=p;j<=r;++j){			//回撤 
			slb[a[j]]=srb[a[j]]=0;
		}
		p=l; 
		ans[q[i].id]=max(ss2,ss);
	} 
} 

int main(){
	input();
	sort(q+1,q+1+m,comp);
	work();
	for(int i=1;i<=m;++i){
		printf("%d\n",ans[i]);
	}
	return 0;
} 
//可以看到这一道题又T又WA,F了
//真不会改,就算WA能该对T怎么改啊aaaaa

Miku's Code
#include<bits/stdc++.h>
using namespace std;


const int maxn=5e4+50;

int n,m,sqn,a[maxn]; 
int bl[maxn],L[maxn],R[maxn];
int ans[maxn];
int lb[maxn],rb[maxn],slb[maxn],srb[maxn];
//lb[v]表示权值v左侧的连续段个数,rb[v]表示右侧的连续段个数
//只有增加值落在在段边界上的时候更新答案 
struct MO{
	int l,r,id;
};MO q[maxn];	//查询 
struct SAVE{
	int type;	//记录修改了哪个数组,1=lb,2=rb
	int pos,val;
};SAVE save[maxn];

bool comp(MO m1,MO m2){
	if(bl[m1.l]==bl[m2.l]){
		return m1.r<m2.r;
	}
	return m1.l<m2.l;
}

void input(){
	scanf("%d%d",&n,&m);
	sqn=sqrt(n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
		bl[i]=(i-1)/sqn+1;
		if(bl[i]!=bl[i-1])	L[bl[i]]=i,R[bl[i-1]]=i-1;
	} 
	R[bl[n]]=n;
	for(int i=1;i<=m;++i){
		scanf("%d%d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
}

void work(){
	int bll=0,ss=0,r=0,ss2=0;
	//bll表示上次询问左端点所在分块,ss是上次在L-R区间里查询到的答案
	//l是公共的,r是上次询问的 
	for(int i=1;i<=m;++i){
		if(bll!=bl[q[i].l]){
			ss=0;
			for(int j=1;j<=n;++j){
				lb[a[j]]=rb[a[j]]=0;
			}
			//l=R[bl[q[i].l]];
			r=bl[q[i].l]*sqn;
			bll=bl[q[i].l];
		}
		while(r<q[i].r){				//计算右端点后一小节 
			++r;
			lb[a[r]]=lb[a[r]-1]+1;
			rb[a[r]]=rb[a[r]+1]+1;
			int tmp=lb[a[r]]+rb[a[r]]-1;
			ss=max(tmp,ss);
			lb[a[r]+rb[a[r]]-1]=tmp;
			rb[a[r]-lb[a[r]]+1]=tmp; 
		}
		ss2=ss;							//还是不对ss直接修改
		int p=0;
		for(int j=q[i].l;j<=min(q[i].r,R[bl[q[i].l]]);++j){
			lb[a[j]]=lb[a[j]-1]+1;
			rb[a[j]]=rb[a[j]+1]+1;
			int tmp=lb[a[j]]+rb[a[j]]-1;
			save[++p].type=1; save[p].pos=a[j]+rb[a[j]]-1; save[p].val=lb[a[j]+rb[a[j]]-1];
			save[++p].type=2; save[p].pos=a[j]-lb[a[j]]+1; save[p].val=rb[a[j]-lb[a[j]]+1];
			//记录修改之前的值 
			lb[a[j]+rb[a[j]]-1]=tmp;
			rb[a[j]-lb[a[j]]+1]=tmp;
			ss2=max(ss2,tmp);
		}
		for(int j=p;j>=1;--j){								//回撤 
			if(save[j].type==1)	lb[save[j].pos]=save[j].val;
			else	rb[save[j].pos]=save[j].val;
		}
		for(int j=q[i].l;j<=min(q[i].r,R[bl[q[i].l]]);++j){	//回撤 
			lb[a[j]]=rb[a[j]]=0;
		}
		ans[q[i].id]=ss2;
	}
} 

int main(){
	input();
	sort(q+1,q+1+m,comp);
	work();
	for(int i=1;i<=m;++i){
		printf("%d\n",ans[i]);
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值