【题解】【动态规划01背包问题】—— [NOIP2012 普及组] 摆花
- [NOIP2012 普及组] 摆花
- 题目描述
- 输入格式
- 输出格式
- 输入输出样例
- 输入 #1
- 输出 #1
- 提示
- 解法1.二维 d p dp dp
- 1.1.思路解析
- 1.2.AC代码
- 解法2.一维 d p dp dp
- 2.1.思路解析
- 2.2.AC代码
- 3.扩展:前缀和优化
[NOIP2012 普及组] 摆花
通往洛谷的传送门
题目描述
小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共 m m m 盆。通过调查顾客的喜好,小明列出了顾客最喜欢的 n n n 种花,从 1 1 1 到 n n n 标号。为了在门口展出更多种花,规定第 i i i 种花不能超过 a i a_i ai 盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。
试编程计算,一共有多少种不同的摆花方案。
输入格式
第一行包含两个正整数 n n n 和 m m m,中间用一个空格隔开。
第二行有 n n n 个整数,每两个整数之间用一个空格隔开,依次表示 a 1 , a 2 , ⋯ , a n a_1,a_2, \cdots ,a_n a1,a2,⋯,an。
输出格式
一个整数,表示有多少种方案。注意:因为方案数可能很多,请输出方案数对 1 0 6 + 7 10^6+7 106+7 取模的结果。
输入输出样例
输入 #1
2 4
3 2
输出 #1
2
提示
【数据范围】
对于 20 % 20\% 20% 数据,有 0 < n ≤ 8 , 0 < m ≤ 8 , 0 ≤ a i ≤ 8 0<n \le 8,0<m \le 8,0 \le a_i \le 8 0<n≤8,0<m≤8,0≤ai≤8。
对于 50 % 50\% 50% 数据,有 0 < n ≤ 20 , 0 < m ≤ 20 , 0 ≤ a i ≤ 20 0<n \le 20,0<m \le 20,0 \le a_i \le 20 0<n≤20,0<m≤20,0≤ai≤20。
对于 100 % 100\% 100% 数据,有 0 < n ≤ 100 , 0 < m ≤ 100 , 0 ≤ a i ≤ 100 0<n \le 100,0<m \le 100,0 \le a_i \le 100 0<n≤100,0<m≤100,0≤ai≤100。
NOIP 2012 普及组 第三题
解法1.二维 d p dp dp
1.1.思路解析
这道题可以用01背包问题的模型来做。
首先,还是动态规划五步走:
1.抽象问题:在 n n n个数中分别取一个数 p ≤ a i p\leq a_i p≤ai,使得每一个 p p p的和为 m m m。
2.状态:dp[i][j]
代表当前只放前 i i i种花刚好放置 j j j盆的方法数。
3.初始条件:dp[0][0]=1
。
4.状态转移方程:
d p [ i ] [ j ] = ∑ k = 1 m i n ( a [ i ] , j ) d p [ i − 1 ] [ j − k ] dp[i][j]=\sum_{k=1}^{min(a[i],j)}dp[i-1][j-k] dp[i][j]=k=1∑min(a[i],j)dp[i−1][j−k]
5.答案:dp[n][m]
。
请听我慢慢解析。
这里的状态的定义和初始条件等都跟01背包问题的模型相似。不会的请看到这里来。
这里主要讲一下动态转移方程的推导。
这道题跟 01 01 01背包问题的模型不同的是, 01 01 01背包问题只有选或者不选某样物品两种状态。可在这里,我们可以选第 i i i件物品 0 0 0至 a i a_i ai中的所有数量。
也就是说,我们其实只需要把不取这个物品,这个物品取一件,取两件……取 a i a_i ai件的方案数用一个循环累加起来就行了。注意,这里不取这个物品的方案数也要加上。
还要注意一件事,如果
a
i
>
j
(
当前放置的花的总数量
)
a_i>j(当前放置的花的总数量)
ai>j(当前放置的花的总数量),可不能取超过
m
m
m盆。即这种花我们最多取
m
m
m盆,所以最后一重循环的判断条件是k<=min(a[i],j)
。
如果看不懂的话那就请看代码。
最后提醒一句,记得取模 1 0 6 + 7 10^6+7 106+7。
1.2.AC代码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 110
const int mod=1000007;//模数
int n,m,a[MAXN],dp[MAXN][MAXN];//01背包
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
dp[0][0]=1;//初始条件,选前0种花摆0盆只有一种方法数,就是不摆
for(int i=1;i<=n;i++)//枚举物品种类
for(int j=0;j<=m;j++)//枚举盆数
for(int k=0;k<=min(a[i],j);k++)//从这个种类里拿出1,2,...参与摆花
dp[i][j]=(dp[i][j]+dp[i-1][j-k])%mod;//状态转移方程
cout<<dp[n][m];
return 0;
}
此算法的时间复杂度是 O ( n m 2 ) O(nm^2) O(nm2),超时警告!
解法2.一维 d p dp dp
2.1.思路解析
如果不懂滚动数组优化的同学们可以先走了。
这道题的优化基本没变,就是要注意,由于滚动数组会保存上次更新后的值,所以将第三重循环的 k = 0 k=0 k=0改成 k = 1 k=1 k=1。
2.2.AC代码
#include<bits/stdc++.h>
using namespace std;
#define MAXN 110//模数
const int mod=1000007;
int n,m,a[MAXN],dp[MAXN];//01背包
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i];
dp[0]=1;//初始条件,摆0盆只有一种方法数,就是不摆
for(int i=1;i<=n;i++)//枚举物品种类
for(int j=m;j>=0;j--)//枚举盆数
for(int k=1;k<=min(a[i],j);k++)//从这个种类里拿出1,2,...参与摆花
dp[j]=(dp[j]+dp[j-k])%mod;//状态转移方程
cout<<dp[m];
return 0;
}
3.扩展:前缀和优化
在上面的代码中,由于第三重循环求的就是
d
p
[
1
]
d
p
[
j
−
1
]
dp[1]~dp[j-1]
dp[1] dp[j−1]这一区间的和,可以考虑用前缀和优化。因为我懒 篇幅关系,这里就不详细解说了。
喜欢就订阅此专辑吧!
【蓝胖子编程教育简介】
蓝胖子编程教育,是一家面向青少年的编程教育平台。平台为全国青少年提供最专业的编程教育服务,包括提供最新最详细的编程相关资讯、最专业的竞赛指导、最合理的课程规划等。本平台利用趣味性和互动性强的教学方式,旨在激发孩子们对编程的兴趣,培养他们的逻辑思维能力和创造力,让孩子们在轻松愉快的氛围中掌握编程知识,为未来科技人才的培养奠定坚实基础。
欢迎扫码关注蓝胖子编程教育