浅谈Link-Cut-Tree([林可砍树]LCT动态树)附例题 Hdu4010

其实一直对LCT很好奇,到底是个什么数据结构可以搞这么多东西,还可以动态地维护,支持加边,合并树,查询等操作,比链剖要强大很多


学过链剖的同学应该可以很快地理解LCT的思路,LCT的实现需要构造辅助树,我们通常选用splay,因为splay方便的序列操作使得LCT的实现显得得心应手,尤其是在换根的时候,只需要splay树上打上一个标记即可

那么LCT到底可以干什么呢?

我们来浅显地介绍这样一个神奇的数据结构

1.支持树链操作,包括树链的边权的加减,查询最大值最小值等,且复杂度均摊是logn的。

看到这里的同学可能会说,这些链剖也可以搞啊,而且链剖那么好写,10分钟写完调完

然而LCT好像并没有那么简单

2.LCT支持加边操作,这个链剖就没法搞了吧,我要把两棵树合到一起,然后再查询跨越两棵树的链的信息,显然这时候用链剖就是大暴力

3.LCT支持换根操作,也就是把一个点和当前的根置换,其实开始我一直理解错了这个换根,以为就只是换一个那两个节点,其实不是的,我们看一张图
这里写图片描述
换根大概是这么个操作,树都还是连在一起的,这个链剖没法搞吧

3.LCT还支持很多很多自己的操作,比如JOIN,CUT等,下面会详细讲解

….


首先,介绍一下一些LCT稍微学术化的概念

如果一个点被访问了,那么指刚刚对这个点执行了ACCESS操作,什么是ACCESS操作,现在可以不必管,大概是把一个点到根拉成一条链吧,想像链剖分轻重链,如果这个点被访问了,那么他就是他father的Preferred Child,并且这个点到father的边被称为Preferred Edge,Preferred Edge连成的边自然就叫Preferred Path啦,大家可以参考一下刘汝佳的图片,这里我随便画个图,大概表示一下这个ACCESS(x)的操作

这里写图片描述
然后这棵树被划分了,也就是如图所示的Preferred_Path和普通的树边, Preferred_Path连起来的所有点是被一棵splay树维护的,而其他所有独立的点(比如上图的左边两点,右边的图中,都分别是一棵splay树,也就是只有一个节点splay树)
好吧大家感性认知一下就好,之后还要理性理解的,放心

ACCESS操作

首先如果我们都知道splay的各种操作,现在,就可以来搞LCT的ACCESS操作了,先附代码

inline void access(int loc){
        for(register int i=0;loc;i=loc,loc=tree[loc].fa){
        //首先,对于一棵平衡树,如果不是根节点,那么father是正常指的,但是如果是根节点,指的应该是该平衡树的根节点的father,见下图,一直到loc=0,也就是根的位置,loc一直往fa上找
            splay(loc);//先吧loc splay到loc所在splay树的根,然后找fa,就可以很快地直接跳到下一棵splay树上,是非常高效的
            tree[loc].child[1]=i;//i的位置在loc的下面,按照深度,深度比loc大,成为loc的右儿子
            pushup(loc);//由于旋转了loc,所以更新信息
        }   
    }

这里写图片描述
显然如果现在我对z,y,x这棵平衡树,进行splay(y)操作,y变成了这棵平衡树的根节点,那么以深度来排平衡树的key值的话,z点是y的左儿子,x是y的右儿子,显然,z和x的father都是y,而y的father,则应该是w,这就是我们构造了辅助树(我们通常选用splay树作为这个辅助树),一定要分清father和son的关系


这个操作可以把一个指定的点到树拉通,但显然需要熟悉地写出splay树,那么下面我们来写splay树(如果很熟练的朋友可以直接跳这部分啦)

inline bool isroot(int x){
        return tree[tree[x].fa].child[0]!=x&&tree[tree[x].fa].child[1]!=x;
    }//判断x节点是不是其所在splay的根,显然根据之前所讲splay的father连接关系,如果其father的左子和右子都不是他,那么显然他是根,下面有图示例子
    inline void add(int loc,int val){
        tree[loc].val+=val;tree[loc].maxn+=val;tree[loc].add+=val;
    }
    //进行add操作,对于这棵splay树的根节点进行val的累加,maxn的累加和标记的累加,注意改变这棵splay树的结构的时候一定要记得pushdown
    inline void pushup(int loc){
        tree[loc].maxn=max(tree[tree[loc].child[0]].maxn,tree[tree[loc].child[1]].maxn);
        tree[loc].maxn=max(tree[loc].maxn,tree[loc].val);
    }
    //pushup看起来是非常清晰得,这里不再过多解释,这里只以维护最大值为例
    inline void pushdown(int loc){
        if(tree[loc].rev){
            tree[loc].rev^=1;tree[tree[loc].child[0]].rev^=1;tree[tree[loc].child[1]].rev^=1;
            swap(tree[loc].child[0],tree[loc].child[1]);
        }//splay写区间翻转就是好,区间翻转就是换根,之后会讲解为什么就是换根
        if(tree[loc].add){
            if(tree[loc].child[0]) add(tree[loc].child[0],tree[loc].add);
            if(tree[loc].child[1]) add(tree[loc].child[1],tree[loc].add);//不要忘了左右子树的判空
            tree[loc].add=0;
        }
    }
    //pushdown操作也是很清晰的操作,会平衡树和线段树等数据结构的朋友应该可以迅速看懂
    inline void Pushdown(int loc){
        if(!isroot(loc)) Pushdown(tree[loc].fa);
        pushdown(loc);//显然一直找到该树的根,然后下传根的标记,因为我们之前打的所有标记都是在root上
    }
    inline void rotate(int x){
        int y=tree[x].fa,z=tree[y].fa,l,r;
        if(tree[y].child[0]==x)l=0;else l=1;r=l^1;
        if(!isroot(y)){
            if(tree[z].child[0]==y) tree[z].child[0]=x;
            else tree[z].child[1]=x; 
        }
        tree[x].fa=z;tree[y].fa=x;tree[y].child[l]=tree[x].child[r];
        tree[tree[x].child[r]].fa=y;tree[x].child[r]=y;
        pushup(y);pushup(x);
    }
    inline void splay(int x){
        Pushdown(x);
        while(!isroot(x)){
            int y=tree[x].fa,z=tree[y].fa;
            if(!isroot(y)){
                if(tree[y].child[0]==loc^tree[z].child[0]) rotate(loc);
                else rotate(y);
            }
            rotate(loc);
        }
    }//splay及其rotate操作,Orz hzwer,不多说

关于isroot函数
这里写图片描述
显然如果现在y是其所在splay树的根节点,那么他的fa一定是连向另一颗splay树的,显然对于另一个splay树的lson和rson都不会连回这棵树,否则就是一棵splay树了


接下来就是几个比较有趣的函数了

比如findroot函数,这个函数是要求loc所在的树(并非辅助树)的根

inline int findroot(int loc){
        access(loc);splay(loc);
        //先将loc所在的到根的链拉成一条,然后把loc旋转到根,显然我们的splay树是用深度来排的序,那么一直往左找,直到不能往下找了,就找到根了
        while(tree[loc].child[0]) loc=tree[loc].child[0];
        return loc;
    }

reverse函数

    inline void reverse(int loc){
        access(loc);splay(loc);tree[loc].rev^=1;
    }

reverse也就是换根操作,也就是splay的区间翻转操作


    inline int link(int x,int y){
        if(findroot(x)==findroot(y)||x==y) return -1;
        reverse(x);tree[x].fa=y;
        return 0;
    }

link函数

    inline int link(int x,int y){
        if(findroot(x)==findroot(y)||x==y) return -1;
        reverse(x);tree[x].fa=y;
        return 0;
    }

显然,这个link函数是把两棵splay森林,也就是LCT连起来,如果x的root和y的root(注意是真正树结构的root,不是splay树的root)或者是x==y,那么说明不能连接,否则直接把x所在的树换根为x,然后再把x的fa设置成y即可[这里有一点值得注意,findroot的时候其实已经access并且splay过x了,所以直接reverse]


cut操作

    inline int cut(int x,int y){
        if(findroot(x)!=findroot(y)||x==y) return -1;//也是保险起见,判一下
        reverse(x);access(y);splay(y);//同样的操作
        tree[tree[y].child[0]].fa=0;tree[y].child[0]=0;pushup(y);//然后切断y与x的边,使其独立成两片splay森林(独立成两棵树)
        return 0;
    }

其他操作,这里以Add和query为代表

    inline int Add(int x,int y,int val){
        if(findroot(x)!=findroot(y)) return -1;//保险起见,特判
        reverse(x);access(y);splay(y);add(y,val);
        return 0;//如果忘了换根之后树的结构变化的同学,可以再看一下前面的图,显然x换成根之后,再access一下y,splay一下y,y就变成了x到y这条链的splay树的根节点,然后就可以add了啦
    }

query是很简单的,不做讲解(如果学懂了Add的话)

    inline int query(int x,int y){
        if(findroot(x)!=findroot(y)) return -1;
        reverse(x);access(y);splay(y);
        return tree[y].maxn;
    }

完整LCT封装结构体代码

struct Link_Cut_Tree{
    inline bool isroot(int x){
        return tree[tree[x].fa].child[0]!=x&&tree[tree[x].fa].child[1]!=x;
    }
    inline void add(int loc,int val){
        tree[loc].val+=val;tree[loc].maxn+=val;tree[loc].add+=val;
    }
    inline void pushup(int loc){
        tree[loc].maxn=max(tree[tree[loc].child[0]].maxn,tree[tree[loc].child[1]].maxn);
        tree[loc].maxn=max(tree[loc].maxn,tree[loc].val);
    }
    inline void pushdown(int loc){
        if(tree[loc].rev){
            tree[loc].rev^=1;tree[tree[loc].child[0]].rev^=1;tree[tree[loc].child[1]].rev^=1;
            swap(tree[loc].child[0],tree[loc].child[1]);
        }
        if(tree[loc].add){
            if(tree[loc].child[0]) add(tree[loc].child[0],tree[loc].add);
            if(tree[loc].child[1]) add(tree[loc].child[1],tree[loc].add);
            tree[loc].add=0;
        }
    }
    inline void Pushdown(int loc){
        if(!isroot(loc)) Pushdown(tree[loc].fa);
        pushdown(loc);
    }
    inline void rotate(int x){
        int y=tree[x].fa,z=tree[y].fa,l,r;
        if(tree[y].child[0]==x)l=0;else l=1;r=l^1;
        if(!isroot(y)){
            if(tree[z].child[0]==y) tree[z].child[0]=x;
            else tree[z].child[1]=x; 
        }
        tree[x].fa=z;tree[y].fa=x;tree[y].child[l]=tree[x].child[r];
        tree[tree[x].child[r]].fa=y;tree[x].child[r]=y;
        pushup(y);pushup(x);
    }
    inline void splay(int x){
        Pushdown(x);
        while(!isroot(x)){
            int y=tree[x].fa,z=tree[y].fa;
            if(!isroot(y)){
                if(tree[y].child[0]==loc^tree[z].child[0]) rotate(loc);
                else rotate(y);
            }
            rotate(loc);
        }
    }
    inline void access(int loc){
        for(register int i=0;loc;i=loc,loc=tree[loc].fa){
            splay(loc);
            tree[loc].child[1]=i;
            pushup(loc);
        }   
    }
    inline int findroot(int loc){
        access(loc);splay(loc);
        while(tree[loc].child[0]) loc=tree[loc].child[0];
        return loc;
    }
    inline void reverse(int loc){
        access(loc);splay(loc);tree[loc].rev^=1;
    }
    inline int link(int x,int y){
        if(findroot(x)==findroot(y)||x==y) return -1;
        reverse(x);tree[x].fa=y;
        return 0;
    }
    inline int cut(int x,int y){
        if(findroot(x)!=findroot(y)||x==y) return -1;
        reverse(x);access(y);splay(y);
        tree[tree[y].child[0]].fa=0;tree[y].child[0]=0;pushup(y);
        return 0;
    }
    inline int Add(int x,int y,int val){
        if(findroot(x)!=findroot(y)) return -1;
        reverse(x);access(y);splay(y);add(y,val);
        return 0;
    }
    inline int query(int x,int y){
        if(findroot(x)!=findroot(y)) return -1;
        reverse(x);access(y);splay(y);
        return tree[y].maxn;
    }
}LCT;

下面是一道例题 HDOJ 4010 我保证我坚决不再刷杭电OJ的题了,太坑了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
const int MAXN=300010;
using namespace std;
inline int readin(){
    int sum=0,fg=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')fg=-1;c=getchar();}
    while(c>='0'&&c<='9'){sum=sum*10+c-'0';c=getchar();}
    return sum*fg;
}
struct lct{
    int fa,child[2],rev,add;
    int val,maxn;
}tree[MAXN];
int n,Q;
struct Link_Cut_Tree{
    inline bool isroot(int x){
        return tree[tree[x].fa].child[0]!=x&&tree[tree[x].fa].child[1]!=x;
    }
    inline void add(int loc,int val){
        tree[loc].val+=val;tree[loc].maxn+=val;tree[loc].add+=val;
    }
    inline void pushup(int loc){
        tree[loc].maxn=max(tree[tree[loc].child[0]].maxn,tree[tree[loc].child[1]].maxn);
        tree[loc].maxn=max(tree[loc].maxn,tree[loc].val);
    }
    inline void pushdown(int loc){
        if(tree[loc].rev){
            tree[loc].rev^=1;tree[tree[loc].child[0]].rev^=1;tree[tree[loc].child[1]].rev^=1;
            swap(tree[loc].child[0],tree[loc].child[1]);
        }
        if(tree[loc].add){
            if(tree[loc].child[0]) add(tree[loc].child[0],tree[loc].add);
            if(tree[loc].child[1]) add(tree[loc].child[1],tree[loc].add);
            tree[loc].add=0;
        }
    }
    inline void Pushdown(int loc){
        if(!isroot(loc)) Pushdown(tree[loc].fa);
        pushdown(loc);
    }
    inline void rotate(int x){
        int y=tree[x].fa,z=tree[y].fa,l,r;
        if(tree[y].child[0]==x)l=0;else l=1;r=l^1;
        if(!isroot(y)){
            if(tree[z].child[0]==y) tree[z].child[0]=x;
            else tree[z].child[1]=x; 
        }
        tree[x].fa=z;tree[y].fa=x;tree[y].child[l]=tree[x].child[r];
        tree[tree[x].child[r]].fa=y;tree[x].child[r]=y;
        pushup(y);pushup(x);
    }
    inline void splay(int x){
        Pushdown(x);
        while(!isroot(x)){
            int y=tree[x].fa,z=tree[y].fa;
            if(!isroot(y)){
                if((tree[y].child[0]==x)^(tree[z].child[0]==y)) rotate(x);
                else rotate(y);
            }
            rotate(x);
        }
    }
    inline void access(int loc){
        for(register int i=0;loc;i=loc,loc=tree[loc].fa){
            splay(loc);
            tree[loc].child[1]=i;
            pushup(loc);
        }   
    }
    inline int findroot(int loc){
        access(loc);splay(loc);
        while(tree[loc].child[0]) loc=tree[loc].child[0];
        return loc;
    }
    inline void reverse(int loc){
        access(loc);splay(loc);tree[loc].rev^=1;
    }
    inline int link(int x,int y){
        if(findroot(x)==findroot(y)||x==y) return -1;
        reverse(x);tree[x].fa=y;
        return 0;
    }
    inline int cut(int x,int y){
        if(findroot(x)!=findroot(y)||x==y) return -1;
        reverse(x);access(y);splay(y);
        tree[tree[y].child[0]].fa=0;tree[y].child[0]=0;pushup(y);
        return 0;
    }
    inline int Add(int x,int y,int val){
        if(findroot(x)!=findroot(y)) return -1;
        reverse(x);access(y);splay(y);add(y,val);
        return 0;
    }
    inline int query(int x,int y){
        if(findroot(x)!=findroot(y)) return -1;
        reverse(x);access(y);splay(y);
        return tree[y].maxn;
    }
}LCT;
struct Line{
    int to,nxt;
}line[MAXN];
int head[MAXN],tail;
void add_line(int from,int to){
    tail++;
    line[tail].nxt=head[from];
    line[tail].to=to;
    head[from]=tail;
}
void dfs(int u,int fa){
    for(register int i=head[u];i;i=line[i].nxt){
        int v=line[i].to;
        if(v!=fa){
            tree[v].fa=u;
            dfs(v,u);
        }
    }
}
void init(){
    memset(head,0,sizeof(head));tail=0;
    memset(tree,0,sizeof(tree));
}
int main(){
    int n;
    while(scanf("%d",&n)!=EOF){
        init();
        int from,to;
        for(int i=1;i<=n-1;i++){
            from=readin();to=readin();
            add_line(from,to);add_line(to,from);
        }
        dfs(1,0);
        int temp;
        for(int i=1;i<=n;i++){
            temp=readin();
            tree[i].val=tree[i].maxn=temp;
        }
        Q=readin();
        while(Q--){
            int opt;
            opt=readin();
            int x,y,z;
            switch(opt){
                case 1: x=readin();y=readin();if(LCT.link(x,y)==-1) printf("-1\n");break;
                case 2: x=readin();y=readin();if(LCT.cut(x,y)==-1)printf("-1\n");break;
                case 3: z=readin();x=readin();y=readin();if(LCT.Add(x,y,z)==-1)printf("-1\n");break;
                case 4: x=readin();y=readin();printf("%d\n",LCT.query(x,y));break;
            }
        }
        puts(""); 
    }
    return 0;
}

这里写图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值