经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » C++ » 查看文章
主席树的简要讲解
来源:cnblogs  作者:Vect0r_lemon  时间:2024/4/23 12:17:53  对本文有异议

区间第k小值

主席树是解决动态查找序列上[l,r]区间中的第k小值的一个数据结构

核心思想:动态开点(后面会讲)
传统线段树都是值域线段树
其实意思就是每个节点都存的是序列上[l,r]的一个区间和,但是考虑我们需要动态处理区间的不是最值,故换一种线段树

主席树一般用的是权值线段树
就是把[l,r]改为[min(a),max(a)] a是序列A[l,r]区间里的一个子序列,每个节点下都保存一个统计数字s,代表在当前权值域里,有多少个数字
array : 1 4 3 3

  1. [1,4]
  2. s = 4
  3. [1,2] [3,4]
  4. s = 1 s = 3
  5. [1,1] [2,2] [3,3] [4,4]
  6. s = 1 s = 0 s = 2 s = 1

 

 (不会画图QWQ)

 步入正题
我们转换思想,每一次开点都存储一个版本的权值线段树,但是这样会让空间复杂度直线上升为O(n^2),这是不能容忍的
但是我们再想
这是一颗树性结构,我们每一次加点都只会改变上下log n个点的数据,为什么我们就不能每一次都开一个log n的新点,将与这次更改无关的点直接开一条新边连接到新点呢?

不能像上一个那样形似了,我直接盗图

这是第一个版本在insert(4)后的结果
每一次insert都存储一个root[i+1],表示第i+1个版本的根节点

 

但是如何查询[l,r]区间内的第k小数呢
这时候就要运用我们前缀和的思想


 [l,r]的信息 = [1,r]的信息 - [1,l-1]的信息
即 S[l,r] = S[1,r] - S[1,l-1]

离散化
这个其实没什么好讲的,就是预处理,详情见代码

 

大概意思是懂了吧,来,让我们步入总结

常规操作
1.插入insert(),动态开点
2.查询[l,r]区间内第k小值
3.离散化

时间复杂度 O(n log^2 n)

luogu P3834
#### C++ 代码

  1. 1 #include"bits/stdc++.h"
  2. 2
  3. 3 using namespace std;
  4. 4 const int N=200010;
  5. 5 #define inl inline
  6. 6 #define reg register
  7. 7 #define regi register int
  8. 8 #define PII pair<int,int>
  9. 9 //#define ll long long
  10. 10 inl int read(void)
  11. 11 {
  12. 12 int x=0,f=1;char ch=getchar();
  13. 13 while(!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
  14. 14 while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
  15. 15 return x*f;
  16. 16 }
  17. 17 int a[N],n,m;
  18. 18 vector<int> num;
  19. 19 struct node
  20. 20 {
  21. 21 int l,r,s;//左右儿子下标,区间中一共有多少个数
  22. 22 }tr[N*20];
  23. 23 int root[N],idx;//root[i]为第i课线段树的节点编号
  24. 24 #define id(x) lower_bound(num.begin(),num.end(),x)-num.begin()
  25. 25 //id(x)为映射
  26. 26 int build(int l,int r)
  27. 27 {
  28. 28 int p=++idx;
  29. 29 if(l==r) return p;
  30. 30 int mid=l+r>>1;
  31. 31 tr[p].l=build(l,mid),tr[p].r=build(mid+1,r);
  32. 32 return p;//返回当前节点的编号
  33. 33 }
  34. 34 inl int insert(int p,int l,int r,int x)
  35. 35 {
  36. 36 int q=++idx;//创建新点q
  37. 37 tr[q]=tr[p];//复制旧点p
  38. 38 if(l==r)
  39. 39 {
  40. 40 tr[q].s++;
  41. 41 return q;
  42. 42 }
  43. 43 int mid=l+r>>1;
  44. 44 if(x<=mid) tr[q].l=insert(tr[p].l,l,mid,x);//x出现在左子树,修改左子树
  45. 45 else tr[q].r=insert(tr[p].r,mid+1,r,x);//x出现在右子树,修改右子树
  46. 46 tr[q].s=tr[tr[q].l].s+tr[tr[q].r].s;//统计
  47. 47 return q;//返回当前分配使用的新节点编号
  48. 48 }
  49. 49 inl int query(int q,int p,int l,int r,int k)//查询区间
  50. 50 {
  51. 51 if(l==r) return l;
  52. 52 int s=tr[tr[q].l].s-tr[tr[p].l].s;//线段树相减
  53. 53 int mid=l+r>>1;
  54. 54 if(k<=s) return query(tr[q].l,tr[p].l,l,mid,k);//左儿子数字大于或等于k时,说明第k小数在右子树
  55. 55 return query(tr[q].r,tr[p].r,mid+1,r,k-s);//否则在左子树
  56. 56 }
  57. 57 void init()
  58. 58 {
  59. 59 //离散化
  60. 60 sort(num.begin(),num.end());
  61. 61 num.erase(unique(num.begin(),num.end()),num.end());
  62. 62 root[0]=build(0,num.size()-1);
  63. 63
  64. 64 }
  65. 65 int main(void)
  66. 66 {
  67. 67 n=read(),m=read();
  68. 68 for(regi i=1;i<=n;i++)
  69. 69 {
  70. 70 a[i]=read();
  71. 71 num.push_back(a[i]);
  72. 72 }
  73. 73 init();
  74. 74 for(regi i=1;i<=n;i++) root[i]=insert(root[i-1],0,num.size()-1,id(a[i]));
  75. 75 while(m--)
  76. 76 {
  77. 77 int l=read(),r=read(),k=read();
  78. 78 printf("%d\n",num[query(root[r],root[l-1],0,num.size()-1,k)]);
  79. 79 }
  80. 80 return 0;
  81. 81 }

可持续化的思想都基本是这样的,包括可持续化平衡树,可持续化堆(注意是思想,比如可持续化堆有的时候就不能动态开点)

 

原文链接:https://www.cnblogs.com/VL-house/p/18152605

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号