Tree

基礎樹論

INDEX

Basics

Sparse Table

Binary Indexed Tree

Segment Tree

簡報自己讀不懂很正常他偏抽象

Basics

基本概念

定義
=每個節點中間洽有一簡單路徑\\
有\ N\ 個節點和\ N - 1\ 條邊
=圖為聯通且沒有環
人工造林
電腦科學裡面的樹
葉子
子節點
父節點
祖先
子樹
深度
1
2
3

Traversal

存圖&遍歷

int n;
vector<vector<int>> adj(n + 1);
for (int i = 0; i < n - 1; i++) {
    int u, v;
    cin >> u >> v;
    adj[u].push_back(v);
    adj[v].push_back(u); // 無向邊要建雙向
}
存圖
void dfs(int u, int p) {
    // process node u
    for (int v : adj[u]) {
        if (v == p) continue; // 防止往回走
        dfs(v, u);
    }
}

// main
dfs(root, -1); // 通常根節點是 1 可以直接 dfs(1, -1);
遍歷
\text{DFS}
\text{lambda}
auto dfs = [&](auto self, int u, int p) -> void {
    // process node u
    for (int v : adj[u]) {
        if (v == p) continue;
        self(self, v, u);
    }
}; // 記得加分號

// main
dfs(dfs, root, -1);
vector<int> dist(n + 1, -1);
queue<int> q;
dist[start] = 0;
q.push(start);
while (!q.empty()) {
    int u = q.front(); q.pop();
    for (int v : adj[u]) {
        if (dist[v] == -1) {
            dist[v] = dist[u] + 1;
            q.push(v);
        }
    }
}
遍歷
\text{BFS}

Subtree

子樹大小

題目

給定每個人的上司,計算出每個人的下屬有幾個
\text{boss}
n
-
1
1
2
1
3
2
4
3
5
5
2
2
1
1
1
2
3
4
5
void dfs(int u, int p) {
    cnt[u] = 1;
    for (int v : adj[u]) {
        if (v == p) continue;
        dfs(v, u); // 要先處理完子節點再更新狀態
        cnt[u] += cnt[v];
    }
}
記錄\ cnt[u]\ 為節點\ u\ 的子樹大小

Diameter

樹直徑

題目

找出樹直徑
\text{edges}
1-2
1-3
3-4
3-5
1
2
3
4
5
兩次\ \text{DFS}
1
2
3
4
5
1
2
3
4
5
1
2
3
5
1
3
2
5
1
5
4
int s = 1, dia = 0;
vector<int> depth(n + 1);
auto dfs = [&](auto self, int u, int p) -> void {
	if (depth[u] > dia) {
		dia = depth[u];
		s = u;
	}

	for (int v : adj[u]) if (v != p) {
		depth[v] = depth[u] + 1;
		self(self, v, u);
	}
};

dfs(dfs, 1, -1);
depth[s] = 0;
dfs(dfs, s, -1);
s\ 是當前最遠點,depth\ 為深度
樹\ \text{DP}
3
7
1
2
4
6
5
auto dfs = [&](auto self, int u, int p) -> int {
	int mx = 0, mx2 = 0;
	for (int v : adj[u]) if (v != p) {
		int h = self(self, v, u) + 1;
			
		if (h > mx) {
			mx2 = mx;
			mx = h;
		} else if (h > mx2) {
			mx2 = h;
		}
	}
	dia = max(dia, mx + mx2);
 
	return mx;
};
 
dfs(dfs, 1, -1);
mx\ 是最大路徑,mx2\ 為次大路徑

All longest paths

全源最長路

題目

找出對於每個節點最遠點的距離
\text{edges}
1-2
1-3
3-4
3-5
1
2
3
4
5
\text{dist}
n
2
1
3
2
2
3
3
4
3
5
分為往上走或往下種兩種情況
1
2
3
4
5
auto dfs = [&](auto self, int u, int p) -> void {
	for (int v : adj[u]) if (v != p) {
		self(self, v, u);
		if (maxDist[v] + 1 > maxDist[u]) {
			secMax[u] = maxDist[u];
			maxDist[u] = maxDist[v] + 1;
			path[u] = v;
		} else if (maxDist[v] + 1 > secMax[u]) {
			secMax[u] = maxDist[v] + 1;
		}
	}
};
計算往子節點走的最大路徑與次大路徑
maxDist[u]\ 為節點\ u\ 的最長路徑,此時是往子節點\ path[u]
auto dfs2 = [&](auto self, int u, int p) -> void {
	for (int v : adj[u]) if (v != p) {

		if (path[u] == v) { // maxDist[u] is the path toward v
			if (secMax[u] + 1 > maxDist[v]) {
				secMax[v] = maxDist[v];
				maxDist[v] = secMax[u] + 1;
				path[v] = u;
			} else if (secMax[u] + 1 > secMax[v]) {
				secMax[v] = secMax[u] + 1;
			}
		} else { // maxDist[u] + 1 >= maxDist[v] in this case
				 // cause if not, then maxDist[u] should calculated as maxDist[v] + 1 in first dfs
			secMax[v] = maxDist[v];
			maxDist[v] = maxDist[u] + 1;
			path[v] = u;
		}
        
		self(self, v, u);
	}
};
更新往父節點走的情況
對於每個子節點都嘗試更新往上走的最大路徑

Rerooting DP

換根 DP

題目

對於每一個節點,輸出他到其他所有點的距離和
\text{edges}
1-2
1-3
3-4
3-5
1
2
3
4
5
n
\text{dist sum}
1
6=1+1+2+2
2
9=1 + 2 + 3 + 3
3
5= 1+1+1+2
4
8=1+2+2+3
5
8=1+2+2+3
+1
-1
1
2
3
4
5
1
1
2
2
6
\text{dist}
n
0
1
1
2
1
3
2
4
2
5
1
2
3
4
5
1
5
1
1
2
\text{dist}
n
1
1
2
2
0
3
1
4
1
5
\Rightarrow dp[v] = dp[u] + (n - sz[v]) - sz[v]
1
2
3
4
5
1
5
1
1
2
\text{dist}
n
1
1
2
2
0
3
1
4
1
5
+1
-1
令\ dp[u]\ 為節點\ u\ 到所有節點的距離和\\ sz[u]\ 為節點\ u\ 的子樹大小
往上的節點數(非子樹)
auto dfs = [&](auto self, int u, int p, int dist) -> void {
	ans[1] += dist;
	sz[u] = 1; // 計算子樹大小
		
	for (int v : adj[u]) if (v != p) {
		self(self, v, u, dist + 1);
		sz[u] += sz[v];
	}
};
計算子樹大小和根節點為\ 1\ 時的答案
換根\ \text{DP}\rightarrow dp[v] = dp[u] + (n - sz[v]) - sz[v]
auto dfs2 = [&](auto self, int u, int p) -> void {
	for (int v : adj[u]) if (v != p) {
		ans[v] = ans[u] - sz[v] + (n - sz[v]);
		self(self, v, u);
	}
};

Binary Lifting

倍增法

題目

定義\ succ(x, k)\ 為節點\ x\ 往上\ k\ 的節點
succ(x, k) = \begin{cases} succ(x, 1)& k=1\\ succ(succ(x, k / 2), k / 2)& k>1\\ \end{cases}
給定每個人的從屬關係和\ Q\ 個詢問:x\ 上面\ k\ 層的的上司是誰?
1
4
2
1
5
往上的路徑一定會形成一條鏈
7
6
9
1
4
2
1
5
7
6
9
succ(9, 5) = succ(succ(9, 4), 0)=succ(4, 1)=2
x
9
7
6
5
4
2
1
succ(x, 1)
7
6
5
4
2
1
-
succ(x, 2)
6
5
4
2
1
-
-
succ(x, 4)
4
2
1
-
-
-
-
定義\ succ[k][u]\ 為節點\ u\ 往上走\ 2^k\ 的點
\Rightarrow succ[k][u] = succ[k - 1][succ[k - 1][u]]
vector<vector<int>> succ(__lg(n) + 1, vector<int>(n + 1));
vector<int> depth(n + 1);
auto dfs = [&](auto self, int u) -> void {
    for (int v : adj[u]) {
        succ[0][v] = u;
        depth[v] = depth[u] + 1;

        for (int i = 1; i <= __lg(depth[v]); i++) {
            succ[i][v] = succ[i - 1][succ[i - 1][v]];
        }

        self(self, v);
    }
};
O(\log n)\ 預處理
for (int i = lg(k); i >= 0; i--) {
    if (k & (1 << i)) {
        x = succ[i][x];
    }
}
O(\log n)\ 查詢

LCA

最低共通祖先

題目

給定每個人的從屬關係和\ Q\ 個詢問:距離\ a\ 和\ b\ 最近的共通祖先為何
3
7
1
2
4
6
5
8
倍增法
3
7
1
2
4
6
5
8
倍增法
3
7
1
2
4
6
5
8
auto lca = [&](int x, int y) -> int {
    if (depth[x] < depth[y]) swap(x, y);
    while (depth[x] > depth[y]) {
        x = succ[lg(depth[x] - depth[y])][x];
    }
    if (x == y) return x; // 如果定位到同深度時已經找到 要先返回
    for (int i = lg(depth[x]); i >= 0; i--) {
        if (succ[i][x] != succ[i][y]) {
            x = succ[i][x], y = succ[i][y];
        }
    }
    return succ[0][x];
};
depth\ 為深度,succ\ 為倍增表
歐拉迴路
3
7
1
2
4
6
5
8
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
node\ id
depth
1
1
2
2
5
3
2
2
6
3
8
4
6
3
2
2
1
1
3
2
1
1
4
2
7
3
4
2
1
1
歐拉迴路
3
7
1
2
4
6
5
8
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
node\ id
depth
1
1
2
2
5
3
2
2
6
3
8
4
6
3
2
2
1
1
3
2
1
1
4
2
7
3
4
2
1
1
vector<int> timer(2 * n + 1), id(n + 1), depth(n + 1);
auto dfs = [&](auto self, int u) -> void {
	id[u] = cnt;
	timer[cnt++] = u;
	for (int v : adj[u]) {
		depth[v] = depth[u] + 1;
		self(self, v);
		timer[cnt++] = u;
	}
};
歐拉迴路
vector<vector<int>> st(lg(cnt) + 1, vector<int>(cnt + 1));
for (int i = 1; i <= cnt; i++) st[0][i] = i;
for (int i = 1; i <= lg(cnt); i++) {
	int len = 1 << (i - 1);
	for (int j = 1; j + len <= cnt; j++) {
		int a = st[i - 1][j], b = st[i - 1][j + len];
		if (depth[timer[a]] <= depth[timer[b]]) st[i][j] = a;
		else st[i][j] = b;
	}
}
O(n \log n)\ 建最小值稀疏表
int l = id[a], r = id[b];
if (l > r) swap(l, r);
int k = lg(r - l + 1);
a = st[k][l], b = st[k][r - (1 << k) + 1];
if (depth[timer[a]] <= depth[timer[b]]) {
	cout << timer[a] << '\n';
} else cout << timer[b] << '\n';
O(1)\ 查詢

Flatten

樹壓平

題目

給定一顆樹,有兩種操作:將節點\ s\ 的值修改成 \ x\ /\ 查詢節點\ s\ 的子樹值之和
1
2
3
4
5
start
end
node\ id
0
5
1
2
2
5
3
4
4
5
1
2
3
4
5
auto dfs = [&](auto self, int u, int p) -> void {
	st[u] = ++cnt;
	for (int v : adj[u]) if (v != p) {
		self(self, v, u);
	}
	en[u] = cnt;
};
st[u]\ 為節點\ u\ 起始時間,en[u]\ 為結束時間
BIT tree(n);
for (int i = 1; i <= n; i++) {
	tree.modify(st[i], v[i]);
}
套資結&初始化
if (o == 1) { // modify
	int x;
	cin >> x;
	tree.modify(st[s], x);
} else {
	cout << tree.query(en[s]) - tree.query(st[s] - 1) << '\n';
}
改值&查詢

掰掰

基礎樹論

By keaucucal

基礎樹論

  • 33