LCT学习笔记
作者:互联网
概念
前置芝士:链剖分
链剖分:指一类对树的边进行划分的操作,这样做可以减少某些链上的修改、查询等操作的复杂度。链剖分分为重链剖分,实链剖分和长链剖分(不常见)。
重链剖分:实际上树剖就是重链剖分的常用称呼。可以看看 树链剖分学习笔记
实链剖分:同样将某一个儿子的连边划分为实边,而连向其他子树的边划分为虚边。区别在于虚边、实边是可以动态变化的,要使用更高级、更灵活的Splay来维护每一条由若干实边连接而成的实链。
基于更逆天的实链剖分,LCT(Link-Cut Tree)应运而生。
LCT维护的对象其实是一个森林,在实链剖分的基础下,LCT资磁更多的操作
such as:
- 查询、修改链上的信息
- 随意换根
- 动态连边、删边
- 动态维护连通性
- and so on
LCT把边分为实边和虚边,每个结点最多有一个实儿子(可以没有实儿子)
每一个Splay维护的是一条从上到下按在原树中深度严格递增的路径,且中序遍历Splay得到的每个点的深度序列严格递增。
每个节点包含且仅包含于一个Splay中。
边分为实边和虚边,实边包含在Splay中,而虚边总是由一棵Splay指向另一个节点(指向该Splay中中序遍历最靠前的点在原树中的父亲)。当某点在原树中有多个儿子时,只能向其中一个儿子拉一条实链(只认一个儿子),而其它儿子是不能在这个 Splay 中的。为了保持树的形状,我们要让到其它儿子的边变为虚边,由对应儿子所属的Splay的根节点的父亲指向该点,而从该点并不能直接访问该儿子(认父不认子)。
实现
Access(x)
LCT 核心操作,也是最难理解的操作。
在 LCT 中,我们不能总是保证两个点之间的路径是直接连通的(在一个Splay上)。
Access即定义为打通根节点到指定节点的实链,使得一条中序遍历以根开始、以指定点结束的Splay出现。相应地,不在路径上且连接了路径上结点的所有实边要变成虚边。
流程:
- 先新建一个空结点 \(y\),把 \(x\) Splay 到根结点
- 将其右儿子改为 \(y\)( \(x\) 成为根结点后所有深度大于 \(x\) 的结点便都集中在了 \(x\) 的右子树里,更改右儿子相当于把 \(x\) 与实儿子之间的实边切换为虚边,也就是断掉了 \(x\) 所在实链中更靠下的部分,并把 \(y\) 对应的实链接了上去 )
- 更新信息
- 令
y=x,x=fa[x]
(这里相当于跳到更上面的实链,继续把之前拼成的实链与上面的链拼接,重复第一步直到走到原树意义上的根结点。
省流:
- 转到根
- 换儿子
- 更新信息
- 当前操作点切换为轻边所指的父亲,重复第一步直到走到原树意义上的根结点。
代码如下:
inline void Access(int x) {
for(int y=0;x;x=fa[y=x]) // 当前操作点切换为轻边所指的父亲
Splay(x),ch[x][1]=y,pushup(x); // 转到根,换儿子,更新信息
}
MakeRoot(x)
换根操作,即让指定点成为原树的根
只是把根到某个节点的路径拉起来并不能满足我们的需要。更多时候,我们要获取指定两个节点之间的路径信息。
然而可能这两个点不是祖孙关系,这样的路径显然不能在一个Splay中。
这时候就利用到 Access(x)
和 Splay(x)
的翻转操作
Access(x)
后 \(x\) 一定是 Splay 中,中序遍历最后的点
Splay(x)
后,\(x\) 在 Splay 中将没有右子树
于是翻转整个Splay,使得所有点的深度都倒过来了,\(x\) 没了左子树,反倒成了深度最小的点,即根节点
Code:
inline void MakeRoot(int x) {
Access(x),Splay(x),Reverse(x);
}
FindRoot(x)
找 \(x\) 所在原树的树根,主要用来判断两点之间的连通性(即两点在原树中的根相同就代表他们联通)
Code:
inline int FindRoot(int x) {
Access(x),Splay(x);
while(ch[x][0])
pushdown(x),x=ch[x][0]; // 下传懒标记
Splay(x); // 保证时间复杂度
return x;
}
Split(x,y)
访问一条在原树中的链
拉出 \(x-y\) 的路径成为一个 Splay
Code:
inline void Split(int x,int y) {
MakeRoot(x),Access(y),Splay(y); // 以y为根
}
Link(x,y)
连一条 \(x \to y\) 的边
Code:
inline void Link(int x,int y) {
MakeRoot(x);
if(FindRoot(y)!=x) // 若两点已经在同一子树中,再连边不合法
fa[x]=y; // 使x的父亲指向y
}
Cut(x,y)
断开 \(x-y\) 的边
我们先判断两点是否连通,再判断他们是否为父子关系和 \(y\) 是否有左儿子。
因为 Access(x)
后,假如 \(y\) 与 \(x\) 在同一 Splay 中而没有直接连边,那么这条路径上就一定会有其它点,在中序遍历序列中的位置会介于 \(x\) 与 \(y\) 之间。
那么可能 \(y\) 的父亲就不是 \(x\) 了。
也可能 \(y\) 的父亲还是 \(x\) ,那么其它的点就在 \(y\) 的左子树中
只有三个条件都满足,才可以断掉。
使 \(x\) 为根后,\(y\) 的父亲一定指向 \(x\) ,深度相差一定是 \(1\) 。当 Access(y)
Splay(y)
以后, \(x\) 一定是 \(y\) 的左儿子,直接双向断开连接
Code:
inline void Cut(int x,int y) {
MakeRoot(x);
if(FindRoot(y)==x && fa[y]==x && !ch[y][0]) {
fa[y]=ch[x][1]=0;
pushup(x);
}
}
P3690 【模板】动态树(Link Cut Tree)
直接套模板即可
Code:
#include <stack>
#include <cstdio>
#include <vector>
using namespace std;
const int N=3e5+7;
int fa[N],val[N],sum[N],tag[N],ch[N][2];
stack<int> sta;
int n,m;
inline bool IsRoot(int x) {
return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
}
inline void pushup(int x) {
sum[x]=sum[ch[x][0]]^sum[ch[x][1]]^val[x];
}
inline void Reverse(int x) {
swap(ch[x][0],ch[x][1]);
tag[x]^=1;
}
inline bool dir(int x) {
return x==ch[fa[x]][1];
}
inline void pushdown(int x) {
if(tag[x]) {
if(ch[x][0])
Reverse(ch[x][0]);
if(ch[x][1])
Reverse(ch[x][1]);
tag[x]=0;
}
}
inline void Rotate(int x) {
int y=fa[x],z=fa[y],k=dir(x),w=ch[x][k^1];
if(IsRoot(y))
ch[z][dir(y)]=x;
ch[x][k^1]=y,ch[y][k]=w;
if(w)
fa[w]=y;
fa[y]=x,fa[x]=z;
pushup(y);
}
inline void Splay(int x) {
int y=x,z;
sta.push(y);
while(IsRoot(y))
y=fa[y],sta.push(y);
while(!sta.empty())
pushdown(sta.top()),sta.pop();
while(IsRoot(x)) {
y=fa[x],z=fa[y];
if(IsRoot(y))
Rotate((dir(x)^dir(y)) ? x : y);
Rotate(x);
}
pushup(x);
}
inline void Access(int x) {
for(int y=0;x;x=fa[y=x])
Splay(x),ch[x][1]=y,pushup(x);
}
inline void MakeRoot(int x) {
Access(x),Splay(x),Reverse(x);
}
inline int FindRoot(int x) {
Access(x),Splay(x);
while(ch[x][0])
pushdown(x),x=ch[x][0];
Splay(x);
return x;
}
inline void Split(int x,int y) {
MakeRoot(x),Access(y),Splay(y);
}
inline void Link(int x,int y) {
MakeRoot(x);
if(FindRoot(y)!=x)
fa[x]=y;
}
inline void Cut(int x,int y) {
MakeRoot(x);
if(FindRoot(y)==x && fa[y]==x && !ch[y][0]) {
fa[y]=ch[x][1]=0;
pushup(x);
}
}
signed main() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",val+i);
for(int op,x,y;m;--m) {
scanf("%d%d%d",&op,&x,&y);
if(op==0)
Split(x,y),printf("%d\n",sum[y]);
else if(op==1)
Link(x,y);
else if(op==2)
Cut(x,y);
else
Splay(x),val[x]=y;
}
return 0;
}
标签:Splay,LCT,ch,int,void,笔记,学习,fa,inline 来源: https://www.cnblogs.com/wshcl/p/LCT.html