最短Hamilton路径
题目描述
给定一张 n 个点的带权无向图,点从 0 → n − 1 0\to n-1 0→n−1 标号,求起点 0 0 0 到终点 n − 1 n-1 n−1 的最短 H a m i l t o n Hamilton Hamilton路径。 H a m i l t o n Hamilton Hamilton路径的定义是从 0 0 0 到 n − 1 n-1 n−1不重不漏地经过每个点恰好一次。
输入格式
第一行输入整数 n n n。
接下来n行每行 n n n个整数,其中第 i i i行第 j j j个整数表示点 i i i到 j j j的距离(记为 a [ i , j ] a[i,j] a[i,j])。
对于任意的 x , y , z x,y,z x,y,z,数据保证 a [ x , x ] = 0 a[x,x]=0 a[x,x]=0, a [ x , y ] = a [ y , x ] a[x,y]=a[y,x] a[x,y]=a[y,x] 并且 a [ x , y ] + a [ y , z ] > = a [ x , z ] a[x,y]+a[y,z]>=a[x,z] a[x,y]+a[y,z]>=a[x,z]。
输出格式
输出一个整数,表示最短 H a m i l t o n Hamilton Hamilton路径的长度。
数据范围
1
≤
n
≤
20
1≤n≤20
1≤n≤20
0
≤
a
[
i
,
j
]
≤
107
0≤a[i,j]≤107
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
解析
暴力的做法
这道题很容易想到暴力的做法,即将这些点排列出来,再按每种方式求值。
0->1->2->3->4
0->1->2->4->3
0->1->3->2->4
...
时间复杂度 O ( n × n ! ) O(n\times n!) O(n×n!),当n取最大值20时,时间复杂度是 O ( 2 432 902 008 176 640 000 ) O(2~432~902~008~176~640~000) O(2 432 902 008 176 640 000),和LLNG_MAX是一个数量级,一定超时.
优化的做法
因为每个节点都有两种状态:访问过和没访问过,所以我们考虑:
用状态压缩DP,将状态定位一个二进制数。
变量规定
F[i, j] = k 表示经过状态为i(i是二进制数), 且当前位置为j时,最优答案为k
w[x, y] 表示x到y的权值
则状态转移方程为
F [ i , j ] = m i n ( F [ k , j ] , F [ i X o r ( 1 L s h j ) , k ] ) + w [ k , j ] F[i, j] = min(\color{red}{F[k, j]},~\color{blue}{F[i~Xor~ (1~Lsh~j),~k]}\color{black}) + w[k, j] F[i,j]=min(F[k,j], F[i Xor (1 Lsh j), k])+w[k,j]
(其中i的第j位为1,k为节点)
其中 F [ i X o r ( 1 L s h j ) , k ] \color{blue}F[i~Xor~ (1~Lsh~j),~k] F[i Xor (1 Lsh j), k]为把 i i i的第 j j j位取反,即
若 j j j未经过,则标记为“此时经过”
若 j j j已经过,则标记为“取消经过”(方程中无此情况).
代码
首次提交,MlE
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 23;
int F[1 << N][N], n, w[N][N];
int dp(int n)
{
F[1][0] = 0;
for(int i = 1; i < (1 << n); ++i)
for(int j = 0; j < n; ++j)
if((i >> j) & 1)//the jth digit of the binary number i is 1
for(int k = 0; k < n; ++k)
if((i >> k) & 1)
F[i][j] = min(F[i][j], F[i ^ (1 << j)][k] + w[k][j]);
return 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", &w[i][j]);
memset(F, 0x3f, sizeof(F));
printf("%d", dp(n));
return 0;
}
稍作改动,AC
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 20;
int F[(1 << N) + 1][N + 1], n, w[N][N];
int dp(int n)
{
F[1][0] = 0;
for(int i = 1; i < (1 << n); ++i)
for(int j = 0; j < n; ++j)
if((i >> j) & 1)//the jth digit of the binary number i is 1
for(int k = 0; k < n; ++k)
if((i >> k) & 1)
F[i][j] = min(F[i][j], F[i ^ (1 << j)][k] + w[k][j]);
return 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", &w[i][j]);
memset(F, 0x3f, sizeof(F));
printf("%d", dp(n));
return 0;
}
说明!!!
//First
const int N = 23;
int F[1 << N][N], n, w[N][N];
//Second
const int N = 20;
int F[(1 << N) + 1][N + 1], n, w[N][N];
这两种申请空间方式中,F数组的空间差很多。
提醒大家,开变量需谨慎,勿让指数爆炸!
完结,撒花
在该网站输入:
0 0 0
0 1 2
0 2 4
0 3 5
0 4 1
1 0 2
1 1 0
1 2 6
1 3 5
1 4 3
2 0 4
2 1 6
2 2 0
2 3 8
2 4 3
3 0 5
3 1 5
3 2 8
3 3 0
3 4 5
4 0 1
4 1 3
4 2 3
4 3 5
4 4 0