POJ 3260 The Fewest Coins(完全背包+多重背包(转化为01背包))

22 篇文章 0 订阅

题目链接:
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值