显然Floyed算法是一个简短而好理解的算法,这里指的好理解是
因为Floyed的代码长度不大,实在没理解都可以背下来,所以说是好理解,实际上是真的好理解吗?我们来看看
最基础的Floyed
Floyed是什么?自然是用来求多源最短路的啦,时间效率是O(n^3),有人会问那我不对每个点做一遍SPFA或dijkstra堆优化,时间效率是O(n^2logn)那不是快很多?实际上因为Floyed常数很小,所以真正比起来,时间效率差不多,所以你是愿意只写5行代码,还是冗杂地打这么长的代码???
Floyed其实用到的是DP的思想f[k,i,j] = min(f[k-1,i,k]+f[k-1,k,j] , f[k-1,i,j]),k是我们用来枚举的断点,说是断点其实就是前K个,只对后面我们将最小环还有其他应用有很大的帮助,因为k是不断递增的,我们可以利用这个性质来做许多题目。通过枚举k,我们可以很快得出任意一点x->y的最短路径。
下面贴上代码
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
是不是简单而好理解???
传递闭包
这是个啥东西?如果我们假设a可以到b,b可以到c,那么a是不是可以到c???实际上这个东西就是指图的连通性,用Floyed处理是不是快很多???比较dfs的话,是优化了很多了。
下面贴上代码
REP(k,1,n)
{
REP(i,1,n)
{
REP(j,1,n)
{
a[i][j]|=(a[i][k]&a[k][j]);
}
}
}
可以做一下POJ3660Cow Contest是一道裸题
注:&是两个二进制位上有0,就是0,|操作是有1,就是1.
最小环
这个东西怎么说呢???其实我们可以发现如果图里面存在环的话,我们假设i,j间有一条简单路径,如果有环我们可以通过另一个点k到达,是不是跟Floyd算法很相似???所以我们最外层的这个循环,我们用来枚举前k个点,找前k个点中的最小环,后面的Floyd照常进行就可以了,下面的代码还有记录路径,pre[i][j],表示从i到j离j最近的一个点。
POJ1734
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
#define REP(i,a,b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++i)
inline int read()
{
char c = getchar();register int fg = 1, sum = 0;
while(c < '0' || c > '9')
{
if(c == '-')fg = -1;
c = getchar();
}
while(c >= '0' && c <= '9')
{
sum = sum * 10 + c - '0';
c = getchar();
}
return fg * sum;
}
int n, m;
int a[200][200],dis[200][200],pre[200][200],path[100000];
#define inf 0x7ffffff
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(a,0,sizeof(a));
memset(pre,0,sizeof(pre));
memset(dis,0,sizeof(dis));
memset(path,0,sizeof(path));
REP(i,1,n)REP(j,1,n)a[i][j] = inf,pre[i][j] = i;
REP(i,1,m)
{
int x,y,z;
x = read(), y = read(), z = read();
a[x][y] = a[y][x] = min(a[y][x],z);
}
memcpy(dis,a,sizeof(dis));
int ans = inf,num = 0;
REP(k,1,n)
{
for(int i = 1; i < k; i++)
{
for(int j = i+1; j < k; j++)
{
int tmp = dis[i][j] + a[i][k] + a[k][j];
if(tmp < ans)
{
ans = tmp;
num = 0;
int now = j;
while(now != i)
{
path[num++] = now;
now = pre[i][now];
}
path[num++] = i,path[num++] = k;
}
}
}
REP(i,1,n)
{
REP(j,1,n)
{
if(dis[i][j] > dis[i][k]+dis[k][j])
{
dis[i][j] = dis[i][k] + dis[k][j];
pre[i][j] = pre[k][j];
}
}
}
}
if(ans == inf){printf("No solution.\n");continue;}
int i;
for(i = 0; i < num-1; i++)printf("%d ",path[i]);
printf("%d\n",path[i]);
}
return 0;
}
其他用途
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
#define REP(i,a,b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++i)
inline int read()
{
char c = getchar();register int fg = 1, sum = 0;
while(c < '0' || c > '9')
{
if(c == '-')fg = -1;
c = getchar();
}
while(c >= '0' && c <= '9')
{
sum = sum * 10 + c - '0';
c = getchar();
}
return fg * sum;
}
int n, m;
int a[200][200],dis[200][200],pre[200][200],path[100000];
#define inf 0x7ffffff
int main()
{
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(a,0,sizeof(a));
memset(pre,0,sizeof(pre));
memset(dis,0,sizeof(dis));
memset(path,0,sizeof(path));
REP(i,1,n)REP(j,1,n)a[i][j] = inf,pre[i][j] = i;
REP(i,1,m)
{
int x,y,z;
x = read(), y = read(), z = read();
a[x][y] = a[y][x] = min(a[y][x],z);
}
memcpy(dis,a,sizeof(dis));
int ans = inf,num = 0;
REP(k,1,n)
{
for(int i = 1; i < k; i++)
{
for(int j = i+1; j < k; j++)
{
int tmp = dis[i][j] + a[i][k] + a[k][j];
if(tmp < ans)
{
ans = tmp;
num = 0;
int now = j;
while(now != i)
{
path[num++] = now;
now = pre[i][now];
}
path[num++] = i,path[num++] = k;
}
}
}
REP(i,1,n)
{
REP(j,1,n)
{
if(dis[i][j] > dis[i][k]+dis[k][j])
{
dis[i][j] = dis[i][k] + dis[k][j];
pre[i][j] = pre[k][j];
}
}
}
}
if(ans == inf){printf("No solution.\n");continue;}
int i;
for(i = 0; i < num-1; i++)printf("%d ",path[i]);
printf("%d\n",path[i]);
}
return 0;
}
FLoyd算法用来解决其他的一些多源最短路的问题有想不到的妙处,比如我们所说的洛谷1119灾后重建为例:
B地区在地震过后,所有村庄都造成了一定的损毁,而这场地震却没对公路造成什么影响。但是在村庄重建好之前,所有与未重建完成的村庄的公路均无法通车。换句话说,只有连接着两个重建完成的村庄的公路才能通车,只能到达重建完成的村庄。
题目描述给出B地区的村庄数N,村庄编号从0到N-1,和所有M条公路的长度,公路是双向的。并给出第i个村庄重建完成的时间t[i],你可以认为是同时开始重建并在第t[i]天重建完成,并且在当天即可通车。若t[i]为0则说明地震未对此地区造成损坏,一开始就可以通车。之后有Q个询问(x, y, t),对于每个询问你要回答在第t天,从村庄x到村庄y的最短路径长度为多少。如果无法找到从x村庄到y村庄的路径,经过若干个已重建完成的村庄,或者村庄x或村庄y在第t天仍未重建完成 ,则需要返回-1。
输入输出格式
输入格式:输入文件rebuild.in的第一行包含两个正整数N,M,表示了村庄的数目与公路的数量。
第二行包含N个非负整数t[0], t[1], …, t[N – 1],表示了每个村庄重建完成的时间,数据保证了t[0] ≤ t[1] ≤ … ≤ t[N – 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是不下降的。
输出格式:
输出文件rebuild.out包含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%的数据,有t[i] = 0,其中有20%的数据有t[i] = 0且N>50;
对于50%的数据,有Q≤100;
对于100%的数据,有N≤200,M≤N*(N-1)/2,Q≤50000,所有输入数据涉及整数均不超过100000。
看数据范围我们很容易想到Floyed,但是怎么做呢???我们不妨假设我们所枚举的k点是指的前k个村庒,由于出题者良心地帮我们排好了顺序,于是我们在Floyd的时候枚举询问,由于你枚举到这个点k,经过k点之前的最短路是被更新出来了的,是不是很明了???
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
#define REP(i,a,b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++i)
inline int read()
{
char c = getchar();register int fg = 1, sum = 0;
while(c < '0' || c > '9')
{
if(c == '-')fg = -1;
c = getchar();
}
while(c >= '0' && c <= '9')
{
sum = sum * 10 + c - '0';
c = getchar();
}
return fg * sum;
}
const int maxn = 100000+10;
int n,m,t[500],qx[maxn],qy[maxn],qt[maxn],dis[500][500];
int main()
{
n = read(), m = read();
memset(dis,0x3f,sizeof(dis));
REP(i,0,n-1)t[i] = read();
REP(i,1,m)
{
int x,y,z;
x = read(), y = read(), z = read();
dis[x][y] = z,dis[y][x] = z;
}
int q = read();
REP(i,1,q)
{
qx[i] = read(), qy[i] = read(), qt[i] = read();
}
int now = 1;
t[n] = t[n-1] + 1;
REP(k,0,n-1)
{
while(now <= q && t[k] > qt[now])
{
int ans = dis[qx[now]][qy[now]];
if(t[qx[now]] >= t[k] || t[qy[now]] >= t[k])ans = -1;
printf("%d\n",ans == dis[n][n] ? -1 : ans);
now++;
}
REP(i,0,n-1)
{
REP(j,0,n-1)
{
if(dis[i][j] > dis[i][k] + dis[k][j])
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
while(now <= q)
{
int ans = dis[qx[now]][qy[now]];
printf("%d\n",ans == dis[n][n] ? -1 : ans);
now++;
}
}