圖論-樹上啟發式合並(DSU On Tree)
阿新 • • 發佈:2018-08-27
pac code include clu 修改 += disjoint namespace std 解決了(假設合並信息是 \(O(1)\) 的)。
Disjoint Set Union On Tree ,似乎是來自 Codeforces 的一種新操作,似乎被叫做“樹上啟發式合並”。
在不帶修改的有根樹子樹信息統計問題中,似乎樹上莫隊和這個 DSU On Tree 是兩類常規操作。
先對樹按輕重鏈剖分。對於每個節點,先計算輕兒子為根的子樹信息,每次計算後消除影響,再去計算其他輕兒子。然後計算重兒子為根的子樹信息,不消除影響,並把輕兒子們為根的子樹信息加入,再合並這個節點本身的信息。由於一個大小 \(x\) 的子樹被消除影響後,都把信息合並到了一個大於等於 \(2x+1\) 的子樹,如此遞歸下去,它顯然至多被消除影響 \(log\ n\) 次。利用輕重鏈剖分的思想,就把這個問題 \(O(n)\)
一道求子樹內眾數(記在 ans 裏)的題的代碼:
#include <stdio.h> #include <vector> using namespace std; const int _N = 160000; vector<int> G[_N]; int hvs[_N], siz[_N], cnt[_N], A[_N], ans[_N], mx; void connect(int p, int dad) { siz[p] = 1; for (int i = G[p].size() - 1; i >= 0; --i) { int v = G[p][i]; if (v == dad) continue; connect(v, p); siz[p] += siz[v]; if (!hvs[p] || siz[hvs[p]] < siz[v]) hvs[p] = v; } return; } void clear(int p, int dad) { --cnt[A[p]]; for (int i = G[p].size() - 1; i >= 0; --i) { int v = G[p][i]; if (v == dad) continue; clear(v, p); } return; } void insert(int p, int dad) { mx = max(mx, ++cnt[A[p]]); for (int i = G[p].size() - 1; i >= 0; --i) { int v = G[p][i]; if (v == dad) continue; insert(v, p); } return; } void dfs(int p, int dad) { for (int i = G[p].size() - 1; i >= 0; --i) { int v = G[p][i]; if (v == dad || v == hvs[p]) continue; dfs(v, p); clear(v, p); mx = 0; } if (hvs[p]) dfs(hvs[p], p); for (int i = G[p].size() - 1; i >= 0; --i) { int v = G[p][i]; if (v == dad || v == hvs[p]) continue; insert(v, p); } ans[p] = mx = max(mx, ++cnt[A[p]]); return; } int main() { int N; scanf("%d", &N); for (int i = 1; i <= N; ++i) scanf("%d", &A[i]); for (int a, b, i = 1 ; i < N; ++i) { scanf("%d%d", &a, &b); G[a].push_back(b), G[b].push_back(a); } connect(1, 0); dfs(1, 0); for (int i = 1; i <= N; ++i) printf("%d ", siz[i] - ans[i]); return 0; }
CF 上原博客 [Tutorial] Sack (dsu on tree)
模板題 CF600E 子樹眾數統計
圖論-樹上啟發式合並(DSU On Tree)