点击查看代码
/*
1.01背包问题
题目:https://ac.nowcoder.com/acm/problem/226514
你有⼀个背包,最多能容纳的体积是 V 。
现在有 n 个物品,第 i 个物品的体积为 vi ,价值为 wi 。
1. 求这个背包⾄多能装多⼤价值的物品?
2. 若背包恰好装满,求⾄多能装多⼤价值的物品?
2.讲解算法原理:
解法一:贪心
1.尽可能的选择价值较大的物品
2,尽可能的选择体积较小的物品
3.尽可能的选择 w/v 较大的物品
......
都不行,有反例
解法二:动态规划
第一问:
1.状态表示
f[i] 表示: 在[1, i]区间内 挑选物品, 所有的选法下, 最大的价值---不行
f[i][j] 表示: 在[1, i]区间内挑选物品, 总体积不能超过j, 所有的选法下, 最大价值
第二问:
f[i][j] 表示: 在[1, i]区间内挑选物品, 总体积必须等于j, 所有的选法下, 最大价值
2.状态转移方程
1.不选i ----> f[i-1][j]
f[i]
2.选i ----> w[i] + f[i-1][j - v[i]] (j >= v[i])
3.初始化
第一问:f[i][j] i == 0 行初始化为0
第二问:
第一种解决方式:
非法的各自初始化为-1,在状态转移方程的时候,特判一下即可
第二种解决方式:(更好)
非法的格子全部初始化为负无穷,仅需在最终结果的位置判断一下即可
4.填表顺序 从上到下每一行
5.最终结果
f[n][v]
6.空间优化
1.考虑是否修改遍历顺序
2.直接在原始的代码上,删掉第一维即可
*/
#include <iostream>
// memset函数需要的头文件(用来初始化数组)
#include <cstring>
// max函数需要的头文件(部分编译器不包含会报错)
#include <algorithm>
using namespace std;
// 定义数组最大长度:题目中n和V的上限是1000,所以设1e3+10避免越界
const int N = 1e3 + 10;
int v[N]; // 存储每个物品的体积,v[i]表示第i个物品的体积
int w[N]; // 存储每个物品的价值,w[i]表示第i个物品的价值
// f[i][j] 核心定义:
// 在[1, i]个物品中挑选,所选物品总体积≤j时,能得到的最大价值
int f[N][N];
int main(){
// n是物品数量,m是背包最大容量(题目里的V)
int n, m;
// 输入物品数量和背包容量
cin >> n >> m;
// 循环输入每个物品的体积和价值(i从1开始,对应“前i个物品”的逻辑)
for(int i = 1; i <= n; i++){
cin >> v[i] >> w[i];
}
// ===================== 方案1:不要求装满背包,求最大价值 =====================
// 遍历每个物品(i从1到n,处理第i个物品)
for(int i = 1; i <= n; i++){
// 遍历所有可能的背包容量(j从0到m,覆盖所有体积上限)
for(int j = 0; j <= m; j++){
// 第一步:不选第i个物品
// 此时最大价值 = 前i-1个物品、体积≤j的最大价值(继承上一轮的结果)
f[i][j] = f[i-1][j];
// 如果当前背包容量j ≥ 第i个物品的体积v[i],说明能装下这个物品
if(j >= v[i]){
// 第二步:尝试选第i个物品,取“选”和“不选”中的最大值
// 选第i个物品的价值 = 前i-1个物品、体积≤(j-v[i])的最大价值 + 第i个物品的价值
// max(不选的价值, 选的价值) → 得到当前状态的最优解
f[i][j] = max(f[i-1][j], f[i-1][j - v[i]] + w[i]);
}
}
}
// 输出方案1的结果:前n个物品、体积≤m(背包最大容量)的最大价值
cout << f[n][m] << endl;
// ===================== 方案2:要求恰好装满背包,求最大价值 =====================
// 初始化数组f为-0x3f(十进制-63),表示“初始时所有状态都是非法的(无法恰好装满)”
// memset是按字节赋值,-0x3f的绝对值小,不会溢出,适合表示“非法状态”
memset(f, -0x3f, sizeof f);
// 唯一的合法初始状态:0个物品、体积0,恰好装满,价值0
f[0][0] = 0;
// 重新遍历每个物品,计算“恰好装满”的状态(逻辑和方案1一致,只是初始化不同)
for(int i = 1; i <= n; i++){
for(int j = 0; j <= m; j++){
// 第一步:不选第i个物品,继承前i-1个物品的状态
f[i][j] = f[i-1][j];
// 如果能装下第i个物品,尝试选它
if(j >= v[i]){
// 选第i个物品的价值 = 前i-1个物品、体积=(j-v[i])的最大价值 + 第i个物品的价值
// 只有当f[i-1][j-v[i]]是合法的(≥-0x3f),选完后才可能合法
f[i][j] = max(f[i-1][j], f[i-1][j - v[i]] + w[i]);
}
}
}
// 输出方案2的结果:
// 如果f[n][m] < 0 → 说明无法恰好装满背包,按题目要求输出0
// 否则 → 输出恰好装满时的最大价值
if(f[n][m] < 0){
cout << 0 << endl;
}else{
cout << f[n][m] << endl;
}
return 0;
}
//空间优化版
#include <iostream>
#include <cstring>
#include <algorithm> // max函数必须的头文件
using namespace std;
const int N = 1e3 + 10;
int v[N], w[N];
// 一维DP数组:f[j] 表示“总体积不超过j(方案1)/恰好为j(方案2)”时的最大价值
int f[N];
int main(){
int n, m;
cin >> n >> m;
// 输入每个物品的体积v[i]和价值w[i]
for(int i = 1; i <= n; i++) cin >> v[i] >> w[i];
// ===================== 方案1:不要求装满背包 =====================
// 一维DP默认初始化为0(全局数组,未赋值时默认0),符合“不装满”的初始化规则
// 遍历每个物品
for(int i = 1; i <= n; i++){
// 逆序遍历体积:从m到v[i](核心!避免同一个物品被重复选)
for(int j = m; j >= v[i]; j--){
// 状态转移:
// f[j] = max(不选第i个物品的价值, 选第i个物品的价值)
// 不选:f[j](上一轮的结果,即前i-1个物品的最优解)
// 选:f[j-v[i]] + w[i](留出v[i]体积装前i-1个物品,加当前物品价值)
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
// 方案1结果:体积不超过m的最大价值
cout << f[m] << endl;
// ===================== 方案2:要求恰好装满背包 =====================
// 初始化:所有状态设为-0x3f(-63),表示“非法(无法恰好装满)”
memset(f, -0x3f, sizeof f);
// 唯一合法初始状态:体积0,恰好装满,价值0
f[0] = 0;
// 重新遍历物品,计算“恰好装满”的最优解
for(int i = 1; i <= n; i++){
// 逆序遍历体积(和方案1一样,避免重复选物品)
for(int j = m; j >= v[i]; j--){
// 转移逻辑和方案1一致,只是初始状态不同
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
// 输出方案2结果:
// f[m] < 0 → 无法恰好装满,输出0;否则输出f[m]
if(f[m] < 0){
cout << 0 << endl;
}else{
cout << f[m] << endl;
}
return 0;
}