CodeVS2800 送外卖

本文介绍了一个状态压缩型动态规划问题的解决方法,包括状态设计、转移方程,并给出了两种实现方式:纯状态压缩DP和Floyd+状态压缩DP,后者通过预处理减少复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

  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预处理替代,第二次更新时直接用最短路来更新就好了。

代码

这是纯状压DP(2533ms)
//状态压缩型动态规划 
#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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值