【知识点】树的直径、中心、重心
前置知识
树的初步学习:树初步
直径
定义
树上任意两节点之间最长的简单路径即为树的直径。
若直径上有偶数个点,则这条直径有两个中点。否则只有一个中点。
性质
- 一棵树可以有多条直径,它们的长度相等。
- 若树上所有边边权均为正,则树的所有直径中点重合。
求法
两次DFS法
从任意节点(一般选1号节点)开始DFS,找到离该节点最远的节点v,则v一定是直径的一个端点。从v开始DFS,找到离v最远的节点u,则u和v之间的路径为直径。
若需要记录直径上的所有节点,可以在第二次DFS时,记录每个节点的前序节点。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
i64 n, u, v, c = 1; //c为DFS找到的端点
vector<i64>edge[100100];
i64 d[100100];
void dfs(i64 now, i64 fa) {
for (i64 nex : edge[now]) {
if (nex == fa)
continue;
d[nex] = d[now] + 1;
if (d[nex] > d[c])
c = nex;
dfs(nex, now);
}
return;
}
int main() {
cin >> n;
for (i64 i = 0; i < n - 1; i++) {
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(1, 0); //执行完后c为第一次DFS找到的端点
d[c] = 0;
dfs(c, 0); //执行完后c为第二次DFS找到的端点
cout << d[c];
return 0;
}
树形DP法(双数组)
相较于两次DFS,树形DP的优势是在有负权边的情况下,仍然能找到直径。(此时直径不一定是以叶子节点为端点)
先假设1为根。记录每个节点为根的子树中,离根最远的最长路径长度\(d_1\)和次长路径(与最长路径无公共边)长度\(d_2\),那么直径就是对于每个点,\(d_1+d_2\)的最大值。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
i64 n, u, v, d = 0;
vector<i64>edge[100100];
i64 d1[100100], d2[100100];
void dfs(i64 now, i64 fa) {
d1[now] = d2[now] = 0;
for (i64 nex : edge[now]) {
if (nex == fa)
continue;
dfs(nex, now);
i64 t = d1[nex] + 1;
if (t > d1[now])
d2[now] = d1[now], d1[now] = t;
else if (t > d2[now])
d2[now] = t;
}
d = max(d, d1[now] + d2[now]);
return;
}
int main() {
cin >> n;
for (i64 i = 0; i < n - 1; i++) {
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(1, 0);
cout << d;
return 0;
}
树形DP法(单数组)
在双数组的情况下,可以删除\(d_2\)数组,只利用\(d_1\)数组,在更新\(d_1\)之前就更新\(d\),防止最长路径和次长路径有公共边。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
i64 n, u, v, d = 0;
vector<i64>edge[100100];
i64 d1[100100];
void dfs(i64 now, i64 fa) {
d1[now] = 0;
for (i64 nex : edge[now]) {
if (nex == fa)
continue;
dfs(nex, now);
d = max(d, d1[now] + d1[nex] + 1);
d1[now] = max(d1[now], d1[nex] + 1);
}
return;
}
int main() {
cin >> n;
for (i64 i = 0; i < n - 1; i++) {
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(1, 0);
cout << d;
return 0;
}
中心
定义
在树中,如果节点\(x\)作为根节点时,从\(x\)出发的最长链最短,那么称\(x\)为这棵树的中心。
性质
- 树的中心不一定唯一,但最多有2个,且这两个中心是相邻的。
- 树的中心一定位于树的直径上。
- 树上所有点到其最远点的路径一定交会于树的中心。
- 当树的中心为根节点时,其到达直径端点的两条链分别为最长链和次长链。
- 当通过在两棵树间连一条边以合并为一棵树时,连接两棵树的中心可以使新树的直径最小。
- 树的中心到其他任意节点的距离不超过树直径的一半。
求法
树形DP法,步骤如下:
- 维护\(d_1[x]\),表示以\(x\)为根的子树中,以\(x\)出发的最长链长度。
- 维护\(d_2[x]\),表示以\(x\)为根的子树中,以\(x\)出发的次长链(与最长链无公共边)长度。
- 维护\(up[x]\),表示除去\(x\)子树,剩余图中,经过\(x\)父亲的最长链长度。
- 找到点使得\(max(d_1[x], up[x])\)最小,即\(x\)为中心。
int d1[N], d2[N], up[N], x, y, mini = 1e9;
//x, y为两个中心
struct node {
int to, val; // to为边指向的节点,val为边权
};
vector<node> nbr[N];
void dfsd(int cur, int fa) { // 求取d1和d2
for (node nxtn : nbr[cur]) {
int nxt = nxtn.to, w = nxtn.val;
if (nxt == fa) {
continue;
}
dfsd(nxt, cur);
if (d1[nxt] + w > d1[cur]) {
d2[cur] = d1[cur];
d1[cur] = d1[nxt] + w;
} else if (d1[nxt] + w > d2[cur]) {
d2[cur] = d1[nxt] + w;
}
}
}
void dfsu(int cur, int fa) {
for (node nxtn : nbr[cur]) {
int nxt = nxtn.to, w = nxtn.val;
if (nxt == fa) {
continue;
}
up[nxt] = up[cur] + w; //默认取值,逐步更新
if (d1[nxt] + w != d1[cur]) { // 如果自己子树里的最长链不在nxt子树里
up[nxt] = max(up[nxt], d1[cur] + w); //则使用最长链
} else { // 自己子树里的最长链在nxt子树里
up[nxt] = max(up[nxt], d2[cur] + w); //只能使用次长链
}
dfsu(nxt, cur);
}
}
void GetTreeCenter() { // 统计树的中心,记为x和y(若存在)
dfsd(1, 0);
dfsu(1, 0);
for (int i = 1; i <= n; i++) {
if (max(d1[i], up[i]) < mini) { // 找到了当前max(len1[x],up[x])最小点
mini = max(d1[i], up[i]);
x = i;
y = 0;
} else if (max(d1[i], up[i]) == mini) { // 另一个中心
y = i;
}
}
}
重心
定义
如果在树中选择某个节点并删除,这棵树将分为若干棵子树,统计子树节点数并记录最大值。取遍树上所有节点,使此最大值取到最小的节点被称为整个树的重心。
性质
- 树的重心如果不唯一,则至多有两个,且这两个重心相邻。
- 以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
- 树中所有点到某个点的距离和中,到重心的距离和是最小的如果有两个重心,那么到它们的距离和一样。
- 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
- 在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
求法
DFS过程中统计,下方所有子树的最大大小,下方所有子树的总大小,再用总点数减去下方总大小得到上方子树大小,根据定义找重心。
int size[MAXN], weight[MAXN], centroid[2]; // centroid用于记录树的重心
void GetCentroid(int cur, int fa) {
size[cur] = 1;
weight[cur] = 0;
for (int i = head[cur]; i != -1; i = e[i].nxt) {
if (e[i].to != fa) {
GetCentroid(e[i].to, cur); //DFS递归
size[cur] += size[e[i].to]; //统计向下子树的点数
weight[cur] = max(weight[cur], size[e[i].to]);
}
}
weight[cur] = max(weight[cur], n - size[cur]);
if (weight[cur] <= n / 2) { // 依照树的重心的定义统计
centroid[centroid[0] != 0] = cur;
}
}

浙公网安备 33010602011771号