莫队或权值线段树 或主席树 p4137
题目描述
有一个长度为n的数组{a1,a2,…,an}。m次询问,每次询问一个区间内最小没有出现过的自然数。
输入格式
第一行n,m。
第二行为n个数。
从第三行开始,每行一个询问l,r。
输出格式
一行一个数,表示每个询问的答案。
一开始以为像答案这样的做法会超时,没想到真是这样写。。。
1 #include<cstdio> 2 #include<algorithm> 3 #include<math.h> 4 #include<string.h> 5 using namespace std; 6 const int maxn=2e5+10; 7 int num[maxn]; 8 int answer,block; 9 int vis[maxn]; 10 struct node 11 { 12 int l,r,id; 13 int Ans; 14 }ans[maxn]; 15 bool cmp(node x,node y) 16 { 17 if(x.l/block!=y.l/block) 18 return x.l<y.l; 19 if(x.l/block&1) //使用波形排序,可进一步的优化 20 return x.r<y.r; 21 return x.r>y.r; 22 } 23 bool CMP(node x,node y) 24 { 25 return x.id<y.id; 26 } 27 void Delete(int pos) 28 { 29 vis[num[pos]]--; 30 if(!vis[num[pos]]&&num[pos]<answer){ 31 answer=num[pos]; 32 } 33 } 34 void add(int pos) 35 { 36 vis[num[pos]]++; 37 if(vis[num[pos]]==1&&answer==num[pos]){ 38 int tmp=answer+1; 39 while(1){ 40 if(!vis[tmp]){ 41 answer=tmp; 42 return; 43 } 44 tmp++; 45 } 46 } 47 } 48 int main() 49 { 50 int n,m; 51 scanf("%d%d",&n,&m); 52 block=sqrt(n); 53 for(int i=1;i<=n;i++) scanf("%d",&num[i]); 54 for(int i=1;i<=m;i++){ 55 scanf("%d%d",&ans[i].l,&ans[i].r); 56 ans[i].id=i; 57 } 58 sort(ans+1,ans+1+m,cmp); 59 int left=1,right=0; 60 for(int i=1;i<=m;i++){ 61 while(ans[i].l>left) Delete(left++); 62 while(ans[i].l<left) add(--left); 63 while(ans[i].r>right) add(++right); 64 while(ans[i].r<right) Delete(right--); 65 ans[i].Ans=answer; 66 } 67 sort(ans+1,ans+1+m,CMP); 68 for(int i=1;i<=m;i++) 69 printf("%d\n",ans[i].Ans); 70 return 0; 71 }
还有一个权值线段树的解法:
对于第i棵权值线段树,维护一下数列前ii项中每个权值出现的最靠右的位置。然后向上更新一下最小值。这样如果某个权值最靠右的位置都比查询区间左端点小的话,那它一定没有在这个区间里出现。维护最小值目的就是看最小的是否比左端点大,根据这个在线段树上二分。
权值范围是10^9109的。题解里有说答案最大为nn,直接把>n>n的a[i]a[i]处理为n+1n+1就行。蒟蒻没有想到QAQQAQ,说一下蒟蒻的处理:
用离散化,但是一个权值没有在数列里出现过的话就不会在离散化数组中,也不会出现在线段树中,查询会出锅。注意到一个询问的答案要么是00,要么是数列中某个数的值+1+1。这样离散化时把00和每个出现过的权值+1+1都丢进去就行了QwQQwQ。
当然这道题不强制在线也不修改,可以把询问离线下来一边扫一边加,遇到询问右端点就查询左端点,不需要主席树(可持久化线段树),一棵权值线段树就可以了。
时间复杂度:O(nlogn)
1 #include<cstdio> 2 #include<algorithm> 3 #include<math.h> 4 #include<string.h> 5 #include<vector> 6 using namespace std; 7 const int maxn=2e5+10; 8 int ans[maxn]; 9 int t; //T为离散化之后的数组长度 10 int a[maxn],b[maxn*2],cnt; //原数组和离散化的数组,以及下标cnt; 11 int tree[maxn<<5]; 12 int root[maxn]; 13 void update(int root) 14 { 15 tree[root]=min(tree[root<<1],tree[root<<1|1]); 16 } 17 void add(int num,int l,int r,int root,int base) 18 { 19 if(l==r){ 20 tree[root]=base; 21 return; 22 } 23 int mid=l+r>>1; 24 if(num<=mid) add(num,l,mid,root<<1,base); 25 else add(num,mid+1,r,root<<1|1,base); 26 update(root); 27 } 28 int query(int pos) 29 { 30 int l=1,r=t; 31 int root=1; 32 while(l<r){ 33 int mid=l+r>>1; 34 //如果左区间的值至少存在一个权值的下标小于pos的,就证明此范围内有符合条件的数; 35 //就像左边二分; 36 if(tree[root<<1]<pos) r=mid,root=root<<1; 37 //否则向右边二分; 38 else l=mid+1,root=root<<1|1; 39 } 40 return b[l]; 41 } 42 int main() 43 { 44 int n,m; 45 scanf("%d%d",&n,&m); 46 b[++cnt]=0; 47 for(int i=1;i<=n;i++){ 48 scanf("%d",&a[i]); 49 b[++cnt]=a[i]; 50 b[++cnt]=a[i]+1; 51 } 52 sort(b+1,b+1+cnt); 53 t=unique(b+1,b+1+cnt)-b-1; 54 for(int i=1;i<=n;i++){ 55 a[i]=lower_bound(b+1,b+1+t,a[i])-b; 56 add(a[i],1,t,1,i,root[i],root[i-1]); 57 } 58 return 0; 59 }
因为权值线段树能解这道题,所以用主席树解就显得没有必要了,但我还是练了练手。
1 #include<cstdio> 2 #include<algorithm> 3 #include<math.h> 4 #include<string.h> 5 using namespace std; 6 const int maxn=4e5+10; 7 int a[maxn],b[maxn],cnt=0,len; 8 struct node{int l,r,id;}tree[maxn<<5];int cot; 9 //主席树需要开的空间比一般线段树更大,另外后面的cot是记录主席树节点用的。 10 int Root[maxn]; 11 void build(int &x,int l,int r) 12 { 13 //这里的&x会把x得到的值传给这个变量,也就是Root[0]; 14 //建一波空树,其实可以不建的,之前打过不建的代码, 15 //但总觉得不建的话心里不踏实。啊哈哈哈 16 x=++cot; 17 if(l==r) return ; 18 int mid=l+r>>1; 19 build(tree[x].l,l,mid); 20 build(tree[x].r,mid+1,r); 21 } 22 void add(int num,int l,int r,int &x,int y,int pos) 23 { 24 //同上,这里的&x会传给root[i]; 25 tree[++cot]=tree[y]; 26 x=cot; //将cot传给x,这个cot储存的是Root[i]这个节点的值,这个节点是根节点。 27 if(l==r){ 28 tree[cot].id=pos; 29 //tree[x].id=pos; 这里也可以这样写 30 return; 31 } 32 int mid=l+r>>1; 33 if(num<=mid) add(num,l,mid,tree[x].l,tree[y].l,pos); 34 else add(num,mid+1,r,tree[x].r,tree[y].r,pos); 35 int left=tree[x].l,right=tree[x].r; 36 tree[x].id=min(tree[left].id,tree[right].id); //传递最小值; 37 } 38 int query(int pos,int base) 39 { 40 //这一部分是本题内容。 41 int l=1,r=len; 42 while(l<r){ 43 int mid=l+r>>1; 44 //left存储的是根节点的左区间,right为右区间。 45 int left=tree[base].l,right=tree[base].r; 46 if(tree[left].id<pos) r=mid,base=left; 47 else l=mid+1,base=right; 48 } 49 return b[l]; 50 } 51 int main() 52 { 53 int n,m; 54 scanf("%d%d",&n,&m); 55 b[++cnt]=0; 56 for(int i=1;i<=n;i++){ 57 scanf("%d",&a[i]); 58 b[++cnt]=a[i]; 59 b[++cnt]=a[i]+1; 60 } 61 sort(b+1,b+1+cnt); 62 len=unique(b+1,b+1+cnt)-b-1; 63 build(Root[0],1,len); 64 for(int i=1;i<=n;i++){ 65 a[i]=lower_bound(b+1,b+1+len,a[i])-b; 66 add(a[i],1,len,Root[i],Root[i-1],i); 67 } 68 for(int i=1;i<=m;i++){ 69 int l,r; 70 scanf("%d%d",&l,&r); 71 int ans=query(l,Root[r]); 72 printf("%d\n",ans); 73 } 74 return 0; 75 }