经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
主席树(可持久化线段树)
来源:cnblogs  作者:zhouruoheng  时间:2024/1/24 9:43:06  对本文有异议

主席树

前言

主席树也是一种数据结构,是线段树的进阶,作用是可以保留历史版本,支持回退,就是回到之前某个未修改的状态。就像我们在写博客时如果误删了重要的内容,就可以用 Ctrl + z 撤回一次操作,也可以用Ctrl + Shift +z 还原我们撤回的操作,这就需要存下每次编辑的操作。

基本原理

可持久化线段树,顾名思义,是可持久的线段树(好像是废话)。那么问题就在于怎么去可持久化,支持访问历史版本。
最暴力的,直接复制整棵树,再对新的这棵树进行修改。但这样的话,节点数为 \(n\),操作数为 \(m\),时间复杂就是 \(O(nm)\)img
好了,直接 TLE。显然复制这棵树会有非常多的浪费,思考一下,我们只需要复制修改后发生改变的点就好了。

img
img

我们要复制的只是蓝色的点其他的保持不变,要在蓝色的点边上复制一个一模一样的点然后修改。如图:
img
红色的点即为修改后的点,继承原节点的所有信息,包括未修改的儿子。这样我们只需要存下每次修改后的根,就能访问该版本的树。修改的时间复杂度为 \(O(\log{n})\)

操作

前置操作

定义节点,存 5 个信息:左右儿子,左右端点,权值。

  1. #define lc a[u].l
  2. #define rc a[u].r
  3. #define L a[u].ls
  4. #define R a[u].rs
  5. struct tree
  6. {
  7. int l,r;
  8. int ls,rs;
  9. int v;
  10. }a[N];

新建节点

  1. int tot;
  2. int New(int u)
  3. {
  4. a[++tot]=a[u];//结构体直接复制所有信息
  5. return tot;
  6. }

建树

和线段树类似,不过节点编号依次增加,但左右儿子不一定是乘二和乘二加一了。

  1. int build(int u,int l,int r)
  2. {
  3. u=++tot;
  4. L=l,R=r;//更新区间端点
  5. if(l==r)
  6. {
  7. a[u].v=w[l];
  8. return u;
  9. }
  10. int mid=(l+r)>>1;
  11. lc=build(lc,l,mid);
  12. rc=build(rc,mid+1,r);
  13. return u;
  14. }

修改

先新建节点,然后在新建的树中修改,要记得更新修改了的儿子。

  1. int modify(int u,int x,int v)
  2. {
  3. u=New(u);
  4. if(L==R) a[u].v=v;
  5. else
  6. {
  7. int mid=(L+R)>>1;
  8. if(x<=mid) lc=modify(lc,x,v);
  9. else rc=modify(rc,x,v);
  10. }
  11. return u;
  12. }

查询

和线段树一毛一样的。

  1. int query(int u,int x)
  2. {
  3. if(L==R) return a[u].v;
  4. else
  5. {
  6. int mid=(L+R)>>1;
  7. if(x<=mid) return query(lc,x);
  8. else return query(rc,x);
  9. }
  10. }

存根

每次修改后最终返回的是新的根节点,所以直接用数组存就好了。

P3919 【模板】可持久化线段树 1(可持久化数组)

P3919 【模板】可持久化线段树 1(可持久化数组)

code

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. typedef long long ll;
  4. #define lc a[u].l
  5. #define rc a[u].r
  6. #define L a[u].ls
  7. #define R a[u].rs
  8. const int M=3e7+5,N=1e6+5;
  9. struct tree
  10. {
  11. int l,r;
  12. int ls,rs;
  13. int v;
  14. }a[M];
  15. int n,tot,w[N],rt,root[N],m;
  16. int New(int u)
  17. {
  18. a[++tot]=a[u];//结构体直接复制所有信息
  19. return tot;
  20. }
  21. int build(int u,int l,int r)
  22. {
  23. u=++tot;
  24. L=l,R=r;//更新区间端点
  25. if(l==r)
  26. {
  27. a[u].v=w[l];
  28. return u;
  29. }
  30. int mid=(l+r)>>1;
  31. lc=build(lc,l,mid);
  32. rc=build(rc,mid+1,r);
  33. return u;
  34. }
  35. int modify(int u,int x,int v)
  36. {
  37. u=New(u);
  38. if(L==R) a[u].v=v;
  39. else
  40. {
  41. int mid=(L+R)>>1;
  42. if(x<=mid) lc=modify(lc,x,v);
  43. else rc=modify(rc,x,v);
  44. }
  45. return u;
  46. }
  47. int query(int u,int x)
  48. {
  49. if(L==R) return a[u].v;
  50. else
  51. {
  52. int mid=(L+R)>>1;
  53. if(x<=mid) return query(lc,x);
  54. else return query(rc,x);
  55. }
  56. }
  57. int main ()
  58. {
  59. ios::sync_with_stdio(false);
  60. cin.tie(0);cout.tie(0);
  61. cin>>n>>m;
  62. for(int i=1;i<=n;i++) cin>>w[i];
  63. root[0]=build(0,1,n);
  64. rt=0;
  65. for(int i=1,op,x,y;i<=m;i++)
  66. {
  67. cin>>rt>>op>>x;
  68. if(op==1) cin>>y,root[i]=modify(root[rt],x,y);
  69. else cout<<query(root[rt],x)<<"\n",root[i]=root[rt];
  70. }
  71. return 0;
  72. }

要注意节点实际个数。

P3834 【模板】可持久化线段树 2

P3834 【模板】可持久化线段树 2

分析

首先,因为数据跨度太大,所以先离散化。嗯,不会?没事,看 离散化

然后,基于数值建立线段树,即权值线段树,维护一段值域内的数的个数。权值线段树求整个区间的第 \(k\) 小的数,就是在权值线段树上二分,从根节点开始,如果 \(k\) 比右儿子大,就说明第 \(k\) 小的数在右儿子所表示的值域中。接着,\(k\) 要减去左儿子,再进入右儿子中继续;如果 \(k\) 比左儿子小,就直接进入左儿子。

那么好,子区间 \([l,r]\) 的第 \(k\) 小的数怎么求呢?(区间都是离散化好了的)
首先思考求 \([1,r]\) 的第 \(k\) 小的数,用主席树,依次加入 \([1,r]\) 的值的节点,生成 \(r\) 个版本,从 \(root[r]\) 开始找就可以了。
那么右边有限制,左边也有限制,该怎么办呢?先明确一点,主席树,每次修改后,对于每个根节点对应的每一棵树,结构都是相同的,就是拓扑排序相同。
也就是说,所有节点对应的区间都是一一对应的。在根节点为 \(root[R]\) 的树中值域为 \([l,r]\) 的个数为 \(cnt1\),在根节点为 \(root[L-1]\) 的树中值域为 \([l,r]\) 的个数为 \(cnt2\),类似于前缀和的思想,\([L,R]\)\([l,r]\) 的个数为 \(cnt1-cnt2\)
这样就可以了,只需要在递归的时候统计个数,找到对应的那个数输出即可。

code

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. #define lc a[u].l
  4. #define rc a[u].r
  5. #define L a[u].ls
  6. #define R a[u].rs
  7. const int N=2e5+5,M=3e7+5;
  8. struct tree
  9. {
  10. int l,r;
  11. int ls,rs;
  12. int cnt;
  13. }a[M];
  14. int n,m,tot,root[N],rt,idx;
  15. int b[N],c[N],C[N];
  16. int New(int u)
  17. {
  18. a[++idx]=a[u];
  19. return idx;
  20. }
  21. void pushup(int u)
  22. {
  23. a[u].cnt=a[lc].cnt+a[rc].cnt;
  24. }
  25. int build(int l,int r)
  26. {
  27. int u=++idx;
  28. L=l,R=r;
  29. if(l==r) return u;
  30. int mid=(l+r)>>1;
  31. lc=build(l,mid);
  32. rc=build(mid+1,r);
  33. return u;
  34. }
  35. int find(int x)
  36. {
  37. return lower_bound(c+1,c+tot+1,x)-c;
  38. }
  39. int modify(int u,int x)
  40. {
  41. u=New(u);
  42. if(L==R)
  43. {
  44. a[u].cnt++;
  45. return u;
  46. }
  47. int mid=(L+R)>>1;
  48. if(x<=mid) lc=modify(lc,x);
  49. else rc=modify(rc,x);
  50. pushup(u);
  51. return u;
  52. }
  53. int query(int u,int v,int x)
  54. {
  55. if(L==R) return R;
  56. int cnt=a[a[v].l].cnt-a[a[u].l].cnt;
  57. if(x<=cnt) return query(a[u].l,a[v].l,x);
  58. else return query(a[u].r,a[v].r,x-cnt);
  59. }
  60. int main ()
  61. {
  62. ios::sync_with_stdio(false);
  63. cin.tie(0);cout.tie(0);
  64. cin>>n>>m;
  65. for(int i=1;i<=n;i++) cin>>b[i],c[i]=b[i];
  66. sort(c+1,c+n+1);
  67. tot=n;//不用去重
  68. root[0]=build(1,tot);
  69. for(int i=1;i<=tot;i++) root[i]=modify(root[i-1],find(b[i]));
  70. while(m--)
  71. {
  72. int l,r,k;
  73. cin>>l>>r>>k;
  74. cout<<c[query(root[l-1],root[r],k)]<<"\n";
  75. }
  76. }

本题离散化不需要去重,因为按值域加点每次只会增加一 QAQ。

原文链接:https://www.cnblogs.com/zhouruoheng/p/17981970

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

本站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号