题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 个顶点的完全距离矩阵 \(A_{i,j}\),判断是否存在一棵带权无向树,使得树上任意两点的距离恰好等于 \(A_{i,j}\)。这是一个树的构造与验证问题,核心在于利用树的度量性质(四点条件、三角不等式等)先尝试构造一棵树,再验证其距离矩阵是否匹配。
-
算法选择:
- 贪心构造 + DFS 验证:以顶点 \(1\) 为根,对每个其他顶点 \(i\),寻找满足 \(A_{1,i} = A_{1,j} + A_{j,i}\) 且 \(A_{1,j}\) 最大的顶点 \(j\) 作为父节点,边权为 \(A_{1,i} - A_{1,j}\)。构造完成后,对每个顶点作为根跑 DFS 计算距离,验证是否与输入矩阵一致
-
关键步骤:
- 读入数据:\(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
-
时间/空间复杂度:
- 时间复杂度:\(O(N^3)\),构造树 \(O(N^2)\),验证阶段 \(N\) 次 DFS 各 \(O(N^2)\)
- 空间复杂度:\(O(N^2)\),存储距离矩阵和邻接表
-
树构造与度量验证的核心思想:
- 树的度量特征:在树中,\(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
浙公网安备 33010602011771号