最短Hamilton路径超详细题解

题目描述

给定一张 n(n≤20)个点的带权无向图,点从0∼n−1标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。

链接:https://ac.nowcoder.com/acm/contest/996/D

来源:牛客网

输入示例1
4
0 2 1 3
2 0 2 1
1 2 0 1
3 1 1 0


题解

首先,哈密尔顿路径是什么?

简单来说是每个点只经过一次连成的图。

输入的样例是个按主对角线对称的矩阵,即点i到点j的距离和点j到点i的距离是一样的,而点i到点i自身的距离为0。

对于本题,我们开一个二维数组

f[已经经过了哪些点][当前在哪个点]=目前路径的长度

  • 对于《已经经过了哪些点》这个维度,设0为没经过,1为经过了。

举个例子,我一共有5个点,我已经经过了点3和点4,那么就表示为01100;我只经过了点1,那么就表示为00001

我们的目标是经过所有的点。如果我一共有n-1个点,那我就希望值变成11111...1(n-1个1),也就是(1<

1<

  • 对于《当前在哪个点》这个维度,直接写它当前所在点的十进制数

非常简单,例如当前在点3,那就是f[xxxxx][3]

将上面两个维度结合起来看,举个例子:

一共有5个点,我已经经过了1->4->3这三个点,当前在点3,现在路径的长度是13,则就表示为:

f[01101][3]=13,即f[1101][3]=13

解释完了这个,我们结合代码讲解接下来的题解:

我对一些初学者可能不懂的点都进行了编号和解释。比较费解的地方都进行了加粗。

#include
using namespace std;
int f[1<<20][20],g[20][20];
int main(){
    int n;cin>>n;
    for(int i=0;i>g[i][j];
    memset(f, 0x3f, sizeof(f));    //1
    f[1][0]=0;    //2
    for(int i=1;i<1<>j&1){    //5
                for(int k=0;k>k&1){//7
                        f[i][j]=min(f[i][j],f[i^(1<

  1. 因为我们f[][]=至目前点的路径长度,我们希望这个值越小越好,所以我们用memset(f, 0x3f, sizeof(f))对它先赋予最大值0x3f,便于之后的比较并寻得最小值。

  1. f[1][0]=0,即f[00000...01][0]=0,也就是我们目前在点0,只经过了点0,我们的路径长度为0

3.i<(1<

这个循环的意思即,我们遍历了所有路径的可能性,举个例子,当n为5时,我们从00001、00010、00011、00100、00101、00110、00111……一直遍历到了11111

4.我们遍历了在i情况下,j作为当前点的可能性,编号4和编号5应该结合起来看。

编号5的意思是,让i退j位,看j这位上是不是1。例如100101退3位,与1做相与运算,发现第3位上是1。也就是j可以作为当前所在点。如果j为0,它根本不在路径里,怎么可能做当前所在点呢?

对于1001011,我们遍历0到6号点,当前所在点只可能是第0、1、3、6号点。我们分别枚举它们做当前所在点的情况。这就是编号4、5代码的意思。

6.我们把编号6,7,8结合起来看

我们先理思路,再理代码。

思路

意思是在走到j前一个步骤时,我在点k。我需要知道从k到j是不是一个好主意。

例如1001011这个数,是经过了0,1,3,6号点的意思。我把j遍历到点3时,我需要知道到底是1->3好还是6->3好。如果是6->3,那f[1001011][3]=f[1000011][6]+g[6][3]

如果是1->3,那就是f[1001011][3]=f[1000011][1]+g[1][3]

我们需要知道f[1001011][3]=min(f[1000011][1]+g[1][3],f[1000011][6]+g[6][3]),选出最佳的那个连法。

代码

6:从0到n遍历点k

7:i^(1<
10101^(1<<2)=10101^100=10001
^为亦或,同为0,异为1。所以只会使第j位的数置0.

i^(1<>k&1,同编号5的操作,确保k是在经过的点里的。

8:选出最短的那条路。

9.(1<

我们再看一遍代码:

#include
using namespace std;
int f[1<<20][20],g[20][20];
int main(){
    int n;cin>>n;
    for(int i=0;i>g[i][j];
    memset(f, 0x3f, sizeof(f));f[1][0]=0;
    for(int i=1;i<1<>j&1){
                for(int k=0;k>k&1){
                        f[i][j]=min(f[i][j],f[i^(1<

是不是思路清晰了很多呢?

那再把总的思路通过例子过一遍吧!

为了方便,就不同代码中把0作为第一个点了。我们把1作为第一个点。

设n=4,而输入的g[i][j]为

0 2 1 3
2 0 2 1
1 2 0 1
3 1 1 0

把f全部置0x3f

我们的i枚举0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100,1101,1110,1111这几个情况

0001

0010

0011=0001+(1,2) or 0010+(2,1)?

0100

0101=0100+(3,1) or 0001+(1,3)?

0110...

0111=0110+(3,1) or 0110+(2,1) or 0101+(3,2) or 0101+(1,2) or...?

.....

1111=1101+(1,2) or 1101+(3,2) or..... or 1011+(1,3) or 1011+(4,3) or....?


我是按照《算法竞赛进阶指南》进行题目练习的,争取每天出一题很笨很详细的题解,把思路都讲清楚。感兴趣的朋友可以关注我,也欢迎指出我的不足。

你可能感兴趣的:(算法)