Description
Input
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]);
}