Floyd(多源最短路)证明 + 输出路径

因为Dijkstra算法求的是到起点的最短路,但是但是咱们有时候需要求任意两个点之间的最短路,这时候写n遍Dijkstra就会特别麻烦,咱们有位名叫Floyd的大佬就用动态规划的思想创造了Floyd算法,下面看一下这个算法具体的代码

void floyd(){
    for (int k = 1; k <= n;k++)
        for (int i = 1; i <= n;i++)
            for (int j = 1; j <= n;j++)
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}

那么这么简单的代码是怎么推过来的呢

假设d[k][i][j]表示从i点只经过1-k这些中间点到j点的最短距离,那么
d[k][i][j] = min(d[k-1][i][j],d[k-1][i][k] + d[k-1][k][j])
那么第一维也可以去掉,至于为什么会去掉呢,现在就我和我同学的讨论来简述一下吧,大家都知道,dp优化掉一维
的做法是屡见不鲜的,从01背包到最长不下降子序列,仿佛哪个dp都能优化掉一维一样,我一开始看y总说把数组优
化掉一维的时候是很懵逼的,真的不明白为啥这个状态没更新删了去就没事,现在随着做题量上去点了也有点领悟了
这个就类似于滚动数组,你用完这个状态后可能就没用了,就比如说这个Floyd算法的d[k][i][j]吧,咱们都是从
k-1给转移过来的,那么k-2呢,是不是就没用了,以后也不会用了,因为咱们最后要的是d[n][i][j],所以说就可以
把k-2的状态给用到k的状态上来,就类似于废物利用吧,然后优化一维的前提是这个状态是k-1的,那么咱们把前面
的一维去掉之后这个递推式子就变成了d[i][j] = min(d[i][j],d[i][k] + d[k][j]),再空间上是减小了不少的空
间复杂度的,但是这个式子为什么会正确呢,我想到了一种情况就是前面的一维不是k-1的情况,就是假如现在的k是
3,i是1,j是3,现在的d[1][3]已经从d[k-1][1][3]更新成为了d[k][1][3]了,但是又是为什么都这样了算法得出
的结果还是正确的呢,不妨想想,d[k][i][j] < d[k-1][i][j],所以说用前者来更新不就更好了吗,因为
d[k][i][j]表示的是从i号点到j号点中间只经过1~k号点的最短距离,d[k-1][i][j]表示的是从i号点到j号点中间只
经过1~k-1号点的最短距离中间的这些点是不包括起点和终点的,那么根据这个状态的表述,d[k][i][k]和
d[k-1][k][j]是可以接起来的,而且是比原来的更好的,这里可以看作是小贪一波哈哈哈,就转移过去了,而且后面
的d[i][k]和d[k][j]一定是动态更新的,假设i到j的最短路径上的点的序号的最大值是x,当k=x遍历完后,这个
d[i][j]就更新完了,这就是我的理解,可能有些不对的地方,还请大家批评指正
最终的结果就是d[i][j] = min(d[i][j],d[i][k] + d[k][j])

应用

这个floyd算法不仅可以算多源最短路,而且他还能判断负环,就是先把每个dis[i][i]设置成0,如果有负环的话,某个点的最后的值就会小于0,可以看一下这个题

Wormholes

在探索他的许多农场时,农民约翰发现了一些惊人的虫洞。虫洞是非常奇特的,因为它是一个单向路径,送你到它的目的地的时间,是在你进入虫洞之前!FJ 的每个农场都包括N (1 ≤ N ≤ 500) 字段,方便编号为 1.。N, M (1 ≤ M ≤ 2500) 路径,和 W (1 ≤ W ≤ 200) 虫洞。

由于FJ是一个狂热的时间旅行的球迷,他想做以下:从某个领域开始,通过一些路径和虫洞旅行,并在他最初离开前一段时间回到起跑场。也许他能:)见到自己。

为了帮助 FJ 了解这是否可能,他将向您提供其农场F(1 ≤ F ≤ 5)的完整地图。没有路径将需要超过 10,000 秒的行程,没有虫洞可以使 FJ 回到时间超过 10,000 秒。

输入

行 1: 单个整数, F.F农场描述如下。
每个农场的1号线:三个空间分离整数:N、M和W
线2。每个农场的M+1:分别描述三个空间分离数字(S、E、T):S 和E之间的双向路径,需要T秒才能穿越。 两个字段可能通过多个路径连接。
行M+2.。每个农场的M+W+1:分别描述三个空间分离数字(S、 E、 T):从S到E的单向路径,也将旅行者向后移动T秒。

输出

行1.。F:对于每个农场,如果 FJ 能够实现他的目标,则输出"是",否则输出"否"(不包括报价)。

示例输入

2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8

样本输出

NO
YES

提示

对于农场 1,FJ 无法及时返回。
对于农场 2,FJ 可以在 1->2->3->1 周期前返回时间,在他离开前 1 秒返回起点位置。他可以从周期的任何地方开始完成这个任务。

这个题的意思就是说存在虫洞可以穿越到以前,问有没有可能过了一段时间以后再回到现在这个时间之前的时间点,那么咱们可以把过了一段时间这个时间看成正权边,把虫洞看成负权边,然后找有没有负权回路就行了,我自己用的是spfa算法判断的负环,现在先提供一种用Floyd算法判断负环的代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int INF = 0x3f3f3f3f;
const int MAXV = 1000;
int n, m, w;
int dis[MAXV][MAXV];
int flag;

bool Floyd() {//核心代码
	for (int k = 1; k <= n; k++) {
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++)
				if (dis[i][k] + dis[k][j] < dis[i][j]) {
					dis[i][j] = dis[i][k] + dis[k][j];
				}
			if (dis[i][i] < 0) {
				return true;
			}
		}
	}
	return false;
}
int main() {
	int f;
	scanf("%d", &f);
	while (f--) {
		scanf("%d%d%d", &n, &m, &w);
		memset(dis,0x3f,sizeof dis);
		for(int i = 1; i <= n; i ++ )
			dis[i][i] = 0;
		int u, v, val;
//		for (int i = 1; i <= m; i++) {//正权边
//			scanf("%d%d%d", &u, &v, &val);
//			if(dis[u][v] > val) dis[u][v] = dis[v][u] = val;
//		}
		for(int i=1; i<=m; i++) { //输入正权边
			scanf("%d%d%d",&u,&v,&val);
			if(dis[u][v]>val) {
				dis[u][v]=val;
				dis[v][u]=val;
			}
		}
		for (int i = 1; i <= w; i++) {//负权边
			scanf("%d%d%d", &u, &v, &val);
			dis[u][v] = -val;
		}
		if (Floyd()) puts("YES");
		else puts("NO");
	}
	return 0;
}

下面看一下纸我写的spfa算法吧

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 510,M = 2510*2,W = 210;
int w[N][N]; 
bool st[N];
int cnt[N],dis[N];
int n,m,f;
queue<int> q;
bool spfa() {
	memset(dis, 0x3f, sizeof dis);
	while(!q.empty()) q.pop();
	for (int i = 1; i <= n; i++) {
		q.push(i);
		st[i] = true;
	}
	while (q.size()) {
		int t = q.front();
		q.pop();
		st[t] = false;
		for(int i=1;i<=n;i++){
			if(w[t][i] != 0x3f3f3f3f){
				if(dis[i] > dis[t] + w[t][i]){
					dis[i] = dis[t] + w[t][i];
					cnt[i] = cnt[t] + 1;
					if(cnt[i] >= n) return true;
					if(!st[i]){
						st[i] = true;
						q.push(i);
					}
				}
			}
		}
	}
	return false;
}
int main() {
	int T;
	scanf("%d",&T);
	while(T--) {
		scanf("%d%d%d",&n,&m,&f);
		memset(cnt,0,sizeof cnt);
		memset(w,0x3f,sizeof w);
		for(int i=1; i<=m; i++) {
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			if(w[a][b] > c){
				w[a][b] = w[b][a] = c;
			}
		}
		for(int i=1; i<=f; i++) {
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			c = -c;
			if(w[a][b] > c) w[a][b] = c;
		}
		if(spfa()) puts("YES");
		else puts("NO");
	}
	return 0;
}

Floyd输出路径

核心代码
for(int k=1;k<=n;k++)
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(dis[i][j] > dis[i][k] + dis[k][j]){
				dis[i][j] = dis[i][k] + dis[k][j];
				path[i][j] = k;
			}

这个k相当于一个中间点,就是 i 到 j 的最短路,那该怎么输出最短路呢

初始化pre[i][j]= j;
int u = st;//st是起点,end是终点
while(u != pre[u][end]){
cout << u << endl;
u = pre[u][end];
}
cout << u << endl;

这个就是不断的更新起点,然后有很多个中间点,一个一个的找,最后就找到终点了,为什么最后找到的是终点呢,是因为一开始咱们初始化了呀

<think>好的,我现在需要处理用户关于蓝桥杯竞赛中Dijkstra算法(包括堆优化)和Floyd短路径的问题,同时提供C++的实现代码和应用实例。首先,我得回忆这两种算法的基本原理和实现方式,确保自己理解正确。 首先,Dijkstra算法是单短路径算法,适用于没有负权边的图。普通版本使用邻接矩阵或邻接表,每次遍历找到当前未访问的近节点,更新其邻居的距离。堆优化版本则用优先队列来提高效率,将时间复杂度从O()降到O((V+E)logV)。用户需要C++模板,我得写出正确的代码结构,包括优先队列的使用和距离数组的更新。 然后是Floyd算法,这是短路径算法,基于动态规划,通过三重循环更新距离矩阵。关键点是理解中间节点的遍历顺序,k在外层。需要给出正确的三重循环实现,并解释其动态规划思想。 接下来是应用实例,用户提到了蓝桥公园和蓝桥王国,可能是指具体的题目。我需要结合这些题目,说明如何应用这两种算法。例如,蓝桥公园可能需要短路径,适合Floyd;而蓝桥王国可能涉及单问题,适合Dijkstra。 关于代码部分,需要注意蓝桥杯竞赛中的常见输入输出格式,比如使用邻接表还是邻接矩阵,节点编号是否从1开始。同时,堆优化Dijkstra的实现要避免常见错误,比如优先队列中节点的处理顺序和距离更新。 用户还提到了2022省赛的“路径”题和2021国赛的“交通规则”题。可能需要分析这些题目如何应用Dijkstra或Floyd。例如,“路径”题可能涉及短路径计算,而“交通规则”可能涉及带条件的短路径,比如交通规则限制,这时候可能需要调整算法逻辑。 在编写回答时,要确保符合系统级指令:行内数学公式用$...$,独立公式用$$...$$,正确的中文回答,生成相关问题,引用标记。例如,在解释算法时,时间复杂度的公式可能需要用行内数学格式,如$O(n^3)$。 后,检查引用是否正确,用户提供的参考引用是关于Floyd和Dijkstra的简要说明,需要确保在回答中正确引用,例如在讨论Floyd算法时添加[^1]。 现在需要组织内容,分点讲解Dijkstra(普通和堆优化)、Floyd,给出代码模板,应用实例,并生成相关问题。确保代码部分语法正确,解释清晰,同时符合用户要求的竞赛应用场景。</think>### Dijkstra算法与堆优化实现 #### 基础Dijkstra算法 适用于**无负权边**的单短路问题,时间复杂度$O(n^2)$。算法步骤: 1. 初始化距离数组`dist[]`,点距离设为0 2. 每次从未访问节点中选择距离小的节点$u$ 3. 更新$u$所有邻居节点$v$的短距离: $$dist[v] = \min(dist[v], dist[u] + w_{uv})$$ ```cpp // 邻接矩阵版(适合稠密图) const int N = 1005; int g[N][N], dist[N], vis[N]; void dijkstra(int src) { memset(dist, 0x3f, sizeof dist); dist[src] = 0; for(int i=0; i<n; i++) { int u = -1; for(int j=1; j<=n; j++) if(!vis[j] && (u == -1 || dist[j]<dist[u])) u = j; vis[u] = 1; for(int v=1; v<=n; v++) dist[v] = min(dist[v], dist[u]+g[u][v]); } } ``` #### 堆优化Dijkstra 使用优先队列优化查找小距离节点的过程,时间复杂度降为$O(m \log n)$,适合稀疏图: ```cpp // 邻接表+堆优化版(2022省赛"路径"题适用) #include <queue> using pii = pair<int, int>; const int N = 2e5+5; vector<pii> g[N]; // first: 邻接点, second: 边权 void dijkstra_heap(int src) { priority_queue<pii, vector<pii>, greater<>> pq; vector<int> dist(n+1, INT_MAX); dist[src] = 0; pq.emplace(0, src); while(!pq.empty()) { auto [d, u] = pq.top(); pq.pop(); if(d > dist[u]) continue; // 已存在更优解 for(auto &[v, w] : g[u]) { if(dist[v] > dist[u] + w) { dist[v] = dist[u] + w; pq.emplace(dist[v], v); } } } } ``` ### Floyd短路算法 通过动态规划思想计算所有节点对的短路径,时间复杂度$O(n^3)$,适合节点数较少的情况(如n≤200)[^1]。 $$dist[k][i][j] = \min(dist[k-1][i][j], dist[k-1][i][k] + dist[k-1][k][j])$$ ```cpp // Floyd模板(2021国赛"交通规则"适用) const int N = 205; int dist[N][N]; void floyd() { for(int k=1; k<=n; k++) for(int i=1; i<=n; i++) for(int j=1; j<=n; j++) dist[i][j] = min(dist[i][j], dist[i][k]+dist[k][j]); } ``` ### 应用实例分析 #### 蓝桥公园(Floyd应用) 题目描述:给定公园景点地图,次查询任意两景点间短距离。 解决方案:预处理所有节点对的短路径,查询时直接返回结果。 #### 蓝桥王国交通规划(Dijkstra应用) 题目描述:建设新交通线路使得所有城市到首都的短距离总和小。 解决方案:使用Dijkstra计算各城市到首都的短路径,再根据交通规则调整权重。 ### 竞赛技巧 1. **堆优化选择**:节点数>1e4时优先使用堆优化版 2. **初始化技巧**:Floyd算法初始化`dist[i][j] = INF`,对角线设为0 3. **负权处理**:Dijkstra不能处理负权边,需改用SPFA算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇智波一打七~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值