题目描述:从0号点出发,经过1-n点,不要求顺序,而且每个点可以走多次,最后回到0点,求最短时间。
因为要走遍1-n点,那么直接最短路是不行的,不保证没有环,所以dfs时间会多一些,而且这里dfs不好剪枝,那么就可以考虑一下dp,状态就是到某个点,走过了一些点,所用的最短时间,那么就要记录走过了哪些点,用bool数组存并不好,因为dp数组要开二维,而且判断哪些点走过时还要去枚举每个点去判断,既费时又费力。所以这个蹩脚的事情推动IT精英们去发明了状态压缩,留给我们使用。
状态压缩,顾名思义,就是压缩了状态(怎么感觉什么都没有说),直接拿这个题当栗子,有三个点,那么关于这三个点有没有走过的问题,可以用这些数来表示:000、001、010、011、100、101、110、111。很容易看出这是二进制,而且一共2ⁿ个,状态就这样地表示出来了,那么怎么这样怎么进行状态记录和转移呢,在我们新到达点i的时候我们可以让状态 +=2的i次方 ;我们查询点i有没有被走过,可以让状态 |2的i次方,这样处理完之后如果状态改变了那么就说明点i没有被走过(只有在i那个位置为0的时候才会改变)。
另外这个题要提前floyd求一下每两个点之间的最短路。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int g[20][20],dp[20][(1<<16)];
int min(int a,int b)
{
return (a<b)?a:b;
}
int main()
{
int n;
cin>>n;
for (int i=0;i<=n;i++)
for (int j=0;j<=n;j++)
scanf("%d",&g[i][j]);
for (int k=0;k<=n;k++)
for (int i=0;i<=n;i++)
if (i!=k)
for (int j=0;j<=n;j++)
if (j!=k&&j!=i)
g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
memset(dp,127/3,sizeof(dp));
dp[0][0]=0;
int len=(1<<(n+1))-1;
for (int k=0;k<=len;k++) //枚举走的情况
for (int i=0;i<=n;i++) //枚举终点,下面那个是枚举起点
{
if ((k|(1<<i))!=k) continue;
for (int j=0;j<=n;j++) dp[i][k]=min(dp[i][k],min(dp[j][k-(1<<i)],dp[j][k])+g[j][i]);
}
cout<<dp[0][len];
return 0;
}