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.

3.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

图论-多源最短路径(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

相关变形结合题目讲,如:负圈、打印路径、最小环、传递闭包

记录坑点:重复边,保留最小的那个。

二、模板

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];
}

三、例题

类型 判负环

题意

  • 正常路是m条双向正权边
  • 虫洞是w条单向负权边
  • 题目让判断是否有负权回路

办法 利用Floyd找两点间花费的最短时间,判断从起始位置到起始位置的最短时间是否为负值(判断负权环),若为负值,说明他通过虫洞回到起始位置时比自己最初离开起始位置的时间早。

代码实现: 在第二重循环,求完第i个结点后判断。ii之间的最短距离是一个负值,说明存在一个经过它的负环。

#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;
}

HDU-1385 Minimum Transport Cost

类型 打印路径

题意 给你所有城市到其他城市的道路成本和经过每个城市的城市税,给你很多组城市,要求你找出每组城市间的最低运输成本并且输出路径,如果有多条路径则输出字典序最小的那条路径。注意,起点城市和终点城市不需要收城市税。

分析 输出路径,多个答案则输出字典序最小的,无法到达输出-1。 读入邻接表, cost[]记录每个城市额外费用, path[][]记录路径,比如 path[i][j]=k 表示 ij的路径是 i先到 k,再从 kjfloyd()里维护即可。然后处理下输出(比较恶心)。

https://juejin.cn/post/6935691567696969764