P3690 【模板】Link Cut Tree (动态树)------LCT树

原题传送门
大佬讲的非常好:大佬讲LCT
看了一天的LCT,终于稍微弄懂了一些,这里有几个个人总结的tip,将来深入应该会继续加一些tip。

LCT就是一颗树,但它上面有很多很多的Splay树,LCT算是一片Splay的集合。

每一颗Splay树的键值都是它在原来树/图里面的深度(这个对理解LCT是非常重要的,是深度!是深度!是深度!)。

每一颗Splay树中的节点都必须保证:最多只有一个属于该Splay树的节点的儿子

因为LCT中有很多Splay树,所以每一颗Splay树内部用实链(例如x,y,当fa[x]=y,t[y][1/0]=x,时为实链;当fa[x]=y,t[y][1/0]!=x,时为虚链),每两颗Splay树之间连接用虚链。

Splay树中有个翻转标记,这个很重要,把它连着“键值是深度”一起想就好理解了。

还有选择一些pushdown和pushup的用法,有一些题是专门卡这些的,如果实在想不通为什么要这样更新,那就这样学模板吧(打不赢就加入,hhh)

#include<bits/stdc++.h>  // 这题初始时,没有LCT树,因为全是些节点,而且还是没有连起来的那种节点
#define lson t[x][0]
#define rson t[x][1]
using namespace std;
const int MX=3e5+9;
int fa[MX],t[MX][2],val[MX],ans[MX],st[MX],n,m;
bool r[MX];

int nroot(int x){   // 判断该x节点是否是它所属的Splay树里面的根节点
    return t[fa[x]][1]==x || t[fa[x]][0]==x ;
}

void pushup(int x){
    ans[x]=ans[lson]^ans[rson]^val[x];
}

void pushr(int x){
    swap(t[x][1],t[x][0]);
    r[x]^=1;    // 这里不直接赋值0是有道理的,可以注意一下makeroot函数
}

void pushdown(int x){    // 听巨佬说这样写可以砍掉很多的特殊情况,所以嫖一下,嘿嘿
    if( r[x] ){    // 这样写基本可以保证:当走到某一个节点x时,x的儿子可以更新,
        if( lson ) pushr(lson);   // 但儿子的儿子还没有更新
        if( rson ) pushr(rson);
        r[x]=0;
    }
}

int get(int x){    // Splay树里面的常规操作,判断x是它父树的左儿子还是右儿子
    return t[fa[x]][1]==x;    
}

void rotate(int x){
    int y=fa[x],z=fa[y],kx=get(x),ky=get(y);
    if( nroot(y) )   // 当y是该Splay树的根节点时,就不允许z的儿子只向x了,因为这里必须要用虚链
        t[z][ky]=x;    // 这里是和普通的Splay树区别的地方
    fa[x]=z;
    t[y][kx]=t[x][!kx],fa[t[x][!kx]]=y;  
    t[x][!kx]=y,fa[y]=x;
    pushup(y),pushup(x);
}

void splay(int x){
    int y=x,z=0;
    st[++z]=y;
    while( nroot(y) ){  // 我们是转到这一颗Splay树的树顶,所以判断条件是nroot(y)
        y=fa[y];    
        st[++z]=y;
    }
    while( z )             // 以x为最底下的节点,向上更新(跟着大佬学的),防止改变树结构时原来
        pushdown(st[z--]);     // 的节点还未更新这种情况
    while( nroot(x) ){
        y=fa[x],z=fa[y];
        if( nroot(y) ){   //判断y是不是顶点,再向上转
            if( (t[y][0]==x)^(t[z][0]==y) )
                rotate(x);
            else
                rotate(y);
        }
        rotate(x);
    }
    pushup(x);
}

void access(int x){
    for( int y=0 ; x ; x=fa[y] ){
        splay(x);
        rson=y;
        pushup(x);   // 这里不能忘记更新
        y=x;
    }
}

void makeroot(int x){
    access(x);
    splay(x);
    pushr(x);
}

int findroot(int x){   // 找到x节点所属LCT树的根,那么它深度必然最小,所以键值最小,也就是最右边
    access(x);
    splay(x);
    while( lson ){   // 注意是lson作为判断
        pushdown(x);   // 还要记得向下更新
        x=lson;
    }
    splay(x);
    return x;
}

void split(int x,int y){   // 把x和y放在同一颗Splay树里面,且y是根,x是最右边的那个
    makeroot(x);
    access(y);
    splay(y);
}

void link(int x,int y){
    makeroot(x);
    if( findroot(y)!=x )    // 只有它们不在同一颗LCT中才能连,不然就形成了环
        fa[x]=y;   // 没有说过子树一颗二叉树,它可以有很多子节点
}

void cut(int x,int y){
    makeroot(x);
    if( findroot(y)==x && fa[y]==x && !t[y][0] ){     // 这个!t[y][0]千万不可以漏掉,因为
        fa[y]=t[x][1]=0;   // t[y][0]=0,可以保证比x(键值,也就是深度)大的只有y,否则就表明x和y
        pushup(x);   // 之间还有其它的节点,还有记得更新
    }
}

int main()
{
    //freopen("input.txt","r",stdin);
    scanf("%d %d",&n,&m);
    for( int i=1 ; i<=n ; i++ )
        scanf("%d",&val[i]);
    while( m-- ){
        int order,x,y;
        scanf("%d %d %d",&order,&x,&y);
        if( order==0 ){
            split(x,y);
            printf("%d\n",ans[y]);
        }
        else if( order==1 ) link(x,y);
        else if( order==2 ) cut(x,y);
        else if( order==3 ){splay(x);val[x]=y;}   // 更新某个节点的值时,一定要放在该Splay树的
    }							// 根节点上再改变
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值