HDU 3591 The trouble of Xiaoqian 混合背包(完全背包和多重背包混合)

来源:互联网 发布:万网域名续费多少钱 编辑:程序博客网 时间:2024/06/14 02:18

传送门:HDU 3591


题目大意:小倩去买一件价值为 t 东西,她有 n 种钱币,第i种价值为 Vi,数量为 Ci。售货员那也有这 n 种货币,但是数量无限。如果小倩付款给的价值大于 t,售货员就要找零。问小倩需要带的货币数加上售货员找零的钱币数的最小和是多少。


Sample Input
3 705 25 505 2 10 0
 
Sample Output
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 0x3f3f3f3fint 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,而不是tdp[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;}

阅读全文
1 0
原创粉丝点击