图片参考YangZhe的论文,FlashHu大佬的博客
Link-Cut-Tree实际靠的是实链剖分,重链剖分和长链剖分珂以参考树链剖分详解
Link-Cut-Tree将某一个儿子的连边划分为实边,而连向其他子树的边划分为虚边
区别在于虚实是可以动态变化的,因此要使用更高级、更灵活的Splay来维护每一条由若干实边连接而成的实链
请先学习Splay之后再阅读本文
Link-Cut-Tree功能强大,能维护以下东西:
查询、修改链上的信息(最值,总和等)
随意指定原树的根(即换根)
动态连边、删边
动态维护连通性
更多毒瘤操作
Link-Cut-Tree的性质
1.每一个Splay维护的是一条从上到下按在原树中深度严格递增的路径,且中序遍历Splay得到的每个点的深度序列严格递增
2.每个节点包含且仅包含于一个Splay中
3.边分为实边和虚边,实边包含在Splay中,而虚边总是由一棵Splay指向另一个节点(指向该Splay中中序遍历最靠前的点在原树中的父亲)
因为性质2,当某点在原树中有多个儿子时,只能向其中一个儿子拉一条实链(只认一个儿子),而其它儿子是不能在这个Splay中的
那么为了保持树的形状,我们要让到其它儿子的边变为虚边,由对应儿子所属的Splay的根节点的父亲指向该点,而从该点并不能直接访问该儿子(认父不认子)
核心操作(以下代码以Luogu P3690 【模板】Link Cut Tree (动态树)为例)
一、access
是Link-Cut-Tree的核心操作也是最难理解的操作
假设有一珂树,有一棵树,一开始实边和虚边是这样划分的(虚线为虚边)

那么所构成的LCT可能会长这样(绿框中为一个Splay,可能不会长这样,但只要满足中序遍历按深度递增(性质1)就对结果无影响)

现在我们要access(N),把A~N的路径拉起来变成一条Splay
因为性质2,该路径上其它链都要给这条链让路,也就是把每个点到该路径以外的实边变虚
所以我们希望虚实边重新划分成这样

那么如何实现这个过程呢?
首先把splay(N),使之成为当前Splay中的根
为了满足性质2,原来N~O的重边要变轻
因为按深度O在N的下面,在Splay中O在N的右子树中,所以直接单方面将N的右儿子置为0(认父不认子)
然后就变成了这样——

我们接着把N所属Splay的虚边指向的I(在原树上是L的父亲)也转到它所属Splay的根,splay(I)
原来在I下方的重边I~K要变轻(同样是将右儿子去掉)
这时候I~L就可以变重了。因为L肯定是在I下方的(刚才L所属Splay指向了I),所以I的右儿子置为N,满足性质1。

或许看了这些聪明的你就能发现规律
剩下的步骤自己脑补
想使一个点到根之间的路径在同一个Splay中只需要循环执行以下操作:
1.转到根
2.换儿子
3.跟新
4.当前操作点切换为轻边所指的父亲
inline void pushup(register int x) { xr[x]=xr[c[x][0]]^xr[c[x][1]]^val[x]; } inline void pushdown(register int x){ if(rev[x]) { register int l=c[x][0],r=c[x][1]; rev[l]^=1,rev[r]^=1,rev[x]^=1; Swap(c[x][0],c[x][1]); } } inline bool isroot(register int x) { return c[fa[x]][0]!=x&&c[fa[x]][1]!=x; } inline void rotate(register int x) { int y=fa[x],z=fa[y],l,r; l=c[y][0]==x?0:1; r=l^1; if(!isroot(y)) c[z][c[z][0]==y?0:1]=x; fa[x]=z; fa[y]=x; fa[c[x][r]]=y; c[y][l]=c[x][r]; c[x][r]=y; pushup(y),pushup(x); } inline void splay(register int x) { top=1; q[top]=x; for(register int i=x;!isroot(i);i=fa[i]) q[++top]=fa[i]; for(register int i=top;i;--i) pushdown(q[i]); while(!isroot(x)) { int y=fa[x],z=fa[y]; if(!isroot(y)) rotate((c[y][0]==x)^(c[z][0]==y)?(x):(y)); rotate(x); } } inline void access(register int x) { for(register int t=0;x;t=x,x=fa[x]) { splay(x); c[x][1]=t; pushup(x); } }
pushdown就跟懒标记差不多(珂以先不看)
二、makeroot
makeroot定义为换根,让指定点成为原树的根
这时候就利用到access(x)和Splay的翻转操作
access(x)后x在Splay中一定是深度最大的点。
splay(x)后,x在Splay中将没有右子树(性质1)。于是翻转整个Splay,使得所有点的深度都倒过来了,x没了左子树,反倒成了深度最小的点(根节点),达到了我们的目的
inline void makeroot(register int x) { access(x); splay(x); rev[x]^=1; }
三、findroot
找x所在原树的树根,主要用来判断两点之间的连通性(findroot(x)==findroot(y)表明x,y在同一棵树中)
inline int findroot(register int x) { access(x); splay(x); while(c[x][0]) x=c[x][0]; return x; }
四、link
在x,y两点之间连边
只在保证题目数据合法的情况下才能使用(不一定合法的话先要判联通(findroot))
inline void link(register int x,register int y) { makeroot(x); fa[x]=y; }
五、cut
将x,y之间的边切断
inline void split(register int x,register int y) { makeroot(x); access(y); splay(y); } inline void cut(register int x,register int y) { split(x,y); if(c[y][0]==x) { c[y][0]=0; fa[x]=0; } }
完整代码
#include <bits/stdc++.h> #define N 300005 #define getchar nc using namespace std; inline char nc(){ static char buf[100000],*p1=buf,*p2=buf; return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; } inline int read() { register int x=0,f=1;register char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f; } inline void write(register int x) { if(!x)putchar('0');if(x<0)x=-x,putchar('-'); static int sta[20];register int tot=0; while(x)sta[tot++]=x%10,x/=10; while(tot)putchar(sta[--tot]+48); } inline void Swap(register int &a,register int &b) { a^=b^=a^=b; } int n,m,val[N]; struct Link_Cut_Tree{ int c[N][2],fa[N],top,q[N],xr[N],rev[N]; inline void pushup(register int x) { xr[x]=xr[c[x][0]]^xr[c[x][1]]^val[x]; } inline void pushdown(register int x){ if(rev[x]) { register int l=c[x][0],r=c[x][1]; rev[l]^=1,rev[r]^=1,rev[x]^=1; Swap(c[x][0],c[x][1]); } } inline bool isroot(register int x) { return c[fa[x]][0]!=x&&c[fa[x]][1]!=x; } inline void rotate(register int x) { int y=fa[x],z=fa[y],l,r; l=c[y][0]==x?0:1; r=l^1; if(!isroot(y)) c[z][c[z][0]==y?0:1]=x; fa[x]=z; fa[y]=x; fa[c[x][r]]=y; c[y][l]=c[x][r]; c[x][r]=y; pushup(y),pushup(x); } inline void splay(register int x) { top=1; q[top]=x; for(register int i=x;!isroot(i);i=fa[i]) q[++top]=fa[i]; for(register int i=top;i;--i) pushdown(q[i]); while(!isroot(x)) { int y=fa[x],z=fa[y]; if(!isroot(y)) rotate((c[y][0]==x)^(c[z][0]==y)?(x):(y)); rotate(x); } } inline void access(register int x) { for(register int t=0;x;t=x,x=fa[x]) { splay(x); c[x][1]=t; pushup(x); } } inline void makeroot(register int x) { access(x); splay(x); rev[x]^=1; } inline int findroot(register int x) { access(x); splay(x); while(c[x][0]) x=c[x][0]; return x; } inline void split(register int x,register int y) { makeroot(x); access(y); splay(y); } inline void cut(register int x,register int y) { split(x,y); if(c[y][0]==x) { c[y][0]=0; fa[x]=0; } } inline void link(register int x,register int y) { makeroot(x); fa[x]=y; } }T; int main() { n=read(),m=read(); for(register int i=1;i<=n;++i) { val[i]=read(); T.xr[i]=val[i]; } while(m--) { int opt=read(); if(opt==0) { int x=read(),y=read(); T.split(x,y); write(T.xr[y]),puts(""); } else if(opt==1) { int x=read(),y=read(); if(T.findroot(x)!=T.findroot(y)) T.link(x,y); } else if(opt==2) { int x=read(),y=read(); T.cut(x,y); } else { int x=read(),y=read(); T.access(x); T.splay(x); val[x]=y; T.pushup(x); } } return 0; }
来源:https://www.cnblogs.com/yzhang-rp-inf/p/10201857.html