開啟章節選單

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';
  }
}