1. 程式人生 > 實用技巧 >link-cut-tree

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);
		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);
		while(x->son[0]!=null) pushdown(x),x=x->son[0];
		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\n",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;
}