一、树的最长路径
树的直径:树上最长的简单路径的长度
一种求树的直径的方式:
- 以任意一点 a a a 为起点,找到树上与该点距离最远的点 u u u
- 以 u u u 为起点,找到树上与 u u u 最远的点 v v v,则 ( u , v ) (u,v) (u,v) 是树的一条直径
下证 u u u 一定是一条直径的一个端点:
用反证法,假设 u u u 不是直径的一个端点,树的一条直径为 ( b , c ) (b,c) (b,c),存在以下两种情况:
- 路径 ( a , u ) (a,u) (a,u) 与路径 ( b , c ) (b,c) (b,c) 无交点,找到 ( a , u ) (a,u) (a,u) 上一点 x x x 和 ( b , c ) (b,c) (b,c) 上一点 y y y 满足 x , y x,y x,y 之间联通
因为 u u u 是距离 a a a 最远的点,所以 ① ≥ ② + ③ ①\ge ②+③ ①≥②+③,从而 ① + ② > ③ ①+②>③ ①+②>③,因此 ( b , u ) (b,u) (b,u) 比 ( b , c ) (b,c) (b,c) 长,这与 ( b , c ) (b,c) (b,c) 是直径矛盾。
2. 路径 ( a , u ) (a,u) (a,u) 与路径 ( b , c ) (b,c) (b,c) 交于点 x x x
因为 u u u 是距离 a a a 最远的点,所以 ① ≥ ② ①\ge ② ①≥②,这与 u u u 不是直径的一个端点矛盾。
但上述做法有个缺陷:仅在树上所有边边权为 1 1 1 时适用,边权不同 (甚至可为负) 时DFS、BFS很难找到离某个点距离最远的点,而若用图论的算法时间复杂度太高。
这里介绍更一般的做法:树形DP,能做到线性时间复杂度。
不妨设树的根为 1 1 1。对于树上每一条路径,找到其路径上高度最高的点,将这条路径归于这个点上。因此,我们可以根据点将所有路径分类。当固定了最高点后,所有路径长度的最大值为从这点往下走的路径的最长距离加上从这点往下走的路径的次长距离。我们在DFS时对每个点找出其往下走的路径的最长距离和次长距离,从而更新答案。
代码实现:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20010;
int n;
int e[N], ne[N], w[N], h[N], idx;
int ans;
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int dfs(int u, int father){
//这里要多传一个父节点作参数,便于判断搜索的方向
//这里与y总代码不同,省去了dist的定义,因为dist总与d1相同,最后返回d1即可
int d1 = 0, d2 = 0; //最长距离和次长距离
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (j == father) continue; //只往子节点搜索
int d = dfs(j, u) + w[i];
if (d >= d1) d2 = d1, d1 = d;
else if (d > d2) d2 = d;
}
ans = max(ans, d1 + d2);
return d1;
}
int main(){
scanf("%d", &n);
memset(h, -1, sizeof h);
for (int i = 1; i < n; i ++){
int a, b, c;
scanf("%d %d %d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs(1, -1);
printf("%d", ans);
return 0;
}
二、树的中心
对于树上任意一个结点,它到其它结点的最远路径可分为两种情况:
- 经过其某个子结点
- 经过其父节点
先考虑第一种情况:此时最远距离即为其子结点向下的最远距离+连接两点的边的边权,前者可以依照上题递归求解。
对于第二种情况,考虑其父节点,最远距离仍有两种情况:
- 其父节点向上的最远距离+连接两点的边的边权
- 其父节点沿另一子结点向下的最远距离+连接两点的边的边权
对于上述第一种情况,仍然可以递归求解;对于上述第二种情况,我们需要在递归求解沿子结点向下的最远距离的同时求出不沿该子结点(即取到向下最远距离的结子点)且向下的最远距离(在此简称为“次长距离”,其实并不是经过该子结点的第二长距离)。
代码实现:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;
int n;
int e[M], ne[M], w[M], h[N], idx;
int d1[N], d2[N], up[N], p1[N];
//分别记录:向下的最远距离,向下的次长距离,向上的最远距离,向下最远距离走的哪个子结点
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int dfs_d(int u, int father){
//向下递归,先递归子结点再处理根结点
d1[u] = d2[u] = -INF;
for (int i = h