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.

3.4 KiB

This file contains ambiguous Unicode 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 451. 摆花

一、题目描述

小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共 m 盆。

通过调查顾客的喜好,小明列出了顾客最喜欢的 n 种花,从 1n 标号。

为了在门口展出更多种花,规定第 i 种花不能超过 a_i 盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。

试编程计算,一共有多少种不同的摆花方案。

输入格式 第一行包含两个正整数 nm,中间用一个空格隔开。 

第二行有 n 个整数,每两个整数之间用一个空格隔开,依次表示 a_1,a_2,…,a_n

输出格式 输出只有一行,一个整数,表示有多少种方案。

注意:因为方案数可能很多,请输出方案数对 1000007 取模的结果。

数据范围 0<n,m≤100,0≤a_i≤100

输入样例

2 4
3 2

输出样例

2

二、题目解析

(DP,多重背包问题,背包问题求方案数) O(n^2a)

问题转化:

  • 将花盆数量看作背包容量;

  • 将花看作物品,体积是1,第 i 种物品最多选 a_i 个;

  • 问题:将背包装满的方案数是多少?

这是典型的多重背包求方案数问题:

状态表示:f[i, j] 表示前i个物品,总体积是j的方案数;

状态计算:$f[i, j] = f[i - 1, j] + f[i - 1, j - 1] + ... + f[i - 1, j - a[i]]$。

三、二维数组解决

#include <bits/stdc++.h>
using namespace std;

const int N = 110, mod = 1000007;

int n, m;
int f[N][N]; // f[i, j] 表示前i个物品总体积是j的方案数

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

    f[0][0] = 1; // 求方案数的base case

    for (int i = 1; i <= n; i++) {
        int a;
        cin >> a; // 最多允许选a个
        for (int j = 0; j <= m; j++) {
            for (int k = 0; k <= min(a, j); k++)
                f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
        }
    }

    cout << f[n][m] << endl;

    return 0;
}

四、一维数组解决

#include <bits/stdc++.h>
using namespace std;

const int N = 110, mod = 1000007;

int n, m;
int f[N];

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

    f[0] = 1; // 求方案数的base case

    for (int i = 1; i <= n; i++) {
        int a;
        cin >> a; // 最多允许选a个
        for (int j = m; j >= 0; j--)
            for (int k = 1; k <= min(a, j); k++)
                f[j] = (f[j] + f[j - k]) % mod;
    }

    cout << f[m] << endl;

    return 0;
}

五、为什么一维与二维的k一个是从1,另一个是从0开始呢?

根据

f[i][j] = f[i - 1][j] + f[i - 1][j - 1] + f[i - 1][j - 2] + ... + f[i - 1][j - a[i]]

可以得知只用上一状态的值 毋须额外储存每一种i的状态 所以可以直接优化掉i

但是去掉后会发现

f[j] = f[j] + f[j - 1] + f[j - 2] + ... + f[j - a[i]]

f[j]已经有上一次状态得到的值了 不能再加一次 所以

f[j] = f[j - 1] + f[j - 2] + ... + f[j - a[i]]

因此k是从1a[i]而不是从0a[i]

当然了,思考一下01背包降维的套路,我们知道这个东西要从后向前枚举才不会依赖错误值。