《算法竞赛进阶指南》-AcWing-91. 最短Hamilton路径-题解

62 阅读2分钟

我正在参与掘金创作者训练营第5期

@TOC

最短Hamilton路径

传送门

题目描述

给定一张 nn 个点的带权无向图,点从 0n10∼n−1 标号,求起点 00 到终点 n1n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 00n1n−1 不重不漏地经过每个点恰好一次。

输入样例:

5
0 2 4 5 1
2 0 6 5 3
4 6 0 8 3
5 5 8 0 5
1 3 3 5 0

其中55代表一共有55个点,下面5×55\times5的矩阵中A[i][j]A[i][j]代表点ii到点jj的路径。

输出样例:

18

输出就直接输出答案即可。

解题思路

  • 首先用一个数statestate来表示当前状态,数字statestate二进制下的第ii位表示点ii是否经过过(1表示已经经过过了)。

  • 然后用一个数组f[state][j]f[state][j]表示终点位于点jj且路径状态位statestate的最短总路径。

    其中合法的f[state][j]f[state][j]必须满足statestate的第jj位是11(因为终点在jj表面jj点经过过),即state>>j&1state>>j\&1为真。

  • f[state][j]f[state][j]的值为所有能一步到达jj的点kk中,f[state_k][k]+kj的路径f[state\_k][k]+k到j的路径的最小值。

  • 初始值:起点是00,这时候只经过了点00,所以statestate只有最低为是11其他位都是00statestate初始值就是11。又因为0000的距离是00,所以初始值f[1][0]=0f[1][0]=0

需要注意-的优先级大于>>

对核心代码的讲解:

这里可以先参考一下后面的完整代码

  1. memset(f, 0x3f, sizeof(f))赋初值为“无穷大”

  2.  // 输入每两点之间的距离
     for(int i=0;i<n;i++)
         for(int j=0;j<n;j++)
             cin>>A[i][j];
    
  3. f[1][0]=0初始化起点自身的状态

  4.  for(int i=0;i<1<<n;i++) // 先枚举每种状态i
         for(int j=0;j<n;j++) // 再枚举这种状态下的终点j
             if(i>>j&1) // 这种状态的第j位必须是1才有意义
                 for(int k=0;k<n;k++) // 最后枚举这种到点j的上一个点k
                     if((i-(1<<j))>>k&1) // (i-(1<<j))是上一种状态,它的第k位必须是1
                         f[i][j]=min(f[i][j], f[i-(1<<j)][k]+A[k][j]); // 取所有能到达这一点的选项中的最小值
    
  5. cout<<f[(1<<n)-1][n-1]<<endl输出最终结果:状态是起点到终点的每个点都经过,终点是n-1。


AC代码

#include <bits/stdc++.h>
using namespace std;
#define mem(a) memset(a, 0, sizeof(a))
#define dbg(x) cout << #x << " = " << x << endl
#define fi(i, l, r) for (int i = l; i < r; i++)
#define cd(a) scanf("%d", &a)
typedef long long ll;
const int N=20, M=1<<20;
int f[M][N], A[N][N];
int main()
{
    memset(f, 0x3f, sizeof(f));
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
        for(int j=0;j<n;j++)
            cin>>A[i][j];
    f[1][0]=0; // 0到0的距离是0!不是1!
    for(int i=0;i<1<<n;i++) // 先枚举每种状态i
        for(int j=0;j<n;j++) // 再枚举这种状态下的终点j
            if(i>>j&1) // 这种状态的第j位必须是1才有意义
                for(int k=0;k<n;k++) // 最后枚举这种到点j的上一个点k
                    if((i-(1<<j))>>k&1) // (i-(1<<j))是上一种状态,它的第k位必须是1
                        f[i][j]=min(f[i][j], f[i-(1<<j)][k]+A[k][j]);
    cout<<f[(1<<n)-1][n-1]<<endl;
    return 0;
}

同步发文于CSDN,原创不易,转载请附上原文链接哦~
Tisfy:letmefly.blog.csdn.net/article/det…