【图论】【数据结构】树的直径、树的中心、树的重心
树的直径
树上 2 个点距离最远的点形成的简单路径。
性质:
- 如果所有边权都为正数,那么所有直径的重点都是一个点,那个点就是树的中心。
- 树上任意点 \(x\) 距离其最远的点一定是树的直径的端点。
- 树的直径的端点一定是度数为 1 的点。
SPOJ PT07Z, Longest path in a tree
两遍 dfs 求树的直径(中心)
#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
vector<int> e[N];
int n;
int dep[N];
void dfs(int u, int fa) {
for (int to : e[u]) {
if (to == fa) continue;
dep[to] = dep[u] + 1;
dfs(to, u);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1, 0);
int maxver = -1, maxx = -0x3f3f3f3f;
for (int i = 1; i <= n; i++) {
if (maxx < dep[i]) {
maxx = dep[i];
maxver = i;
}
}
memset(dep, 0, sizeof(dep));
dfs(maxver, 0);
cout << *max_element(dep + 1, dep + n + 1) << '\n';
return 0;
}
树性 dp 求树的直径(中心):·求出每个点往下走的最长链和次长链,答案就是所有的点的最长链和次长链的长度之和的最大值。
#include <bits/stdc++.h>
using namespace std;
const int N = 10010, M = 20010;
struct edge {
int to, next;
} e[M];
int head[N], idx = 1;
void add(int u, int v) {
idx++, e[idx].to = v, e[idx].next = head[u], head[u] = idx;
}
int fzmax[N], fcmax[N];
void dfs(int u, int fa) {
for (int i = head[u]; i; i = e[i].next) {
int to = e[i].to;
if (to == fa) continue;
dfs(to, u);
int d = fzmax[to] + 1;
if (d > fzmax[u]) {
fcmax[u] = fzmax[u];
fzmax[u] = d;
}
else if (d > fcmax[u]) fcmax[u] = d;
}
}
int n;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
memset(fzmax, 0, sizeof(fzmax));
memset(fcmax, 0, sizeof(fcmax));
dfs(1, 0);
int ans = 0;
for (int i = 1; i <= n; i++) ans = max(ans, fzmax[i] + fcmax[i]);
cout << ans << '\n';
return 0;
}
例题
CF911F Tree Destruction
先标记树的直径,然后将除了直径之外的点删除,将它们到直径两端的最大值累加到答案中。最后将直径一点点删掉就可以了。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N = 200010;
const i64 INF = 0x3f3f3f3f;
int n;
vector<int> e[N];
int dep1[N], dep2[N];
int f[N];
bool path[N];
int out[N];
void dfs(int u, int fa, int *dep) {
f[u] = fa;
for (int to : e[u]) {
if (to == fa) continue;
dep[to] = dep[u] + 1;
dfs(to, u, dep);
}
}
i64 res;
vector<pair<int, pair<int, int> > > ans;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
out[u]++, out[v]++;
}
dfs(1, 0, dep1);
int maxx = -INF, root = -1;
for (int i = 1; i <= n; i++) {
if (maxx < dep1[i]) {
maxx = dep1[i];
root = i;
}
}
memset(dep1, 0, sizeof(dep1));
dfs(root, 0, dep1);
int maxver = -1;
maxx = -INF;
for (int i = 1; i <= n; i++) {
if (maxx < dep1[i]) {
maxx = dep1[i];
maxver = i;
}
}
int v = maxver;
while (v != f[root]) {
path[v] = true;
v = f[v];
}
dfs(maxver, 0, dep2);
queue<int> q;
for (int i = 1; i <= n; i++) {
if (path[i]) continue;
if (out[i] == 1) {
// cout << i << endl;
q.push(i);
}
}
while (q.size()) {
int t = q.front();
q.pop();
if (path[t]) continue;
for (int to : e[t]) {
if (path[to]) continue;
out[to]--;
if (out[to] == 1) q.push(to);
}
if (dep1[t] < dep2[t]) {
res += dep2[t];
ans.push_back({maxver, {t, t}});
}
else {
res += dep1[t];
ans.push_back({root, {t, t}});
}
}
v = root;
while (v != maxver) {
res += dep2[v];
ans.push_back({maxver, {v, v}});
v = f[v];
}
cout << res << '\n';
for (auto x : ans) cout << x.first << ' ' << x.second.first << ' ' << x.second.second << '\n';
return 0;
}
树的重心
树的重心
定义:对于一棵树形结构,若节点 \(x\) 为根节点时,其最大子树最小,则 \(x\) 称为树的重心。
性质:
- 树上结点到重心 \(x\) 的距离之和是最小的。
- 树的重心至多两个且一定相邻。
- 若树删除(增加) \(1\) 个叶子节点,则重心至多偏移 \(1\) 位。
- 若两颗树的重心分别为 \(x, y\) 那么任意在两棵树之间连边新的重心一定在 \(x\) 到 \(y\) 的路径上。
- 树的重心为根节点时,其最大的子树一定不超过 \(\frac{n}{2}\),其中 \(n\) 为重点总数。
代码
void get_root(int u, int fa) {
sz[u] = 1;
int s = 0;
for (int i = head[u]; i; i = e[i].next) {
int to = e[i].to;
if (to == fa) continue;
get_root(to, u);
s = max(s, sz[to]);
sz[u] += sz[to];
}
s = max(s, sum - sz[u]);
if (s <= sum / 2) {
root = u;
}
}