求一棵树任意子树的重心

(2022.11.21 重写)

定义

siz(x)\newcommand\siz{\operatorname{siz}}\newcommand\hson{\operatorname{hson}}\siz(x) 表示 xx 的子树大小(含 xx),hson(x)\hson(x) 表示 xx重儿子。将边 (x,hson(x))(x,\hson(x)) 称为一条重边,其余不满足该条件的边称为轻边;将相邻的重边两两相连形成的链称为重链。

命题

一棵以 rt\newcommand\rt{\text{rt}}\rt 为根的树,其重心 cc 满足以下条件:

  • cc 在以 rt\rt 为顶的重链上;(1)(1)
  • cc 的所有祖先(记其中一个为 xx),均满足 siz(hson(x))>siz(rt)2\siz(\hson(x))>\frac{\siz(rt)}{2}(2)(2)
  • siz(hson(c))siz(rt)2\siz(\hson(c))\leq \frac{\siz(rt)}{2}(3)(3)

证明

n=siz(rt)n=\siz(\rt)。考虑任意节点 xx。不难发现若 yhson(x)y\neq \hson(x),则 siz(y)<siz(x)2\siz(y)<\frac{\siz(x)}{2},又 siz(x)n\siz(x)\leq n,进而 siz(y)<n2\siz(y)<\frac{n}{2},则 nsiz(y)>n2n-\siz(y)>\frac{n}{2}。换句话说,以 yy 子树中任意节点为根,在其祖先方向上的所有节点数量已超过全树一半,不可能成为重心。考虑从 x=rtx=\rt 开始反复应用以上结论,(1)(1) 即得证。

对于 (2)(2)(3)(3),我们考虑归纳法。对于 x=rtx=\rt,结论显然成立;假如现在处于节点 xx,它位于 rt\rt 为顶的重链上,且其所有祖先均满足 (2)(2)。则有 nsiz(x)n2n-\siz(x)\leq\frac{n}{2}。若有 siz(hson(x))n2\siz(\hson(x))\leq\frac{n}{2},则其轻儿子的大小亦然。根据定义,它显然为整棵树的重心;否则由 (3)(3) 可以得到应向 hson(x)\hson(x) 探查,同时由 siz(hson(x))>n2\siz(\hson(x))>\frac{n}{2} 继续保证了 nsiz(hson(x))n2n-\siz(\hson(x))\leq\frac{n}{2}\square

应用

CF685B – Kay and Snowflake

本题要你求取一棵树以 xx 为根的子树的任意一个重心。我们将该树作轻重链剖分。对于每条重链,我们开设顺序存储数据结构保存其每一节点的重儿子大小。易知该序列是单减的。在上面二分找到首个满足条件的节点即可。或者直接从底向上依次求取每个子树的重心,则答案单调“上移”(深度不增),优化复杂度到线性。

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
#include <bits/stdc++.h> using namespace std; const int N = 327680; int n, x, y, q, head[N], e_cnt, lim; int f[N], h_son[N], centroid[N], father[N]; // f[i]是以i为根的子树的节点数;h_son[i]是i的重儿子。 int ans[N]; struct edge { int v, nxt; } e[N << 1]; void add_e (int x, int y) { e[++e_cnt].nxt = head[x], head[x] = e_cnt, e[e_cnt].v = y; } void dfs (int x, int fr) { f[x] = 1, father[x] = fr, centroid[x] = x; // 叶子节点的重心是它自己 for (int i = head[x]; i; i = e[i].nxt) { dfs (e[i].v, x); f[x] += f[e[i].v]; if (f[e[i].v] > f[h_son[x]]) // 更新重儿子信息 h_son[x] = e[i].v; } if (!h_son[x]) return; int cent = centroid[h_son[x]]; while (cent != x) { if (f[x] - f[cent] <= (f[x] >> 1) && f[h_son[cent]] <= (f[x] >> 1)) { // 判断是否能成为重心 centroid[x] = cent; break; } cent = father[cent]; } } int main () { /* CF685B / hwhw-树形DP(树的直径和重心) G - Kay and Snowflake 吴秋实 */ scanf ("%d%d", &n, &q); for (int i = 2; i <= n; ++i) { scanf ("%d", &y); add_e (y, i); } dfs (1, 0); for (int i = 1; i <= q; ++i) { scanf ("%d", &x); printf ("%d\n", centroid[x]); } return 0; }
  • 2021年7月23日