简介
Link Cut Tree(简称 LCT)是使用 Splay 和实链剖分来维护森林的一个数据结构,它支持以下在线操作:
- 查询 / 修改链上的信息
- 换根
- 动态连边 / 删边
- 查询连通性
- ……
实现
在 LCT 中,每个 Splay 维护的是一条从上到下且深度严格递增的实链,且 Splay 按照点的深度排序。
不同于重链剖分,实链剖分选择的实儿子由我们自己决定。
比如这棵树,我们如果选择 做实儿子,则实链有 ;如果选择 做实儿子,则实链有 。
实链剖分具有灵活的特性,可以方便地维护动态问题。
对于每条实链,我们为它创建一个 Splay 来维护。
它有以下性质:
- 每个节点被有且仅有一个 Splay 包含;
- 边分为实边和虚边,实边的两个端点在同一个 Splay 中,虚边则是从一个 Splay 中深度最低的节点指向另一个 Splay。
为了保持 Splay 的二叉树形状,我们让节点不指向虚儿子,只让儿子指向它(即“认父不认子”)。
前置操作
存储方式
用 struct 和指针存储。
1 | struct tree { |
更新子树信息
洛谷例题要求维护的是异或和,所以代码中亦是如此。
1 | inline void push_up(tree *x) |
判断是否为 Splay 的根
根据“认父不认子”,如果 不是其父亲的左 / 右儿子,则它就是当前 Splay 的根。
1 | inline bool is_root(tree *x) |
判断儿子类型
与 Splay 的思想一致。
1 | inline int son_type(tree *x) |
区间翻转
与 Splay 翻转的思想一致。
1 | inline void push_down(tree *x) |
Update 操作
将 到根的所有节点 Push_down。
可用递归实现。
1 | void update(tree *x) |
上旋
如果当前节点的父亲是 Splay 的根则需特判。
1 | void rotate(tree *x) |
Splay 操作
1 | void splay(tree *x) |
Access 操作
将根节点到某个节点的所有边变为实边,并且将它的儿子全部设为虚儿子。
我们从 出发,将其 Splay 到当前 Splay 树的根,然后将它的右儿子设置为空(即断开它与它的实儿子)。
然后我们跳到 的父节点 ,同样将其 Splay 到根,然后将它的右儿子设置为 。
循环处理,直到跳到树根。
1 | void access(tree *x) |
换根
让指定点成为原树的根。
首先对 进行 Access,此时 一定为 Splay 树中深度最大的点。
原因:Access 将它的儿子全部设为虚儿子。
然后我们将 进行 Splay,此时它就没有了右子树且成为了根节点。
最后将 的翻转标记异或 即可。
1 | void make_root(tree *x) |
找根
寻找 所在原树的根。
与换根类似,首先对 进行 Access,然后我们将 进行 Splay,沿途 Push_down 并一路向左找即可(根的深度最浅)。
1 | tree *find_root(tree *x) |
访问原树的链
需保证 连通。
先将 设为根,然后对 进行 Access 与 Splay,此时 上就维护了从 到 的链上的信息。
1 | void split(tree *x,tree *y) |
虽然我很好奇为什么它叫 Split(分裂)。
终于到了 LCT 的 Link 和 Cut 了!🎉
连边
使 成为根,如果 不连通则将 的父亲指向 。
1 | void link(tree *x,tree *y) |
断边
使 成为根,如果 直接连通则双向断开关系。
不要忘了更新 维护的信息。
1 | void cut(tree *x,tree *y) |
资料
本文部分参考 LCT 总结 - Flash_Hu。