动态树(Link Cut Tree)
LCT,是一个强大的数据结构,通常用来维护动态森林的一些操作
我们将原树剖分成多条实链,满足每个实链内的点在原树的深度是严格递增的,每个实链用一个 splay 来维护
每个结点到其子结点最多只有一条是实边,其余的都是虚边,保证每个结点只在一个 splay 中
每个 splay 的根节点指向整个链在原树中的父亲,但这个父亲不记录这个儿子,称作“认父不认子”
约定:splay(x)
表示将 \(x\) 翻转到它所在的 splay 的根
LCT 的基本操作
① access(x)
将 \(x\) 到原树的根这条链变为实链:
-
先
splay(x)
-
将上一个点 \(y\) (初始化为 \(0\))接到 \(x\) 的右子树,更新 \(x\) 的信息
-
让 \(y=x\),\(x\) 则跳至它的父亲处(也就是跳到另一条实链处了);若 \(x\) 非空,重复步骤 \(1\)
inline void access(int x)
{
for(int y = 0; x; y = x, x = p[x].fa)
splay(x), p[x].s[1] = y, ud(x);
}
② makeroot(x)
将 \(x\) 设为原树的根:
先 access(x)
,再 splay(x)
因为只有 \(x\) 到之前的根的深度会反转,因此我们在 \(x\) 处打个翻转标记,并将 \(x\) 的左右儿子交换
inline void mkrt(int x)
{access(x), splay(x), p[x].rev();}
③ findroot(x)
查找 \(x\) 所在的原树的根:
先 splay(x)
,然后一直向左儿子找到最浅的点即可
在遍历的过程中要将标记下传;最后将答案结点 splay 一下,保证复杂度
inline int findrt(int x)
{
access(x), splay(x);
while(p[x].s[0]) dn(x), x = p[x].s[0]; // 记得要下传标记
splay(x); // 记得 splay 一下保持平衡
return x;
}
④ split(x,y)
将 \((x,y)\) 这条链整理到一个 splay 中:
我们先 makeroot(x)
,再 access(y)
,这时 \((x,y)\) 就是一条实链
最后 \(splay(y)\) 保证复杂度
inline void split(int x, int y)
{mkrt(x), access(y), splay(y);}
// 记得 splay 一下保持平衡,注意这时 LCT 的根为 y
⑤ link(x,y)
\(x,y\) 之间连接一条边(如果原本已联通,则忽略):
先 makeroot(x)
,然后判断 \(findroot(y)\) 是否等于 \(x\),如果是,说明它们已经在一个连通块中;否则将 \(x\) 的父亲设为 \(y\)
inline void lk(int x, int y)
{
mkrt(x);
if(findrt(y) == x) return;
p[x].fa = y;
}
⑥ cut(x,y)
将 \(x,y\) 之间的连边断开(如果没有连边,则忽略):
先 makeroot(x)
,再 splay(y)
如果 \(x,y\) 之间有连边,那么 \(y\) 的左儿子一定为 \(x\),且 \(x\) 的右儿子一定为空
断绝 \(x,y\) 的所有关系,并更新对应的信息即可
inline void cut(int x, int y)
{
split(x, y);
if(p[y].s[0] != x || p[x].s[1]) return;
p[x].fa = p[y].s[0] = 0; ud(y);
}
这样,基础操作就做完了
一些相关模型先咕着,等清理完题单在来学