其他分享
首页 > 其他分享> > LCT学习笔记

LCT学习笔记

作者:互联网

概念

前置芝士:链剖分

链剖分:指一类对树的边进行划分的操作,这样做可以减少某些链上的修改、查询等操作的复杂度。链剖分分为重链剖分,实链剖分和长链剖分(不常见)。

重链剖分:实际上树剖就是重链剖分的常用称呼。可以看看 树链剖分学习笔记

实链剖分:同样将某一个儿子的连边划分为实边,而连向其他子树的边划分为虚边。区别在于虚边、实边是可以动态变化的,要使用更高级、更灵活的Splay来维护每一条由若干实边连接而成的实链。

基于更逆天的实链剖分,LCT(Link-Cut Tree)应运而生。
LCT维护的对象其实是一个森林,在实链剖分的基础下,LCT资磁更多的操作

such as:

LCT把边分为实边和虚边,每个结点最多有一个实儿子(可以没有实儿子)

每一个Splay维护的是一条从上到下按在原树中深度严格递增的路径,且中序遍历Splay得到的每个点的深度序列严格递增。

每个节点包含且仅包含于一个Splay中。

边分为实边和虚边,实边包含在Splay中,而虚边总是由一棵Splay指向另一个节点(指向该Splay中中序遍历最靠前的点在原树中的父亲)。当某点在原树中有多个儿子时,只能向其中一个儿子拉一条实链(只认一个儿子),而其它儿子是不能在这个 Splay 中的。为了保持树的形状,我们要让到其它儿子的边变为虚边,由对应儿子所属的Splay的根节点的父亲指向该点,而从该点并不能直接访问该儿子(认父不认子)。

实现

Access(x)

LCT 核心操作,也是最难理解的操作。
在 LCT 中,我们不能总是保证两个点之间的路径是直接连通的(在一个Splay上)。
Access即定义为打通根节点到指定节点的实链,使得一条中序遍历以根开始、以指定点结束的Splay出现。相应地,不在路径上且连接了路径上结点的所有实边要变成虚边。

流程:

  1. 先新建一个空结点 \(y\),把 \(x\) Splay 到根结点
  2. 将其右儿子改为 \(y\)( \(x\) 成为根结点后所有深度大于 \(x\) 的结点便都集中在了 \(x\) 的右子树里,更改右儿子相当于把 \(x\) 与实儿子之间的实边切换为虚边,也就是断掉了 \(x\) 所在实链中更靠下的部分,并把 \(y\) 对应的实链接了上去 )
  3. 更新信息
  4. y=x,x=fa[x] (这里相当于跳到更上面的实链,继续把之前拼成的实链与上面的链拼接,重复第一步直到走到原树意义上的根结点。

省流:

  1. 转到根
  2. 换儿子
  3. 更新信息
  4. 当前操作点切换为轻边所指的父亲,重复第一步直到走到原树意义上的根结点。

代码如下:

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);
	}
}

直接套模板即可

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