[学习笔记] Link-Cut-Tree

模板题点这里

大体思路

可以看到,(LCT)就是用于解决这一类问题的,下面我们就来看一下它是怎么实现的。

我们知道有一种叫做树剖的东西,这玩意儿好像可以支持链上的一些操作。

我们还知道有一种叫做(Splay)的东西,这玩意儿貌似可以可以通过瞎搞完成很多动态的操作。

要不?让他们生个孩子?!

XD

嗯反正大致思路就是这样子的啦!

一些定义

Preferred Child: 重儿子,重儿子与父亲节点在同一棵Splay中,一个节点最多只能有一个重儿子。

Preferred Edge: 重边,连接父亲节点和重儿子的边。

Preferred Path : 重链,由重边及重边连接的节点构成的链。

Auxiliary Tree(辅助树):

  • 由一条重链上的所有节点所构成的(Splay)称作这条链的辅助树。

  • 每个点的键值为这个点的深度,即这棵(Splay)的中序遍历是这条链从链顶到链底的所有节点构成的序列

  • 辅助树的根节点的父亲指向链顶的父亲节点,然而链顶的父亲节点的儿子并不指向辅助树的根节点
    (也就是说父亲不认轻儿子只认重儿子,儿子都认父亲)

具体实现

假如我们现在有两种操作:

  • (access(x))可以让你把(x)到当前实树的根的路径全部变成重链,也就是说,(x)到根路径上的所有点都会在一棵(Auxiliary Tree)中,而这可(Auxiliary Tree)上只有这些节点

  • (makert(x))可以让你把(x)节点当做实树中的根(因为是树嘛,所以根是可以变的)

(Query)

有了这两种逆天操作,那么我们现在应该怎么维护题中的询问操作呢?

显然,我们只需要把(x)(y)路径上的那些点都放到一棵(Auxiliary Tree)即可。

那么我们就可以先把(x)作为实树的根,然后再把(y)(x)之间的路径打通即可,这样这些点就全部在同一颗(Auxiliary Tree)上了。

如果要求值的话就(Splay(x/y)),然后你的(Sum[x/y])就是答案了。

void query(int x,int y){
	makert(x),access(y),splay(y);
}

(Findrt)

我们应该怎么寻找包含点(x)的实树的根呢?

我们只要先(access(x))打通(x)到根的路径,再(Splay(x)),然后一直往左找就是根了。

因为根的深度是最小的嘛!

int findrt(int x){
	access(x),splay(x);
	while (ch[x][0]) pushdown(x),x=ch[x][0]; 
	splay(x);
	return x;
}

(Link)

连边操作,想想应该怎么搞。

很简单 (是真的简单) ,先判断一下(x)(y)两点是不是在一颗实树内,如果不在的话把他们连上就是了,当然,连的是虚边,儿子认爸爸,爸爸不认儿子的那种。

void link(int x,int y){
	makert(x),fa[x]=y;
}
if (findrt(x)!=findrt(y)) link(x,y);

(Cut)

先判一判他们两个点之间是不是有边连接。

怎么判呢?

先把(x)变成根,再打通(x)(y)之间的路,如果(x)(y)之间直接就有连边的话,点(y)肯定就是在(x)的下面一层.

那么把(y点)(Splay)到根之后(x)肯定是他的左儿子,而且点(x)是没有右儿子的,因为(x)的重儿子只有一个且只能是(y),所以不会再出现深度比(x)大,比(y)小的点。

void cut(int x,int y){
	makert(x),access(y),splay(y);
	if (ch[y][0]!=x&&!ch[ch[y][0]][1]) return;
	ch[y][0]=fa[x]=0,update(y);
}

(Access)

相信把上面的操作都讲了一圈以后,大家对于(access)操作的理解都加深了吧,那么接下来(access)的实现方法就很简单了。

(access)操作中,我们要做到两点:

  • 断掉父亲节点原来的重链

  • 把父亲节点和它的辅助树合并

我们只要一直重复这两步,一直到父亲为实树的根为止(具体表现为(fa[x])(0))。

void access(int x){
	for (int y=0;x;y=x,x=fa[x]) splay(x),ch[x][1]=y,update(x);
}

(Makert)

很简单,把点(x)到根的路径打通,然后把(x)(Splay)到根即可。

注意,因为此时的根节点发生变换,所以所有节点的深度都会翻转,要给(x)打一个翻转标记。

void makert(int x){
	access(x),splay(x),rever(x);
}

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=300005;
int fa[N],ch[N][2],val[N],w[N],rev[N];
int n,q,opt,x,y;
int where(int x){return ch[fa[x]][1]==x;}
bool isrt(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
void rever(int x){rev[x]^=1,swap(ch[x][0],ch[x][1]);}
void update(int x){val[x]=val[ch[x][0]]^val[ch[x][1]]^w[x];}
void pushdown(int x){
	if (rev[x]){
		if (ch[x][0]) rever(ch[x][0]);
		if (ch[x][1]) rever(ch[x][1]);
		rev[x]=0;
	}
}
void Allpushdown(int x){
	if (!isrt(x)) Allpushdown(fa[x]);
	pushdown(x);
}
void rotate(int x){
	int par=fa[x],son=x,c=where(x);
	if (!isrt(par)) ch[fa[par]][where(par)]=son;
	fa[son]=fa[par];
	ch[par][c]=ch[son][c^1];
	fa[ch[par][c]]=par;
	ch[son][c^1]=par;
	fa[par]=son;
	update(par),update(son);
}
void splay(int x){
	Allpushdown(x);
	for (;!isrt(x);rotate(x))
		if (!isrt(fa[x])) rotate(where(fa[x])==where(x)?fa[x]:x);
}
void access(int x){
	for (int y=0;x;y=x,x=fa[x]) splay(x),ch[x][1]=y,update(x);
}
void makert(int x){
	access(x),splay(x),rever(x);
}
int findrt(int x){
	access(x),splay(x);
	while (ch[x][0]) pushdown(x),x=ch[x][0]; 
	splay(x);
	return x;
}
void link(int x,int y){
	makert(x),fa[x]=y;
}
void cut(int x,int y){
	makert(x),access(y),splay(y);
	if (ch[y][0]!=x&&!ch[ch[y][0]][1]) return;
	ch[y][0]=fa[x]=0,update(y);
}
void query(int x,int y){
	makert(x),access(y),splay(y);
}
int main(){
	scanf("%d%d",&n,&q);
	for (int i=1;i<=n;i++) scanf("%d",&w[i]);
	while (q--){
		scanf("%d%d%d",&opt,&x,&y);
		if (opt==0) query(x,y),printf("%d
",val[y]);
		if (opt==1) if (findrt(x)!=findrt(y)) link(x,y);
		if (opt==2) cut(x,y);
		if (opt==3) w[x]=y,splay(x);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/WR-Eternity/p/10862762.html