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;
}