Link-Cut Tree
树剖,也被称作“静态树”是用线段树维护树上每条链的信息
而Link-Cut Tree用Splay森林维护树上的动态信息
先明确几个定义
1.重儿子 这里指推广之后的重儿子,满足I,II两条性质
I:重儿子和它的父亲在同一棵Splay中
II:一个节点最多有一个重儿子
2.重边 连接重儿子和它父亲的边
3.重链 重边组成的一条链
我们用一个Splay(这个Splay称作“辅助树”即Auxiliary Tree)来维护一条重链,这个Splay中每个点的值等于它在原树中的深度
同时“父亲不认轻儿子只认重儿子,儿子都认父亲”
具体体现为辅助树的根节点的父亲指向链顶的父亲节点,然而链顶的父亲节点的儿子并不指向辅助树的根节点
这条性质方便了我们后续的操作
注意:辅助树<>原树 辅助树的树根<>原树树根 辅助树中的父亲<>原树中的父亲
辅助树在不断变化
LCT所用的Splay跟普通的Splay不一样,因为是森林,所以要判断某个点是不是根节点
同时,略去了值的维护和查找
旋转方式也与原来有了很大的区别
下面是两道例题
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int maxn=200010; int n,m; int next[maxn]; int rt,Size; struct LCT { int son[maxn][2],f[maxn],val[maxn],size[maxn],cnt[maxn],rev[maxn],st[maxn]; inline void pushup(int x) { size[x]=size[son[x][0]]+size[son[x][1]]+1; } inline int isroot(int x){return son[f[x]][0]!=x && son[f[x]][1]!=x;} inline void rotate(int x) { int y=f[x],z=f[y],l,r; if(son[y][0]==x)l=0; else l=1;r=l^1; if(!isroot(y)) { if(son[z][0]==y)son[z][0]=x; else son[z][1]=x; } f[x]=z;f[y]=x;f[son[x][r]]=y; son[y][l]=son[x][r];son[x][r]=y; pushup(y),pushup(x); } inline void pushdown(int id) { int lch=son[id][0],rch=son[id][1]; if(rev[id]) { rev[id]^=1;rev[lch]^=1;rev[rch]^=1; swap(son[id][0],son[id][1]); } } inline void Splay(int x) { int top=0;st[++top]=x; for(int i=x;!isroot(i);i=f[i])st[++top]=f[i]; for(int i=top;i;i--)pushdown(st[i]); while(!isroot(x)) { int y=f[x],z=f[y]; if(!isroot(y)) { if(son[y][0]==x^son[z][0]==y)rotate(x); else rotate(y); } rotate(x); } } inline void access(int x) { int t=0; while(x) { Splay(x); son[x][1]=t; t=x; x=f[x]; } } inline void rever(int x){access(x);Splay(x);rev[x]^=1;} inline void link(int x,int y){rever(x);f[x]=y;Splay(x);} inline void cut(int x,int y){rever(x);access(y);Splay(y);son[y][0]=f[x]=0;} inline int findf(int x) { access(x);Splay(x); int y=x; while(son[y][0])y=son[y][0]; return y; } }Tree; int main() { n=read(); for(int i=1;i<=n;i++) { int x=read(); Tree.f[i]=x+i;Tree.size[i]=1; if(Tree.f[i]>n+1)Tree.f[i]=n+1; next[i]=Tree.f[i]; } Tree.size[n+1]=1; m=read(); for(int i=1;i<=m;i++) { int op=read(); if(op==1) { Tree.rever(n+1); int x=read();x++; Tree.access(x);Tree.Splay(x);printf("%d\n",Tree.size[Tree.son[x][0]]); } else { int x=read(),y=read();x++; int t=min(n+1,x+y); Tree.cut(x,next[x]);Tree.link(x,t);next[x]=t; } } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstdio> #include<iostream> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int maxn=200010; int n,m; int rt,Size; struct LCT { int son[maxn][2],f[maxn],val[maxn],size[maxn],cnt[maxn],rev[maxn],st[maxn]; inline int isroot(int x){return son[f[x]][0]!=x && son[f[x]][1]!=x;} inline void rotate(int x) { int y=f[x],z=f[y],l,r; if(son[y][0]==x)l=0; else l=1;r=l^1; if(!isroot(y)) { if(son[z][0]==y)son[z][0]=x; else son[z][1]=x; } f[x]=z;f[y]=x;f[son[x][r]]=y; son[y][l]=son[x][r];son[x][r]=y; } inline void pushdown(int id) { int lch=son[id][0],rch=son[id][1]; if(rev[id]) { rev[id]^=1;rev[lch]^=1;rev[rch]^=1; swap(son[id][0],son[id][1]); } } void Splay(int x) { int top=0;st[++top]=x; for(int i=x;!isroot(i);i=f[i])st[++top]=f[i]; for(int i=top;i;i--)pushdown(st[i]); while(!isroot(x)) { int y=f[x],z=f[y]; if(!isroot(y)) { if(son[y][0]==x^son[z][0]==y)rotate(x); else rotate(y); } rotate(x); } } inline void access(int x) { int t=0; while(x) { Splay(x); son[x][1]=t; t=x; x=f[x]; } } inline void rever(int x){access(x);Splay(x);rev[x]^=1;} inline void link(int x,int y){rever(x);f[x]=y;Splay(x);} inline void cut(int x,int y){rever(x);access(y);Splay(y);son[y][0]=f[x]=0;} inline int findf(int x) { access(x);Splay(x); int y=x; while(son[y][0])y=son[y][0]; return y; } }Tree; int main() { char ch[10]; int x,y; n=read();m=read(); for(int i=1;i<=m;i++) { scanf("%s",ch); x=read();y=read(); if(ch[0]=='C')Tree.link(x,y); else if(ch[0]=='D')Tree.cut(x,y); else { if(Tree.findf(x)==Tree.findf(y))printf("Yes\n"); else printf("No\n"); } } return 0; }
重点讲一下一个最重要的操作:Access
这个操作的意思是将一个点与原先的重儿子切断,并使这个原树上这个点到根路径上的边全都变为重边
用处:使这个节点到根的路径上的所有节点形成了一棵Splay,便于操作或查询节点到根路径上的所有节点
实现:不断把X旋到当前辅助树的根,然后它的右子树就是重儿子了,修改即可
后面的几个操作比较显然,可以看代码,这里不赘述了