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.\n");
  else printf("%d\n",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 最近的是K~n~ )我们用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.\n");
    else{
        for(int i=1;i<=cnt;++i)printf("%d%s",path[i],i==cnt?"\n":" ");
    }
}

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

时间: 2024-09-30 05:53:47

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

通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的

实验一:通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的 学号:20135114 姓名:王朝宪 注: 原创作品转载请注明出处   <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 1 1)实验部分(以下命令为实验楼64位Linux虚拟机环境下适用,32位Linux环境可能会稍有不同) 使用 gcc –S –o main.s main.c -m32 命令编译成汇编代码,如下代码中的数字请自行修改以防与

Linux内核分析--理解进程调度时机、跟踪分析进程调度和进程切换的过程

ID:fuchen1994 姓名:江军 作业要求: 理解Linux系统中进程调度的时机,可以在内核代码中搜索schedule()函数,看都是哪里调用了schedule(),判断我们课程内容中的总结是否准确: 使用gdb跟踪分析一个schedule()函数 ,验证您对Linux系统进程调度与进程切换过程的理解:推荐在实验楼Linux虚拟机环境下完成实验. 特别关注并仔细分析switch_to中的汇编代码,理解进程上下文的切换机制,以及与中断上下文切换的关系: 实验过程: 1. 进程调度的时机 中断

理解计算机的工作方式——通过汇编一个简单的C程序并分析汇编代码

Author: 翁超平 Notice:原创作品转载请注明出处 See also:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000  本文通过汇编一个简单的C程序,并分析汇编代码,来理解计算机是如何工作的.整个过程都在实验楼上完成,感兴趣的读者可以通过上面给出的课程链接自行动手学习.以下是实验过程和结果. 一.操作步骤 1.首先在通过vim程序建立main.c文件.代码如下: 图1 2.使用如下命令将main.c编

Java NIO原理 图文分析及代码实现

Java NIO原理 图文分析及代码实现 博客分类: java底层 java NIO原理阻塞I/O非阻塞I/O Java NIO原理图文分析及代码实现 前言:  最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.可以参考:http://baike.baidu.com/view/32726.htm )机制时,发现hadoop的RPC机制的实现主要用到了两个技术

[JavaEE]Java NIO原理图文分析及代码实现

转http://weixiaolu.iteye.com/blog/1479656 目录: 一.java NIO 和阻塞I/O的区别      1. 阻塞I/O通信模型      2. java NIO原理及通信模型 二.java NIO服务端和客户端代码实现 具体分析: 一.java NIO 和阻塞I/O的区别 1. 阻塞I/O通信模型 假如现在你对阻塞I/O已有了一定了解,我们知道阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超 时)才会返回:同

静态代码分析与代码质量安全

HeartBleed Bug Heartbleed漏洞,这项严重缺陷(CVE-2014-0160)的产生是由于未能在memcpy()调用受害用户输入内容作为长度参数之前正确进行边界检查.攻击者可以追踪OpenSSL所分配的64KB缓存.将超出必要范围的字节信息复制到缓存当中再返回缓存内容,这样一来受害者的内存内容就会以每次64KB的速度进行泄露.     代码静态分析   ? 定义:在不执行计算机程序的条件下,对源代码进行分析,找出代码 缺陷 ? 执行方式:一般配合静态程序分析工具进行 ? 采用

洛谷 P2194 HXY烧情侣【Tarjan缩点】 分析+题解代码

洛谷 P2194 HXY烧情侣[Tarjan缩点] 分析+题解代码 题目描述: 众所周知,HXY已经加入了FFF团.现在她要开始喜(sang)闻(xin)乐(bing)见(kuang)地烧情侣了.这里有n座电影院,n对情侣分别在每座电影院里,然后电影院里都有汽油,但是要使用它需要一定的费用.m条单向通道连接相邻的两对情侣所在电影院.然后HXY有个绝技,如果她能从一个点开始烧,最后回到这个点,那么烧这条回路上的情侣的费用只需要该点的汽油费即可.并且每对情侣只需烧一遍,电影院可以重复去.然后她想花尽

hdu 1599 find the mincost route 最小环

题目链接:HDU - 1599 杭州有N个景区,景区之间有一些双向的路来连接,现在8600想找一条旅游路线,这个路线从A点出发并且最后回到A点,假设经过的路线为V1,V2,....VK,V1,那么必须满足K>2,就是说至除了出发点以外至少要经过2个其他不同的景区,而且不能重复经过同一个景区.现在8600需要你帮他找一条这样的路线,并且花费越少越好. Input 第一行是2个整数N和M(N <= 100, M <= 1000),代表景区的个数和道路的条数.接下来的M行里,每行包括3个整数a

hdu 1599 floyd 最小环

floyd真的是水很深啊 各种神奇 #include<stdio.h> #include<string.h> #include<math.h> #include<iostream> #include<algorithm> #include<queue> #include<stack> #define mem(a,b) memset(a,b,sizeof(a)) #define ll __int64 #define MAXN