AcWing 278. 数字组合

AcWing 278. 数字组合

【总结】背包问题的至多/恰好/至少

一、题目描述

给定 N 个正整数 A1,A2,,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。

输入格式

第一行包含两个整数 NM

第二行包含 N个整数,表示 A1,A2,,AN

输出格式

包含一个整数,表示可选方案数。

数据范围
1N100,1M10000,1Ai1000,答案保证在 int 范围内。

输入样例

4 4
1 1 2 2

输出样例

3

二、01背包求解恰好装满方案数

分析

对于本题我们可以把每个 正整数 看作是一个 物品

正整数 的值就是 物品体积

我们方案选择的 目标 是最终 体积 恰好m 时的 方案数

于是本题就变成了 01背包求解方案数 的问题了

状态表示

f[i][j]:考虑前i个数,当前总和 恰好j时,方案数量是多少。

状态转移

  • 不选if[i][j]+=f[i1][j]
  • if[i][j]+=f[i1][jv[i]]

初始状态:

f[i][0]=1

解释:不管你让我从多少个物品中选择,只要是背包容量是0,那么方案就只有1种,就是,啥都不要。

目标状态

f[n][m]

时间复杂度

O(n×m)

三、二维代码

#include <bits/stdc++.h>

using namespace std;
const int N = 110;
const int M = 10010;
int n, m;
int v;
int f[N][M];

int main() {
    cin >> n >> m;

    for (int i = 0; i <= n; i++) f[i][0] = 1; // base case

    for (int i = 1; i <= n; i++) {
        cin >> v;
        for (int j = 1; j <= m; j++) {
            // 从前i-1个物品中选择,装满j这么大的空间,假设方案数是5个
            // 那么,在前i个物品中选择,装满j这么大的空间,方案数最少也是5个
            // 如果第i个物品,可以选择,那么可能使得最终的选择方案数增加
            f[i][j] = f[i - 1][j];
            // 增加多少呢?前序依赖是:f[i - 1][j - v]
            if (j >= v) f[i][j] += f[i - 1][j - v];
        }
    }
    // 输出结果
    printf("%d\n", f[n][m]);
    return 0;
}

四、一维代码

#include <bits/stdc++.h>

using namespace std;
const int N = 10010;

int n, m;
int v;
int f[N]; // 在前i个物品,体积是j的情况下,恰好装满的方案数

int main() {
    cin >> n >> m;

    // 体积恰好j, f[0]=1, 其余是0
    f[0] = 1;
    for (int i = 1; i <= n; i++) {
        cin >> v;
        for (int j = m; j >= v; j--)
            f[j] += f[j - v];
    }
    printf("%d\n", f[m]);
    return 0;
}

五、常见问题

Q:如果讨论的不是数量,而是最大价值,有什么区别呢?

A:我们可以将结论推广到不同属性的情况下,本题的属性是数量,但如果是最大价值呢?
我们不难得到需要将f[0]初始化为0f[1n]初始化为负无穷

为什么要这样设置呢?因为每一个新状态,都需要知道它可以从哪些旧状态转移而来,如果上一个状态是合法的,那么有可能从上一个状态转移而来,但如何标识上一个状态是不是合法呢?比如如果初始化状态值是0,并且上一个状态是0,表示的是目前的最大值,那是不是合法呢?不好说啊,为什么呢?

  • 上一个状态不合法,没有状态转移过来
  • 上一个状态合法,因为有负数等原因,造成最大值确实为0

这就很难办的,是吧。搞不清楚上一个状态是不是合法,我就没法决策是不是可以从它转移过来,我必须想办法对合法与不合法状态进行区分,是吧?

办法就是初始化为INF,再转移啥负值,也不可能小于INF,所以,很容易就区分开了是不是正常合法状态,还是从来就没有到达过的不合法状态。

posted @   糖豆爸爸  阅读(277)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
历史上的今天:
2017-12-09 为预热准备更新时间列的查询办法,解决原表中没有索引的问题
2015-12-09 需要继续研究
Live2D
点击右上角即可分享
微信分享提示