51nod1175区间第k大(小)数(主席树模板)

题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1175

 

分析和思路:

可能最先想到的就是把l,r区间的数拿出来排序后得到答案,但多次频繁操作时间复杂度太高,这时候强大的数据结构主席树就发挥出它强大的作用,这就是主席树模板题目,套模板。

主席树本质就是线段树(又称可持久化线段树),可以将历史版本保留下来。对于区间[l, r]的第K大询问,如果我们能够得到一个插入原序列中[1, l - 1]元素的线段树,和一颗插入了[1, r]元素的线段树,由于线段树是开在值域上,区间长度是一定的,所以结构也必然是完全相同的,我们可以直接对这两颗线段树进行相减,得到的是相当于插入了区间[l ,r]元素的线段树。注意这里利用到的区间相减性质,实际上是用两颗不同历史版本的线段树进行相减:一颗是插入到第l-1个元素的旧树,一颗是插入到第r元素的新树。这样相减之后得到的是相当于只插入了原序列中[l, r]元素的一颗记录了区间数字个数的线段树。

插入时,我们显然不能每次插入一个元素,就从头建立一颗全新的线段树,否则内存开销无法承受。事实上,每次插入一个新的元素时,我们不需要新建所有的节点,而是只新建增加的节点。也就是从根节点出发,先新建节点并复制原节点的值,然后进行修改即可。

 

我的天呐,看了这么长时间总算懂得一点点主席树了,拿小本本记下来。。

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 using namespace std;
 6 const int maxn=1e5+5;
 7 int a[maxn],sorted[maxn],root[maxn];//root每棵树根存结点 
 8 int cnt;
 9 struct node
10 {
11     int sum,lson,rson;//重要:ls,rs代表左右儿子结点编号!(泪奔线段树l,r代表本结点管辖范围!!)
12 }Tree[maxn<<5];
13 
14 int createnode(int su,int ls,int rs)//创建新树(先等于原树)
15 {
16     int idx=++cnt;
17     Tree[idx].sum=su;
18     Tree[idx].lson=ls;
19     Tree[idx].rson=rs;
20     return idx;
21 }
22 void update(int l,int r,int &root,int pre_rt,int pos)//&编号,void&x(原空间)代替了return x返回型更方便更新修改 
23 {
24     root=createnode(Tree[pre_rt].sum+1,Tree[pre_rt].lson,Tree[pre_rt].rson);//从根节点往下更新到叶子,新建立出一路更新的节点,这样就是一颗新树了
25     if (l==r) return;
26     
27     int mid=(l+r)>>1;
28     if (pos<=mid) update(l,mid,Tree[root].lson,Tree[pre_rt].lson,pos);
29     else update(mid+1,r,Tree[root].rson,Tree[pre_rt].rson, pos);
30 }
31 
32 int query(int l,int r,int S,int E,int k)
33 {
34     if (l==r) return l;//说明最终返回的是答案离散后的结果即编号(排第几) 
35     
36     int mid=(l+r)>>1;
37     int sum=Tree[Tree[E].lson].sum-Tree[Tree[S].lson].sum;//两左二子数目相减得到该节点真正个数判断 
38     if (k<=sum) return query(l,mid,Tree[S].lson,Tree[E].lson,k);
39     else return query(mid+1,r,Tree[S].rson,Tree[E].rson,k-sum);
40 }
41 int main()
42 {
43     int n,m,num,pos,T;
44     scanf("%d %d",&n,&m);
45     cnt=0; root[0]=0;
46     for(int i=1;i<=n;i++)
47     {
48         scanf("%d",&a[i]);
49         sorted[i]=a[i];
50     }
51 
52     sort(sorted+1,sorted+1+n);
53     num=unique(sorted+1,sorted+n+1)-(sorted+1);//num去杂之后的最终范围
54     for(int i=1;i<=n;i++)
55     {   //实际上是对每个元素建立了一颗线段树,保存其根节点
56         pos=lower_bound(sorted+1,sorted+num+1,a[i])-sorted;//得到编号即离散化后的映射
57         update(1,num,root[i],root[i-1],pos);
58     }
59 
60     while(m--)
61     {
62         int x,y,k;
63         scanf("%d%d%d",&x,&y,&k);
64         x++; y++; 
65         int kk=y-x+1;//数学转换小变大(或者查询时直接右递归)
66         kk=kk-(k-1); 
67         //if(Tree[root[y]].sum-Tree[root[x-1]].sum<k) {cout<<"no"<<endl;continue;} 
68         pos=query(1,num,root[x-1],root[y],kk);
69         printf("%d\n",sorted[pos]);
70     }
71 
72     return 0;
73 } 

 

牛客小白月赛9E:https://ac.nowcoder.com/acm/contest/275/E

查询区间内<=x的数量

思路:本质还是查询第k小(大),用了lower_bound转换而已

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 using namespace std;
 6 const int maxn=1e5+5;
 7 int a[maxn],sorted[maxn],root[maxn];//root每棵树根存结点
 8 int cnt;
 9 struct node
10 {
11     int sum,lson,rson;//重要:ls,rs代表左右儿子结点编号!(泪奔线段树l,r代表本结点管辖范围!!)
12 }Tree[maxn<<5];
13  
14 int createnode(int su,int ls,int rs)//创建新树(先等于原树)
15 {
16     int idx=++cnt;
17     Tree[idx].sum=su;
18     Tree[idx].lson=ls;
19     Tree[idx].rson=rs;
20     return idx;
21 }
22 void update(int l,int r,int &root,int pre_rt,int pos)//&编号,void&x(原空间)代替了return x返回型更方便更新修改
23 {
24     root=createnode(Tree[pre_rt].sum+1,Tree[pre_rt].lson,Tree[pre_rt].rson);//从根节点往下更新到叶子,新建立出一路更新的节点,这样就是一颗新树了
25     if (l==r) return;
26      
27     int mid=(l+r)>>1;
28     if (pos<=mid) update(l,mid,Tree[root].lson,Tree[pre_rt].lson,pos);
29     else update(mid+1,r,Tree[root].rson,Tree[pre_rt].rson, pos);
30 }
31  
32 int query(int l,int r,int S,int E,int k)
33 {
34     if (1<=l && r<=k) return Tree[E].sum-Tree[S].sum;//说明最终返回的是答案离散后的结果即编号(排第几)
35      
36     int mid=(l+r)>>1;
37     //int sum=Tree[Tree[E].lson].sum-Tree[Tree[S].lson].sum;//两左二子数目相减得到该节点真正个数判断
38     int ans=0;
39     ans+=query(l,mid,Tree[S].lson,Tree[E].lson,k);
40     if(k>=mid+1)  ans+=query(mid+1,r,Tree[S].rson,Tree[E].rson,k);
41  
42     return ans;
43 }
44  
45 int main()
46 {
47     int n,m,num,pos,T;
48     scanf("%d %d",&n,&m);
49     cnt=0; root[0]=0;
50     for(int i=1;i<=n;i++)
51     {
52         scanf("%d",&a[i]);
53         sorted[i]=a[i];
54     }
55  
56     sort(sorted+1,sorted+1+n);
57     num=unique(sorted+1,sorted+n+1)-(sorted+1);//num去杂之后的最终范围
58     for(int i=1;i<=n;i++)
59     {   //实际上是对每个元素建立了一颗线段树,保存其根节点
60         pos=lower_bound(sorted+1,sorted+num+1,a[i])-sorted;//得到编号即离散化后的映射
61         update(1,num,root[i],root[i-1],pos);
62     }
63  
64     while(m--)
65     {
66         int x,y,k;
67         scanf("%d%d%d",&x,&y,&k);
68          
69         int t=lower_bound(sorted+1,sorted+1+num,k)-(sorted+1)+1;//lower_bound查询排第几常用优化技巧操作
70         //if(Tree[root[y]].sum-Tree[root[x-1]].sum<k) {cout<<"no"<<endl;continue;}
71         if(sorted[t]!=k) t--;
72         if(t<=0) { printf("0\n"); continue; }//这个不特判会死循环
73          
74          
75         int ans=query(1,num,root[x-1],root[y],t);
76         printf("%d\n",ans);
77     }
78  
79     return 0;
80 }

 

查询区间不同数的个数(做法很多,可树状数组,莫队,主席树)

sum存的不是第几大了,而是区间每个不同数的最右边的位置

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 using namespace std;
 6 const int maxn=1e5+5;
 7 int a[maxn],sorted[maxn],root[maxn];
 8 int vis[maxn*10];
 9 int cnt;
10 struct node
11 {
12     int sum,lson,rson;
13 }Tree[maxn<<5];
14 
15 int createnode(int su,int ls,int rs)
16 {
17     int idx=++cnt;
18     Tree[idx].sum=su;
19     Tree[idx].lson=ls;
20     Tree[idx].rson=rs;
21     return idx;
22 }
23 void update(int l,int r,int &root,int pre_rt,int pos,int val)
24 {
25     root=createnode(Tree[pre_rt].sum+val,Tree[pre_rt].lson,Tree[pre_rt].rson);
26     if (l==r) return;
27     
28     int mid=(l+r)>>1;
29     if (pos<=mid) update(l,mid,Tree[root].lson,Tree[pre_rt].lson,pos,val);
30     else update(mid+1,r,Tree[root].rson,Tree[pre_rt].rson,pos,val);
31 }
32 
33 int query(int l,int r,int posl,int posr,int E)
34 {
35     if (posl<=l && r<=posr) return Tree[E].sum;
36     
37     int mid=(l+r)>>1;
38    
39     int ans=0;
40     if(mid>=posl) ans+=query(l,mid,posl,posr,Tree[E].lson);
41     if(mid<posr)  ans+=query(mid+1,r,posl,posr,Tree[E].rson);
42 
43     return ans;
44 }
45 
46 int main()
47 {
48     int n,m,num,pos,T;
49     scanf("%d",&n);
50     cnt=0; root[0]=0;
51     for(int i=1;i<=n;i++)
52     {
53         scanf("%d",&a[i]);
54         
55         if(vis[a[i]])
56         {
57             update(1,n,root[i],root[i-1],vis[a[i]],-1);
58             update(1,n,root[i],root[i],i,1);
59         }
60         else
61         {
62             update(1,n,root[i],root[i-1],i,1);
63             vis[a[i]]=i;
64         }
65     }
66 
67     scanf("%d",&m);
68     while(m--)
69     {
70         int x,y;
71         scanf("%d%d",&x,&y);
72         
73         //int t=lower_bound(sorted+1,sorted+1+num,k)-(sorted+1)+1;
74         //if(Tree[root[y]].sum-Tree[root[x-1]].sum<k) {cout<<"no"<<endl;continue;} 
75         //if(sorted[t]!=k) t--;
76         //if(t<=0) { printf("0\n"); continue; }
77         
78         int ans=query(1,n,x,y,root[y]);
79         printf("%d\n",ans);
80     }
81 
82     return 0;
83 }

 

 

 

 

转载于:https://www.cnblogs.com/redblackk/p/9552108.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值