题解:学而思编程 智能饭盒

【题目来源】

学而思编程:智能饭盒

【题目描述】

\(n\) 个食物和一个饭盒。第 \(i\) 个食物含有 \(a_i\) 克脂肪和 \(b_i\) 克蛋白质。

小猴要选择一些食物装进饭盒里,这是个智能饭盒,可以测定食物的脂肪和蛋白质含量,小猴想要选出的食物合计至少含有 \(x\) 克脂肪和 \(y\) 克蛋白质。他最少需要选择多少个食物装进饭盒?

【输入】

\(1\) 行,\(1\) 个正整数 \(n\)

\(2\) 行,\(2\) 个正整数 \(x,y\)

接下来n行,每行两个正整数\(a_i,b_i\)

【输出】

\(1\) 个整数,需要选择的最少食物数;如果无法得到要求的脂肪和蛋白质含量,输出 \(−1\)

【输入样例】

3
5 6
2 1
3 4
2 3

【输出样例】

2

【核心思想】

  1. 问题分析:给定 \(n\) 个食物,每个食物提供 \(a_i\) 克脂肪和 \(b_i\) 克蛋白质,要求选出最少数量的食物使得总脂肪至少 \(x\) 克且总蛋白质至少 \(y\) 克。这是一个二维 0-1 背包问题,关键在于将"双维度资源约束下的最少物品数"转化为 DP 状态设计。

  2. 算法选择

    • 二维 0-1 背包:将脂肪 \(j\) 和蛋白质 \(k\) 作为两个资源维度,物品数量作为待最小化的目标值
    • 反向遍历优化:两个维度均反向遍历,确保每个食物只被选取一次
    • 状态压缩:利用 max(j - a_i, 0)max(k - b_i, 0) 将超过目标值的状态压缩到边界,降低状态空间
  3. 关键步骤

    • 初始化
      • 读取 \(n\)(食物数量)、\(x\)(目标脂肪)、\(y\)(目标蛋白质)
      • 读取每个食物的 \(a_i\)\(b_i\)
      • f[j][k] 表示达到至少 \(j\) 克脂肪和至少 \(k\) 克蛋白质所需的最少食物数
      • memset(f, 0x3f, sizeof f):初始化为正无穷(不可达)
      • f[0][0] = 0:不选任何食物时,脂肪和蛋白质均为 \(0\),需要 \(0\) 个食物
    • DP 转移(遍历 \(i\)\(1\)\(n\)):
      • 遍历 \(j\)\(x\)\(0\)(脂肪维度反向):
        • 遍历 \(k\)\(y\)\(0\)(蛋白质维度反向):
          • f[j][k] = \min(f[j][k], f[\max(j - a_i, 0)][\max(k - b_i, 0)] + 1)
          • 不选第 \(i\) 个食物:保持 f[j][k] 不变
          • 选第 \(i\) 个食物:从"扣除该食物贡献后的状态"转移,食物数加 \(1\)
          • \max(j - a_i, 0)\max(k - b_i, 0):将超过目标的状态压缩到 \(0\),避免数组越界并统一处理"超额满足"的情况
    • 判断输出
      • f[x][y] \geq 0x3f3f3f3f:状态不可达,输出 \(-1\)
      • 否则输出 f[x][y]
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times x \times y)\)\(n\) 个物品,每个物品更新 \(x \times y\) 个状态
    • 空间复杂度:\(O(x \times y)\),二维 DP 数组 \(f[0..x][0..y]\)
  5. 二维 0-1 背包的核心思想

    • 双维度状态设计f[j][k] 同时追踪两个资源维度(脂肪和蛋白质),将"满足双约束的最少物品数"作为优化目标
    • 反向遍历保证 0-1 性:两个维度均从大到小遍历,确保第 \(i\) 个食物的状态转移只使用第 \(i-1\) 个食物的状态,每个食物最多选一次
    • 状态压缩技巧:用 \max(j - a_i, 0) 将超过目标值的状态映射到 \(0\),既避免数组越界,又将"超额满足"统一为"恰好满足",简化状态空间
    • 最少物品数的转化:传统背包求最大价值,本题将"物品数量"作为 DP 值,目标资源作为状态维度,实现"逆向"背包
    • 适用于"双资源约束下最少选取物品数"的组合优化问题,状态压缩可有效降低空间复杂度

【解题思路】

【算法标签】

01背包

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, x, y;
int f[305][305], a[305], b[305];

int main()
{
    cin >> n >> x >> y;  // 输入物品数量和目标值
    for (int i = 1; i <= n; i++) {
        cin >> a[i] >> b[i];  // 输入每个物品的属性
    }

    // 初始化DP数组为无穷大
    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;  // 不需要任何物品就可以达到(0,0)

    // 动态规划
    for (int i = 1; i <= n; i++) {  // 考虑前i个物品
        for (int j = x; j >= 0; j--) {  // 反向遍历,实现0-1背包
            for (int k = y; k >= 0; k--) {
                // 状态转移:选或不选第i个物品
                f[j][k] = min(f[j][k], f[max(j - a[i], 0)][max(k - b[i], 0)] + 1);
            }
        }
    }

    // 输出结果
    if (f[x][y] >= 0x3f3f3f3f)  // 如果不可达
        cout << -1;
    else
        cout << f[x][y];

    return 0;
}

【运行结果】

3
5 6
2 1
3 4
2 3
2
posted @ 2026-07-04 12:14  团爸讲算法  阅读(4)  评论(0)    收藏  举报