前言
Floyd-Warshall算法多用于求解以下三个问题:
- 多源最短路,任意两点的距离关系
- 图上的传递闭包,任意两点的连通关系
- 最小环路问题
void Floyd(){
//初始化
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
dis[i][j]=inf;
path[i][j]=j;
}
}
//更新最短路径
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j]+b[k]){
a[i][j]=a[i][k]+a[k][j]+b[k];
path[i][j]=path[i][k];
}
//输出结点u到v的路径
int tmp=u;
while(tmp!=v){
printf("%d ",tmp);
tmp=path[tmp][v];
}
printf("%d\n",v);
}
下面再用几个典型的例题来总结一下Floyd算法的简单应用。
求解传递闭包
题意
- N个人玩一个游戏,每两个人都要进行一场比赛
- 已知M个胜负关系,每个关系为 A B ,表示 A 比 B 强,胜负关系具有传递性
- 试问有多少场比赛的胜负无法预先得知
Input
第一行给出数据组数。
每组数据第一行给出 N 和 M(N , M <= 500)。
接下来 M 行,每行给出 A B,表示 A 可以胜过 B。
Output
对于每一组数据,判断有多少场比赛的胜负不能预先得知。注意 (a, b) 与 (b, a) 等价,即每一个二元组只被计算一次。
Sample Input
3
3 3
1 2
1 3
2 3
3 2
1 2
2 3
4 2
1 2
3 4
Sample Output
0
0
4
思路
F l o y d − W a r s h a l l Floyd-Warshall Floyd−Warshall 算法在求解传递闭包中的应用,根据输入数据创建邻接矩阵 d i s dis dis
- d i s [ a ] [ b ] = 1 dis[a][b]=1 dis[a][b]=1 表示 a a a 比 b b b 强
- d i s [ a ] [ b ] = 0 dis[a][b]=0 dis[a][b]=0 表示 a a a 与 b b b 的胜负关系不明
- d i s [ a ] [ b ] = 0 dis[a][b]=0 dis[a][b]=0 且 d i s [ b ] [ a ] = 0 dis[b][a]=0 dis[b][a]=0 表示 a a a 与 b b b 的胜负关系无法预先判断
注意:由于 F l o y d Floyd Floyd 算法求解传递闭包的时间复杂度为 O ( n 3 ) O(n^3) O(n3),为了防止超时,往往再加一步剪枝操作, d i s [ i ] [ j ] = d i s [ i ] [ j ] ∣ ( d i s [ i ] [ k ] dis[i][j]=dis[i][j]|(dis[i][k] dis[i][j]=dis[i][j]∣(dis[i][k]& d i s [ k ] [ j ] ) dis[k][j]) dis[k][j]),在这一步中,若 d i s [ i ] [ k ] dis[i][k] dis[i][k]等于0,那么后边的操作都是多余的,直接剪去即可。另一点需要注意的是三重循环中 i , j , k i,j,k i,j,k 的顺序。
代码实现
#include <iostream>
#include <queue>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <climits>
#include <cstring>
using namespace std;
int cnt,n,m,a,b;
int dis[510][510];
int main()
{
scanf("%d",&cnt);
while(cnt--){
scanf("%d%d",&n,&m);
memset(dis,0,sizeof(dis));
for(int i=0;i<m;i++){
scanf("%d%d",&a,&b);
dis[a][b]=1;
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
if(!dis[i][k])
continue;
for(int j=1;j<=n;j++){
dis[i][j]=dis[i][j]|(dis[i][k]&dis[k][j]);
}
}
}
int count=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j)
continue;
if(dis[i][j]==0&&dis[j][i]==0){
count++;
}
}
}
count/=2;
printf("%d\n",count);
}
return 0;
}
多源最短路问题
HDU - 1385 Minimum Transport Cost
题意
N 个城市之间进行运输,路过每个城市要交一定量的税,问运输过程中的最小花费(给定多对城市之间的运输)并输出路径。
Input
First is N, number of cities. N = 0 indicates the end of input.
The data of path cost, city tax, source and destination cities are given in the input, which is of the form:
a11 a12 … a1N
a21 a22 … a2N
…
aN1 aN2 … aNN
b1 b2 … bN
c d
e f
…
g h
where aij is the transport cost from city i to city j, aij = -1 indicates there is no direct path between city i and city j. bi represents the tax of passing through city i. And the cargo is to be delivered from city c to city d, city e to city f, …, and g = h = -1. You must output the sequence of cities passed by and the total cost which is of the form:
Output
From c to d :
Path: c–>c1–>…–>ck–>d
Total cost : …
…
From e to f :
Path: e–>e1–>…–>ek–>f
Total cost : …
如果有多条最短路径,则输出字典序最小的那一条路径
Sample Input
5
0 3 22 -1 4
3 0 5 -1 -1
22 5 0 9 20
-1 -1 9 0 4
4 -1 20 4 0
5 17 8 3 1
1 3
3 5
2 4
-1 -1
0
Sample Output
From 1 to 3 :
Path: 1–>5–>4–>3
Total cost : 21
From 3 to 5 :
Path: 3–>4–>5
Total cost : 16
From 2 to 4 :
Path: 2–>1–>5–>4
Total cost : 17
代码实现
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
const int inf=1e9;
const int maxn=110;
int n=100,m,u,v;
int a[maxn][maxn],b[maxn],path[maxn][maxn];
//path[i][j]保存了从i到j路径的第一个点(除i以外)
int main()
{
while(~scanf("%d",&n)){
if(n==0)
break;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&m);
a[i][j]=(m==-1?inf:m);
path[i][j]=j;
}
}
for(int i=1;i<=n;i++){
scanf("%d",&b[i]);
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(a[i][j]>a[i][k]+a[k][j]+b[k]){
a[i][j]=a[i][k]+a[k][j]+b[k];
path[i][j]=path[i][k];
}
else if(a[i][j]==a[i][k]+a[k][j]+b[k]){
path[i][j]=min(path[i][j],path[i][k]);
}
}
}
}
while(~scanf("%d%d",&u,&v)){
if(u==-1&&v==-1)
break;
printf("From %d to %d :\n",u,v);
printf("Path: %d",u);
int tmp=u;
while(tmp!=v){
printf("-->%d",path[tmp][v]);
tmp=path[tmp][v];
}
printf("\nTotal cost : %d\n",a[u][v]);
printf("\n");
}
}
return 0;
}
最小环问题
POJ - 1734 Sightseeing trip
题意
N N N 个路口, M M M 条双向道路,两个路口可以和多条道路相连,但没有一条道路可以将一个路口与自己相连。每条观光路线都由 y 1 , … , y k , k > 2 y_1,…, y_k,k>2 y1,…,yk,k>2 组成,其中 y i ( 1 < = i < = k − 1 ) y_i(1<=i<=k-1) yi(1<=i<=k−1) 连接路口 x i x_i xi 与 x i + 1 , y k x_{i+1}, y_k xi+1,yk 连接路口 x k x_k xk 与 x 1 , x 1 , … , x k x_1,x_1,…,x_k x1,x1,…,xk 应该各不相同,求解观光路线的最短路径,若不存在则输出 N o No No s o l u t i o n . solution. solution.
ps:这道题是 Special Judge 输出环的起点终点顺序无所谓。
Sample Input
5 7
1 4 1
1 3 300
3 1 10
1 2 16
2 3 100
2 5 15
5 3 20
Sample Output
1 3 5 2
思路
- 环的判断方法: d i s [ i ] [ j ] + a [ i ] [ k ] + a [ k ] [ j ] < I N F dis[i][j]+a[i][k]+a[k][j]<INF dis[i][j]+a[i][k]+a[k][j]<INF
- 在更新最短路之前,由于此时的 k k k 还没被用于更新最短路 d i s [ i ] [ j ] dis[i][j] dis[i][j],故可以先判断当前的 k k k(对应第一层循环)是否能构成环
- 若能构成环,则更新最小环的长度并存储路径( i i i 到 j j j的路径 + j − > k + k − > i + j->k + k->i +j−>k+k−>i)
代码实现
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int inf=1e8;
int n,m,u,v,w,tmp,tot=0;
int a[110][110],dis[110][110],path[110][110],ans[110];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
a[i][j]=dis[i][j]=inf;
path[i][j]=j;
}
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
a[u][v]=a[v][u]=min(w,a[u][v]);
dis[u][v]=dis[v][u]=a[u][v];
}
int res=inf;
for(int k=1;k<=n;k++){
for(int i=1;i<k;i++){
for(int j=1;j<i;j++){
if(dis[i][j]+a[i][k]+a[k][j]<res){
res=dis[i][j]+a[i][k]+a[k][j];
tot=0,tmp=i;
while(tmp!=j){
ans[tot++]=tmp;
tmp=path[tmp][j];
}
ans[tot++]=j;
ans[tot++]=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]=path[i][k];
}
}
}
}
if(res==inf)
printf("No solution.\n");
else{
for(int i=0;i<tot;i++){
printf("%d ",ans[i]);
}
printf("\n");
}
return 0;
}