bzoj 2073:[POI2004]PRZ 状压DP

Description

一只队伍在爬山时碰到了雪崩,他们在逃跑时遇到了一座桥,他们要尽快的过桥. 桥已经很旧了, 所以它不能承受太重的东西. 任何时候队伍在桥上的人都不能超过一定的限制. 所以这只队伍过桥时只能分批过,当一组全部过去时,下一组才能接着过. 队伍里每个人过桥都需要特定的时间,当一批队员过桥时时间应该算走得最慢的那一个,每个人也有特定的重量,我们想知道如何分批过桥能使总时间最少.

Input

第一行两个数: w – 桥能承受的最大重量(100 <= w <= 400) 和 n – 队员总数(1 <= n <= 16). 接下来n 行每行两个数分别表示: t – 该队员过桥所需时间(1 <= t <= 50) 和 w – 该队员的重量(10 <= w <= 100).

Output

输出一个数表示最少的过桥时间.

        最近一直在逐个过知识点,所以我会连续发几道状压DP的题目。

      这道题的DP应该是很好想的,将dp[state] state的0或1表示每个人选或者没选。

      转移也比较容易想到,即由state中一部分1变为0的更小的dp[state]转译而来。

      可是这是我们发现,这题n有16,如果直接从0开始枚举能转移到当前state的子状态应该会超时,所以我们需要对这个子状态的枚举进行优化,由于直接枚举会有非常多的冗余状态,所以我们可以想一个办法只枚举当前状态的1选或不选,这样该怎么枚举呢,先上代码。

       for(int substate=state;substate;substate=(state)&(substate-1))

       这是我在题解里找到的...大家都说自己画一画就出来了,但是我却花了半节数学课理解,下面说一下这个枚举的合理性。

       对于每次的substate-1,从二进制上来看,就是将最右边的一个1变为0,其右边的0变成1。它在与上原状态后,则能保证这时有1的一定是原状态中的1。所以每次去掉最右边的一个1,把其右边的0变为1,当最左边的1去掉后也不可能复原,所以即可从大到小枚举所有子状态了。

     (其实后来我用for(int substate=0;substate<=state;substate++)的方法试了一下,发现也可以过,不过慢了25倍!)

       对于时间和体重的处理可以在预处理中进行,求出每个状态要花的总时间以及重量,即可做到很快的进行转移了。

       最后答案就是所有人全选状态的答案了。

       下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#define maxn 17
using namespace std;
int T,n;
int t[maxn],w[maxn];
int wei[(1<<maxn)],maxx[(1<<maxn)];
int dp[(1<<maxn)];
int getwei(int now)
{
	int ans=0;
	for(int i=0;i<n;i++)
	if((1<<i)&now)
	{
		ans+=w[i];
	}
	return ans;
}
int gettim(int now)
{
	int ans=0;
	for(int i=0;i<n;i++)
	if((1<<i)&now)
	{
		ans=max(ans,t[i]);
	}
	return ans;
}
int main()
{
	scanf("%d%d",&T,&n);
	
	for(int i=0;i<n;i++)
	scanf("%d%d",&t[i],&w[i]);
	
	for(int state=0;state<(1<<n);state++)
	{
		wei[state]=getwei(state);
		maxx[state]=gettim(state);
	}
	
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
	
	for(int state=0;state<(1<<n);state++)
	{
		for(int substate=0;substate<=state;substate++)
		if(wei[substate]<=T)
		{
			dp[state]=min(dp[state],dp[state^substate]+maxx[substate]);
		}
	}
	printf("%d\n",dp[(1<<n)-1]);
} 


      

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值