1 条题解
-
0
「POI2018 R1」地铁路线图 Subway design 题解
题目大意
给定 个地铁站,编号 到 ,其中 号是火车站, 号是机场。已知:
- :从火车站到 号站的最短距离()
- :从机场到 号站的最短距离()
地铁网络是一棵树( 条边,连通无环)。要求判断是否存在满足距离约束的树,如果存在输出任意一种方案。
核心观察
1. 关键等式
设 为火车站到机场的距离。
对于任意站 ():
- 如果 在 的路径上,则
- 如果 不在 的路径上,则
证明:在树中, 的路径长度是 ,而 有唯一最短路径 。如果 在这条路径上,则路径就是 ;否则需要绕路,距离更大。
2. 挂接点的性质
对于不在路径上的点 ,设它挂在路径上的点 上,则有:
- ( 是挂接边的长度)
两式相减得:
这意味着:不在路径上的点必须与路径上某个点的 值相同。
算法步骤
步骤 1:确定
计算
如果找不到这样的 (即 时没有中间点),则 可以是任意正数,通常取 。
步骤 2:找出路径上的点
路径上的点满足 ,包括:
- 站 :
- 站 :
- 中间站 :
将这些点按 值从小到大排序,就得到路径顺序 。
步骤 3:检查路径合法性
- 值必须严格递增
- 值必须严格递减(因为 )
- 相邻点间的边权 必须是正整数
步骤 4:处理不在路径上的点
对于每个不在路径上的点 :
- 计算
- 在路径上寻找满足 且 的点
- 如果找到,计算边权 ,必须
- 添加边
步骤 5:最终检查
- 必须有恰好 条边
- 所有边权为正整数
- 图连通(构造方法保证)
正确性证明
必要性
如果存在满足条件的树,那么:
- 必须等于某个 的最小值(路径上的点)
- 路径点按 排序后必须严格递增
- 挂接点必须满足 且
充分性
按照上述方法构造的树:
- 路径部分保证了 的距离为
- 挂接部分保证了每个点 到 和 的距离正确
- 树结构保证了距离的唯一性
复杂度分析
- 排序路径点:
- 处理挂接点:(使用 map)
- 总复杂度:,可处理
代码实现细节
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 500005; int n; ll d[MAXN], l[MAXN]; bool on_path[MAXN]; vector<pair<ll, int>> path_nodes; // (d[i], i) map<ll, int> path_by_diff; // d[u] - l[u] -> 最大的d[u]对应的u struct Edge { int a, b; ll c; }; vector<Edge> edges; int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n; for (int i = 2; i <= n-1; i++) cin >> d[i]; for (int i = 2; i <= n-1; i++) cin >> l[i]; // 特殊情况处理 if (n == 2) { cout << "TAK\n"; cout << "1 2 1\n"; return 0; } // 步骤1:计算D ll D = 1e18; for (int i = 2; i <= n-1; i++) { D = min(D, d[i] + l[i]); } // 步骤2:找出路径上的点 d[1] = 0; l[1] = D; d[n] = D; l[n] = 0; for (int i = 1; i <= n; i++) { if (i >= 2 && i <= n-1) { if (d[i] + l[i] == D) { path_nodes.emplace_back(d[i], i); on_path[i] = true; } } else { path_nodes.emplace_back(d[i], i); on_path[i] = true; } } // 步骤3:检查路径合法性 sort(path_nodes.begin(), path_nodes.end()); // 检查d值严格递增 for (int i = 1; i < (int)path_nodes.size(); i++) { if (path_nodes[i].first == path_nodes[i-1].first) { cout << "NIE\n"; return 0; } } // 检查首尾 if (path_nodes[0].second != 1 || path_nodes.back().second != n) { cout << "NIE\n"; return 0; } // 构建路径边 for (int i = 1; i < (int)path_nodes.size(); i++) { int u = path_nodes[i-1].second; int v = path_nodes[i].second; ll w = path_nodes[i].first - path_nodes[i-1].first; if (w <= 0) { cout << "NIE\n"; return 0; } edges.push_back({u, v, w}); } // 记录路径上各diff对应的最大d的点 for (auto& p : path_nodes) { int u = p.second; ll diff = d[u] - l[u]; if (path_by_diff.find(diff) == path_by_diff.end() || d[path_by_diff[diff]] < d[u]) { path_by_diff[diff] = u; } } // 步骤4:处理挂接点 for (int i = 2; i <= n-1; i++) { if (!on_path[i]) { ll diff = d[i] - l[i]; if (path_by_diff.find(diff) == path_by_diff.end()) { cout << "NIE\n"; return 0; } int p = path_by_diff[diff]; if (d[p] >= d[i]) { cout << "NIE\n"; return 0; } ll w = d[i] - d[p]; if (w != l[i] - l[p] || w <= 0) { cout << "NIE\n"; return 0; } edges.push_back({p, i, w}); } } // 步骤5:最终检查 if ((int)edges.size() != n-1) { cout << "NIE\n"; return 0; } // 输出结果 cout << "TAK\n"; for (auto& e : edges) { cout << e.a << " " << e.b << " " << e.c << "\n"; } return 0; }样例分析
样例输入
7 6 6 2 2 1 5 3 5 1 4处理过程
- 计算 $D = \min(6+5, 6+3, 2+5, 2+1, 1+4) = \min(11,9,7,3,5) = 3$
- 路径点:满足 的点是 和 等,加上站1和站7
- 构建路径,挂接其他点
- 输出6条边构成树
总结
本题的关键在于利用树的路径结构特性,通过距离约束反推拓扑结构。这种"主路径+挂接点"的构造方法在树形问题中很常见,掌握后可以解决许多类似题目。
算法的正确性依赖于树中距离关系的严格数学性质,通过逐步验证这些性质,可以保证构造出的解满足所有约束条件。
- 1
信息
- ID
- 3300
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 9
- 已通过
- 1
- 上传者