给出n种面值的硬币,以及它们的数量,问能组成不超过m的面值有多少个。
样例2解释:
2 5 1 4 2 1面值为1的硬币2个,面值为4的硬币1个。能组成的面值有1、2、4、5、6。其中不超过面值5的面值有4个。
典型的多重背包问题。
这里的面值既相当于背包问题中的物品体积,又相当于物品的价值。
因此dp[i]表示的是能组成的不超过i的最大面值,即dp[i]<=i,对于某种面值k,若i>k且dp[i]=k,则之前必然有dp[k]=k。
最终统计答案的时候,从1到m扫一遍,统计dp[i]=i的面值i的数量即可,这样可保证能组成的各种面值刚好只被统计一次。
关于二进制优化,其实就是利用二进制的原理对同种的物品的数量进行划分。将多个同种物品视为一个物品,其价值为原价值乘上划分的数量,体积为原体积乘上划分的数量。
然后将问题转化为0-1背包求解。相比于直接转换为0-1背包,时间复杂度由O(V*sum)优化到O(V*(log(sum)) 其中sum为所有物品的总数量。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define inf 0x3f3f3f3f #define maxn 101 #define maxV 100005 int v[maxn],num[maxn]; int dp[maxV]; inline void zero(int vol,int val,int W) { for(int i=W;i>=vol;--i) dp[i]=max(dp[i],dp[i-vol]+val); } inline void complet(int vol,int val,int W) { for(int i=vol;i<=W;++i) dp[i]=max(dp[i],dp[i-vol]+val); } void multi(int vol,int val,int num,int W) { if(vol*num>=W){ complet(vol,val,W); return; } for(int i=1;i<=num;i<<=1){ zero(i*vol,i*val,W); num-=i; } if(num) zero(num*vol,num*val,W); } int main() { int n,W,i; while(scanf("%d%d",&n,&W)!=EOF&&(n+W)) { memset(dp,0,sizeof(dp)); for(i=0;i<n;++i) scanf("%d",&v[i]); for(i=0;i<n;++i) scanf("%d",&num[i]); for(i=0;i<n;++i) multi(v[i],v[i],num[i],W); int ans=0; for(i=1;i<=W;++i) if(dp[i]==i) ++ans; printf("%d\n",ans); } return 0; }