UVa 12176 Bring Your Own Horse

题目描述

骑士们需要带着自己的马参加锦标赛。从一个地方到另一个地方可能需要多天时间,但马匹在一天内行走超过一定距离后会拒绝继续前进。骑士需要选择一条路线,使得路线中单日行走的最大距离尽可能小。

给定 NNN 个地点和 RRR 条双向道路,每条道路有长度。对于每个查询 (k,t)(k, t)(k,t),求从 kkkttt 的所有路径中,路径上最大边权的最小值

题目分析

问题转化

这个问题可以转化为:在无向连通图中,对于任意两个顶点 kkkttt,找到一条从 kkkttt 的路径,使得该路径上的最大边权最小,并输出这个最小的最大值。

关键观察

这个问题的解有一个重要性质:最小生成树(MST\texttt{MST}MST)中任意两点之间的路径上的最大边权,就是所有路径中最大边权的最小值

为什么?

  • MST\texttt{MST}MST 中,任意两点之间的路径是唯一的
  • 如果存在另一条路径的最大边权更小,那么我们可以用这条路径替换 MST\texttt{MST}MST 中的某些边,得到更小的生成树,这与 MST\texttt{MST}MST 的定义矛盾

算法选择

  1. 构建最小生成树:使用 Kruskal\texttt{Kruskal}Kruskal 算法

    • 时间复杂度:O(Rlog⁡R)O(R \log R)O(RlogR)
    • 空间复杂度:O(N+R)O(N + R)O(N+R)
  2. 处理查询:在 MST\texttt{MST}MST 上执行 DFS/BFS\texttt{DFS/BFS}DFS/BFS

    • 对每个查询 (k,t)(k, t)(k,t),在 MST\texttt{MST}MST 上找到从 kkkttt 的路径,并记录路径上的最大边权
    • 时间复杂度:O(Q⋅N)O(Q \cdot N)O(QN),在题目约束下可行

复杂度分析

  • N≤3000N \leq 3000N3000R≤100000R \leq 100000R100000Q≤1000Q \leq 1000Q1000
  • Kruskal\texttt{Kruskal}Kruskal 算法:O(Rlog⁡R)≈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(QN)3×106 操作
  • 总复杂度在可接受范围内

解题思路详解

步骤 1:构建最小生成树

使用 Kruskal\texttt{Kruskal}Kruskal 算法:

  1. 将所有边按权重升序排序
  2. 初始化并查集,每个节点自成一个集合
  3. 遍历排序后的边,如果边的两个端点不在同一集合中,则将该边加入 MST\texttt{MST}MST,并合并两个集合

步骤 2:构建 MST\texttt{MST}MST 的邻接表

将 MST 中的边构建成无向图的邻接表形式,便于后续的 DFS\texttt{DFS}DFS 遍历。

步骤 3:处理查询

对于每个查询 (k,t)(k, t)(k,t)

  1. 从起点 kkk 开始 DFS\texttt{DFS}DFS 遍历
  2. 记录从 kkk 到当前节点的路径上的最大边权
  3. 当到达目标节点 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;
}

算法优化空间

  1. LCA\texttt{LCA}LCA 预处理:可以预处理 MST\texttt{MST}MSTLCA\texttt{LCA}LCA(最近公共祖先),从而在 O(log⁡N)O(\log N)O(logN) 时间内回答每个查询
  2. 路径压缩:使用更高效的数据结构来存储和查询路径上的最大值

总结

本题的关键在于将问题转化为最小生成树上的路径最大值问题。通过利用 MST\texttt{MST}MST 的性质,我们能够高效地解决这个看似复杂的最优化问题。这种"最小化最大值"或"最大化最小值"的问题在图中经常可以通过 MST\texttt{MST}MST 来解决,这是一个重要的解题技巧。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值