题目:从价值和数量分别为a[i]和c[i]的n种硬币中,最多可以组成多少不超过m价值的组合?也就是问在1-m中,有多少个数可以由这些硬币加和得到。(楼教主的男人八题之一,算是一类经典问题)
与多重背包问题很相似,但是用多重背包直接dp或者简单优化的dp很可能会T。
(感觉自己都会写,其实不是那么一回事,并不代表就能AC)
dp[i][j]表示前i种硬币构成j的钱数时,第i种硬币还剩多少,(不能构成则为负数)。
如果dp[i-1][j]>=0(即前i-1种已经可以加和为j),则dp[i][j]=c[i](即全部剩下)。
如果上述不成立并且a[i]<j,那么肯定此时不能加和为j,dp[i][j]=-1。
其他情况直接dp[i][j-a[i]]-1即可。
因为题中i和 i-1状态不必同时出现,又因为dp[j-a[i]]是向后更新的,所以可以重复利用一维dp数组,更新时正循环。
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=110;
const int maxm=100010;
int a[maxn],c[maxn],dp[maxm];
int n,m;
int main()
{
while(~scanf("%d%d",&n,&m),n+m)
{
memset(dp,-1,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)///j++为正循环
{
if(dp[j]>=0) ///判断条件中的dp[j]其实是dp[i-1][j]
dp[j]=c[i];
else if(j<a[i])
dp[j]=-1;
else
dp[j]=dp[j-a[i]]-1;
}
int ans=0;
for(int i=1;i<=m;i++)
if(dp[i]>=0) ans++;
printf("%d\n",ans);
}
return 0;
}