P2871 [USACO07DEC] Charm Bracelet S

记录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

    cpp 复制代码
    dp[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]
相关推荐
郝学胜-神的一滴2 小时前
深入epoll反应堆模型:从libevent源码看高性能IO设计精髓
linux·服务器·开发语言·c++·网络协议·unix·信息与通信
_F_y2 小时前
C++11 异步操作实现线程池
java·jvm·c++
!停2 小时前
C++入门STL容器Vector使用基础,深挖 Vector替代 C 语言繁琐容器的利器
开发语言·c++
CoderCodingNo2 小时前
【CSP】CSP-J 2019 江西真题 | 面积 luogu-P5681 (适合GESP一级、二级考生练习)
算法
Mr_pyx2 小时前
【LeetHOT100】合并两个有序链表——Java多解法详解
算法
tankeven2 小时前
C++ 学习杂记06:std::unordered_map
c++
t***5442 小时前
如何在 Dev-C++ 中设置 MinGW 和 Clang 的路径
java·前端·c++
yu85939582 小时前
利用MATLAB进行木材图像去噪
开发语言·算法·matlab
cpp_25013 小时前
P2722 [USACO3.1] 总分 Score Inflation
数据结构·c++·算法·动态规划·题解·洛谷·背包dp