问题:
给出一个图,问其中的有 (n) 个节点构成的边权和最小的环 ((ngeq 3)) 是多大。
暴力做法:
删除掉 (u) 和 (v) 之间的边,如何求出 (u o v) 的不经过该边的最短路,加上该边即可。
(Dijsktra):
在暴力算法的基础上,优化求最短路的过程。
枚举所有边,每一次求删除一条边之后对这条边的起点跑一次 (Dijkstra),道理同上。
复杂度:(O(m*nlogn))
(Floyd):
记原图中(u,v) 之间边的边权为 (val(u,v))。
(Floyd) 算法有一个性质:
当外层循环到 (k) (尚未开始第 (k) 次循环)时,最短路数组 (dis[u][v])中, 表示的是从 (u) 到 (v) 且仅经过编号在 ([1,k)) 区间中的点的最短路。
显然,一个环可以表示为 (u o v) 的不经过点 (k) 的最短路(+ u)到 (k) 的距离(+ v)到 (k) 的距离。但是正常的 (Floyd) 算法过程中,点 (k) 会把任意两点之间的最短路更新。如果 (u) 到 (v) 的最短路中出现了 (k) ,那么就不满足要求。
可以采用如下做法:
由最小环的定义可知其至少有三个顶点,设其中编号最大的顶点为 (maxn),环上与 (maxn) 相邻两侧的两个点为 (u,v) ,则在最外层循环枚举到 (maxn) 时,
该环的长度为:(val(u,v)+val(u,maxn)+val(v,maxn))。
故在循环时对于每个 (k),枚举满足 ((i<k,j<k)) 的 (i,j),更新答案即可。
复杂度:(O(n^3))
代码实现:
int floyd(int n)
{
int ans=inf;
for(int k=1;k<=n;k++)
{
for(int i=1;i<k;i++)
for(int j=i+1;j<k;j++)
ans=min(dis[i][j]+pic[i][k]+pic[k][j],ans);//最小环
for(int i=1;i<=n;i++)//正常的Floyd求最短路
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][k]+dis[k][j],dis[i][j]);
}
return ans;
}
模板:【HDU-1599】无向带权图
#include <bits/stdc++.h>
using namespace std;
const int inf=1e8;
int dis[105][105],pic[105][105];
int floyd(int n)
{
int ans=inf;
for(int k=1;k<=n;k++)
{
for(int i=1;i<k;i++)
for(int j=i+1;j<k;j++)
ans=min(dis[i][j]+pic[i][k]+pic[k][j],ans);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dis[i][j]=min(dis[i][k]+dis[k][j],dis[i][j]);
}
return ans;
}
int main()
{
int n,m;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
dis[i][j]=inf;
if(i==j)
dis[i][j]=0;
pic[i][j]=dis[i][j];
}
}
for(int i=1;i<=m;i++)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
dis[x][y]=min(dis[x][y],w);
dis[y][x]=dis[x][y];
pic[x][y]=pic[y][x]=dis[x][y];
}
int ans=floyd(n);
if(ans==inf)
printf("It's impossible.
");
else
printf("%d
",ans);
}
return 0;
}
Sightseeing trip POJ - 1734【Floyd存最小环路径】
因为要存路径,一开始用 (dijsktra),然后果断超时。
最后还是得用 (floyd),在原来的基础上增加一个 (turn) 数组,用来存两个点之间的转折点。寻找路径的过程,就是一个递归的过程,见代码。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#include <string>
using namespace std;
const int inf=1e8+7;
const int N=110;
int pic[N][N],dis[N][N],path[N],turn[N][N];
int n;
void read(int &x)
{
x=0;
int f=1;
char ch=getchar();
while(!isdigit(ch))
{
if(ch=='-')
f=-1;
ch=getchar();
}
while(isdigit(ch))
{
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
x*=f;
}
void getway(int &cnt,int a,int b)
{
if(turn[a][b]==-1)
return;//无转折点
getway(cnt,a,turn[a][b]);
path[++cnt]=turn[a][b];
getway(cnt,turn[a][b],b);
}
int floyd(int &num)
{
int res=inf;
for(int k=1;k<=n;k++)
{
for(int i=1;i<k;i++)
{
for(int j=i+1;j<k;j++)
{
if(dis[i][j]+pic[i][k]+pic[j][k]<res)
{
res=dis[i][j]+pic[i][k]+pic[j][k];
num=0;
path[++num]=i;
path[++num]=k;
path[++num]=j;
getway(num,j,i);
}
}
}
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];
turn[i][j]=k;
}
}
}
}
return res;
}
int main()
{
int m,ans=inf;
read(n);
read(m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
pic[i][j]=inf;
turn[i][j]=-1;
if(i==j)
pic[i][j]=0;
dis[i][j]=pic[i][j];
}
}
int u,v,w;
for(int i=1;i<=m;i++)
{
read(u);
read(v);
read(w);
pic[u][v]=min(pic[u][v],w);
pic[v][u]=pic[u][v];
dis[u][v]=dis[v][u]=pic[u][v];
}
int num=0;
ans=floyd(num);
if(ans==inf)
printf("No solution.
");
else
{
for(int i=1;i<=num;i++)
printf("%d%c",path[i],i==num?'
':' ');
}
return 0;
}
补充一个(floyd)求路径的博客floyd多源最短路+打印路径