溫故知新 —— Floyd算法


什么?Floyd?sb O(n ^ 3) 算法早不用了,右上角紅叉吧。
我之前雖然也認識過 Floyd 算法的重要性,不過多少也是這么想的。
然而最近三天連續 rand 到了好幾道有關的題目,讓我徹底重新審視了 Floyd —— 既然能夠作為一個重要的算法流傳至今,那自有他的重要之處。

Floyd 是一個求解所有點對間的最短路算法,也可能是絕大多數人接觸的最早的最短路算法。它適用於無負權邊的圖,時間復雜度約為 O(n ^ 3) 。因為時間復雜度太高了,所以也是很多人起初都對它有些成見的原因,再加上任意點對間最短路用的又少,會誤認為這個算法后期就一無是處了。
眾所周知,Floyd 的本質是動態規划。對於每一對頂點 u 和 v,看看是否存在一個頂點 w 使得從 u 到 w 再到 v 比己知的路徑更短。如果是就更新它。正是由於這個動態規划思想的精髓所在,以及一層層更新的特性,使得 Floyd 大有用武之地。
廢話不多說了,反正四行的 Floyd 沒人不會……

萬惡之源那天,我正在 Luogu 愉快地隨機跳題,於是就 rand 到了 P2103 道路值守
很開心,這不就是個最短路計數?正巧前一天就 A 了一道類似的題,很快就敲出來准備 AC 了。結果嘛……
怎么辦,蒟蒻 Nanjo_Qi 瞬間就沒有思路了,於是只能求助題解。
結果是個用到 Floyd 的題目。

Floyd 的重要特性是:全面枚舉,有序更新。

Floyd題:https://www.luogu.org/problemnew/show/P2103
這個也是:https://www.luogu.org/problemnew/show/P1476
這個也是:https://www.luogu.org/problemnew/show/P1119

 

P2103 道路值守:

利用 Floyd 層層松弛更新,以及循環遍歷所有點對的特性,完全無需考慮時間復雜度;
首先求出所有點對間最短路,然后枚舉尋找那些可以作為方案數加入的點(另一條與最短路長度相等,或中途匯入最短路的道路,以此形成可行方案),分別在每個匯入點記錄,最后累計。

 1 #include <queue>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 using namespace std;
 6 
 7 const int maxn = 500 + 10;
 8 int n, m, g[maxn][maxn], dis[maxn][maxn], method[maxn][maxn];
 9 
10 int main(int argc, char const *argv[])
11 {
12   memset(dis, 0x3f, sizeof dis);
13   scanf("%d%d", &n, &m);
14   for(int i = 1; i <= m; ++i) {
15     int u = 0, v = 0, w = 0;
16     scanf("%d%d%d", &u, &v, &w);
17     dis[u][v] = dis[v][u] = g[u][v] = g[v][u] = w;
18   }
19   for(int i = 1; i <= n; ++i) dis[i][i] = 0;
20   for(int k = 1; k <= n; ++k)
21     for(int i = 1; i <= n; ++i)
22       for(int j = 1; j <= n; ++j)
23         dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
24 
25   for(int i = 1; i <= n; ++i) {
26     int tmp[maxn] = {0};
27     for(int j = 1; j <= n; ++j) if( i != j && dis[i][j] != dis[0][0] )
28       for(int k = 1; k <= n; ++k) if( g[k][j] )
29         if( dis[i][j] == dis[i][k] + g[k][j] ) ++tmp[j];
30 
31     for(int j = 1; j <= n; ++j) if( i != j )
32       for(int k = 1; k <= n; ++k)
33         if( dis[i][j] == dis[i][k] + dis[k][j] ) method[i][j] += tmp[k];
34   }
35 
36   for(int i = 1; i <= n; ++i)
37     for(int j = i + 1; j <= n; ++j)
38       printf("%d ", method[i][j]);
39 
40   // printf("_______________________________________________\n");
41   // printf("Process Exited Correctly With A Return Value 0.\n");
42   // printf("All Rights Reserved By Kimitsu Nanjo In 2018.\n\n");
43   return 0;
44 }
View Code

 

P1476 休息中的小呆:

求 1 到 n + 1 的最長路;
用 Dijkstra 不知道為什么跪在了記錄路徑上,還是用 Floyd 邊枚舉邊輸出。

 1 #include <queue>
 2 #include <cctype>
 3 #include <cstdio>
 4 #include <cstring>
 5 #include <algorithm>
 6 using namespace std;
 7 
 8 int n, m, dis[105][105];
 9 
10 int main(int argc, char const *argv[])
11 {
12   scanf("%d%d", &n, &m);
13   for(int i = 1; i <= m; ++i) {
14     int u = 1, v = 1, w = 1;
15     scanf("%d%d%d", &u, &v, &w);
16     dis[u][v] = w;
17   }
18   for(int k = 1; k <= n + 1; ++k)
19     for(int i = 1; i <= n + 1; ++i)
20       for(int j = 1; j <= n + 1; ++j)
21         if( i != j && j != k && dis[i][k] && dis[k][j] )
22           dis[i][j] = max(dis[i][j], dis[i][k] + dis[k][j]);
23   printf("%d\n", dis[1][n + 1]);
24   for(int i = 1; i <= n + 1; ++i)
25     if( dis[1][i] + dis[i][n + 1] == dis[1][n + 1] )
26       printf("%d ", i);
27 
28 //   printf("_______________________________________________\n");
29 //   printf("Process Exited Correctly With A Return Value 0.\n");
30 //   printf("All Rights Reserved By Kimitsu Nanjo In 2018.\n\n");
31   return 0;
32 }
View Code

 

P1119 災后重建:

一兩個月前做的題,本質是最短路,那時卻完全不知道這個時間限制怎么處理(那時的代碼還是如此的丑);
依然是 Floyd,因為這道題保證修復時間和詢問都是是遞增的,所以就使 k 作為全局變量,每次 k 只以已經修復完成的村庄進行松弛更新,一旦發現村庄 k 的修復時間大於此時的時間,k 就停止自增,等待下一次詢問。

 1 #include<cstdio>
 2 #include<cstring>
 3 
 4 int g[210][210], t[210];
 5 int n, m, q, u, v, w, d, k;
 6 inline int min(int a, int b) {
 7     return a>b?b:a;
 8 }
 9 
10 int main() {
11     scanf("%d%d", &n, &m);
12     memset(g, 0x3f, sizeof(g));
13     memset(t, 0x3f, sizeof(t));
14     for(int i=0; i<n; ++i) {
15         scanf("%d", &t[i]);
16         g[i][i] = 0;
17     }
18     for(int i=0; i<m; ++i) {
19         scanf("%d%d%d", &u, &v, &w);
20         g[u][v] = g[v][u] = w;
21     }
22     
23     scanf("%d", &q);
24     for(int i=0; i<q; ++i) {
25         scanf("%d%d%d", &u, &v, &d);
26         while( t[k]<=d ) {
27             for(int i=0; i<n; ++i) {
28                 for(int j=0; j<n; ++j) {
29                     g[i][j] = min(g[i][j], g[i][k]+g[k][j]);
30                 }
31             }
32             ++k;
33         }
34         if( t[u]>d || t[v]>d || g[u][v]==0x3f3f3f3f ) {
35             printf("-1\n");
36         }
37         else {
38             printf("%d\n", g[u][v]);
39         }
40     }
41     return 0;
42 }
View Code

 

 

另外,Floyd 還可以用來求最小環,一個環中的最大結點為 k,與他相連的兩個點為 i,j,這個環的最短長度為 g[i][k] + g[k][j] + dis[i][j](i 到 j 的路徑中,所有結點編號都小於 k 的最短路徑長度)。根據 Floyd 的原理,在最外層循環做了 k-1 次之后,dist[i][j] 則代表了 i 到 j 的路徑中,所有結點編號都小於 k 的最短路徑。故該算法一定能找到圖中最小環。代碼如下:

 1 void floyd() {
 2   for(int k = 1; k <= n; ++k) {  // 求最小環,不包含第k個點
 3     for(int i = 1; i < k; ++i) {    // 到k-1即可
 4       for(int j = i + 1; j < k; j++)  // 到k-1即可
 5         mincircle = min(mincircle , dis[i][j] + g[i][k] + g[k][j]);    //無向圖 
 6     }
 7 
 8     for(int i = 1; i <= n; ++i)  // 更新最短路
 9       for(int j = 1; j <= n; ++j)
10           dis[i][j] = min(dis[i][k] + dis[k][j] , dis[i][j]);
11   }
12 }
View Code

 

Floyd 還有很多用途,限於篇幅不再贅述了。

                         —— 還記得那天的景色,就像真的到達了那個世界。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM