树的直径总结

265 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情

一. 定义

树上任意两点间最大距离(最长路)

二. 性质(重要)

  1. 设树T存在直径a1-b1,a2-b2......an-bn,从树上任意点c搜索最远点,可能有多个最远点,它们与c的距离一定相同,且任取一最远点,一定是某条直径的端点,因此最长距离d = max(dis(c, a1), dis(c, b1)) = max(dis(c, a2), dis(c, b2)) = ... = max(dis(c, an), dis(c, bn))。

    推论:以某直径端点a搜索最远点,可能有多个最远点,且a与这些最远点距离都为树上最大距离,即a与它们都能组成直径。

    证明见树的直径(最长路) 的详细证明 - Because Of You - 博客园 (cnblogs.com)

  2. 一棵树的所有直径必定以同一点为中点。

    证明见与图论的邂逅01:树的直径&基环树&单调队列 - 修电缆的建筑工 - 博客园 (cnblogs.com)

  3. 只有a1~b1直径上的点i才满足这个条件:d1[i]+d2[i] == d1[b1],其中d1[i]表示i点距a1的距离,d2[i]表示i点距b1的距离。

    证明:当i点在直径a1~b1上时,显然成立。当i点不在直径上时,反证法,假设满足d1[i]+d2[i] == d1[b1],首先i一定通过某条路径与直径上某点j连通,因为在树上,所以d1[i]与d2[i]路径唯一,有d1[i] = dis(a1, j)+dis(j, i),d2[i] = dis(a2, j)+dis(j, i),二者相加显然不等于dis(a1, j)+dis(j, a2),因此得证。

三. 两种方法求直径长度

  1. 根据性质及推论,可以通过两次dfs/bfs找到一条直径的两个端点,并得到直径长度。

    具体代码如下

    int _max = 0, id;//id记录最远点标号 
    
    void dfs(int now, int fa, int dis)//dis记录起点距离now的距离
    {
            if(dis > _max)
            {
                    _max = dis;
                    id = now;
            }
            for(int i = head[now]; i; i = edges[i].next)//链式前向星遍历子节点 
            {
                    if(edges[i].v == fa)//如果下一步要原路返回就跳过,防止无限递归
                            continue;
                    dfs(edges[i].v, now, dis+edges[i].w);
            }
    }
    

    第一次调用时任选起点,这里以1为例,同时起点的父节点也可以任取:dfs(1,0,0)。

    递归结束后id中保存距离起点最远的节点标号。

    第二次调用时以id为起点:dfs(id,0,0)。

    递归结束后_max保存树上最长距离,即树的直径。

  2. 树形dp,dp1[i]记录以i为根的子树中最长路径长度,dp2[i]记录以i为根的子树中次长路径长度。(实际的根不变)

    具体代码如下

    void dfs(int now, int fa)//dfs的任务就是更新dp[now]
    {
            for(int i = head[now]; i; i = edge[i].next)//遍历每条边 
            {
                    if(edge[i].to == fa)
                            continue;
                    dfs(edge[i].to, now);
                    if(dp1[edge[i].to]+edge[i].w > dp1[now])
                    {
                            dp2[now] = dp1[now];
                            dp1[now] = dp1[edge[i].to]+edge[i].w;
                    }
                    else if(dp2[now] < dp1[edge[i].to]+edge[i].w)
                            dp2[now] = dp1[edge[i].to]+edge[i].w;
            }
            ans = max(dp1[now]+dp2[now], ans);
    }