最短哈密顿图(状态dp)

题目描述 :给定一张 n(n \leq 20)(n≤20) 个点的带权无向图,点从0 \sim n-10∼n−1标号,求起点 0 到终点 n-1 的最短Hamilton路径。 Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。输入描述:第一行一个整数n。接下来n行每行n个整数,其中第i行第j个整数表示点i到j的距离(一个不超过10^710 7 的正整数,记为a[i,j])。对于任意的x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x] 并且a[x,y]+a[y,z] \geq a[x,z]a[x,y]+a[y,z]≥a[x,z]。

示例1
输入
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

链接:https://ac.nowcoder.com/acm/contest/996/D
来源:牛客网

思路:
问题的建设:

1.问题建模描述 :
给定一个n个结点,m条有向边(边权为正)的图,求出一条路径满足如下条件:
条件一:该路径可以从任意节点开始,不过起点和终点必须相同。
条件二:该路径除了起点和终点,其他结点都必须经过,且只能经过一次。
条件三:在满足上述两条件的前提下,要求路径尽可能短。

条件一、二的继续探究:
无论如何走,都是从起点开始到达终点,而中间的走法,才是问题。

例如,一共有五个点,从0…4,起点是0,终点是4,而中间的走法就是1,2,3的全排列。

但是,这样用全排列的方式,时间复杂度是n!,耗时太长,十分有可能超时。
在这里,我们采用二进制压缩来判断。

判断是否走过:

状态定义:
d p ( s , t , s t a t e ) 表 示 从 s 号 点 出 发 , 到 达 t 号 点 , 当 前 路 径 状 态 为 s t a t e 需 要 的 最 短 距 离 是 多 少 .
首先,我们从某一个点s出发,走到了一个点t,那么我如何知道我之前经过了哪些点呢?
最简单的办法当然就是开一个数组记录下来了,不过这样还是有一些浪费空间的。
所以我们就想到这样一个办法:用0 00表示没到过,用1 11表示到过,这样一个长度为n的01序列,就能表示这n个点我分别有没有到过了。
那么这个长度为n的01序列,恰好就可以看成是一个二进制数字,那么我们就可以把它当成数组的下标储存起来了,这个二进制数字,就是state也就是路径状态。
我们还可以更形象的举个栗子:比如state=14,那么它表示成2进制就是:1110,就表示到过的结点的标号为:1 , 2 , 3 没到过的结点的标号为:0。

关键dp方程:

f[i][j] = min(f[i][j], f[i - (1 << j)][k] + a[k][j]);

#include<bits/stdc++.h>

using namespace std;

const int N = 21;
int a[N][N];
int f[1 << N][N];
int n;

int main()
{
    scanf("%d", &n);

    for(int i = 0; i < n; i++)
        for(int j = 0; j < n; j++)
            scanf("%d", &a[i][j]);

    memset(f, 0x3f, sizeof f);
    f[1][0] = 0;

    int len = 1 << n;
    //cout<<len<<endl;
    for(int i = 1; i < len; i++)
        for(int j = 0; j < n; j++)
            if(i >> j & 1)//当要到达的点存在,这个状态才会存在
                for(int k = 0; k < n; k++)
                    if((i - (1 << j)) >> k & 1) //去掉j这个点后,如果k这个点存在,这个状态才会存在
                        f[i][j] = min(f[i][j], f[i - (1 << j)][k] + a[k][j]);

    printf("%d\n", f[(1 << n) - 1][n - 1]);

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值