【冬季集训】Mex II
Time Limit:20000MS Memory Limit:123456K
Total Submit:64 Accepted:19
Case Time Limit:1000MS
Description
在SG定理中,对于一个由自然数组成的有限集合S,mex{S}定义为不在集合S中的最小自然数,例如mex{0,1,2}=3,mex{2,3,5}=0.
给你一个长度为N的非负整数序列{A1,A2, ... ,AN},定义mex(l,r)=mex{Al,Al+1, ... ,Ar}.
现有Q个询问,每次提问mex(l,r)的值.
Input
第一行两整数N,Q,
第二行N个整数,第i个数为Ai
接下来Q行每行两个数为一次提问。
Output
对每次提问输出一行,为这次提问的答案。
Sample Input
7 5
0 2 1 0 1 3 2
1 3
2 3
1 4
3 6
2 7
Sample Output
3
0
3
2
4
Hint
1<=N<=200000
1<=Q<=200000
0<=Ai<=200000
Source
这道题是一道异常恶心的题,据说可以用线段树来优化,但是能力有限,写了个不算暴力的暴力,然后加了个输入优化,勉勉强强能A
这道题类似于博客中前一道题HH的项链,都要用到离线操作且方式基本类似
我们要首先预处理两个东西,一个next[i]和一个mex[i]数组,其中mex[i]表示序列A中第1个数到第i个数的mex值,这个东西用小根堆可以轻松搞定,具体看下面代码
然后next数组就类似于链表,next[i]记录的是与a[i]数字相同的数字的下一个坐标
然后我们设mex(a,b)表示a,b区间的mex值,由于我们知道了mex(1,b),那么只要扔掉第一个元素我就可以知道mex(2,b),并以此类推
然后难点就难在怎么求扔了一个元素后某一个区间的mex
我们要注意到mex的值是单调不降的,我们要利用好这个性质。
考虑将mex(k,k),mex(k,k+1),mex(k,k+2),.......,mex(k,N)
这么一个序列通过什么操作可以变成mex(k+1,k+1),mex(k+1,k+2),mex(k+1,k+3),......,mex(k+1,N)这个序列。
我们发现,去掉A[k]之后,从k+1到next[k]-1之间的所有大于A[k]的数都变成了A[k].
由于mex序列的单调不降的性质,我们只需要将一段区间的mex全部改成A[k]就行了。
然后很明显这里可以用线段树维护mex,但是太复杂,而且本人能力有限,就写了个类暴力,运气比较好还能过
#include<cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
const int inf=1e9;
const int maxn=200005;
int s[maxn],n,m,a,b;
int mex[maxn],NEXT[maxn],head[maxn];
bool mark[maxn];
priority_queue<int ,vector<int>,greater<int> >q;
struct node{int l,r,id,ans;}w[maxn];
bool cmp1(node a,node b){return a.l==b.l?a.r<b.r:a.l<b.l;}
bool cmp2(node a,node b){return a.id<b.id;}
inline void _read(int &x){
char t=getchar();bool sign=true;
while(t<'0'||t>'9')
{if(t=='-')sign=false;t=getchar();}
for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
if(!sign)x=-x;
}
int main(){
_read(n);_read(m);
int i,j,l=1;
for(i=1;i<=n;i++){
_read(s[i]);
if(!mark[s[i]]){
q.push(s[i]);
mark[s[i]]=1;
}
mex[i]=mex[i-1];
while(q.size()&&q.top()==mex[i]){
mex[i]++;
q.pop();
}
}//边输入边用小根堆求mex数组
for(i=n;i;i--)
NEXT[i]=head[s[i]],head[s[i]]=i;//next数组求法
for(i=1;i<=m;i++){
_read(w[i].l);_read(w[i].r);
w[i].id=i;
}
sort(w+1,w+1+m,cmp1);
for(i=1;i<=m;i++){
while(l<w[i].l){
if(NEXT[l]==0)NEXT[l]=n+1;
int pos=lower_bound(mex+1+l,mex+NEXT[l],s[l])-mex;
if(pos<s[l])pos=inf;
for(j=pos;j<=min(NEXT[l]-1,n);j++)mex[j]=s[l];
l++;
}
w[i].ans=mex[w[i].r];
}//这里的变量l作用与HH的项链一题类似,可以参考上一篇博客
sort(w+1,w+1+m,cmp2);
for(i=1;i<=m;i++)
printf("%d\n",w[i].ans);
}
然后经过研究,我套了个线段树的模板上去,修修改改居然过了,而且比上面的暴力快了很多,A题肯定就没有问题了,下面附上代码
#include<cstdio>
#include<iostream>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn=200005;
const int inf=1e9;
priority_queue<int,vector<int>,greater<int> >q;
int n,m,s[maxn],mex[maxn],head[maxn],NEXT[maxn],tot,a,b;
bool mark[maxn];
struct wk{int l,r,ans,id;}qest[maxn];
struct wr{int a,b,left,right,minn,maxx,lazy;}tree[maxn<<2];
inline void _read(int &x){
char ch=getchar(); bool mark=false;
for(;!isdigit(ch);ch=getchar())if(ch=='-')mark=true;
for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
if(mark)x=-x;
}
bool cmp1(wk a,wk b){return a.l==b.l?a.r<b.r:a.l<b.l;}
bool cmp2(wk a,wk b){return a.id<b.id;}
void build(int x,int y){
int r=++tot;
tree[r].a=x,tree[r].b=y;
tree[r].lazy=-1;
if(x<y){
int mid=(x+y)>>1;
tree[r].left=tot+1;
build(x,mid);
tree[r].right=tot+1;
build(mid+1,y);
tree[r].maxx=max(tree[tree[r].left].maxx,tree[tree[r].right].maxx);
tree[r].minn=min(tree[tree[r].left].minn,tree[tree[r].right].minn);
}
else tree[r].minn=tree[r].maxx=mex[x];
}
void pushdown(int r){
int ls=tree[r].left,rs=tree[r].right;
tree[ls].lazy=tree[rs].lazy=tree[r].lazy;
tree[ls].minn=tree[rs].minn=tree[r].lazy;
tree[ls].maxx=tree[rs].maxx=tree[r].lazy;
tree[r].lazy=-1;
}
void change(int r,int d){
if(tree[r].lazy>=0)pushdown(r);
if(a<=tree[r].a&&b>=tree[r].b){
tree[r].lazy=tree[r].minn=tree[r].maxx=d;
return ;
}
int mid=(tree[r].a+tree[r].b)>>1;
if(a<=mid)change(tree[r].left,d);
if(b>mid)change(tree[r].right,d);
tree[r].maxx=max(tree[tree[r].left].maxx,tree[tree[r].right].maxx);
tree[r].minn=min(tree[tree[r].left].minn,tree[tree[r].right].minn);
}
int ask(int r,int d){
int ls=tree[r].left,rs=tree[r].right;
int mid=(tree[r].a+tree[r].b)>>1;
if(tree[r].lazy>=0)pushdown(r);
if(tree[r].a==tree[r].b)return tree[r].minn;
if(d<=mid)return ask(ls,d);
else return ask(rs,d);
}
int swer(int r,int p){
int ls=tree[r].left,rs=tree[r].right;
int mid=(tree[r].a+tree[r].b)>>1;
if(tree[r].lazy>=0)pushdown(r);
if(tree[r].minn>=p&&a<=tree[r].a&&b>=tree[r].b)return tree[r].a;
if(a<=mid&&tree[ls].maxx>=p)return swer(ls,p);
else if(b>mid&&tree[rs].maxx>=p)return swer(rs,p);
else return inf;
}
int main(){
int i;
_read(n); _read(m);
for(i=1;i<=n;i++){
_read(s[i]);
if(!mark[s[i]]){
q.push(s[i]);
mark[s[i]]=1;
}
mex[i]=mex[i-1];
while(!q.empty()&&mex[i]==q.top()){
mex[i]++;
q.pop();
}
}
for(i=n;i;i--)
NEXT[i]=head[s[i]],head[s[i]]=i;
for(i=1;i<=m;i++){
_read(qest[i].l);_read(qest[i].r);
qest[i].id=i;
}
sort(qest+1,qest+1+m,cmp1);
build(1,n);
int l=1;
for(i=1;i<=m;i++){
while(l<qest[i].l){
if(NEXT[l]==0)NEXT[l]=n+1;
a=l,b=NEXT[l]-1;
int pos=swer(1,s[l]);
if(pos&&pos<NEXT[l]){
a=pos;
change(1,s[l]);
}
l++;
}
qest[i].ans=ask(1,qest[i].r);
}
sort(qest+1,qest+1+m,cmp2);
for(i=1;i<=m;i++)
printf("%d\n",qest[i].ans);
}