You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.1 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 1023. 买书

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

一、题目描述

小明有 m 块钱,现有 10 元, 20 元, 50 元, 100 元 的书

每本书可以 购买多次,求小明有 多少种 买书 方案

注:钱需要花完

输入格式

一个整数 n,代表总共钱数。

输出格式

一个整数,代表选择方案种数。

数据范围

0≤n≤1000

输入样例1

20

输出样例1

2

输入样例2

15

输出样例2

0

输入样例3

0

输出样例3

1

二、分析

一共有 n 个物品,每个物品有体积 v_i,价值 w_i,每个物品能够选多次

求总体积恰好是m的方案数

这是一道 裸的完全背包问题求解方案数

闫氏DP分析法

状态表示——集合:f[i][j] 表示考虑前i个数字,且总数字和 恰好 j的集合下能获得的方案数。

状态表示——属性:因为是求方案数,故为 count

状态计算——集合划分:考虑第 i 个数选不选。

  • 不选或选不了(剩余数量不够 j<a[i]f[i1][j]
  • 选:f[i][ja[i]]

初始状态:f[0][0]

目标状态:f[n][m]

二、朴素版本

时间复杂度:O(n^2 \times m)

#include <bits/stdc++.h>

using namespace std;

const int N = 5;
const int M = 1010;

int v[N] = {0, 10, 20, 50, 100}; // 每种货币下标从1开始
int n, m;                        // 货币种类,钱数
int f[N][M];                     // 前i种物品体积恰好是j的情况下的最大值

// 完全背包
int main() {
    n = 4;
    cin >> m;
    // 前0种物品体积是0的情况下只有一种方案
    // 一般询问方案数的问题f[0]都会设置为1
    //  Q:那20元钱呢不买买两本10块的每一本20的。三种呀
    //  A:题目说的全部,钱要花完
    f[0][0] = 1;
    for (int i = 1; i <= n; i++)                // 每个物品
        for (int j = 0; j <= m; j++)            // 每个体积
            for (int k = 0; v[i] * k <= j; k++) // 个数
                f[i][j] += f[i - 1][j - v[i] * k];
    printf("%d\n", f[n][m]);
    return 0;
}

三、完全背包—经典优化

使用瞪眼大法,观察 f(i,j)状态转移方程 进行变形 尝试找出f(i,j)与它的前序f(i,j-v_i)之间的关联关系,看看能不能实现f(i,j-v_i)->f(i,j)的迁移:

\large f(i,j)=f(i-1,j)+f(i-1,j-v_i)+...+f(i-1,j-s\cdot v_i)①
\large f(i,j-v_i)=   f(i-1,j-v_i)+...+f(i-1,j-s\cdot v_i)②

注:把体积j-v_i代入①式,就可以得到 ②式

Q:①和②中的s是一个值吗,为什么? 答:是一个值的。原因可以从事情本质出发,思考一下s\cdot v_i的含义是什么:就是在j这么大的空间限制下,最多可以装多少个i物品,当然是同一个个数值s了。

由上述两个等式可以获得如下递推式:

\LARGE f(i,j)=f(i1,j)+f(i,jv_i)

把这个等式作为 状态转移方程 ,就可以把时间复杂度优化到 O(n \times m) 同时,观察到该 转移方程 对于第 i 阶段的状态,只会使用第 i-1 层和第 i 层的状态

因此我们也可以采用 01背包 的 空间优化方案

时间复杂度:O(n×m) 空间复杂度:O(m)

二维优化版本

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;
int v[5] = {0, 10, 20, 50, 100};
int f[5][N];

int main() {
    int m;
    cin >> m;
    // 前0种物品体积是0的情况下只有一种方案
    f[0][0] = 1;
    for (int i = 1; i <= 4; i++)
        for (int j = 0; j <= m; j++) {
            f[i][j] = f[i - 1][j];
            if (v[i] <= j) f[i][j] += f[i][j - v[i]];
        }
    printf("%d\n", f[4][m]);
    return 0;
}

一维优化解法

#include <bits/stdc++.h>

using namespace std;
const int N = 1010;
int v[5] = {0, 10, 20, 50, 100};
int f[N];

// 体积限制是恰好是因此需要初始化f[0][0]为合法解1其他位置为非法解0。
int main() {
    int m;
    cin >> m;
    // 前0种物品体积是0的情况下只有一种方案
    f[0] = 1;
    for (int i = 1; i <= 4; i++)
        for (int j = v[i]; j <= m; j++)
            f[j] += f[j - v[i]];
    // 输出
    printf("%d\n", f[m]);
    return 0;
}