最小环问题

问题:

给出一个图,问其中的有 \(n\) 个节点构成的边权和最小的环 \((n\geq 3)\) 是多大。

暴力做法:

删除掉 \(u\)\(v\) 之间的边,如何求出 \(u\to v\) 的不经过该边的最短路,加上该边即可。

\(Dijsktra\)

在暴力算法的基础上,优化求最短路的过程。
枚举所有边,每一次求删除一条边之后对这条边的起点跑一次 \(Dijkstra\),道理同上。
复杂度:\(O(m*nlogn)\)

\(Floyd\)

记原图中\(u,v\) 之间边的边权为 \(val(u,v)\)

\(Floyd\) 算法有一个性质
当外层循环到 \(k\) (尚未开始第 \(k\) 次循环)时,最短路数组 \(dis[u][v]\)中, 表示的是从 \(u\)\(v\) 且仅经过编号在 \([1,k)\) 区间中的点的最短路。

  显然,一个环可以表示为 \(u \to 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.\n");
        else
            printf("%d\n",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.\n");
    else
    {
        for(int i=1;i<=num;i++)
            printf("%d%c",path[i],i==num?'\n':' ');
    }
    return 0;
}

补充一个\(floyd\)求路径的博客floyd多源最短路+打印路径

posted @ 2020-03-17 15:57  xzx9  阅读(112)  评论(0编辑  收藏  举报