Link-Cut-Tree详解

Posted yzhang-rp-inf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Link-Cut-Tree详解相关的知识,希望对你有一定的参考价值。

图片参考YangZhe的论文FlashHu大佬的博客

区别在于虚实是可以动态变化的,因此要使用更高级、更灵活的Splay来维护每一条由若干实边连接而成的实链

请先学习Splay之后再阅读本文

  • 查询、修改链上的信息(最值,总和等)

  • 随意指定原树的根(即换根)

  • 动态连边、删边

  • 动态维护连通性

  • 更多毒瘤操作

1.每一个Splay维护的是一条从上到下按在原树中深度严格递增的路径,且中序遍历Splay得到的每个点的深度序列严格递增

2.每个节点包含且仅包含于一个Splay中

3.边分为实边和虚边,实边包含在Splay中,而虚边总是由一棵Splay指向另一个节点(指向该Splay中中序遍历最靠前的点在原树中的父亲)

因为性质2,当某点在原树中有多个儿子时,只能向其中一个儿子拉一条实链(只认一个儿子),而其它儿子是不能在这个Splay中的

那么为了保持树的形状,我们要让到其它儿子的边变为虚边,由对应儿子所属的Splay的根节点的父亲指向该点,而从该点并不能直接访问该儿子(认父不认子)

一、access

假设有一珂树,有一棵树,一开始实边和虚边是这样划分的(虚线为虚边)

技术分享图片

那么所构成的LCT可能会长这样(绿框中为一个Splay,可能不会长这样,但只要满足中序遍历按深度递增(性质1)就对结果无影响)

技术分享图片

现在我们要access(N),把A~N的路径拉起来变成一条Splay

因为性质2,该路径上其它链都要给这条链让路,也就是把每个点到该路径以外的实边变虚

所以我们希望虚实边重新划分成这样

技术分享图片

那么如何实现这个过程呢?

首先把splay(N),使之成为当前Splay中的根

为了满足性质2,原来N~O的重边要变轻

因为按深度O在N的下面,在Splay中O在N的右子树中,所以直接单方面将N的右儿子置为0(认父不认子)

然后就变成了这样——

技术分享图片

我们接着把N所属Splay的虚边指向的I(在原树上是L的父亲)也转到它所属Splay的根,splay(I)

原来在I下方的重边I~K要变轻(同样是将右儿子去掉)

这时候I~L就可以变重了。因为L肯定是在I下方的(刚才L所属Splay指向了I),所以I的右儿子置为N,满足性质1。

技术分享图片

或许看了这些聪明的你就能发现规律

剩下的步骤自己脑补

想使一个点到根之间的路径在同一个Splay中只需要循环执行以下操作:

1.转到根
2.换儿子
3.跟新
4.当前操作点切换为轻边所指的父亲
    inline void pushup(register int x)
    {
        xr[x]=xr[c[x][0]]^xr[c[x][1]]^val[x];
    }
    inline void pushdown(register int x){
        if(rev[x])
        {
            register int l=c[x][0],r=c[x][1];
            rev[l]^=1,rev[r]^=1,rev[x]^=1;
            Swap(c[x][0],c[x][1]);
        }
    }
    inline bool isroot(register int x)
    {
        return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;
    }
    inline void rotate(register int x)
    {
        int y=fa[x],z=fa[y],l,r;
        l=c[y][0]==x?0:1;
        r=l^1;
        if(!isroot(y))
            c[z][c[z][0]==y?0:1]=x;
        fa[x]=z;
        fa[y]=x;
        fa[c[x][r]]=y;
        c[y][l]=c[x][r];
        c[x][r]=y;
        pushup(y),pushup(x);
    }
    inline void splay(register int x)
    {
        top=1;
        q[top]=x;
        for(register int i=x;!isroot(i);i=fa[i])
            q[++top]=fa[i];
        for(register int i=top;i;--i)
            pushdown(q[i]);
        while(!isroot(x))
        {
            int y=fa[x],z=fa[y];
            if(!isroot(y))
                rotate((c[y][0]==x)^(c[z][0]==y)?(x):(y));
            rotate(x);
        }
    }
    inline void access(register int x)
    {
        for(register int t=0;x;t=x,x=fa[x])
        {
            splay(x);
            c[x][1]=t;
            pushup(x);
        }
    }

pushdown就跟懒标记差不多(珂以先不看)

二、makeroot

makeroot定义为换根,让指定点成为原树的根

这时候就利用到access(x)和Splay的翻转操作

access(x)后x在Splay中一定是深度最大的点。

splay(x)后,x在Splay中将没有右子树(性质1)。于是翻转整个Splay,使得所有点的深度都倒过来了,x没了左子树,反倒成了深度最小的点(根节点),达到了我们的目的

    inline void makeroot(register int x)
    {
        access(x);
        splay(x);
        rev[x]^=1;
    }

三、findroot

找x所在原树的树根,主要用来判断两点之间的连通性(findroot(x)==findroot(y)表明x,y在同一棵树中)

inline int findroot(register int x)
    {
        access(x);
        splay(x);
        while(c[x][0])
            x=c[x][0];
        return x;
    }

在x,y两点之间连边

只在保证题目数据合法的情况下才能使用(不一定合法的话先要判联通(findroot))

    inline void link(register int x,register int y)
    {
        makeroot(x);
        fa[x]=y;
    }

五、cut

将x,y之间的边切断

    inline void split(register int x,register int y)
    {
        makeroot(x);
        access(y);
        splay(y);
    }
    inline void cut(register int x,register int y)
    {
        split(x,y);
        if(c[y][0]==x)
        {
            c[y][0]=0;
            fa[x]=0;
        }
    }

完整代码

#include <bits/stdc++.h>
#define N 300005
#define getchar nc
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf; 
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; 
}
inline int read()
{
    register int x=0,f=1;register char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return x*f;
}
inline void write(register int x)
{
    if(!x)putchar('0');if(x<0)x=-x,putchar('-');
    static int sta[20];register int tot=0;
    while(x)sta[tot++]=x%10,x/=10;
    while(tot)putchar(sta[--tot]+48);
}
inline void Swap(register int &a,register int &b)
{
    a^=b^=a^=b;
}
int n,m,val[N];
struct Link_Cut_Tree{
    int c[N][2],fa[N],top,q[N],xr[N],rev[N];
    inline void pushup(register int x)
    {
        xr[x]=xr[c[x][0]]^xr[c[x][1]]^val[x];
    }
    inline void pushdown(register int x){
        if(rev[x])
        {
            register int l=c[x][0],r=c[x][1];
            rev[l]^=1,rev[r]^=1,rev[x]^=1;
            Swap(c[x][0],c[x][1]);
        }
    }
    inline bool isroot(register int x)
    {
        return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;
    }
    inline void rotate(register int x)
    {
        int y=fa[x],z=fa[y],l,r;
        l=c[y][0]==x?0:1;
        r=l^1;
        if(!isroot(y))
            c[z][c[z][0]==y?0:1]=x;
        fa[x]=z;
        fa[y]=x;
        fa[c[x][r]]=y;
        c[y][l]=c[x][r];
        c[x][r]=y;
        pushup(y),pushup(x);
    }
    inline void splay(register int x)
    {
        top=1;
        q[top]=x;
        for(register int i=x;!isroot(i);i=fa[i])
            q[++top]=fa[i];
        for(register int i=top;i;--i)
            pushdown(q[i]);
        while(!isroot(x))
        {
            int y=fa[x],z=fa[y];
            if(!isroot(y))
                rotate((c[y][0]==x)^(c[z][0]==y)?(x):(y));
            rotate(x);
        }
    }
    inline void access(register int x)
    {
        for(register int t=0;x;t=x,x=fa[x])
        {
            splay(x);
            c[x][1]=t;
            pushup(x);
        }
    }
    inline void makeroot(register int x)
    {
        access(x);
        splay(x);
        rev[x]^=1;
    }
    inline int findroot(register int x)
    {
        access(x);
        splay(x);
        while(c[x][0])
            x=c[x][0];
        return x;
    }
    inline void split(register int x,register int y)
    {
        makeroot(x);
        access(y);
        splay(y);
    }
    inline void cut(register int x,register int y)
    {
        split(x,y);
        if(c[y][0]==x)
        {
            c[y][0]=0;
            fa[x]=0;
        }
    }
    inline void link(register int x,register int y)
    {
        makeroot(x);
        fa[x]=y;
    }
}T; 
int main()
{
    n=read(),m=read();
    for(register int i=1;i<=n;++i)
    {
        val[i]=read();
        T.xr[i]=val[i];
    }
    while(m--)
    {
        int opt=read();
        if(opt==0)
        {
            int x=read(),y=read();
            T.split(x,y);
            write(T.xr[y]),puts("");
        }
        else if(opt==1)
        {
            int x=read(),y=read();
            if(T.findroot(x)!=T.findroot(y))
                T.link(x,y);
        }
        else if(opt==2)
        {
            int x=read(),y=read();
            T.cut(x,y);
        }
        else
        {
            int x=read(),y=read();
            T.access(x);
            T.splay(x);
            val[x]=y;
            T.pushup(x);
        }
    }
    return 0;
} 

以上是关于Link-Cut-Tree详解的主要内容,如果未能解决你的问题,请参考以下文章

题解Luogu P2147 [SDOI2008]洞穴勘测

题解 luogu P1501[国家集训队]Tree II(Link-Cut-Tree)

Link-Cut-Tree 动态树算法

(转) Java中的负数及基本类型的转型详解

详解Android WebView加载html片段

SPOJQTREE6(Link-Cut-Tree)