【背包专题】D - Coins hdu 2844 【01背包+二进制优化】

本文介绍了一种通过给定的多种硬币面额及其数量,计算出能够组合成指定价格区间内所有可能价格的数量的算法。该算法使用了动态规划的思想,并通过二进制优化减少时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Whuacmers use coins.They have coins of value A1,A2,A3...An Silverland dollar. One day Hibix opened purse and found there were some coins. He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch. 

You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.

InputThe input contains several test cases. The first line of each test case contains two integers n(1 ≤ n ≤ 100),m(m ≤ 100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1 ≤ Ai ≤ 100000,1 ≤ Ci ≤ 1000). The last test case is followed by two zeros.OutputFor each test case output the answer on a single line.Sample Input

3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0

Sample Output

8
4

题意:问能够将输入的钱币组合为1~m之间的数的种类总数,比如样例2:m=5,钱币可以组合为:1,2,4,5.所以总的种类数为4.
思路:没有求最值,而是求可到达状态数。参考《背包九讲》里的第九讲。定义dp[i]表示是否可以从这些硬币中选取一些硬币,使得面值之和为i,dp[i]的取值为1表示可以,0表示不可以。
  状态转移方程为dp[i] = dp[i]||dp[i-value[i]]||...dp[i-num[i]*value[i]]
处理每种硬币时,每个状态要转移的次数和num[i]有关,num[i]较大时,时间复杂度比较高,我们可以采用二进制数的方法优化,1,2,4...2^n,只要是小于num[i]的数,我们都可以用这些数字组合而来,将每一组硬币
面值相加作为一个硬币来处理,就可以转为01背包的问题来求解。
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define N 100010

int n,m;
int dp[N],w[10010],value[110],num[110];

int main()
{
    int i,j,ans,x;
    while(scanf("%d%d",&n,&m),n+m)
    {
        memset(dp,0,sizeof(dp));
        for(i = 1; i <= n; i ++)
            scanf("%d",&value[i]);//硬币价值 
        for(i = 1; i <= n; i ++)
            scanf("%d",&num[i]);//硬币数量 
        ans = 0;    
        for(i = 1; i <= n; i ++)
        {
            for(j = 1;num[i] > 0 ; j*=2)//二进制划分硬币数量 
            {
                x = min(j,num[i]);
                w[++ans] = x*value[i];
                num[i] -= x;
            }
        }
        dp[0] = 1;//初始状态为可到达状态 
        for(i = 1; i <= ans; i ++)
        {
            for(j = m; j >= w[i];j--)
            {
                if(dp[j-w[i]])//如果状态可以到达 
                    dp[j] = 1;
            }
        }
        ans = 0;
        for(i = 1; i <= m; i ++)
            if(dp[i])
                ++ans;
        printf("%d\n",ans);
    }
    return 0;
}

<think>嗯,用户问的是为什么在动态规划解决凑零钱问题的时候,dp[i] = min(dp[i], dp[i - coins[j]] + 1) 这里要加一。这个问题看起来是关于状态转移方程的理解,特别是那个加1的意义。 首先,我得回想一下凑零钱问题的基本思路。动态规划中的dp数组通常用来记录达到某个金额所需的最小硬币数。比如,dp[i]表示凑出金额i需要的最少硬币数。那当处理到金额i的时候,如果当前遍历到一个硬币coins[j],那么可能需要用这个硬币来组合成i。 接下来,假设当前处理的是金额i,而coins[j]的面额小于等于i,这时候可以考虑使用这个硬币。这时候的问题就转化为,凑出i - coins[j]这个金额所需的最小硬币数,再加上当前这枚硬币(也就是加1)。因为用了coins[j]这枚硬币,所以总数需要加一。 用户可能不太明白这里的加1是哪里来的。我需要解释清楚,这个加1代表的是当前选择的硬币本身。比如,如果i是5,coins[j]是2,那么i - coins[j]就是3。假设凑出3需要的最少硬币数是dp[3],那么加上这枚2的硬币,总数就是dp[3] + 1。这时候比较当前的dp[5]和这个新计算的值,取最小值。 另外,用户可能对状态转移方程的整体结构不太理解,或者对动态规划的基本思想不熟悉。可能需要用更具体的例子来说明。比如,假设硬币有1、2、5,目标金额是11。当处理到i=11时,检查每个硬币,比如硬币5,这时候要看dp[6]的值,然后加上1,也就是用了5这个硬币后的总数量。 还需要考虑到初始化的部分,比如dp[0] = 0,因为凑0元不需要硬币,而其他的初始化为较大值。这样在每次更新的时候,都是基于之前已经计算好的更小金额的最优解来推导当前金额的解。 可能用户没有完全理解状态转移方程中的递推关系,特别是为什么是i - coins[j]的位置,然后加一。这时候需要强调,每个硬币的选择都会减少目标金额,而加上这个硬币的数量,所以需要加一。 另外,可能用户对动态规划中的子问题分解不太清楚。凑零钱问题将大问题分解为较小的子问题,每个子问题的最优解组合起来得到原问题的最优解。因此,当选择某个硬币时,剩下的金额的最优解加上这一个硬币就是当前可能的解之一,这时候需要比较所有可能的解,找到最小的那个。 总结来说,用户需要明白加1是因为使用了当前硬币,所以硬币数量要增加1,而状态转移方程是通过比较所有可能的硬币选择来找到最小值的。需要从动态规划的基本原理出发,结合具体例子来解释,确保用户能够理解这个关键的步骤。</think>好的,我来解释这个动态规划递推式中 **"+1"** 的含义和必要性。这个表达式是凑零钱问题的核心逻辑: ```c dp[i] = fmin(dp[i], dp[i - coins[j]] + 1); ``` --- ### 一、直观理解:硬币数量的累加 #### 1. 核心思想 - **$dp[i]$** 表示凑出金额 $i$ 需要的最少硬币数 - **$coins[j]$** 是当前尝试使用的硬币面值 #### 2. 关键步骤分解 1. **拆分金额**:当使用一个面值为 $coins[j]$ 的硬币时,剩余的金额为 $i - coins[j]$ 2. **继承子问题解**:$dp[i - coins[j]]$ 是凑出剩余金额的最优解 3. **累加当前硬币**:$+1$ 表示使用了一个 $coins[j]$ 硬币,总数量增加 1 #### 3. 举例说明 假设: - $coins[j] = 5$(硬币面值5元) - $i = 11$(目标金额11元) 则: 1. 使用1个5元硬币后,剩余金额 $11 - 5 = 6$ 元 2. 凑6元的最优解是 $dp[6] = 2$(比如5+1元) 3. 总硬币数 $dp[6] + 1 = 3$ --- ### 二、数学视角:状态转移方程 #### 1. 递推公式 $$ dp[i] = \min_{\substack{coins[j] \leq i}} \Big( dp[i - coins[j]] + 1 \Big) $$ #### 2. 符号含义 | 符号 | 意义 | |-----|-----| | $dp[i - coins[j]]$ | 不包含当前硬币的子问题最优解 | | $+1$ | 当前硬币的贡献 | #### 3. 动态规划的本质 - **子问题组合**:将大问题分解为更小金额的子问题 - **决策过程**:每个硬币选择都是一次决策,记录最优决策路径 --- ### 三、为什么必须加1? #### 1. 硬币数量的物理意义 - 若不加1,相当于假设 $coins[j]$ 硬币没有使用 - 加1是为了明确计入当前硬币的消耗 #### 2. 错误示例 假设硬币 `[1,2,5]`,目标金额 `3`: - 正确逻辑:$dp[3] = \min(dp[2] + 1, dp[1] + 1)$ - $dp[2] = 1$(2元硬币),$dp[1] = 1$(1元硬币) - 结果:$\min(1 + 1, 1 + 1) = 2$(正确解:2+1元或1+2元) - 错误逻辑(不加1):$\min(dp[2], dp[1]) = 1$(完全错误) --- ### 四、与其他场景对比 #### 1. 对比背包问题 - **背包问题**:价值累加,不加1(价值可能非整数) - **凑零钱**:硬币数量累加,必须加1(每个硬币计数为1) #### 2. 对比路径问题 - **网格路径数**:直接相加(路径数量不涉及"操作次数") - **凑零钱**:操作次数累加(每个硬币对应一次操作) --- ### 五、总结 | 关键点 | 说明 | |-------|-----| | **物理意义** | 明确计入当前使用的硬币 | | **数学表达** | 状态转移方程的必要组成部分 | | **算法逻辑** | 保证子问题解与当前操作的衔接 | 通过这种设计,动态规划可以准确记录每步操作的成本(硬币数量),最终得到全局最优解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值