首先从两个背包说起......
虚假的背包:
负重有限
物品占空间大
无法快速确定物品价值
大量占用人类生存空间
真正的背包:
0/1背包
完全背包
多重背包
...........................................................................................................................................................
负重无限
物品占用空间很小
物品价值确定快
不占用人类生存空间
这才是真正的背包!
好了,不多说,直接进入正轨——
——————————————————神奇的背包问题——————————————————
背包问题看似🐮,其实万变不离其宗:
其实是0/1背包,表示一个物品放进去了还是没放,但本身还是动态规划,只要不输出位置,bool和它没有关系 不分物品种类,数量有限制 | |
完全背包 | 物品种类有限制,数量无限制 |
多重背包 | 物品种类有限制,数量有限制 也就是集01加上完全 |
ps:完全、多重背包除不同点外,均与01背包相同!(请铭记这句话)
以上表格仅供参考,一切以实物为主......
01背包
解决背包题,首先你要记住:
背 包 基 本 都 是 动 态 规 划(类似于递推)
所以不用想别的啦!专心打表吧!
原理:
先假设four个物品,int w[?]={2,3,6,5}
当然还有价值:int v[?]={6,3,5,9}
设背包容量为9
则可得出以下表格(ps:行对应物品数量,列对应背包现存价值):
这就是dp[4][9](数组大小看题目):
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
当然一开始都是0
一行一行考虑:
如果只放前1个(第一个),那么其价值是物品1的价值,容量只有2,再多也放不进去,
所以dp[1][2]=2的价值6,所以dp[1][2]=6,整行除0外都是6(此情况就此一种方案)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
再考虑放前两个(第一、二个):
第二个,重量是3,可以放在dp[2][3]
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
2 | 0 | 0 | 6 | 0+3 | ? | ? | ? | ? | ? | ? |
但要放第二个,第一个又受不了,
太挤了,3个空放不下!
怎么办?
此时我们有两种方案:
方案1:舍弃第一个,放入第二个;
方案2:不放第二个,第一个仍在。
如何判断是采取方案1还是方案2?
看价值。(说得跟个黑社会似的)
dp数组用于存价值,第一个和第二个数价值相比较,就能够判定。
那么这么看来,w[1]明显高于w[2];(6>3)
那么启用方案2.
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
2 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
但如果背包容量是5呢?
显然,我们要求的最优条件失效了:w[1]+w[2]=9,比6和3都大!
那么我们启用——
方案3:当容量足够,均可加入总价值。
那么考虑前i个数......
最终,得到的儿dp表格如下:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
2 | 0 | 0 | 6 | 6 | 6 | 9 | 9 | 9 | 9 | 9 |
3 | 0 | 0 | 6 | 6 | 6 | 9 | 9 | 9 | 11 | 11 |
4 | 0 | 0 | 6 | 6 | 6 | 9 | 9 | 10 | 11 | 11 |
所以最后答案是:dp[4][9]=11,输出,AC。
代码如下:(仅供参考,切勿抄袭!!!)
#include <bits/stdc++.h>
using namespace std;
int w[201], v[31], dp[201][201];
int main() {
memset(dp, 0, sizeof(dp));
int m, n;
scanf("%d%d", &m, &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &w[i], &v[i]);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (w[i] > j)//当前容量小于该物品重量,装不下
dp[i][j] = dp[i - 1][j];//沿袭上一情况
else//当前容量大于该物品重量,装得下
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
//装或不装,max判断巧取方案
}
}
printf("%d\n", dp[n][m]);
return 0;
}
真的那么简单吗?
不可能滴!
还有更简单滴!
空间优化——滚动数组
数据很大,空间要优化!
别急,先观察dp表——
从表格中可以发现,每一行都是先前一行推出来的
那么用新的一行覆盖原来的(一维数组进行滚动)
就可以实现优化,实现AC
错误代码如下:
#include<bits/stdc++.h>
#include<algorithm>
using namespace std;
int w[10001],v[1001],dp[10001][1001];
int main(){
memset(dp,0,sizeof(dp));
int m,n;
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[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]]+v[i]);//省去if判断,直接max
cout<<dp[m]<<endl;
return 0;
}
//当然,它运行不了,有作者精心设计的“编译错误”,聪明的你能找出来吗?
朕乏了,完全和多重明天再弄吧......
路过的银呐,用行动告诉我你曾来过这里!🙂