zoukankan      html  css  js  c++  java
  • 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,^ _ ^

  • 相关阅读:
    2009年度最佳jQuery插件
    转:Jeff Dean的Stanford演讲
    Zookeeper的RPC框架
    转:电商推荐技术
    NoSQL设计思想(从辅到主)
    工作一年小结
    转:MySQL索引背后的数据结构
    java多线程并发,java的几种状态
    转发:Linux Socket编程
    几个linux shell的讲解网站
  • 原文地址:https://www.cnblogs.com/A-sc/p/11437563.html
Copyright © 2011-2022 走看看