传送门:HDU 3591
题目大意:小倩去买一件价值为 t 东西,她有 n 种钱币,第i种价值为 Vi,数量为 Ci。售货员那也有这 n 种货币,但是数量无限。如果小倩付款给的价值大于 t,售货员就要找零。问小倩需要带的货币数加上售货员找零的钱币数的最小和是多少。
3 70 5 25 50 5 2 1 0 0
Case 1: 3
首先给大家推荐一个很不错的题解:HDU 3591题解。
思路:既然让求两者和最小,则分别求出最小值相加就可以了。很明显,我们应该将价值 t 看作背包的容量,而物品的花费和价值只告诉了一个——每种物品的价值 Vi。我们要求的是数量的最小值。所以我们应该做一下转换,把数量看作装入该物品可以获得的奖励,把物品的价值看作装入该物品的花费。
对于小倩来说,钱币数量有限制,应该用多重背包(dp1)解决。其思路是如果某种物品的总花费大于等于m(Vi * Ci>=m),则相当于该物品有无数件,转化为多重背包解决。反之,转化为01背包解决。也就是将该种物品拆分成单个物品,但是这样时间复杂度很高,所以转化为先拆出1个,如果还可以拆,再拆出2个,4个,8个……即2的 i 次幂个,将拆出的 2^i 个物品看成一个物品。我称之为倍增法。如果剩下的不足 2^i 个,则它们单独看成一个。
例如:13,先拆出1个,剩余12个;再拆出2个,剩余10个;再拆出4个,剩余6个;再拆出8个,发现不够了,这6个看作同一个物品。
当将 num 个物品看成一个物品时,其花费为其总价值 num*Vi,其钱币数量或者说奖励为 num。
对于售货员来说,钱币数量无限制,应该用完全背包(dp2)解决。加入1个某种物品,花费为其价值 Vi,钱币数量或者说奖励为 1,因为加入的是1个。
具体实现:输入后,对于第 i 种物品分别跑一遍多重背包和完全背包,分别表示小倩和售货员使用的钱币的最小数。假设小倩付款 a 元,售货员找零 b 元,则有 a-b=t。所以答案就是 max( dp1[a] + dp2[a-t] )。
注意:
1.当无法通过购买找零得到价值 t 时,输出 -1,并且前面也要带 “Case x:”,被这个地方坑过一次。
2.由于是求最小值,所以背包中的max要改为min,初始化时除了 dp[0],其他都设为无穷大,这样也保证了是正好能装满该背包时候的最小值。
3.求解背包时的容量上限不是价值 t ,而是最大值 20000,因为小倩付得钱可能比 t 大。
4.在多重背包转化为完全背包的代码中,有句 cnt*wt>=t ,改为 cnt*w>=20000 也可以。个人感觉从想法上应该写第一种。
#include<stdio.h>
#include<string.h>
#define inf 0x3f3f3f3f
int t,dp1[20010],dp2[20010];
int min(int a,int b)
{
if(a<b) return a;
else return b;
}
//01背包,prz为奖励(价值),wt为花费(重量)
void ZOP(int dp[],int prz,int wt)
{
int j;
for(j=20000;j>=wt;j--) //注意循环的上限是20000,而不是t
dp[j]=min(dp[j],dp[j-wt]+prz);
}
//完全背包,prz为奖励(价值),wt为花费(重量)
void CP(int dp[],int prz,int wt)
{
int j;
for(j=wt;j<=20000;j++) //注意循环的上限是20000,而不是t
dp[j]=min(dp[j],dp[j-wt]+prz);
}
//多重背包,wt为花费(重量),cnt为物品数量
void MP(int dp[],int wt,int cnt)
{
//该处可以改为 cnt*wt>=20000
if(cnt*wt>=t) CP(dp,1,wt); //转换为完全背包
else //转换为01背包,类似于倍增的思想
{
int k=1;
while(k<=cnt)
{
ZOP(dp,k,wt*k); //耗费为物品总价值,奖励为物品数量
cnt-=k;
k<<=1;
}
if(cnt>0) ZOP(dp,cnt,wt*cnt);
}
}
int main()
{
int i,j,n,ans,cas,v[110],c[110];
cas=1;
while(~scanf("%d%d",&n,&t)&&n&&t)
{
for(i=1;i<=n;i++) scanf("%d",&v[i]);
for(i=1;i<=n;i++) scanf("%d",&c[i]);
memset(dp1,inf,sizeof(dp1));
memset(dp2,inf,sizeof(dp2));
dp1[0]=0;
dp2[0]=0;
for(i=1;i<=n;i++)
{
MP(dp1,v[i],c[i]);
CP(dp2,1,v[i]);
}
ans=inf;
for(i=t;i<=20000;i++)
ans=min(ans,dp1[i]+dp2[i-t]);
if(ans==inf) printf("Case %d: -1\n",cas++);
else printf("Case %d: %d\n",cas++,ans);
}
return 0;
}