NKOI 2691 MEX ll

【冬季集训】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

BZOJ 3339


这道题是一道异常恶心的题,据说可以用线段树来优化,但是能力有限,写了个不算暴力的暴力,然后加了个输入优化,勉勉强强能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);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值