树的直径——直径的性质
一.定义
树的直径:树上任意两节点之间最长的简单路径即为树的直径。
一棵树可以有多条直径,他们的长度相等。如下图:
二.求解树的直径
1.方法一:(两次DFS求解)
前提条件:树中边权不能是负数
-
首先从任意节点
z 开始进行第一次DFS ,到达距离其最远的节点,记为x -
然后再从 开始做第二次
DFS ,到达距离x 最远的节点,记为y ,则S(x,y) 即为树的直径。
(1)过程示意图:
(2)正确性证明
显然,如果第一次
即:算法第一步找到的节点
定理:在一棵树上,从任意节点
证明:使用反证法,记出发点
按照出发点
- 情况1:若
z 在S(s,t) 上:
有
-
情况2:若
z 不在S(s,t) 上,且S(z,x) 与S(s,t) 存在重合路径: 有S(z,x) > S(z,t) \Rightarrow S(u,x) > S(u,t) \Rightarrow S(s,x)>S(s,t) ,与S(s,t) 为树上任意两节点之间最长的简单路径矛盾。 - 情况3:若
z 不在S(s,t) 上,且S(z,x) 与(s,t) 不存在重合路径: 有S(z,x) > S(z,t) \Rightarrow S(v,x) > S(u,t) \Rightarrow S(u,x) > S(u,t) \Rightarrow S(s,x) > S(s,t) ,与S(s,t) 为树上任意两节点 之间最长的简单路径矛盾。
综上,三种情况下假设均会产生矛盾,故定理得证。
解释:前提条件:树中边权不能是负数
如果边权为负数,则以上第三种情况不能证明出来。如下图:
(3)代码实现
int n, c, d[N];
vector<int> E[N];
void dfs(int u, int fa) {
for (int v : E[u]) {
if (v == fa) continue;
d[v] = d[u] + 1;
if (d[v] > d[c]) c = v;
dfs(v, u);
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d %d", &u, &v);
E[u].push_back(v), E[v].push_back(u);
}
dfs(1, 0);
d[c] = 0, dfs(c, 0);
printf("%d\n", d[c]);
return 0;
}
2.方法二:(树形DP)
备注:树中边权可以是负数,也可以是正数
- 符号定义
d1[u] :表示节点 u 向下延伸的最大路径长度
d2[u] :表示节点 u 向下延伸的次大路径长度
f[u] :表示以节点 u 为根的子树并经过节点 u 的最长的路径长度
-
状态转移:
f[u]=d1[u]+d2[u] -
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int h[N], e[N], ne[N], w[N], idx = 1;
int f[N], n, res;
int d1[N], d2[N];
void dfs(int u, int fa) {
d1[u] = d2[u] = 0;
for(int i=h[u];i;i=ne[i]){
int v = e[i];
if (v == fa) continue;
dfs(v, u);
int t = d1[v] + w[i];
if (t > d1[u])
d2[u] = d1[u], d1[u] = t;
else if (t > d2[u])
d2[u] = t;
}
f[u] = d1[u] + d2[u];
}
void add(int u, int v, int z)
{
e[idx] = v;
w[idx] = z;
ne[idx] = h[u];
h[u] = idx++;
}
int main() {
scanf("%d", &n);
for (int i = 1; i < n; i++) {
int u, v,w;
scanf("%d%d%d", &u, &v,&w);
add(u, v, w);
add(v, u, w);
}
dfs(1, 0);
for (int i = 1; i <= n; i++) res = max(res, f[i]);
printf("%d\n", res);
return 0;
}
- 如果需要求出条直径上所有的节点,则可以在
DP 的过程中,记录下每个节点能向下延伸的最长路径与次长路径(定义同上)所对应的子节点,在求d 的同时记下对应的节点u,使得d=d1[u]+d2[u] ,即可分别沿着从u 开始的最长路径的次长路径对应的子节点一路向某个方向(对于无根树,虽然这里指定了1 为树的根,但仍需记录每点跳转的方向;对于有根树,一路向上跳即可),遍历直径上所有的节点。
三.树的直径的性质
- 性质1:对于一棵所有边权均为正的树,如果其存在多条直径,则树上必存在一点
p ,使得所有直径均经过该点 (简单来说,所有直径必交于至少一点)。 - 性质2:若树上所有边边权均为正,则树的所有直径中点重合(不一定恰好是某个节点,可能在某条边的内部)
- 性质3:若两条直径有重叠的部分,则于重叠部分同一端点引出的两条直径的非重叠的部分的长度相等。