题解:洛谷 P2639 Bessie's Weight Problem

【题目来源】

洛谷:P2639 [USACO09OCT] Bessie's Weight Problem G - 洛谷

【题目描述】

Bessie 像她的诸多姊妹一样,因为从 Farmer John 的草地吃了太多美味的草而长出了太多的赘肉。所以 FJ 将她置于一个及其严格的节食计划之中。她每天不能吃多过 \(H (5 \le H \le 45,000)\) 公斤的干草。Bessie 只能吃一整捆干草;当她开始吃一捆干草的之后就再也停不下来了。她有一个完整的\(N (1 \le N \le 500)\) 捆可以给她当作晚餐的干草的清单。她自然想要尽量吃到更多的干草。很自然地,每捆干草只能被吃一次(即使在列表中相同的重量可能出现 \(2\) 次,但是这表示的是两捆干草,其中每捆干草最多只能被吃掉一次)。给定一个列表表示每捆干草的重量 \(S_i (1 \le S_i \le H)\) ,求 Bessie 不超过节食的限制的前提下可以吃掉多少干草(注意一旦她开始吃一捆干草就会把那一捆干草全部吃完)。

【输入】

第一行有两个由空格隔开的整数 \(H\)\(N\)

\(2\) 到第 \(N+1\) 行,第 \(i+1\) 行是一个单独的整数,表示第 \(i\) 捆干草的重量 \(S_i\)

【输出】

第一行一个单独的整数表示 Bessie 在限制范围内最多可以吃多少公斤的干草。

【输入样例】

56 4
15
19
20
21

【输出样例】

56

【核心思想】

  1. 问题分析:给定 \(N\) 捆干草,每捆重量 \(S_i\),Bessie 每天最多吃 \(H\) 公斤。每捆只能完整吃或不吃,求不超过 \(H\) 的前提下能吃的最大重量。这是一个经典的01背包问题,每捆干草是"物品",重量既是体积也是价值,背包容量为 \(H\)

  2. 算法选择

    • 01背包(二维)\(dp[i][j]\) 表示前 \(i\) 捆干草、总重量不超过 \(j\) 时的最大可吃重量
    • 01背包(一维滚动数组优化)\(dp[j]\) 表示背包容量为 \(j\) 时的最大价值,逆序遍历容量避免重复选取
  3. 关键步骤

    • 读取输入\(H\)(容量)和 \(N\)(物品数),以及 \(N\) 个重量 \(S_i\)
    • 初始化\(dp\) 数组全 \(0\)
    • 01背包递推\(i\)\(1\)\(N\)):
      • 二维版:对于 \(j\)\(1\)\(H\)
        • \(j < S_i\)\(dp[i][j] = dp[i-1][j]\)(装不下)
        • 否则:\(dp[i][j] = \max(dp[i-1][j],\ dp[i-1][j-S_i] + S_i)\)(不选或选)
      • 一维滚动优化:对于 \(j\)\(H\)\(S_i\) 逆序:
        • \(dp[j] = \max(dp[j],\ dp[j-S_i] + S_i)\)
    • 输出 \(dp[H]\)(或 \(dp[N][H]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \cdot H)\)\(N \le 500\)\(H \le 45000\)
    • 空间复杂度:\(O(H)\),一维滚动数组优化后
  5. 01背包的核心思想

    • 选或不选的决策:对于每捆干草,只有两种状态——吃(占用 \(S_i\) 容量,获得 \(S_i\) 价值)或不吃(不占用容量,价值不变)
    • 最优子结构\(dp[j]\) 仅依赖 \(dp[j]\)\(dp[j-S_i]\),满足无后效性
    • 滚动数组优化:逆序遍历容量保证每个物品只被选取一次,将空间从 \(O(N \cdot H)\) 降为 \(O(H)\)
    • 价值=重量:本题中每捆干草的价值等于其重量,是01背包的特例,但递推公式不变
    • 适用于"有限物品 + 容量约束 + 最大化价值"类经典背包问题

【解题思路】

【算法标签】

普及- #01背包

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int h, n, wi[505],ci[505];
int dp[205][45005] = {0};
int main()
{
    cin >> h >> n;  // 输入h和n,类似01背包中的N和M(h=M,n=N)
    for (int i=1; i<=n; i++) {  // 记录n件物品的Wi和Ci
        cin >> wi[i];
        ci[i] = wi[i];  // 题目描述中没有ci,但最后求最大重量,可以理解为最大价值,就让ci=wi
    }
    for (int i=1; i<=n; i++) {  // 遍历n个物品
        for (int j=1; j<=h; j++) {  // 遍历背包容量,即吃干草重量
            if (j<wi[i]) {  // 如果背包装不下
                dp[i][j] = dp[i-1][j];  // 则此i件物品不装的价值等于i-1时背包容量为j的价值
            } else {  // 如果装的下
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-wi[i]] + ci[i]);  // 计算i-1时背包容量为j为价值,与装入该物品的价值的最大值,dp[i-1][j-wi[i]]表示没有装入当前物品的价值,+ci[i]表示增加当前物品后的价值
            }
        }
    }
    cout << dp[n][h] << endl;  // 输出结果
    return 0;
}
// 01背包完全体
#include <bits/stdc++.h>
using namespace std;
int h, n, wi[505],ci[505];
// int dp[205][45005] = {0};
int dp[45005] = {0};
int main()
{
    cin >> h >> n;  // 输入h和n,类似01背包中的N和M(h=M,n=N)
    for (int i=1; i<=n; i++) {  // 记录n件物品的Wi和Ci
        cin >> wi[i];
        ci[i] = wi[i];  // 题目描述中没有ci,但最后求最大重量,可以理解为最大价值,就让ci=wi
    }
    for (int i=1; i<=n; i++) {  // 遍历n个物品
        for (int j=h; j>=1; j--) {  // 遍历背包容量,即吃干草重量(此处是滚动数组模板)
            if (j>=wi[i]) {  // 如果装的下
                dp[j] = max(dp[j], dp[j-wi[i]] + ci[i]);  // dp[j-wi[i]]表示没有装入当前物品的价值,+ci[i]表示增加当前物品后的价值
            }
        }
    }
    cout << dp[h] << endl;  // 输出背包容量为h时是的结果
    return 0;
}

【运行结果】

56 4
15
19
20
21
56
// dp[n][h]打印结果:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 15 15 15 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34
0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 15 15 15 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 34 35 35 35 35 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 54 54 54
0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 15 15 15 19 20 21 21 21 21 21 21 21 21 21 21 21 21 21 34 35 36 36 36 39 40 41 41 41 41 41 41 41 41 41 41 41 41 41 54 55 56
posted @ 2026-08-28 15:54  团爸讲算法  阅读(2)  评论(0)    收藏  举报