dsu,對於無修改子樹信息查詢,而且操做支持undo的問題ios
暴力dfs,對於每一個節點,對全部輕兒子dfs下去,而後再消除輕兒子的影響c++
dfs重兒子,而後dfs暴力恢復輕兒子們的影響,再把當前節點影響算進去算法
就有了整棵子樹的信息了,時間複雜度O(nlogn)ide
經典例題:http://codeforces.com/contest/600/problem/Espa
1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 typedef long long ll; 6 7 const int N = 1e5 + 5; 8 9 int n, c[N]; 10 11 int cnt[N], maxCnt; 12 13 int siz[N], son[N]; 14 15 vector <int> e[N]; 16 17 ll ans[N], sum[N]; 18 19 void dfs1(int u, int fr) { 20 siz[u] = 1; 21 for (int v : e[u]) { 22 if (v == fr) continue; 23 dfs1(v, u); 24 siz[u] += siz[v]; 25 if (siz[v] > siz[son[u]]) son[u] = v; 26 } 27 } 28 29 void update(int x, int y) { 30 sum[cnt[x]] -= x; 31 cnt[x] += y; 32 sum[cnt[x]] += x; 33 if (cnt[x] > maxCnt) maxCnt = cnt[x]; 34 if (sum[maxCnt] == 0) maxCnt --; 35 } 36 37 void dfs3(int u, int fr, int val) { 38 update(c[u], val); 39 for (int v : e[u]) { 40 if (v == fr) continue; 41 dfs3(v, u, val); 42 } 43 } 44 45 void dfs2(int u, int fr) { 46 for (int v : e[u]) { 47 if (v == fr || v == son[u]) continue; 48 dfs2(v, u), dfs3(v, u, -1); 49 } 50 if (son[u]) dfs2(son[u], u); 51 for (int v : e[u]) { 52 if (v == fr || v == son[u]) continue; 53 dfs3(v, u, 1); 54 } 55 update(c[u], 1); 56 ans[u] = sum[maxCnt]; 57 } 58 59 int main() { 60 ios::sync_with_stdio(false); 61 cin >> n; 62 for (int i = 1; i <= n; i ++) 63 cin >> c[i]; 64 for (int u, v, i = 1; i < n; i ++) { 65 cin >> u >> v; 66 e[u].push_back(v); 67 e[v].push_back(u); 68 } 69 dfs1(1, 1), dfs2(1, 1); 70 for (int i = 1; i <= n; i ++) 71 cout << ans[i] << ' '; 72 cout << endl; 73 return 0; 74 }
長鏈剖分,選擇深度大的兒子做爲重兒子code
O(1)繼承重兒子信息,而後按深度合併輕兒子信息blog
由於每一個節點被做爲輕鏈節點只會被合併一次,因此O(n)繼承
例題:http://codeforces.com/problemset/problem/1009/Fci
1 /* 長鏈剖分,選擇深度最大的兒子做爲重兒子,用於合併以深度爲下標的信息 2 * 像 dsu 同樣,直接繼承重兒子信息,而後按深度暴力合併其餘兒子信息 3 * 時間複雜度考慮每一個節點做爲輕兒子裏的節點被合併只會有一次,因此 O(n) 4 * 另外一種用法,能夠 O(nlogn) 預處理後,O(1) 找到 k 級祖先 5 */ 6 int n; 7 int len[N], son[N], ans[N]; 8 vector <int> e[N]; 9 int tmp[N], *ptr, *f[N]; 10 void dfs(int u, int fr) { 11 for (int v : e[u]) { 12 if (v == fr) continue; 13 dfs(v, u); 14 if (len[v] > len[son[u]]) son[u] = v; 15 } 16 len[u] = len[son[u]] + 1; 17 } 18 void dp(int u, int fr) { 19 f[u][0] = 1; 20 if (son[u]) { 21 f[son[u]] = f[u] + 1; 22 dp(son[u], u); 23 ans[u] = ans[son[u]] + 1; 24 } 25 for (int v : e[u]) { 26 if (v == son[u] || v == fr) continue; 27 f[v] = ptr, ptr += len[v]; 28 dp(v, u); 29 for (int j = 0; j < len[v]; j ++) { 30 f[u][j + 1] += f[v][j]; 31 if ((f[u][j + 1] > f[u][ans[u]]) || (f[u][j + 1] == f[u][ans[u]] && j + 1 < ans[u])) 32 ans[u] = j + 1; 33 } 34 } 35 if (f[u][0] >= f[u][ans[u]]) ans[u] = 0; 36 } 37 int main() { 38 in(n); 39 for (int u, v, i = 1; i < n; i ++) { 40 in(u), in(v); 41 e[u].push_back(v); 42 e[v].push_back(u); 43 } 44 dfs(1, 1); 45 f[1] = ptr = tmp, ptr += len[1]; 46 dp(1, 1); 47 for (int i = 1; i <= n; i ++) 48 printf("%d\n", ans[i]); 49 return 0; 50 }
區分幾種算法(dsu,樹鏈剖分,樹分治)用途:get
樹鏈剖分分爲重鏈剖分和長鏈剖分
重鏈剖分應用比較多也比較常見再也不贅述
固然dsu雖然也是一種應用但仍是拿出來提一下把
下面的樹分治僅僅針對點分治
dsu,長鏈剖分,點分治
三種都是無修改的樹上信息查詢算法
dsu應用限制:
只能統計子樹中全部點的信息,而且操做必須支持刪除
因此沒法維護鏈的信息
長鏈剖分應用限制:
由於通常用於基於深度的信息合併
因此沒法維護子樹所有信息,只能維護深度相關信息
因此對於有邊權的樹通常都沒有辦法
樹分治應用限制:
多用來樹上路徑的統計計數
缺點是沒法像上述兩種算法O(1)繼承某個兒子的信息
因此可維護的信息種類相對有限