Link-Cut Tree

推荐资料:

《SPOJ375 QTREE 解法的一些研究》by Yang Zhe

《link cut tree》by popoqqq

正文:

LCT 是解决动态树问题的一种数据结构

LCT=树链剖分+splay

LCT利用splay来维护树上的树链,但是树链不能再以size来剖分了,否则树是静态的。

LCT利用Access操作,将需要访问的节点合并成一个splay,再进行操作。

在splay中是以点的深度作为关键字的。

为了更好维护这一堆splay,需要引入一个叫做Auxiliary Tree(辅助树)的东西。
这里写图片描述

在辅助树中,如果两个节点u,v,u认为v是父亲,而v不认u是儿子,那么v就是一个splay的根,这条边就叫虚边。

所以这就解释了下面Access中为什么只更新父亲节点的儿子信息,而没有更新父亲节点原来Preferred Child的父亲信息(这一点很精妙,仔细体会)。
具体函数

Access(x):这是一切操作的根源,把x到根的路径上的所有点都合成一个splay,方便操作。

make_root(x):将x设为原树的根
make_root(5)就像如图所示:
make_root(5)

可以发现,除了5到根的路径上的节点深度发生了翻转,别的节点深度相对关系是不变的,所以在splay上将5到根的路径的所有点翻转一下。

find(x):找到x的根。由于是按深度为关键字,所以把x旋成x所在的splay的根,再一直向左儿子找,没有了左儿子之后这个点就是根。

Link(x,y):连接x,y。先将x旋成原树的根,再把fa[x]设成y(注意x的儿子不用更新,这是一条虚边)。

Cut(x,y):断开x,y的连边。先将x旋成原树的根,再Access(y),Splay(y),于是x在y左儿子,切断。

维护其他x到y路径上的信息,只要make_root(x),Access(y),Splay(y)就好了。

代码

就拿hdu4010 Query on the tree了:

#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
const int INF=2000000000;
const int M=300005;
int n;
int tag[M],rev[M],c[M][2],fa[M],mx[M],v[M];
struct Edge{
    int to,nxt;
}edge[M<<1];
int T,head[M],stk[M];
void init(){
    for(int i=0;i<=n;i++)
        tag[i]=rev[i]=fa[i]=c[i][0]=c[i][1]=0,head[i]=-1;
    mx[0]=-INF;T=0;
}
void add_edge(int a,int b){
    edge[T]=(Edge){b,head[a]};
    head[a]=T++;
    edge[T]=(Edge){a,head[b]};
    head[b]=T++;
}
void rec(int x,int f){
    fa[x]=f;
    for(int i=head[x];~i;i=edge[i].nxt)
        if(edge[i].to!=f)
            rec(edge[i].to,x);
}
struct Link_Cut_Tree{
    bool is_root(int x){
        return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;
    }
    void push_up(int x){
        int l=c[x][0],r=c[x][1];
        mx[x]=max(mx[l],mx[r]);
        mx[x]=max(mx[x],v[x]);
    }
    void update(int x,int w){
        tag[x]+=w;
        mx[x]+=w;
        v[x]+=w;
    }
    void push_down(int x){
        int l=c[x][0],r=c[x][1];
        if(rev[x]){
            rev[l]^=1;
            rev[r]^=1;
            rev[x]^=1;
            swap(c[x][0],c[x][1]);
        }
        if(tag[x]){
            if(l) update(l,tag[x]);
            if(r) update(r,tag[x]);
            tag[x]=0;
        }
    }
    void Rotate(int x){
        int y=fa[x],z=fa[y],l,r;
        l=(c[y][1]==x);
        r=l^1;
        if(!is_root(y)) c[z][c[z][1]==y]=x;
        fa[x]=z;
        fa[y]=x;
        fa[c[x][r]]=y;
        c[y][l]=c[x][r];
        c[x][r]=y;
        push_up(y);push_up(x);
    }
    void Splay(int x){
        int top=0;
        stk[++top]=x;
        for(int i=x;!is_root(i);i=fa[i])
            stk[++top]=fa[i];
        while(top) push_down(stk[top--]);
        while(!is_root(x)){
            int y=fa[x];
            int z=fa[y];
            if(!is_root(y)){
                if(c[y][0]==x^c[z][0]==y) Rotate(x);
                else Rotate(y);
            }
            Rotate(x);
        }
    }
    void solve(int x,int y){
        make_root(x);
        Access(y);
        Splay(y);
    }
    void Access(int x){
        for(int t=0;x;t=x,x=fa[x])
            Splay(x),c[x][1]=t,push_up(x);
    }   
    void make_root(int x){
        Access(x);
        Splay(x);
        rev[x]^=1;
    }
    void link(int x,int y){
        make_root(x);
        fa[x]=y;
    }
    void cut(int x,int y){
        make_root(x);
        Access(y);
        Splay(y);
        c[y][0]=fa[c[y][0]]=0;
        push_up(y);
    }
    int find(int x){
        Access(x);
        Splay(x);
        while(c[x][0]) x=c[x][0];
        return x;
    }
    void add(int x,int y,int val){
        make_root(x);
        Access(y);
        Splay(y);
        tag[y]+=val;
        mx[y]+=val;
        v[y]+=val;
    }
}lct;
inline void Rd(int&res){
    res=0;char c;
    while(c=getchar(),!isdigit(c));
    do res=(res<<1)+(res<<3)+(c^48);
    while(c=getchar(),isdigit(c));
}
int main(){
    while(scanf("%d",&n)!=EOF){
        init();
        for(int i=1;i<n;i++){
            int a,b;
            Rd(a);Rd(b);
            add_edge(a,b);
        }
        for(int i=1;i<=n;i++)
            Rd(v[i]),mx[i]=v[i];
        rec(1,0);
        int m;
        scanf("%d",&m);
        while(m--){
            int opt,x,y;
            Rd(opt);Rd(x);Rd(y);
            if(opt==1){
                if(lct.find(x)==lct.find(y)) puts("-1");
                else lct.link(x,y);
            }else if(opt==2){
                if(lct.find(x)!=lct.find(y)||x==y) puts("-1");
                else lct.cut(x,y);
            }else if(opt==3){
                int z=x;
                x=y;
                Rd(y);
                if(lct.find(x)!=lct.find(y)) puts("-1");
                else lct.add(x,y,z);
            }else{
                if(lct.find(x)!=lct.find(y)) puts("-1");
                else{
                    lct.solve(x,y);
                    printf("%d\n",mx[y]);
                }
            }
        }
        puts("");
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值