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 小时前
ZILOT 论文解析
算法
kloc2 小时前
聊天室项目入口网关子服务的实现
linux·c++
饮水桶_T3 小时前
JAVA IO流笔记
开发语言·数据结构·算法
星夜离尘564 小时前
MATLAB 离散数据分箱:半开区间、末端点与计数、加权和、平均
算法·matlab
Pniubi4 小时前
力扣76.最小覆盖子串
算法·leetcode·职场和发展
地平线开发者5 小时前
征程6|用 hrt\_model\_exec 看清 YOLOv5x 的输入、原始输出和后处理
人工智能·算法
csdn_aspnet5 小时前
C++ 3个有序点的方向(Orientation of 3 ordered points)
c++·算法
赤生云电竞6 小时前
GPU算力共享平台架构实践:任务分发与资源隔离的关键配置
算法·架构·gpu算力
小七在进步6 小时前
string的使用(2)
c++
青少儿编程课堂7 小时前
蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:折半搜索优化子集枚举(Meet in the Middle)解析
c++·python·算法·bfs·信息学竞赛