Link-Cut Tree

Link-Cut Tree 动态树详解
本文详细介绍了Link-Cut Tree(LCT)这一数据结构,它是一种用于维护动态森林的有效工具,支持树的合并、拆分、换根等操作。文章深入探讨了LCT的算法思想、核心功能实现及代码实现细节。

Link-Cut Tree动态树

好难啊o(>﹏<)o

目录


概念与应用

  • 动态树LCT是维护动态森林的一种数据结构,支持树的合并link拆分cut(正如其名),换根make root(我喜欢把这个函数叫be root~(≧▽≦)/~)动态LCA(不会╮(╯﹏╰)╭),和所有树链剖分能支持(不针对子树)的操作;
  • LCT与树剖的区别在于树剖以线段树为基础,而LCT以splay(按原节点深度维护)为基础,这使得LCT相较前者可以支持动态的操作
  • 但虽然LCT每次基本操作复杂度为均摊O(logn),由于其常数较大,比树剖略慢。

算法思想

  • LCT中也有所谓的轻重链的概念(应该叫实虚边,也有叫偏爱边的),但其并划分以子树大小为根据,而是在操作的过程中进行不断修改的;
  • LCT中首尾相连的实边组成一个路径。路径中深度最大的节点为路径的头部,深度最小的节点为路径的尾部;
  • LCT中,对于每条路径,都有一个按深度大小维护的splay来保存信息。类似树剖的性质,任意两条路径间有且只有一条虚边将其相连。所以只需要再单独考虑虚边的维护就可以了;
  • 普通splay的fa(root)==0,但在这里,我们可以令一个splay的root—— rti 的父亲为其所在实边尾部节点的父亲。这不仅不会对其它操作有任何影响,而且还机智地记录了路径之间的关系,即虚边的信息,于是原森林的信息得到完整保存;
  • 有了LCT,动态操作物理上并不对原森林进行修改,只是借助splay的分裂与合并,进行逻辑上的修改
  • 其实,即使是对原森林没有修改的查询操作,LCT也会对splay进行分裂与合并,使要访问的点位于同一路径上(反正LCT里的实边可以随意更改),方便后续操作;
  • 但需要特别说明的是,LCT 并不支持对于子树的操作,但仍可以通过维护虚边的信息来完成一些简单的操作。

核心功能的实现

  • 底层函数

    1. Access接驳 //access不仅有访问的意思,这里其实应该是接驳的意思,即无缝连接(照搬词典O(∩_∩)O)
      将u到root的“路径”(通常说的简单路径)变为路径(LCT里特指的有相连实边组成的路径)u下方的点到u的路径变为虚边

      实现:
      将从u不断向上找父亲,并将其变为父亲的儿子(即将这些点放在同一颗splay里)。

    2. Make Root换根
      将u变为原树的根。

      实现:
      先Access(u),再Reverse(u),把这颗splay中节点的深度全部翻转。

    3. Splay Mergesplay合并 //不知道大家为啥叫它split,那不是分裂的意思吗?(⊙v⊙)
      将u,v加入到同一颗splay中。

      实现:
      先Make Root(u),再Access(v),很好理解。
      ps:这样还隐式的让u为合并后的splay的根节点,v为u的儿子(右)。

    4. Find Root查找根
      查找u所在原树的根节点,即判断u在哪颗树里,通常用于判断两点在原树是否相连。

      实现:
      先Access(u),再把Splay(u)到根,之后一直找左儿子(深度比u小的点),找到最后即为深度最小的点——root。

  • 功能函数

    1. Link连接
      在原森林中连一条边(u,v)。

      实现:
      先Make Root(u),让u为所在原树的根节点(对原树结构其实毫无影响,只是所在路径相对深度大小改变),再让v为u的父亲(即在LCT中连一条虚边(u,v))。

    2. Cut断开
      在原树中将边(u,v)断开。

      实现:
      先Splay Merge(u,v),将u,v放于同一颗splay中,便于操作,再把splay边(u,v)断开。

    3. Modify/Query修改或询问
      修改或询问原树中的一些信息,eg.修改val(u),询问路径(u,v)的点权和等。

      实现:
      对于修改(一般都是单点修改),通常都要先Access(u),再Splay(u)到根节点再改,方便更新;
      对于查询(如路径点权和),提前在每个点里记录一下路径头部到该节点的点权和,直接Splay Merge(u,v),输出sum(u),即可,注意操作时及时更新信息。


代码

洛谷P3690 模板题
注意开读入优化,不然会TLE

#include <cstdio>
#include <algorithm>
using namespace std;

inline int read();

const int MAXN=300005;
int n,m;

struct node{
    int s[2],fa,w,xr;
    bool rev;
};

class LCT{
    private:
        int s[MAXN],top;
        node d[MAXN];
        void upd(int u){
            d[u].xr=d[d[u].s[0]].xr^d[d[u].s[1]].xr^d[u].w;
        }
        void push_d(int u){
            if(!d[u].rev) return;
            d[d[u].s[0]].rev^=1;
            d[d[u].s[1]].rev^=1;
            swap(d[u].s[0],d[u].s[1]);
            d[u].rev=0;
        }
        bool jud_rt(int u){
            return ((d[d[u].fa].s[0]!=u) && (d[d[u].fa].s[1]!=u));
        }
        void rot(int u){
            int ufa=d[u].fa;
            push_d(ufa),push_d(u);
            bool lr= d[ufa].s[1]==u;
            d[u].fa=d[ufa].fa;
            if(!jud_rt(ufa))
                d[d[u].fa].s[d[d[u].fa].s[1]==ufa]=u;
            d[ufa].s[lr]=d[u].s[lr^1];
            d[d[ufa].s[lr]].fa=ufa;
            d[u].s[lr^1]=ufa;
            d[ufa].fa=u;
            upd(ufa),upd(u);
        }
        void spl(int u){
            push_d(u);
            int &ufa=d[u].fa,&ugfa=d[ufa].fa;
            while(!jud_rt(u)){
                if(!jud_rt(ufa)){
                    if((d[ufa].s[0]==u)^(d[ugfa].s[0]==ufa))
                        rot(u);
                    else rot(ufa);
                }
                rot(u);
            }
        }
        void acc(int u){
            for(int v=0;u;v=u,u=d[u].fa){
                spl(u);
                d[u].s[1]=v;
                upd(u);
            }
        }
        void be_rt(int u){
            acc(u);
            spl(u);
            d[u].rev^=1;
        }
        int find_rt(int u){
            acc(u);
            spl(u);
            while(d[u].s[0])
                u=d[u].s[0];
            return u;
        }
        void mg_spl(int u,int v){
            be_rt(u);
            acc(v);
            spl(v);
        }
    public:
        void add(int u,int val){
            d[u].w=d[u].xr=val;
        }
        int qry_xr(int u,int v){
            mg_spl(u,v);
            return d[v].xr;
        }
        void link(int u,int v){
            int urt=find_rt(u),vrt=find_rt(v);
            if(urt==vrt) return;
            be_rt(u);
            d[u].fa=v;
        }
        void cut(int u,int v){
            int urt=find_rt(u),vrt=find_rt(v);
            if(urt!=vrt) return;
            mg_spl(u,v);
            if(d[v].s[0]==u){
                d[v].s[0]=0;
                d[u].fa=0;
            }
        }
        void mdf(int u,int x){
            acc(u);
            spl(u);
            d[u].w=x;
            upd(u);
        }
}T;

int main(){
    n=read(),m=read();
    for(int i=1,tmp;i<=n;++i){
        tmp=read();
        T.add(i,tmp);
    }
    for(int i=1,opt,x,y;i<=m;++i){
        opt=read(),x=read(),y=read();
        switch(opt){
            case 1: T.link(x,y); break;
            case 2: T.cut(x,y); break;
            case 3: T.mdf(x,y); break;
            default:printf("%d\n",T.qry_xr(x,y));
        }
    }
    return 0;
}

inline int read(){
    char c; int x;
    while(c=getchar(),c<'0' || '9'<c);
    x=c-'0';
    while(c=getchar(),'0'<=c && c<='9')
        x=(x<<3)+(x<<1)+c-'0';
    return x;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值