氪金带东(树的直径)

问题描述

实验室里原先有一台电脑(编号为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的证明)。因此这个题需要用到树的直径。

树的直径

求解方法
  1. 从任意一点 v 1 v_1 出发,进行一次BFS(或DFS),找到与它距离最远的点 v 2 v_2 v 2 v_2 必定是树直径的端点。
  2. v 2 v_2 出发,进行一次BFS(或DFS),找到距离它最远的点 v 3 v_3 ,则 v 3 v_3 必定是另一端。即从 v 2 v_2 出发到 v 3 v_3 经过到路径就是树的直径。

时间复杂度为 O ( n ) O(n)

证明
符号说明:

( i , j ) (i,j) 表示从 i i j j 的一条路径。
d i s [ i , j ] dis[i,j] 表示从 i i j j 的路径的长度。

证明过程:

假设树的直径两个端点是 v 2 v 3 v_2,v_3 。我们从 v 2 v_2 出发开始BFS(或DFS),找到的距离最远的端点,必定是 v 3 v_3 。否则 ( v 2 , v 3 ) (v_2,v_3) 就不是直径了,会有更长的直径。则求解方法中的步骤2正确。

对于步骤1,使用反证法,我们找到任意一点 v 1 v_1 ,通过BFS(或DFS)找到的最远的点是 v 4 v_4 ,假设 v 4 v_4 不是直径的端点,直径是 ( v 2 , v 3 ) (v_2,v_3) 。则 ( v 1 , v 4 ) (v_1,v_4) ( v 2 , v 3 ) (v_2,v_3) 可能相交,也可能不相交。

  1. 如果相交,设交点为 v 5 v_5 ,如下图所示:
    在这里插入图片描述
    由于 v 4 v_4 是我们从 v 1 v_1 开始搜索,找到的最远的点,也就是说, d i s [ v 1 , v 4 ] > d i s [ v 1 , v 2 ] dis[v_1,v_4]>dis[v_1,v_2] 。那么 d i s [ v 5 , v 4 ] > d i s [ v 5 , v 2 ] dis[v_5,v_4]>dis[v_5,v_2] ,则 d i s [ v 2 , v 3 ] < d i s [ v 4 , v 3 ] dis[v_2,v_3]<dis[v_4,v_3] ,那么 ( v 2 , v 3 ) (v_2,v_3) 不是直径,与假设不符合。
  2. 如果不相交,假设 v 5 v_5 ( v 1 , v 4 ) (v_1,v_4) 上的任意一点, v 6 v_6 ( v 2 , v 3 ) (v_2,v_3) 上的任意一点, v 5 , v 6 v_5,v_6 必定相连(树上任意两点都联通)。如下图所示:
    在这里插入图片描述
    与相交证明方法类似,由于 v 4 v_4 是我们从 v 1 v_1 开始搜索,找到的最远的点,也就是说, d i s [ v 1 , v 4 ] > d i s [ v 1 , v 2 ] dis[v_1,v_4]>dis[v_1,v_2] ,那么 d i s [ v 5 , v 4 ] > d i s [ v 5 , v 2 ] dis[v_5,v_4]>dis[v_5,v_2] ,易知 d i s [ v 5 , v 6 ] > 0 dis[v_5,v_6]>0 ,那么 d i s [ v 5 , v 4 ] + d i s [ v 5 , v 6 ] > d i s [ v 5 , v 2 ] d i s [ v 5 , v 6 ] dis[v_5,v_4]+dis[v_5,v_6]>dis[v_5,v_2]-dis[v_5,v_6] ,即 d i s [ v 4 , v 6 ] > d i s [ v 2 , v 6 ] dis[v_4,v_6]>dis[v_2,v_6] ,所以 d i s [ v 3 , v 4 ] > d i s [ v 2 , v 3 ] dis[v_3,v_4]>dis[v_2,v_3] ,此时 ( v 2 , v 3 ) (v_2,v_3) 不是直径,与假设不符合。

上述两种情况都否定了假设,说明 v 4 v_4 是直径的一端,求解方法中的步骤1正确。

树的直径在本题中的应用

那么这个题是求得直径后,所有点分别计算距离直径两端点的距离,然后取最大值吗?

理论上是这样的,但是这样对每个点进行一次搜索,时间复杂度太大,我们逆向想一想,不妨从直径两个端点开始搜索,记录到达所有端点的距离,使用 d i s 1 [ i ] dis1[i] 记录一个端点到其他点的距离,用 d i s 2 [ i ] dis2[i] 记录另一个端点到其他点的距离,然后对一个特定的点 i i ,我们取 m a x ( d i s 1 [ i ] , d i s 2 [ i ] ) max(dis1[i],dis2[i]) 即可。这样就只搜索了两个点而不是n-2个点。

坑点

  1. 注意是多组数据
  2. 使用STL可能会超时
  3. 没有用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

发布了32 篇原创文章 · 获赞 24 · 访问量 2226

猜你喜欢

转载自blog.csdn.net/weixin_43347376/article/details/105099260
今日推荐