记录109
cpp
#include<bits/stdc++.h>
using namespace std;
int dp[13000];//重量状态
int w[3500];//重量
int d[3500];//价值
int main(){
int n,m;//物品个数,总重
cin>>n>>m;//输入
for(int i=1;i<=n;i++) cin>>w[i]>>d[i];//输入
for(int i=1;i<=n;i++){//遍历物品
for(int j=m;j>=w[i];j--){//重量状态
dp[j]=max(dp[j],dp[j-w[i]]+d[i]);//更新背包
}
}
cout<<dp[m];//输出
return 0;//结束程序
}
题目传送门
https://www.luogu.com.cn/problem/P2871
突破口
有 N 件物品和一个容量为 M 的背包。第 i 件物品的重量是 Wi,价值是 Di。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。
🔍 一、题目核心理解
🎯 问题描述
这是一个经典的 0-1 背包问题(0-1 Knapsack Problem):
- 有
N件物品,每件有 重量W_i和 价值D_i - 背包容量为
M - 每件物品 只能选一次或不选
- 目标:在 总重量 ≤ M 的前提下,使总价值最大
✅ 这是动态规划中最基础、最重要的模型之一。
📌 输入样例解析(输入 #1)
cpp
4 6 → N=4 件物品,背包容量 M=6
1 4 → 物品1:重1,价值4
2 6 → 物品2:重2,价值6
3 12 → 物品3:重3,价值12
2 7 → 物品4:重2,价值7
尝试组合:
- 选物品1(1,4) + 物品3(3,12) + 物品4(2,7) → 总重=1+3+2=6,总价值=4+12+7=23 ✅
- 其他组合均 ≤23
输出:23
🧠 二、解题思路:0-1 背包动态规划
状态定义
dp[j]表示:在背包容量为 j 时,能获得的最大价值
状态转移
对第 i 件物品(重量 w[i],价值 d[i]):
-
不选 :
dp[j]不变 -
选 :前提是
j ≥ w[i],则价值为dp[j - w[i]] + d[i] -
取两者最大值:cpp
cppdp[j] = max(dp[j], dp[j - w[i]] + d[i]);
初始化
dp[0..M] = 0(全局数组自动初始化为 0)- 表示"没有物品可选"时,任何容量下最大价值为 0
遍历顺序
- 外层:遍历每件物品
i = 1 to N - 内层:倒序遍历容量
j = M downto w[i]- 倒序是为了防止同一物品被重复使用(保证是 0-1 背包,不是完全背包)
最终答案
dp[M]:容量不超过 M 时的最大价值(注意:DP 定义的是"恰好用 j 容量"的最大值,但由于我们从 0 开始且只做 max,dp[M]实际上是"≤M"的最大值)
💡 为什么
dp[M]就是答案?
- 因为我们在更新过程中,所有
j ≤ M的状态都被考虑- 而
dp[j]是非递减的(容量越大,能装的不会更少),所以dp[M]是最大值
cpp
#include<bits/stdc++.h>
using namespace std;
int dp[13000]; // dp[j]:容量为 j 时的最大价值(M ≤ 12880,开13000足够)
int w[3500]; // w[i]:第 i 件物品的重量(N ≤ 3402,开3500足够)
int d[3500]; // d[i]:第 i 件物品的价值
- 数组大小略大于题目上限,避免越界
cpp
int main(){
int n, m; // n: 物品数量, m: 背包容量
cin >> n >> m; // 读入 N 和 M
cpp
for(int i = 1; i <= n; i++)
cin >> w[i] >> d[i]; // 读入每件物品的重量和价值(从下标1开始)
- 使用 1-based 索引,便于理解
cpp
for(int i = 1; i <= n; i++){ // 遍历每一件物品
for(int j = m; j >= w[i]; j--){ // 倒序遍历背包容量
dp[j] = max(dp[j], dp[j - w[i]] + d[i]); // 状态转移
}
}
🔄 关键逻辑说明:
- 外层循环:逐个考虑物品
- 内层循环倒序 :
- 如果正序(
j = w[i] to m),则dp[j - w[i]]可能已经包含了当前物品i,导致重复选择 → 变成完全背包 - 倒序确保
dp[j - w[i]]来自前 i-1 件物品的状态,符合 0-1 背包要求
- 如果正序(
✅ 举例:若
w[i]=2,正序时dp[2]更新后,dp[4]会用到新的dp[2],相当于选了两次物品 i
cpp
cout << dp[m]; // 输出最大价值
return 0;
}
dp[m]即为所求答案
⚠️ 关键细节说明
| 细节 | 说明 |
|---|---|
| 数组初始化 | 全局 int dp[] 自动初始化为 0,正确 |
| 下标从1开始 | 避免处理 i=0 的边界,清晰 |
| 倒序更新 | 0-1 背包的核心,防止重复选择 |
| 空间优化 | 使用一维数组,将空间从 O(N×M) 降为 O(M) |
| 时间复杂度 | O(N × M) ≈ 3402 × 12880 ≈ 4.4e7,在 C++ 中可接受 |
总结:问题与解法对应
| 题目要素 | DP 设计 |
|---|---|
| N 件物品 | 外层循环遍历 |
| 重量 W_i,价值 D_i | 存入数组 |
| 容量 M | 内层循环上限 |
| 每件至多选一次 | 倒序更新 |
| 最大化总价值 | max 转移,输出 dp[M] |