题目描述
骑士们需要带着自己的马参加锦标赛。从一个地方到另一个地方可能需要多天时间,但马匹在一天内行走超过一定距离后会拒绝继续前进。骑士需要选择一条路线,使得路线中单日行走的最大距离尽可能小。
给定 NNN 个地点和 RRR 条双向道路,每条道路有长度。对于每个查询 (k,t)(k, t)(k,t),求从 kkk 到 ttt 的所有路径中,路径上最大边权的最小值。
题目分析
问题转化
这个问题可以转化为:在无向连通图中,对于任意两个顶点 kkk 和 ttt,找到一条从 kkk 到 ttt 的路径,使得该路径上的最大边权最小,并输出这个最小的最大值。
关键观察
这个问题的解有一个重要性质:最小生成树(MST\texttt{MST}MST)中任意两点之间的路径上的最大边权,就是所有路径中最大边权的最小值。
为什么?
- 在 MST\texttt{MST}MST 中,任意两点之间的路径是唯一的
- 如果存在另一条路径的最大边权更小,那么我们可以用这条路径替换 MST\texttt{MST}MST 中的某些边,得到更小的生成树,这与 MST\texttt{MST}MST 的定义矛盾
算法选择
-
构建最小生成树:使用 Kruskal\texttt{Kruskal}Kruskal 算法
- 时间复杂度:O(RlogR)O(R \log R)O(RlogR)
- 空间复杂度:O(N+R)O(N + R)O(N+R)
-
处理查询:在 MST\texttt{MST}MST 上执行 DFS/BFS\texttt{DFS/BFS}DFS/BFS
- 对每个查询 (k,t)(k, t)(k,t),在 MST\texttt{MST}MST 上找到从 kkk 到 ttt 的路径,并记录路径上的最大边权
- 时间复杂度:O(Q⋅N)O(Q \cdot N)O(Q⋅N),在题目约束下可行
复杂度分析
- N≤3000N \leq 3000N≤3000,R≤100000R \leq 100000R≤100000,Q≤1000Q \leq 1000Q≤1000
- Kruskal\texttt{Kruskal}Kruskal 算法:O(RlogR)≈1.7×106O(R \log R) \approx 1.7 \times 10^6O(RlogR)≈1.7×106 操作
- 查询处理:最坏 O(Q⋅N)≈3×106O(Q \cdot N) \approx 3 \times 10^6O(Q⋅N)≈3×106 操作
- 总复杂度在可接受范围内
解题思路详解
步骤 1:构建最小生成树
使用 Kruskal\texttt{Kruskal}Kruskal 算法:
- 将所有边按权重升序排序
- 初始化并查集,每个节点自成一个集合
- 遍历排序后的边,如果边的两个端点不在同一集合中,则将该边加入 MST\texttt{MST}MST,并合并两个集合
步骤 2:构建 MST\texttt{MST}MST 的邻接表
将 MST 中的边构建成无向图的邻接表形式,便于后续的 DFS\texttt{DFS}DFS 遍历。
步骤 3:处理查询
对于每个查询 (k,t)(k, t)(k,t):
- 从起点 kkk 开始 DFS\texttt{DFS}DFS 遍历
- 记录从 kkk 到当前节点的路径上的最大边权
- 当到达目标节点 ttt 时,返回路径上的最大边权
步骤 4:输出结果
按要求格式输出每个测试用例的结果。
代码实现
// Bring Your Own Horse
// UVa ID: 12176
// Verdict: Accepted
// Submission Date: 2025-11-19
// UVa Run Time: 0.010s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
struct Edge {
int u, v, w;
bool operator<(const Edge& other) const {
return w < other.w;
}
};
vector<int> parent;
int findSet(int x) {
if (parent[x] != x)
parent[x] = findSet(parent[x]);
return parent[x];
}
bool unionSet(int a, int b) {
a = findSet(a);
b = findSet(b);
if (a == b) return false;
parent[b] = a;
return true;
}
vector<vector<pair<int, int>>> mstGraph;
void dfs(int u, int p, int target, int maxSoFar, vector<int>& visited, int& result) {
if (u == target) {
result = maxSoFar;
return;
}
for (auto& e : mstGraph[u]) {
int v = e.first, w = e.second;
if (v == p || visited[v]) continue;
visited[v] = 1;
dfs(v, u, target, max(maxSoFar, w), visited, result);
if (result != -1) return;
}
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
for (int caseNum = 1; caseNum <= T; ++caseNum) {
int N, R;
cin >> N >> R;
vector<Edge> edges(R);
for (int i = 0; i < R; ++i)
cin >> edges[i].u >> edges[i].v >> edges[i].w;
sort(edges.begin(), edges.end());
parent.resize(N + 1);
for (int i = 1; i <= N; ++i) parent[i] = i;
mstGraph.assign(N + 1, vector<pair<int, int>>());
for (const auto& e : edges) {
if (unionSet(e.u, e.v)) {
mstGraph[e.u].emplace_back(e.v, e.w);
mstGraph[e.v].emplace_back(e.u, e.w);
}
}
int Q;
cin >> Q;
cout << "Case " << caseNum << "\n";
while (Q--) {
int k, t;
cin >> k >> t;
vector<int> visited(N + 1, 0);
visited[k] = 1;
int result = -1;
dfs(k, -1, t, 0, visited, result);
cout << result << "\n";
}
cout << "\n";
}
return 0;
}
算法优化空间
- LCA\texttt{LCA}LCA 预处理:可以预处理 MST\texttt{MST}MST 的 LCA\texttt{LCA}LCA(最近公共祖先),从而在 O(logN)O(\log N)O(logN) 时间内回答每个查询
- 路径压缩:使用更高效的数据结构来存储和查询路径上的最大值
总结
本题的关键在于将问题转化为最小生成树上的路径最大值问题。通过利用 MST\texttt{MST}MST 的性质,我们能够高效地解决这个看似复杂的最优化问题。这种"最小化最大值"或"最大化最小值"的问题在图中经常可以通过 MST\texttt{MST}MST 来解决,这是一个重要的解题技巧。
2205

被折叠的 条评论
为什么被折叠?



