AcWing 345 牛站 【BellmanFord演算法,非正解】
阿新 • • 發佈:2022-03-23
- \(Bellman-Ford\)演算法求解經過不超過\(k\)條邊的最短路
- \(Bellman-Ford\)演算法求解經過恰好\(k\)條邊的最短路(可以重複經過)
一、解法I
#pragma GCC optimize(2) //累了就吸點氧吧~ #include <bits/stdc++.h> using namespace std; int id[1010]; const int N = 210; const int M = 1000010; int d[N], backup[N]; struct Edge { int a, b, c; } edges[M]; int n, k, m, S, E; int bellmanFord() { memset(d, 0x3f, sizeof(d)); d[S] = 0; for (int i = 0; i < k; i++) { memcpy(backup, d, sizeof(d)); //與最多經過k條邊這裡不同! memset(d, 0x3f, sizeof(d)); for (int j = 0; j < m; j++) { int a = edges[j].a, b = edges[j].b, c = edges[j].c; //與最多經過k條邊這裡不同! d[b] = min(d[b], backup[a] + c); d[a] = min(d[a], backup[b] + c); } } return d[E]; } int main() { cin >> k >> m >> S >> E; //開始節點為1號 if (!id[S]) id[S] = ++n; //結束結點為2號,為了防止起點和終點是一個,採用了判斷的辦法 if (!id[E]) id[E] = ++n; //修改原來S和E的離散化後的值 S = id[S], E = id[E]; // m條邊 for (int i = 0; i < m; i++) { int a, b, c; cin >> c >> a >> b; if (!id[a]) id[a] = ++n; if (!id[b]) id[b] = ++n; edges[i].a = id[a], edges[i].b = id[b], edges[i].c = c; } //上面的離散化簡直太牛B了! printf("%d", bellmanFord()); return 0; }
二、解法II
#pragma GCC optimize(2) //累了就吸點氧吧~ #include <bits/stdc++.h> using namespace std; const int N = 205, M = 105; //現在看來,這個程式碼寫的好垃圾啊,離散化用的不好。程式碼長 //而且,在用舊的號碼記錄到鄰接表中,然後再去修改,邏輯太長,垃圾! //相對於另外兩套程式碼,都是在使用hashTable或者unordered_map進行對映後 //再放入鄰接表(鄰接矩陣),就方便多了~ int m; //圖的邊數 int k; //恰好是k條邊 int s, t; //起點和終點 struct Edge { int a; //起點 int b; //終點 int w; //邊長 } edges[M]; int p[N]; //使用了哪些點,為了照顧後面的離散化,肯定要儘可能的把點都儲存起來, int tot; int dist[N], backup[N]; //通過STL將原來的x找到現在新的下標位置 int get(int x) { return lower_bound(p, p + tot, x) - p; } int bellmanFord() { //最多經過 k 條邊:在迴圈外初始化一次 //恰好經過 k 條邊:除在迴圈外初始化一次外 //還需要在第一層迴圈中memcpy(backup)後面再次初始化 memset(dist, 0x3f, sizeof dist); dist[s] = 0; //出發點 for (int i = 0; i < k; i++) { // 恰好是k條邊,迭代k次 /* 1、memset可以理解為是陣列有多大,就執行多少次(實際效能比這個強,可以這樣估算) 2、其實就算不離散化,直接上來就BellmanFord也是可以的,就是會TLE 3、為什麼會TLE呢?因為n最大是1000,點數最大是1e6,那麼將面對1000*1e6=1e9的時長 ,C++1秒過不了。 4、觀察到點數雖多,但邊數少,只有100條。滿打滿算邊兩頭的端點都不一樣的話,最多 也就200個點。 5、也就是說很多點沒有邊連著,是沒用的,可以考慮去掉這些無用的點,保留有邊的點。 6、去的辦法就是離散化,列舉邊,找出邊的兩個端點,然後對端點集合進行離散化。 7、離散化後,需要修改原來點與點之間的關聯關係,比如原來是1000,2000這兩個點之間 有一條長度為100的邊,現在1000對映成了1號點,2000對映成了2號點,需要修改為1到2有 一條長度為100的邊才對。 8、我們需對點進行離散化,所以需要把點放到一個數組中,在本程式碼中就是p陣列, point的意思。這裡使用的儲存圖的方式為按點+邊儲存方式,所有點儲存在p陣列中, 所有邊儲存在e陣列中,每一條邊包含三個資訊:起點,終點,長度。 這樣儲存的辦法,使得我們可以離散化掉無用的點。 BellmanFord演算法的存圖方式比較牛X(yxc大佬原話),用一個結構體Edge(u,v,w), 然後開一個數組就行了,隨便存,與之前學習的鄰接表不同。 原因很簡單:因為BellmanFord需要遍歷每一條邊,怎麼能快速遍歷每一條邊就是關鍵。 我們以前學習過的鄰接表,是按點做索引的,然後記錄這個點出發到達哪些點。 如果採用鄰接表,就需要列舉每個點,然後二次迴圈找出每條邊,就麻煩了,不如直接 以邊為索引來的快了。 9、本題還有一個重要問題:bellmon-ford 演算法解決的問題是最多經過k條邊的最短路, 而題目問的是恰好經過k條邊的最短路徑,怎麼解決呢? 答:bellmon-ford 每次強制轉移即可,即把新的陣列賦值為正無窮。 這樣每迭代一次陣列中i代表的一定是恰好走k步的步數。 這麼做的原理是什麼呢?什麼地方講過這個原理呢?是現想出來的嗎?不可能吧~ */ // 每次在處理dist陣列前備份一下到backup陣列 // 只用上一次的迭代結果,就不會發生串聯了。 // 上一次的迭代結果可以理解為在上一次x條邊限定的情況下的意思 // https://www.acwing.com/video/285/ // 第18分鐘講到了關於串聯的問題 memcpy(backup, dist, sizeof dist); memset(dist, 0x3f, sizeof dist); for (int j = 1; j <= m; j++) { //列舉m條邊 int a = edges[j].a, b = edges[j].b, w = edges[j].w; dist[b] = min(dist[b], backup[a] + w); dist[a] = min(dist[a], backup[b] + w); } } //計算在n條限定下的最短距離 return dist[t]; } int main() { cin >> k >> m >> s >> t; for (int i = 1; i <= m; i++) { int a, b, w; //一條邊的邊長以及構成邊的兩個點的編號 cin >> w >> a >> b; edges[i] = {a, b, w}; //記錄有哪些點,準備離散化去重 p[tot++] = a, p[tot++] = b; } //離散化 sort(p, p + tot); tot = unique(p, p + tot) - p; //列舉每條邊,將第i條邊的起點和終點轉化為離散化後的新序號 for (int i = 1; i <= m; i++) edges[i].a = get(edges[i].a), edges[i].b = get(edges[i].b); //將起點和終點也轉化為離散化後的新點號 s = get(s), t = get(t); //呼叫bellmanFord演算法 printf("%d\n", bellmanFord()); return 0; }