演算法學習筆記(16):Link Cut Tree

Qerrj發表於2024-05-07

簡稱LCT(不是Li Chao Tree), 是一種非常強大的資料結構。

宣告

該部落格寫來很大部分目的是幫助自己理解, 筆者水平有限, 沒辦法完全原創, 有很多內容源自於OI-wiki,和網上部落格, 見諒。

功能

考慮一些問題:

  1. 樹上單點查, 樹上路徑修改, 這是樹上差分可以解決的。
  2. 那麼如果路徑查, 路徑修呢? 或者再加上子樹查, 子樹修改, 單點查呢? 樹鏈剖分可以解決。
  3. 那麼再加上可以任意刪邊連邊呢?(保證一直都是一棵樹), 這時候就需要LCT了。

所以LCT可以解決:

  1. 修改兩點間路徑權值。
  2. 查詢兩點間路徑權值和。
  3. 修改某點子樹權值。
  4. 查詢某點子樹權值和。
  5. 斷邊連邊。
  6. 還可以維護一些奇怪資訊, 這是因為Splay可以搞區間操作。

演算法

實鏈剖分

這是比較類似重鏈剖分的, 重鏈剖分的重鏈是根據子樹的大小來確定的, 而我們的實鏈是自己欽定的, 隨時可以變化。
所以原樹會被我們剖成若干個實鏈, 每條實鏈由一顆Splay維護。 並且這個Splay是根據深度作為BST的鍵值的。
所以, 就會有一些美好性質:

  1. 輔助樹由多棵 Splay 組成,每棵 Splay 維護原樹中的一條路徑,且中序遍歷這棵 Splay 得到的點序列,從前到後對應原樹「從上到下」的一條路徑。
  2. 原樹每個節點與輔助樹的 Splay 節點一一對應。
  3. 輔助樹的各棵 Splay 之間並不是獨立的。每棵 Splay 的根節點的父親節點本應是空,但在 LCT 中每棵 Splay 的根節點的父親節點指向原樹中 這條鏈 的父親節點(即鏈最頂端的點的父親節點)。這類父親連結與通常 Splay 的父親連結區別在於兒子認父親,而父親不認兒子,對應原樹的一條 虛邊。因此,每個連通塊恰好有一個點的父親節點為空。
  4. 由於輔助樹的以上性質,我們維護任何操作都不需要維護原樹,輔助樹可以在任何情況下拿出一個唯一的原樹,我們只需要維護輔助樹即可。

操作

考慮我們訪問樹上路徑, 這就需要兩個很重要的操作。
Access()

void access(int u) {
	for (int f = 0; u; f = u, u = fa[u]) 
		splay(u), ch[u][1] = f, maintain(u);
}

表示將節點 \(x\) 到根的路徑全部欽定為實邊, 這樣就可以得到 \(x\) 到根的路徑了。 因為只有實邊才會被Splay維護, 才可以進行操作。
考慮實現過程, 就是把 \(x\) 翻到所在Splay的根, 然後把他父親的右兒子指向它(這一步相當於就是把這條虛邊欽定為實邊), 一直到根, 就OK了。
Makeroot()

void makeroot(int u) {
	access(u);
	splay(u);
	swap(ls, rs);
	rev[u] ^= 1;
}

考慮access()操作只能訪問 \(x\) 到根的路徑, 但實際上樹上路徑不一定深度嚴格遞增, 解決辦法就是把某一個點欽定為根, 考慮一個性質, 如果把一個樹看成一個內向樹或者外向樹, 那麼換根就是把 \(x\) 到原來的根的路徑上的邊全部反向, 所以我們用Splay的區間翻轉, 就可以實現換根。
實現過程: 訪問 \(x\) 到根節點的路徑, 也就是 access(x), 然後將這條路徑的Splay的節點 \(x\) 翻到根, 然後打翻轉標記就行了。

其他操作就很簡單啦, 建議自行yy以加強對這兩個核心操作的理解。

模板題

P1501 [國家集訓隊] Tree II

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10;
const int mod = 51061;
int n, q;
char op;
struct Splay{
	int ch[N][2], fa[N], siz[N], val[N], sum[N], rev[N], add[N], mul[N];
	#define ls ch[u][0]
	#define rs ch[u][1]
	void clear(int u) {
		ls = rs = fa[u] = siz[u] = val[u] = sum[u] = rev[u] = add[u] = 0;
		mul[u] = 1;
	}
	int get(int u) { return (ch[fa[u]][1] == u); }
	int isroot(int u) {
		clear(0); 
		return ch[fa[u]][0] != u && ch[fa[u]][1] != u;
	} 
	void maintain(int u) {
		clear(0);
		siz[u] = (siz[ls] + 1 + siz[rs]) % mod;
		sum[u] = (sum[ls] + val[u] + sum[rs]) % mod;
	}
	void pushdown(int u) {
		clear(0);
		if (mul[u] != 1) {
			if (ls)
				mul[ls] = (mul[u] * mul[ls]) % mod,
				val[ls] = (mul[u] * val[ls]) % mod,
				sum[ls] = (mul[u] * sum[ls]) % mod,
				add[ls] = (mul[u] * add[ls]) % mod;
			if (rs) 
				mul[rs] = (mul[u] * mul[rs]) % mod,
				val[rs] = (mul[u] * val[rs]) % mod,
				sum[rs] = (mul[u] * sum[rs]) % mod,
				add[rs] = (mul[u] * add[rs]) % mod;
			mul[u] = 1;
		}
		if (add[u]) {
			if (ls) 
				add[ls] = (add[u] + add[ls]) % mod,
				val[ls] = (add[u] + val[ls]) % mod,
				sum[ls] = (add[u] * siz[ls] % mod + sum[ls]) % mod;
			if (rs)
				add[rs] = (add[u] + add[rs]) % mod,
				val[rs] = (add[u] + val[rs]) % mod,
				sum[rs] = (add[u] * siz[rs] % mod + sum[rs]) % mod;
			add[u] = 0;
		}
		if (rev[u]) {
			if (ls) rev[ls] ^= 1, swap(ch[ls][0], ch[ls][1]);
			if (rs) rev[rs] ^= 1, swap(ch[rs][0], ch[rs][1]);
			rev[u] = 0;
		}
	}
	void update(int u) {
		if (!isroot(u)) update(fa[u]);
		pushdown(u);
	}
	void rotate(int u) {
		int f = fa[u], gf = fa[f], chk = get(u);
		fa[u] = gf;
		if (!isroot(f)) ch[gf][f == ch[gf][1]] = u;
		ch[f][chk] = ch[u][chk ^ 1];
		if (ch[u][chk ^ 1]) fa[ch[u][chk ^ 1]] = f;
		ch[u][chk ^ 1] = f;
		fa[f] = u;
		maintain(f); 
		maintain(u);  
		maintain(gf);
	}
	void splay(int u) {
		update(u);
		for (int f = fa[u]; f = fa[u], !isroot(u); rotate(u)) {
			if (!isroot(f)) rotate(get(u) == get(f) ? f : u);
		}
	}
	void access(int u) {
		for (int f = 0; u; f = u, u = fa[u]) 
			splay(u), ch[u][1] = f, maintain(u);
	}
	void makeroot(int u) {
		access(u);
		splay(u);
		swap(ls, rs);
		rev[u] ^= 1;
	}
	int find(int u) {
		access(u); 
		splay(u);
		while (ls) u = ls;
		splay(u);
		return u;
	}
	void add_edge(int u, int v) {
		if (find(u) != find(v)) 
			makeroot(u), fa[u] = v; 
	}
	void modify_add(int u, int v, int z) {
		makeroot(u); access(v); splay(v);
		val[v] = (val[v] + z) % mod;
		sum[v] = (sum[v] + z * siz[v] % mod) % mod;
		add[v] = (add[v] + z) % mod;
	}
	void modify_mul(int u, int v, int z) {
		makeroot(u); access(v); splay(v);
		val[v] = val[v] * z % mod;
		sum[v] = sum[v] * z % mod;
		mul[v] = mul[v] * z % mod;
	}
	void cut(int u, int v) { 
		makeroot(u); access(v); splay(v); 
		if (ch[v][0] == u && !rs)
			ch[v][0] = fa[u] = 0; 
	}
	void link(int u, int v) { 
		makeroot(u); splay(u); 
		if (find(u) != find(v)) fa[u] = v; 
	}
	int query(int u, int v) { 
		makeroot(u); access(v); splay(v); 
		return sum[v]; 
	}
}T; 
signed main() {
	scanf("%lld%lld", &n, &q);
	for (int i = 1; i <= n; i++) 
		T.val[i] = 1, T.maintain(i);
	for (int i = 1, u, v; i < n; i++) 
		scanf("%lld%lld", &u, &v), T.add_edge(u, v);
	for (int i = 1, op, u, v, z; i <= q; i++) {
		scanf(" %c%lld%lld", &op, &u, &v);
		switch(op) {
			case '+': scanf("%lld", &z); T.modify_add(u, v, z); break;
			case '-': T.cut(u, v); scanf("%lld%lld", &u, &v); T.link(u, v); break;
			case '*': scanf("%lld", &z); T.modify_mul(u, v, z); break;
			case '/': printf("%lld\n", T.query(u, v)); break; 
		}
	}
	return 0;
}

相關文章