题解:洛谷 AT_abc451_e Tree Distance

【题目来源】

洛谷:AT_abc451_e [ABC451E] Tree Distance - 洛谷

【题目描述】

判断是否存在一棵有 \(N\) 个结点的无向带权树,满足以下条件:

  • 对于任意的整数 \(i\) 和 \(j\),其中 \(1 \le i < j \le N\),结点 \(i\) 到结点 \(j\) 的距离为 \(A_{i,j}\)。

这里,结点 \(i\) 到结点 \(j\) 的距离定义为连接这两个结点的唯一简单路径上所有边权的和。

【输入】

输入按以下格式从标准输入给出:

\(N\) \(A_{1,2}\) \(A_{1,3}\) \(\ldots\) \(A_{1,N}\) \(A_{2,3}\) \(\ldots\) \(A_{2,N}\) \(\vdots\) \(A_{N-1,N}\)

【输出】

如果存在满足条件的树,输出 Yes;否则输出 No。

【输入样例】

4
2 5 4
3 2
5

【输出样例】

Yes

【核心思想】

  1. 问题分析:给定 \(N\) 个顶点的完全距离矩阵 \(A_{i,j}\),判断是否存在一棵带权无向树,使得树上任意两点的距离恰好等于 \(A_{i,j}\)。这是一个树的构造与验证问题,核心在于利用树的度量性质(四点条件、三角不等式等)先尝试构造一棵树,再验证其距离矩阵是否匹配。

  2. 算法选择:

    • 贪心构造 + DFS 验证:以顶点 \(1\) 为根,对每个其他顶点 \(i\),寻找满足 \(A_{1,i} = A_{1,j} + A_{j,i}\) 且 \(A_{1,j}\) 最大的顶点 \(j\) 作为父节点,边权为 \(A_{1,i} - A_{1,j}\)。构造完成后,对每个顶点作为根跑 DFS 计算距离,验证是否与输入矩阵一致
  3. 关键步骤:

    • 读入数据:\(N\) 和上三角距离矩阵 \(A_{i,j}\),对称填充完整矩阵
    • 构造树(\(i\) 从 \(2\) 到 \(N\)):
      • 寻找父节点 \(fa\):满足 \(A_{1,i} = A_{1,j} + A_{j,i}\) 且 \(A_{1,j}\) 最大的 \(j\)
      • 计算边权 \(w = A_{1,i} - A_{1,fa}\)
      • 若 \(w \leq 0\),输出 No 并结束
      • 添加双向边 \((fa, i, w)\)
    • 验证距离矩阵(\(i\) 从 \(1\) 到 \(N\)):
      • 从 \(i\) 开始 DFS 计算到所有点的距离 \(dis[j]\)
      • 若存在 \(dis[j] \neq A_{i,j}\),输出 No 并结束
    • 输出结果:全部验证通过则输出 Yes
  4. 时间/空间复杂度:

    • 时间复杂度:\(O(N^3)\),构造树 \(O(N^2)\),验证阶段 \(N\) 次 DFS 各 \(O(N^2)\)
    • 空间复杂度:\(O(N^2)\),存储距离矩阵和邻接表
  5. 树构造与度量验证的核心思想:

    • 树的度量特征:在树中,\(d(1, i) = d(1, j) + d(j, i)\) 当且仅当 \(j\) 在 \(1\) 到 \(i\) 的唯一路径上。利用这一性质,对每个 \(i\) 寻找路径上离 \(1\) 最近的点(即 \(A_{1,j}\) 最大的满足条件的 \(j\))作为父节点
    • 边权的非负性约束:树边权必须为正,因此 \(w = A_{1,i} - A_{1,fa} > 0\) 是必要条件。若 \(w \leq 0\) 则直接判定不存在
    • 验证的完备性:构造完成后必须验证整个距离矩阵,因为贪心构造只保证了以 \(1\) 为根的部分性质,其他点对间的距离可能不满足树的度量(如四点条件)
    • DFS 距离计算:树上两点距离 = 从根出发的距离之差,利用 DFS 从每个点作为根计算到所有点的距离,与输入矩阵逐位比对
    • 适用于树的度量空间判定、距离矩阵重构、图论验证类问题

【算法标签】

普及+ #DFS-图

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 3005;
int n, a[N][N];  // 输入的矩阵
struct Edge
{
    int v, w;  // 邻接点和边权
};
vector<Edge> e[N];  // 邻接表
int dis[N], vis[N];  // 距离和访问标记

// 深度优先搜索计算距离
void dfs(int u, int fa)
{
    vis[u] = 1;  // 标记已访问
    for (Edge edge : e[u])  // 遍历邻接点
    {
        if (edge.v == fa)  // 跳过父节点
        {
            continue;
        }
        dis[edge.v] = dis[u] + edge.w;  // 更新距离
        dfs(edge.v, u);  // 递归搜索
    }
}
int main()
{
    cin >> n;  // 输入矩阵大小
    for (int i = 1; i <= n; i++)
    {
        for (int j = i + 1; j <= n; j++)
        {
            cin >> a[i][j];  // 输入上三角
            a[j][i] = a[i][j];  // 对称赋值
        }
    }

    // 构建树结构
    for (int i = 2; i <= n; i++)  // 从节点2开始构建
    {
        int fa = 1;  // 初始父节点为1
        for (int j = 1; j <= n; j++)  // 寻找i的父节点
        {
            if (j == i)  // 跳过自身
            {
                continue;
            }
            // 满足树的性质:d(1,i) = d(1,j) + d(j,i)
            if (a[1][i] == a[1][j] + a[j][i] && a[1][j] > a[1][fa])
            {
                fa = j;  // 更新父节点
            }
        }
        int w = a[1][i] - a[1][fa];  // 计算边权
        if (w <= 0)  // 边权必须为正
        {
            cout << "No" << endl;
            return 0;
        }
        e[fa].push_back({i, w});  // 添加双向边
        e[i].push_back({fa, w});
    }

    // 验证距离矩阵
    for (int i = 1; i <= n; i++)  // 对每个节点作为根验证
    {
        memset(dis, 0, sizeof(dis));  // 重置距离
        memset(vis, 0, sizeof(vis));  // 重置访问标记
        dfs(i, 0);  // 从节点i开始DFS
        for (int j = 1; j <= n; j++)  // 验证距离
        {
            if (dis[j] != a[i][j])  // 距离不匹配
            {
                cout << "No" << endl;
                return 0;
            }
        }
    }
    cout << "Yes" << endl;  // 所有验证通过
    return 0;
}

【运行结果】

4
2 5 4
3 2
5
Yes
posted @ 2026-08-26 10:12  团爸讲算法  阅读(8)  评论(0)    收藏  举报