ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

LCT学习笔记

2022-07-01 15:05:36  阅读:140  来源: 互联网

标签:Splay LCT ch int void 笔记 学习 fa inline


概念

前置芝士:链剖分

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

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

实链剖分:同样将某一个儿子的连边划分为实边,而连向其他子树的边划分为虚边。区别在于虚边、实边是可以动态变化的,要使用更高级、更灵活的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出现。相应地,不在路径上且连接了路径上结点的所有实边要变成虚边。

流程:

  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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有