洛谷 P1164 小A点菜 C语言实现 背包问题

原题地址:P1164 小A点菜 - 洛谷
题目描述
em…这道题写的是真的让我难受,本来最近在学习dp,于是打算找点dp的题练一下…

于是乎这个题代码我就用dp(自认为的dp)写出来了,然后也很顺利的通过了样例。

于是喜得AC 代码

#include <stdio.h>

int sum = 0;
void dp(int* a, int m, int n);

int main(){
    int m, n;
    scanf("%d %d", &m, &n);
    int a[500];
    int i;
    for(i = 0; i < n; i++)
        scanf("%d", &a[i]);
    dp(a, m, n - 1);
    printf("%d", sum);
    return 0;
}

void dp(int *a, int m, int n){
    if(m == 0){
        sum++;
        return;
    }
    else if(n < 0)
        return;
    else if(a[n] > m){
        dp(a, m, n - 1);
        return;
    }
    else{
        dp(a, m, n - 1);
        dp(a, m - a[n], n - 1);
        return;
    }
    return;
}

然后洛谷oj很现实的给了我一巴掌,还是狠狠的一巴掌…

洛谷给我的一巴掌
于是博主我选择了最偷懒的方法 最棒的方法,选择去看题解

或许是老天爷嫌弃我太懒了,只找到c++,java,python语言写的题解。。。难受,但也只能将就去看了。。。

看完别人代码后,发现自己对背包问题理解太浅了。。。于是乎又去找了讲解背包问题的博客,很幸运找到了一个讲的很全面的博客,嘿嘿嘿!这里附一下连接,有兴趣的朋友可以去看看:背包问题九讲笔记_01背包

好了,看完这个博客后,就开始对本题这个背包问题进行分析。

首先这个问题是在刚好花完钱的情况下,有几种点餐方案。那么我们建立一个f[i][j] 的二维数组。i,j表示在刚好花完j元,有i道菜,有多少种点餐的情况。我们令f数组初始化为0,代表最初都是0钟方案。

然后有以下三种情况:

  • if(j==第i道菜的价格)f[i][j]=f[i-1][j]+1;
    这种情况是,新添加的一道菜的价格如果刚好和我包里的总钱一样,那么就可以在之前的方案上添加一个方案(所有钱都来买这一道菜), 所以f[i][j] = f[i-1][j] + 1;

  • if(j>第i道菜的价格) f[i][j]=f[i-1][j]+f[i-1][j-第i道菜的价格];
    这种情况是,我买了新添加的一道菜后,钱还有剩余,然后剩余的钱可以点餐的方案和f[i-1][j-第i道菜的价格]的点餐方案一样多,再加上我不买这道菜的点餐方案为f[i][j],那么加上这道菜后的点餐方案为我买这道菜和不买这道菜的方案加起来的总和。

  • if(j<第i道菜的价格) f[i][j]=f[i-1][j];
    这种情况是,新添加的一道菜的价格比我我包里的总钱还多,就算我用所有钱来买这道菜也买不起,那么这个时候我只能对这道菜进行不选,于是方案和少了这份菜的点餐方案一样多

这个时候我们就只需要对f数组从头到尾依次循环一次就行了,这样就能得到不同的菜数和总钱的点餐方案了,循环完了之后我们只需要再讲f数组最后的一个数输出就可以了。
于是乎,顺利ac。

AC代码

#include <stdio.h>

int a[101], f[101][10001] = {0};

int main()
{
    int n, m, i, j;
    scanf("%d%d", &n, &m);
    for(i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for(i = 1; i <= n; i++)
    	for(j = 1; j <= m; j++)
      	{
          	if(j == a[i])
          		f[i][j] = f[i - 1][j] + 1;
          	else if(j > a[i])
          		f[i][j] = f[i - 1][j] + f[i - 1][j - a[i]];
          	else if(j<a[i])
          		f[i][j] = f[i - 1][j];
      	}
    printf("%d", f[n][m]);
    return 0;
}

但是,我们的故事还没有结束!

由于看了前文提到的那篇博客,我知道这种背包问题可以从二维转化为一维,然后就继续做出了新的尝试。

对于一维而言,其实就算不断的覆盖之前存储的数据。

但由于i-1时的数据时i时计算的根本,所以要解决这个问题的方法就算i 正序循环,j 逆序循环。因为f[i][j] = f[i - 1][j] + f[i - 1][j - a[i]];只会调用上一层小于或等于本层价格的数,如果我们倒序来就不会把我们将会使用的数字给覆盖了。
但这个时候由于计算方式改变,要初始化f[0] = 1(代表0元0个菜,有一种点餐方式),其他为0。
于是乎再次得出AC代码

AC代码

#include <stdio.h>
#define N 110
int n, m, a[N],f[10010];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
      scanf("%d",&a[i]);
    f[0]=1;
    for(int i=1;i<=n;i++)
      for(int j=m;j>=a[i];j--)
          f[j]=f[j]+f[j-a[i]];
    printf("%d", f[m]);
    return 0;
}
  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这道题目是一道经典的背包问题,要求从给定的 n 种菜品中选出若干个菜品,使得它们的价格之和恰好为 m。 我们可以使用动态规划的方法来解决这个问题。具体来说,我们可以定义一个二维数组 f[i][j],表示从前 i 种菜品中选,总价值恰好为 j 的方案数。初始状态为 f[0][0] = 1,表示从 0 种菜品中选出总价值为 0 的方案数为 1(即不选任何菜品)。 然后,我们可以使用状态转移方程 f[i][j] = f[i-1][j] + f[i-1][j-a[i]],表示要么不选第 i 种菜品,此时方案数为 f[i-1][j];要么选第 i 种菜品,此时方案数为 f[i-1][j-a[i]],因为选了这个菜品后,剩余的价值就是 j-a[i]。 最后,我们输出 f[n][m],即从 n 种菜品中选出总价值恰好为 m 的方案数。 下面是 AC 代码和一些细节处理的实现建议: ```c++ #include <iostream> #include <cstring> using namespace std; const int MAXN = 105; const int MAXM = 10005; int f[MAXN][MAXM]; // f[i][j] 表示从前 i 种菜品中选,总价值恰好为 j 的方案数 int a[MAXN]; // a[i] 表示第 i 种菜品的价格 int main() { int n, m; cin >> n >> m; for (int i = 1; i <= n; i++) { cin >> a[i]; } memset(f, 0, sizeof(f)); // 初始化为 0 f[0][0] = 1; // 初始状态 for (int i = 1; i <= n; i++) { for (int j = a[i]; j <= m; j++) { // 注意这里要从 a[i] 开始枚举 f[i][j] = f[i-1][j] + f[i-1][j-a[i]]; // 状态转移方程 } } cout << f[n][m] << endl; // 输出最终答案 return 0; } ``` 需要注意的细节有: 1. 状态转移方程中,第二个下标 j 要从 a[i] 开始枚举,因为如果 j < a[i],则选第 i 种菜品的话,总价值就会小于 a[i],不符合题意。 2. 初始状态要赋值为 1,因为从 0 种菜品中选出总价值为 0 的方案数只有一种(即不选任何菜品)。 3. 可以使用 memset 函数将 f 数组初始化为 0。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值