题意:
给定一棵树,树中包 含 n
个结点(编号1~n
)和 n−1
条 无向边,每条边都有一个正权值。
在树中找到一个点,使得 该点到树中其他结点的最远距离最近。
思路:
我们以题目给出的样例构建一棵树:
分别求出每个点到其它所有点的最长距离,在 所有距离之中取最小值
考虑一下 每个点到其它所有点距离 分为哪几类
我们可以分为 两大类,
- 第一大类:从 当前点开始往子节点走 的距离(往下)
- 第二大类:从 当前点开始往父节点走 的距离(往上)
其中第一大类可以用 上一题:AcWing 1072. 树的最长路径(树形dp) 的思路求解,我们可以先求出每个点向下走的 最大距离down1[i]
和 次大距离down2[i]
对于 第二大类情况最大值 该如何求解呢?
我们还是从 集合角度 进行分析,不妨 以树中第 2
号节点为起点,可以发现,第二类情况中所有路径的 第一步都是从 2
走到它的父节点 1
,之后再向任意其它节点走。
要求所有这样形式路径的最大值,可以将其分为 两部分
- 第一部分
2->1
:显然这部分已经确定,就是2->1
的权值 - 第二部分 除第一部分之外的全部:从
1
号点 不返回2
的所有路径长度的最大值。同样,第二部分又可以分为两大类, -
- 第一类 继续往上走:从父节点
1
往上走的最大长度(设为up[1]
)
- 第一类 继续往上走:从父节点
-
- 第二类 往下走:此处又可分为两类(设为
down[1]
)
- 第二类 往下走:此处又可分为两类(设为
-
-
- ① 如果从父节点
1
号点往下走最长的边 未经过当前点2
号点(此时答案即为父节点1
的 向下走最大距离down1[1]
)
- ① 如果从父节点
-
-
-
- ② 如果从父节点
1
号点往下走最长的边 经过当前点2
号点(当然再度走到2
号点是不合法的,此时答案即为父节点1
的 向下走次大距离down2[1]
)
- ② 如果从父节点
-
所以对于 第二大类,答案:up[j] = max(up[u],down[u]) + w[i]
(j
为 当前节点编号,u
为其 父节点编号,w[i]
为 父子节点之间已确定的边权值,down[u]
根据情况 返回 父节点 u
向下走 的 最大值down1[u] 或 次大值down2[u])
综合上面分类讨论的全部情况,循环每个节点的max
值并取 min
即可得到最终答案res
:
int res = 0x3f3f3f3f;
for(int i=1; i<=n; ++i)
{
int dis = max(up[i], down1[i]);
res = min(res, dis);
}
所以,本题最终做法是 跑两遍树的深度优先遍历,
第一遍从上往下递归,用 子节点的信息更新父节点的信息(更新出每个节点往下走的 最大down1[i]
值 和 次大down2[i]
值)
第二遍 用父节点的信息将子节点的信息更新,每个子节点往上的最大距离应分两种情况分析(具体见上文)
两点注意:
- ①如果题目中给出树的边权可能为负数,那么,只有当节点不是叶子节点时,才存在向下走的路径;同理只有当节点不是根节点时,才存在向上走的路径。所以最后在求最小值时,如果是叶节点,则只能用
up[i]
更新,如果是根节点,则只能用down1[u]
更新。
int res = down1[1];
for(int i=2; i<=n; ++i)
{
if(is_leaf[i]) res = min(res, up[i]);
else res = min(res, max(down1[i], up[i]));
}
- ②我们在第一次向下
dfs
的过程中还需要存一下每个节点 最长边 是从 哪个子节点 上来的,因为后续第二次dfs
时需要判断 父节点的最长边 是 哪个子节点上来,所以我们需要开一个新的数组p1
存储这个信息。
时间复杂度:
O ( n + m ) O(n + m) O(n+m)
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
int n;
int h[N], e[N<<1], ne[N<<1], w[N<<1], idx;
int up[N], down1[N], down2[N];
int p1[N];
void add(int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
int dfs_d(int u, int father)
{
down1[u] = down2[u] = 0;//因为题目保证都是正权边,因此初始化为 0 即可
for(int i=h[u]; ~i; i=ne[i])
{
int j = e[i];
if(j==father) continue;
int d = dfs_d(j, u) + w[i];
if(down1[u]<=d) {down2[u] = down1[u], down1[u] = d; p1[u] = j;}
else if(down2[u]<d) down2[u] = d;
}
return down1[u];
}
void dfs_u(int u, int father)
{
for(int i=h[u]; ~i; i=ne[i])
{
int j = e[i];
if(j==father) continue;
if(p1[u]==j) up[j] = max(up[u], down2[u]) + w[i];
else up[j] = max(up[u], down1[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;
scanf("%d%d%d", &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)
{
int dis = max(up[i], down1[i]);
res = min(res, dis);
}
cout<<res<<endl;
return 0;
}