最短Hamilton路径(27-27)

79 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 27 天,点击查看活动详情

最短Hamilton路径

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

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

输入格式

第一行输入整数 nn

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

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

输出格式

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

数据范围

1n201≤n≤20
0a[i,j]1070≤a[i,j]≤10^7

输入样例:

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

输出样例:

1

题目分析

这是一道状态压缩的题目,自我感觉具体方案和下节的flyod很相似。

我们假设起点为 sta,终点为 fin,中间路径中一点为 mid

从动态规划的角度考虑,我们可以知道从 finmid 的所有路径中,我们不需要知道具体的路径以及最优路径,只需要记录最优路径对应的消耗。

定义 f[i][j]f[i][j] 表示在状态 ii 以及当前终点为 jj 的情况下,路径消耗最小值。

此处的状态为一个二进制串,如 : 100101,表示在经过了点 0,2,5 的路径中,到达 j 的最小路径( j 为路径中三个点中的一个)。

欲得到 f[i][fin]f[i][fin] 的值,需要对 ii 中路径所有到达点作为 fin 的前一点 mid,以 f[i][fin]=min(f[i][fin],f[i(1<<mid)][mid)]+g[mid][fin]f[i][fin]=min(f[i][fin],\, f[i\oplus(1<<mid)][mid)]+g[mid][fin] 转移。

注意代码中的细节实现,本文进行了位运算的优化,最终时间复杂度为 O(2nn2)O(2^nn^2)

Accept代码 O(2nn2)O(2^nn^2)

#include <bits/stdc++.h>

using namespace std;

const int N = 21;

int n;
int g[N][N];
int f[1 << N][N];
int log_2[1 << N];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++)
        for (int j = 0; j < n; j ++)
            cin >> g[i][j];
            
    for (int i = 0; i < n; i ++) log_2[1 << i] = i;
    
    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;
    for (int i = 1; i < 1 << n; i ++)
        if (i & 1)
        {
            for (int st = i; st; st &= st - 1)
            {
                int fin = st & (-st);
                for (int ts = i ^ fin; ts; ts &= ts - 1)
                {
                    int sta = ts & (-ts);
                    int ls = log_2[sta], lf = log_2[fin];
                    f[i][lf] = min(f[i][lf], f[i ^ fin][ls] + g[ls][lf]);
                }
            }
        }
    cout << f[(1 << n) - 1][n - 1];
    return 0;
}