赞
踩
"01背包"是一个经典的动态规划问题。在01背包中,给定一个背包容量和一组物品,每个物品都有自己的重量和价值。问题的目标是选择一些物品放入背包中,使得放入的物品总重量不超过背包容量,同时使得放入的物品总价值最大。
"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]; //状态转移方程(得到每个容量的最大价值)
- }
- }
-
-
完全背包问题是另一种常见的背包问题,与01背包问题不同的是,在完全背包问题中,每种物品可以选择 无限次 放入背包中。也就是说,对于每种物品,可以选择放入0个、1个、2个,直至放入尽可能多个,只要总重量不超过背包的容量。完全背包问题与01背包问题的主要区别在于状态转移方程的变化。
- 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
时放入所有物品所能获得的最大价值。
- 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
时放入所有物品所能获得的最大价值。
例题链接: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就会错乱。
多重背包问题是背包问题的一个变种,与0-1背包和完全背包不同,多重背包问题中每种物品的数量是有限的,即可以选择多个物品,但每种物品的数量是有限的。
- 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]);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。