状压DP特点是数据范围极小,在20以内。一般这样的题目可以通过暴力搜索与剪枝通过,但更优的方法莫过于状压DP。n种物体选与不选共有(1<<n)种方案,方案下界为0,上界为(1<<n)-1,也就是都选。
状压DP一般是前推后且无后效性。对于每一个位置,都要遍历0-(1<<n)全部状态,对于每一个状态,再枚举各个物体选与否的情况,已经选了就略过,否则前推后进行优化。
注意物体的元素下标最好从0开始,便于二进制表示。
dp[i][j]代表了开i个背包时,装下j状态时的情况,对于每一个j状态,考虑里面没有装的,能装在本背包时,就修改 dp[i][j|(1<<k)]=min(dp[i][j|(1<<k)],dp[i][j]+a[k]); 让第i个背包装下最小的,使空间充分利用。如果装不下该物体,就将dp[i+1][j|(1<<k)]=min(dp[i][j|(1<<k)],a[k]);因为是前推后,每次只能溢出一个,故完全可以直接用a[k]来修改dp[i+1][j|(1<<k)]的情况,使得将全部物体装入这些背包,且前几个背包得到充分利用。
#include<stdio.h>
#include<cmath>
#include<algorithm>
# include<iostream>
# define INF 0x3f3f3f3f
# define LINF 0x3f3f3f3f3f3f3f3f
using namespace std;
int a[20];
int dp[20][1<<20];
int main()
{
int n,v;
cin>>n>>v;
int up=(1<<n)-1;
for(int i=0;i<n;i++)
{
cin>>a[i];
}
for(int i=0;i<n;i++)
{
for(int j=0;j<=up;j++)
{
dp[i][j]=INF;
}
}
for(int i=0;i<n;i++)
{
dp[1][1<<i]=a[i];
}
for(int i=0;i<n;i++)
{
for(int j=0;j<=up;j++)
{
if(dp[i][j]!=INF)
{
for(int k=0;k<n;k++)
{
if((j&(1<<k))!=0)
continue;
if(dp[i][j]+a[k]<=v)
{
dp[i][j|(1<<k)]=min(dp[i][j|(1<<k)],dp[i][j]+a[k]);
}
else
{
dp[i+1][j|(1<<k)]=min(dp[i][j|(1<<k)],a[k]);
}
}
}
}
}
for(int i=0;i<=n;i++)
{
if(dp[i][up]!=INF)
{
cout<<i;
return 0;
}
}
return 0;
}