小A点菜
题目背景
uim
神犇拿到了uoi
的ra
(镭牌)后,立刻拉着基友小A
到了一家......餐馆,很低端的那种。
uim
指着墙上的价目表(太低级了没有菜单),说:"随便点"。
题目描述
不过uim
由于买了一些辅(e)辅(ro)书
,口袋里只剩\(M\)元\((M \le 10000)\)。
餐馆虽低端,但是菜品种类不少,有\(N\)种\((N \le 100)\),第\(i\)种卖\(a_i\)元\((a_i \le 1000)\)。由于是很低端的餐馆,所以每种菜只有一份。
小A
奉行"不把钱吃光不罢休",所以他点单一定刚好吧uim
身上所有钱花完。他想知道有多少种点菜方法。
由于小A
肚子太饿,所以最多只能等待\(1\)秒。
输入输出格式
输入格式
第一行是两个数字,表示\(N\)和\(M\)。
第二行起\(N\)个正数\(a_i\)(可以有相同的数字,每个数字均在\(1000\)以内)。
输出格式
一个正整数,表示点菜方案数,保证答案的范围在\(int\)之内。
输入输出样例
输入样例 #1
4 4
1 1 2 2
输出样例 #1
3
分析
裸01
背包。(好吧没有采药裸,这题实际上有点变化)
首先我们设\(dp_{i,j}\)为点前\(i\)个菜花费\(j\)元钱的方案数,那么状态转移方程有\(3\)种情况:\(j > a_i\),\(j = a_i\),\(j < a_i\)。分别考虑:
如果\(j < a_i\),说明当前的钱买不了这个菜了。那还能怎么办?方案数和不买这个菜的数一样,也就是\(dp_{i,j} = dp_{i - 1, j}\)。
如果\(j = a_i\),说明当前的钱恰好可以买这个菜,也就是多了一种买菜方案。\(dp_{i,j} = dp_{i - 1,j} +1\)。
如果\(j > a_i\),那么我们可以吃,可以不吃。吃的方案数是\(dp_{i - 1,j - a_i}\),不吃的还是\(dp_{i-1,j}\)。答案就是两者之和:\(dp_{i - 1,j - a_i} + dp_{i-1,j}\)。
细心的同学可能会发现,第二个条件和第三个条件可以合并考虑。\(j = a_i\)的时候,如果把他用\(j > a_i\)的方法计算,结果是\(dp_{i - 1,0} + dp_{i-1,j}\),和原来的\(dp_{i,j} = dp_{i - 1,j} +1\)对比一下,我们只要初始化所有的\(dp_{i,0}\)都为1,第二条和第三条就能合并考虑。
二维版本的递推方程:
\(dp_{i,j}=\begin{cases}dp_{i - 1, j}&j < a_i\\dp_{i - 1,j - a_i} + dp_{i-1,j}&j \ge a_i\end{cases}\)
接下来考虑降维。采用滚动数组的思想,\(dp_{i-1}\)降维成\(pre\),\(dp_{i}\)降维成\(now\)。
\(now_j=\begin{cases}pre_j&j < a_i\\pre_{j - a_i} + pre_j&j \ge a_i\end{cases}\)
但是实际过程中,我们优化成了一个一维数组。咋肥四呢?首先我们先把\(now\)和\(pre\)都无脑换成\(dp\)。
\(dp_j = \begin{cases}dp_j&j < a_i\\dp_{j - a_i} + dp_j&j \ge a_i\end{cases}\)
也就是说,当\(j \ge a_i\)的时候,\(dp_j = dp_j + dp_{j-a_i}\)。
但是这个递推方程仍然可以画上等号吗?
首先我们令\(now_j \rightarrow dp_j\),首先当\(now_j\)没更新的时候,\(now_j = pre_j\),但是我们还得让\(pre_{j - a_i} = dp_{j-a_i}\),也就是说,我们需要让\(dp_{j-a_i}\)保持在\(i - 1\)层状态,大白话,我们不能让\(dp_{j-a_i}\)更新。但是\(j-a_i \le j\),如果我们内部循环是正这遍历的,求\(dp_j\)的时候,\(dp_{j - a_i}\)必然会更新,这会导致结果的错误。所以我们必须让内部循环倒着遍历,这样\(dp_{j - a_i}\)还是旧的\(i - 1\)层,结果就正确啦。
刚刚证明完了可以画等号,只是需要倒序遍历,也就是说\(j \ge a_i\)的时候,\(dp_j = dp_j + dp_{j-a_i}\)这个结论是正确的。这下整个问题就会变得异常简单,我们只需要让\(j\)从\(m\)倒序遍历到\(a_i\),然后dp[j] += dp[j - a[i]]
。
上代码。
代码
#include <iostream>
#include <cstdio>
const int maxn = 105;
const int maxm = 10005;
int n, m, a[maxn], dp[maxm] = {1};//别忘了,dp[0]必须是1哦!
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for(int i = 1; i <= n; i++)
for(int j = m; j >= a[i]; j--)
dp[j] += dp[j - a[i]];
printf("%d\n", dp[m]);
return 0;
}
评测结果
AC 100
:R31579801