问题描述
过年了,小蓝想要回家串门。
蓝桥村可以抽象为 n 个节点, n−1 条边的一棵树,每条边有边权长度 wi。
小蓝可以选择任意一个点作为起点,然后选择一条路径,可以访问每个节点至少一次。他想知道最短的路径长度是多少。
输入格式
第一行输入一个整数 n,表示节点数量。
接下来 n−1 行,每行三个整数vi,ui,wi,表示(vi,ui) 存在一条wi 的边。
输出格式
输出一个整数,表示最短路径。
样例输入
4
1 2 3
1 3 4
1 4 5
样例输出
15
说明
路径为:4→51→32→31→434→51→32→31→43,路径和值为 1515。
保证输入数据是一棵树。
不得不说,关于树的各种路径问题一直是我的薄弱点。这道题我一开始看的时候完全没有思路,但看了两篇题解,思路一下就被打开了,看来的却是缺少这方面的训练。
回到正题。我们可以思考,若是要遍历整棵树的所有节点并在最后回到原点的话,这棵树的每条路径一定是要走两遍的,也就是总路径之和为两倍树的路径长之和。但这道题不需要回到原点,也就是说,我们可以简化他为从树的一端到另一端。在纸上画几棵树,然后拿手比划着遍历一下,你就会发现,一定是有一条路径只需要走一遍的,并且这条路径一定以树的顶点为中间的某个点,也就是一定会经过树的顶点,我们将之称之为树的直径。而其他所有路线都会作为这条直径的分叉树,也就是走过再回来的路径和一定是其路径长的两倍。因此,我们只需要找到最长的那条直径作为直用走一遍的直径,我们得到的也就是遍历所需的最短路了。
因此我们可以这样:先把所有边权值加起来乘二存起来,然后用dfs,找到树的顶点的最长权值与第二长的权值,然后用前者减去后者就是我们的答案了。
关于这道题的图的存储的数据结构,我们选择链式前向星。代码如下:
#include <iostream>
#include<vector>
#include<algorithm>
#define int long long
using namespace std;
const int N = 1e5 + 10;
struct edge
{
int to, w, next;//分别代表这条边的终点,权值以及同起点的下一条边编号
};
edge e[N];
int head[N];//e数组存储边,head数组存储以i点为起点的第一条边
int DFS(int i)
{
int m1 = 0, m2 = 0;
int num = head[i];
while (num != -1)//枚举以该点为起点的每一条边
{
int dis = DFS(e[num].to) + e[num].w;
if (dis > m1)
m2 = m1, m1 = dis;
else if (dis > m2)
m2 = dis;
num = e[num].next;
}
return m1 + m2;
}
signed main()
{
int n;cin >> n;
int all = 0;
//有了以某个点为起点的这第一条边与这条边的上一条边的编号,就可以像遍历链表那样遍历以该点为起点的所有边
for (int i = 0;i < N;i++)
head[i] = -1;
for (int i = 1;i <= n - 1;i++)
{
int a, b, c;// 起点,终点,权值
cin >> a >> b >> c;
int aa = min(a, b), bb = max(a, b);//
e[i].to = bb, e[i].w = c, e[i].next = head[aa];;
head[aa] = i;
all += c;
}
all *= 2;
cout << all - DFS(1);
return 0;
}