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\)是按深度遞增的,沒有變,要變成按深度遞減,那麼就需要把這棵樹翻轉一下
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);
}