金币 (coin) hgoi0422

金币 (coin) hgoi0422 题解


题目描述

  • 金银岛上的人使用金币,每种金币面值分别是 A1; A2; A3; : : : ; An 元。一天 Tony 决定在
    附近商店买一个非常好的表,他想在付钱的时候不要找零,但是他发现他的钱包里每种金
    币的数量分别只有 C1; C2; C3; : : : ; Cn 个。不过,Tony 知道这块表的价格不会超过 M 元金币
    (他不知道表的精确价格)。不知他的付钱方式能否实现。
    你的任务是帮助 Tony 算一下,在 1::M 元范围内(包括边界),他钱包中的金币可以精
    确支付多少种价格。

输入

  • 输入包括多组测试数据。每组测试数据的格式如下:
    第一行包括 2 个整数 n; M。
    第二行包括 2n 个整数 A1; A2; A3; : : : ; An 和 C1; C2; C3; : : : ; Cn。
    测试数据的最后一行有 2 个 0,这一行无需处理。

输出

  • 每组测试数据输出一行,为一个整数,即能精确支付的价格种数。

样例

  • 输入样例
  • 3 10
    1 2 4 2 1 1
    2 5
    1 4 2 1
    0 0
  • 输出样例
  • 8
    4
  • 数据范围
  • 对于 30% 的数据,1 N 30; 1 M 1000
    对于 60% 的数据,1 N 60
    对于 100% 的数据,1 n 100; 1 M 105
    ; 1 Ai 105
    ; 1 Ci 1000

题目解析

  • 本题是多重背包,可以考虑将其化为01背包方便代码。
  • 主要的思路是将价值为 a 的 c 件物品进行分割,放到一个大的背包当中进行01背包。
  • 最容易想到的是直接将 c 件物品放入,但是仔细思考会发现这样做的时间复杂度会达到O(nm∑c),
    显然已经超出了能够承受的范围。
  • 于是可以稍微改变思路。将c 件 价值为a的物品分成恰好能合成 1到c 所有数量的几份
    很显然,用二进制的思路可以解决这个问题。
  • c = 2^t - 1 + k,k为常数项,那么c就等于 2^0 + 2^1 +…2^t-1 + k,而前面二的幂次可以代表二进制上的每一位
    加上后面的常数项即可通过几项相加表达出1到c所有的整数
  • 需要注意的是这里的t需要是可行的最大值,否则会带来错误。
  • 如此解决此题的时间复杂度为O(nm∑log2c)

附上AC程序

时间复杂度 O (nm∑log2c)

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <cmath>
#include <cstring>
using namespace std;
long n,m,a[101],c[101],f[51200100];
vector<long> src;
void finit(){
    freopen("coin.in","r",stdin);
    freopen("coin.out","w",stdout);
}
int main(){
    finit();
    while (cin>>n>>m){
        src.clear();
        if (n==0&&m==0)
            break;
        for (int i=1;i<=n;i++)
            cin>>a[i];
        for (int i=1;i<=n;i++)
            cin>>c[i];
        src.push_back(0);//新的大01背包
        for (int i=1;i<=n;i++){
            int j=0;
            while (c[i]>=(1<<j)){
                f[a[i]*(1<<j)]=1;
                src.push_back(a[i]*(1<<j));
                c[i]-=(1<<j);
                j++;
            }
            if (c[i]>0){
                f[a[i]*c[i]]=1;
                src.push_back(a[i]*c[i]);   
            }//对每种物品进行分割
        }
        memset(f,-0x3f,sizeof(f));
        f[0]=0;
        for (int i=1;i<=src.size()-1;i++)
            for (int j=m;j>=src[i];j--)
                f[j]=max(f[j],f[j-src[i]]+1);//01背包
        int res=0;
        for (int i=1;i<=m;i++)      
            if (f[i]>0)
                res++;//统计最终解
        cout<<res<<'\n';
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值