LCT概述

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lvmaooi/article/details/82944687

LCT全称是link-cut-tree,从这个名字就可以看出这玩意儿的逼格很高。实际上确是是一个很强的数据结构。
建议在学LCT之前可以先学习一下树链剖分,这里就不赘述了。

那么LCT是一个什么东西呢?这个数据结构正如它的名字一样,他可以支持树的link和cut,专治各种毒瘤题

其实LCT可以看做树链剖分的升级版,树链剖分只能解决静态树的根源是什么?是因为线段树这个东西就只能是静态的。而且就算把线段树强行动态一下,重链剖分这种复杂度基于静态的东西也会被卡。

那么我们就需要一个“灵活“”的数据结构。说道这个“灵活”,聪明人就已经想到splay了。没错,我们就用splay维护树链。但是这样就有一个问题,我们如何保证复杂度?

其实我也不知道

去问tarjin爷爷吧,他可毒瘤 和蔼了。

LCT是把树分成很多条链,每一条链都用splay维护一下。每个splay的根节点的fa存这条链在树上实际的父亲是什么。这些splay就可以把树完整地还原出来。值得注意的是,splay的顺序是按深度维护的。而且所有的fa都不一定是它树上真正的父亲。

在这里插入图片描述
LCT神奇的地方就是它并不需要一种严格的链剖方案来保证复杂度,怎么划分链都是可以的。像上图我们就可以直接用4棵splay来维护。

LCT需要支持一下几种操作:

1.access:
把点x到根的路径“打通”,就是把点x和根划到同一棵splay当中,并且把x的儿子从这棵splay里剔除。比如上图,我们access点e:
在这里插入图片描述
在连接的同时把其它链断开。

2.link:连接两棵树
3.cut:切开两棵树
4.bert:把一个点变成根

splay怎么保证复杂度咧?答:多splay一下
LCT怎么保证复杂度咧?答:多access一下

在splay转得头晕的情况下如何保证信息维护正确呢?
还是看一下代码吧,这玩意儿好像看着代码比较好讲。
题目来自:【模板】Link Cut Tree (动态树)

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct sbt_tree{
    int son[2],fa,x,num,size;
    bool fg;
}b[300005];

int n,m,a1,a2,a3;

void pushdown(int u){
    if(u==0||b[u].fg==0) return;
    b[b[u].son[0]].fg^=1;b[b[u].son[1]].fg^=1;
    swap(b[u].son[0],b[u].son[1]);
    b[u].fg=0;
}

void update(int u){
    b[u].size=b[b[u].son[0]].size+b[b[u].son[1]].size+1;
    b[u].num=(b[b[u].son[0]].num^b[b[u].son[1]].num^b[u].x);
}

void rotate(int x,int &u){
    int y=b[x].fa,z=b[y].fa;
    if(y==u) u=x,b[x].fa=b[y].fa;
    else{
        if(b[z].son[0]==y) b[z].son[0]=x;
        else b[z].son[1]=x;
        b[x].fa=b[y].fa;
    }
    int L=(b[y].son[1]==x),R=L^1;
    b[y].son[L]=b[x].son[R];b[b[y].son[L]].fa=y;
    b[x].son[R]=y;b[y].fa=x;
    update(y),update(x);//rotate中的update和平常一样
}

void splay(int x,int &u){
    while(x!=u){
        int y=b[x].fa,z=b[y].fa;
        pushdown(z),pushdown(y),pushdown(x);//pushdown自己和祖先
        if(y!=u){
            if((b[z].son[0]==y)^(b[y].son[0]==x)) rotate(x,u);
            else rotate(y,u);
        }
        rotate(x,u);
    }
    pushdown(x);//这里一定要pushdown,因为不一定会进循环导致点x没有pushdown
}

bool isrt(int x){//判断当前点是不是splay的根
    return !(b[b[x].fa].son[0]==x||b[b[x].fa].son[1]==x);
}
int findrt(int x){//找到spaly的根
    while(!isrt(x)) x=b[x].fa;return x;
}
void change(int x,int y){//把点x链上本来的深度更深的点换成y
    int u=findrt(x);
    splay(x,u);
    b[x].son[1]=y;update(x);//换儿子的时候要update
}
int access(int x){//如上文所述,并且返回整棵树的根
    for(change(x,0);b[x].fa!=0;x=b[x].fa) change(b[x].fa,x);//这个写法感觉很妙
    int u=x;//找到树上实际的根,并把它转到splay的根
    while(b[u].son[0]!=0) u=b[u].son[0],pushdown(u);//找的时候记得pushdown
    splay(u,x);return u;
}
void link(int x,int y){//连接两棵树
    b[x=access(x)].fg^=1;b[x].fa=y;
}
void cut(int x,int y){//断掉xy间的边
    change(x,0);change(y,0);
    if(b[x].fa==y&&b[x].son[0]==0) b[x].fa=0;//感觉是一个容错性很高的写法,xy间没有连边就不会cut
    if(b[y].fa==x&&b[y].son[0]==0) b[y].fa=0;
}
bool isconnect(int x,int y){//判断xy是否在一棵树内
    return access(x)==access(y);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a1),b[i].size=1,b[i].x=a1,b[i].num=a1;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&a1,&a2,&a3);
        if(a1==0){
            link(a2,0);
            printf("%d\n",b[access(a3)].num);
        }
        if(a1==1&&!isconnect(a2,a3)) link(a2,a3);
        if(a1==2) cut(a2,a3);
        if(a1==3) b[a2].x=a3,access(a2);
    }
} 

好像没什么说的了?
那就这样吧。

猜你喜欢

转载自blog.csdn.net/lvmaooi/article/details/82944687
LCT
今日推荐