P1855 榨取kkksc03 题解复盘
模块:动态规划
目标:在金钱和时间都有限的情况下,最多完成多少个愿望
基本信息
| 项目 | 内容 |
|---|---|
| 题目编号、来源 | P1855 洛谷 / 榨取kkksc03 |
| 训练层级 | B 变形题 |
| 知识版块 | 二维费用背包、01背包、动态规划 |
解题前 · 关键信号识别
| 维度 | 分析 |
|---|---|
| 目标、约束、底层结构 | 目标 :在总金钱不超过 M、总时间不超过 T 的情况下,尽可能完成更多愿望。 约束 :每个愿望只能完成一次。 底层结构:每个物品有两个费用限制(金钱、时间),属于二维费用 01 背包。 |
| 数据规模 | n≤100,M,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,不能误写成 M 或 T。 |
| 边界和易错点 | 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≤400,n≤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],两个容量全部倒序枚举。