Link-Cut Tree
Link - Cut Tree
LCT 一般用于解决动态树问题(加边、删边)。
可以非常轻松地维护树链信息,但不太擅长维护子树信息。
Splay \(\rightarrow\) 辅助树 \(\rightarrow\) LCT
实链剖分
区别于重儿子,实儿子是我们自己选择的,于是具有极高的自由度。
LCT 通过更改实链结构达成正确的均摊复杂度。
辅助树
每条实链维护一个 Splay,中序遍历是原树上从浅到深的顺序遍历。
每条实链的 Splay 根,单向往原树上链头的父亲连虚边。
也就是说,原树上的虚边和辅助树上的虚边一一对应。
单向的意思是 儿子认父亲,父亲不认儿子。
借用 OI-wiki 的图,左边是原树,右边是辅助树。
注意:
- 辅助树的根 不等于 原树的根
- 辅助树的 father 不等于 原树的 father
- 辅助树可以在保证性质的前提下随意换根
函数 & 代码
splay & rotate
与普通 Splay 中略有不同
inline void rotate(int x){
int y=fa[x],z=fa[y],sid=locate(x);
if(!isroot(y)) son[z][locate(y)]=x;
// 必须判断 isroot,且必须写在前面
if(son[x][sid^1]) fa[son[x][sid^1]]=y;
son[y][sid]=son[x][sid^1];
son[x][sid^1]=y;
fa[y]=x;fa[x]=z;
pushup(y),pushup(x);
}
inline void update(int x){
if(!isroot(x)) update(fa[x]);
pushdown(x);
}
inline void splay(int x){
update(x);// 先把会 rotate 到的所有节点 pushdown
while(!isroot(x)){
int y=fa[x];
if(!isroot(y)) rotate(locate(x)==locate(y)?y:x);
rotate(x);
}
pushup(x);
}
isroot
Splay 根向父亲连的是虚边,父亲不认儿子。
所以如果父亲的两个儿子都不是 \(x\) ,那么 \(x\) 就是 Splay 的根。
inline bool isroot(int x){
return son[fa[x]][0]!=x && son[fa[x]][1]!=x;
}
access
LCT 核心函数,功能是 将 \(x\) 到 原树 根的路径放到一个实链中。
我们有这样一棵树:
辅助树可能是这样:
现在要 access(N) 把 A - N 的路径放到一个 Splay 里,就是要把虚边变成实边,原来的实边变成虚边。
根据辅助树的定义,实儿子一定是中序遍历上右边相邻的点。
实现过程:
- 把 N 旋转到当前 Splay 的根
- N 连的实边要变成虚边,即把 Splay 上右儿子设为 0
此时变成了这样:
- 把 N 的 father I 旋转到 Splay 的根
- I 之前连的实边要变成虚边,现在向 L 连的边变成实边,即把 Splay 上 I 的右儿子变成 N
此时得到了 I - L 的 Splay
同理对 H 和 A 进行操作,就能把 A - N 的路径都放进一个 Splay
inline void access(int x){
for(int y=0;x;y=x,x=fa[x])
splay(x),son[x][1]=y,pushup(x);
return $x$ ;
}
makeroot
把 \(x\) 变成 原树 的根。
相当于是把 \(x\) 到本来的根的路径区间翻转。
先 access(x),此时 \(x\) 到根的路径放到了同一个 Splay。
然后 Splay(x),再给 \(x\) 打一个翻转 tag,就完成了路径翻转。
inline void pushr(int x){
swap(son[x][0],son[x][1]);
rev[x]^=1;
}
inline void makeroot(int x){
access(x),splay(x);
pushr(x);
}
findroot
找到 \(x\) 所在 原树 的根。
access 再 splay 后,原树的根就是最左边的节点。
inline int findroot(int x){
access(x),splay(x);
while(son[x][0]) pushdown(x),x=son[x][0];
splay(x);
return $x$ ;
}
link
把 \(x\) 转到根,然后让 \(x\) 指向 \(y\) 即可。
inline void link(int x,int y){
makeroot(x);
if(findroot(y)!=x) fa[x]=y;
}
cut
需要判断原来有边。
钦定 \(x\) 为根,先判断是否在一棵树里,然后判断 \(x,y\) 是否直接连边。
要求 \(y\) 的左子树没有点(如果有,说明在 $x\to y $ 的链上,即 \(x,y\) 没有直接连边)。
inline void cut(int x ,int y){
makeroot(x);
if(findroot(y)==x && fa[y]==x && !son[y][0])
son[x][1]=0,fa[y]=0,pushup(x);
}
split
把 x 到 y 的路径拆出来,简单平凡。
inline void split(int x,int y){
makeroot(x),access(y),splay(y);
}

浙公网安备 33010602011771号