ACM图论—最小环问题 ( 仔细分析+理解+代码 )(HDU 1599 ) (POJ 1743)

说明:如果发现错误或者有任何问题,任何不理解的地方请评论提出,或私信me,^ _ ^

ACM—图论 最小环问题(Floyd算法应用)


最小环问题是Floyd算法的应用,并不难,和Floyd算法一样难度。但是如果要输出最小环路径就要稍微麻烦一点,也不难。

1.计算最小环值(HDU 1599)
  1. 有向图最小环:

    有向图最小环最少要有2个点组成环,这个的写法就是用Floyd()求最短距离,最后所有点中的最短距离的最小值就是答案。

  2. 无向图最小环

    肯定和有向环做法有区别,无向图构成环最少要有3个点,所以求最小环可以枚举最大环中的连接点,更新答案。(这里的最大环指的是环中的节点尽可能的多,同时在枚举增加环中点的同时也要使环上边权值最小) ,这里如果不懂可能是我描述问题,实际上不难,请继续看下面的部分会明白的。

    **(1)和Floyd()关系: **

    的你肯定好奇这和Floyd()有什么关系?其实仔细想想Folyd()中要遍历所有点作为k点,而我们给最小环中加点是也是遍历所有点去考虑要不要添加这个点,同时我们更新ans时要用到两点间的最短距离dis[i] [j](这个下面会说),所以我们完全可以将更新ans的步骤放在Folyd()的经典的3次循环中。

    **(2) 如何更新ans: **

    这里直接从开始讲不太容易说明,所以我们先假设已经处理到第k个点了,这意味着1 ~ k-1 的点它们之间的最小值在只有k-1个点的情况下已经确定。这时我们枚举前k-1个点中的两个点 i , j 组合,这里我们可以先认为只有前k-1个点时的最小环已经得出了,就是i , j 与一些点所连的环。

    所以很容易想到我们现在的任务是求有前k个点时的最小环值,也就是在前k-1个点中的最小环中添加k,看满不满足加入k点后环上权值和减小,求出这里面的最小值更新ans(未更新前ans是前k-1个点最小环值),由于不保存环,所以每次要枚举前k-1个点中的i,j组合作为插入k的位置。(这里提前说一下:要想将k加入到i,j所在的环里面,k点一定与i,j都相连,注意前面的条件我们就是要从i,j这里加入k,所以一定相连)如何判断环上权值和减少呢?就是ans>dis[i] [j]+e[i] [k]+e[k] [j] (如果k与i,j任意一个不相连这里右边都是INF,不会更新答案)。这也就是前面说要用到dis[] []的原因。

看这幅图,灰色的部分时前k-1个点的最小环,ans就是灰色部分加i,j之间的距离,更新比较的就是灰色部分加k,i边的权值和k,j边的权值。(具体实现见代码讲解)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MA=1e3+5;
const int INF= 0xfffffff;

int e[MA][MA];//存图
int dis[MA][MA];//两点间的最小距离

int n,m;

int init()//初始化
{
    for(int i=1;i<=n;++i){
        for(int j=0;j<=n;++j){
            if(i==j)e[i][j]=dis[i][j]=0;
            else e[i][j]=dis[i][j]=INF;
        }
    }
}

void Floyd()
{
  int ans=INF;//初始化ans
  for(int k=1;k<=n;++k){
     //注意要先更新ans,再更新dis[][].因为更新ans用的是只有前k-1个点的dis[][]
     for(int i=1;i<k;++i){
        for(int j=i+1;j<k;++j){
            ans=min(ans,dis[i][j]+e[i][k]+e[k][j]);//这里见讲解(2)部分
        }
     }
      //就是普通Floyd()更新
     for(int i=1;i<=n;++i){
        for(int j=1;j<=n;++j){
            dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
        }
     }
  }
   //如果处理完后ans未更新说明无环。
  if(ans==INF)printf("No solution.
");
  else printf("%d
",ans);
}

int main()
{
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i=1;i<=m;++i){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            if(c>e[a][b])continue;
            e[a][b]=e[b][a]=dis[a][b]=dis[b][a]=c;
        }
        Folyd();
    }
    return 0;
}

2. 输出最小环路径 ( poj 1734 )
  1. **方法1 数组记录前驱: **

    用一个数组R[i] [j]维护 i 到 j 的最小路径上j上一个点是谁

见上图(1) 我们假设从j到i的逆时针的环中的小黑点分别代表这个环里面最小环上的点,分别标为$k_{1},k_{2},k_{3} ···k_{n} $。 (注意离 i 最近的是Kn )我们用path[] 记录最小环上的路径。

**下面这个图(2)是关于更新最短路的图(3重循环): **

步骤:

(1) 初始化R[] [],遍历所有i,j 让R[i] [j]=i,见 图(1) 也就是R[i] [(k_{n})] = R[i] [(k_{n-1})]= · · · R[i][(k_{1})] = R[i] [j] = i 。

(2) 这种做法主要理解R[] []的更新过程,在第二次3重循环松弛最短路过程中(见代码)。首先右边灰色的环就是dis[i] [j] ,(接下来的i,j见图2)而在更新的过程中这条线一旦被哪个 k 点更新,我们就让R[i] [j]=R[k] [j] (也就是R[i] [j]=k)。也就是说最后R[a] [b]保存a,b两点之间最短路径上b前面的点。在图(1)中就是 R[i] [j]=(k_{1}), R[i] [(k_{1})]=(k_{2}),· · · R[i] [(k_{n})] =i。

(3)你会惊讶的发现当我们有一个j 时 ,我们就可以由R[i] [j]得到(k_{1}) (也就是i,j最小路径上j上一个点), 就可以由R[i] [(k_{1})]得到(k_{2}),一直得到 i 。

//下面代码实现由j得到路径所有点。
int tedge=j;
while(tedge!=i){
    path[++cnt]=tedge;
    tedge=R[i][tedge];
}
path[++cnt]=i;
path[++cnt]=k;
//由与用i作为结束最后还要加上i,见图(1)不能只保存灰色线上的点,还要把k点加上。

(4) 那么所有问题就变成确定这个j,如果你把第1个问题(求最小环)理解了那么这里你应该已经很清楚了。就是看每次 ans>dis[i] [j]+e[i] [k]+e[k] [j] 时更新了新的 j。这时你要让cnt=0(cnt是path[]指向下一个添加位置的变量,可以理解未当前存的点个数)这步就相当于删除了path[]里存的之前得路径,然后按(3)步骤重新存路径上的点。最后输出path[]中的点就是路径

//代码注释需要的话私信我补上
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MA=1e3+5;
const int INF=0xfffffff;

int edge[MA][MA];
int dis[MA][MA];
int R[MA][MA];
int path[MA];
int ans;
int cnt;
int n,m;

void init()
{
    for(int i=0;i<=n;++i){
        for(int j=0;j<=n;++j){
            edge[i][j]=dis[i][j]=INF;
            R[i][j]=i;
        }
    }
}

void Floyd()
{
    ans=INF;
    cnt=0;
    for(int k=1;k<=n;++k){
        for(int i=1;i<=k;++i){
            for(int j=i+1;j<=k;++j){
                if(ans>dis[i][j]+edge[i][k]+edge[k][j]){
                    cnt=0;
                    ans=dis[i][j]+edge[i][k]+edge[k][j];
                    int tedge=j;
                    while(tedge!=i){
                        path[++cnt]=tedge;
                        tedge=R[i][tedge];
                    }
                    path[++cnt]=i;
                    path[++cnt]=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];
                    R[i][j]=R[k][j];
                }
            }
        }
    }
    if(ans==INF)printf("No solution.
");
    else{
        for(int i=1;i<=cnt;++i)printf("%d%s",path[i],i==cnt?"
":" ");
    }
}

int main()
{
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i=1;i<=m;++i){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            if(c>edge[a][b])continue;
            edge[a][b]=edge[b][a]=dis[a][b]=dis[b][a]=c;
        }
        Floyd();
    }
    return 0;
}


这篇自己感觉说的很细了,应该好理解一些。^ _ ^

学习博客:

https://www.cnblogs.com/DF-yimeng/p/8858184.html(博客园)

https://wenku.baidu.com/view/d1031265657d27284b73f242336c1eb91a373384.html(百度文库)

https://blog.csdn.net/qq_34798152/article/details/77688814(最小环+路径题,代码学习)(poj1734)


最重要的事: 如果发现错误或者有任何问题,任何不理解的地方请评论提出,或私信me,^ _ ^

原文地址:https://www.cnblogs.com/A-sc/p/11437563.html