一、题意
一棵树有n个结点(n<=1e6),每个结点的权重为其深度,根节点深度为0。给这棵树选一个根节点,使得它的权重和最小。
二、解析
即使没学过树的重心这个概念,凭直觉就能感受到,以“重心”为根节点的树的权重和应该是最小的。事实上也是如此。
树的重心:
定义:对于一棵树的每一个结点u,它们的最大子树的结点数记为dp[u]。所有的结点中,dp最小的那个结点就是重心。
性质:
- 重心到所有节点的距离和最小(边权为1)
- 两棵树合并,新的重心在两棵树重心的路径上
- 一棵树添加或删除一个节点,重心最多移动一条边的位置
- 重心的最大的子树的节点数最小
三、代码
#include <iostream> #include <vector> #include <cmath> using namespace std; const int maxn = 1e6 + 5, INF = 1 << 30; vector<int> G[maxn]; int n, cnt[maxn], dp[maxn], min_dp = INF; // dp[i]存放以i为根的树的最大子树的结点个数 int dfs(int u, int fa) { int res = 1; for(int v : G[u]) if(v != fa) { int tmp = dfs(v, u); res += tmp; dp[u] = max(dp[u], tmp); } dp[u] = max(dp[u], n - res); min_dp = min(min_dp, dp[u]); return res; } int solve(int u, int fa, int dep) { int res = dep; for(int v : G[u]) if(v != fa) { res += solve(v, u, dep + 1); } return res; } int main() { cin >> n; for(int i = 0; i < n - 1; i ++) { int u, v; cin >> u >> v; G[u].push_back(v); G[v].push_back(u); } dfs(1, 0); for(int i = 1; i <= n; i ++) if(dp[i] == min_dp) { cout << solve(i, 0, 0) << endl; break; } }
四、归纳
- 树的重心求法:
- 先以任意点为根求出每个结点子树的结点数目 cnt[maxn];
- 在计算cnt时顺便计算每个结点的最大子树结点数 dp[maxn];
- 最后遍历所有结点,dp[i] == min_dp 的就是重心(可以有多个)。
- 树重心模板:
vector<int> G[maxn]; int n, cnt[maxn], dp[maxn], min_dp = INF; // dp[i]存放以i为根的树的最大子树的结点个数 int dfs(int u, int fa) { int res = 1; for(int v : G[u]) if(v != fa) { int tmp = dfs(v, u); res += tmp; dp[u] = max(dp[u], tmp); // 维护dp } dp[u] = max(dp[u], n - res); // 不要漏了以“上面”的结点构成的子树 min_dp = min(min_dp, dp[u]); // 维护最小的dp,它对应的树根u就是重心 return res; } int main() { ...... dfs(1, 0); for(int i = 1; i <= n; i ++) if(dp[i] == min_dp) { ... // i就是重心 } }