定义

  1. 对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心。
  2. 这里以及下文中的“子树”都是指无根树的子树,即包括“向上”的那棵子树,并且不包括整棵树自身。

性质

  1. 以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
  2. 树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
  3. 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
  4. 在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

求法

  1. DFS 中计算每个子树的大小,记录“向下”的子树的最大大小,
  2. 利用总点数 - 当前子树(这里的子树指有根树的子树)的大小得到“向上”的子树的大小,
  3. 然后就可以依据定义找到重心了。

代码

  1. // 这份代码默认节点编号从 1 开始,即 i ∈ [1,n]
  2. int size[MAXN], // 这个节点的“大小”(所有子树上节点数 + 该节点)
  3. weight[MAXN], // 这个节点的“重量”
  4. centroid[2]; // 用于记录树的重心(存的是节点编号)
  5. void GetCentroid(int cur, int fa) { // cur 表示当前节点 (current)
  6. size[cur] = 1;
  7. weight[cur] = 0;
  8. for (int i = head[cur]; i != -1; i = e[i].nxt) {
  9. if (e[i].to != fa) { // e[i].to 表示这条有向边所通向的节点。
  10. GetCentroid(e[i].to, cur);
  11. size[cur] += size[e[i].to];
  12. weight[cur] = max(weight[cur], size[e[i].to]);
  13. }
  14. }
  15. weight[cur] = max(weight[cur], n - size[cur]);
  16. if (weight[cur] <= n / 2) { // 依照树的重心的定义统计
  17. centroid[centroid[0] != 0] = cur;
  18. }
  19. }

例题,D. Kay and Snowflake

  1. 题意:
  2. 给你n个点,以1为根,然后给你2-n的节点的父亲节点编号。Q个询问,问以询问的节点为根的重心编号。
  3. 性质:
  4. 1.树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和一样。
  5. 2.把两个树通过一条边相连得到一个新的树,那么新的树的重心在连接原来两个树的重心的路径上。
  6. 3.把一个树添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
  7. 本题使用的就是第2条定理。
  8. 如果深搜此树,在回溯的时候,遇到一个以u为节点的子树,
  9. 如果其u的临接点vu的重儿子(节点u的儿子中规模最大的子树的节点)。
  10. 那么两个部分相连之后的新的重心在u到以v为根的子树的重心这条链上。
  11. 性质:
  12. 一棵树的重心一定在它的根上或是在它的子树最大的子节点所在子树内,
  13. 且一定在该子树的重心或其上方
  14. (u的重心,一定在最大的儿子里面(重儿子))
  15. 因此我们可以从下往上找,重心默认为它自己,
  16. 若存在一个子节点的子树大小大于其一半,就将重心改为这个子节点的重心,
  17. 并不断向上跳,
  18. 直到满足重心(树的重心的定义:以这个点为根,那么其所有的子树的大小都不超过整个树的一半)。
  19. 因为每条边最多被跳一次,所以复杂度仍然为O(n),不仅好写,而且时间空间均十分优越
  1. Solve it for all subtrees.
  2. We can solve the problem for some subtree,
  3. after solving the problem for all of it's children.
  4. Let's choose the heaviest child.
  5. Note that the centroid of the child after a certain lifting goes into our centroid.
  6. Let's model the lifting.
  7. Thus we get the answers for all vertex and we need only output answers for queries.
  8. Complexity O(n + q)
  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 3e5 + 10;
  4. int h[N], e[N * 2], ne[N * 2], idx;
  5. int fa[N], mxs[N], sz[N];
  6. int ans[N];
  7. int n, q;
  8. void add(int a, int b){
  9. e[idx] = b, ne[idx] = h[a], h[a] = idx++;
  10. }
  11. void dfs(int u, int father){
  12. sz[u] = 1, ans[u] = u, mxs[u] = u;
  13. int mx = 0;
  14. for (int i = h[u]; i != -1; i = ne[i]){
  15. int j = e[i];
  16. if (j == father) continue;
  17. dfs(j, u);
  18. sz[u] += sz[j];
  19. if (mx < sz[j]){
  20. mx = sz[j];
  21. mxs[u] = j; // 更新重儿子
  22. }
  23. }
  24. // 一棵树的重心一定在它的根上或是在它的子树最大的子节点所在子树内
  25. // 且一定在该子树的重心或其上方
  26. ans[u] = ans[mxs[u]];
  27. //while (ans[u] != u && ((sz[u] - sz[ans[u]]) > sz[u] / 2)){
  28. // ans[u] = fa[ans[u]];
  29. //}
  30. while (ans[u] != u && sz[ans[u]] * 2 < sz[u]){
  31. ans[u] = fa[ans[u]];
  32. }
  33. }
  34. int main(){
  35. cin >> n >> q;
  36. memset(h, -1, sizeof h);
  37. for (int i = 2; i <= n; i++){
  38. int x;
  39. scanf("%d", &x);
  40. fa[i] = x;
  41. add(i, x); add(x, i);
  42. }
  43. dfs(1, 0);
  44. while (q--){
  45. int x;
  46. scanf("%d", &x);
  47. printf("%d\n", ans[x]);
  48. }
  49. return 0;
  50. }