题目链接:
POJ 3260 The Fewest Coins
题意:
有个人携带了n种货币,需要付款m元,给出n种货币的面值和每种货币的数量,付款总是用最少数量的货币,找零也总是用最少数量的货币,并且找零的货币种类也是他携带的那些货币种类,但是找零的货币数量是不限的,问完成付款和找零最少需要多少枚货币?如果完成不了,输出-1。
例如:n=3,m=70,货币面额是5,25,50,对应的货币数量是5,2,1.那么只需要3枚货币即可。(付款:1*25+50*1,找零:5*1)
分析:
付款是多重背包,找零是完全背包。
处理多重背包时可以用01背包。将第i种物品Mi件分成若干件01背包中的物品,其中每件物品有一个系数。这件物品的费用和价值均是原来的费用和价值乘以这个系数。令这些系数分别为1,2,2^2…2^(k-1),Mi-2^k+1,且k是满足Mi-2^k+1>0的最大整数。
例如,如果Mi=13,则相应的k=3,这种最多取13件的物品应被分成系数分别为1,2,4,6的四件物品。
这是二进制的思想,因为不管最优策略选几件第i种物品,其件数写成二进制后总可以表示成若干个2^k件物品的和。
最要紧的是背包上限,我以为是m+MAX_VAL,(MAX_VAL是最大面额),结果WA了n发。。。
看了discuss发现原来是MAX_VAL*MAX_VAL,虽然不是很理解,但是下次遇到我一定把数组开得大大的!!!
//456K 32MS
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int MAX_N=110;
const int MAX_M=20150;
const int MAX_CHANGE=4000;
const int INF=0x3f3f3f3f;
int n,m;
int val[MAX_N],num[MAX_N],dp1[MAX_M],dp2[MAX_CHANGE];
//dp1[j]是付款j元的最少硬币数,dp2[j]是找回j元的最少硬币数
int main()
{
while(~scanf("%d%d",&n,&m)){
int MAX_VAL=0;
for(int i=1;i<=n;i++){
scanf("%d",&val[i]);
MAX_VAL=max(MAX_VAL,val[i]);
}
MAX_VAL*=MAX_VAL/4;
for(int i=1;i<=n;i++){
scanf("%d",&num[i]);
}
for(int i=1;i<=m+MAX_VAL;i++) dp1[i]=INF;
dp1[0]=0;
for(int i=1;i<=n;i++){
int j;//多重背包用01背包
for(j=1;2*j<=num[i]&&j*val[i]<=m+MAX_VAL;j*=2){
for(int k=m+MAX_VAL;k>=j*val[i];k--){
dp1[k]=min(dp1[k],dp1[k-j*val[i]]+j);
//if(dp1[k]!=INF) printf("i=%d j=%d k=%d dp1[k]=%d\n",i,j,k,dp1[k]);
}
}
int left=(num[i]-j+1);
for(int k=m+MAX_VAL;k>=left*val[i]&&left*val[i]<=m+MAX_VAL;k--){
dp1[k]=min(dp1[k],dp1[k-left*val[i]]+left);
//if(dp1[k]!=INF) printf("i=%d left=%d k=%d dp1[k]=%d\n",i,left,k,dp1[k]);
}
}
for(int i=1;i<=MAX_VAL;i++) dp2[i]=INF;
dp2[0]=0;
for(int i=1;i<=n;i++){
for(int j=val[i];j<=MAX_VAL;j++){
dp2[j]=min(dp2[j],dp2[j-val[i]]+1);
}
}
int ans=INF;
for(int i=m;i<=m+MAX_VAL;i++){
if(dp1[i]==INF||dp2[i-m]==INF) continue;
//printf("i=%d dp1[i]=%d dp2[i-m]=%d\n",i,dp1[i],dp2[i-m]);
ans=min(ans,dp1[i]+dp2[i-m]);
}
printf("%d\n",ans==INF?-1:ans);
}
return 0;
}