P1164 小A点菜

题目背景

uim 神犇拿到了 uoi 的 ra(镭牌)后,立刻拉着基友小 A 到了一家……餐馆,很低端的那种。

uim 指着墙上的价目表(太低级了没有菜单),说:“随便点”。

题目描述

不过 uim 由于买了一些书,口袋里只剩 M 元 (M≤10000)。

餐馆虽低端,但是菜品种类不少,有 N 种 (N≤100),第 ii 种卖 ai​ 元 (ai​≤1000)。由于是很低端的餐馆,所以每种菜只有一份。

小 A 奉行“不把钱吃光不罢休”的原则,所以他点单一定刚好把 uim 身上所有钱花完。他想知道有多少种点菜方法。

由于小 A 肚子太饿,所以最多只能等待 1 秒。

输入格式

第一行是两个数字,表示 N 和 M。

第二行起 N 个正数 ai​(可以有相同的数字,每个数字均在 1000 以内)。

输出格式

一个正整数,表示点菜方案数,保证答案的范围在 int 之内。

输入输出样例

输入 #1

4 4
1 1 2 2

输出 #1

3

思路

动态规划:

这道题目的考点是动态规划。具体来说,它是一个典型的0-1背包问题的变种。什么是背包问题呢?背包问题描述了一种情况:你有一个背包,它有一个有限的容量Volume,同时你有一些物品,每个物品都有一个重量Weight和价值Value。目标是在不超过背包容量的前提下,选择一些物品放入背包,使得背包中的总价值最大化。而动态规划是解决背包问题最常用的方法之一。我们通过创建一个二维数组来存储每个物品组合下的最大价值,然后逐步计算直到找到最优解。

这道题就是将我们的口袋钱数作为背包,在背包完全被占满,即钱数完全花完的情况下,要得到最大价值,也就是总点菜方案是多少种。

我们创建一个二维数组cnt[i][j],i代表我们有前i个菜品可以考虑,j代表我们现在有j元的钱。cnt[i][j]代表在面对前i个菜品,我们有j元的情况下,我们最多有几种选择方案。

对于每一次,我们有三种情况
(1)当钱数不够时,我们可以选择的方案数只能等于拥有相同钱数 j 在面对i-1个菜品时的方案数cnt[i-1][j]。
(2)当钱数正好相等时,我们可以选择的方案就多了1,也就是拥有相同钱数 j 在面对i-1个菜品时的方案数cnt[i-1][j]+1。
(3)当钱数大于时,我们有两种选择,买或者不买。买的话,那么我们花的钱数就得加上money[i] , 所以我们要满足刚好把 j 元花完的情况的话,我们考虑的方案数就得是拥有钱数 j - money[i] 在面对i-1个菜品时的方案数cnt[i-1][j-money[i]](注意这与刚好钱数相等的情况不同,不需要加1);不买的话,那么我们的方案数就只能等于拥有相同钱数 j 面对i-1个菜品时的方案数cnt[i-1][j]。总次数就是两种情况相加起来。

cnt[i][j] = cnt[i-1][j] (j<money[i])

cnt[i][j] = cnt[i-1][j] + 1 (j==money[i])

cnt[i][j] = cnt[i-1][j] + cnt[i-1][j-money[i]](j>money[i])

我们再以图片的形式来直观的理解一下。

 循环从i = 1,开始遍历,内层循环从j = 1开始遍历。

i = 1,j = 1,满足j == money[1],cnt[1][1] 加+1,cnt[1][1] = 1。j>1时,因为题目要求我们手里的前得全部花完,所以在只有一种菜品的情况下,我们后面的情况方案数都是0。

i = 2,j = 1,满足j == money[2],cnt[2][1] 加+1,cnt[i][j] = 2。j = 2,j>money[2],cnt[2][2] = cnt[1][2] + cnt[1][1] = 1  + 0 = 1。

i = 3,j = 1,j<money[3] ,cnt[3][1] = cnt[2][1]  =2,j = 2,j = money[3],cnt[3][2] = cnt[2][2] + 1 = 2, j = 3,j>money[3],cnt[3][3] = cnt[2][1] + cnt[2][3] = 2 + 0 = 2, j = 4,j>money[3],cnt[3][4] = cnt[2][2] + cnt[2][4] = 1 + 0 = 1。

i = 4,j = 1,j<money[4] ,cnt[4][1] = cnt[3][1]  =2,j = 2,j = money[4],cnt[4][2] = cnt[3][2] + 1 = 3, j = 3,j>money[4],cnt[4][3] = cnt[3][1] + cnt[3][3] = 2 + 2 = 4, j = 4,j>money[4],cnt[4][4] = cnt[3][2] + cnt[3][4] = 2 + 1 = 3。

最后我们的答案也出来了,就是cnt[4][4] = 3。

dfs:

对于深搜来说,思路也是差不多的,钱数小于就没法买,大于等于的话就可以选择买或者不买,深搜就是将所有情况都搜索一般,那么在大于等于的时候我们买和不买都需要遍历。函数的参数有两个,一个是搜索到第几个菜品,另一个是手里还剩的钱数。在结束条件有两种,一种是刚好钱花完了,即m = 0,此时我们返回1,表示这是一种方案;另一种是已经到了最后一个菜品了,但是钱还没花完,即x = n,此时我们返回0,表示这种选择不符合。

同时我们还要再加上一个“记忆”的二维数组cnt[i][j],当为第i个菜品,还剩j块钱时,方案数为cnt[i][j]。定义一个int类的ans,记录这层递归的总方案数,在返回时,将ans记录到cnt中。所以在两个结束条件之前我们还要再加上一个if语句,判断这个cnt是否已经记录,如果已经记录,那么直接返回记录的数据即可。

递归的话,会比较绕,大家可以在草稿纸上进行模拟,从(0,m)开始,如果买得起就分为两支,一支是要买(1,m - money),一支是不买(1,m),买不起就只有一支:不买(1,m)。然后依次类推,逻辑应该也是很清晰的。

接下来就是代码环节啦!

AC代码

动态规划

#include<iostream>
#include<vector>
using namespace std;
int main()
{
	int n, m;
	cin >> n >> m;
	vector<int> money(n+1);
	for (int i = 1; i <= n; i++) {
		cin >> money[i];
	}
	vector< vector<int> > cnt(n+1, vector<int>(m+1,0));//注意vector二维数组的初始化和创建
 	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (j == money[i]) {
				cnt[i][j] = cnt[i - 1][j] + 1;
			}
			else if (j < money[i]) {
				cnt[i][j] = cnt[i - 1][j];
			}
			else if (j > money[i]) {
				cnt[i][j] = cnt[i - 1][j] + cnt[i - 1][j - money[i]];
			}
		}
	}
	cout << cnt[n][m];
	return 0;
}

dfs

#include<iostream>
using namespace std;
const int N = 110;
int money[N];
int n, m;
int cnt[N][10010];
//x:第几个位置
//m:钱数上限
int dfs(int x, int M) {
    if (cnt[x][M]) return cnt[x][M];
    int ans = 0;
    if (M == 0) return 1;//如果钱花完了,就直接返回1
    if (x == n) return 0;//如果到了第n个,钱还没花完就返回0
    if (money[x + 1] <= M) {
        ans += dfs(x + 1, M - money[x + 1]);//选择当前位置的菜
    }
    ans += dfs(x + 1, M);  //不选择当前位置的菜
    cnt[x][M] = ans;
    return ans;
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        cin >> money[i];
    }
    cout << dfs(0, m) << endl;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值