题目
http://codevs.cn/problem/2800/
题解
这是状态压缩型动态规划。
mdzz做了一节晚自习结果1A了好爽qaq
第一次做状压感觉有点费劲。。。
状态很好设计:f[i][j]表示在i情形下,当前所在城市为j的最小时间。i是一个二进制数,有N+1位,每一位为0或1代表了这个城市是否被送过外卖。
很明显目标状态是f[(1<<(N+1))-1][0],就是说全都送完了并且最后在0这个点的最短时间。
考虑状态f[i][j],首先考虑我是第一次来j这个点的情况,那么就要在i中抠去1<<j,令t=i-(1<<j),用剩余的1转移f[i][j],就是这个意思:对于t中每一个1所对应的点的编号k,用f[t][k]+dist[k][j]去更新f[i][j]。这样得到的f[i][j]是说我仅仅最后一步走到了j这个点,之前一直没有走过。
根据题目要求,一个点可能被走多次。考虑一个点被走了多次的情况。那么你就要用一个第j位是1的状态来更新f[i][j],即是说用f[i][k]+dist[k][j]更新f[i][j]。在做这次更新之前,你的f[i][j]存的应该是最后一步走到j且之前没有来过j的情况,那么用f[i][k]+dist[k][j]更新f[i][j]的意义就是仍然在i情形下,又从j出发走了一步到已走过的点。根据题意,可能存在如下情形:
明显地,走到2之后,你要走两步到0,如果仅转移一次的话,答案就成了102,而实际是4。也就是说我们应该做N次这样的同情形下的转移。
(后来补上的:)
其实上面最后讨论的那个走多次的问题,让复杂度多一个N。而这个其实可以用一个floyd预处理替代,第二次更新时直接用最短路来更新就好了。
代码
//状态压缩型动态规划
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 17
#define inf 0x3f3f3f3f
using namespace std;
int f[1<<maxn][maxn], N, map[maxn][maxn];
void input()
{
int i, j;
scanf("%d",&N);
for(i=0;i<=N;i++)for(j=0;j<=N;j++)scanf("%d",map[i]+j);
}
int dp()
{
int i, j, k, ans=inf, ii;
memset(f,inf,sizeof(f));
f[1][0]=0;
for(i=1;i<(1<<N+1);i++)
{
for(j=0;j<=N;j++)
if(i&(1<<j))
for(k=0;k<=N;k++)
if(i&(1<<k))
f[i][j]=min(f[i][j],f[i&~(1<<j)][k]+map[k][j]);
for(ii=1;ii<=N;ii++)
for(j=0;j<=N;j++)
if(i&(1<<j))
for(k=0;k<=N;k++)
if(i&(1<<k))
f[i][j]=min(f[i][j],f[i][k]+map[k][j]);
}
return f[(1<<(N+1))-1][0];
}
int main()
{
input();
printf("%d",dp());
return 0;
}
这是Floyd+状压DP(487ms)
//fpoyd+状压DP
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 17
#define inf 0x3f3f3f3f
using namespace std;
int f[1<<maxn][maxn], N, dist[maxn][maxn];
void input()
{
int i, j;
scanf("%d",&N);
for(i=0;i<=N;i++)for(j=0;j<=N;j++)scanf("%d",dist[i]+j);
}
void floyd()
{
int i, j, k;
for(k=0;k<=N;k++)
for(i=0;i<=N;i++)
for(j=0;j<=N;j++)
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
}
int dp()
{
int i, j, k;
memset(f,inf,sizeof(f));
f[0][0]=0;
for(i=1;i<(1<<(N+1));i++)
{
for(j=0;j<=N;j++)
if(i&(1<<j))
for(k=0;k<=N;k++)
if(i&(1<<j))
f[i][j]=min(f[i][j],f[i&~(1<<j)][k]+dist[k][j]);
for(j=0;j<=N;j++)
if(i&(1<<j))
for(k=0;k<=N;k++)
if(i&(1<<k))
f[i][j]=min(f[i][j],f[i][k]+dist[k][j]);
}
return f[(1<<(N+1))-1][0];
}
int main()
{
input();
floyd();
printf("%d",dp());
return 0;
}