题目背景
uim神犇拿到了uoi的ra(镭牌)后,立刻拉着基友小A到了一家……餐馆,很低端的那种。
uim指着墙上的价目表(太低级了没有菜单),说:“随便点”。
题目描述
不过uim由于买了一些辅(e)辅(ro)书,口袋里只剩MM元(M \le 10000)(M≤10000)。
餐馆虽低端,但是菜品种类不少,有NN种(N \le 100)(N≤100),第ii种卖a_ia
i
元(a_i \le 1000)(a
i
≤1000)。由于是很低端的餐馆,所以每种菜只有一份。
小A奉行“不把钱吃光不罢休”,所以他点单一定刚好吧uim身上所有钱花完。他想知道有多少种点菜方法。
由于小A肚子太饿,所以最多只能等待11秒。
输入格式
第一行是两个数字,表示NN和MM。
第二行起NN个正数a_ia
i
(可以有相同的数字,每个数字均在10001000以内)。
输出格式
一个正整数,表示点菜方案数,保证答案的范围在intint之内。
输入输出样例
输入 #1复制
4 4
1 1 2 2
输出 #1复制
3
问题分析
这题是一个计数性的背包问题,要求正好花M元,可以选择N种才,有多少种选法,并且限定了每种菜只能点一种。
最后一步子问题分解
很容易定义一个dp[i][j]的含义为可选择前i种,恰好划分j元,一共有dp[i][j]种点菜方法(按照集合算,而非顺序)。那么计算dp[i][j]的方法计数可以分为两个集合,即:
集合1:一定有最后一种菜,此时花j-list[i].v有多少种点菜方法。那么就有dp[i-1][j-list[i].v]
集合2:一定没有最后一种菜,此时花j元有多少种买菜方法,那么就有dp[i-1][j]
是否有重复项
思考两种方案的计数方法之间是否互相重复,一定是不重复的,因为两种方法的计数中,一个是一定没有第i种菜的,另外一个是一定有第i种菜的;因此两者一定是不重复的。而两种计数方法内部也是不重复的,因此这个计算是合理的。
计算顺序与初始条件的选择
状态方程为dp[i][j]=dp[i-1][j-list[i].v]+dp[i-1][j]
此方程可以优化为S(M)的算法,另外此问题的初始条件dp[0][0]=1;
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
struct obj {
int v;
};
int main() {
obj list[101];
int N, M;//N种M元
cin >> N >> M;
for (int i = 1; i <= N; i++) {
cin >> list[i].v;
}
vector<vector<int>> dp(N+1, vector<int>(M+1));
dp[0][0] = 1;
for (int i = 1; i <= N; i++) {
for (int j = 0; j <= M; j++) {
if (j >= list[i].v)
dp[i][j] = dp[i - 1][j - list[i].v] + dp[i - 1][j];
else
dp[i][j] = dp[i - 1][j];
}
}
cout << dp[N][M];
return 0;
}