1 条题解

  • 0
    @ 2025-10-28 11:22:51

    题目大意

    给定一个有向图,nn 个点 mm 条边,边权为正整数。
    再给定 kk 个特殊点,要求找出这 kk 个特殊点之间两两最短距离的最小值。

    注意:是有向图,aabbbbaa 的路径长度可能不同,也可能不存在路径。


    样例分析

    样例1

    6 7 3
    1 5 3
    2 3 5
    1 4 3
    5 3 2
    4 6 5
    4 3 7
    5 6 4
    1 3 6
    

    特殊点:1,3,61, 3, 6

    计算:

    • 131 \to 3:路径 1531\to 5\to 3,长度 3+2=53+2=5
    • 161 \to 6:路径 1461\to 4\to 6,长度 3+5=83+5=8(或 1561\to 5\to 6 长度 3+4=73+4=7
    • 313 \to 1:无路径
    • 363 \to 6:无路径
    • 616 \to 1:无路径
    • 636 \to 3:无路径

    最近点对是 (1,3)(1,3),距离 55


    思路分析

    1. 暴力做法(不可行)

    直接枚举 kk 个点的所有点对,对每个点对跑单源最短路。
    复杂度:O(kmlogn)O(k \cdot m\log n),当 kk 很大时不可行。

    2. 多源最短路优化

    我们并不需要所有点对,只需要找到最小值。可以利用这个性质进行优化。

    关键观察:最近的特殊点对 (u,v)(u,v) 一定满足 uuvv 在某个最短路树中相邻(在路径上相邻)。


    标准解法:多源 Dijkstra

    算法步骤

    1. 建立一个超级源点 SS,连接到所有 kk 个特殊点,边权为 00
    2. SS 开始跑 Dijkstra
    3. 当扩展到一个特殊点 uu 时,记录它是从哪个特殊点 vv 扩展过来的
    4. 如果 uuvv 是不同的特殊点,那么 (v,u)(v,u) 就是一个候选的最近点对

    原理
    在 Dijkstra 算法中,当我们第一次遇到一个特殊点 uu 时,它一定是离源点 SS 最近的特殊点之一。
    如果 uu 是从另一个特殊点 vv 扩展过来的,那么 vvuu 的路径就是当前已知的最短路径之一。


    具体实现细节

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef pair<ll, int> pli;
    
    const ll INF = 2e18;
    
    ll solve() {
        int n, m, k;
        cin >> n >> m >> k;
        
        vector<vector<pair<int, int>>> g(n + 1);
        for (int i = 0; i < m; i++) {
            int u, v, w;
            cin >> u >> v >> w;
            g[u].emplace_back(v, w);
        }
        
        vector<int> special(k);
        for (int i = 0; i < k; i++) {
            cin >> special[i];
        }
        
        ll ans = INF;
        
        // 从超级源点S=0开始
        vector<ll> dist(n + 1, INF);
        vector<int> from(n + 1, -1);  // 记录是从哪个特殊点扩展来的
        
        priority_queue<pli, vector<pli>, greater<pli>> pq;
        
        // 初始化:所有特殊点加入队列
        for (int i = 0; i < k; i++) {
            int u = special[i];
            dist[u] = 0;
            from[u] = u;  // 从自己扩展来
            pq.emplace(0, u);
        }
        
        while (!pq.empty()) {
            auto [d, u] = pq.top();
            pq.pop();
            
            if (d != dist[u]) continue;
            
            // 如果u是特殊点,且不是第一次被扩展(即从其他特殊点扩展来)
            if (from[u] != -1 && from[u] != u) {
                // 这里找到了一对特殊点 (from[u], u),距离为d
                ans = min(ans, d);
            }
            
            for (auto [v, w] : g[u]) {
                ll nd = d + w;
                if (nd < dist[v]) {
                    dist[v] = nd;
                    from[v] = from[u];  // 继承来源特殊点
                    pq.emplace(nd, v);
                }
            }
        }
        
        return ans == INF ? -1 : ans;
    }
    

    算法正确性证明

    设最近的特殊点对是 (A,B)(A,B),距离为 dd
    在 Dijkstra 执行过程中,当扩展到 BB 时:

    • 如果 BB 是从 AA 扩展过来的,那么我们会发现这对点,距离为 dd
    • 如果 BB 是从其他点 CC 扩展过来的,那么 dist[C]+w(C,B)ddist[C] + w(C,B) \le d,且 CC 也是特殊点,这与 (A,B)(A,B) 是最近点对矛盾

    因此算法一定能找到最近的特殊点对。


    复杂度分析

    • 时间复杂度:O(mlogn)O(m\log n),与普通 Dijkstra 相同
    • 空间复杂度:O(n+m)O(n + m)

    这比暴力 O(kmlogn)O(k \cdot m\log n) 优化了很多。


    注意事项

    1. 有向图:路径是单向的,ABA\to BBAB\to A 要分开考虑
    2. 无穷大处理:如果所有特殊点对都不可达,需要特殊处理
    3. 边权范围z2×109z \le 2\times 10^9,要用 long long 存储距离

    样例验证

    样例2

    7 7 4
    5 3 10
    6 2 7
    1 2 6
    5 4 2
    4 3 4
    1 7 3
    7 2 4
    1 2 5 3
    

    特殊点:1,2,5,31, 2, 5, 3

    计算发现:

    • 121 \to 2:路径 1721\to 7\to 2,长度 3+4=73+4=7;或 121\to 2 直接长度 66
    • 535 \to 3:路径 5435\to 4\to 3,长度 2+4=62+4=6

    最近距离是 66


    总结

    本题的关键在于:

    1. 识别出这是有向图多源最短路问题
    2. 利用 Dijkstra 的性质,在扩展过程中直接检测最近点对
    3. 通过超级源点技巧,将多源问题转化为单源问题

    这种解法优雅地避免了暴力枚举,将复杂度从 O(kmlogn)O(k\cdot m\log n) 降为 O(mlogn)O(m\log n),能够处理大规模数据。

    • 1

    信息

    ID
    4445
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者