Floyd算法学习笔记

Floyd算法

这是最简单 码量最低 最无脑我们学的第一个最短路径算法。

他的本质其实是动态规划。万恶的 DP !

要想求任意两点最短路,我们就要把它存储起来(废话),由于 Floyd 的运算方法的特性,必须用邻接矩阵来储存。讲句废话,邻接矩阵的储存方法是如果 ij 之间有一条边,则 f[i][j] 表示距离,否则初始化为无穷大,比如 2147483648 0x3f3f3f3f 之类的。

方法

核心就是“三角不等式”——也就是说,如果绕路要比直接走近,那么就绕路。注意,这里的直接走并不仅仅是指走直线,它也可能是绕路更新以后变成当前的“直线”距离的。

那么,具体我们应该怎样实现呢?必须遵循一定的规律。考虑到 DP 是具有“阶段”这个概念的,对于这个算法也一样。枚举每一个点,然后看两个点之间经过这个点会不会更短。具体代码就是这样:

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

因此,这种算法的时间复杂度为 O(n3)。似乎效率较为低下,但是其实不然,因为:

  • Floyd 求出的是任意两点间的最短路。Dijkstra 和某种死了的算法都只能求出“单源最短路”。

  • Floyd 的一个性质是“传递闭包”。在解决某些看似与最短路毫无关系的问题时,这个性质却经常有很大的用处。

模板代码

B3647 【模板】Floyd为例

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=110;
int a[N][N];
int n,m;
int main(){
	cin>>n>>m;
	memset(a,0x3f,sizeof(a));
	for(int i=1;i<=n;i++)a[i][i]=0;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v>>w;
		a[u][v]=a[v][u]=min(a[u][v],w);
	}
	for(int k=1;k<=n;k++){
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++){
				a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++) cout<<a[i][j]<<" ";
		cout<<endl;
	}
}

应用 1:传递闭包

比较常见的例子有P1037 [NOIP2002 普及组] 产生数

这个题让人一眼就想到了 DFS。遗憾的是,这种情况下搜索会非常耗时,于是就会非常影响心情。对此我们可以用 Floyd 的传递闭包性质,在这里也就是说,假如 abbc 都是可行的,那么 ac 也是可行的。这就是所谓的传递性。这里我们只需要对弗洛伊德的模板算法略加修改,就可以得到正解。

for(int k=1;k<=n;k++){
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			a[i][j]=a[i][j]||(a[i][k]&&a[k][j]);
		}
	}
}

这样我们就能找到每一个数的变化方式,最后根据乘法原理,把他们都乘起来就可以了。代码:

点击查看代码
#include<bits/stdc++.h>
#define very_long __uint128_t
using namespace std;
const int N=110;
bool a[N][N];
int s[N];
void put_vl(__uint128_t a){
	if(a>9) put_vl(a/10);
	putchar(a%10+'0');
}
int wei(int x){
	int tot=0;
	do{
		tot++;
	}while(x=x/10);
}
unsigned long long int m;
very_long ans=1;
string n;
int main(){
	cin>>n>>m;
	memset(a,0,sizeof(a));
	for(int i=0;i<=9;i++)a[i][i]=1;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		a[u][v]=1;
	}
	for(int k=0;k<=9;k++){
		for(int i=0;i<=9;i++){
			for(int j=0;j<=9;j++){
				a[i][j]=(a[i][k]&&a[k][j])||a[i][j];
			}
		}
	}
	for(int i=0;i<=9;i++){
		for(int j=0;j<=9;j++)s[i]+=a[i][j];
	}
	int cnt=n.size();
	while(cnt--)ans*=s[n[cnt]-'0'];
	put_vl(ans);
}

__uint128_t是一种非常大的整数。由于积可能很大,必须用uint128或者是干脆用高精度)

应用 2:k条边的最短路

有些时候,我们希望找到一条最短路径,但是我们有些时候需要找到的是要“经过 k 条边”的最短路径。这个时候,原来的算法就不好用了。然而我们依然可以通过这个思路,定义一个数组 cc[k][i][j] 表示经过 i 条边的最短路即可。我们发现,这个过程类似于乘法,因为它是满足结合律的。这也就是说,我们可以把快速幂集成到这里。这样的话,整个过程的时间复杂度就是 O(n3logk)。我们以P2886 [USACO07NOV] Cow Relays G为例来展示一下代码。注意,这道题的不同之处在于,点的“编号”会达到 5000,但是实际的“数量”却仅仅是 200。因此我们需要用到离散化——也就是说把点进行编码,否则会超时。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct matrix{
	int x[201][201];
}d;
int n,t,s,e;
bool vis[1001];
int nm[1001],tot;
matrix operator *(matrix A,matrix B){
	matrix C;
	memset(C.x,0x3f,sizeof(C.x));
	for(int k=0;k<tot;k++)
		for(int i=0;i<tot;i++)
			for(int j=0;j<tot;j++)	C.x[i][j]=min(A.x[i][k]+B.x[k][j],C.x[i][j]);
	return C;
}
matrix Fast(matrix A,long long n){
	matrix S=A;
	n--;
	while(n){
		if(n&1){
			S=S*A;
		}
		A=A*A;
		n=n>>1;
	}
	return S;
}
int main(){
	cin>>n>>t>>s>>e;
	memset(d.x,0x3f,sizeof(d.x));
	for(int i=0;i<t;i++){
		int u,v,w;
		cin>>w>>u>>v;
		if(!vis[u])vis[u]=1,nm[u]=tot++;
		if(!vis[v])vis[v]=1,nm[v]=tot++;
		d.x[nm[u]][nm[v]]=d.x[nm[v]][nm[u]]=w;
	}
	d=Fast(d,n);
	cout<<d.x[nm[s]][nm[e]]<<endl;
}

本文作者:liyuanzhuo6811

本文链接:https://www.cnblogs.com/liyuanzhuo6811/p/18030942/floyd-learn-bi-ji

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   liyuanzhuo6811  阅读(24)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.