Link-cut Tree
链剖分
重链剖分
使用静态数据结构维护,按照把子树大小最大的设为重儿子,然后连重边,如此剖分。这样做的好处是,每条路经经过 \(\log n\) 条重边。
实链剖分(LCT)
逻辑基本相同,选某个点的儿子与这个点之间连一条实边。但是更加灵活,比如access操作之后可能一个点和它儿子只有虚边
使用splay维护,bst序越大,这个点就越深。
虚边认父不认子,连接两个splay。
LCT核心操作
基本思想
剖分完之后,使用splay维护,可以动态修改,查询路径,并且维护动态树连通性。
access(x)
从指定节点到根节点,自下往上,打通根节点到指定节点的实链,是的一条从根开始指定节点结束的splay出现
splay旋转路径上对于有实变得点,让他变虚,对于x路径上的点变重。把要改虚的边用旋转去掉。
splay是二叉树,在splay上进行操作
- 转到根 splay
- 换儿子
- 更新信息
- 当前操作节点切换到轻边所指的父节点
void access(int x){
for(int y=0;x;y=x,x=t[x].f){
splay(x);
t[x].son[1]=y;
pushup(x);
}
}
makeroot(x)
将 x 变成原树的根。首先 access(x) 得到树根到 x的路径,然后 splay(x),将x作为splay的根。反转整个splay,所有点的深度倒过来,则x成为新的树根,且因为认父不认子,这个splay的bst序不变,其他splay的信息不变,则树的结构也不变。
void op_rev(int x){
if(!x)return;
swap(t[x].son[0],t[x].son[1]);
t[x].rev ^= 1;
}
void make_root(int x){
access(x);
splay(x);
op_rev(x);
}
findroot(x)
找到 \(x\) 所在连通块的根。首先 access(x),让 \(x\) 与根在同一个splay中。此时根在x的最左子节点,只要循环下去找就行。
void pushdown(int p){
if(t[p].rev){
op_rev(t[p].son[0]);
op_rev(t[p].son[1]);
t[p].rev=0;
}
}
int find_root(int x){
access(x);
splay(x);
while(t[x].son[0])pushdown(x),x=t[x].son[0];
splay(x);
return x;
}
split(x,y)
将x到y的路径放到一个单独的splay中。首先将 \(x\) 作为跟,然后 access(y),就做到了。然后根不确定,最好将 x 或 y 作为根。/
void split(int x,int y){
make_root(x);
access(y);
splay(x);//有用。不能直接用y的值:根不知道是谁。把x或y作为根,方便操作
}
link
连一条x-y的边。先判断是否在一个连通块里面(或者题目给出条件跳过判断),然后,将 \(x\) 作为根与 \(y\) 连接。
void link(int x,int y){
make_root(x);
if(find_root(y)!=x)t[x].f=y;
}
cut
将x-y边断开。首先判断他们是否相邻有边(或者题目给出条件跳过判断),然后将这条实边割掉。
void cut(int x,int y){
make_root(x);
if(find_root(y)==x&&t[y].f==x&&t[y].son[0]==0){
t[y].f=t[x].son[1]=0;
pushup(x);
}
}
注意点
splay 的判根方式要改变,因为有虚边存在。fa不为0也可能是根,要特别写isRoot函数,判断父节点认不认这儿子。认父不认子的悲惨情况可能发生。
bool isRoot(int p){
return t[t[p].f].son[0]!=p&&t[t[p].f].son[1]!=p;
}
splay的时候要记得从上往下pushdown。
void splay(int x){
int y=x,z;
stack<int> stk;
while(!isRoot(y))stk.emplace(y),y=t[y].f;
stk.emplace(y);
while(!stk.empty())pushdown(stk.top()),stk.pop();
while(!isRoot(x)){
y=t[x].f,z=t[y].f;
if(!isRoot(y))
rotate(t[y].son[1]==x^t[z].son[1]==y?x:y);
rotate(x);
}
pushup(x);
}
时间复杂度
势能分析,单次 access(x) 操作 \(O(\log n)\)
练习题
P1501 提示:懒惰标记,LCT,树
调试经验:pushup时一般不特判儿子为0的情况,所以对于编号为0的节点要进行处理,siz为0,sum为0,不要瞎搞。
计算范围要落到实处,不要口胡,借鉴这篇分析
P3690 提示:LCT,模板
P4234 提示:LCT,排序,双指针
注意判自环呀
闲谈
这可能是我第一次学数据结构如此绝望
Q:不会splay怎么办?A:学,资源很多
splay修改l,r,但是提根要提l-1,r+1
LCT真的不难,写完竟然只有不到200行。

浙公网安备 33010602011771号