二维费用01背包

P1855 榨取kkksc03 题解复盘

模块:动态规划

目标:在金钱和时间都有限的情况下,最多完成多少个愿望

基本信息

项目 内容
题目编号、来源 P1855 洛谷 / 榨取kkksc03
训练层级 B 变形题
知识版块 二维费用背包、01背包、动态规划

解题前 · 关键信号识别

维度 分析
目标、约束、底层结构 目标 :在总金钱不超过 M、总时间不超过 T 的情况下,尽可能完成更多愿望。 约束 :每个愿望只能完成一次。 底层结构:每个物品有两个费用限制(金钱、时间),属于二维费用 01 背包。
数据规模 n≤100M,T≤200,使用 O(nMT) 可以通过。
候选算法和依据 算法:二维费用 01 背包。 依据:每个愿望只能选择一次,同时消耗两种资源,因此需要用二维状态记录金钱和时间。
复杂度预判 时间复杂度 O(n×M×T),空间复杂度 O(M×T)

解题后 · 外化复盘

维度 内容
实现结构 / 核心思路 定义 dp[j][k] 表示使用不超过 j 元钱和 k 分钟时间时,最多可以完成的愿望数。枚举每个愿望,再分别倒序枚举金钱和时间,使用 dp[j][k]=max(dp[j][k],dp[j-m][k-t]+1) 更新答案。
错因回溯 1. 容易把二维费用背包误写成一维背包,只记录一种资源。 2. 容易把金钱或时间正序枚举,导致同一个愿望在同一轮被重复选择。 3. 输入愿望数量时应枚举 n,不能误写成 MT
边界和易错点 1. 每个愿望只能完成一次,因此两个容量都必须倒序枚举。 2. dp 初始值默认为 0 即可,因为什么都不选时完成愿望数为 0。 3. 最终答案是 dp[M][T]
下次看到什么信号,我应该想到这个方法 看到"每件物品只能选一次 + 有两个容量限制 + 求最大价值/最多数量",想到:二维费用 01 背包

AC 完整代码

cpp 复制代码
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
#include<iomanip>
using namespace std;

struct node{
    int m,t;
};

int dp[205][205];

int main(){
    int n,M,T;
    cin>>n>>M>>T;

    node v[105];

    for(int i=1;i<=n;i++){
        cin>>v[i].m>>v[i].t;
    }

    for(int i=1;i<=n;i++){
        for(int j=M;j>=v[i].m;j--){
            for(int k=T;k>=v[i].t;k--){
                dp[j][k]=max(
                    dp[j][k],
                    dp[j-v[i].m][k-v[i].t]+1
                );
            }
        }
    }

    cout<<dp[M][T];

    return 0;
}

本题知识点总结

1. 状态定义

cpp 复制代码
dp[j][k]

表示:

在金钱不超过 j、时间不超过 k 的情况下,最多可以完成多少个愿望。

普通 01 背包只有一个容量:

cpp 复制代码
dp[j]

这题有两个限制:

text 复制代码
金钱
时间

所以状态增加一维:

cpp 复制代码
dp[j][k]

2. 状态转移

对于当前愿望:

text 复制代码
需要金钱:m
需要时间:t

如果选择它:

cpp 复制代码
dp[j-m][k-t]+1

如果不选择:

cpp 复制代码
dp[j][k]

因此:

cpp 复制代码
dp[j][k]=max(
    dp[j][k],
    dp[j-m][k-t]+1
);

3. 为什么两个容量都倒序?

因为:

每个愿望只能完成一次。

如果金钱或时间正序枚举,本轮刚刚更新出的状态可能继续参与后面的转移,相当于同一个愿望被重复使用。

因此必须:

cpp 复制代码
for(int j=M;j>=m;j--)
{
    for(int k=T;k>=t;k--)
    {
        ...
    }
}

4. 与普通01背包对比

普通 01 背包:

text 复制代码
一个容量限制

状态:

cpp 复制代码
dp[j]

转移:

cpp 复制代码
dp[j]=max(dp[j],dp[j-w]+v);

二维费用背包:

text 复制代码
两个容量限制

状态:

cpp 复制代码
dp[j][k]

转移:

cpp 复制代码
dp[j][k]=max(
    dp[j][k],
    dp[j-w1][k-w2]+value
);

5. 为什么这题价值是 +1?

因为每完成一个愿望,答案增加一个。

所以:

cpp 复制代码
dp[j-v[i].m][k-v[i].t]+1

这里的:

text 复制代码
+1

就是:

再完成当前这个愿望。

如果题目改成每个愿望还有一个价值 w[i],那就会变成:

cpp 复制代码
dp[j][k]=max(
    dp[j][k],
    dp[j-m][k-t]+w[i]
);

模型对比

模型 资源限制 每件物品次数 状态
01背包 1种 一次 dp[j]
完全背包 1种 无限次 dp[j]
二维费用01背包 2种 一次 dp[j][k]

一句话总结

看到"两个容量限制 + 每件物品只能选一次",想到二维费用 01 背包;定义 dp[j][k],两个容量全部倒序枚举。

P1507 NASA的食物计划 题解复盘

模块:动态规划

目标:在体积和质量都有限的情况下,选择若干食品,使总卡路里最大

基本信息

项目 内容
题目编号、来源 P1507 洛谷 / NASA的食物计划
训练层级 B 变形题
知识版块 二维费用背包、01背包、动态规划

解题前 · 关键信号识别

维度 分析
目标、约束、底层结构 目标 :在总体积不超过 H、总质量不超过 T 的情况下,使所选食品的总卡路里最大。 约束 :每个食品只能选择一次。 底层结构:每件物品同时消耗体积和质量两种资源,因此属于二维费用 01 背包。
数据规模 H,T≤400n≤50,使用 O(nHT) 可以通过。
候选算法和依据 算法:二维费用 01 背包。 依据:每件食品只能选择一次,并且有体积、质量两个容量限制。
复杂度预判 时间复杂度 O(n×H×T),空间复杂度 O(H×T)

解题后 · 外化复盘

维度 内容
实现结构 / 核心思路 定义 dp[j][k] 表示在体积不超过 j、质量不超过 k 的情况下能够获得的最大卡路里。枚举每件食品,再倒序枚举体积和质量,使用 dp[j][k]=max(dp[j][k],dp[j-h][k-t]+x) 更新答案。
错因回溯 1. 容易把二维费用背包写成普通一维 01 背包,漏掉质量或体积其中一个限制。 2. 如果体积或质量正序枚举,会导致同一件食品在同一轮被重复选择。 3. 容易把第三个属性卡路里误当成容量,而实际上它是价值。
边界和易错点 1. 每件食品只能使用一次,因此两个容量都必须倒序。 2. dp 初始值为 0 即可,因为可以什么都不选。 3. 最终答案是 dp[H][T]
下次看到什么信号,我应该想到这个方法 看到"每件物品只能选一次 + 有两个容量限制 + 求最大价值",想到:二维费用 01 背包

AC 完整代码

cpp 复制代码
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
#include<iomanip>
using namespace std;

struct node{
    int h,t,x;
};

int dp[405][405];

int main(){
    int H,T,n;
    cin>>H>>T>>n;

    node v[55];

    for(int i=1;i<=n;i++){
        cin>>v[i].h>>v[i].t>>v[i].x;
    }

    for(int i=1;i<=n;i++){
        for(int j=H;j>=v[i].h;j--){
            for(int k=T;k>=v[i].t;k--){
                dp[j][k]=max(
                    dp[j][k],
                    dp[j-v[i].h][k-v[i].t]+v[i].x
                );
            }
        }
    }

    cout<<dp[H][T];

    return 0;
}

本题知识点总结

1. 状态定义

cpp 复制代码
dp[j][k]

表示:

在体积不超过 j、质量不超过 k 的情况下,能够获得的最大总卡路里。

这里:

text 复制代码
j

对应体积,

text 复制代码
k

对应质量。


2. 状态转移

当前食品:

text 复制代码
体积 h
质量 t
卡路里 x

如果不选:

cpp 复制代码
dp[j][k]

如果选择:

cpp 复制代码
dp[j-h][k-t]+x

所以:

cpp 复制代码
dp[j][k]=max(
    dp[j][k],
    dp[j-h][k-t]+x
);

3. 为什么两个容量都倒序?

因为每件食品只能选一次。

如果正序:

cpp 复制代码
for(int j=h;j<=H;j++)

当前食品刚更新出来的状态可能继续参与本轮后面的转移,相当于:

同一件食品被使用了多次。

因此必须:

cpp 复制代码
for(int j=H;j>=h;j--)
{
    for(int k=T;k>=t;k--)
    {
        ...
    }
}

4. 与普通01背包对比

普通 01 背包:

text 复制代码
一个容量限制

状态:

cpp 复制代码
dp[j]

转移:

cpp 复制代码
dp[j]=max(dp[j],dp[j-w]+v);

NASA 食物计划:

text 复制代码
两个容量限制

状态:

cpp 复制代码
dp[j][k]

转移:

cpp 复制代码
dp[j][k]=max(
    dp[j][k],
    dp[j-h][k-t]+x
);

5. 与 P1855 榨取kkksc03 对比

题目 两个容量 价值
P1855 榨取kkksc03 金钱 + 时间 每完成一个愿望 +1
P1507 NASA的食物计划 体积 + 质量 每件食品有自己的卡路里 x

两题本质完全一样:

二维费用 01 背包

只是价值不同。

模型对比

模型 资源限制 每件物品次数 状态 转移
01背包 1种 一次 dp[j] max(dp[j],dp[j-w]+v)
完全背包 1种 无限次 dp[j] 容量正序
二维费用01背包 2种 一次 dp[j][k] 两个容量都倒序

一句话总结

看到"两个容量限制 + 每件物品只能选一次 + 求最大价值",想到二维费用 01 背包;定义 dp[j][k],两个容量全部倒序枚举。

相关推荐
2601_956121971 小时前
高精度问题
c++
橙橙笔记1 小时前
C++的学习第三部分
开发语言·c++·学习
码匠许师傅9 小时前
【C++ 面试真题】聊聊 C++ 的移动语义与右值引用
java·c++·面试
小小龙学IT11 小时前
DuckDB 深度实战:用 C++ 在进程内跑一个「分析型数据库」
数据库·c++
知无不研12 小时前
lambda表达式的使用(3)
开发语言·c++·lambda
C++ 老炮儿的技术栈14 小时前
基于Qt实现轻量化本地音乐播放器
开发语言·c++·qt·c·播放器·音乐
qz_Serene16 小时前
C++:类和对象(上)
开发语言·c++
郝学胜-神的一滴17 小时前
干货版《算法导论》17:二叉树核心原理、遍历逻辑与高阶实操全解
数据结构·c++·python·算法·计算机·编程
hetao173383718 小时前
2026-08-09~08-14 hetao1733837 的刷题记录
c++·算法