题目链接: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 }
完