Floyd算法详解

写在前面

在做洛谷的树上dp题单的时候遇到了一道题目P1613 跑路发现自己对flyod的理解太浅薄了,于是去重新学习了一遍,又做了几道题目,然后结合了acwing的算法提高课的总结,于是乎有了这篇博客。

要说floyd就不能只说板子

什么是floyd,提到floyd肯定就会想到最短路。

Floyd算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似。该算法名称以创始人之一、1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。

我们现在看到的形式

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

现在我们的板子中经常看到的floyd都是二维,其实最原始的状态是三维

状态

d[k][i][j]:从i点到j点只经过,前k个结点123...k1k的最短距离

转移

转移的时候是以经不经过第k个结点来划分进行转移的。
不经过:
d[k][i][j]=d[k1][i][j]
经过:
d[k][i][j]=d[k1][i][k]+d[k1][j][k]
所以状态转移方程为:
d[k][i][j]=min(d[k1][i][j],d[k1][i][k]+d[k1][j][k])

空间优化

观察上面的状态转移方程可以看到在算第k层的状态时,只用到了k1层的状态,这就启发了我们是不是可以像背包问题那样,优化掉一维的空间。
我们可以看一下去掉第一维会不会得到的方程是否和原方程等价.
也就是说我们在去点第一维后,在算第k层的d[i][j]时d[i][k]d[j][k]中存的仍然是第k1层的结果.
是不是这样呢?
要想看d[i][k]d[j][k]中存的是不是第k1层的结果.我们可以考虑d[i][k]d[j][k]在k层时什么时候会更新
先考虑d[i][k]什么时候更新

d[k][i][j]=min(d[k1][i][j],d[k1][i][k]+d[k1][j][k])

j=k时显然第k1层的d[i][k]会更新,我们把j=k带入有=d[k-1][i][k]

d[k][i][k]=min(d[k1][i][j],d[k1][i][k]+d[k1][k][k])

由于d[k1][k][k]=0,有

d[k][i][k]=d[k1][i][k]

所以这是等价变形的.
然后可以考虑i=k时,情况是一样的.
然后我们就可以证明这两个状态转移方程是等价的.也就有了新的状态转移方程

d[i][j]=min(d[i][j],d[i][k]+d[k][j])

kij or ijk

从dp的角度考虑也就很自然的会得出循环k在在最外层.
具体的为什么在最外层知乎上已经很多回答了.可以看下面的链接
为什么是kij不是ijk

应用

求多源最短路

AcWing 1125. 牛的旅行

题意是给了一堆牧场

#include <bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i = (a); i <= (b); ++i)
#define fep(i,a,b) for(int i = (a); i >= (b); --i)
#define pii pair<int, int>
#define pdd pair<double,double>
#define ll long long
#define db double
#define endl '\n'
#define x first
#define y second
#define pb push_back
#define vi vector<int>


using namespace std;

int n,m;
const int N=200;
const db inf=1e20;
db d[N][N],mv[N];
pdd a[N];
char g[N][N];

void solve() {
	cin>>n;
	rep(i,1,n) {
		cin>>a[i].x>>a[i].y;
	}
	rep(i,1,n) {
		cin>>g[i]+1;
	}


	auto dist=[&](pdd a, pdd b) {
		db dx=a.x-b.x;
		db dy=a.y-b.y;
		return sqrt(dx*dx+dy*dy);
	};

	rep(i,1,n) {
		rep(j,1,n) {
			if(i==j) {
				d[i][j]=0;
			} else if(g[i][j]=='1') {
				d[i][j]=dist(a[i],a[j]);
			} else {
				d[i][j]=inf;
			}
		}
	}

	//跑一遍floyd
	rep(k,1,n) {
		rep(i,1,n) {
			rep(j,1,n) {
				d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
			}
		}
	}

	//预处理出来每个点到连通块其他点的最短路的最大值
	db ans1=0,ans2=inf;

	rep(i,1,n) {
		rep(j,1,n) {
			if(d[i][j]<inf) {
				mv[i]=max(mv[i],d[i][j]);
			}
		}
		ans1=max(ans1,mv[i]);
	}
//	cout<<ans2<<endl;

	rep(i,1,n) {
		rep(j,1,n) {
			if(d[i][j]>=inf) {
				ans2=min(ans2,mv[i]+mv[j]+dist(a[i],a[j]));
			}
		}
	}
	cout<<setiosflags(ios::fixed)<<setiosflags(ios::right)<<setprecision(6)<<max(ans1,ans2)<<endl;
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
//	freopen("1.in", "r", stdin);
	int _;
//	cin>>_;
//	while(_--)
	solve();
	return 0;
}

求传递闭包

传递闭包:在有向图中,如果a->b,并且b->c那么a->c.换句话说将所有能间接到达的点都连上直接的边后的图就是原图的传递闭包

oiwiki上面用bitset进行的优化
1708700577628.png

AcWing 343. 排序

思路:
da4c44307aa5b7606ac68dada80e109.jpg
1d6a83bdcd9dbc288cb692eca7916df.jpg
时间复杂度:O(mn3)
代码有点长

#include <bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i = (a); i <= (b); ++i)
#define fep(i,a,b) for(int i = (a); i >= (b); --i)
#define pii pair<int, int>
#define pdd pair<double,double>
#define ll long long
#define db double
#define endl '\n'
#define x first
#define y second
#define pb push_back
#define vi vector<int>


using namespace std;

void solve() {
	int n,m;
	
	while(cin>>n>>m,n||m){
		vector<vector<int>>g(n+1,vector<int>(n+1));
		vector<vector<int>>d(n+1,vector<int>(n+1));
		auto floyd=[&](){
			rep(k,0,n-1){
				rep(i,0,n-1){
					rep(j,0,n-1){
						d[i][j]|=d[i][k]&&d[k][j];
					}
				}
			}		
		};
		auto check=[&](){
			rep(i,0,n-1){
				if(d[i][i])	return 2;
			}
			rep(i,0,n-1){
				rep(j,0,i-1){
					if(!d[i][j]&&!d[j][i]){
						return 0;
					}
				}
			}
			return 1;
		};
		int type=0,turn=0;
		rep(i,1,m){
			string s;
			cin>>s;
			int u=s[0]-'A',v=s[2]-'A';
			if(!type){
				d[u][v]=1;
				floyd();
				type=check();
				if(type){
					turn=i;
				}
			}
		}
		if(!type){
			cout<<"Sorted sequence cannot be determined."<<endl;
		}else if(type==2){
			cout<<"Inconsistency found after "<<turn<<" relations."<<endl;
		}else{
			vector<int>st(n+1,0);
			auto getmn=[&](){
				rep(i,0,n-1){
					if(!st[i]){
						int flag=0;
						rep(j,0,n-1){
							if(!st[j]&&d[j][i]){
								flag=1;
								break;
							}
						}
						if(!flag){
							st[i]=1;
							char c='A'+i;
							return c;
						}
					}
				}
			};
			cout<<"Sorted sequence determined after "<<turn<<" relations: ";
			rep(i,0,n-1){
				cout<<getmn();
			}
			cout<<"."<<endl;
		}
	}
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
//	freopen("1.in", "r", stdin);
	int _;
//	cin>>_;
//	while(_--)
	solve();
	return 0;
}

考虑加上优化

每加入一条边之后其实并不需要再重新跑一遍floyd,我们只需要考虑这条新加入的边会产生什么影响即可。
8df63bc478d463a15c581fbb0d4c8b6.jpg
时间复杂度:O(mn2)

#include <bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i = (a); i <= (b); ++i)
#define fep(i,a,b) for(int i = (a); i >= (b); --i)
#define pii pair<int, int>
#define pdd pair<double,double>
#define ll long long
#define db double
#define endl '\n'
#define x first
#define y second
#define pb push_back
#define vi vector<int>


using namespace std;

void solve() {
	int n,m;
	
	while(cin>>n>>m,n||m){
		vector<vector<int>>g(n+1,vector<int>(n+1));
		vector<vector<int>>d(n+1,vector<int>(n+1));
		auto check=[&](){
			rep(i,0,n-1){
				if(d[i][i])	return 2;
			}
			rep(i,0,n-1){
				rep(j,0,i-1){
					if(!d[i][j]&&!d[j][i]){
						return 0;
					}
				}
			}
			return 1;
		};
		
		int type=0,turn=0;
		rep(i,1,m){
			string s;
			cin>>s;
			int u=s[0]-'A',v=s[2]-'A';
			if(!type){
				d[u][v]=1;
				auto kkk=[&](){
					rep(i,0,n-1){
					    //处理能到达v能到的所有点
						if(d[v][i]) d[u][i]=1;
						//所有能到u的点都能到v
						if(d[i][u]) d[i][v]=1;
						
						//所有能到u的点不包含u
						if(d[i][u]){
							rep(j,0,n-1){
								//所有v能到的点不包含v
								if(d[v][j]){
									d[i][j]=1;
								}
							}
						}
					}
				};
				kkk();
				type=check();
				if(type){
					turn=i;
				}
			}
		}
		if(!type){
			cout<<"Sorted sequence cannot be determined."<<endl;
		}else if(type==2){
			cout<<"Inconsistency found after "<<turn<<" relations."<<endl;
		}else{
			vector<int>st(n+1,0);
			auto getmn=[&](){
				rep(i,0,n-1){
					if(!st[i]){
						int flag=0;
						rep(j,0,n-1){
							if(!st[j]&&d[j][i]){
								flag=1;
								break;
							}
						}
						if(!flag){
							st[i]=1;
							char c='A'+i;
							return c;
						}
					}
				}
			};
			cout<<"Sorted sequence determined after "<<turn<<" relations: ";
			rep(i,0,n-1){
				cout<<getmn();
			}
			cout<<"."<<endl;
		}
	}
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
//	freopen("1.in", "r", stdin);
	int _;
//	cin>>_;
//	while(_--)
	solve();
	return 0;
}

求无向图的最小环

AcWing 344. 观光之旅
oiwiki上面无向图找环说的很清楚。
1708703766532.png
关于这道题目的一个难点是记录状态。

#include <bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i = (a); i <= (b); ++i)
#define fep(i,a,b) for(int i = (a); i >= (b); --i)
#define pii pair<int, int>
#define pdd pair<double,double>
#define ll long long
#define db double
#define endl '\n'
#define x first
#define y second
#define pb push_back
#define vi vector<int>


using namespace std;

void solve() {
	int n,m;
	cin>>n>>m;
	//g用于存原图,因为求最小环的时候需要原图中的边长
	vector<vi>g(n+1,vi(n+1,0x3f3f3f3f));
	vector<vi>pos(n+1,vi(n+1));
	//d用于做floyd的最短路径
	rep(i,1,m){
		int u,v,w;
		cin>>u>>v>>w;
		g[u][v]=g[v][u]=min(g[u][v],w);
	}
	vector<vi>d=g;
	int res=0x3f3f3f3f;
	vi ans;
	auto dfs=[&](auto &&dfs,int i, int j)->void{
		if(!pos[i][j])	return;
		int k=pos[i][j];
		dfs(dfs,i,k);
		ans.pb(k);
		dfs(dfs,k,j);
	};
	auto get=[&](int i, int j,int k){
		ans.clear();
		ans.pb(k);
		ans.pb(i);
		dfs(dfs,i,j);
		ans.pb(j);
	};
	rep(k,1,n){
		//枚举i,j找最小环
		rep(i,1,k-1){
			rep(j,i+1,k-1){
				if(res>d[i][j]+g[i][k]+g[k][j]){
					res=d[i][j]+g[i][k]+g[k][j];
					get(i,j,k);
				}
			}			
		}
		
		//正常floyd
		rep(i,1,n){
			rep(j,1,n){
				if(d[i][j]>d[i][k]+d[k][j]){
					d[i][j]=d[i][k]+d[k][j];
					pos[i][j]=k;
				}
			}
		}
	}
	if(res==0x3f3f3f3f){
		cout<<"No solution."<<endl;
	}else{
		for(auto it:ans){
			cout<<it<<' ';
		}
		cout<<endl;
	}
}

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
//	freopen("1.in", "r", stdin);
	int _;
//	cin>>_;
//	while(_--)
	solve();
	return 0;
}

恰好经过k条边的最短路径

posted @   cxy8  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
点击右上角即可分享
微信分享提示