树的重心
概念
在一棵
删去重心后得到的若干子树大小均不超过
相关性质
先约定记号:
:删去边 后,含 的连通分量大小(即以 为根时 的子树大小); :删去 后最大的连通分量大小; :所有节点到 的距离和。
显然,重心的定义就是
先指出一个树的基本事实:删去任意节点
性质一:三个等价定义
以下三个条件等价:
(重心定义); 在所有节点中最小; 在所有节点中最小(距离和最小)。
证明:
先证
:设 。对任意 ,删去 后 所在连通分量记为 , 。删去 后,含 的连通分量至少包含 与 ,大小 ,故 。由 的任意性, 最小。 :反证。若 ,设删去 后最大的连通分量为 ( ,从而 ), 是 中与 相邻的唯一点(由上面的树的事实)。删去 后,含 的连通分量大小恰为 , 内部其余各分量均 ,于是 ,与 最小矛盾。
再证
:若 ,则对 的每个邻点 都有 ,代入换根公式得 ,即 在 的邻域内取最小。沿任意路径 , 随 严格递减( 侧的分量被 侧的分量严格包含),所以 沿路径先不增、后不降(单峰),邻域最小即全局最小。故 最小。 :反证。若存在 的邻点 使 ,由换根公式 ,即 ,与 最小矛盾。
三个定义等价得证。这也说明:求重心既可以从"最大连通分量最小"出发,也可以从"距离和最小"出发,两种求法见下文。
性质二:重心至多两个;若有两个则相邻,且删去它们的连边后树被等分
证明:设
- 若
:对任意 ,删去 后 所在分量 满足 。删去 后,含 的分量大小 ,故 , 不是重心。此时重心唯一。 - 若
:删去 后恰有一个分量 大小为 (其余分量之和为 ,均小于 )。设 是 中与 相邻的唯一点。删去 后,含 的分量大小为 , 的各分量均 ,故 , 也是重心。而对其他节点 :若 不在 中,删去 后 侧大小 ;若 ,删去 后含 的分量至少包含 ( 仍与 相邻),大小 。所以其他节点都不是重心,重心恰为 两个,它们相邻,且删去边 后两个连通分量大小均为 。
性质三:添加或删除一个叶子,重心至多移动一条边
证明(以添加叶子为例,删除叶子对称):
- 原树只有唯一重心
。由性质二,此时必有 ,故删去 后任意分量 满足 。新叶子 挂在 内,新树中该分量变为 ,大小 ,不超过新树节点数的一半, 仍是新树的重心。 - 原树有两个重心
,删去边 后两分量等大(各 )。若新叶子 挂在 侧分量内:删去 后含 侧大小为 , 不再是重心;删去 后各分量仍 , 仍是重心(新树节点数为奇数,重心唯一)。挂在 侧时对称。
综上,新树的重心一定在旧树重心与新叶子的路径上,至多移动一条边。
性质四:两棵树用一条边相连,新树重心在两树重心连线的路径上
证明思路(归纳):设
性质五:有根树中,重心在根的重链上;重心是其重子节点子树重心的祖先
设以
证明:
- 若
:删去 后最大连通分量恰为 , 是重心。 - 若
:对任意不在 子树内的节点 ,删去 后含 的连通分量大小 , 不是重心,重心必在 的子树内。对 的子树递归应用同样的结论,重心最终落在"从根沿重链一路向下"的路径上。
因此,整棵树的重心就是从根沿重链下降到第一个满足"最大连通分量
求法一:从定义出发(DFS 统计子树大小)
任选一个根(如
由定义(性质一),重心就是
完整代码(输出重心编号与删去它后最大连通分量的大小):
#include <bits/stdc++.h>
using namespace std;
const int N = 100010, M = 2 * N;
int h[N], e[M], ne[M], idx;
int siz[N];
bool st[N];
int n, best = N, center;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int dfs(int u) { // 返回以 u 为根的子树大小
st[u] = true;
siz[u] = 1;
int mx = 0; // 删去 u 后最大的连通分量大小
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (st[v]) continue;
siz[u] += dfs(v);
mx = max(mx, siz[v]);
}
mx = max(mx, n - siz[u]); // "向上"的连通分量
if (mx < best) {
best = mx;
center = u;
}
return siz[u];
}
int main() {
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i < n; i++) {
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
dfs(1);
cout << center << ' ' << best << endl;
return 0;
}复杂度
从定义出发时也可以这样求树的重心:
假设树的节点总数为
核心代码如下:
int n; // 树中节点总数
int ans; // 树的重心
int f[N]; // f[u] 代表以 u 为根的子树的节点总数
bool sym = false;
void dfs(int u, int fa) {
f[u] = 1;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == fa) continue;
dfs(v, u);
f[u] += f[v];
}
if (f[u] * 2 >= n && !sym) {
ans = u;
sym = true;
}
}AcWing 846. 树的重心
题意:
求法二:从性质出发(换根 DP 求距离和最小)
由性质一,距离和最小的节点就是重心。先以
求出以每个节点为根时的距离和(
完整代码(多个重心时取编号最小):
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100010, M = 2 * N;
int h[N], e[M], ne[M], idx;
int siz[N];
LL dp[N], S[N];
int n;
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs1(int u, int fa) { // 求以 1 为根时各子树大小与子树内距离和
siz[u] = 1;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == fa) continue;
dfs1(v, u);
siz[u] += siz[v];
dp[u] += dp[v] + siz[v]; // v 子树内每个节点到 u 比到 v 远 1
}
}
void dfs2(int u, int fa) { // 换根
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == fa) continue;
S[v] = S[u] - siz[v] + (n - siz[v]);
dfs2(v, u);
}
}
int main() {
cin >> n;
memset(h, -1, sizeof h);
for (int i = 1; i < n; i++) {
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
}
dfs1(1, 0);
S[1] = dp[1];
dfs2(1, 0);
LL mini = (LL)4e18;
int center = 0;
for (int i = 1; i <= n; i++) {
if (S[i] < mini || (S[i] == mini && i < center)) {
mini = S[i];
center = i;
}
}
cout << center << ' ' << mini << endl;
return 0;
}复杂度
洛谷 P1395. 会议
题意:
补充:利用性质五还可以"沿重链下降"求重心——从根出发,若当前节点的某个子树大小
