题解:洛谷 B4016 树的直径

【题目来源】

洛谷:B4016 树的直径 - 洛谷

【题目描述】

给定一棵 \(n\) 个结点的树,树没有边权。请求出树的直径是多少,即树上最长的不重复经过一个点的路径长度是多少。

【输入】

第一行输入一个正整数 \(n\),表示结点个数。

第二行开始,往下一共 \(n-1\) 行,每一行两个正整数 \((u,v)\),表示一条边。

【输出】

输出一行,表示树的直径是多少。

【输入样例】

5
1 2
2 4
4 5
2 3

【输出样例】

3

【核心思想】

  1. 问题分析:给定一棵 \(n\) 个结点的无权树,需要求出树的直径,即树上任意两点之间的最长路径长度。由于树中两点之间路径唯一,问题等价于找到所有点对中距离最大的那一对的距离。直接枚举所有点对会超时(\(O(n^2)\)),需利用树的特殊性质进行优化。

  2. 算法选择

    • 两次 DFS/BFS 法:利用树的一个重要性质——从任意点出发,最远点一定是直径的一个端点。因此,第一次 DFS 从任意点(如 1 号点)出发找到最远点 \(t\),第二次 DFS 从 \(t\) 出发找到最远点,两者的距离即为树的直径。
    • 树形 DP(备选):也可通过树形 DP 求每个节点子树内的最长链和次长链,取最大值得到直径。但两次 DFS 实现更简洁直观。
    • 复杂度:两次 DFS 均为 \(O(n)\),总时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)
  3. 关键步骤

    • 建图:读入 \(n\)\(n-1\) 条边,构建无向邻接表。
    • 第一次 DFS:从节点 1 出发,DFS 遍历整棵树,记录每个节点的深度 deep[u](从起点到该节点的边数),同时维护最大深度 maxx 和对应的节点 t。遍历完成后,t 即为距离节点 1 最远的节点(直径的一个端点)。
    • 第二次 DFS:重置 deep 数组和 maxx,从节点 \(t\) 出发再次 DFS,找到距离 \(t\) 最远的节点,此时的 maxx 即为树的直径长度。
    • 输出答案:输出 maxx
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),两次 DFS 均遍历所有节点和边。
    • 空间复杂度:\(O(n)\),存储邻接表和深度数组。
  5. 两次 DFS 求直径的核心思想

    • 树的性质:在树中,从任意节点出发,最远到达的节点一定是树的直径的某个端点。这个性质保证了第一次 DFS 能找到直径的一个端点,第二次 DFS 从该端点出发就能找到直径的另一端,从而确定直径长度。
    • 正确性证明:利用反证法或树的直径性质可证明上述结论,是树结构中最经典的直径求解方法。
    • 适用场景:适用于无权树(边权为 1)或有权树(边权为正)的直径求解;若边权为负,则需使用树形 DP。

【解题思路】

【算法标签】

普及 #树形DP

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = N*2;
int n, u, v, deep[N], t, maxx;
int h[N], e[M], ne[M], idx;

// 添加无向边
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// 深度优先搜索
void dfs(int u, int fa)
{
    // 计算当前节点的深度
    if (fa)
    {
        deep[u] = deep[fa] + 1;
    }

    // 更新最深节点
    if (deep[u] > maxx)
    {
        maxx = deep[u];  // 更新最大深度
        t = u;           // 记录最深的节点
    }

    // 遍历所有邻接节点
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;  // 跳过父节点
        dfs(j, u);  // 递归搜索子节点
    }
}

int main()
{
    memset(h, -1, sizeof(h));  // 初始化邻接表
    cin >> n;  // 读入节点数

    // 读入边,构建无向图
    for (int i = 1; i < n; i++)
    {
        cin >> u >> v;
        add(u, v), add(v, u);
    }

    // 第一次DFS:从任意节点1开始,找到最远的节点t
    dfs(1, 0);

    // 重置深度数组和最大深度
    memset(deep, 0, sizeof(deep));
    maxx = 0;

    // 第二次DFS:从节点t开始,找到离t最远的节点
    dfs(t, 0);

    // 输出树的直径(最远两节点间的距离)
    cout << maxx << endl;
    return 0;
}

【运行结果】

5
1 2
2 4
4 5
2 3
3
posted @ 2026-09-01 23:09  团爸讲算法  阅读(13)  评论(0)    收藏  举报