一、Floyd算法

如何求任意两点最短路?我们可以运行n次SPFA或Dijkstra求得,

而Floyd算法能在\(O(N^3)\)的时间复杂度内求出图中任意两点的最短路(多源最短路),且代码十分简短。

Floyd算法(弗洛伊德算法)的本质是动态规划。设\(f(k,i,j)\)表示"由若干个编号不超过k的节点中转后"从\(i\)到\(j\)的最短路。

该"动态规划"有两个决策,一是经过编号不超过\(k-1\)的节点由\(i\)到\(j\),二是先由\(i\)到\(k\),再由\(k\)到\(j\)。

我们很容易写出此时的转移方程:

\[f(k,i,j)=min(f(k-1,i,j),f(k-1,i,k)+f(k-1,k,j))
\]

初始\(f\)数组所有值均为正无穷,随后令\(f(0,i,j)=maps(i,j)\),其中\(map(i,j)\)为邻接矩阵。

由于\(k\)是动态规划的阶段,因此\(k\)为最外层循环,可以得到如下代码:

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

算法最终\(f(n,i,j),i\in [1,n],j\in [1,n]\)为最终答案。

显然三维数组\(f\)在高强度数据下爆内存,使得该算法失去用途。我们发现,求出\(f(k,i,j)\)只与\(f\)的\(k-1\)层有关,因此我们可以用滚动数组的方式将第一维滚去,此时的转移方程为:

\[f(i,j)=min(f(i,j),f(i,k)+f(k,j))
\]

最终\(f(i,j),i\in [1,n],j\in [1,n]\)就保存了由\(i\)到\(j\)的最短路。

最终Floyd的主体部分:

for(int k=1;k<=n;k++)
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]);

模板:

#include<bits/stdc++.h>
using namespace std;
int f[100][100],n,m;
int main()
{ scanf("%d%d",&n,&m);
memset(f,127,sizeof(f));
for(int i=1,u,w,v;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
f[u][v]=min(w,f[u][v]);
}
for(int i=1;i<=n;i++) f[i][i]=0;
for(int k=1;k<=n;k++)
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]);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
printf("%d ",f[i][j]);
printf("\n");
}
return 0;
}

二、Floyd算法的应用

1. 传递闭包

给出若干个元素以及他们的两两关系,如果这些元素具有传递性,我们就可以推出尽可能多的元素之间的关系。

解决"利用元素的传递性求出尽可能多的元素的关系"这类问题的算法就叫做传递闭包。

此时\(f(i,j)\)数组的意义(这里为link数组)变为\((i,j)\)是否具有关系,有关系\(f(i,j)=1\),无关系\(f(i,j)=0\)。

例如,利用传递闭包可以快速地求出图中两点是否可以直接/间接到达。

#include<bits/stdc++.h>
using namespace std;
int link[1000][1000];
int main()
{
int m,n,u,v,w,ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
link[u][v]=1;//u->v可达
}
for(int i=1;i<=n;i++) link[i][i]=1;//自己到自己可达
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
link[i][j]=link[i][j]||(link[i][k]&&link[k][j]);
//link[i][j]=link[i][j]|(link[i][k]&link[k][j]);
//这两种写法都可行,具体解释为i,j既可以经过不大于k-1的节点连通,也可以由i经过k中转到j来连通。
}
for(int i=1;i<=n;i++){//某两点是否可达
for(int j=1;j<=n;j++){
printf("%d ",link[i][j]);
}
printf("\n");
}
return 0;
}

除了这种方法,我们还可以使用\(bitset\)代替这个二维数组。

设:\(bitset<1000> link[1000]\),为长度为1000的一维数组,其中每一格都有长度为1000的二进制串。

其中link可以直接调用这个二进制串的下标,例如\(link[3][5];\)。

此外需要注意的是二进制串中只能存储0或1。

以下列举一些可能会用到的函数:

bitset<100> bs;

bs.count();//返回bs串中1的个数

bs.size()//返回bs串的位数

bs.any()//返回bs串是否有1; bs.none()//返回bs串是否没有1

bs.set()//将bs串全部位变为1; bs.set(p)//将p+1位变为1

bs.reset()//将bs串全部位变为0; bs.reset(p)//将p+1位变为0

bs.flip()//将bs串取反(flip v.快速翻转); bs.flip(p)//只将p+1位取反

利用\(bitset\)实现的传递闭包(核心代码):

int m,n,u,v;
int ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
link[u][v]=1;
}
for(int i=1;i<=n;i++) link[i][i]=1;//自身到自身可达
for(int i=1;i<=n;i++)//Floyd
for(int j=i+1;j<=n;j++){
if(link[i][j])//如果i-->j有连接
link[i]=link[i]|link[j];//那么与i连通的点与j也会连通
}

例1:P2881 [USACO07MAR]排名的牛Ranking the Cows

相当于给定一张有向图,求出还需要加多少条边才能使这个图连通。

设x强于y,y强于z,那么一定x强于z,由于具有传递性,因此可以用传递闭包来做。

Code:

#include<bits/stdc++.h>
using namespace std;
bitset<1010> link[1010];
int main()
{
int m,n,x,y,ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
link[x][y]=1;
}
for(int i=1;i<=n;i++) link[i][i]=1;
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++){
if(link[i][k])
link[i]=link[i]|link[k];
}
for(int i=1;i<=n;i++) ans=ans+link[i].count();
ans-=n;//减去自己与自己连通的边数
printf("%d",n*(n-1)/2-ans);
return 0;
}

例2:P2419 [USACO08JAN]牛大赛Cow Contest

每头牛编程强度同样具有传递性,因此可以确定尽量多的每头牛之间的两两关系。

如果一头牛与其他的任意一头牛都能确定关系,那么我们就能知道这头牛的能力排名。

Code:

#include<bits/stdc++.h>
using namespace std;
int f[120][120];
int main()
{
int m,n;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
f[u][v]=1;
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
f[i][j]=f[i][j]|(f[i][k]&f[k][j]);
}
int ans=0;
for(int i=1;i<=n;i++){
int cnt=1;
for(int j=1;j<=n;j++){
if(i==j) continue;
if(!f[i][j]&&!f[j][i]){//与其他牛的关系都能确定
cnt=0;break;
}
}
ans+=cnt;
}
printf("%d",ans);
return 0;
}

2.快速求出多源最短路

例1:P1522 牛的旅行 Cow Tours

利用Floyd算法快速求出任意两点的最短路,以便求出任一点到其他点的最远距离。

此时对不连通的牧场增添一条边来更新直径。

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 200010
using namespace std;
double f[200][200],x[200],y[200];
double len[200],glen,glen2=INF;
int n;char ch;
inline double calc_dis(int a,int b){
return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]);
for(int i=1;i<=n;i++)//初始化f数组
for(int j=1;j<=n;j++){
cin>>ch;
if(ch=='1') f[i][j]=calc_dis(i,j);
else if(i!=j) f[i][j]=INF;
}
for(int k=1;k<=n;k++)//最短路
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]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++) if(f[i][j]<INF) len[i]=max(len[i],f[i][j]);//求出该点到其他点的最大距离
glen=max(glen,len[i]);//求出原图的直径
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)//对不连通的牧场连一条边
if(f[i][j]>=INF) glen2=min(glen2,len[i]+calc_dis(i,j)+len[j]);//求出连边后最小的直径
cout<<fixed<<setprecision(6)<<max(glen2,glen);
return 0;
}

3.解决双权值问题

在解决双权值问题时,如果我们同时考虑两个权值会显得很麻烦。

此时应该枚举一个权值再计算另外一个权值。

利用Floyd算法解决双权值问题的核心在于巧妙地变化最外层循环k的含义。

我们之前已经提到过,Floyd求出\(i\)到\(j\)的最短路是不断由节点\(k\)中转更新得到的,即\(i\),\(j\)的最短路经过节点不断\(1,2,...,k-1,k\)松弛得到。

此时相当于不断枚举节点k,来更新i,j的最短路。

例1:P1119 灾后重建

每个村庄都有建成的时间,同时询问节点\(x\)到节点\(y\)通车最短距离。

我们知道原Floyd算法中\(k\)的意义是:

  • 对于此时枚举的\(k\),任意节点\(i\),\(j\)只允许通过编号不大于\(k\)的节点更新最短路。

因为本题中有只有在询问的时间内修好的村庄才允许通车,此时\(k\)的意义为:

  • 对于此时枚举的\(k\),任意节点\(i\),\(j\)只允许通过建成时间小于询问时间的节点更新最短路。

这与Floyd算法中\(k\)循环的思路相同,因此可以使用Floyd算法解决。

剩余需要注意的地方:

  1. 本题包含节点0。
  2. 由于题目的询问时间具有单调性,所以只要不断枚举此时可用于更新的节点\(k\)即可。
  3. 若\(i\),\(j\)经过节点\(k\)更新,则节点\(k\)需要打上标记避免下次重复无意义地进入循环(会TLE)。

Code:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
int vis[201],n,m,q;;
int t[201],f[201][201],tot;
int from[50001],to[50001],day[50001];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&t[i]);//第i个村庄修好的日期
for(int i=0;i<n;i++) for(int j=0;j<n;j++) f[i][j]=INF;
for(int i=1,u,v,w;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
f[u][v]=f[v][u]=w;
}
for(int i=0;i<n;i++) f[i][i]=0;
scanf("%d",&q);
for(int i=1;i<=q;i++)//询问 ,第day[i]天从from[i]到to[i]的最短路
scanf("%d%d%d",&from[i],&to[i],&day[i]);
for(int num=1;num<=q;num++){//对于每个询问
for(int k=0;k<n;k++)
if(t[k]<=day[num]&&!vis[k]){//i,j经由建成时间小于询问时间的节点k来更新最短路
vis[k]=1;//做标记
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
f[i][j]=min(f[i][j],f[i][k]+f[k][j]);//求得最短路
}
if(f[from[num]][to[num]]!=INF&&t[from[num]]<=day[num]&&t[to[num]]<=day[num])
//此时询问的节点连通,且起点与终点的建成时间不大于询问的时间才有答案
printf("%d\n",f[from[num]][to[num]]);
else printf("-1\n");//不能通车
}
return 0;
}

[总结]Floyd算法及其应用的更多相关文章

  1. 最短路径之Floyd算法

    Floyd算法又称弗洛伊德算法,也叫做Floyd's algorithm,Roy–Warshall algorithm,Roy–Floyd algorithm, WFI algorithm. Floy ...

  2. 最短路径—Dijkstra算法和Floyd算法

    原文链接:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/31/2615833.html 最后边附有我根据文中Dijkstra算法的描述使用jav ...

  3. 最短路径问题——floyd算法

    floyd算法和之前讲的bellman算法.dijkstra算法最大的不同在于它所处理的终于不再是单源问题了,floyd可以解决任何点到点之间的最短路径问题,个人觉得floyd是最简单最好用的一种算法 ...

  4. floyd算法小结

    floyd算法是被大家熟知的最短路算法之一,利用动态规划的思想,f[i][j]记录i到j之间的最短距离,时间复杂度为O(n^3),虽然时间复杂度较高,但是由于可以处理其他相似的问题,有着广泛的应用,这 ...

  5. Uvaoj 10048 - Audiophobia(Floyd算法变形)

    1 /* 题目大意: 从一个点到达另一个点有多条路径,求这多条路经中最大噪音值的最小值! . 思路:最多有100个点,然后又是多次查询,想都不用想,Floyd算法走起! */ #include< ...

  6. Floyd算法(三)之 Java详解

    前面分别通过C和C++实现了弗洛伊德算法,本文介绍弗洛伊德算法的Java实现. 目录 1. 弗洛伊德算法介绍 2. 弗洛伊德算法图解 3. 弗洛伊德算法的代码说明 4. 弗洛伊德算法的源码 转载请注明 ...

  7. Floyd算法(二)之 C++详解

    本章是弗洛伊德算法的C++实现. 目录 1. 弗洛伊德算法介绍 2. 弗洛伊德算法图解 3. 弗洛伊德算法的代码说明 4. 弗洛伊德算法的源码 转载请注明出处:http://www.cnblogs.c ...

  8. Floyd算法(一)之 C语言详解

    本章介绍弗洛伊德算法.和以往一样,本文会先对弗洛伊德算法的理论论知识进行介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现. 目录 1. 弗洛伊德算法介绍 2. 弗洛伊德算法图解 3 ...

  9. 最短路径---Dijkstra/Floyd算法

    1.Dijkstra算法基础: 算法过程比prim算法稍微多一点步骤,但思想确实巧妙也是贪心,目的是求某个源点到目的点的最短距离,总的来说dijkstra也就是求某个源点到目的点的最短路,求解的过程也 ...

  10. 最短路径(Floyd)算法

    #include <stdio.h>#include <stdlib.h>/* Floyd算法 */#define VNUM 5#define MV 65536int P[VN ...

随机推荐

  1. 【简介】OpenOCD 由jtag操作到parport driver流程

    1. 定义 jtag_command_type 在 OpenOCD 中,JTag 命令在枚举 jtag_command_type 中定义,定义如下: /** * The type of the @c ...

  2. Redis 的键命令、HyperLogLog 命令、脚本命令、连接命令、服务器命令

    Redis 的键命令.HyperLogLog 命令.脚本命令.连接命令.服务器命令 Redis 的键命令 Redis 的键命令主要用于管理 Redis 的键,如删除键.查询键.修改键及设置某个键等. ...

  3. git 使用和一些错误

    一.简单使用 Git是目前世界上最先进的分布式版本控制系统,用于自动记录每次文件的改动,但是和所有版本控制系统一样,只能跟踪文本文件的改动,比如TXT文件,网页,所有的程序代码等,而图片.视频这些二进 ...

  4. 洛谷1514 引水入域 dp+记忆化搜索

    题目链接:https://www.luogu.com.cn/problem/P1514 题意大致是:给定一个(n,m)的数值矩阵,可以在第一行建造水库,如果一个格子周围的某格子值小于它,那水就可以流到 ...

  5. 动态规划-Minimum Cost to Merge Stones

    2019-07-07 15:48:46 问题描述: 问题求解: 最初看到这个问题的时候第一反应就是这个题目和打破气球的题目很类似. 但是我尝试了使用dp将问题直接转为直接合并到一个堆问题复杂度迅速提高 ...

  6. [源码分析] 从FlatMap用法到Flink的内部实现

    [源码分析] 从FlatMap用法到Flink的内部实现 0x00 摘要 本文将从FlatMap概念和如何使用开始入手,深入到Flink是如何实现FlatMap.希望能让大家对这个概念有更深入的理解. ...

  7. 强化学习之一:从TensorFlow开始(Start from TensorFlow)

    本文是对Tensorflow官方教程的个人(tomqianmaple@outlook.com)中文翻译,供大家学习参考. 官方教程链接 tf的扬帆起航Getting Started With Tens ...

  8. 学习Angular1

    教程: 参考教程: https://www.runoob.com/angularjs/angularjs-tutorial.html 一.angular的简介AngularJS 是一个 JavaScr ...

  9. 045.集群存储-CSI存储机制

    一 CSI存储机制 1.1 CSI简介 Kubernetes从1.9版本开始引入容器存储接口Container Storage Interface(CSI)机制,用于在Kubernetes和外部存储系 ...

  10. Spring Boot 邮件发送

    pom文件依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...