洛谷题单指南-递推与递归-P1164 小A点菜
原题链接:https://www.luogu.com.cn/problem/P1164
题意解读:要求正好把钱花完,并且统计不同的点菜方案数,本质上是一个背包问题,给定背包体积,要求物品正好装满背包的方案数。
解题思路:
1、最直观的解法是暴搜:
DFS枚举每一道菜,有点或者不点两种选择,并且累加上已花费的总金额
递归前,判断总金额是否正好等于预算金额,是则方案数+1
但是菜的数量多达100种,时间复杂度为2100,约等于1030,超时是肯定的,只能得到部分分
但是也可以做剪枝优化:
先将菜按金额从大到小排序,递归时对某一道菜先选点菜、后选不点,如果已花费的金额超出预算,提前结束递归。
经验证,这样可以得到90分。
2、要得到100分,必须采用递推&DP的方法:
设a[n]存储每道菜的价钱,dp[i][j]表示点前i道菜,总金额正好是j的方案数,根据题意得到递推公式
当a[i] > j时,dp[i][j] = dp[i-1][j] (j金额不够点第i道菜,则方案数与去掉i的方案数一致)
当a[i] <= j时,dp[i][j] = dp[i-1][j] + dp[i-1][j-a[i]] (j金额够点第i道菜,可以选择点或者不点,不点就是dp[i-1][j],点就是dp[i-1][j-a[i]] )
注意a[i] == j时,如果点i就是一种方案,dp[i-1][j-a[i]] = 1,因此要初始化dp[0][0] = 1
具体过程参考下面代码实现。
90分代码-DFS&暴搜:
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int a[N];
long long ans;
int n, m;
//k是第几道菜,amount是已花费的金额
void dfs(int k, int amount)
{
if(k > n) //递归完了所有的菜
{
if(amount == m) ans++; //如果正好把钱花完,方案数+1
return;
}
if(amount > m) return; //花费的金额超出预算
dfs(k + 1, amount + a[k]); //点了第k道菜
dfs(k + 1, amount); //不点第k道菜
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + n + 1, greater<int>()); //由大到小排序,优先选择贵的,便于尽快退出递归
dfs(1, 0);
cout << ans;
return 0;
}
100分代码-递推&DP:
#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = 10005;
int n, m;
int v[N];
int dp[N][M]; //dp[i][j]表示用j元钱点i道菜的方案数
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> v[i];
dp[0][0] = 1;
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= m; j++)
{
if(j < v[i]) dp[i][j] = dp[i-1][j]; //j不够点第i道菜
else dp[i][j] = dp[i-1][j] + dp[i-1][j-v[i]]; //j够点第i道菜,可以不点,也可以点
}
}
cout << dp[n][m];
return 0;
}