题目描述
实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。
Input
输入文件包含多组测试数据。对于每组测试数据,第一行一个整数N (N<=10000),接下来有N-1行,每一行两个数,对于第i行的两个数,它们表示与i号电脑连接的电脑编号以及它们之间网线的长度。网线的总长度不会超过10^9,每个数之间用一个空格隔开。
Output
对于每组测试数据输出N行,第i行表示i号电脑的答案 (1<=i<=N).
Sample Input
5
1 1
2 1
3 1
1 1
Sample Output
3
2
3
4
4
解题思路
首先看到题,可以抽象为一个图的问题(每台电脑是点,每根网线是边),并且是一个无向图。为了表示边和点的关系,需要采用邻接矩阵,邻接链表的方式来存储。但是这里有一种新的简便的方法——链式前向星,即通过数组的方式来表示链表。每一个数组元素对应一个点,其中存放的是邻接边的序号,然后根据此序号访问下一条边,直到序号为-1(结尾标志)。
想清楚用什么表示以后,需要确定计算的方法。要计算每一个点到其它点的最远距离,首先的想法是依次遍历每个点,用bfs的方式求出最远的距离。但这样时间复杂度太高,每个店都需要计算一次最远距离。
一种新的做法是利用图的直径——相距最远的两个点之间的距离。图中任意点的最远距离一定是到这两个点其中之一的距离。那么问题就简单了,先根据任一点找到最远距离,然后再根据找到的这个点遍历得到直径的另一个端点,再用这个端点遍历图的每一个顶点,最后比较直径两个端点到达每个点的距离,取出大的那一条边,即是该点的最大距离。
这样只用了三次遍历即可得到结果,大大降低复杂度。这里使用的遍历方式是dfs。
代码
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN=10010;
int head[MAXN],vis[MAXN],ans[MAXN],ans1[MAXN];
int tot=0;//定义链式前向星的数组,tot是Edges的下标
struct Edge
{
int u,v,w,nxt;//出发点,到达点,权值,下一个点
}Edges[MAXN];
int v1=1;//记录遍历到达最远的点
void addEdge(int u,int v,int w)
{
Edges[tot].u=u;
Edges[tot].v=v;
Edges[tot].w=w;
Edges[tot].nxt=head[u];//在数组首位插入
head[u]=tot;
tot++;
}
void dfs(int u,int length,int *dis)
{
dis[u]=length;
vis[u]=1;
if(dis[u]>dis[v1])
v1=u;
for(int i=head[u];i!=-1;i=Edges[i].nxt)
{
if(!vis[Edges[i].v])
dfs(Edges[i].v,length+Edges[i].w,dis);
}
}
int main()
{
int N,a,b;
while(cin>>N)
{
memset(head,-1,sizeof(head)); //初始化
for(int i=2;i<=N;i++)
{
cin>>a>>b;
addEdge(i,a,b);//无向图,所以插两次边
addEdge(a,i,b);
}
memset(vis,0,sizeof(vis));//标记每个点均为未到达
dfs(1,0,ans);
memset(vis,0,sizeof(vis));
dfs(v1,0,ans);
memset(vis,0,sizeof(vis));
dfs(v1,0,ans1);
for(int i=1;i<=N;i++)
cout<<max(ans[i],ans1[i])<<endl;
}
return 0;
}
反思与感悟
- 变量定义在静态区域(const),会提高代码的效率,并且初始化一个数组时使用memset比for循环效率更高。
- 写代码时一定要关注时间复杂度,不要放过任何一个降低复杂度的机会,因为可能因为任何一个地方导致TLE。