洛谷 P1164 小A点菜

P1164 小A点菜

题目背景


uim神犇拿到了uoi的ra(镭牌)后,立刻拉着基友小A到了一家……餐馆,很低端的那种。
uim指着墙上的价目表(太低级了没有菜单),说:“随便点”。

题目描述


不过uim由于买了一些辅(e)辅(ro)书,口袋里只剩MMM元(M≤10000)(M \le 10000)(M≤10000)。
餐馆虽低端,但是菜品种类不少,有NNN种(N≤100)(N \le 100)(N≤100),第iii种卖aia_iai​元(ai≤1000)(a_i \le 1000)(ai​≤1000)。由于是很低端的餐馆,所以每种菜只有一份。
小A奉行“不把钱吃光不罢休”,所以他点单一定刚好吧uim身上所有钱花完。他想知道有多少种点菜方法。
由于小A肚子太饿,所以最多只能等待1秒。

输入格式:

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

输出格式:

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


输出格式:

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

输入输出样例
输入 #1

4 4
1 1 2 2

输出 #1

3

1.为什么可以转为一维
首先观察二维状态转移方程 dp[i][j]只由dp[i-1]层推导而来,所以我们不必要保存i - 2层,
当我们去掉i时,只需要长度为j的数组,保存确认过当前最新的一层。作为下一层的参考

2.为什么要逆序
首先,通过上一个问题,我们确认了我们目前一维的dp数组,保存的是确认过的最新一层的数据,即上一层的数据。
当我们计算当前层时,对于二维时的状态转移方程有 dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
可以看到,dp[i - 1][j - v[i]] + w[i] 使用的上一层的原始数据(dp[i - 1]),而我们使用一维的状态转移方程时有

dp[j] = max(dp[j], dp[j - v[i]] + w[i]);

如果是从小到大更新,我们算dp[j]的时候已经算过dp[j - v[i]]了,这样就可能读入脏数据,所以从大到小更新,要求
dp[3]就要先求dp[2],往前推就不会出错

一维优化代码:

/*m元 ,n个菜 ,每个菜只能点一次,刚好把钱用完的方法数*/
//一维优化
#include <iostream>
using namespace std;

const int N = 105, M =10005;

int a[N];
int dp[M]; //价值为m的总共有多少种方法 

int n, m ;

int main()
{
	cin >> n  >> m ;
	
	for(int i =0 ; i  < n ; i ++) 
		cin >> a[i];
		
	dp[0] = 1;  //刚好可以买那个也是一种办法 
	
	for(int i = 0 ; i < n ; i ++ )
		for(int j =  m ; j >= a[i]; j --)
	 		dp[j] += dp[j - a[i]];

	cout<< dp[m];

	return 0 ;
}

二维朴素代码:

#include <iostream>
using namespace std;

const int N = 105, M =10005;

int a[N];
int dp[N][M]; //价值为m的总共有多少种方法 

int n, m ;

int main()
{
	cin >> n  >> m ;
	
	for(int i =0 ; i  < n ; i ++) 
	{
		cin >> a[i];
		dp[i][0] = 1;
	}

	for(int i = 1 ; i <= n ; i ++ )
		for(int j = 1 ; j <= m; j ++)
	 	{
		 	dp[i][j] = dp[i - 1][j];
		 	if(j >= a[i]) 
		 		dp[i][j] += dp[i - 1][j -a[i]];
	}

	cout << dp[n][m];

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值