当前位置:首页 > 技术 > 正文内容

HDU 2844 硬币问题:多重背包解法

访客 技术 2026年8月28日 1

问题描述

Whuacmers 使用硬币进行支付。他们拥有面值为 A1, A2, A3...An 的银币。某天 Hibix 打开钱包发现了一些硬币,他想用这些硬币购买一块价格不超过 m 的手表,并且希望正好支付手表的价格(不需要找零)。

编写程序读取 n、m 以及各硬币的面值和数量,计算能够恰好支付多少种不同的价格(范围从 1 到 m)。

输入格式

输入包含多个测试用例。每个测试用例的第一行包含两个整数 n(1 ≤ n ≤ 100)和 m(m ≤ 100000)。第二行包含 2n 个整数,表示 A1, A2, A3...An, C1, C2, C3...Cn(1 ≤ Ai ≤ 100000, 1 ≤ Ci ≤ 1000)。输入以两个 0 结束。

输出格式

对于每个测试用例,在一行中输出答案。

样例输入

3 10
1 2 4 2 1 1

2 5
1 4 2 1

0 0

样例输出

8
4

解决方案

这是一个典型的多重背包问题。每种面值的硬币都有一定的数量限制,需要计算在给定约束下可以组成多少种不同的金额。

解决思路是将多重背包问题转化为 01 背包和完全背包的组合。当某种硬币的总价值超过目标金额时,可视为完全背包处理;否则使用二进制优化的 01 背包方法。

实现方案一:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;

int value[110];
int count[110];
int dp[100005];

int main()
{
    int n, m;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        if(n == 0 && m == 0)
            break;
            
        memset(value, 0, sizeof(value));
        memset(count, 0, sizeof(count));
        for(int i = 1; i <= m; i++)
            dp[i] = -9999999;
        dp[0] = 1;
        
        for(int i = 1; i <= n; i++)
            scanf("%d", &value[i]);
        for(int i = 1; i <= n; i++)
            scanf("%d", &count[i]);
            
        for(int i = 1; i <= n; i++)
        {
            if(value[i] * count[i] >= m) // 完全背包
            {
                for(int j = value[i]; j <= m; j++)
                {
                    dp[j] = max(dp[j], dp[j - value[i]] + value[i]);
                }
            }
            else // 01背包优化
            {
                for(int k = 1; k <= count[i]; k *= 2)
                {
                    for(int j = m; j >= value[i] * k; j--)
                    {
                        dp[j] = max(dp[j], dp[j - value[i] * k] + value[i] * k);
                    }
                    count[i] -= k;
                }
                if(count[i] > 0)
                {
                    for(int j = m; j >= value[i] * count[i]; j--)
                        dp[j] = max(dp[j], dp[j - value[i] * count[i]] + value[i] * count[i]);
                }
            }
        }
        
        int result = 0;
        for(int i = 1; i <= m; i++)
        {
            if(dp[i] >= 0)
                result++;
        }
        printf("%d\n", result);
    }
    return 0;
}

实现方案二:

#include<iostream>
#include<string.h>
using namespace std;

int coinValue[200], coinCount[200], dp[100005];

void zeroOnePack(int val, int vol, int capacity)
{
    for(int i = capacity; i >= vol; --i)
    {
        dp[i] = max(dp[i], dp[i - vol] + val);
    }
}

void completePack(int val, int vol, int capacity)
{
    for(int i = vol; i <= capacity; ++i)
    {
        dp[i] = max(dp[i], dp[i - vol] + val);
    }
}

void multiplePack(int val, int vol, int num, int capacity)
{
    if(vol * num >= capacity)
    {
        completePack(val, vol, capacity);
        return;
    }
    
    for(int i = 0; (1 << i) <= num; ++i)
    {
        zeroOnePack(val << i, vol << i, capacity);
        num -= (1 << i);
    }
    
    if(num)
    {
        zeroOnePack(val * num, vol * num, capacity);
    }
}

int main()
{
    int n, m;
    while(cin >> n >> m)
    {
        if(n + m == 0)
            break;
            
        memset(dp, 0, sizeof(dp));
        
        for(int i = 0; i < n; i++)
            cin >> coinValue[i];
        for(int i = 0; i < n; i++)
            cin >> coinCount[i];
            
        for(int i = 0; i < n; i++)
        {
            multiplePack(coinValue[i], coinValue[i], coinCount[i], m);
        }
        
        int total = 0;
        for(int i = 1; i <= m; i++)
        {
            if(dp[i] == i)
                total++;
        }
        cout << total << endl;
    }
    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。