经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
【学习笔记】 - 基础数据结构 :Link-Cut Tree
来源:cnblogs  作者:Vsinger_洛天依  时间:2024/2/26 15:10:55  对本文有异议

发现树剖代码太长了,给我恶心坏了

学个代码短点的能写树剖题的数据结构吧

前置知识

简介以及优缺点介绍

Link-Cut Tree,也就是LCT,一般用于解决动态树问题

Link-Cut Tree可用于实现重链剖分的绝大多数问题,复杂度为\(O(n \log n)\),看起来比树剖的\(O(n \log^2 n)\)复杂度更小,但则不然,基于splay实现的Link-Cut Tree常数巨大(约11倍常数),往往表现不如树剖

Link-Cut Tree的代码往往比树剖少一些

动态树问题

维护一个森林,支持删除某条边,连接某条边,并保证加边/删边之后仍是森林

同时维护这个森林的一些信息

实链剖分

  • 回顾重链剖分

    • 按子树大小剖分整棵树并重新标号

    • 此时树上形成了一些以链为单位的连续区间,用线段树进行区间操作

我们发现,诶重剖怎么是按子树大小来剖的,这也不能搞动态树啊

显然我们需要让剖分的链是我们指定的链,以便利用来求解

  • 实链剖分

    对于一个点连向它所有儿子的边,我们自己选择一条边进行剖分,我们称被选择的边为实边,其他边则为虚边。

    我们称实边所连接的儿子为实儿子,实边组成的链称之为实链

    选择实链剖分的最重要的原因便是因为实链是我们选择的,灵活且可变

    正是它的这种灵活可变性,用 Splay 来维护这些实链

Link-Cut Tree

我们可以把 LCT 理解为用一些 Splay 来维护动态树剖并实现动态树上的区间操作

每条实链都建一个 Splay 维护整个链的区间信息

  • 辅助树

    我们认为一些 Splay 共同构成了一颗辅助树,每个辅助树都维护了一颗树,所有的辅助树构成了 Link-Cut Tree,维护了整个森林

    辅助树有很多性质

    • 辅助树由多棵 Splay 组成,每棵 Splay 都维护了树中一条严格在原树中「从上到下」深度单调递增的路径,且中序遍历这棵 Splay 得到的点的深度序列单调递增

    • 原本的树的每个节点与辅助树的 Splay 节点一一对应。

    • 辅助树各棵 Splay 间并不独立。在 LCT 中每棵 Splay 的根节点的父亲节点指向原树中这条链的父亲节点(即链最顶端的点的父亲节点)。

      特殊的,这里的儿子认父亲,父亲却不认儿子,对应原树的一条 虚边

      故每个连通块恰好有一个点的父亲节点为空

    • 维护任何操作都不需要维护原树

      辅助树可以在任何情况下拿出一个唯一的原树

      只需维护辅助树即可

    这是一颗原树 \(\gets\)

    这是建出的辅助树 \(\gets\)

代码实现

这里只有 LCT 特有的几个操作

  • 数组定义

    1. fa[x] //x的父亲节点
    2. son[x][2] //x的左右儿子
    3. sz[x] //x的子树大小
    4. rev[x] //x是否需要对儿子进行翻转
  • splay操作

    和正常splay不同的是LCT的每次splay影响的所有点都必须是当前splay中的钱

    而且在splay操作前必须把它的所有祖先全都pushdown,因为LCT不一定把哪个点应用splay操作

    • 代码

      1. inline bool isroot(int x){
      2. return ((son[fa[x]][0]==x)||(son[fa[x]][1]==x));
      3. }
      4. inline void splay(int x){
      5. int y=x,z=0;
      6. st[++z]=y;
      7. while(isroot(y)){
      8. st[++z]=y=fa[y];
      9. }
      10. while(z){
      11. push_down(st[z--]);
      12. }
      13. while(isroot(x)){
      14. y=fa[x],z=fa[y];
      15. if(isroot(y))
      16. rotate((son[y][0]==x)^(son[z][0]==y)?x:y);
      17. rotate(x);
      18. }
      19. push_up(x);
      20. }
  • access操作

    LCT最重要的操作,其他所有操作都要用到它

    含义是访问某节点,作用是对于访问的节点 \(x\) 打通一条从树根到 \(x\) 的实链

    如果有其他实边与新的实链相连则改为轻边

    可以理解为专门开辟一条从 \(x\)\(root\) 的路径,用splay来维护这条路径

    • 实现方法

      先把 \(x\) 旋转到所在Splay的根

      \(y\) 记录上一次的 \(x\) (初始化\(y=0\)),把 \(y\) 接到 \(x\) 的右儿子上

      这样就把上一次的实链接到了当前实链下

      它原来的右儿子(也就是LCT树中在 \(x\) 下方的点)与它所有的边自然变成了虚边

      记得pushup

    • 代码

      1. inline void access(int x){
      2. for(int y=0;x;x=fa[y=x])
      3. splay(x),
      4. rc=y,push_up(x);
      5. }
  • 换根操作

    作用是把某个节点变成树根(这里的根指的是整颗LCT的根)

    再加上access操作就能方便的提取出LCT上两点之间距离

    提取\(u\)\(v\)的路径只需要toroot(u),access(v),然后\(v\)所在的Splay对应的链就是\(u\)\(v\)的路径

    • 实现方法

      access 一下,这样 \(x\) 就一路打通到了根,然后再splay(x),由于x是这条实链最下面的点,所以 \(x\)splay 的右儿子是空的,左儿子是它上面所有点

      因为 splay 是支持区间翻转的,所以只要给x打个翻转标记就翻转到根了

    • 代码

      1. inline void toroot(int x){
      2. access(x);
      3. splay(x);
      4. reserve(x);
      5. }
  • link操作

    作用是链接两个辅助树,对于link(u,v),表示 \(u\) 所在的辅助树和 \(v\) 所在的辅助树

    • 实现方法

      只需要先toroot(u),然后记 fa[u]=v 就可以了,就是把一整颗辅助树连到另一个点上

    • 代码

      1. inline void link(int x,int y){
      2. toroot(x);
      3. if(Find(y)!=x)
      4. fa[x]=y;
      5. }
  • cut操作

    这个操作作用是切断某条边

    • 实现方法

      先分离出 \(x\)\(y\) 的这条链

      我们假设切断的点一定是相邻的(不相邻的特判掉),然后把 \(y\) 的左儿子(也就是 LCT\(y\) 的父亲)与 \(y\) 的边断掉就好了

    • 代码

      1. inline void split(int x,int y){
      2. toroot(x);
      3. access(y);
      4. splay(y);
      5. }
      6. inline int Find(int x){
      7. access(x);
      8. splay(x);
      9. while(lc)
      10. push_down(x),x=lc;
      11. splay(x);
      12. return x;
      13. }
      14. inline void cut(int x,int y){
      15. toroot(x);
      16. if(Find(y)==x&&fa[y]==x&&!son[y][0]){
      17. fa[y]=son[x][1]=0;
      18. push_up(x);
      19. }
      20. }

完整代码

模板题

点击查看代码
  1. #define lc son[x][0]
  2. #define rc son[x][1]
  3. int fa[N],son[N][2],val[N],ans[N],st[N];
  4. bool rev[N];
  5. inline bool isroot(int x){
  6. return ((son[fa[x]][0]==x)||(son[fa[x]][1]==x));
  7. }
  8. inline void push_up(int x){
  9. ans[x]=ans[lc]^ans[rc]^val[x];
  10. }
  11. inline void reserve(int x){
  12. int t=lc;
  13. lc=rc;rc=t;
  14. rev[x]^=1;
  15. }
  16. inline void push_down(int x){
  17. if(rev[x]){
  18. if(lc)reserve(lc);
  19. if(rc)reserve(rc);
  20. rev[x]=0;
  21. }
  22. }
  23. inline void rotate(int x){
  24. int y=fa[x],z=fa[y],k=son[y][1]==x,w=son[x][!k];
  25. if(isroot(y))
  26. son[z][son[z][1]==y]=x;
  27. son[x][!k]=y;
  28. son[y][k]=w;
  29. if(w)
  30. fa[w]=y;
  31. fa[y]=x;fa[x]=z;
  32. push_up(y);
  33. }
  34. inline void splay(int x){
  35. int y=x,z=0;
  36. st[++z]=y;
  37. while(isroot(y)){
  38. st[++z]=y=fa[y];
  39. }
  40. while(z){
  41. push_down(st[z--]);
  42. }
  43. while(isroot(x)){
  44. y=fa[x],z=fa[y];
  45. if(isroot(y))
  46. rotate((son[y][0]==x)^(son[z][0]==y)?x:y);
  47. rotate(x);
  48. }
  49. push_up(x);
  50. }
  51. inline void access(int x){
  52. for(int y=0;x;x=fa[y=x])
  53. splay(x),
  54. rc=y,push_up(x);
  55. }
  56. inline void toroot(int x){
  57. access(x);
  58. splay(x);
  59. reserve(x);
  60. }
  61. inline int Find(int x){
  62. access(x);
  63. splay(x);
  64. while(lc)
  65. push_down(x),x=lc;
  66. splay(x);
  67. return x;
  68. }
  69. inline void split(int x,int y){
  70. toroot(x);
  71. access(y);
  72. splay(y);
  73. }
  74. inline void link(int x,int y){
  75. toroot(x);
  76. if(Find(y)!=x)
  77. fa[x]=y;
  78. }
  79. inline void cut(int x,int y){
  80. toroot(x);
  81. if(Find(y)==x&&fa[y]==x&&!son[y][0]){
  82. fa[y]=son[x][1]=0;
  83. push_up(x);
  84. }
  85. }
  86. signed main(){
  87. int n,m;FastI>>n>>m;
  88. for(int i=1;i<=n;++i)
  89. FastI>>val[i];
  90. while(m--){
  91. int opt,x,y;
  92. FastI>>opt>>x>>y;
  93. if(opt==0){
  94. split(x,y);
  95. FastO<<ans[y]<<endl;
  96. }
  97. else if(opt==1){
  98. link(x,y);
  99. }
  100. else if(opt==2){
  101. cut(x,y);
  102. }
  103. else if(opt==3){
  104. splay(x);
  105. val[x]=y;
  106. }
  107. }
  108. }

原文链接:https://www.cnblogs.com/Vsinger-LuoTianYi/p/18029661

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

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