Rmq Problem bzoj-3339||mex bzoj-3585
题目大意:给定一个长度为n的数列a,多次讯问区间l,r中最小的不属于集合{$A_l,A_{l+1}...A_r$}的非负整数。
注释:n,q$le$200,000 ; 0$le A_i le$200,000 ; $A_i$均为非负整数,1<=l<=r<=n,l和r均为正整数。
想法:网上很多其他的算法(suika:离线+莫队,WinnieChen:在线权值线段树),我们来聊一聊离线加线段树。
首先,我们将询问离线,按左端点为第一关键字排序。
紧接着,预处理一点东西:暴力求出1~i之间的答案,以及每一个数位置pos的数a[pos]右边第一个等于a[pos]的数,它的位置我们记为nxt[pos]。
然后,我们从左往右扫,对于当前数a[l],对于l+1到nxt[l]-1之间的数都需要进行区间赋值乘a[l],这个操作我们用线段树完成。如果当前节点还是一个被查询区间的左端点的话,我们直接输出右端点对应的mex即可。
最后,附上丑陋的代码... ...
#include <iostream> #include <cstdio> #include <algorithm> #define lson pos<<1 #define rson pos<<1|1 #define inf 0x7fffffff using namespace std; int n,m,k=0; int a[200005],sg[200005],ans[200005],next[200005],last[200005]; int ls[600005],rs[600005],mn[600005]; bool mark[200001]; struct data{int l,r,id;}q[200005]; bool cmp(data a,data b) {return a.l<b.l;} void build(int l,int r,int pos) { ls[pos]=l; rs[pos]=r; mn[pos]=inf; if(l==r) { mn[pos]=sg[l]; return; } int mid=(l+r)>>1; build(l,mid,lson);build(mid+1,r,rson); } void pushdown(int pos) { int l=ls[pos],r=rs[pos]; if(l==r)return; mn[lson]=min(mn[pos],mn[lson]); mn[rson]=min(mn[pos],mn[rson]); } int ask(int pos,int x) { if(mn[pos]!=inf)pushdown(pos); int l=ls[pos],r=rs[pos]; if(l==r)return mn[pos]; int mid=(l+r)>>1; if(x<=mid)return ask(lson,x); return ask(rson,x); } void update(int pos,int x,int y,int val) { if(mn[pos]!=inf)pushdown(pos); int l=ls[pos],r=rs[pos]; if(l==x&&y==r){mn[pos]=min(mn[pos],val);return;} int mid=(l+r)>>1; if(y<=mid)update(lson,x,y,val); else if(x>mid)update(rson,x,y,val); else { update(lson,x,mid,val); update(rson,mid+1,y,val); } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); } for(int i=1;i<=n;i++) { mark[a[i]]=1; if(a[i]==k) while(mark[k])k++; sg[i]=k; } build(1,n,1); for(int i=n;i>0;i--) next[i]=last[a[i]],last[a[i]]=i; 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,cmp); int now=1; for(int i=1;i<=m;i++) { while(now<q[i].l) { if(!next[now])next[now]=n+1; update(1,now,next[now]-1,a[now]); now++; } ans[q[i].id]=ask(1,q[i].r); } for(int i=1;i<=m;i++) printf("%d ",ans[i]); return 0; }
小结:对着hzwer学长写的动态开点,感觉爆炸。注意pushdown的时候要记得删除之前的标记。