problem
数据范围:
n
,
q
≤
2
×
1
0
5
n,q\le 2\times 10^5
n,q≤2×105,
0
≤
a
i
≤
2
×
1
0
5
0\le a_i\le 2\times 10^5
0≤ai≤2×105。
solution
这道题删点很好做,每次删点之后只有它本身可能成为 m e x mex mex,但是加点不太好做。
只减不加的回滚莫队:
- 对原序列进行分块,以左端点所在的块升序为第一关键字,以右端点降序为第二关键字来排序。
- 对于处理所有左端点在块 T T T 内的询问,我们先将莫队区间左端点初始化为 L [ T ] L[T] L[T],右端点初始化为 n n n,这是一个大区间。
- 对于左右端点在同一个块中的询问,我们直接暴力扫描回答即可。
- 对于左右端点不在同一个块中的所有询问,由于其右端点降序,从 n n n 的位置开始,我们对右端点只做删点操作,总共最多删点 n n n 次。
- 对于左右端点不在同一个块中的所有询问,其左端点是可能乱序的,我们每一次从 L [ T ] L[T] L[T] 的位置出发,只做删点操作,到达询问位置即可,每一个询问最多加 n \sqrt n n 次。回答完询问后,我们撤销本次移动左端点的所有改动,使左端点回到 L [ T ] L[T] L[T] 的位置。
- 按照相同的方式处理下一块。
时间复杂度 O ( n n ) O(n\sqrt n) O(nn)。
code
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
int n,m,S,sum,now;
int a[N],in[N],ans[N],cnt[N],num[N];
struct Query{int l,r,id;}Q[N];
bool operator<(const Query &p,const Query &q){
return in[p.l]==in[q.l]?p.r>q.r:p.l<q.l;
}
int init(int l,int r){
memset(cnt,0,sizeof(cnt));
for(int i=l;i<=r;++i) ++cnt[a[i]];
for(int i=0;;++i) if(!cnt[i]) return i;
}
int Get(int l,int r){
memset(num,0,sizeof(num));
for(int i=l;i<=r;++i) ++num[a[i]];
for(int i=0;;++i) if(!num[i]) return i;
}
void Del(int pos){
cnt[a[pos]]--;
if(!cnt[a[pos]]) now=min(now,a[pos]);
}
int solve(int i,int id){
int lim=(id-1)*S+1,L=lim,R=n;
now=init(L,R);
for(;in[Q[i].l]==id;++i){
if(in[Q[i].l]==in[Q[i].r]){
ans[Q[i].id]=Get(Q[i].l,Q[i].r);
continue;
}
while(R>Q[i].r) Del(R--);
int tmp=now;
while(L<Q[i].l) Del(L++);
ans[Q[i].id]=now;
while(L>lim) cnt[a[--L]]++;
now=tmp;
}
return i;
}
int main(){
scanf("%d%d",&n,&m),S=sqrt(n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]),in[i]=(i-1)/S+1;
}
sum=in[n];
for(int i=1;i<=m;++i){
scanf("%d%d",&Q[i].l,&Q[i].r),Q[i].id=i;
}
sort(Q+1,Q+m+1);
for(int i=1,id=1;id<=sum;++id) i=solve(i,id);
for(int i=1;i<=m;++i) printf("%d\n",ans[i]);
return 0;
}