题目大意:给定一张图,有0至n(n<=15)这些点,两两之间均有通路,求从0开始经过所有点(可以重复)再回到0的最小花费。
思路:n挺小,但dfs还是会超时,正解是floyd+状压DP。
首先需要面对的一个问题是如何表示访问过哪些点,没访问过哪些点,这可以用二进制的思想解决。即利用某个数的二进制表示中某一位的0或1来表示该点是否存在于集合中,例如,一共n+1个点,如果都在集合中,则可以表示为 1<<(n+1)−1 ,某一个点i在集合中,表示为 1<<i 。
floyd用于求出某两点间的最短路,用于转移状态。用f[i][j]表示在访问情况为i时访问至j点所需要的最小花费,则有状态转移方程(bit(j)表示1左移j位):f[i][j]=min(f[i][j],min(f[i-bit(j)][k]+map[k][j],f[i][k]+map[k][j])),i&bit(j)=1
初始化f[0][0]=0,其余 ∞ ,不必考虑上式中k是否存在于集合,因为程序中的if (i&bit(j))使得k这时已经被去除考虑范围,而初始化为
∞ ,求的是min。代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=17;
int n,map[maxn][maxn],f[1<<maxn][maxn];
int bit(int x)
{
return 1<<x;
}
int main()
{
scanf("%d",&n);
int i,j,k;
for (i=0;i<=n;++i)
for (j=0;j<=n;++j)
scanf("%d",&map[i][j]);
for (k=0;k<=n;++k)
for (i=0;i<=n;++i)
for (j=0;j<=n;++j)
map[i][j]=min(map[i][j],map[i][k]+map[k][j]);
memset(f,0x3f3f3f,sizeof(f));
f[0][0]=0;
for (i=0;i<=(1<<(n+1))-1;++i)
for (j=0;j<=n;++j)
for (k=0;k<=n;++k)
if (i&bit(j))
f[i][j]=min(f[i][j],min(f[i-bit(j)][k]+map[k][j],f[i][k]+map[k][j]));
printf("%d",f[(1<<n+1)-1][0]);
return 0;
}