问题描述
实验室里原先有一台电脑(编号为1),最近氪金带师咕咕东又为实验室购置了N-1台电脑,编号为2到N。每台电脑都用网线连接到一台先前安装的电脑上。但是咕咕东担心网速太慢,他希望知道第i台电脑到其他电脑的最大网线长度,但是可怜的咕咕东在不久前刚刚遭受了宇宙射线的降智打击,请你帮帮他。
提示: 样例输入对应这个图,从这个图中你可以看出,距离1号电脑最远的电脑是4号电脑,他们之间的距离是3。 4号电脑与5号电脑都是距离2号电脑最远的点,故其答案是2。5号电脑距离3号电脑最远,故对于3号电脑来说它的答案是3。同样的我们可以计算出4号电脑和5号电脑的答案是4.
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),找到与它距离最远的点 , 必定是树直径的端点。
- 从 出发,进行一次BFS(或DFS),找到距离它最远的点 ,则 必定是另一端。即从 出发到 经过到路径就是树的直径。
时间复杂度为 。
证明
符号说明:
表示从
到
的一条路径。
表示从
到
的路径的长度。
证明过程:
假设树的直径两个端点是 。我们从 出发开始BFS(或DFS),找到的距离最远的端点,必定是 。否则 就不是直径了,会有更长的直径。则求解方法中的步骤2正确。
对于步骤1,使用反证法,我们找到任意一点 ,通过BFS(或DFS)找到的最远的点是 ,假设 不是直径的端点,直径是 。则 与 可能相交,也可能不相交。
- 如果相交,设交点为
,如下图所示:
由于 是我们从 开始搜索,找到的最远的点,也就是说, 。那么 ,则 ,那么 不是直径,与假设不符合。 - 如果不相交,假设
是
上的任意一点,
是
上的任意一点,
必定相连(树上任意两点都联通)。如下图所示:
与相交证明方法类似,由于 是我们从 开始搜索,找到的最远的点,也就是说, ,那么 ,易知 ,那么 ,即 ,所以 ,此时 不是直径,与假设不符合。
上述两种情况都否定了假设,说明 是直径的一端,求解方法中的步骤1正确。
树的直径在本题中的应用
那么这个题是求得直径后,所有点分别计算距离直径两端点的距离,然后取最大值吗?
理论上是这样的,但是这样对每个点进行一次搜索,时间复杂度太大,我们逆向想一想,不妨从直径两个端点开始搜索,记录到达所有端点的距离,使用 记录一个端点到其他点的距离,用 记录另一个端点到其他点的距离,然后对一个特定的点 ,我们取 即可。这样就只搜索了两个点而不是n-2个点。
坑点
- 注意是多组数据
- 使用STL可能会超时
- 没有用vis数组的用上vis,数据可能含有网线长度为0的情况
完整代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
const int maxn=10000+1;
struct node
{
int w;
int to;
int next;
};
node edge[maxn*2];
int head[maxn],dis[maxn],mindis[maxn],n,cnt,knot,knot1,knot2,sum;
bool vis[maxn];
void add(int x,int y,int w)
{
cnt++;
edge[cnt].to=y;
edge[cnt].w=w;
edge[cnt].next=head[x];
head[x]=cnt;
}
void bfs(int start)
{
queue<int> q;
memset(dis,0,sizeof(dis));
memset(vis,false,sizeof(vis));
while(!q.empty()) q.pop();
vis[start]=true;
q.push(start);
knot=start;
sum=0;
while(!q.empty())
{
int temp=q.front(); q.pop();
for (int i=head[temp]; i; i=edge[i].next)
{
if(!vis[edge[i].to])
{
dis[edge[i].to]=dis[temp]+edge[i].w;
vis[edge[i].to]=true;
q.push(edge[i].to);
if(sum<dis[edge[i].to])//找到最远的点
{
sum=dis[edge[i].to];
knot=edge[i].to;
}
}
}
}
}
int getint()
{
int x=0,s=1;
char ch=' ';
while(ch<'0' || ch>'9')
{
ch=getchar();
if(ch=='-') s=-1;
}
while(ch>='0' && ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*s;
}
int main()
{
while(scanf("%d",&n)==1)
{
for (int i=1; i<=n; i++) head[i]=0;
cnt=0;
for (int i=2; i<=n; i++)
{
int x=getint(),y=getint();
add(i,x,y); add(x,i,y);
}
bfs(1);
//knot1=knot;
bfs(knot);
//knot2=knot;
//此时knot1,knot2是直径的两端
for (int i=1; i<=n; i++)
mindis[i]=dis[i];
bfs(knot);
for (int i=1; i<=n; i++)
printf("%d\n",max(mindis[i],dis[i]));
}
return 0;
}
参考博客
证明过程参考的这篇博客:https://blog.csdn.net/forever_dreams/article/details/81051578