有关其他的背包问题👇👇👇
背包五讲合集:0-1背包、完全背包、多重背包、分组背包、二维费用的背包。
题目描述
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
8
二维
定义二维数组dp[i][j]
表示在只能放前i
个物品的情况下,容量为j
的背包所能达到的最大总价值。
对应地,数组w[]
和v[]
分别存物品i
的重量和价值。
初始状态下有:
- 枚举所有
j
使dp[0][j] = 0
,表示没有物品可放时的最大价值恒为0 - 枚举所有
i
使dp[i][0] = 0
,表示容量为0时的最大价值恒为0
假设已处理好前i-1
件物品,对于第i
件物品所作出的决策为:
- 如果
j < w[i]
,背包容不下第i
件物品,只能是dp[i][j] = dp[i - 1][j]
,此时和前i - 1
件物品状态相等,表示没放入第i
件物品。 - 如果
j >= w[i]
,背包可以容下第i
件物品,此时面临放入还是不放入第i
件物品两种选择,要放入的话则是dp[i][j] = dp[i - 1][j - w[i]] + v[i]
,表示在前i - 1
件物品状态基础上,背包失去了w[i]
的容量,得到了v[i]
的价值。 - 由于追求价值最大,纠其二者,取一个
max()
即为最佳决策,所以得到了状态转移方程为
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
枚举所有的物品件数,再嵌套枚举所有的背包容量来更新dp[i][j]
,最终答案即存在了dp[N][V]
里。
C++代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int N, V;
int w[1010], v[1010];
int dp[1010][1010];
int main(){
cin >> N >> V;
for(int i = 1;i <= N;i ++)
cin >> w[i] >> v[i];;
for(int i = 0;i <= N;i ++)
dp[i][0] = dp[0][i] = 0;
for(int i = 1;i <= N;i ++){
for(int j = 1;j <= V;j ++){
dp[i][j] = (j >= w[i]) ? max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]) : dp[i - 1][j];
}
}
cout << dp[N][V] << endl;
return 0;
}
一维
考虑到定义二维数组时如果数据量较大则可能导致内存溢出等问题,其实该问题可以简化至一维处理。
经过上述分析,不难得出dp[i][j]
只与dp[i - 1][j]
有关,且更新前者时不会再去更新后者,所以[i]
这一维可以不进行存取,而将其体现在外循环上,定义f[j]
表示背包容量为j
时所能达到的最大总价值,那么第i
轮循环的f[j]——>原来的dp[i][j]
,只受到前一轮循环i - 1
时的f[j]
影响。
- 初始状态有:
f[0] = 0
- 状态转移方程变为:
f[j] = max(f[j], f[j - w[i]] + v[i])
,注*:右边的f[j]
不同于左边的f[j]
接下来有个很细节的点就是内层循环上发生了改变。
此时的 j
是从V
开始逆序向前循环。原因在于:
使用逆序循环时才能 保证第i
轮循环时读取的f[j]
是i - 1
轮循环后更新好的f[j]
数据(代表着方程中右边的f[j]
)。 因为如果是顺序循环j
从1到V
,中途某处读取到的f[j - w[i]]
或者f[j]
在前面j
较小时已被更新,而此时处在第i
轮循环,被更新的部分象征的是原来的dp[i][j](j或j - w[i]均由j表示)
,但需要比较的却是原来的dp[i - 1][j]
,于是f[j]
数据被 “污染” 了;
所以j
从大到小更新对应的f值可以保障读到的f[j较小]
是在本轮循环下没被更新的(对应到原来的dp[i - 1][j较小]
)。
下面以表格形式展现样例中f[j]
的更新过程,试着手算一遍会更便于理解:
C++代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int N, V;
int w[1010], v[1010];
int f[1010];
int main(){
cin >> N >> V;
for(int i = 1;i <= N;i ++)
cin >> w[i] >> v[i];;
f[0] = 0;
for(int i = 1;i <= N;i ++){
for(int j = V;j >= w[i];j --){
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
}
cout << f[V] << endl;
return 0;
}
注*:
本来j
要遍历V->1
的,然后代码应是
for(int i = 1;i <= N;i ++){
for(int j = V;j >= 1;j --){
f[j] = j >= w[i] ? max(f[j], f[j - w[i]] + v[i]) : f[j];
}
}
但观察到当j < w[i]
之后一直做的是f[j] = f[j]
操作,相当于是空操作,所以可以省去j < w[i]
的循环次数,将条件j >= 1
改为j >= w[i]
。
分割线:2022.8.18
偶然看到的,原来上面用一维数组来优化的做法是一个已提出的概念,叫滚动数组:
具体说明可以参照一下这位博主的文章👉滚动数组(简单说明)
大概含义是讲,对于某些只需要最终答案的题目,我们可以抛弃掉当中一些不必要存贮的数据,来减少空间的使用;做法是维护一个一维数组,先调用其位置之前的旧数据,然后再将当前位置更新覆盖掉原来的旧数据,而在0-1背包中需要逆序遍历才保证了数据更新的有序性