链接:https://ac.nowcoder.com/acm/contest/1109/H
来源:牛客网
题目描述
In ICPCCamp there were n towns conveniently numbered with 1,2,…,n1, 2, \dots, n1,2,…,n
connected with (n - 1) roads.
The i-th road connecting towns aia_iai and bib_ibi has length cic_ici.
It is guaranteed that any two cities reach each other using only roads.
Bobo would like to build (n - 1) highways so that any two towns reach each using *only highways*.
Building a highway between towns x and y costs him δ(x,y)\delta(x, y)δ(x,y) cents,
where δ(x,y)\delta(x, y)δ(x,y) is the length of the shortest path between towns x and y using roads.
As Bobo is rich, he would like to find the most expensive way to build the (n - 1) highways.
输入描述:
The input contains zero or more test cases and is terminated by end-of-file. For each test case:
The first line contains an integer n.
The i-th of the following (n - 1) lines contains three integers aia_iai, bib_ibi and cic_ici.
* 1≤n≤1051 \leq n \leq 10^51≤n≤105
* 1≤ai,bi≤n1 \leq a_i, b_i \leq n1≤ai,bi≤n
* 1≤ci≤1081 \leq c_i \leq 10^81≤ci≤108
* The number of test cases does not exceed 10.
输出描述:
For each test case, output an integer which denotes the result.
示例1
输入
5
1 2 2
1 3 1
2 4 2
3 5 1
5
1 2 2
1 4 1
3 4 1
4 5 2
输出
19
15
题意:第一行给定一个n,接下来(n-1)行,每一行给出三个数a,b,c,分别代表两点编号和其距离(即两点及其边权),两点之间建一条路的花费等于其距离,输出用n-1条边将所有点连起来的最大花费。
思路:因为n个点,只有n-1条边,每两点间仅有一条边,没有多余的边,所以可以看成一个树,显然,要使总花费最大,每个点就要和距离它最远的点相连从而构成一个新的树。在树中,我们定义:树中最远的两个结点之间的距离被称为树的直径,连接这两点的路径被称为树的最长链。可以看出直径就是我们要找的一条边,此外,距离其他点最远的点一定是这条直径的两个端点之一(直径的性质)。最后我们总的花费即为新构成的树的这些边权之和。
树的直径的相关定理:
树上距离任意一点最远的点一定是端点。
特殊的,当任意一点为确定的端点时,距离端点最远的点一定为另一端点。
该定理通过树的直径的定义可证:
证:假设树上距离任意一点最远的点不是直径的端点,即:存在距离任意一点更远的点,这样就可找到一条更长的链(直径两端点中距离现在所假设的距离任意点更远的那个点较远的端点与现在所假设的距离任意点更远的那个点之间可构成一条比当前确定的直径更长的链),这条链比直径更长,这与直径的定义矛盾,所以假设不成立,结论正确
因此, 注意:1、树上距离任意一点最远的点一定是端点
2、距离端点最远的点一定为另一端点
具体做法:跑三遍dfs即可。每遍dfs步骤:选取一个顶点作为起点,可以用dis[i]数组保存每个点到起带点的最短路的最大距离,然后遍历dis数组即可找出以当前起点为端点的最大长度(可能是直径)。
第一遍dfs(确定出直径的一个端点(first)):首先任意选取一点(这里可以取1号顶点)作为起点(该起点可能为端点),用dis0[i]数组保存每个点到当前起点(1)的最短路的最大距离,然后遍历dis0数组即可找出以当前起点(1)为端点的最大长度len(可能是直径),并记录一下到当前起点(1)距离最大的顶点(first)(该点一定为直径的两端点之一(根据直径的定理:树上距离任意一点最远的点一定是端点));
第二遍dfs(记录每个点到first的最大距离,确定出直径和另一端点):取first为新的起点,同样的方法和步骤再跑一遍dfs,这时用dis1[i]数组每个点到当前起点(first)的最短路的最大距离,然后遍历dis1数组即可找出以当前起点(first)为端点的最大长度maxlen(即直径),并记录一下到当前起点(first)距离最大的顶点(second)(该点确定为另一端点(根据直径的定理:距离端点最远的点一定为另一端点))。
显然,第二个端点一定为1和second这两点之一,并且maxlen一定大于等于len,如果len==maxlen,则端点为second=1(说明1就是端点)和first,直径(maxlen)为len;否则端点为first和second,直径(maxlen)为maxlen;
因此,直径为maxlen(即:first到second),first即为直径的一端(同样通过直径定义可证)
第三遍dfs(记录每个点到确定的另一端点(second)的最大距离dis0[i])。
完整代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=8e5;
typedef struct Edge
{
int next,to,w;//链式前向星建图
}edg;
edg e[maxn];
int cnt,head[maxn],dis0[maxn],dis1[maxn],vis[maxn],n,a,b,c;
//dis0[i]表示i号结点到一个端点(该端点经两次确定)的最大距离,dis1[i]表示i号结点到另一个端点(该端点一次确定)的最大距离
void AddEdge(int u,int v,int w)
{
e[cnt].to=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt++;
}
void dfs0(int u)
{
vis[u]=1;
for(int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if(!vis[v]){
dis0[v]=dis0[u]+e[i].w;
dfs0(v);
}
}
}
void dfs1(int u)
{
vis[u]=1;
for(int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if(!vis[v]){
dis1[v]=dis1[u]+e[i].w;
dfs1(v);
}
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
while(cin>>n)
{
int N=n-1;
cnt=0;
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(dis0,0,sizeof(dis0));
memset(dis1,0,sizeof(dis1));//注意这里初始化
while(N--)
{
cin>>a>>b>>c;
AddEdge(a,b,c);
AddEdge(b,a,c);
}
dfs0(1);
int len=-1,first=0;
for(int i=1;i<=n;i++)
{
if(dis0[i]>len){
len=dis0[i];
first=i;
}
}
memset(vis,0,sizeof(vis));
dfs1(first);
int maxlen=-1,second=0;
for(int i=1;i<=n;i++)
{
if(dis1[i]>maxlen){
maxlen=dis1[i];
second=i;
}
}
memset(vis,0,sizeof(vis));
memset(dis0,0,sizeof(dis0));//注意这里dis0要清空,重新存每个点到这次确定的端点的最大距离
dfs0(second);
int sum=0;
for(int i=1;i<=n;i++)
{
sum+=max(dis0[i],dis1[i]);//每个点每次取到距离其最远的端点的距离作为边权,并用sum累加此次边权
}
sum-=maxlen;//注意上面遍历时会遍历到两个端点(端点取端点到其本身和到另一端点距离较大者。即:取了两次max(0,maxlen)=2*maxlen),所以要减去一个
cout<<sum<<endl;
}
return 0;
}