news 2026/1/10 16:33:12

算法基础-多源最短路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算法基础-多源最短路

多源最短路

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

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]为初始状态下ij的距离,如果没有边则为⽆穷。
5. 填表顺序:
⼀定要先枚举k,再枚举ij。因为我们填表的时候,需要依赖的是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。

数据中可能存在重边。


【解法】

模板题。

【参考代码】

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

2Clear 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 算法求出任意两点的最短路即可。

【参考代码】

#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 之前的点,都可以加⼊到最短路的 更新中。
那么就可以⼀边读取询问,⼀边通过时间限制,更新最短路。

【参考代码】

#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]
环的最⼤编号是任意的,因此在所有情况下取最⼩值即可

【参考代码】

#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; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/23 23:30:09

KeyShot许可证激活步骤及使用指南

一、KeyShot许可证激活步骤 获取许可证文件&#xff1a;从官方渠道或授权合作伙伴处获取KeyShot许可证文件。 打开KeyShot软件&#xff1a;启动软件后&#xff0c;您将看到许可证激活界面。 输入许可证信息&#xff1a;在界面中输入许可证文件的名称、许可证密钥等必要信息。 选…

作者头像 李华
网站建设 2026/1/2 12:00:08

网络编程基础:OSI 模型与 TCP/IP 协议栈详解

作为网络编程的入门核心&#xff0c;理解网络分层模型是掌握数据通信逻辑的关键。本文将拆解 OSI 七层模型的功能&#xff0c;并对比 TCP/IP 协议栈的简化设计&#xff0c;帮你快速建立网络通信的底层认知。一、OSI 七层模型&#xff1a;网络通信的 “标准框架”OSI&#xff08…

作者头像 李华
网站建设 2025/12/23 23:17:19

EagleTrader交易员采访|不遵守交易规则,真的是自由吗?

对很多交易员来说&#xff0c;真正的困难&#xff0c;从来不是行情本身。而是在一次次盈利与回撤之间&#xff0c;仍然愿意相信规则&#xff1b;是在没有掌声、没有监督的交易日里&#xff0c;持续做出那些“看起来不那么刺激”的选择。这类情况也往往只会被少数人长期坚持。朱…

作者头像 李华
网站建设 2026/1/9 1:05:06

java计算机毕业设计鲜花商城网 基于SpringBoot的线上花店零售与配送平台 面向节日场景的O2O鲜花订购与会员营销系统

计算机毕业设计鲜花商城网9&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。情人节、母亲节、毕业季……“送花”永远是最保险的表达&#xff0c;却也是最耗时的线下跑腿&#xff…

作者头像 李华
网站建设 2026/1/9 8:16:01

注意!教你选出合肥市面上正规又靠谱的门头设计安装企业!

注意&#xff01;教你选出合肥市面上正规又靠谱的门头设计安装企业&#xff01;在商业竞争日益激烈的当下&#xff0c;一个独特且合适的门头设计安装对于企业的品牌形象塑造至关重要。然而&#xff0c;市面上的门头设计安装企业众多&#xff0c;质量参差不齐&#xff0c;如何选…

作者头像 李华
网站建设 2025/12/23 22:52:56

失业 3 个月投 127 份简历?网安零成本转行月薪 12K,你们敢试吗?

失业 3 个月投了 127 份简历&#xff1f;别卷了&#xff01;我靠网安转行月薪 12K&#xff0c;附 3 个月零成本入门攻略 去年被裁那天&#xff0c;我盯着招聘软件上 “35 岁以下优先” 的字样&#xff0c;把简历里的 “5 年行政经验” 改了又改&#xff0c;结果投出去的 127 份…

作者头像 李华