示例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;
}