记录101
cpp
#include<bits/stdc++.h>
using namespace std;
int info[1010],wo[1010],mon[1010],dp[1010][1010];
int main(){//01背包+双重消耗
int n,m,x;
cin>>n>>m>>x;
for(int i=1;i<=n;i++) cin>>info[i]>>wo[i]>>mon[i];
for(int i=1;i<=n;i++){//二维背包
for(int j=m;j>=wo[i];j--){
for(int k=x;k>=mon[i];k--){
dp[j][k]=max(dp[j][k],dp[j-wo[i]][k-mon[i]]+info[i]);
}
}
}
cout<<dp[m][x];
return 0;
}
题目传送门
https://www.luogu.com.cn/problem/P1910
突破口
你现在有 N 个人选,每个人都有这样一些数据:A(能得到多少资料)、B(伪装能力有多差)、C(要多少工资)。已知敌人的探查间谍能力为 M(即去的所有人 B 的和要小于等于 M)和手头有 X 元钱,请问能拿到多少资料?
思路
🔍 一、题目核心理解
🎯 问题描述
-
有
N个间谍候选人 -
每人有三个属性:
A_i:能获取的资料量(价值)B_i:伪装能力差的程度(越小越好)→ 敌人侦察值限制总和 ≤ MC_i:工资要求 → 总花费 ≤ X
-
给定两个资源上限:
- 总伪装差值 ≤ M(即 ∑B_i ≤ M)
- 总工资 ≤ X(即 ∑C_i ≤ X)
-
目标 :在满足上述两个约束的前提下,最大化总资料量 ∑A_i
✅ 这是一个典型的 二维 0-1 背包问题:
- 每个物品(间谍)只能选或不选
- 有两个"重量"维度:
B(伪装差)和C(工资)- "价值"是
A(资料量)
📌 举例(样例)
输入:
3 10 12
10 1 11 → 资料10,伪装差1,工资11
1 9 1 → 资料1,伪装差9,工资1
7 10 12 → 资料7,伪装差10,工资12
约束:总 B ≤ 10,总 C ≤ 12
可行方案:
- 选第1人:B=1≤10,C=11≤12 → 资料=10
- 选第2人:B=9≤10,C=1≤12 → 资料=1
- 选1+2:B=1+9=10≤10,C=11+1=12≤12 → 资料=10+1=11 ✅
- 选第3人:C=12≤12,但 B=10≤10 → 资料=7 < 11
→ 最优答案:11
🧠 二、解题思路:二维 0-1 背包 DP
状态定义
dp[j][k]= 在 总伪装差 ≤ j 且 总工资 ≤ k 的条件下,能获得的最大资料量
状态转移(0-1 背包)
对每个间谍 i(资料 info[i],伪装差 wo[i],工资 mon[i]):
-
倒序遍历
j从M到wo[i] -
倒序遍历
k从X到mon[i] -
更新:
cppdp[j][k] = max(dp[j][k], dp[j - wo[i]][k - mon[i]] + info[i])
⚠️ 必须倒序!否则会重复选择同一间谍(变成完全背包)
初始化
dp[0..M][0..X] = 0(全局变量自动初始化为 0)- 表示:不选任何人时,资料量为 0
答案
dp[M][X]:在总伪装差 ≤ M、总工资 ≤ X 下的最大资料量
💡 因为
dp[j][k]是"不超过 j 和 k"的最大值,所以dp[M][X]就是全局最优
代码分析
cpp
#include<bits/stdc++.h>
using namespace std;
int info[1010], wo[1010], mon[1010]; // 存储每个间谍的 A, B, C
int dp[1010][1010]; // dp[j][k]:伪装差≤j、工资≤k 时的最大资料量
- 数组大小
1010:因为M, X ≤ 1000,开1010安全(避免越界) info[i]= A_i(资料)wo[i]= B_i(伪装差,"weight of observation")mon[i]= C_i(工资,"money")
cpp
int main(){
int n, m, x;
cin >> n >> m >> x; // n: 人数;m: 伪装差上限;x: 工资上限
cpp
for(int i = 1; i <= n; i++)
cin >> info[i] >> wo[i] >> mon[i];
- 读入每个间谍的三项数据
- 从
i=1开始存储,方便后续循环
cpp
for(int i = 1; i <= n; i++){ // 枚举每个间谍(物品)
for(int j = m; j >= wo[i]; j--){ // 倒序枚举伪装差上限(第一维容量)
for(int k = x; k >= mon[i]; k--){ // 倒序枚举工资上限(第二维容量)
dp[j][k] = max(dp[j][k], dp[j - wo[i]][k - mon[i]] + info[i]);
}
}
}
🔄 三层循环详解:
- 外层
i:处理第i个间谍 - 中层
j(倒序) :- 从
m到wo[i],确保j - wo[i] ≥ 0 - 倒序防止重复选择(0-1 背包标准技巧)
- 从
- 内层
k(倒序) :- 从
x到mon[i],确保k - mon[i] ≥ 0 - 同样倒序,保证状态来自"未选当前间谍"的情况
- 从
✅ 转移含义:
- 不选间谍
i:dp[j][k]保持不变- 选间谍
i:dp[j - wo[i]][k - mon[i]] + info[i]- 取两者最大值
cpp
cout << dp[m][x];
return 0;
}
- 直接输出
dp[m][x]:在总伪装差 ≤ m、总工资 ≤ x 下的最大资料量
⚠️ 关键细节说明
| 细节 | 说明 |
|---|---|
| 为什么倒序? | 防止同一个间谍被多次选择(0-1 背包核心) |
| 数组大小 1010? | M, X ≤ 1000,开 1010 避免越界(dp[1000][1000] 需要下标到 1000) |
| 初始值为 0? | 全局变量自动初始化为 0,正确表示"无间谍可选" |
答案是 dp[m][x]? |
是的,因为 dp[j][k] 表示"不超过 j 和 k"的最大值,单调不减 |
总结:问题与解法对应
| 题目要求 | DP 设计 |
|---|---|
| 每个间谍只能选一次 | 0-1 背包 |
| 两个资源限制(伪装差、工资) | 二维背包(dp[j][k]) |
| 最大化资料总量 | 价值 = A_i,求最大总价值 |
| 输出最大资料量 | dp[M][X] |