跳转至

树的直径 (Tree Diameter)

树的直径(Tree Diameter)是无向无环图(树)中最基础也是最重要的拓扑度量之一,指的是树中任意两点之间简单路径长度(权值和)的最大值。连接该最长路径的两个端点,称为直径端点;该最长路径本身亦常被称为树的直径。

树的直径不仅在网络路由、分布式系统拓扑分析、最长关键路径规划等工业场景中扮演核心角色,也是算法竞赛(Codeforces, AtCoder, IOI/NOIP)及高频算法面试中的经典核心考点。


1. 核心定义与拓扑性质

1.1 基础定义

\(T = (V, E)\) 为一棵含有 \(n\) 个节点的无向无环树,边 \(e = (u, v) \in E\) 具有权值 \(w(e)\)(若为无权树,则默认边权为 \(1\))。

  • 路径距离:对任意两点 \(u, v \in V\),记 \(\text{dist}(u, v)\)\(u\)\(v\) 之间唯一的简单路径上所有边权之和。
  • 树的直径: $\(D(T) = \max_{u, v \in V} \text{dist}(u, v)\)$

  • 偏心距 (Eccentricity):任意节点 \(u\) 到树中其他节点的最大距离,记作: $\(\epsilon(u) = \max_{v \in V} \text{dist}(u, v)\)$

  • 树的半径 (Radius)树的中心 (Center)

  • 树的半径为所有节点偏心距的最小值:\(R(T) = \min_{u \in V} \epsilon(u)\)
  • 使得偏心距取得最小值的节点 \(c\) 称为树的中心点 (Center)

1.2 核心拓扑定理

[!NOTE] 引理 1(最远点引理 / 端点贪心引理) 在所有边权均为非负数(\(w(e) \ge 0\))的树中,从树上任意选定一个节点 \(u \in V\) 出发,所能到达的距离 \(u\) 最远的节点 \(p\)(即满足 \(\text{dist}(u, p) = \epsilon(u)\)),必定是树的某条直径的一个端点

[!NOTE] 定理 1(若尔当树中心定理 / Jordan, 1869) 任何树的所有直径路径必定相交于同一个公共部分。树的中心节点至多有 1 个(若直径边数为偶数,中心为单个节点)或 2 个(若直径边数为奇数,中心为一条边的两个相邻端点)。树的中心恰好位于任何一条直径的中点上,且树的半径与直径满足: $\(R(T) = \left\lceil \frac{D(T)}{2} \right\rceil\)$

[!NOTE] 定理 2(两树合并新直径定理) 设树 \(T_1\) 的一条直径端点为 \(\{u_1, v_1\}\),树 \(T_2\) 的一条直径端点为 \(\{u_2, v_2\}\)。若添加一条权重为 \(w\) 的无向边将 \(T_1\) 的某个节点与 \(T_2\) 的某个节点相连形成新树 \(T'\),则新树 \(T'\) 的直径端点必定完全落在集合 \(\{u_1, v_1, u_2, v_2\}\)


2. 算法一:两次 DFS / BFS 算法与严格数学证明

2.1 算法流程

两次搜索法是求解无负权边树直径最直观、常数最小的算法,时间复杂度严格为 \(\mathcal{O}(V + E) = \mathcal{O}(n)\)

  1. 第一遍搜索:从树中任取一个节点 \(x\) 出发,运行一次 BFS 或 DFS,找到距离 \(x\) 最远的节点,记为 \(p\)
  2. 第二遍搜索:从节点 \(p\) 出发,运行第二次 BFS 或 DFS,找到距离 \(p\) 最远的节点,记为 \(q\)
  3. 输出结果\(p\)\(q\) 即为树的一对直径端点,路径 \(p \leadsto q\) 的长度 \(\text{dist}(p, q)\) 即为树的直径 \(D(T)\)。在搜索过程中记录每个节点的前驱指针,即可 \(\mathcal{O}(n)\) 还原整条直径上的节点序列。
graph LR
    X((任意起点 x)) -->|第一次 BFS/DFS| P((最远点 p\n直径端点 A))
    P -->|第二次 BFS/DFS| Q((最远点 q\n直径端点 B))
    P -.->|树的直径路径 dist(p, q)| Q

2.2 严格数学证明(反证法)

许多初学者容易对“为什么从任意点出发找到的最远点 \(p\) 一定是直径端点”感到困惑。下面给出严谨的数学证明。

证明目标

设树中某条真实直径为路径 \(AB\),其长度为 \(D = \text{dist}(A, B)\)。任选起点 \(u\),令 \(p\) 满足 \(\text{dist}(u, p) = \max_{x} \text{dist}(u, x)\)。求证:存在一条树的直径以 \(p\) 为其中一个端点。

分类讨论

树中任意两点间的简单路径是唯一的。根据路径 \(u \leadsto p\) 与已存在的某条直径 \(A \leadsto B\) 是否有公共交点,分为两种情况:

情况 1:路径 \(u \leadsto p\) 与直径 \(A \leadsto B\) 有公共相交节点

设两路径相交的节点中离 \(p\) 最近的交点为 \(C\)(由于树无环,交集必然是一条连续路径或单个节点,设 \(C\) 属于该交集)。

  • 根据路径分解: $\(\text{dist}(u, p) = \text{dist}(u, C) + \text{dist}(C, p)\)$

  • 因为 \(A\) 是树中节点,所以从 \(u\)\(A\) 的距离为: $\(\text{dist}(u, A) = \text{dist}(u, C) + \text{dist}(C, A)\)$

  • \(p\) 的定义知 \(p\) 是距离 \(u\) 的全局最远点,故: $\(\text{dist}(u, p) \ge \text{dist}(u, A)\)$ $\(\text{dist}(u, C) + \text{dist}(C, p) \ge \text{dist}(u, C) + \text{dist}(C, A)\)$

  • 两侧消去非负项 \(\text{dist}(u, C)\),得到重要不等式: $\(\text{dist}(C, p) \ge \text{dist}(C, A)\)$

  • 现考察从直径另一端点 \(B\) 经由 \(C\) 走到 \(p\) 的路径 \(B \leadsto C \leadsto p\): $\(\text{dist}(B, p) = \text{dist}(B, C) + \text{dist}(C, p) \ge \text{dist}(B, C) + \text{dist}(C, A) = \text{dist}(A, B) = D\)$

  • 但已知 \(D\) 是全树最长简单路径的距离上限,因此必然有 \(\text{dist}(B, p) \le D\)

  • 结合上述两式,必有 \(\text{dist}(B, p) = D\)
  • 结论:路径 \(B \leadsto p\) 的长度恰好等于树的直径 \(D\),即 \(p\) 是树的一条合法直径端点。证毕。
情况 2:路径 \(u \leadsto p\) 与直径 \(A \leadsto B\) 无公共相交节点

由于树是连通无环图,路径 \(u \leadsto p\) 与直径 \(A \leadsto B\) 之间必然存在唯一的一条无环连接链。设该连接链在 \(u \leadsto p\) 上的接入点为 \(X\),在直径 \(A \leadsto B\) 上的接入点为 \(C\)(其中 \(X \neq C\),连接链长度 \(\text{dist}(X, C) > 0\))。

  • 此时从 \(u\)\(A\) 的路径必须经过 \(X\)\(C\): $\(\text{dist}(u, A) = \text{dist}(u, X) + \text{dist}(X, C) + \text{dist}(C, A)\)$

  • \(u\)\(p\) 的路径经过 \(X\): $\(\text{dist}(u, p) = \text{dist}(u, X) + \text{dist}(X, p)\)$

  • \(p\) 是距 \(u\) 的最远点性质: $\(\text{dist}(u, p) \ge \text{dist}(u, A)\)$ $\(\text{dist}(u, X) + \text{dist}(X, p) \ge \text{dist}(u, X) + \text{dist}(X, C) + \text{dist}(C, A)\)$

  • 消去 \(\text{dist}(u, X)\): $\(\text{dist}(X, p) \ge \text{dist}(X, C) + \text{dist}(C, A)\)$

  • 两侧同时加上非负距离 \(\text{dist}(X, C)\): $\(\text{dist}(C, p) = \text{dist}(C, X) + \text{dist}(X, p) \ge 2 \cdot \text{dist}(X, C) + \text{dist}(C, A) > \text{dist}(C, A)\)$

  • 于是考察路径 \(B \leadsto C \leadsto X \leadsto p\) 的总长: $\(\text{dist}(B, p) = \text{dist}(B, C) + \text{dist}(C, p) > \text{dist}(B, C) + \text{dist}(C, A) = D\)$

  • 这推导出了 \(\text{dist}(B, p) > D\),与 \(D\) 是全树的最大距离相矛盾!

  • 因此在非负权树中,情况 2 实际上不可能出现。综合情况 1 与情况 2,命题得证。

[!WARNING] 负权边反例与适用边界 上述证明中,消去不等式公共项高度依赖所有边权非负的先决条件。若树中存在负权边,\(p\) 可能只是因为经过了某条负权抵消路径而不再是直径端点。因此: - 两次 DFS/BFS 法:仅适用于非负边权树; - 带负权边或带点权树:必须使用树形 DP 求解。


3. 算法二:树形动态规划(Tree DP)算法

树形 DP 是求解树的直径最普适的方法,它不仅支持负权边,还能轻松拓展到节点带权(点权)子树约束等各种复杂变种中。

3.1 核心状态设计

在树中,任意一条简单路径都可以唯一地被其最高转折点(即该路径两个端点的最近公共祖先 LCA)所标识。

任选树中一个节点作为根节点(例如节点 \(1\)\(0\)),后序遍历整棵树:

  1. \(d_1[u]\) 表示以节点 \(u\) 为根的子树中,\(u\) 向下延伸到叶子节点的最大深度(最长链长度)
  2. \(d_2[u]\) 表示以节点 \(u\) 为根的子树中,\(u\) 向下延伸且与最长链不属于同一子树分支次大深度(次长链长度)
  3. 对于当前节点 \(u\),穿过 \(u\) 且以 \(u\) 为最高顶点的最长简单路径长度显然等于: $\(L(u) = d_1[u] + d_2[u]\)$

  4. 全树的直径即为遍历所有节点作为最高转折点时的最大值: $\(D(T) = \max_{u \in V} (d_1[u] + d_2[u])\)$

graph TD
    U((转折点 u)) -->|最长链 d1[u]| V1((子树分支 1 ... 最深叶子))
    U -->|次长链 d2[u]| V2((子树分支 2 ... 次深叶子))
    style U fill:#f96,stroke:#333,stroke-width:2px

3.2 状态转移方程

在 DFS 遍历 \(u\) 的每一个子节点 \(v\)(边权为 \(w\))时,动态更新最大值与次大值:

设当前子节点贡献的链长为 \(cur = d_1[v] + w\)

  1. 先用已有的 \(d_1[u]\) 与新来的 \(cur\) 更新全局直径: $\(\text{diameter} = \max(\text{diameter}, d_1[u] + cur)\)$

  2. \(cur > d_1[u]\): $\(d_2[u] = d_1[u], \quad d_1[u] = cur\)$

  3. 否则若 \(cur > d_2[u]\): $\(d_2[u] = cur\)$

void dfs(int u, int p) {
    d1[u] = 0;
    d2[u] = 0;
    for (const auto &[v, w] : adj[u]) {
        if (v == p) continue;
        dfs(v, u);
        int cur = d1[v] + w;
        if (cur > d1[u]) {
            d2[u] = d1[u];
            d1[u] = cur;
        } else if (cur > d2[u]) {
            d2[u] = cur;
        }
    }
    max_diameter = std::max(max_diameter, d1[u] + d2[u]);
}

3.3 两种解法综合对比

算法指标 两次 DFS / BFS 算法 树形动态规划 (Tree DP)
时间复杂度 \(\mathcal{O}(V + E)\)(精确访问 \(2n\) 次) \(\mathcal{O}(V + E)\)(精确访问 \(n\) 次)
空间复杂度 \(\mathcal{O}(n)\) \(\mathcal{O}(n)\)(调用栈开销)
边权支持范围 仅限非负权边\(w \ge 0\) 支持任意实数边权(含负权)
点权支持能力 较繁琐(需转换边权) 极度天然支持(如 LC 124)
还原直径路径 极其容易(直接前驱回溯) 较繁琐(需额外记录转移来源)
树的中心提取 极度便捷(直接取路径中点) 需结合换根 DP 才能方便得到

4. 进阶拓扑性质与拓展应用

4.1 树的中心 (Tree Center) 与最小高度树

在无向图中,若我们希望选定一个节点作为树的根节点,使得整棵树的“最大高度”(即根节点的偏心距 \(\epsilon(\text{root})\))最小,这个最优的根节点即为树的中心

求解树中心的两种方法

  1. 方法一:直径中点法(基于两次 BFS)
  2. 运行两次 BFS 求出一条直径序列 \(path = [p, v_1, v_2, \dots, q]\),总边数为 \(D\)
  3. \(D\) 为偶数,中心节点为 \(path[D / 2]\)(唯一);
  4. \(D\) 为奇数,中心节点为 \(path[\lfloor D / 2 \rfloor]\)\(path[\lceil D / 2 \rceil]\)(共 2 个)。

  5. 方法二:拓扑剥洋葱法(类似 Kahn 算法)

  6. 将所有度数为 \(1\) 的叶子节点推入队列;
  7. 逐层向内剥除叶子,并将邻接点度数减 \(1\)
  8. 重复剪枝,直到最后剩余 \(1\) 个或 \(2\) 个节点,该剩余节点即为树的中心(LeetCode 310 标准解法)。

4.2 两棵树合并后的新直径定理

在题目 LeetCode 3203 - 合并两棵树后的最小直径 中:

给定两棵相互独立的树 \(T_1\)\(T_2\),各自的直径分别为 \(D_1\)\(D_2\)。我们可以在 \(T_1\) 中选定一个节点 \(u\),在 \(T_2\) 中选定一个节点 \(v\),并在 \(u\)\(v\) 之间添加一条权值为 \(1\) 的边将两棵树联结成一棵大树 \(T'\)。如何选点才能使得 \(T'\) 的直径最小

定理推导与最优策略

添加边 \((u, v)\) 后,新树 \(T'\) 中的最长路径只有三种可能来源:

  1. 完全位于树 \(T_1\) 内部:最大长度为 \(D_1\)
  2. 完全位于树 \(T_2\) 内部:最大长度为 \(D_2\)
  3. 跨越两棵树:最长路径经过跨树边 \((u, v)\),长度为 \(\epsilon_{T_1}(u) + 1 + \epsilon_{T_2}(v)\)

为了最小化跨树路径,我们必须使 \(\epsilon_{T_1}(u)\)\(\epsilon_{T_2}(v)\) 同时达到各自树内的最小值。根据若尔当定理:

  • 偏心距最小的节点即为树的中心节点,其最小偏心距为树的半径 \(R(T) = \lceil D / 2 \rceil\)

因此,最优策略是将两棵树的中心节点连接,合并后新树的最小直径为: $\(D_{\min}(T') = \max\left(D_1, \; D_2, \; \left\lceil \frac{D_1}{2} \right\rceil + \left\lceil \frac{D_2}{2} \right\rceil + 1\right)\)$


5. 现代 C++ 规范实现模板

下面给出封装完整的 TreeDiameter 模板,同时支持带权边求直径长度提取整条直径路径提取树的中心节点

#include <vector>
#include <algorithm>
#include <queue>

struct TreeDiameter {
    int n;
    struct Edge {
        int to;
        long long weight;
    };
    std::vector<std::vector<Edge>> adj;

    explicit TreeDiameter(int n) : n(n), adj(n + 1) {}

    void add_edge(int u, int v, long long w = 1) {
        adj[u].push_back({v, w});
        adj[v].push_back({u, w});
    }

    // 运行单源 BFS,返回 (最远点, 距离数组, 前驱父节点数组)
    struct BFSResult {
        int farthest_node;
        std::vector<long long> dist;
        std::vector<int> parent;
    };

    BFSResult bfs(int start_node) const {
        std::vector<long long> dist(n + 1, -1);
        std::vector<int> parent(n + 1, 0);
        std::queue<int> q;

        dist[start_node] = 0;
        q.push(start_node);

        int farthest_node = start_node;
        long long max_d = 0;

        while (!q.empty()) {
            int u = q.front();
            q.pop();

            if (dist[u] > max_d) {
                max_d = dist[u];
                farthest_node = u;
            }

            for (const auto &edge : adj[u]) {
                if (dist[edge.to] == -1) {
                    dist[edge.to] = dist[u] + edge.weight;
                    parent[edge.to] = u;
                    q.push(edge.to);
                }
            }
        }
        return {farthest_node, dist, parent};
    }

    // 求解直径,返回结构包含:长度、端点 u 与 v、直径完整路径节点
    struct DiameterResult {
        long long diameter;
        int u;
        int v;
        std::vector<int> path;
        std::vector<int> centers; // 树的中心节点 (1 或 2 个)
    };

    DiameterResult get_diameter() const {
        // 第一次搜索:从任意点 1 找到最远点 u
        BFSResult r1 = bfs(1);
        int u = r1.farthest_node;

        // 第二次搜索:从点 u 找到最远点 v
        BFSResult r2 = bfs(u);
        int v = r2.farthest_node;
        long long max_diameter = r2.dist[v];

        // 回溯还原路径:从 v 沿着 parent 指针反推至 u
        std::vector<int> path;
        int curr = v;
        while (curr != 0) {
            path.push_back(curr);
            if (curr == u) break;
            curr = r2.parent[curr];
        }
        std::reverse(path.begin(), path.end());

        // 计算中心点 (无权边意义下等分节点数量)
        std::vector<int> centers;
        int k = path.size();
        if (k % 2 == 1) {
            centers.push_back(path[k / 2]);
        } else {
            centers.push_back(path[k / 2 - 1]);
            centers.push_back(path[k / 2]);
        }

        return {max_diameter, u, v, path, centers};
    }
};

6. 精选 LeetCode 经典真题与解题提示

以下精选了 7 道覆盖二叉树、带负权点权、多叉树、拓扑剥叶、树合并及子树枚举的代表性真题:

1. LeetCode 543 - 二叉树的直径 (Diameter of Binary Tree) 简单

  • 题意简述:给定一棵二叉树,求任意两个节点之间最长路径的长度(以边数衡量)。
  • 算法核心
  • 二叉树树形 DP 经典入门题;
  • 后序遍历递归函数 maxDepth(node) 计算并返回当前节点向下的单侧最大深度;
  • 递归回溯时,用左子树深度与右子树深度之和 left + right 更新全局直径变量;
  • 向上返回当前节点的最大单侧深度 max(left, right) + 1
查看解题核心代码
class Solution {
    int max_diameter = 0;
    int maxDepth(TreeNode* root) {
        if (!root) return 0;
        int left = maxDepth(root->left);
        int right = maxDepth(root->right);
        max_diameter = std::max(max_diameter, left + right);
        return std::max(left, right) + 1;
    }
public:
    int diameterOfBinaryTree(TreeNode* root) {
        maxDepth(root);
        return max_diameter;
    }
};

2. LeetCode 124 - 二叉树中的最大路径和 (Binary Tree Maximum Path Sum) 困难

  • 题意简述:路径被定义为一条从树中任意节点出发,沿父子相连到达任意节点的序列。序列中至少包含一个节点,求路径中节点值的最大总和(节点权值可能为负数)。
  • 算法核心
  • 典型的带负权点权的树的直径问题,两次 BFS 贪心失效,必须使用树形 DP;
  • 递归函数计算每个子树能为父节点提供的单侧最大正贡献:若子树贡献为负数,则贪心放弃(与 \(0\)\(\max\));
  • 经过当前根节点的最大路径和为:root->val + left_gain + right_gain,用其维护全局答案。
查看解题核心代码
class Solution {
    int max_sum = INT_MIN;
    int maxGain(TreeNode* root) {
        if (!root) return 0;
        int left = std::max(0, maxGain(root->left));
        int right = std::max(0, maxGain(root->right));
        max_sum = std::max(max_sum, root->val + left + right);
        return root->val + std::max(left, right);
    }
public:
    int maxPathSum(TreeNode* root) {
        maxGain(root);
        return max_sum;
    }
};

3. LeetCode 1245 - 树的直径 (Tree Diameter) 中等

  • 题意简述:给定一棵无向无环树的边集列表 edges,求该树的直径(最长简单路径的边数)。
  • 算法核心
  • 标准多叉无向树直径模板题;
  • 既可以运行两次 BFS(先从节点 \(0\) 出发搜到最远点 \(p\),再从 \(p\) 出发搜到最远点 \(q\) 返回距离);
  • 也可以通过一次 DFS 树形 DP,维护每个节点子树的最大深度 \(d_1\) 和次大深度 \(d_2\)

4. LeetCode 310 - 最小高度树 (Minimum Height Trees) 中等

  • 题意简述:选定树中某一个节点作为根,使得整棵树的高度最小。找出所有能够使得树高度最小的根节点列表。
  • 算法核心
  • 本质是求解树的中心节点 (Tree Center)
  • 解法一(拓扑剥洋葱):维护各节点度数,初始时将所有度数为 \(1\) 的叶子节点入队,逐轮向内剥除,直到全图仅剩 \(1\) 个或 \(2\) 个节点即为答案;
  • 解法二(直径中点法):用两次 BFS 求出任一条直径的完整节点路径,取正中间的 \(1\) 个或 \(2\) 个节点返回。
查看解题核心代码 (拓扑剥洋葱法)
class Solution {
public:
    std::vector<int> findMinHeightTrees(int n, std::vector<std::vector<int>>& edges) {
        if (n == 1) return {0};
        std::vector<std::vector<int>> adj(n);
        std::vector<int> deg(n, 0);
        for (const auto &e : edges) {
            adj[e[0]].push_back(e[1]);
            adj[e[1]].push_back(e[0]);
            deg[e[0]]++;
            deg[e[1]]++;
        }

        std::queue<int> q;
        for (int i = 0; i < n; ++i) {
            if (deg[i] == 1) q.push(i);
        }

        int remaining = n;
        while (remaining > 2) {
            int sz = q.size();
            remaining -= sz;
            for (int i = 0; i < sz; ++i) {
                int u = q.front();
                q.pop();
                for (int v : adj[u]) {
                    if (--deg[v] == 1) {
                        q.push(v);
                    }
                }
            }
        }

        std::vector<int> ans;
        while (!q.empty()) {
            ans.push_back(q.front());
            q.pop();
        }
        return ans;
    }
};

5. LeetCode 2246 - 相邻字符不同的最长路径 (Longest Path With Different Adjacent Characters) 困难

  • 题意简述:给定一棵多叉有根树,每个节点分配有一个小写英文字母。求树中一条最长简单路径,使得路径上相邻两个节点的字符都不相同。返回路径包含的节点总数。
  • 算法核心
  • 带有字符约束的树形 DP 直径模型;
  • 递归遍历子节点 \(v\):只有当 \(s[u] \neq s[v]\) 时,子节点的分支链才能有效延伸至父节点 \(u\)
  • 维护有效分支的最大链长 \(d_1\) 和次大链长 \(d_2\)
  • 穿过节点 \(u\) 的最长合法路径节点数为 \(d_1 + d_2 + 1\),全局取 \(\max\)

6. LeetCode 3203 - 合并两棵树后的最小直径 (Find Minimum Diameter After Merging Two Trees) 困难

  • 题意简述:给定两棵独立的无向无环树,添加恰好一条边连接两棵树,求连接后新树直径的最小可能值。
  • 算法核心
  • 直径合并理论的完美考察(定理 2 与树中心定理);
  • 分别求出两棵树的原直径 \(D_1\)\(D_2\)
  • 两树各选其中心节点进行连接,跨树的最长链为 \(\lceil D_1 / 2 \rceil + \lceil D_2 / 2 \rceil + 1\)
  • 最终答案为 \(\max\left(D_1, \; D_2, \; \lfloor (D_1 + 1) / 2 \rfloor + \lfloor (D_2 + 1) / 2 \rfloor + 1\right)\)
查看解题核心代码
class Solution {
    int getDiameter(const std::vector<std::vector<int>>& edges) {
        int n = edges.size() + 1;
        std::vector<std::vector<int>> adj(n);
        for (const auto &e : edges) {
            adj[e[0]].push_back(e[1]);
            adj[e[1]].push_back(e[0]);
        }

        auto bfs = [&](int start) -> std::pair<int, int> {
            std::vector<int> dist(n, -1);
            std::queue<int> q;
            dist[start] = 0;
            q.push(start);
            int farthest = start;
            while (!q.empty()) {
                int u = q.front();
                q.pop();
                if (dist[u] > dist[farthest]) farthest = u;
                for (int v : adj[u]) {
                    if (dist[v] == -1) {
                        dist[v] = dist[u] + 1;
                        q.push(v);
                    }
                }
            }
            return {farthest, dist[farthest]};
        };

        int u = bfs(0).first;
        return bfs(u).second;
    }
public:
    int minimumDiameterAfterMerge(std::vector<std::vector<int>>& edges1, std::vector<std::vector<int>>& edges2) {
        int d1 = getDiameter(edges1);
        int d2 = getDiameter(edges2);
        int r1 = (d1 + 1) / 2;
        int r2 = (d2 + 1) / 2;
        return std::max({d1, d2, r1 + r2 + 1});
    }
};

7. LeetCode 1617 - 统计子树中城市之间最大距离 (Count Subtrees With Max Distance Between Cities) 困难

  • 题意简述:给定包含 \(n \le 15\) 个节点的树。对于每个 \(d \in [1, n - 1]\),统计满足以下条件的非空子图数量:该子图是原树的一棵子树,且子树中所有节点对之间的最大距离恰好等于 \(d\)
  • 算法核心
  • 数据范围 \(n \le 15\),强力提示二进制状态压缩:枚举所有 \(2^n - 1\) 个非空子集;
  • 对于每个状态掩码 \(mask\)
    1. 连通性校验:利用 BFS 遍历验证该子集导出的子图是否连通(且边数等于点数减 \(1\));
    2. 求子图直径:在子树内部通过两次 BFS 或树形 DP 求出子树的直径 \(D\)
    3. 若连通且直径为 \(D\),则将计数器 ans[D - 1]\(1\)