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]
相关推荐
Cccp.12310 分钟前
【leetcode】(十四)从暴力递归到动态规划
数据结构·算法·leetcode·动态规划
艾莉丝努力练剑12 分钟前
【AI大模型接入SDK】Trae生成代码介绍
网络·c++·人工智能·学习·架构
syagain_zsx25 分钟前
BRpc 搭建简单 rpc 服务器
服务器·c++·网络协议·rpc·protobuf·库
旖旎夜光41 分钟前
LeetCode 215: 数组中的第K个最大元素 (快速排序) —— 题解
c++·学习·算法·leetcode·力控
技术胖1 小时前
奎爷巡演 AIGC 视频教程 文档
算法
德彪稳坐倒骑驴1 小时前
Leetcode力扣刷题记录与题解
算法·leetcode·职场和发展
lzhdim7 小时前
AI的分类
人工智能·算法·机器学习·分类·数据挖掘
凤凰院凶涛QAQ10 小时前
《Java版数据结构 & 集合类剖析》Map & Set & 反射:“从哈希碰撞到红黑树转换,反射帮你扒开 HashMap 的桶数组看个究竟”
java·数据结构·算法
H.莓飛10 小时前
【数据结构】排序_OJ题(一):计数、桶与排序后的处理
java·开发语言·数据结构
w_zero_one12 小时前
算法--模拟
java·数据结构·算法