树的最长路径

  • Post author:
  • Post category:其他




树的最长路径



题目描述

在这里插入图片描述




样例解释

在这里插入图片描述




前置知识

树的直径,又称

树的最长链

,定义为一棵树上

最远的两个节点的路径

,即树上一条不重复经过某一条边的最长的路径。树的直径也可以代指这条路径的长度。

求树的直径有两种比较常用的方法:树形DP和搜索。


如何找树的直径呢?

  1. 任取一点作为起点,找到距离该点最远的一个点u。 这一步可以用DFS或者BFS来解决。
  2. 再找到距离点u最远的一个点v。这一步可以用DFS或者BFS来解决。
  3. 那么点u到点v之间的路径就是一条直径了。


如何证明点u到点v之间的路径就是一条直径了呢?

要想证明点u到点v之间就是一条直径,得证明出从点u出发到达点v得路径是最长。

在这里插入图片描述




核心思路

  • 任取一点u,从点u向下搜索,返回时搜集

    边的权值

    ,记录两条路径:
  • d1:记录从点u向下走的最长路径的长度
  • d2:记录从点u向下走的次长路径的长度



  • d

    [

    u

    ]

    =

    d

    1

    +

    d

    2

    d[u]=d1+d2






    d


    [


    u


    ]




    =








    d


    1




    +








    d


    2





    :表示

    悬挂在点u上

    的最长路径的长度,也就是穿过点u的最长路径的长度。

  • 因为点u是任取的,所以遍历完树中的所有节点后,会得到一组



    d

    [

    i

    ]

    d[i]






    d


    [


    i


    ]





    ,答案就是



    a

    n

    s

    =

    m

    a

    x

    (

    d

    [

    i

    ]

    )

    ans=max(d[i])






    a


    n


    s




    =








    m


    a


    x


    (


    d


    [


    i


    ]


    )





  • 但是我们可以不需要开



    d

    [

    i

    ]

    d[i]






    d


    [


    i


    ]





    数组,只要我们每次遍历完一个节点,及时更新全局变量ans即可,即



    a

    n

    s

    =

    m

    a

    x

    (

    a

    n

    s

    ,

    d

    1

    +

    d

    2

    )

    ans=max(ans,d1+d2)






    a


    n


    s




    =








    m


    a


    x


    (


    a


    n


    s


    ,




    d


    1




    +








    d


    2


    )





  • 从整棵树的根节点出发,从父节点依次到叶子节点,再从叶子节点回到父节点,最终回到整棵树的根节点。总是在返回时收集子树的信息(这里是边的权值),从小到大更新每个节点的信息d[u],更新最大值。这就是在树上做DP。

    在这里插入图片描述

以节点1作为根节点,得到的搜索过程如下:

在这里插入图片描述

以节点2为根节点,得到搜索过程如下:

在这里插入图片描述


如何理解开的bool st数组呢?

在这里插入图片描述


为什么dfs函数的返回类型是int,但是我们在主函数中并没有设置变量来接收这个返回值呢?

因为dfs函数它的返回值是d1,含义是返回从节点u往下走的最大长度,但是d1并不是我们想要求的树的最长路径的长度,ans才是我们想要求的,并且我们在dfs函数也已经求出来了。所以我们并不需要d1的值,也就不需要设置变量来接收dfs的返回值啦。当然如果你想设置变量来接收也可以,只不过它的返回值是d1而不是说ans。




代码


写法1

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10010,M=N*2;
int n;
int h[N],e[M],ne[M],w[M],idx;
bool st[N]; //标记点是否被搜过,防止向上搜索
int ans;//记录树的最长路径的长度。
//邻接表存边
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

int dfs(int u)
{
    //由于我们需要记录的是从u这个点向下搜的最长路径,比如u->x->v,权值为4,8,那么为4+8=12
    //但是由于无向边被我们看做是方向相反的两条有向边,回溯时有可能把从下到上的那种有向边的权值都算进去了
    //即4+8+8+4=24,但是我们只需要记录从上到下的路径长度,不需要回溯时的长度,所以,我们要开一个bool数组
    //来记录节点是否已经被遍历过了,这样再回溯时就不会加上那种从下往上的有向边的权值了。
    st[u]=true;//标记u这个点已经被搜过了,防止往上搜
    int d1=0;   //记录从u节点往下走的最大长度
    int d2=0;   //记录从u节点往下走的次大长度
    for(int i=h[u];~i;i=ne[i])  //i是边的编号
    {
        int j=e[i]; //j是u的邻接点
        //如果j这个点已经被遍历过了,则跳过,避免向上查找
        //有可能j是u的父节点,那么j也是u的邻接点,但是由于我们是记录从上往下的路径长度,所以不能
        //又回去遍历上面的,这样就又重复记录权值了
        if(st[j])
        continue;
        //d是记录从节点u经过节点j往下走的最大长度
        int d=dfs(j)+w[i];
        //如果d的长度大于等于d1的长度
        if(d>=d1)
        {
            d2=d1;//要先把原来的最大长度传给次大长度,更新现在新的次大长度
            d1=d;//更新现在新的最大长度
        }
        //否则如果d的长度大于d2的长度(也可以写成大于等于),则只能更新次大长度
        //注意这里要写成else if 而不能写成if 因为首先最大长度d1肯定是大于次大长度d2的
        //如果d>=d1,则说明d>d2,如果写成if,那么可以进入第一个if语句,还能进入第二个if语句,又更新了d2,这样就错了
        else if(d>d2)
        d2=d;   //更新次大长度
    }
    ans=max(ans,d1+d2); //更新答案
    return d1;  //返回从节点u往下走的最大长度
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d",&n);
    //树有n个节点,则有n-1条边
    for(int i=0;i<n-1;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        //把无向边看做是方向相反的两个有向边
        add(a,b,c);
        add(b,a,c);
    }
    //任取一个节点开始深搜
    dfs(1);
    printf("%d\n",ans);
    return 0;
}


写法2

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10010,M=N*2;
int n;
int h[N],e[M],ne[M],w[M],idx;
int ans;//记录树的最长路径的长度。
//邻接表存边
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

//father是节点u的父节点
int dfs(int u,int father)
{
    //由于我们需要记录的是从u这个点向下搜的最长路径,比如u->x->v,权值为4,8,那么为4+8=12
    //但是由于无向边被我们看做是方向相反的两条有向边,回溯时有可能把从下到上的那种有向边的权值都算进去了
    //即4+8+8+4=24,但是我们只需要记录从上到下的路径长度,不需要回溯时的长度,所以,我们要开一个bool数组
    //来记录节点是否已经被遍历过了,这样再回溯时就不会加上那种从下往上的有向边的权值了。
    int d1=0;   //记录从u节点往下走的最大长度
    int d2=0;   //记录从u节点往下走的次大长度
    for(int i=h[u];~i;i=ne[i])  //i是边的编号
    {
        int j=e[i]; //j是u的邻接点
        //如果j这个点已经被遍历过了,则跳过,避免向上查找
        //有可能j是u的父节点,那么j也是u的邻接点,但是由于我们是记录从上往下的路径长度,所以不能
        //又回去遍历上面的,这样就又重复记录权值了
        if(j==father)
        continue;
        //d是记录从节点u经过节点j往下走的最大长度
        int d=dfs(j,u)+w[i];
        //如果d的长度大于等于d1的长度
        if(d>=d1)
        {
            d2=d1;//要先把原来的最大长度传给次大长度,更新现在新的次大长度
            d1=d;//更新现在新的最大长度
        }
        //否则如果d的长度大于d2的长度(也可以写成大于等于),则只能更新次大长度
        //注意这里要写成else if 而不能写成if 因为首先最大长度d1肯定是大于次大长度d2的
        //如果d>=d1,则说明d>d2,如果写成if,那么可以进入第一个if语句,还能进入第二个if语句,又更新了d2,这样就错了
        else if(d>d2)
        d2=d;   //更新次大长度
    }
    ans=max(ans,d1+d2); //更新答案
    return d1;  //返回从节点u往下走的最大长度
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d",&n);
    //树有n个节点,则有n-1条边
    for(int i=0;i<n-1;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        //把无向边看做是方向相反的两个有向边
        add(a,b,c);
        add(b,a,c);
    }
    //任取一个节点开始深搜   同时记录某个节点它的父节点
    dfs(1,-1);
    printf("%d\n",ans);
    return 0;
}



版权声明:本文为qq_45832461原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。