树的重心⚓︎
在一棵树中,选择某个节点并删除后,树会分为若干棵子树。统计这些子树的节点数,并记录其中的最大值。遍历树上的所有节点,使得这个最大值最小的节点被称为树的重心。
树的重心有以下性质:
- 重心的唯一性:树的重心如果不唯一,则至多有两个,且这两个重心是相邻的
- 子树大小限制:以树的重心为根时,所有子树的大小都不超过整棵树节点数的一半
- 距离和最小性:树中所有点到某个点的距离和中,到重心的距离和是最小的。如果树有两个重心,则到这两个重心的距离和相等
- 重心在合并树中的位置:将两棵树通过一条边相连形成一棵新的树,那么新树的重心一定在连接原来两棵树的重心的路径上
- 重心的动态变化:在一棵树上添加或删除一个叶子节点时,树的重心最多只会移动一条边的距离
根据这些性质可以求解树的重心。
树的重心
以某个节点为根时,最大子树的节点数最少,那么这个节点是重心
C++
vector<int> find_centroid(const vector<vector<int>> &tree, int root = 0) {
int n = tree.size();
vector<int> size(n); // 子树节点数
int best = n; // 最小的最大子节点数
vector<int> centroids; // 重心节点, 最多两个
std::function<void(int, int)> dfs = [&](int x, int from) {
size[x] = 1;
int max_child = 0; // 最大子节点数
for (int y : tree[x]) {
if (y != from) {
dfs(y, x);
size[x] += size[y];
max_child = max(max_child, size[y]);
}
}
max_child = max(max_child, n - size[x]); // x的补树节点数
if (max_child < best) {
best = max_child;
centroids = {x};
} else if (max_child == best) {
centroids.push_back(x);
}
};
dfs(root, -1);
return centroids;
}
以某个节点为根时,每颗子树的节点数不超过总节点数的一半,那么这个节点是重心
C++
vector<int> find_centroid(const vector<vector<int>> &tree, int root = 0) {
int n = tree.size();
vector<int> size(n); // 子树节点数
vector<int> max_subtree(n); // 最大子树节点数
vector<int> centroids; // 重心节点, 最多两个
std::function<void(int, int)> dfs = [&](int x, int from) {
size[x] = 1;
for (int y : tree[x]) {
if (y != from) {
dfs(y, x);
size[x] += size[y];
max_subtree[x] = max(max_subtree[x], size[y]);
}
}
max_subtree[x] = max(max_subtree[x], n - size[x]); // x的补树节点数
};
dfs(root, -1);
for (int i = 0; i < n; ++i) {
if (max_subtree[i] <= n / 2) { centroids.push_back(i); }
}
return centroids;
}
树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样
C++
vector<int> find_centroid(const vector<vector<int>> &tree, int root = 0) {
int n = tree.size();
vector<int> size(n); // 子树节点数
vector<int> dist(n); // 以节点i为根时,所有节点到i的距离和
vector<int> centroids; // 重心节点, 最多两个
// 计算以root为根时,所有节点到root的距离和
std::function<void(int, int)> dfs1 = [&](int x, int from) {
size[x] = 1;
for (int y : tree[x]) {
if (y != from) {
dfs1(y, x);
size[x] += size[y];
dist[x] += dist[y] + size[y]; // dist[y] + size[y]: y的子树节点到x的距离和
}
}
};
dfs1(root, -1);
// 换根dp, 计算以每个节点为根时,所有节点到该节点的距离和
std::function<void(int, int)> dfs2 = [&](int x, int from) {
for (int y : tree[x]) {
if (y != from) {
dist[y] = dist[x] - size[y] + (n - size[y]);
dfs2(y, x);
}
}
};
dfs2(root, -1);
int best = *min_element(dist.begin(), dist.end());
for (int i = 0; i < n; ++i) {
if (dist[i] == best) { centroids.push_back(i); }
}
return centroids;
}
Balancing Act
给定一棵有 n 个节点的树,求出它的重心节点编号以及删除该节点后,剩余子树中节点数最多的子树的节点数。
C++
// This file is written in C++98 standard.
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<vector<int> > tree;
vector<int> subtree_size; // 子树节点数
// DFS 计算每个节点子树大小,并找重心
void dfs(int x, int from, int &best, int ¢er) {
subtree_size[x] = 1;
int max_child = 0;
for (size_t i = 0; i < tree[x].size(); ++i) {
int y = tree[x][i];
if (y != from) {
dfs(y, x, best, center);
subtree_size[x] += subtree_size[y];
max_child = std::max(subtree_size[y], max_child);
}
}
max_child = std::max(max_child, n - subtree_size[x]);
if (max_child < best) {
best = max_child;
center = x;
} else if (max_child == best) {
center = std::min(x, center);
}
}
void solve() {
int n;
cin >> n;
tree = vector<vector<int> >(n + 1);
subtree_size = vector<int>(n + 1);
for (int i = 0; i < n - 1; ++i) {
int x, y;
cin >> x >> y;
tree[x].push_back(y);
tree[y].push_back(x);
}
int best = n; // 最小的最大子节点数
int center = -1; // 重心节点, 找到编号最小的
dfs(1, -1, best, center);
cout << center << " " << best << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while ((t--) != 0) { solve(); }
return 0;
}