CH0103 最短Hamilton路径

16 篇文章 0 订阅
描述

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

输入格式

第一行一个整数n。

接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(一个不超过10^7的正整数,记为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路径的长度。

样例输入
4
0 2 1 3
2 0 2 1
1 2 0 1
3 1 1 0
样例输出
4
样例解释

从0到3的Hamilton路径有两条,0-1-2-3和0-2-1-3。前者的长度为2+2+1=5,后者的长度为1+2+1=4


状态压缩DP

使用二进制数来表示点是否被经过,如果i位为1表示被经过,否则未被经过

使用 d p [ i ] [ j ] ( 0 ≤ i &lt; 2 n , 0 ≤ j &lt; n ) dp[i][j] (0 \le i &lt; 2^n,0\le j &lt; n) dp[i][j](0i<2n,0j<n)来表示点被经过的状态为i处于位置j时的最短路径

起始状态dp[1][0],只有0点被经过,最少步数为0,目标状态dp[(1<<n)-1][n-1],所有的点都被经过,并处于终点

方法:

通过枚举所有的可能状态来寻找最短路径,类似于CH0201费解的开关中枚举第一行的所有状态,前提:每个点只能被经过一次

如果当前状态i下经过了点j,因为每个点只能被经过一次,所以j一定是被刚刚经过,而上一时刻所在的点k可能是任意位置,通过枚举所有可能的点k并考虑从点kj距离的最小值,就能把转移方程确定下来了

枚举所有状态是 2 n 2^n 2n,枚举点j n n n,枚举点k也是 n n n,所以时间复杂度为 O ( 2 n ∗ n 2 ) O(2^n*n^2) O(2nn2)


代码实现:

枚举所有的状态 i ∈ [ 1 , 1 &lt; &lt; n ) i\in [1,1&lt;&lt;n) i[1,1<<n) ,寻找当前已经经过了的点 j ∈ [ 0 , n − 1 ] j\in[0,n-1] j[0,n1] ,寻找 在没有经过点 j j j的情况下 是从哪个点 k ∈ [ 0 , n − 1 ] k \in [0,n-1] k[0,n1]来到 j j j的,取路径的最小值

经过点j表示为状态ij位为1:i>>j&1(取i中第j位)

当前状态其他点状态不变,不经过点j表示为:i'=i^1<<j(把ij位取反)

寻找ki'>>k&1(i^1<<j)>>k&1

状态转移方程

dp[i][j]=min(dp[i][j],dp[(i^1<<j)][k]+value[k][j]);

dp初值赋为无穷

经过了j点并处在j点的最短路径为,从没有经过j的状态下的k点到达j,并加上从kj的权值

ps:位运算优先级

加减移位比较大小位与异或位或
+,-<<,>>>,<,==,!=&xor(^)|
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
static const auto io_sync_off = []() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    return nullptr;
}();

const int maxn = 22;
int v[maxn][maxn];
int dp[1 << 20][maxn];

int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < n; ++j)
            cin >> v[i][j];

    memset(dp, 0x3f, sizeof(dp));
    dp[1][0] = 0;
    for (int i = 1; i < 1 << n; ++i)//状态
        for (int j = 0; j < n; ++j)//j点
            if (i >> j & 1)
                for (int k = 0; k < n; ++k)//k点
                    if ((i ^ 1 << j) >> k & 1)
                        dp[i][j] = min(dp[i][j], dp[i ^ 1 << j][k] + v[k][j]);
    cout << dp[(1 << n) - 1][n - 1];
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值