1. 程式人生 > >動態樹 之 Link-Cut Tree (LCT)

動態樹 之 Link-Cut Tree (LCT)

自此 Frocean 不想搞資料結構了(當然熟練一下LCT還是要的) 暫時是夠用的了

2017年9月 初三 退出腐敗界潛心搞OI 當然已經欠得很多了

2018年4月 當時剛學完線段樹 然後總是聽見機房dalao們喊樹剖 於是去找樹剖題 順便請教了某dalao 然後該dalao看了跟我說這類題目LCT都能做 我:"那我要學LCT惹~" 然而又要先搞樹剖

於是概念之類的東西瞎折騰了兩週多 然後程式碼交到改對又花了大概一週(真是弱啊 現在我LCT兩天就弄完= =)

然後中途忘掉了這東西 做了些水題鞏固基礎 外加一些其他基礎演算法

2018年8月 來雙鴨山訓練的時候聽說A組一天一個LCT 突然想起來這事 然後瞎搞與LCT有關的splay結果被教練抓住叫去弄USACO題庫

2018年9月 splay搞定了 然而區間翻轉還是不會 但是LCT要用到

2018年10月 splay區間翻搞定了 然後31號開始看LCT

2018.11.1 LCT搞定了 當然現在還不熟

現在想想為了個LCT我學的東西還挺多的 然而同樣是平衡樹Treap我卻沒學

 

好了心路歷程記完了 現在開始說說LCT

前置知識 Splay(力推我的) + 樹鏈剖分(最好理解) 我當諸君都很熟練了啊QAQ

原理概念之類的戳這裡 他講的挺不錯的 然後我主要記記操作&程式上

話說LCT裡相關操作的子程式好多 講起來比較亂 首先規定一個模板題 然後相關基礎操作圍繞此題展開

先把核心操作丟上面 然後分詢問要求放相關操作 建議順序:Link—>delete—>change(date)—>query(get) 然後多翻幾遍核心操作啦

 

注:個人習慣原因有些子程式名字會相替換 個人的丟括號裡面

 

1.isroot(pdroot)

這個就是判斷當前點是不是他所在的 splay 的根了啦 其實我也不想放前面但後面常常用到

如果一個點是根 那他父親就和他沒什麼關係了 在樹上一大把 splay 的 LCT 上 連線他們的只是各個根的父親丟到上面的 splay 裡

所以一個根的父親的兒子不會指向他!我們可以利用這個性質判斷一個點是不是他所在 splay 的 根

程式碼只有一行 見下(說四行的我不打你)

inline short pdroot(int x)
{
	return (e[e[x].fa].son[0] != x) && (e[e[x].fa].son[1] != x);
}

 

然後我先把 splay 相關丟出來好了 這樣看著 LCT 親切點

2.pushup(update)

這個地方除了rotate其他時候也要用到哦 因為不止這地方換了父子從屬關係

所以打成了個子程式= = 本題裡就是維護路徑的異或和

下放程式碼

inline void update(int x)
{
	e[x].ans = e[e[x].son[0]].ans ^ e[e[x].son[1]].ans ^ e[x].v;
}

 

3.pushdown

就是下放旋轉的 lazy 標記

和splay區間翻的完全一樣啦

下放程式碼

inline void pushdown(int x)
{
	if (!e[x].laz) return;
	int bot = e[x].son[0];
	e[x].son[0] = e[x].son[1];
	e[x].son[1] = bot;
	e[e[x].son[0]].laz ^= 1;
	e[e[x].son[1]].laz ^= 1;
	e[x].laz = 0;
}

 

4.relax(change)

這個就是在換父子關係前同一將涉及到的點的lazy標記下放

因為某些原因我們只能從底下那個點找起 但是標記更新要從最上面啊

所以 遞迴 然後從最上面更新 這裡利用pushdown

其實pushdown和relax只有splay裡面涉及到的惹= =其他地方不用(其他地方用splay2333)

下放程式碼

inline void change(int x)
{
	if (!pdroot(x)) change(e[x].fa);
	pushdown(x);
}

 

5.splay

許多人的 splay (包括我) 的迴圈判斷都是 父節點不為0的時候往下跑

但這裡很多 splay 而且判斷父節點是不是出去了很麻煩

於是判斷當前點是不是根就好啦 往上翻 1.isroot 其他照常 splay

下放程式碼

inline void splay(int x)
{
	change(x);
	while (!pdroot(x))
	{
		int y = e[x].fa,z = e[y].fa;
		if (!pdroot(y)) {
			if ((e[z].son[0] == y) ^ (e[y].son[0] == x)) rotate(x);
			else rotate(y);
		} rotate(x);
	}
}

 

6.rotate

對了就是熟悉的 rotate 但是有不同的地方 (x是當前點 y是父親 z是祖父)

這裡關於z的話 如上 1.isroot 中所說 如果 z 和 x 不是同一 splay 中的話 z 的孩子就不用更新成 x

所以這裡丟個判定就好了啦 然後....這類題目不用維護 size 喜大普奔 但是求答案還是要維護下

下放程式碼 (關於求答案 到時候 access 從根連起來直接找根的答案陣列就可以輸出了)

 

inline void rotate(int x)
{
	int y = e[x].fa,z = e[y].fa,mode = e[y].son[0] == x;
	if (!pdroot(y)) e[z].son[e[z].son[1] == y] = x;
	e[x].fa = z;
	e[y].son[mode ^ 1] = e[x].son[mode];
	e[e[x].son[mode]].fa = y;
	e[x].son[mode] = y;
	e[y].fa = x;
	update(y); //update先後關係注意 和splay一樣先子後父
	update(x);
}

 

好了splay相關講完了 下面就是LCT的核心部分了(上面也包括了的哈)

7.access

這個大有用處了 作用是把某點和原樹的根所在路徑變成重邊

然後因為性質之類的 需要把原重邊 (如果不一樣) 換成輕邊

這個用處= =之後會提到的 就是方便處理問題(因為要處理的兩個數會是打通的重邊的兩個最外面的點)

然後怎麼更新呢 首先斷掉要通到的點的兒子

然後我們要splay一下 因為這樣可使連著當前點的重邊在點的右兒子處

然後更新當前點兒子 這裡和首先斷點可以連起來 設個變數一開始為0 然後第一次更新的點重兒子通向0 就是無重兒子了

很巧妙 然後後面把當前點記錄 之後推到他父親的時候把他父親的重兒子記錄到他上面 再更新他

splay之後的更新也會使當前點的兒子變化 所以要update當前點的異或和的答案

下放程式碼

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

 

8.makeroot

就是把某個點變成他所在的splay的根啦

首先打通他到原樹根的路徑 使得他為全樹深度最大 然後把他弄到根 這時候他是沒有右兒子的

然後為了處理該點和另外一個點的相對關係 (對 makeroot就是讓這兩點產生更親♂密的關係的)

我們用access和splay把當前點通到根 然後還要預翻轉一下 設當前點為 x 另一點為 y

這樣使得處理 y 點時 因為處理 y 點時必有access使得之後的splay變成兩點中的唯一簡單路徑

然後splay把 x + 1 到 y 翻轉 y 就是 x + 1 (在那個地方) 於是可以通過父子關係處理操作

絕妙啊 不說了丟程式碼

inline void makeroot(int x)
{
	access(x);
	splay(x);
	e[x].laz ^= 1;
}

 

好了好了接下來依次處理本題的詢問 首先是link的兩個

9.findroot(f)

就是找當前點所在的splay的root啦

首先打通 然後當前點旋到根 然後.......

顯然是深度往上跳嘛 那就找左兒子啦 深度減小嘛

直到找到某點兒子不是他 說明他兒子和他是屬於不同的樹了的 返回他即可

下放程式碼

inline int f(int p)
{
	access(p),splay(p);
	while (e[p].son[0]) p = e[p].son[0];
	return p;
}

 

10.link

就是連線兩點嘛 首先先判兩點是否聯通 如果聯通還連邊那這種題是不能用LCT做的惹qwq

然後讓其中一個點旋到根 那他是沒父親的了

把他父親變成另外一個點 那就連起來了啦

另外一個點的兒子不用更新 不然原本的就亂了 這個等之後處理其他操作的時候整理就好

下放程式碼

inline void link(int x,int y)
{
	if (f(x) != f(y)) makeroot(x),e[x].fa = y;
}

 

11.delete(dele)

刪邊 同link一樣要先makeroot其中一個點

但刪邊對兩點的相對關係要求更多 我們要把兩點弄到一塊

對了makeroot裡面翻轉使得兩點丟到一塊了

設兩點為 x 點和 y 點

於是 此時 x 變成根了 然後 y 的深度就是 x + 1

但是這樣很可能 y 在 x 的右兒子的左子樹裡面

所以 y 旋上去 然後比 y 小的 只有 x 於是 x 必定是其左兒子

先判定一下是不是 x 嘛 如果不是說明 (x,y) 這條邊不存在

然後父子關係都清零就好了

下放程式碼

inline void dele(int x,int y)
{
	makeroot(x);
	access(y);
	splay(y);
	if (e[y].son[0] == x) e[y].son[0] = 0,e[x].fa = 0;
}

 

12.modify(date)

原本想用update的但是重名了= =

就是更新點權 超簡單的

當前點權直接換掉 旋到根

splay經典操作不多講了

下放程式碼

inline void date(int x,int y)
{
	e[x].v = y;
	splay(x);
}

 

13.query(get)

好了最後的查詢

一個點丟到根上面 makeroot大法好

access打通到另一個點 然後兩點間簡單路徑的異或和就是答案~~

但是!這兩操作是把鏈提取出來了 但我們要得到整棵splay的答案 就需要splay另一個點 然後再返回根節點的值

所以還要把另一個點旋上來 輸出這個點的答案

下放程式碼

inline int get(int x,int y)
{
	makeroot(x);
	access(y);
	splay(y);
	return e[y].ans;
}

 

 

 

好了好了總算完了=-= 下面塞個總的程式碼 模板這裡再丟一個

#include <cstdio>
#define I inline
#define like int
#define love void
using namespace std;
const int MAXN = 300010;
struct lct {
	int fa,son[2],ans,laz,v;
} e[MAXN];
I like r()
{
	char q = getchar(); int x = 0,y = 0;
	while (q < '0' && q != '-' || q > '9') q = getchar();
	if (q == '-') ++ y,q = getchar();
	while ('0' <= q && q <= '9')
		x = (x << 3) + (x << 1) + q - (3 << 4),q = getchar();
	return y ? -x : x;
}
I like pdroot(int x)
{
	return e[e[x].fa].son[0] != x && e[e[x].fa].son[1] != x;
}
I love pushdown(int x)
{
	if (!e[x].laz) return;
	int bot = e[x].son[0];
	e[x].son[0] = e[x].son[1];
	e[x].son[1] = bot;
	e[e[x].son[0]].laz ^= 1;
	e[e[x].son[1]].laz ^= 1;
	e[x].laz = 0;
}
I love change(int x)
{
	if (!pdroot(x)) change(e[x].fa);
	pushdown(x);
}
I love update(int x)
{
	e[x].ans = e[e[x].son[0]].ans ^ e[e[x].son[1]].ans ^ e[x].v;
}
I love rotate(int x)
{
	int y = e[x].fa,z = e[y].fa,mode = e[y].son[0] == x;
	if (!pdroot(y)) e[z].son[e[z].son[1] == y] = x;
	e[x].fa = z;
	e[y].son[mode ^ 1] = e[x].son[mode];
	e[e[x].son[mode]].fa = y;
	e[x].son[mode] = y;
	e[y].fa = x;
	update(y);
	update(x);
}
I love splay(int x)
{
	change(x);
	while (!pdroot(x))
	{
		int y = e[x].fa,z = e[y].fa;
		if (!pdroot(y)) {
			if ((e[z].son[0] == y) ^ (e[y].son[0] == x)) rotate(x);
			else rotate(y);
		} rotate(x);
	}
}
I love access(int x)
{
	for (int y = 0 ; x ; y = x,x = e[x].fa)
	{
		splay(x);
		e[x].son[1] = y;
		update(x);
	}
}
I like f(int p)
{
	access(p),splay(p);
	while (e[p].son[0]) p = e[p].son[0];
	return p;
}
I love makeroot(int x)
{
	access(x);
	splay(x);
	e[x].laz ^= 1;
}
I like get(int x,int y)
{
	makeroot(x);
	access(y);
	splay(y);
	return e[y].ans;
}
I love link(int x,int y)
{
	if (f(x) != f(y)) makeroot(x),e[x].fa = y;
}
I love dele(int x,int y)
{
	makeroot(x);
	access(y);
	splay(y);
	if (e[y].son[0] == x) e[y].son[0] = 0,e[x].fa = 0;
}
I love date(int x,int y)
{
	e[x].v = y;
	splay(x);
}
int main()
{
	int n = r(),m = r();
	for (int a = 1 ; a <= n ; ++ a) e[a].ans = e[a].v = r();
	while (m--)
	{
		int mode = r(),x = r(),y = r();
		switch (mode)
		{
			case 0:printf("%d\n",get(x,y));break;
			case 1:link(x,y);break;
			case 2:dele(x,y);break;
			case 3:date(x,y);break;
		}
	}
	return 0;
}

 

2018.11.4 update——

萬般借鑑然後瞎搞一天半終於搞出 LCT模板2 (lazy加法乘法下放) 了

這題.....就是多了兩個 lazy 標記

這裡標記和siz差不多維護就好了呀 對於操作就是放到那個點上 更新那個點值 然後標記放放

因此換父子關係的時候就要處理兩子節點 然後更新兩子節點的值

其他照常 對了建圖用link就好了啦

下放程式碼

#include <cstdio>
#define mod 51061
#define MAXN 100010
#define ll long long
using namespace std;
struct splay {
	int fa,son[2],siz,rot;
	ll ans,v,laz1,laz2;
} e[MAXN];
inline int r()
{
	char q = getchar(); int x = 0,y = 0;
	while (q < '0' && q != '-' || q > '9') q = getchar();
	if (q == '-') ++ y,q = getchar();
	while ('0' <= q && q <= '9')
		x = (x << 3) + (x << 1) + q - (3 << 4),q = getchar();
	return y ? -x : x;
}
inline short pdroot(int x)
{
	return e[e[x].fa].son[0] != x && e[e[x].fa].son[1] != x;
}
inline void update(int x)
{
	e[x].siz = e[e[x].son[0]].siz + e[e[x].son[1]].siz + 1;
	e[x].ans = (e[e[x].son[0]].ans + e[e[x].son[1]].ans + e[x].v) % mod;
}
inline void muldown(int x,ll k)
{
	e[x].laz1 = e[x].laz1 * k % mod;
	e[x].laz2 = e[x].laz2 * k % mod;
	e[x].ans = e[x].ans * k % mod;
	e[x].v = e[x].v * k % mod;
}
inline void adddown(int x,ll k)
{
	e[x].ans = (e[x].ans + e[x].siz * k) % mod;
	e[x].laz1 = (e[x].laz1 + k) % mod;
	e[x].v = (e[x].v + k) % mod;
}
inline void rotdown(int x)
{
	int bot = e[x].son[0];
	e[x].son[0] = e[x].son[1];
	e[x].son[1] = bot;
	e[x].rot ^= 1;
}
inline void pushdown(int x)
{
	if (e[x].laz2 != 1)
	{
		muldown(e[x].son[0],e[x].laz2);
		muldown(e[x].son[1],e[x].laz2);
		e[x].laz2 = 1;
	}
	if (e[x].laz1)
	{
		adddown(e[x].son[0],e[x].laz1);
		adddown(e[x].son[1],e[x].laz1);
		e[x].laz1 = 0;
	}
	if (!e[x].rot) return;
	rotdown(e[x].son[0]);
	rotdown(e[x].son[1]);
	e[x].rot = 0;
}
inline void rotate(int x)
{
	int y = e[x].fa,z = e[y].fa,mode = e[y].son[0] == x;
	if (!pdroot(y)) e[z].son[e[z].son[1] == y] = x;
	e[x].fa = z;
	e[y].son[mode ^ 1] = e[x].son[mode];
	e[e[x].son[mode]].fa = y;
	e[x].son[mode] = y;
	e[y].fa = x;
	update(y);
	update(x);
}
inline void change(int x)
{
	if (!pdroot(x)) change(e[x].fa);
	pushdown(x);
}
inline void splay(int x)
{
	change(x);
	while (!pdroot(x))
	{
		int y = e[x].fa,z = e[y].fa;
		if (!pdroot(y)) {
			if ((e[z].son[0] == y) ^ (e[y].son[0] == x)) rotate(x);
			else rotate(y);
		} rotate(x);
	}
}
inline void access(int x)
{
	for (int y = 0 ; x ; y = x,x = e[x].fa)
	{
		splay(x);
		e[x].son[1] = y;
		update(x);
	}
}
inline void makeroot(int x)
{
	access(x);
	splay(x);
	rotdown(x);
}
inline void link()
{
	int x = r(),y = r();
	makeroot(x);
	e[x].fa = y;
}
inline void delink()
{
	int x = r(),y = r();
	makeroot(x);
	access(y);
	splay(y);
	e[x].fa = 0;
	e[y].son[0] = 0;
	update(y);
	link();
}
inline void add()
{
	int x = r(),y = r();
	makeroot(x);
	access(y);
	splay(y);
	ll k = r();
	adddown(y,k);
}
inline void mul()
{
	int x = r(),y = r();
	makeroot(x);
	access(y);
	splay(y);
	ll k = r();
	muldown(y,k);
}
inline void get()
{
	int x = r(),y = r();
	makeroot(x);
	access(y);
	splay(y);
	printf("%d\n",e[y].ans);
}
int main()
{
	int n = r(),q = r(),x,y;
	for (int a = 1 ; a <= n ; ++ a)
	{
		e[a].v = 1;
		e[a].siz = 1;
		e[a].laz2 = 1;
	}
	for (int a = 1 ; a < n ; ++ a) link();
	char w[1 << 2];
	while (q--)
	{
		scanf("%s",w);
		switch (w[0])
		{
			case '-':delink();break;
			case '+':add();break;
			case '*':mul();break;
			case '/':get();break;
		}
	}
	return 0;
}

 

搞其他東西去了惹qwq