1. 程式人生 > 實用技巧 >Link Cut Tree 動態樹

Link Cut Tree 動態樹

幹啥用的

對於一個森林,查詢鏈上資訊、

前置芝士:splay

P3690 【模板】Link Cut Tree (動態樹)

(開始大量盜圖)

首先確定LCT的一些基本概念

  • 實邊:每個點最多連一條實邊(類似樹剖),這個邊是隨便連的,而且是可以變的、虛邊:不是實邊的邊
  • 實鏈:對於每個實邊構成的鏈(這條鏈必須是從上到下深度遞增),開一個splay,存的是樹上的節點,按深度排序(中序遍歷為深度遞增),平衡樹上維護的資訊就是這一條鏈的資訊

對於每個節點,\(fa[x]\)表示父親(實鏈則表示splay上的父親,否則表示虛鏈上的父親),\(son[x][0/1]\)表示splay上的左右兒子,不記錄虛鏈的兒子

這樣判斷是否為當前這個splay的根的方法就變為

inline bool is_root(int x){return son[fa[x]][0]!=x&&son[fa[x]][1]!=x;}

這一棵樹

按splay劃分就是

因為有翻轉操作(之後會提到哪裡會用),所以需要用到文藝平衡樹

int son[N][2],val[N],sum[N],tag[N],fa[N];
inline void update(int x){sum[x]=sum[son[x][0]]^sum[son[x][1]]^val[x];}
inline void rev(int x){swap(son[x][1],son[x][0]);tag[x]^=1;}
inline void pushdown(int x){
	if(!tag[x])return;tag[x]=0;
	if(son[x][0])rev(son[x][0]);
	if(son[x][1])rev(son[x][1]);
}
inline int id(int x){return x==son[fa[x]][1];}
inline void rotate(int x){
	int f=fa[x],gf=fa[f],idf=id(f),idx=id(x);
	if(!is_root(f))son[gf][idf]=x;fa[x]=gf;
	son[f][idx]=son[x][idx^1];son[x][idx^1]=f;
	if(son[f][idx])fa[son[f][idx]]=f;fa[f]=x;
	update(f);update(x);
}
int stac[N],top;
inline void splay(int x){
	top=0;int cur=x;stac[++top]=x;
	while(!is_root(cur))cur=fa[cur],stac[++top]=cur;
	while(top)pushdown(stac[top--]);//從上到下下放標記
	while(!is_root(x)){
		if(!is_root(fa[x])){
			if(id(fa[x])==id(x))rotate(fa[x]);
			else rotate(x);
		}
		rotate(x);
	}
}

\(access(x)\)

LCT基本操作,讓\(x\)到根的路徑上為一條實鏈

目標:

依次完成:

把自己變為根,斷掉原來的兒子,向上

把自己變為根,替換掉原來的兒子,向上

把自己變為根,替換掉原來的兒子,向上

把自己變為根,替換掉原來的兒子,向上

直到合併到根為止

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

其餘操作都在\(splay\)\(access\)基礎上

\(make\_root(x)\)

\(x\)成為根

先打通\(x\)到根的路徑,然後讓\(x\)

成為當前\(splay\)的根

但是這時的\(splay\)是按深度遞增的,沒有變,要變成按深度遞減,那麼就需要把這棵樹翻轉一下

inline void make_root(int x){
	access(x);	splay(x);	rev(x);
}

\(find\_root(x)\)

找到\(x\)所在樹的根

先打通\(x\)到根的路徑,找到這個splay的深度最低的點即可(注意\(pushdown\)

inline int find_root(int x){
	access(x);	splay(x);	pushdown(x);
	while(son[x][0])x=son[x][0],pushdown(x);
	splay(x);
	return x;
}

\(link(x,y)\)

先讓\(x\)成為根,判斷一下\(y\)的根是不是\(x\)

是的話就已經在一棵樹內,不用連邊

否則連一條虛邊

inline void link(int x,int y){
	make_root(x);
	if(find_root(y)==x)return ;
	fa[x]=y;
}

\(cut(x,y)\)

同樣的先把\(x\)作為根,判斷一下\(y\)在不在\(x\)的子樹內

注意這裡呼叫了\(find\_root(y)\),若\(y\)\(x\)的子樹內,那麼\(x\)\(y\)的鏈已經打通,\(x,y\)在同一顆\(splay\)內,且這棵\(splay\)的根為\(x\)

那麼如果\(y\)\(x\)有連邊,那麼\(y\)的深度等於\(dep[x]+1\),又因為splay維護一條鏈的資訊,這個\(dep[x]+1=dep[y]\)的點事唯一的,即\(y\)一定是\(x\)的右兒子,且\(y\)沒有左兒子(不存在在\(x,y\)之間的點)

inline void cut(int x,int y){
	make_root(x);
	if(find_root(y)!=x||fa[y]!=x||son[y][0])return;
	fa[y]=son[x][1]=0;update(x);
	return ;
}

\(query(x,y)\)

\(x\)為根,連上\(x,y\)的邊,以\(x/y\)為splay的根

這樣跟的資訊就是整條鏈的資訊

inline int query(int x,int y){
	make_root(x);access(y);splay(y);
	return sum[y];
}

\(modify(x,val)\)

修改資訊

即把\(x\)作為其所在\(splay\)的根,然後跟新自己的\(val\),最後\(update\)即可

inline void modify(int x,int v){
	splay(x);val[x]=v;update(x);
}