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]
相关推荐
指尖的爷18 分钟前
C++头文件的作用
开发语言·c++
智者知已应修善业34 分钟前
【51单片机0.1秒计时到21.0时点亮LED】2024-1-5
c++·经验分享·笔记·算法·51单片机
apcipot_rain38 分钟前
计科八股20260606——二叉树、PCA、图深度学习、进程上下文、C语言预编译、文件读写、单精度浮点数
c语言·数据结构·算法·pca·图神经网络
scx_link41 分钟前
逻辑回归的总结
算法·机器学习·逻辑回归
zh路西法42 分钟前
【rosbridge-websocket】跨网络的ROS1与ROS2通讯法(上)
linux·网络·c++·python·websocket·网络协议
j7~1 小时前
【C++】类和对象(下)--详解之再探构造函数,友元,static成员,类型转换等
开发语言·c++·类型转换·友元·匿名对象·内部类·编译器优化
稷下元歌1 小时前
7天学会plc加机器视觉关于运动控制部份,配套视频在bib
开发语言·c++·git·vscode·python·docker·pip
薇茗1 小时前
【C++】 类与对象 基础篇
开发语言·c++·基础语法·类与对象
A_humble_scholar1 小时前
Linux(三)深入理解 Makefile:自动变量、增量编译原理与文件时间属性
linux·服务器·c++·makefile
沐籽李1 小时前
Proteina-Complexa:NVIDIA 如何把蛋白 Binder 设计推进到全原子生成时代?
大数据·人工智能·算法·英伟达·蛋白质生成