模板:Link Cut Tree(LCT)
文章目錄
- 前言
- 解析
- 原理
- rotate(x)
- splay(x)
- access(x)
- findroot(x)
- makeroot(x)
- split(x,y)
- link(x,y)
- cut(x,y)
- pushdown(x)
- 完整代碼
所謂Link Cut Tree,就是林可卡特發(fā)明的tree
(逃)
前言
終于走到了這一天…
其實(shí)感覺(jué)沒(méi)有預(yù)想的那么難(單純就學(xué)習(xí)算法和理解模板而言)
至少感覺(jué)比學(xué)平衡樹(shù)輕松
LCT是一種很強(qiáng)大的樹(shù)形數(shù)據(jù)結(jié)構(gòu)
最重要的功能優(yōu)勢(shì)就是支持單log的復(fù)雜度完成加邊、刪邊、換根的操作
在路徑問(wèn)題上十分強(qiáng)大
解析
原理
LCT的核心原理是實(shí)鏈剖分
和重鏈剖分類似的,將一棵樹(shù)中的邊分為實(shí)邊和虛邊
每個(gè)結(jié)點(diǎn)的最多有一條連向兒子的實(shí)邊(與樹(shù)鏈剖分不同的,也可以沒(méi)有)
把實(shí)邊連成的鏈成為實(shí)鏈
把在同一條實(shí)鏈上的結(jié)點(diǎn)放到同一個(gè)splay中
這個(gè)splay的中序遍歷就是這條鏈從上到下的順序
這樣就間接的表示了所有實(shí)邊的信息
那么如何表示虛邊?
假設(shè)原樹(shù)上有一條 fafafa 指向sonsonson的虛邊
就把son所在的splay的根節(jié)點(diǎn)指向fa,從而表示出虛邊
根節(jié)點(diǎn)也有父親了,如何判斷根節(jié)點(diǎn)呢?
只有該結(jié)點(diǎn)的父親的左右兒子都不是自己,那么這個(gè)結(jié)點(diǎn)就是根節(jié)點(diǎn)
下面我們來(lái)講講具體的操作函數(shù)
rotate(x)
和普通的splay大同小異,唯一需要注意的是有一句
if(gfa) tr[gfa][which(fa)]=x;變成了:
if(!isroot(fa)) tr[gfa][which(fa)]=x;較好理解,因?yàn)榕卸ǜ臈l件變了
splay(x)
將x轉(zhuǎn)到所在splay的根節(jié)點(diǎn),由于只需要轉(zhuǎn)到根一個(gè)功能,穿一個(gè)參即可
注意:不同于正常的splay,需要先把鏈上的標(biāo)記下放
實(shí)現(xiàn)有兩種方法
第一種是遞歸:
第二種是手寫(xiě)棧:
int y=x,top=0;zhan[++top]=y;while(!isroot(y)) zhan[++top]=y=f[y];建議使用第二種,因?yàn)榈谝环N不小心容易寫(xiě)完函數(shù)后忘記調(diào)用(別問(wèn)我為什么知道)
然后和rotate類似的,對(duì)根的判斷略有區(qū)別
不過(guò)都是一個(gè)道理
access(x)
重中之重,LCT的靈魂
功能:提取出 x 到根節(jié)點(diǎn)的一條實(shí)鏈
先把x的尾巴去掉,具體來(lái)說(shuō)就是轉(zhuǎn)到根,然后砍掉右子樹(shù)
然后一路往上直至 fa 指針為空,過(guò)程中需要改變實(shí)虛邊的狀態(tài)
findroot(x)
功能:找到x所在的樹(shù)的根節(jié)點(diǎn)(注意是真實(shí)的樹(shù)而不是splay的根)
先access(x),然后把x轉(zhuǎn)到根,不斷往左走即可
inline int findroot(int x) {access(x);splay(x);while(pushdown(x),tr[x][0]) x=tr[x][0];splay(x);return x; }makeroot(x)
功能:把x作為其所在樹(shù)的根(換根)
前面說(shuō)過(guò),父子關(guān)系在splay中的體現(xiàn)是中序遍歷
所以換根(即顛倒x-rt路徑上所有的父子關(guān)系),其實(shí)就是區(qū)間翻轉(zhuǎn)
先access(x),然后轉(zhuǎn)到根,然后用類似文藝平衡樹(shù)的方式在x上打一個(gè)翻轉(zhuǎn)標(biāo)記即可
(最愉快的代碼)
split(x,y)
功能:提取出以x、y為兩端點(diǎn)的實(shí)鏈(前提是二者聯(lián)通)
makeroot(x)之后access(y)即可
為了后面方便,常常再加一步splay(y)(不然連根是誰(shuí)都不知道提取個(gè)寂寞)
link(x,y)
功能:加一條邊(x,y)(x,y)(x,y)(在有出現(xiàn)環(huán)的時(shí)候忽略操作)
出現(xiàn)環(huán)(即xy原來(lái)就聯(lián)通)可以用findroot判掉
否則,先makeroot(x),然后把f(x)指向y即可
記得按需要更新y的信息!
cut(x,y)
功能:切斷邊(x,y)(x,y)(x,y)(在沒(méi)有這條邊時(shí)忽略操作)
先makeroot(x),然后access(y),splay(y)到根
此時(shí)如果存在這條邊,必然x和y是在同一個(gè)splay中
判斷的充要條件:x是y的左兒子且x沒(méi)有右兒子,比較顯然
如果存在的話,把這條y的左兒子和x的父親賦值為0即可
不要忘記更新y的信息(又一次)
pushdown(x)
大部分時(shí)候只需要翻轉(zhuǎn)
一個(gè)看到的地方都說(shuō)比較“穩(wěn)妥”的寫(xiě)法
(誰(shuí)不喜歡穩(wěn)妥呢)
完整代碼
經(jīng)驗(yàn)傳送門(mén)
#include<bits/stdc++.h> using namespace std; #define ll long long const int N=2e5+100; const int mod=1e9+7; const double eps=1e-9; inline ll read() {ll x(0),f(1);char c=getchar();while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f; }int n,m;#define ls(o) tr[o][0] #define rs(o) tr[o][1] int tr[N][2],f[N],rev[N],val[N],mx[N]; inline bool isroot(int x) {return tr[f[x]][0]!=x&&tr[f[x]][1]!=x; } inline bool which(int x) {return tr[f[x]][1]==x; } inline void pushup(int x) {if(x){mx[x]=x;if(ls(x)&&val[mx[ls(x)]]>val[mx[x]]) mx[x]=mx[ls(x)];if(rs(x)&&val[mx[rs(x)]]>val[mx[x]]) mx[x]=mx[rs(x)];}return; } inline void tag(int x) {if(x) {rev[x]^=1;swap(tr[x][0],tr[x][1]);} } inline void pushdown(int x) {if(rev[x]){rev[x]=0;tag(tr[x][0]);tag(tr[x][1]);}return; } void debug(int x) {if(!x) return;pushdown(x);debug(tr[x][0]);printf("debug: x=%d ls=%d rs=%d\n",x,tr[x][0],tr[x][1]);debug(tr[x][1]);return; } inline void rotate(int x) {int fa=f[x],gfa=f[fa];int d=which(x),son=tr[x][d^1];f[x]=gfa;if(!isroot(fa)) tr[gfa][which(fa)]=x;f[fa]=x;tr[x][d^1]=fa;if(son){f[son]=fa;}tr[fa][d]=son;pushup(fa);pushup(x);return; } int zhan[N]; inline void splay(int x) {int y=x,top=0;zhan[++top]=y;while(!isroot(y)) zhan[++top]=y=f[y];while(top) pushdown(zhan[top--]);for(int fa; fa=f[x],!isroot(x); rotate(x)) {if(!isroot(fa)) which(fa)==which(x)?rotate(fa):rotate(x);}return; } inline void access(int x) {for(int y(0); x; y=x,x=f[x]) {splay(x);tr[x][1]=y;pushup(x);if(y) f[y]=x;}return; } inline void makeroot(int x) {access(x);splay(x);tag(x);return; } inline int findroot(int x) {access(x);splay(x);while(pushdown(x),tr[x][0]) x=tr[x][0];splay(x);return x; } inline void link(int x,int y) {makeroot(x);if(findroot(y)==x) return;f[x]=y;pushup(y);//printf("link: %d -> %d\n",x,y); } inline void cut(int x,int y) {makeroot(x);access(y);splay(y);if(tr[y][0]!=x||tr[x][1]) return;tr[y][0]=f[x]=0;pushup(y);return; } inline void split(int x,int y){//y is fathermakeroot(x);access(y);splay(y);return; }總結(jié)
以上是生活随笔為你收集整理的模板:Link Cut Tree(LCT)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 开源修图工具 GIMP 2.10.36
- 下一篇: YBTOJ洛谷P2387: 魔法森林(L