洛谷题单指南-递推与递归-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;
}

 

posted @ 2024-02-19 10:04  五月江城  阅读(64)  评论(0编辑  收藏  举报