看到数据范围这么小,第一眼想到爆搜。
然而这样做的复杂度是 (mathcal{O}(n! imes n)) 的,明显会 TLE。
于是考虑状压 DP。
我们设 (dp_{i,j}) 表示当前走过的集合为 (i),且停留在 (j) 号点的最短路径长度。
转移的话可以枚举一个点 (k),意为从 (k) 号点走到点 (j),走过的集合变成了 (i)。然后就有了转移方程:(dp_{i,j}=min{dp_{i-2^j,k}+a_{k,j}}),其中 (a_{k,j}) 表示点 (k) 到点 (j) 的距离。
注意点的标号从 (0) 开始。
这里介绍一个判断 (j) 号点是否出现在集合 (i) 中的技巧:直接判断 i >> j & 1
是否为 ( ext{true}) 即可。
#include <bits/stdc++.h>
#define DEBUG fprintf(stderr, "Passing [%s] line %d
", __FUNCTION__, __LINE__)
#define itn int
#define gI gi
using namespace std;
typedef long long LL;
typedef pair <int, int> PII;
typedef pair <int, PII> PIII;
inline int gi()
{
int f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return f * x;
}
inline LL gl()
{
LL f = 1, x = 0; char c = getchar();
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return f * x;
}
int n, m, a[23][23], dp[(1 << 20) + 5][23];
int main()
{
//freopen(".in", "r", stdin);
//freopen(".out", "w", stdout);
n = gi();
for (int i = 0; i < n; i+=1)
for (int j = 0; j < n; j+=1)
a[i][j] = gi();
memset(dp, 0x3f, sizeof dp);
dp[1][0] = 0;
for (int i = 0; i < (1 << n); i+=1)
{
for (int j = 0; j < n; j+=1)
{
if (i >> j & 1) //判断集合 i 中是否含有 j
{
for (int k = 0; k < n; k+=1)
{
if ((i - (1 << j)) >> k & 1) //判断没有访问 j 之前有没有访问过 k
{
dp[i][j] = min(dp[i][j], dp[i - (1 << j)][k] + a[k][j]); //转移
}
}
}
}
}
printf("%d
", dp[(1 << n) - 1][n - 1]);
return 0;
}