You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

204 lines
6.7 KiB

2 years ago
## 图论-多源最短路径($Floyd$算法)
### 一、$Floyd$
$Floyd$算法是一次性求所有结点之间的最短距离,能处理负权边的图,程序比暴力的$DFS$更简单,但是复杂度是$O(n^3)$,只适合 $n < 200$
$Floyd$运用了 **动态规划** 的思想,求 $i 、 j$两点的最短距离,可分两种情况考虑,即经过图中某个点 $k$的路径和不经过点 $k$ 的路径,**取两者中的最短路径**。
2 years ago
- 判断负圈
眼尖的人儿可能发现邻接矩阵 $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];
}
```
2 years ago
### 三、判负环
2 years ago
2 years ago
#### [$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 <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
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;
}
```
2 years ago
### 四、练习题
2 years ago
2 years ago
#### [$HDU-1385$ $Minimum$ $Transport$ $Cost$](http://acm.hdu.edu.cn/showproblem.php?pid=1385)
**类型**
打印路径
**题意**
2 years ago
给你所有城市到其他城市的道路成本和经过每个城市的城市税,给你很多组城市,要求你找出每组城市间的最低运输成本并且输出路径,**如果有多条路径则输出字典序最小的那条路径**。 **注意**,起点城市和终点城市不需要收城市税(中间点才收税,也就是插值的$k$收税)。
2 years ago
**分析**
输出路径,多个答案则输出字典序最小的,无法到达输出$-1$。
2 years ago
读入邻接表, $w[]$记录每个城市额外费用, $path[][]$记录路径,$floyd()$里维护即可。然后处理下输出(比较恶心)。
2 years ago
2 years ago
> **解释**`int path[N][N]; `
$i \rightarrow j$ 可能存在多条路线,我要找最短的。如果有多条最短的,我要字典序最小的。现在路线唯一了吧!比如这条路线最终是
$i \rightarrow a \rightarrow b \rightarrow c \rightarrow d \rightarrow j$,则$path[i][j]=a$,也就是第一个后继节点。
2 years ago
2 years ago
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
2 years ago
const int N = 110;
2 years ago
const int INF = 0x3f3f3f3f;
2 years ago
// Floyd+记录起点后继
int n;
int g[N][N], w[N];
int path[N][N]; // 记录i到j最短路径中i的后继
2 years ago
void floyd() {
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
2 years ago
for (int j = 1; j <= n; j++) {
if (g[i][j] > g[i][k] + g[k][j] + w[k]) {
g[i][j] = g[i][k] + g[k][j] + w[k];
path[i][j] = path[i][k]; // i->j这条最短路径上i后面第一个节点是i->k路径上第一个节点
2 years ago
}
2 years ago
// 相同路径下选择后继更小的(为了字典序)
if (g[i][j] == g[i][k] + g[k][j] + w[k])
if (path[i][j] > path[i][k])
path[i][j] = path[i][k];
}
}
// 递归输出路径
void print(int s, int e) {
printf("-->%d", path[s][e]); // 输出s的后继
if (path[s][e] != e) // 如果不是直连
print(path[s][e], e); // 递归输出后继
2 years ago
}
2 years ago
/*
From 1 to 3 :
Path: 1-->5-->4-->3
Total cost : 21
From 3 to 5 :
Path: 3-->4-->5
Total cost : 16
From 2 to 4 :
Path: 2-->1-->5-->4
Total cost : 17
*/
2 years ago
int main() {
2 years ago
#ifndef ONLINE_JUDGE
freopen("HDU1385.in", "r", stdin);
#endif
2 years ago
while (cin >> n, n) {
for (int i = 1; i <= n; i++)
2 years ago
for (int j = 1; j <= n; j++) {
2 years ago
cin >> g[i][j];
if (g[i][j] == -1) g[i][j] = INF;
path[i][j] = j;
2 years ago
}
2 years ago
for (int i = 1; i <= n; i++) cin >> w[i];
2 years ago
floyd();
2 years ago
int s, e;
2 years ago
2 years ago
while (cin >> s >> e, ~s && ~e) {
printf("From %d to %d :\n", s, e);
printf("Path: %d", s);
if (s != e) print(s, e); // 起点与终点不同开始递归
printf("\nTotal cost : %d\n\n", g[s][e]);
2 years ago
}
}
return 0;
}
```
2 years ago
2 years ago
2 years ago
#### [$HDU$-$1599$ $find$ $the$ $mincost$ $route$](https://acm.hdu.edu.cn/showproblem.php?pid=1599)
(最小环)
#### [$HDU$-$1704$ $Rank$](https://acm.hdu.edu.cn/showproblem.php?pid=1704)
(传递闭包)
#### [$HDU$-$3631$ $Shortest$ $Path$](https://acm.hdu.edu.cn/showproblem.php?pid=3631)
(变形)
**$TODO$**
据说可以使用$Dijkstra$算法解决,有空可以试试: **[链接](https://blog.csdn.net/K_R_forever/article/details/80525757)**
2 years ago
https://juejin.cn/post/6935691567696969764