開啟章節選單

10740 Not the Best

題目連結

題目敘述

Abul 不是班上最優秀的學生,也不是隊上最出色的球員
這並不是說他不好;他其實真的不錯,但很不幸地,他不是最好的那一個

上個學期,我們這位「不太頂尖」的 Abul 上了一門演算法課
在其中一份作業中,他被要求找出加權有向圖中從某一給定頂點 x 到另一頂點 y 的最短路徑
你或許已經猜到了,他很少能找出最短路徑
他總是最終找到從 x 到 y 的第 k 短路徑(2 ≤ k ≤ 10)
如果他夠幸運,第 k 短的路徑與前面的所有路徑長度相同,那麼他的解答也會被認可

例如,在右側的圖中,Abul 被要求找出從頂點 5 到頂點 2 的最短路徑
從頂點 5 到頂點 2 的前 7 條最短路徑如下所示,按路徑長度遞增排序
對這個圖而言,Abul 能夠找到第 5 短的路徑,這條路徑可以是:

5 → 4 → 3 → 2 → 5 → 1 → 2,或 5 → 1 → 2 → 5 → 4 → 3 → 2

這兩條路徑的長度皆為 15

路徑長度
5 → 1 → 25
5 → 4 → 3 → 26
5 → 1 → 2 → 5 → 1 → 214
5 → 4 → 3 → 2 → 5 → 1 → 215
5 → 1 → 2 → 5 → 4 → 3 → 215
5 → 4 → 3 → 2 → 5 → 4 → 3 → 216
5 → 1 → 2 → 5 → 1 → 2 → 5 → 1 → 223

給定圖的描述、起點頂點 x、終點頂點 y 及值 k,你需要找出 Abul 所計算的路徑長度。你可以假設給定圖中 x 到 y 至少存在一條路徑。

輸入說明

輸入可能包含多組測資

每組測資的第一行包含兩個整數 n(2 ≤ n ≤ 100)與 m(1 ≤ m ≤ 1000)
分別代表圖中頂點的數量與邊的數量
圖中每個頂點以 [1, n] 之間的唯一整數標識

第二行包含三個整數 x、y 和 k(1 ≤ x, y ≤ 100, x ≠ y, 2 ≤ k ≤ 10)

接下來的 m 行中,每行包含三個整數 u、v 和 l(1 ≤ u, v ≤ 100, 0 ≤ l ≤ 10000)
表示從頂點 u 指向頂點 v 的一條長度為 l 的有向邊

當 n 與 m 為兩個 0 時,輸入結束

輸出說明

對於輸入中的每組測資,輸出一行整數,表示圖中第 k 短從 x 到 y 的路徑長度
如果從 x 到 y 的路徑少於 k 條,輸出 -1

解題思路

使用修改過的 Dijkstra 的算法,也可以說是有權重的 BFS
只是這次為了求第 k 短路徑,而非最短路徑
所以不對當前節點做一次性鎖定
而是對其標記次數,當目標節點經過了第 k 次,代表其為第 k 短路徑的經過
也不管是不是已經有更短的路徑
而是把所有能夠往下走訪的節點都 push 進 priority queue
並針對經過次數超過 k 次的路徑不再展開,直接跳過

程式碼

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define PII pair<int, int>

signed main() {
    int n, m;
    while (cin >> n >> m, n+m) {
        vector<vector<PII>> g(n+1); // {連結的節點, 兩點距離}
        int x, y, k; cin >> x >> y >> k; // x:起點 y:終點 k:目標為第幾短路徑
        while (m--) { // 建圖
            int a, b, w;
            cin >> a >> b >> w;
            g[a].push_back({b, w});
        }

        vector<int> vis(n+1);
        priority_queue<PII, vector<PII>, greater<PII>> pq; // {目前距離, 當前節點}
        pq.push({0, x});
        int ans = -1;
        while (pq.size()) {
            PII now = pq.top(); pq.pop();
            if (vis[now.second]++ > k) continue; // 剪枝
            if (now.second == y && vis[now.second] == k) { // 若當前節點為終點、經過次數為 k
                ans = now.first; // 記錄下答案
                break;
            }
            for (auto &i : g[now.second]) pq.push({now.first+i.second, i.first}); // 當前節點往下走訪
        }

        cout << ans << '\n';
    }
}