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);
}
}
好像没什么说的了?
那就这样吧。