AcWing 342 道路與航線
阿新 • • 發佈:2022-03-16
分析:
本題解題邏輯比較複雜,但是一旦理順了思路,也是可以很快\(AC\)的。首先分析下題意,城鎮之間有兩種路徑,雙向、邊權非負的道路,以及單向、邊權可能是負數的航線,並且航線不存在環。抽象成圖模型就是有兩類邊,正權的雙向邊和可以是負權的單向邊,若存在從\(a\)到\(b\)的單向邊,則\(b\)不可能通過一些單向邊或者雙向邊到達\(a\)。如果只當普通的含負權邊的最短路問題,只需要用\(spfa\)演算法就可以求解,但是本題測試資料會卡掉\(spfa\),卡成\(O(nm)\)後,\(25000*150000\)(因為雙向邊,所以是兩倍,而航線還可能有\(50000\),所以是\(150000\)),顯然會超時,因此需要採取更加高效的解法去求解。
SPFA寫法
#include <bits/stdc++.h> using namespace std; const int N = 25005, M = 150005, INF = 0x3f3f3f3f; typedef pair<int, int> PII; /* SPFA直接幹,結果 通過了 14/16個數據 */ //存圖 int idx, h[N], e[M], w[M], ne[M]; void add(int a, int b, int c) { e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++; } int T; // 城鎮數量 int R; // 道路數量 int P; // 航線數量 int S; // 出發點 int dist[N]; //從A點出發,到達每個點的最大距離 bool st[N]; //點i是不是已經進入佇列 //從start出發 void spfa(int start) { queue<int> q; dist[start] = 0; q.push(start); st[start] = true; //標識在佇列中 while (q.size()) { int u = q.front(); //取出當前要處理的節點 q.pop(); st[u] = false; // u節點出佇列 //列舉u節點的每一條出邊 for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[u] + w[i]) { dist[j] = dist[u] + w[i]; if (!st[j]) { st[j] = true; q.push(j); } } } } } int main() { //初始化鄰接表表頭 memset(h, -1, sizeof h); //初始化最短距離 memset(dist, 0x3f, sizeof dist); //城鎮數量,道路數量,航線數量,出發點 cin >> T >> R >> P >> S; dist[S] = 0; //出發點距離自己的長度是0 int a, b, c; //讀入道路 while (R--) { cin >> a >> b >> c; add(a, b, c), add(b, a, c); //城鎮是無向圖 } //處理完無向圖,再來考慮有向邊,航線 while (P--) { cin >> a >> b >> c; add(a, b, c); } spfa(S); //從S到達城鎮i的最小花費 for (int i = 1; i <= T; i++) { if (dist[i] == INF) printf("NO PATH\n"); else printf("%d\n", dist[i]); } return 0; }
首先講下預備知識,我們知道,拓撲排序可以求一個\(DAG\)(有向無環圖)的拓撲序列,從而確定任務完成的先後關係,但是容易忽略的是拓撲排序也可以求\(DAG\)的最短路徑長度,不管存不存在負權邊。
證明也很簡單,考慮一般的數學歸納法即可證明,假設一個點的前驅節點離起點的最短距離都確定了,則這個節點離起點的最短距離可以通過前驅結點加上到該節點的邊權的最小值決定。邊界情況是起點的入度為\(0\)時,其後繼節點的最短路就可以直接通過比較確定下來。雖然本題並沒有用這種辦法去求\(DAG\)的最短路徑,但是節點最短路的求解順序卻是按照拓撲序來的。