【题解】Acwing 91 最短Hamilton路径

最短Hamilton路径

题目传送门

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

输入格式

第一行输入整数n。

接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(记为a[i,j])。

对于任意的x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且 a[x,y]+a[y,z]>=a[x,z]。

输出格式

输出一个整数,表示最短Hamilton路径的长度。

数据范围

1≤n≤20
0≤a[i,j]≤107

输入样例:

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

输出样例:

18

思路分析:

这道题很容易想到的朴素算法,枚举n个点的全排列。

时间复杂度为O((n * n!))

使用下面的状态压缩dp可以优化到O((n^2 * 2 ^ n))

比如在 0, 1, 2, 3 这几个点中,我们需要从0 -> 3

0 -> 2 -> 1 -> 3 所需10

0 -> 1 -> 2 -> 3 所需20

我们肯定只选第一条路

很容易理解,若还有其他的点,第二条路总是会比第一条花费多,

所以第二条边没有维护的必要,可以给它删除,保留第一条这条更优的。

注意,删除的前提是他们二者访问的点的个数是一样的,现在在的点的位置是一样的。

但朴素的爆搜还是会搜下去,直到搜完全部点。

这就是主要优化的地方。

(可以去看一下 Acwing y总的视频讲解,本人虽然知道 状压dp 的时间复杂度比 爆搜 要低

但一直搞不清楚它这个 状压dp 优化在哪,看了y总的讲解恍然大悟)

最大有20个点,为了表示每个点的状态当前点的位置

可以开一个f[1 << 20][20]来记录 在 i 状态 j 位置的最小代价

i 状态 j 位置 的代价 需要从 i 状态 下访问过不为j 的k点来到达

只需要f[i][j] = min(f[i][j], f[i - (1<<j)][k])

下方代码有本人认为根据本题要求可以做出的一些小优化?注释标注。

代码展示:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;

const int N = 25;
int n, g[N][N], f[1 << 20][20]; // i 状态 j位置

inline void work (int n) {
    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;
    for(int i = 1; i < 1 << n; i += 2)  // 修改  i ++ -> i += 2。遍历状态
        for(int j = 1; j < n; j ++) if((i >> j) & 1) // j = 0 -> j = 1。 遍历结束点
                for(int k = 0; k < n; k ++) if((i ^ 1<<j)>>k & 1) //遍历到结束点的点
                        f[i][j] = min(f[i][j], f[i ^ 1<<j][k] + g[k][j]);
    printf("%d", f[(1 << n) -1][n - 1]);
}

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < n; j ++)
            scanf("%d", &g[i][j]);
    work(n);
    return 0;
}
原文地址:https://www.cnblogs.com/RemnantDreammm/p/14126682.html