link-cut-tree

AgOH 大佬的视频:https://www.bilibili.com/video/BV1G4411z7mN

link-cut-tree 用来维护动态森林,可以支持连边、断边、查询树链信息的操作,树链剖分的加强版

实链剖分:每个非叶子节点都有一个实儿子,和它之间的边是实边,和其它儿子间的边都是虚边。实边和虚边是可以相互转化的
lct 中,使用单向边(从儿子到父亲)来表示虚边,双向边表示实边
lct 具体由 splay 来维护,满足这些性质:

  • 每个节点在且只在一个 splay 中
  • 对于每一个 splay,他维护的是一条实链,且它中序遍历的结果,在原树中深度递增
  • 实边包含在了 splay 中,虚边都由 splay 里的根节点的 fa 指针指向另一个 splay 中的节点(非根节点的 fa 指针当然就正常的指向它 splay 里的父亲)

access 操作,最基本的操作,将某一结点与它在原树中的根节点之间的路径打通成一条实链
对于当前的一个 (x),先把它伸展到它所在的 splay 的根节点(注意区分原图和 splay),把这个 (x) 的右儿子修改为上一个 (x)(因为上一个的深度肯定比这个大,所以是右儿子)
修改后,(x) 和它原来的右儿子之间边变成了虚边
最后 (x) 变成它的父亲(经过一条虚边)
初始时 (x) 当然是 null

makeroot 操作:让一个节点变成他在原树中的根
很简单,就先把 (x) 用一个 access,然后再伸展到根
此时,由于 (x) 是深度最深的,所以它没有右儿子,然后如果把这棵树反转一下,那么中序遍历也就都反转了(像文艺平衡树那样),此时 (x) 深度最小,变成了根
反转当然就是用懒标记了

findroot 操作:找到一个节点在原树中的根
还是打通 (x) 到根的路径,然后把它伸展到 splay 的根
此时,因为根的深度在原树中最小,所以就从 splay 的根开始,一直往左儿子走,走到不能再走,最后那个点就是中序遍历的第一个点,深度最小,也就是原树的根了

link 操作:将两个点连边,要处理数据不合法的情况
先把其中一个点,(x) 弄成原树的根节点
如果 (y) 不在 (x) 的树中(用 findroot 来处理),就连一条虚边,(x) 的父指针指向 (y)

cut 操作:将两个点的边断开,同样要处理数据不合法的情况
还是把 (x) 先弄成原树的根节点
首先如果 (y) 不在这棵树,肯定不用断边。如果在这个 splay,当且仅当 (x)(y) 在中序遍历中相邻时,他们之间才有边
也能简单的判断出来:如果 (y) 的父亲不是 (x),显然不行
如果 (y) 有左儿子,也不行,它的左儿子会比 (y) 中序遍历靠前,而 (x) 的中序遍历肯定在最前(它被变成根了),所以叶不是相邻的

split 操作:把两个点之间的路径放到一个 splay 上摘出来,方便做一些处理
(x) 变成根,然后打通 (y)(x) 之间的路径,再把 (x) 旋转到根,方便访问

然后查询操作用 split 完成,更改节点权值的的操作就把对应的点伸展到 splay 的根进行处理
还有记得下方标记,一些细节问题和一般的 splay 还是有区别的

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
	register int x=0;register int y=1;
	register char c=std::getchar();
	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
	return y?x:-x;
}
#define N 100005
struct LCT{
	struct tr{
		tr *fa,*son[2];
		int val,res;
		int tag;
	}*null,*pos[N],dizhi[N];
	#define ident(tree,fa) (fa->son[1]==tree)
	#define pushup(tree) tree->res=tree->son[0]->res^tree->son[1]->res^tree->val
	#define notroot(tree) (tree->fa->son[0]==tree||tree->fa->son[1]==tree)
	inline void connect(tr *tree,tr *fa,int k){fa->son[k]=tree;tree->fa=fa;}
	inline void pushdown(tr *tree){
		if(!tree->tag) return;
		tree->son[0]->tag^=1;tree->son[1]->tag^=1;
		std::swap(tree->son[0],tree->son[1]);
		tree->tag=0;
	}
	inline void rotate(tr *tree){
		tr *fa=tree->fa,*faa=fa->fa;
		pushdown(fa);pushdown(tree);
		//顺序不能变,因为如果 pushdown(tree) 以后,pushdown(fa) 又更改了 tree 的 tag,那带着 tag 旋转出现错误
		int k=ident(tree,fa);
		connect(tree->son[k^1],fa,k);
		tree->fa=faa;
		if(notroot(fa)) faa->son[ident(fa,faa)]=tree;//fa 是跟,fa faa 之间就是虚边
		connect(fa,tree,k^1);//保证顺序,在此语句更改 fa->fa 之前,判断 notroot(fa)
		pushup(fa);pushup(tree);
	}
	inline void splay(tr *tree){
		reg tr *fa,*faa;
		while(notroot(tree)){
			fa=tree->fa;faa=fa->fa;
			if(notroot(fa)) ident(fa,faa)^ident(tree,fa)?rotate(tree):rotate(fa);
			rotate(tree);
		}
	}
	inline void access(reg tr *x){
		for(reg tr *lastx=null;x!=null;lastx=x,x=x->fa){
			pushdown(x);
			splay(x);
			x->son[1]=lastx;pushup(x);
		}
	}
	inline void makeroot(tr *x){
		access(x);
		splay(x);
		x->tag^=1;
	}
	inline tr *findroot(tr *x){
		access(x);splay(x);
		pushdown(x);
		while(x->son[0]!=null) x=x->son[0],pushdown(x);
		splay(x);
		return x;
	}
	inline void link(tr *x,tr *y){
		makeroot(x);
		if(findroot(y)!=x) x->fa=y;
	}
	inline void cut(tr *x,tr *y){
		makeroot(x);
		if(findroot(y)!=x||y->fa!=x||y->son[0]!=null) return;
		y->fa=x->son[1]=null;
		pushup(x);
	}
	inline void split(tr *x,tr *y){
		makeroot(x);
		access(y);splay(y);
		//splay(y) 以后,y 没有右儿子,且其左儿子是一条通到 x 的实链
	}
	inline void creat(int i,int val){
		pos[i]=&dizhi[i];
		dizhi[i].res=dizhi[i].val=val;
		dizhi[i].son[0]=dizhi[i].son[1]=dizhi[i].fa=null;
	}
}lct;
int main(){
	lct.null=&lct.dizhi[0];
	int n=read(),m=read();
	for(reg int i=1;i<=n;i++) lct.creat(i,read());
	reg int op,x,y;
	while(m--){
		op=read();x=read();y=read();
		if(!op) lct.split(lct.pos[x],lct.pos[y]),printf("%d
",lct.pos[y]->res);
		else if(op==1) lct.link(lct.pos[x],lct.pos[y]);
		else if(op==2) lct.cut(lct.pos[x],lct.pos[y]);
		else{
			lct.splay(lct.pos[x]);
			lct.pos[x]->val=y;
			pushup(lct.pos[x]);
		}
	}
	return 0;
}

原文地址:https://www.cnblogs.com/suxxsfe/p/13454347.html