首页 > 其他分享 >【学习笔记】 - 基础数据结构 :Link-Cut Tree

【学习笔记】 - 基础数据结构 :Link-Cut Tree

时间:2024-02-23 20:56:33浏览次数:26  
标签:Cut int void Tree son splay fa Link inline

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

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

前置知识

简介以及优缺点介绍

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 特有的几个操作

  • 数组定义

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

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

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

    • 代码

      inline bool isroot(int x){
          return ((son[fa[x]][0]==x)||(son[fa[x]][1]==x));
      }
      inline void splay(int x){
          int y=x,z=0;
          st[++z]=y;
          while(isroot(y)){
              st[++z]=y=fa[y];
          }
          while(z){
              push_down(st[z--]);
          }
          while(isroot(x)){
              y=fa[x],z=fa[y];
              if(isroot(y))
                  rotate((son[y][0]==x)^(son[z][0]==y)?x:y);
              rotate(x);
          }
          push_up(x);
      }
      
  • access操作

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

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

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

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

    • 实现方法

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

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

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

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

      记得pushup

    • 代码

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

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

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

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

    • 实现方法

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

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

    • 代码

      inline void toroot(int x){
          access(x);
          splay(x);
          reserve(x);
      }
      
  • link操作

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

    • 实现方法

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

    • 代码

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

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

    • 实现方法

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

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

    • 代码

      inline void split(int x,int y){
          toroot(x);
          access(y);
          splay(y);
      }
      inline int Find(int x){
          access(x);
          splay(x);
          while(lc)
              push_down(x),x=lc;
          splay(x);
          return x;
      }
      inline void cut(int x,int y){
          toroot(x);
          if(Find(y)==x&&fa[y]==x&&!son[y][0]){
              fa[y]=son[x][1]=0;
              push_up(x);
          }
      }
      

完整代码

模板题

点击查看代码
#define lc son[x][0]
#define rc son[x][1]
int fa[N],son[N][2],val[N],ans[N],st[N];
bool rev[N];
inline bool isroot(int x){
    return ((son[fa[x]][0]==x)||(son[fa[x]][1]==x));
}
inline void push_up(int x){
    ans[x]=ans[lc]^ans[rc]^val[x];
}
inline void reserve(int x){
    int t=lc;
    lc=rc;rc=t;
    rev[x]^=1;
}
inline void push_down(int x){
    if(rev[x]){
        if(lc)reserve(lc);
        if(rc)reserve(rc);
        rev[x]=0;
    }
}
inline void rotate(int x){
    int y=fa[x],z=fa[y],k=son[y][1]==x,w=son[x][!k];
    if(isroot(y))
        son[z][son[z][1]==y]=x;
    son[x][!k]=y;
    son[y][k]=w;
    if(w)
        fa[w]=y;
    fa[y]=x;fa[x]=z;
    push_up(y);
}
inline void splay(int x){
    int y=x,z=0;
    st[++z]=y;
    while(isroot(y)){
        st[++z]=y=fa[y];
    }
    while(z){
        push_down(st[z--]);
    }
    while(isroot(x)){
        y=fa[x],z=fa[y];
        if(isroot(y))
            rotate((son[y][0]==x)^(son[z][0]==y)?x:y);
        rotate(x);
    }
    push_up(x);
}
inline void access(int x){
    for(int y=0;x;x=fa[y=x])
        splay(x),
        rc=y,push_up(x);
}
inline void toroot(int x){
    access(x);
    splay(x);
    reserve(x);
}
inline int Find(int x){
    access(x);
    splay(x);
    while(lc)
        push_down(x),x=lc;
    splay(x);
    return x;
}
inline void split(int x,int y){
    toroot(x);
    access(y);
    splay(y);
}
inline void link(int x,int y){
    toroot(x);
    if(Find(y)!=x)
        fa[x]=y;
}
inline void cut(int x,int y){
    toroot(x);
    if(Find(y)==x&&fa[y]==x&&!son[y][0]){
        fa[y]=son[x][1]=0;
        push_up(x);
    }
}
signed main(){
    int n,m;FastI>>n>>m;
    for(int i=1;i<=n;++i)
        FastI>>val[i];
    while(m--){
        int opt,x,y;
        FastI>>opt>>x>>y;
        if(opt==0){
            split(x,y);
            FastO<<ans[y]<<endl;
        }
        else if(opt==1){
            link(x,y);
        }
        else if(opt==2){
            cut(x,y);
        }
        else if(opt==3){
            splay(x);
            val[x]=y;
        }
    }
}

标签:Cut,int,void,Tree,son,splay,fa,Link,inline
From: https://www.cnblogs.com/Vsinger-LuoTianYi/p/18029661

相关文章

  • CF916E Jamie and Tree 题解
    题目链接:CF或者洛谷本题难点在于换根LCA与换根以后的子树范围寻找,重点讲解先说操作一,假如原根为\(1\)变为了\(x\),又变为了\(y\),那么其实\(y\)和\(x\)都可以看做由\(1\)变化而来的,即\(1\rightarrowx\)与\(1\rightarrowy\),原因很简单,我们可以把\(1\rightar......
  • 基于Mamdani模糊神经网络的调速控制系统simulink建模与仿真
    1.算法运行效果图预览   2.算法运行软件版本matlab2022a 3.算法理论概述      基于Mamdani模糊神经网络的调速控制系统是一种结合模糊逻辑与神经网络技术的智能控制方法,旨在提高调速系统的性能。随着工业技术的不断发展,对调速控制系统的性能要求也越来越高。......
  • flink实时读取kafka数据到mysql flink 读取kafka 依赖 Flink 1.8.0
    flink实时读取kafka数据到mysqlflink读取kafkaFlink提供了Kafka连接器,用于从或向Kafka读写数据。本文总结Flink与Kafka集成中的问题,并对一些疑点进行总结和梳理。问题一:读Kafka的方式登录后复制##读取一个TopicFlinkKafkaConsumer010#FlinkKafkaConsumer010(Stringtopi......
  • flink之核心抽象--Window窗口及窗口操作全面详解
    flink之核心抽象--Window窗口及窗口操作全面详解标签:flink 窗口 String val -- 元素 Long window1.Windows1.1.基本概念窗口是处理无限流的核心。窗口将流划分为固定大小的“桶”,方便程序员在上面应用各种计算。Window操作是流式数据处理的一种非常核心的抽象,......
  • flink 窗口函数 中文解释和案例
    flink窗口函数中文解释和案例文章目录窗口函数时间语义处理时间事件时间摄入时间水位线有序流中的水位线乱序流中的水位线生成水位线生成水位线原则水位线生成策略flink内置水位线生成器有序流乱序流自定义水位线周期性水位线生成器断点式水位线生成器水位线的传递......
  • LiRank: LinkedIn在2月新发布的大规模在线排名模型
    LiRank是LinkedIn在2月份刚刚发布的论文,它结合了最先进的建模架构和优化技术,包括残差DCN、密集门控模块和Transformers。它引入了新的校准方法,并使用基于深度学习的探索/利用策略来优化模型,并且通过压缩技术,如量化和词表压缩,实现了高效部署。LinkedIn将其应用于Feed、职位推荐和......
  • Flink教程(6)-Flink Window 详解
    Flink教程(6)-FlinkWindow详解文章目录FlinkWindowwindow原理与分类基于时间的TimeWindow基于计数的CountWindowwindowapi1)滚动时间窗口2)滑动时间窗口3)会话窗口4)滚动计数窗口5)滑动计数窗口6)全量窗口7)简写方式8)WindowAssigner9)开窗函数(windowfunction)10)案例:自定义数据......
  • K-DTree 学习笔记
    原理:沿$x$轴,$y$轴交替依次按坐标点的中位数对半分开,直到只剩下一个点为止。复杂度分析:考虑一条边只会横跨两个区间,所以沿坐标轴划分矩形数量与边界划分数量是同阶的。有$T(n)=2\timesT(\frac{n}{4})+O(1)$,单次操作复杂度是$\sqrtn$的。例题: $\mathbb{T1}\\\text......
  • Machine Learning - A Forest of Trees
    BuildaRandomForestmodel.TaskYouwillbegivenafeaturematrixXandtargetarrayy.Yourtaskistosplitthedataintotrainingandtestsets,buildaRandomForestmodelwiththetrainingset,andmakepredictionsforthetestset.Givetherandom......
  • HarmonyOS—@Observed装饰器和@ObjectLink嵌套类对象属性变化
    @Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化概述@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:被@Observed装饰的类,可以被观察到属性的变化;子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件......