洛谷 B4016:树的直径 ← 两次 DFS 法 + 链式前向星

【题目来源】
https://www.acwing.com/problem/content/1074/
https://www.luogu.com.cn/problem/B4016

【题目描述】
给定一棵 n 个结点的树,树没有边权。请求出树的直径是多少,即树上最长的不重复经过一个点的路径长度是多少。

【输入格式】
第一行输入一个正整数 n,表示结点个数。
第二行开始,往下一共 n−1 行,每一行两个正整数 (u,v),表示一条边。

【输出格式】
输出一行,表示树的直径是多少。

【输入样例】
5
1 2
2 4
4 5
2 3

【输出样例】
3

【数据范围】
数据保证,1≤n≤10^5。

【算法分析】
(一)两次 DFS 法 
给定一棵边权均为非负数的无向树
(1)任选树上任意一点 root,从 root 执行一次 DFS / BFS 遍历整棵树,求出所有点到 root 的距离,取距离最远的点记作 p;
(2)再从点 p 执行一次 DFS / BFS 遍历整棵树,求出所有点到 p 的距离,取距离最远的点记作 q;
两点间距离 dist(p,q) 即为这棵树的直径(树上任意两点间的最长路径长度)

(二)树形 DP 法:https://blog.csdn.net/hnjzsyjyj/article/details/155581179
● 什么是树的直径?树上任意两结点之间最长的简单路径即为树的直径。
若无负权边,可以采用两次 DFS 或者树形 DP 的方法在 O(n) 时间求出树的直径;若有负权边,则只能采用树形 DP 求解树的直径。显然,一棵树可以有多条直径,因为树中可能存在最长长度相等的多条简单路径。→ 推荐使用树形 DP 求解树的直径

● 根据树形 DP 法原理,树的直径计算方法如下:
(1)路径定义‌:树的直径是树中任意两节点间最长的简单路径长度。
(2)状态转移‌:以某节点为根的子树,其延伸的最长路径长度记为 d1,次长路径长度记为 d2。树的直径即为所有节点 d1+d2 的最大值。
(3)路径特性‌:最长路径 d1 和次长路径 d2 无公共边,确保路径唯一性。

● 路径更新
若 d1[j]+1>d1[u],说明通过 j 的路径更长,更新 d1[u]=d1[j]+1,同时旧的最长路径 d1[u] 转为次长路径 d2[u]。
若 d1[j]+1<=d1[u] 但 d1[j]+1>d2[u],说明 j 的路径虽非最长,但比当前次长路径更长,更新 d2[u]=d1[j]+1。

● 更新方向
在树形 DP 中,路径长度从叶子节点(无子节点)开始计算,逐层向上更新父节点的路径长度。因此,j(子节点)确实会先于 u(父节点)被处理,符合“自下向上更新”的描述。
d1 和 d2 初始值为 0,表示所有节点的最长和次长路径长度从 0 开始计算,符合树形 DP 的初始化要求。

(三)链式前向星:https://blog.csdn.net/hnjzsyjyj/article/details/139369904
“链式前向星”就是“多单链表”,每条单链表基于“头插法”并用 e[]、ne[]、h[] 、val[] 等数组进行模拟创建。其中:
e[idx]:存储序号为 idx 的边的终点值
ne[idx]:存储序号为 idx 的边指向的边的序号(模拟链表指针)‌
h[a]:存储头结点 a 指向的边的序号
val[idx]:存储序号为 idx 的边的权值(可选)

【算法代码:链式前向星存图

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int e[N<<1],ne[N<<1],h[N],idx;
int dis[N];

void add(int a,int b) {
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

//Calculate distance to each node from u.
void dfs(int u,int fa) {
    for(int i=h[u]; i!=-1; i=ne[i]) {
        int v=e[i];
        if(v==fa) continue;
        dis[v]=dis[u]+1;
        dfs(v,u);
    }
}

int main() {
    memset(h,-1,sizeof h);
    int n;
    cin>>n;
    for(int i=1; i<n; i++) {
        int u,v;
        cin>>u>>v;
        add(u,v),add(v,u);
    }

    /*First DFS: Start from any point
    and find the farthest point p*/
    memset(dis,0,sizeof dis);
    dfs(1,0);
    int p=1;
    for(int i=1; i<=n; i++) {
        if(dis[i]>dis[p]) p=i;
    }

    /*Second DFS: Starting from point p,
    find the farthest point q.
    The distance from p to q is the diameter.*/
    memset(dis,0,sizeof dis);
    dfs(p,0);
    int q=1;
    for(int i=1; i<=n; i++) {
        if(dis[i]>dis[q]) q=i;
    }

    cout<<dis[q]<<endl;

    return 0;
}

/*
in:
5
1 2
2 4
4 5
2 3

out:
3
*/




【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/155581179
https://blog.csdn.net/hnjzsyjyj/article/details/162608874
https://blog.csdn.net/hnjzsyjyj/article/details/140309677
https://blog.csdn.net/hnjzsyjyj/article/details/140286717
 

 

posted @ 2026-07-05 22:34  Triwa  阅读(6)  评论(0)    收藏  举报