算法竞赛进阶指南0x01D 最短Hamilton路径 题解

最短Hamilton路径

http://www.nowcoder.com/questionTerminal/bdb5d7d3051a47c197d4631eabebcbcc

最短Hamilton路径 题解

题意分析

  • 给你0~n-1标号的n个点,以及它们之间的距离。
  • 现在起点一定要是0,终点一定要是n-1
  • 求0到n-1不重不漏每个点恰好经过一次,最短的路程是多少?
    • Sample Input
    • 4
    • 0 2 1 3
    • 2 0 2 1
    • 1 2 0 1
    • 3 1 1 0
  • 首先确定是求:从0到3的Hamilton路径
  • 样例解释表明:这样的路径有两条,0-1-2-3和0-2-1-3。前者的长度为2+2+1=5,后者的长度为1+2+1=4

解题思路

第一想法:递归

  • 时间复杂度为。我一开始想,这个题目无非是设置一个used数组,然后开始dfs,
  • 对于每一个点,它能跳转到的点,无非是别的没跳转到过的点而已。没毛病吧?
  • 显然这个方法是超时的,我没有上手实现这个代码。

正解:动态规划

  • 时间复杂度为
  • 这个方法对于蒟蒻来说实在是太难了
    • 其实在上一种方法上,我还想到了一个方法:就是开一个数组f[1<<20],f数组下标代表“选定的集合”,然后用元素值来存选定这个集合,里面的最短路径是多少。
    • 一样使用dfs来更新,然后进行一些最优型剪枝……
    • 但是这样的方法,不难看出时间复杂度最大还是那么多,并不是什么正解。
  • 在放学坐车的路上遇到了机房大佬,藉口手机没电(其实有)后和机房大佬开始聊天。我借机赶紧转移话题,趁机套他这一题的题解。
  • “正解是动态规划。”
  • 为什么要开2维数组?
      • 第一维代表:
        • 由于我们确定最后的状态一定是"1111...",所以这个f数组下标代表一个的集合,下标代表已走过的点是哪些点,而这个下标的元素值代表走这些点的最短路径
        • 其实,这个和上面提到的那个方法的f数组,不论是下标还是元素值作用都相同。
        • 接下来看第二维是怎么妙用的。
      • 第二维代表:
        • 既然上一维已经经过了这些(集合i里面的)点,那么第二维代表的就是“最后一个到达的是哪个点?”
        • 假如集合i是1111,那么j=0代表最后一个到达的是点1,j=1代表是点2,……
  • 怎么想Dp?
      • 第一重循环代表枚举集合。(00000...~11111...)
        • 无非就是在集合尽可能空的情况下,不断给i集合放入新的点,更新f数组答案
      • 第二重循环代表枚举上一个点走过的是哪个()
        • 枚举i集合里面的每个点,如果0~n-1的所有点里面,有i已经走过的,我们就跳转(跳转到第三重循环
        • ,进入第三重:k循环代表我们要跳转到的下一个点,那个被跳转到的点位置上必须是0。()
    • 状态转移方程:
      • 当前我们已经确定下一个要跳转去的点是k点,
      • 所以转移方程要用Fij(也就是集合i,最后一个点走的是j的最短总路程,加上j到k的路程,尝试更新答案(也就是f[{i}+k],[k],为什么第二维是k?因为从j到k之后,最后一个经过的点就是k点了。))。
      • 这样,我们的答案(也就是,终点必须是n-1,就会是最小的了)

参考程序

#include<stdio.h>
#include<string.h>
int min(int a,int b)
{
    if(a<b)
        return a;
    return b;
}
int n,dist[21][21];
int f[1<<21][21];
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            scanf("%d",&dist[i][j]);

    memset(f,0x3f3f3f3f,sizeof f);
    f[1][0]=0;
    //两个很特殊的对于dp数组的预处理
    //第一重枚举集合
    for(int i=0;i<(1<<n);i++)
        for(int j=0;j<n;j++)
            //我们设上一个经过的点为j
            if((i>>j)&1)
                //该位置上有1,启动k,
                //用k来转移
                for(int k=0;k<n;k++)
                    if(~(i>>k)&1)
                        {
                            f[i+(1<<k)][k]=min(f[i+(1<<k)][k],f[i][j]+dist[j][k]);
                        }
    printf("%d",f[(1<<n)-1][n-1]);
    return 0;
    //第二重枚举集合i里面的每一个有1的点
}
全部评论
想问一下题目中“其中第i行第j个整数表示点i到j的距离(记为a[i,j])”什么意思啊,题目就是找最短的方案能理解,这个题目 第 i 行 当中 第 j 个整数 表示 点 i 到 j 的距离,这个点 i ,j 从何来啊,希望大佬给解释下
2 回复 分享
发布于 2020-04-16 00:12

相关推荐

点赞 评论 收藏
分享
评论
14
收藏
分享
牛客网
牛客企业服务