一、01背包
1、01背包介绍
"01背包"是一个经典的动态规划问题。在01背包中,给定一个背包容量和一组物品,每个物品都有自己的重量和价值。问题的目标是选择一些物品放入背包中,使得放入的物品总重量不超过背包容量,同时使得放入的物品总价值最大。
"01"表示每种物品最多 只能选择一次,即要么放入背包中,要么不放入。这是与其他背包问题(如完全背包和多重背包)的区别之一。
2、01背包核心代码
我们设背包容量为w,每个物品的质量和价值分别为wi,vi 。 我们把这个容量为w的背包分成w份存储在dp数组中,分别是1,2,3......w,代表这当背包容量为1,2,3....w时这个背包最大最多能装多少价值的东西:
for(int i=0;i<n;i++){ //遍历每一个物品
for(int j=w;j>=ww[i];j--){ // 从最大容量向下遍历
dp[j]=max(dp[j],dp[j-ww[i]]+v[i]; //状态转移方程(得到每个容量的最大价值)
}
}
二、完全背包
1、完全背包解释
完全背包问题是另一种常见的背包问题,与01背包问题不同的是,在完全背包问题中,每种物品可以选择 无限次 放入背包中。也就是说,对于每种物品,可以选择放入0个、1个、2个,直至放入尽可能多个,只要总重量不超过背包的容量。完全背包问题与01背包问题的主要区别在于状态转移方程的变化。
2、完全背包核心代码
for(int i=0;i<n;i++){
for(int j=ww[i];j<=w;j++){
dp[j]=max(dp[j,dp[j-ww[i]]]+v[i]);
}
}
- 外层循环
for(int i=0;i<n;i++)
遍历每个物品i
。 - 内层循环
for(int j=ww[i];j<=w;j++)
从背包容量ww[i]
开始,逐步遍历背包容量w
,更新背包容量为j
时的最大价值。 - 在内层循环中,通过状态转移方程
dp[j]=max(dp[j], dp[j-ww[i]]+v[i])
来更新背包容量为j
时的最大价值。 - 其中,
dp[j]
表示当前背包容量为j
时的最大价值,dp[j-ww[i]]+v[i]
表示将第i
个物品放入背包后的总价值,取两者的最大值作为更新后的最大价值。
通过动态规划的方式填充dp
数组,最终得到dp[w]
即为问题的解,表示背包容量为w
时放入所有物品所能获得的最大价值。
3、完全背包进阶(背包放满的最大价值)
1、核心代码:
memset(dp, -0X3f, sizeof(dp));
dp[0] = 0;
for (int i = 1; i <= n; i++)
for (int j = v[i]; j <= V; j++)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
if(dp[V]<0) dp[V]=0;
memset(dp, -0X3f, sizeof(dp));
:将dp
数组中的所有元素初始化为一个较大的负值(这里使用-0X3f),表示初始时背包中的最大价值为负无穷。dp[0] = 0;
:将背包容量为0时的最大价值初始化为0。for (int i = 1; i <= n; i++)
:遍历每个物品i
。for (int j = v[i]; j <= V; j++)
:从当前物品i
的价值v[i]
开始,逐步遍历背包容量V
。dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
:更新背包容量为j
时的最大价值,取当前最大价值dp[j]
与放入第i
个物品后的总价值dp[j - v[i]] + w[i]
的较大值。if(dp[V]<0) dp[V]=0;
:如果背包容量为V
时的最大价值为负值,则将其设为0,表示背包中没有物品时的最大价值为0。
最终,通过动态规划的方式填充dp
数组,得到dp[V]
即为问题的解,表示背包容量为V
时放入所有物品所能获得的最大价值。
2、例题
例题链接:https://ac.nowcoder.com/acm/problem/226516
完整ac代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1010;
const int INF = 0x3f3f3f3f;
int n, V, v[N], w[N], dp[N];
int main()
{
cin >> n >> V;
for (int i = 1; i <= n; i++)
{
cin >> v[i] >> w[i];
}
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++)
for (int j = v[i]; j <= V; j++)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
cout << dp[V] << endl;
memset(dp, -0X3f, sizeof(dp));
dp[0] = 0;
for (int i = 1; i <= n; i++)
for (int j = v[i]; j <= V; j++)
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
if(dp[V]<0) dp[V]=0;
cout << dp[V] << endl;
return 0;
}
这里有个要注意在使用memset
函数初始化数组时,传入的负数值不能太小。因为memset
函数将传入的值按字节进行复制,如果传入的是太小的负数,可能会导致在某些编译器中出现意外的行为。
比如在第二个memset之中不用0x3f用 -1000就会错乱。
三、多重背包
1、多重背包介绍
多重背包问题是背包问题的一个变种,与0-1背包和完全背包不同,多重背包问题中每种物品的数量是有限的,即可以选择多个物品,但每种物品的数量是有限的。
2、核心代码
for(int i=0;i<n;i++){
int k=1;
int x,w,v;
cin>>x>>w>>v;
while(k<=x){ //根据每种物品的数量进行了二进制拆分,有效地处理了每种物品的数量限制。
cnt++;
ww[cnt]=w*k;
vv[cnt]=v*k;
x-=k;
k<<=1;
}
if(x){
cnt++;
ww[cnt]=w*x;
vv[cnt]=v*x;
}
} //拆分之后用01 背包即可
for(int i=1;i<=cnt;i++){
for(int j=t;j>=ww[i];j--){
dp[j]=max(dp[j],dp[j-ww[i]]+vv[i]);
}
}
对这段这个二进制拆分(可以把这个和我之间写的快速幂一起理解,有点类似)有疑惑的同学可以参考B站:https://www.bilibili.com/video/BV1MA41177cg/?spm_id_from=333.1007.top_right_bar_window_history.content.click
3、例题
链接:https://ac.nowcoder.com/acm/problem/235950
完整代码:
#include<bits/stdc++.h>
using namespace std;
const int N=100006;
int main(){
int n,t;
int ww[N]={0},vv[N]{0},dp[N]={0};
int cnt=0;
cin>>n>>t;
for(int i=0;i<n;i++){
int k=1;
int x,w,v;
cin>>x>>w>>v;
while(k<=x){
cnt++;
ww[cnt]=w*k;
vv[cnt]=v*k;
x-=k;
k<<=1;
}
if(x){
cnt++;
ww[cnt]=w*x;
vv[cnt]=v*x;
}
}
for(int i=1;i<=cnt;i++){
for(int j=t;j>=ww[i];j--){
dp[j]=max(dp[j],dp[j-ww[i]]+vv[i]);
}
}
cout<<dp[t]<<endl;
return 0;
}
for(int i=0;i<n;i++){
for(int j=ww[i];j<=w;j++){
dp[j]=max(dp[j,dp[j-ww[i]]]+v[i]);
}
}