动态树(Link Cut Tree)

学习笔记

LCT,是一个强大的数据结构,通常用来维护动态森林的一些操作

我们将原树剖分成多条实链,满足每个实链内的点在原树的深度是严格递增的,每个实链用一个 splay 来维护

每个结点到其子结点最多只有一条是实边,其余的都是虚边,保证每个结点只在一个 splay 中

每个 splay 的根节点指向整个链在原树中的父亲,但这个父亲不记录这个儿子,称作“认父不认子”

约定:splay(x) 表示将 \(x\) 翻转到它所在的 splay 的根


LCT 的基本操作

access(x)

\(x\) 到原树的根这条链变为实链:

  1. splay(x)

  2. 将上一个点 \(y\) (初始化为 \(0\))接到 \(x\)右子树,更新 \(x\) 的信息

  3. \(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);
}

这样,基础操作就做完了

代码


一些相关模型先咕着,等清理完题单在来学

posted @ 2022-09-13 10:30  zuytong  阅读(27)  评论(0编辑  收藏  举报