树的直径问题

发布时间 2023-07-24 19:34:28作者: pig_pig

树上任意两节点之间最长的简单路径即为树的「直径」。

显然,一棵树可以有多条直径,他们的长度相等。

可以用两次 DFS 或者树形 DP 的方法在 O(n) 时间求出树的直径。

两次DFS

这是一种非常容易理解的方法

从树上任意一点出发,进行 dfs ,记其所能到达最远的点(即所经路径之和最大)为 k1

再从 k1 点出发进行 dfs ,记其所能到达最远的点(即所经路径之和最大)为 k2

那么 k1 , k2 之间的路径即为树的直径

树形DP

这里介绍树形 DP 方法

首先观察以下图

现在取任意一点,将其所能经过的最长路径与次长路径(与最长路径无公为共边)记录下来,那么

  1. 如果该点位于直径上,那么两者之和即为路径长
  2. 如果该点不位于直径上,那么该点的两者之和一定小于直径长

这里从一个点出发所能到达的最长和次长路径的终点 相当于(尝试)碰到 k1, k2两点

为了便于操作,我们定义当 1 为树的根时,将每个节点作为其子树的根,向下所能延伸的最长路径长度为 d1 ,次长路径(与最长路径无公为共边)长度为 d2,那么直径就是对于每一个点,该点 d1 + d2 能取到的值中的最大值。

f1[]为最长距离,f2[]为次长距离

树形dp求直径
void dp(int x,int fa)
{
    for(int i=head[x];i!=-1;i=e[i].next)链式前向星存树
    {
        int v=e[i].to;
        if(v==fa) continue;//防止原路返回
        dp(v,x);//dp过程应该是由叶节点开始的,也就是说先递归到叶节点再开始进行状态转移
        if(f1[x]<f1[j]+e[i].w)//如果子节点的最大距离+子节点与父节点之间边的权重大于父节点的最大距离,那么父节点的最大距离和次大距离都要得到相应更新
        {
            f2[x]=f1[x];
            f1[x]=f1[j]+e[i].w;
        }
        else if(f2[x]<f1[j]+e[i].w)//若子节点的最大距离+子节点与父节点之间边的权重小于父节点的最大距离,再判断与父节点的次大距离的关系
            f2[x]=f1[j]+e[i].w;
        ans=max(ans,f1[x]+f2[x]);//在搜索过程中找到树的直径
    }
}