NC16576 [NOIP2012]摆花

题目链接

题目

题目描述

小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m 盆。通过调查顾客的喜好,小明列出了顾客最喜欢的n 种花,从1 到n 标号。为了在门口展出更多种花,规定第i 种花不能超过ai 盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。

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

输入描述

第一行包含两个正整数n和m,中间用一个空格隔开。
第二行有n个整数,每两个整数之间用一个空格隔开,依次表示a1、a2、……an。

输出描述

输出只有一行,一个整数,表示有多少种方案。注意:因为方案数可能很多,请输出方案数对1000007取模的结果。

示例1

输入

2 4
3 2

输出

2

说明

有2种摆花的方案,分别是(1,1,1,2),(1,1,2,2)。括号里的1和2表示两种花,比如第一个方案是前三个位置摆第一种花,第四个位置摆第二种花。

备注

对于20%数据,有0<n≤8,0<m≤8,0≤ai≤8;

对于50%数据,有0<n≤20,0<m≤20,0≤ai≤20;

对于100%数据,有0<n≤100,0<m≤100,0≤ ai≤100。

题解

知识点:背包dp,计数dp。

可以看作一个多重背包,每种花是看作物品,花的盆数是物品数量。于是有转移方程:

\[dp[i][j] = sum[j] - sum[j-a[i]-1],sum[n] = \sum_{j=1}^{n} dp[i-1][j] \]

表示考虑到第 \(i\) 盆花已经摆了 \(j\) 盆时的方案数是考虑到 \(i-1\) 盆摆了 \([j-a[i],j]\) 盆的方案数的总和,可以用前缀和优化,普通的多重背包就不一定可以这样优化qwq。

可以滚动数组优化空间。

题外话

一个经典模型 \(x_1 + x_2 + \cdots + x_m = n,x_i \geq 1 \and x_i \in \Z^+\) ,每个整数位置和值不同就是方案不同,问有多少种方案。

考虑把整数当成多个不加区分的 \(1\) 合在一起的结果,用不加区分的隔板分隔不同的整数。例如:111|11|1111|1|1,就分出了 \([3,2,4,1,1]\)

于是对于这个模型,有 \(m-1\) 个隔板,\(n\)\(1\) ,一共会有 \(n+m-1\) 个位置放 \(1\) 和隔板,先放隔板有 \(C_{n+m-1}^{m-1}\) 种方案,剩下的放 \(1\)\(C_n^n\) 种方案,共计 \(C_{n+m-1}^{m-1} = C_{n+m-1}^{n}\) 种方案。

注意 \(x_i \geq 1\) 是必须的,如果 $x_i \geq a_i $ 则可以通过 \(x_i - a_i+1\) 的变形使得符合条件。

但本题是 \(1 \leq x_i \leq a_i\) ,就不能用这个经典模型了。

其实经典模型是个完全背包,而本题则是多重背包。

时间复杂度 \(O(nm)\)

空间复杂度 \(O(m)\)

代码

#include <bits/stdc++.h>

using namespace std;

const int mod = 1000007;
int a[107], dp[107], sum[107];///第i种花,一共摆了j个

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    dp[0] = 1;
    for (int i = 1;i <= n;i++) cin >> a[i];
    for (int i = 1;i <= n;i++) {
        sum[0] = dp[0];
        for (int j = 1;j <= m;j++) sum[j] = (sum[j - 1] + dp[j]) % mod;
        for (int j = 0;j <= m;j++)
            dp[j] = (sum[j] - (j - a[i] - 1 < 0 ? 0 : sum[j - a[i] - 1]) + mod) % mod;
    }
    cout << dp[m] << '\n';
    return 0;
}
posted @ 2022-08-13 21:06  空白菌  阅读(25)  评论(0编辑  收藏  举报