算法基础-多源最短路

多源最短路

多源最短路:即图中每对顶点间的最短路径。


floyd 算法本质是动态规划 ,⽤来求任意两个结点之间的最短路,也称 插点法 。通过不断在两点之间加 ⼊新的点,来更新最短路。
适⽤于 任何图 ,不管有向⽆向,边权正负,但是最短路必须存在(也就是不存在负环)。


1. 状态表⽰:
f[k][i][j] 表⽰:仅仅经过 [1, k] 这些点,结点 i ⾛到结点 j 的最短路径 的⻓度。
2. 状态转移⽅程:
• 第⼀种情况,不选新来的点: f[k][i][j] = f[k - 1][i][j] ;
• 第⼆种情况,选择新来的点: f[k][i][j] = f[k - 1][i][k] + f[k - 1][k][j]
3. 空间优化:
只会⽤到上⼀层的状态,因此可以优化到第⼀维。

4. 初始化:
• f[i][i] = 0 ;
• f[i][j] 为初始状态下 i 到 j 的距离,如果没有边则为⽆穷。

5. 填表顺序:
• ⼀定要先枚举 k ,再枚举 i 和 j 。因为我们填表的时候,需要依赖的是 k - 1 层的状态,因
此 k 必须先枚举。

1 【模板】floyd

题⽬来源: 洛⾕
题⽬链接: B3647 【模板】Floyd
难度系数: ★

题目描述

给出一张由 n 个点 m 条边组成的无向图。

求出所有点对 (i,j) 之间的最短路径。

输入格式

第一行为两个整数 n,m,分别代表点的个数和边的条数。

接下来 m 行,每行三个整数 u,v,w,代表 u,v 之间存在一条边权为 w 的边。

输出格式

输出 n 行每行 n 个整数。

第 i 行的第 j 个整数代表从 i 到 j 的最短路径。

输入输出样例

输入 #1复制

复制代码
4 4
1 2 1
2 3 1
3 4 1
4 1 1

输出 #1复制

复制代码
0 1 2 1
1 0 1 2
2 1 0 1
1 2 1 0

说明/提示

对于 100% 的数据,n≤100,m≤4500,任意一条边的权值 w 是正整数且 1⩽w⩽1000。

数据中可能存在重边。


【解法】

模板题。


【参考代码】

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

const int N = 110;  // 最多110个点(适配题目数据范围)
int n, m;           // n=点数,m=边数
int f[N][N];        // 邻接矩阵:f[i][j] = i到j的最短距离

int main() {
    // 第一步:输入点数和边数
    cin >> n >> m;
    
    // 第二步:初始化邻接矩阵
    memset(f, 0x3f, sizeof f);  // 所有距离初始化为"无穷大"(0x3f≈10亿)
    for (int i = 1; i <= n; i++) {
        f[i][i] = 0;  // 自己到自己的距离为0
    }
    
    // 第三步:输入m条无向边,更新邻接矩阵
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        // 无向边:u↔v,取最小边权(防止重边)
        f[u][v] = f[v][u] = min(f[u][v], w);
    }
    
    // 第四步:Floyd核心------三层循环更新所有点对的最短距离
    // k:中间点(枚举所有可能的中转点)
    for (int k = 1; k <= n; k++) {
        // i:起点
        for (int i = 1; i <= n; i++) {
            // j:终点
            for (int j = 1; j <= n; j++) {
                // 状态转移:i→j的最短距离 = min(原距离, i→k + k→j)
                f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
            }
        }
    }
    
    // 第五步:输出结果(n行n列)
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cout << f[i][j] << " ";
        }
        cout << endl;  // 每行结束换行
    }
    
    return 0;
}

2 Clear And Present Danger

题⽬来源: 洛⾕
题⽬链接: P2910 [USACO08OPEN] Clear And Present Danger S
难度系数: ★★

题目描述

农夫约翰正驾驶一条小艇在牛勒比海上航行。

海上有 N(1≤N≤100) 个岛屿,用 1 到 N 编号。约翰从 1 号小岛出发,最后到达 N 号小岛。

一张藏宝图上说,如果他的路程上经过的小岛依次出现了 A1​,A2​,...,AM​(2≤M≤10000) 这样的序列(不一定相邻),那他最终就能找到古老的宝藏。但是,由于牛勒比海有海盗出没,约翰知道任意两个岛屿之间的航线上海盗出没的概率,他用一个危险指数 Di,j​(0≤Di,j​≤100000) 来描述。他希望他的寻宝活动经过的航线危险指数之和最小。那么,在找到宝藏的前提下,这个最小的危险指数是多少呢?

输入格式

第一行:两个用空格隔开的正整数 N 和 M。

第二到第 M+1 行:第 i+1 行用一个整数 Ai​ 表示 FJ 必须经过的第 i 个岛屿。保证 A1​=1,AM​=N。

第 M+2 到第 N+M+1 行:第 i+M+1 行包含 N 个用空格隔开的非负整数分别表示 i 号小岛到第 1...N 号小岛的航线各自的危险指数。保证第 i 个数是 0。

输出格式

第一行:FJ 在找到宝藏的前提下经过的航线的危险指数之和的最小值。

显示翻译

题意翻译

输入输出样例

输入 #1复制

复制代码
3 4 
1 
2 
1 
3 
0 5 1 
5 0 2 
1 2 0 

输出 #1复制

复制代码
7 

说明/提示

样例说明 #1

这组数据中有三个岛屿,藏宝图要求 FJ 按顺序经过四个岛屿:1 号岛屿、2 号岛屿、回到 1 号岛屿、最后到 3 号岛屿。每条航线的危险指数也给出了:航路(1,2),(2,3),(3,1) 和它们的反向路径的危险指数分别是 5,2,1。

FJ 可以通过依次经过 1,3,2,3,1,3 号岛屿以 7 的最小总危险指数获得宝藏。这条道路满足了奶牛地图的要求 (1,2,1,3)。我们避开了 1 号和 2 号岛屿之间的航线,因为它的危险指数太大了。

注意:测试数据中 a 到 b 的危险指数不一定等于 b 到 a 的危险指数!

Translated by @LJC00125


【解法】

⽤ floyd 算法求出任意两点的最短路即可。

【参考代码】

cpp 复制代码
#include<iostream>
#include<cstring>
using namespace std;

typedef long long LL;  // 防止总距离溢出(比如多次累加大数)
const int N=110;       // 最多110个点(Floyd算法n³复杂度适配)
const int M=1e4+10;    // 最多1e4个访问点

int n, m;              // n=点数,m=访问序列长度
int a[M];              // 访问序列:a[1]~a[m]是依次要走的点
int f[N][N];           // 邻接矩阵:f[i][j] = i到j的最短距离

int main() {
    // 第一步:输入点数n和访问序列长度m
    cin >> n >> m;
    
    // 第二步:输入访问序列(m个点的编号)
    for(int i=1; i<=m; i++) {
        cin >> a[i];
    }
    
    // 第三步:输入初始邻接矩阵(n行n列,f[i][j]是i到j的初始距离)
    for(int i=1; i<=n; i++) {
        for(int j=1; j<=n; j++) {
            cin >> f[i][j];
        }
    }
    
    // 第四步:Floyd算法预处理所有点对的最短路径
    // k:中转点,i:起点,j:终点
    for(int k=1; k<=n; k++) {
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=n; j++) {
                // 状态转移:i→j的最短距离 = min(原距离, i→k + k→j)
                f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
            }
        }
    }
    
    // 第五步:计算访问序列的总最短距离
    LL ret = 0;  // 用LL防止溢出
    for(int i=2; i<=m; i++) {
        // 累加a[i-1]到a[i]的最短距离
        ret += f[a[i-1]][a[i]];
    }
    
    // 第六步:输出总距离
    cout << ret << endl;

    return 0;
}

3 灾后重建

题⽬来源: 洛⾕
题⽬链接: P1119 灾后重建
难度系数: ★★★


题目背景

B 地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。

题目描述

给出 B 地区的村庄数 N,村庄编号从 0 到 N−1,和所有 M 条公路的长度,公路是双向的。并给出第 i 个村庄重建完成的时间 ti​,你可以认为是同时开始重建并在第 ti​ 天重建完成,并且在当天即可通车。若 ti​ 为 0 则说明地震未对此地区造成损坏,一开始就可以通车。之后有 Q 个询问 (x,y,t),对于每个询问你要回答在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少。如果无法找到从 x 村庄到 y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x 或村庄 y 在第 t 天仍未重建完成,则需要输出 −1。

输入格式

第一行包含两个正整数 N,M,表示了村庄的数目与公路的数量。

第二行包含 N 个非负整数 t0​,t1​,⋯,tN−1​,表示了每个村庄重建完成的时间,数据保证了 t0​≤t1​≤⋯≤tN−1​。

接下来 M 行,每行 3 个非负整数 i,j,w,w 不超过 10000,表示了有一条连接村庄 i 与村庄 j 的道路,长度为 w,保证 i=j,且对于任意一对村庄只会存在一条道路。

接下来一行也就是 M+3 行包含一个正整数 Q,表示 Q 个询问。

接下来 Q 行,每行 3 个非负整数 x,y,t,询问在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少,数据保证了 t 是不下降的。

输出格式

共 Q 行,对每一个询问 (x,y,t) 输出对应的答案,即在第 t 天,从村庄 x 到村庄 y 的最短路径长度为多少。如果在第 t 天无法找到从 x 村庄到 y 村庄的路径,经过若干个已重建完成的村庄,或者村庄 x 或村庄 y 在第 t 天仍未修复完成,则输出 −1。

输入输出样例

输入 #1复制

复制代码
4 5
1 2 3 4
0 2 1
2 3 1
3 1 2
2 1 4
0 3 5
4
2 0 2
0 1 2
0 1 3
0 1 4

输出 #1复制

复制代码
-1
-1
5
4

说明/提示

  • 对于 30% 的数据,有 N≤50;
  • 对于 30% 的数据,有 ti=0,其中有 20% 的数据有 ti=0 且 N>50;
  • 对于 50% 的数据,有 Q≤100;
  • 对于 100% 的数据,有 1≤N≤200,0≤M≤2N×(N−1),1≤Q≤50000,所有输入数据涉及整数均不超过 105。

【解法】

希望⼤家通过这道题理解 floyd 算法的本质。
在 floyd 算法中,我们是⼀个点⼀个点加⼊到最短路的更新中,那么这道题其实就是限制了我们加点的 时机。当从前往后遍历每次询问的时候,直到时间点在询问的时间 t 之前的点,都可以加⼊到最短路的 更新中。
那么就可以⼀边读取询问,⼀边通过时间限制,更新最短路。


【参考代码】

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

const int N = 210;          // 村庄数量上限(210)
const int INF = 0x3f3f3f3f; // 无穷大(≈10亿,大于最大可能路径和)

int n, m;                   // n=村庄数,m=公路数
int t[N];                   // t[i]:第i个村庄的重建完成时间
int f[N][N];                // 邻接矩阵:f[i][j] = i到j的最短路径长度

// Floyd核心函数:加入中转点k,更新所有i→j的最短路径
void floyd(int k) {
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            // 状态转移:i→j的最短路径 = min(原路径, i→k + k→j)
            f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
        }
    }
}

int main() {
    // 第一步:输入村庄数和公路数
    cin >> n >> m;
    
    // 第二步:输入每个村庄的重建完成时间(题目保证t[0]≤t[1]≤...≤t[n-1])
    for (int i = 0; i < n; i++) {
        cin >> t[i];
    }
    
    // 第三步:初始化邻接矩阵
    memset(f, 0x3f, sizeof f); // 所有路径初始化为无穷大
    for (int i = 0; i < n; i++) {
        f[i][i] = 0; // 自己到自己的路径长度为0
    }
    
    // 第四步:输入公路信息(双向边)
    for (int i = 1; i <= m; i++) {
        int a, b, c;
        cin >> a >> b >> c;
        // 双向公路,取最小长度(题目保证无重边,min可省略,但保留更通用)
        f[a][b] = f[b][a] = min(f[a][b], c);
    }
    
    // 第五步:处理查询
    int Q; // 查询数量
    cin >> Q;
    int pos = 0; // 记录当前已加入的最后一个中转点(利用t递增、查询t不下降的特性)
    
    while (Q--) {
        int x, y, time; // 查询:time天,x到y的最短路径
        cin >> x >> y >> time;
        
        // 关键:把所有重建完成时间≤time的村庄作为中转点加入Floyd
        while (pos < n && t[pos] <= time) {
            floyd(pos); // 加入中转点pos,更新所有路径
            pos++;      // 下一个待加入的中转点
        }
        
        // 判定输出条件:
        // 1. x或y未重建(t[x]/t[y] > time);2. x到y无路径(f[x][y]=INF)→ 输出-1
        if (t[x] > time || t[y] > time || f[x][y] == INF) {
            cout << -1 << endl;
        } else {
            cout << f[x][y] << endl;
        }
    }
    
    return 0;
}

4 ⽆向图的最⼩环问题

题⽬来源: 洛⾕
题⽬链接: P6175 ⽆向图的最⼩环问题
难度系数: ★★


题目描述

给定一张无向图,求图中一个至少包含 3 个点的环,环上的节点不重复,并且环上的边的长度之和最小。该问题称为无向图的最小环问题。在本题中,你需要输出最小的环的边权和。若无解,输出 No solution.

输入格式

第一行两个正整数 n,m 表示点数和边数。

接下来 m 行,每行三个正整数 u,v,d,表示节点 u,v 之间有一条长度为 d 的边。

输出格式

输出边权和最小的环的边权和。若无解,输出 No solution.

输入输出样例

输入 #1复制

复制代码
5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20

输出 #1复制

复制代码
61

说明/提示

样例解释:一种可行的方案:1−3−5−2−1。

对于 20% 的数据,n,m≤10。

对于 60% 的数据,m≤100。

对于 100% 的数据,1≤n≤100,1≤m≤5×103,1≤d≤105。

无解输出包括句号。


【解法】

floyd 算法的性质:
• 在计算第 k 层的时候, f[i][j] ⾥⾯存储着中转点为 [1, k - 1] 时的最短路。
如果设环⾥的最⼤结点的编号为 k ,与 k 相邻的点为 i, j ,其中 i < k && j < k && i
!= j :
• 那么我们在 floyd 算法循环到 k 的时候,这个环的最⼩⻓度为: f[i][j] + e[i][k] +
e[j][k] 。
• 环的最⼤编号是任意的,因此在所有情况下取最⼩值即可


【参考代码】

cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, INF = 1e8;
int n, m;
int e[N][N];
int f[N][N];
int main()
{
cin >> n >> m;
// memset(e, 0x3f, sizeof e);
// memset(f, 0x3f, sizeof f);
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
f[i][j] = e[i][j] = INF;
for(int i = 1; i <= n; i++) e[i][i] = f[i][i] = 0;
for(int i = 1; i <= m; i++)
{
int a, b, c; cin >> a >> b >> c;
e[a][b] = e[b][a] = f[a][b] = f[b][a] = min(e[a][b], c);
}
int ret = INF;
for(int k = 1; k <= n; k++)
{
// 最⼩环
for(int i = 1; i < k; i++)
for(int j = i + 1; j < k; j++)
ret = min(ret, f[i][j] + e[i][k] + e[k][j]);
// 最短距离
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
}
if(ret == INF) cout << "No solution." << endl;
else cout << ret << endl;
return 0;
}
相关推荐
晚晶5 小时前
[C++/流媒体/tcp/rtsp]构建一个简单的流媒体转发服务器,用于将rtsp推流转发出去
服务器·c++·tcp/ip·流媒体·转发·rtsp
阿闽ooo5 小时前
单例模式深度解析:从饿汉到懒汉的实战演进
开发语言·c++·笔记·设计模式
火羽白麟5 小时前
大坝安全的“大脑”——模型与算法
算法·模型·大坝安全
x70x806 小时前
C++中auto的使用
开发语言·数据结构·c++·算法·深度优先
xu_yule6 小时前
算法基础-单源最短路
c++·算法·单源最短路·bellman-ford算法·spfa算法
Evand J6 小时前
【MATLAB免费例程】多无人机,集群多角度打击目标,时间与角度约束下的协同攻击算法,附下载链接
算法·matlab·无人机
YGGP6 小时前
【Golang】LeetCode 118. 杨辉三角
算法·leetcode
拼好饭和她皆失6 小时前
c++---快速记忆stl容器
开发语言·c++
蒲小英6 小时前
算法-二分查找
算法