主席树入门
emmm…最近入门了主席树, 感觉其实不是很难, 主要理解了就很简单了(毕竟代码这么短)
主席树的用处:
给出一个数列,求区间 l ~ r 之间的第 k 大值
主席树的概念:
利用数列中 n 个数据建立 n 棵树, 其中第 i 棵树维护 1 ~ i 这个前缀内的数据信息
第 i 棵树中的每个节点都有其所代表的区间范围(和线段树一样), 但这些节点所代表的区间范围指的是值域范围,
即每个节点维护 1 ~ i 之间的全部节点中在此至于范围内的数的个数
主席树的建立
(图片出自主席树详解 )
下面是一棵空树(在代码实现中可以不建树)
之前说的主席树的概念中, 主席树是要对每个节点 i 建立一棵维护前缀信息的树.
但是对于每一个前缀数列都建一棵树所要使用的节点是 O(n logn )的.
所以… 真的如此朴素的建树的话必然会爆内存.但是我们仔细想想之后可以发现:
第 i 棵树 和 第 i-1 棵树中许多节点都是信息相同(即 在一棵树中的位置相同且维护的数值相同)的相似节点, .
那么我们可不可以考虑偷懒一点, 将上一棵树的节点直接拿过来用呢?当然是可以的, 图如下.
下面还有一张, 是没有重复利用节点的…森林吧 (图中相似节点编号相同).
将相同编号的节点合并之后是这样的:
主席树的模板
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 //by Judge 2 #include<bits/stdc++.h> 3 #define mid (l+r>>1) 4 using namespace std; 5 const int M=2e5+111; 6 inline int read(){ 7 int x=0,f=1; char c=getchar(); 8 for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; 9 for(;isdigit(c);c=getchar()) x=x*10+c-'0'; 10 return x*f; 11 } 12 int n,m,q,cnt; 13 int a[M],b[M],t[M],sum[M<<5],L[M<<5],R[M<<5]; 14 void update(int las,int &now,int l,int r,int x){ //插入节点 15 if(!now) now=++cnt; sum[now]=sum[las]+1; if(l==r) return ; 16 // l==r直接返回, 否则向下新建改变了的节点, 没改变的直接调用 17 if(x<=mid) R[now]=R[las],update(L[las],L[now],l,mid,x); 18 else L[now]=L[las],update(R[las],R[now],mid+1,r,x); 19 } 20 int query(int u,int v,int l,int r,int k){ //查询 u ~ v 区间内第 k 大的数离散后的值 21 if(l>=r) return l; int x=sum[L[v]]-sum[L[u]]; 22 //这里与二分查找树类似 23 if(x>=k) return query(L[u],L[v],l,mid,k); 24 else return query(R[u],R[v],mid+1,r,k-x); 25 } 26 int main(){ 27 n=read(), q=read(); 28 for(int i=1;i<=n;++i) 29 b[i]=a[i]=read(); 30 sort(b+1,b+1+n); 31 m=unique(b+1,b+1+n)-(b+1); //排序并去重 32 int x,y,k; 33 for(int i=1;i<=n;++i) 34 k=lower_bound(b+1,b+1+m,a[i])-b, //离散 35 update(t[i-1],t[i],1,m,k); 36 while(q--) 37 x=read(), y=read(), k=read(), 38 printf("%d\n",b[query(t[x-1],t[y],1,m,k)]); //将离散前的值输出 39 return 0; 40 }
最后感谢 bzt 大佬的blog
同学们bye ( ^_^ )/~~