莫队或权值线段树 或主席树 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 }

 

posted @ 2019-10-06 19:16  古比  阅读(162)  评论(0编辑  收藏  举报