Link-Cut-Tree 的基本姿势

来源:互联网 发布:淘宝化工店 编辑:程序博客网 时间:2024/05/22 14:55

Link-Cut-Tree之前经常听大神说,然而发现它是动态树的时候,我才知道是我太弱了= =

那么LCT好处都有啥?

1、支持动态加边删边(维护森林)

2、可以维护区间信息

3、所有操作可在线完成且均摊效率log(n)

LCT的结构:将整个树的边分为轻边和重边(Preferred edge)  ,与树链剖分类似(没学过请自觉面壁)

其中重边是进行操作的边,每个点只能有一个重边连接的儿子(Preferred child),于是可以证明每个点在且仅在一个重链上(没有边的点本身算作重链)

我们要做的是将重链放到splay中维护(没学过请自觉面壁),同时让splay树的根记录原树重链顶部节点的(轻边所连的)父亲,这时原树已经成为了一个概念,实际保存的是用father指针维护的各种splay,值得指出的是,splay的根不一定是真正的重链的顶点,实际的顶点应当在splay的最左端,因为对于splay的每个节点,其左儿子为它原树上的祖先,右儿子为它原树重链上的后代

这样我们要维护的数组有:f[maxn] ch[maxn][2],其中f对于每棵splay的根节点表示这个重链顶端的父亲,其余用于维护splay结构 ;ch维护splay上的左右儿子,并不表示原树上的边

根据如上定义可以给出判断一个点是否为splay 的根的方法

bool isroot(int x){    return ch[f[x]][0]!=x&&ch[f[x]][1]!=x;}

通常来说,LCT只需要拿来判断图的联通,即森林中的两点是否在同一棵树上(例题:bzoj2049),有些题目在link-cut的同时也要求维护权值,只要加一个函数maintain一下就可以支持。需要maintain的权值(sum,max等)信息随题目而异,但大体需要从splay上的两个儿子更新自身的值。以下代码中含maintain如只需判断连通可删去。

下面是LCT的基本操作


1、splay(t) 及 rotate:使t到达splay的根,与正常splay不同的是要通过儿子找到父亲再旋转,代码如下

void rotate(int k)//lift k to a higher level{int t=f[k];bool l;l=(ch[t][0]!=k);bool r=l^1;if(!isroot(t)) ch[f[t]][ch[f[t]][1]==t]=k;f[k]=f[t];f[t]=k;f[ch[k][r]]=t;ch[t][l]=ch[k][r];ch[k][r]=t;maintain(t);maintain(k);}void splay(int t){stack<int> stk;int i;for(i=t;!isroot(i);i=f[i])stk.push(i);stk.push(i);while(!stk.empty()){pushdown(stk.top());stk.pop();}while(!isroot(t)){int fa=f[t];if(!isroot(fa)){int ffa=f[fa];bool d1=ch[ffa][1]==fa;bool d2=ch[fa][1]==t;if(d1^d2) rotate(t);else rotate(fa);}rotate(t);}}

pushdown的作用在第四条体现

2、access(t):使根节点到t的路径变为一个重链,且保证t所在splay上只含root-t路径上的点

具体方法:首先将t splay到所在splay_tree的根,然后断开它与其splay上右儿子,即原树上t的后代,然后找到该链的父亲将其重儿子设置为t,重复操作直到到达原树的根所在splay的根,也就是没有父亲的点

void access(int t){int tmp=0;while(t){splay(t);ch[t][1]=tmp;tmp=t;maintain(t);t=f[t];}}

3、root(t) 找到t所在原树的根,用于判断两点之间是否连通

代码1:真正找到所在原树的根,这需要将t与root相连,然后确保t在splay根节点后向左走

int root(int t){access(t);splay(t);while(ch[t][0]) t=ch[t][0];return t;}
代码2:找到原树的根所在splay的根,同样可以判断连通,而且代码更加简洁,但不够严谨

int root(int t){    while(f[t]) t=f[t];    return t;}

4、moveroot(t) :将t所在原树的树根设为t

void moveroot(int t){access(t);splay(t);rev[t]^=1;}
rev是反转标记,这段代码的用意是将root-t路径分离出来,然后对条路径上的儿子-父亲关系进行反转,体现在splay上就是区间翻转的操作 注意换根操作只对root-t路径产生影响

pushdown函数就是为了支持rev的翻转操作

void pushdown(int t){if(rev[t]){rev[ch[t][0]]^=1;rev[ch[t][1]]^=1;rev[t]=0;swap(ch[t][0],ch[t][1]);}}
5、split(x,y)

将x到y的路径分离出来放到splay中且保证y为splay的根,若需要维护路径信息,y上的值即为答案

void split(int x,int y){moveroot(x);access(y);splay(y);}
6、cut(x,y)

void try_cut(int x,int y){split(x,y);if(ch[y][0]==x&&!ch[x][0]&&!ch[x][1])//判断节点是否有边{ch[y][0]=f[x]=0;maintain(y);}}
7、link(x,y)

void link(int x,int y){moveroot(x);//保证f[x]为空f[x]=y;}
附:bzoj2049代码

/**************************************************************    Problem: 2049    User: Leo_h    Language: C++    Result: Accepted    Time:4800 ms    Memory:992 kb****************************************************************/ #include<cstdio>#include<algorithm>#include<stack>using namespace std;#define maxn 10005int n,m;int f[maxn];int ch[maxn][2];int rev[maxn];bool isroot(int x){    return ch[f[x]][0]!=x&&ch[f[x]][1]!=x;}void rotate(int k)//lift k to a higher level{    int t=f[k];    bool l;    l=(ch[t][0]!=k);    bool r=l^1;    if(!isroot(t))         ch[f[t]][ch[f[t]][1]==t]=k;    f[k]=f[t];    f[t]=k;    f[ch[k][r]]=t;    ch[t][l]=ch[k][r];    ch[k][r]=t;}void pushdown(int t){    if(rev[t])    {        rev[ch[t][0]]^=1;        rev[ch[t][1]]^=1;        rev[t]=0;        swap(ch[t][0],ch[t][1]);    }}void splay(int t){    stack<int> stk;    int i;    for(i=t;!isroot(i);i=f[i])        stk.push(i);    stk.push(i);//you modified this    while(!stk.empty())    {        pushdown(stk.top());        stk.pop();    }    while(!isroot(t))    {        int fa=f[t];        if(!isroot(fa))        {            int ffa=f[fa];            bool d1=ch[ffa][1]==fa;            bool d2=ch[fa][1]==t;            if(d1^d2) rotate(t);            else rotate(fa);        }        rotate(t);    }}void access(int t){    int tmp=0;    while(t)    {        splay(t);        ch[t][1]=tmp;        tmp=t;        t=f[t];    }}void moveroot(int t){    access(t);    splay(t);    rev[t]^=1;}int root(int t){    while(f[t]) t=f[t];    return t;}void link(int x,int y){    moveroot(x);    f[x]=y;}void split(int x,int y){    moveroot(x);    access(y);    splay(y);}void cut(int x,int y){    split(x,y);    ch[y][0]=f[x]=0;}int main(){    scanf("%d%d",&n,&m);    char str[10];    int f1,f2;    for(int i=1;i<=m;i++)    {        scanf("%s%d%d",str,&f1,&f2);        switch(*str)        {            case 'C':                link(f1,f2);                break;            case 'D':                cut(f1,f2);                break;            case 'Q':                if(root(f1)==root(f2)) printf("Yes\n");                else printf("No\n");                break;        }    }}







0 0