题目地址:
https://www.acwing.com/problem/content/1075/
给定一棵树,树中包含 n n n个结点(编号 1 ∼ n 1\sim n 1∼n)和 n − 1 n−1 n−1条无向边,每条边都有一个权值。请你在树中找到一个点,使得该点到树中其他结点的最远距离最近。
输入格式:
第一行包含整数
n
n
n。接下来
n
−
1
n−1
n−1行,每行包含三个整数
a
i
,
b
i
,
c
i
a_i,b_i,c_i
ai,bi,ci,表示点
a
i
a_i
ai和
b
i
b_i
bi之间存在一条权值为
c
i
c_i
ci的边。
输出格式:
输出一个整数,表示所求点到树中其他结点的最远距离。
数据范围:
1
≤
n
≤
10000
1≤n≤10000
1≤n≤10000
1
≤
a
i
,
b
i
≤
n
1≤a_i,b_i≤n
1≤ai,bi≤n
1
≤
c
i
≤
1
0
5
1≤c_i≤10^5
1≤ci≤105
思路是DFS,枚举每个顶点,不妨设正在枚举 x x x,求出从该顶点出发的最长的路径的长度,然后找到那个最长路径最短的那个节点。我们任取一个顶点作为树根,然后将整棵树看成一个有根树,这样可以将所有路径按照其经过的最高点来分类,然后枚举顶点 x x x,求出以 x x x为最高点的最长路径长度。设 f ( x ) f(x) f(x)是从 x x x向下走的所有路径里最长的路径,设 g ( x ) g(x) g(x)是从 x x x向上走的所有路径里最长的路径。设 x x x的孩子有 c 1 , c 2 , . . . , c k c_1,c_2,...,c_k c1,c2,...,ck,那么 f ( x ) = ∣ ( x , c i ) ∣ + f ( c i ) f(x)=|(x,c_i)|+f(c_i) f(x)=∣(x,ci)∣+f(ci),这个可以直接以DFS求解。但是对于 g ( x ) g(x) g(x)则稍微复杂一些,设 p x p_x px是 x x x的父亲,那么 g ( x ) g(x) g(x)肯定是先向上走一步的,但是接下来怎么走就得分类了。首先有可能继续向上走,那么就是 ∣ ( x , p x ) ∣ + g ( p x ) |(x,p_x)|+g(p_x) ∣(x,px)∣+g(px);也有可能是向下走,但是向下走不能走回 x x x,我们可以这样考虑,如果 f ( p x ) f(p_x) f(px)是不通过 x x x的,那么向下走的那条路可以走取到 f ( p x ) f(p_x) f(px)的那条;否则的话就要找到去掉通过 x x x的路径之后,剩余路径里的最长路径。所以我们在算 f f f的时候,还要记录一下从 x x x向下走的路径里最长和第二长的路径分别经过了哪个儿子(注意这里的最长和第二长是按经过哪个儿子来分类的,即求 f ( c i ) f(c_i) f(ci)取到最大和次大值的那个 c i c_i ci)。代码如下:
#include <iostream>
#include <cstring>
using namespace std;
// 边数是顶点数乘以2
const int N = 10010, M = 2 * N;
int n;
int h[N], e[M], ne[M], w[M], idx;
// d1[x]是从x向下走的第一长的路径长度,d2[x]是从x向下走的第二长的路径长度;
// p1[x]是从x向下走的第一长的路径长度经过的孩子节点编号,p2[x]是从x向下走的
// 第二长的路径长度经过的孩子节点编号;up[x]是从x向上走的最长路径长度;
// 以上所说路径都是不自交的路径
int d1[N], d2[N], p1[N], p2[N], up[N];
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
// 求从u出发向下走的最长和次长路径长度,及它们经过的孩子编号
int dfs_d(int u, int parent) {
// 枚举u的孩子
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
// 不走回头路
if (j == parent) continue;
// 递归求解从j出发向下的最长路径长度
int d = dfs_d(j, u) + w[i];
if (d >= d1[u]) {
d2[u] = d1[u], d1[u] = d;
p2[u] = p1[u], p1[u] = j;
} else if (d > d2[u]) {
d2[u] = d;
p2[u] = j;
}
}
return d1[u];
}
// 求从u出发向上走的最长路径长度
void dfs_u(int u, int parent) {
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (j == parent) continue;
// 如果从u出发向下走的最长路就是通过j的,那么就应该累加从u向上走的
// 最长路长度和从u向下走的第二长路长度之更大者;否则累加从u向上走的
// 最长路长度和从u向下走的最长路长度之更大者;
if (p1[u] == j) up[j] = max(up[u], d2[u]) + w[i];
else up[j] = max(up[u], d1[u]) + w[i];
dfs_u(j, u);
}
}
int main() {
cin >> n;
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i++) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dfs_d(1, -1);
dfs_u(1, -1);
int res = 0x3f3f3f3f;
for (int i = 1; i <= n; i++) res = min(res, max(d1[i], up[i]));
cout << res << endl;
return 0;
}
时空复杂度 O ( n ) O(n) O(n)。