## 图论-多源最短路径($Floyd$算法) ### 一、$Floyd$ $Floyd$算法是一次性求所有结点之间的最短距离,能处理负权边的图,程序比暴力的$DFS$更简单,但是复杂度是$O(n^3)$,只适合 $n < 200$的情况。 $Floyd$运用了 **动态规划** 的思想,求 $i 、 j$两点的最短距离,可分两种情况考虑,即经过图中某个点 $k$的路径和不经过点 $k$ 的路径,**取两者中的最短路径**。 - 判断负圈 眼尖的人儿可能发现邻接矩阵 $mp$ 中, $mp[i][i]$并没有赋初值$0$,而是 $inf$。并且计算后 $mp[i][i]$的值也不是 $0$,而是 $mp[i][i]=mp[i][u]+……+mp[v][i]$,即从外面绕一圈回来的最短路径,而这正 **用于判断负圈**,即 $mp[i][i]<0$。 相关变形结合题目讲,如:负圈、打印路径、最小环、传递闭包 记录坑点:**重复边**,保留最小的那个。 ### 二、模板 ```cpp {.line-numbers} void floyd() { for (int k = 1; k <= n; k++) for (int i = 1; i <= n; i++) if (g[i][k] != inf) //优化 for (int j = 1; j <= n; j++) if (g[i][j] > g[i][k] + g[k][j]) g[i][j] = g[i][k] + g[k][j]; } ``` ### 三、例题 #### [$POJ-3259$ $Wormholes$](https://link.juejin.cn/?target=https%3A%2F%2Fvjudge.net%2Fproblem%2FPOJ-3259) **类型** 判负环 **题意** - 正常路是$m$条双向正权边 - 虫洞是$w$条单向负权边 - 题目让判断是否有负权回路 **办法** 利用$Floyd$找两点间花费的最短时间,判断从起始位置到起始位置的最短时间是否为负值(判断负权环),若为负值,说明他通过虫洞回到起始位置时比自己最初离开起始位置的时间早。 **代码实现**: 在第二重循环,求完第$i$个结点后判断。$i$到$i$之间的最短距离是一个负值,说明存在一个经过它的负环。 ```cpp {.line-numbers} #include #include #include #include using namespace std; const int INF = 0x3f3f3f3f; const int N = 502; int n, m, w; int g[N][N]; // floyd判断是否存在负圈 bool floyd() { for (int k = 1; k <= n; k++) for (int i = 1; i <= n; i++) if (g[i][k] != INF) { // 优化 for (int j = 1; j <= n; j++) if (g[i][j] > g[i][k] + g[k][j]) g[i][j] = g[i][k] + g[k][j]; if (g[i][i] < 0) return true; // 发现负圈 } return false; } int main() { int T; cin >> T; while (T--) { cin >> n >> m >> w; memset(g, INF, sizeof g); // 初始化邻接矩阵 // 双向正值边 while (m--) { int a, b, c; cin >> a >> b >> c; // 注意坑:重边 g[a][b] = g[b][a] = min(c, g[a][b]); } // 单向负值边 while (w--) { int a, b, c; cin >> a >> b >> c; g[a][b] = -c; // 负值边 } if (floyd()) puts("YES"); else puts("NO"); } return 0; } ``` #### [$HDU-1385$ $Minimum$ $Transport$ $Cost$](http://acm.hdu.edu.cn/showproblem.php?pid=1385) **类型** 打印路径 **题意** 给你所有城市到其他城市的道路成本和经过每个城市的城市税,给你很多组城市,要求你找出每组城市间的最低运输成本并且输出路径,如果有多条路径则输出字典序最小的那条路径。**注意**,起点城市和终点城市不需要收城市税。 **分析** 输出路径,多个答案则输出字典序最小的,无法到达输出$-1$。 读入邻接表, $cost[]$记录每个城市额外费用, $path[][]$记录路径,比如 $path[i][j]=k$ 表示 $i$到 $j$的路径是 $i$先到 $k$,再从 $k$到 $j$,$floyd()$里维护即可。然后处理下输出(比较恶心)。 https://juejin.cn/post/6935691567696969764