在csdn上看了很多关于01背包的问题,感觉这样说理解的比较快(也不能说理解吧,应该是大体知道这个公式是怎么运作的)
(这是作为一只菜鸡原来只知道模板,过程啥都不知道,只是知道这样算就是对的,现在终于知道它是怎么运作的,希望你也能有所收获,)
希望你能耐心的看完,以前我看别人的博客也是一扫而过,感觉字太多,太深奥,就不想看,这篇绝对是菜鸡式入门
0-1 背包问题:给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi 。
问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?
分析一波,面对每个物品,我们只有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次。
解决办法:声明一个 大小为 m[n][c] 的二维数组,m[ i ][ j ] 表示 在面对第 i 件物品,且背包容量为 j 时所能获得的最大价值 ,那么我们可以很容易分析得出 m[i][j] 的计算方法,
(1). j < w[i] 的情况,这时候背包容量不足以放下第 i 件物品,只能选择不拿
m[ i ][ j ] = m[ i-1 ][ j ]
(2). j>=w[i] 的情况,这时背包容量可以放下第 i 件物品,我们就要考虑拿这件物品是否能获取更大的价值。
如果拿取,m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + v[ i ]。 这里的m[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。
如果不拿,m[ i ][ j ] = m[ i-1 ][ j ] , 同(1)
究竟是拿还是不拿,自然是比较这两种情况那种价值最大。
由此可以得到状态转移方程
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
例:0-1背包问题。在使用动态规划算法求解0-1背包问题时,使用二维数组m[i][j]存储背包剩余容量为j,可选物品为i、i+1、……、n时0-1背包问题的最优值。绘制
价值数组v = {8, 10, 6, 3, 7, 2},
重量数组w = {4, 6, 2, 2, 5, 1},
背包容量C = 12时对应的m[i][j]数组。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
1 | 0 | 0 | 0 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 |
2 | 0 | 0 | 0 | 8 | 8 | 10 | 10 | 10 | 10 | 18 | 18 | 18 |
3 | 0 | 6 | 6 | 8 | 8 | 14 | 14 | 16 | 16 | 18 | 18 | 24 |
4 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 19 | 24 |
5 | 0 | 6 | 6 | 9 | 9 | 14 | 14 | 17 | 17 | 19 | 21 | 24 |
6 | 2 | 6 | 6 | 9 | 11 | 14 | 16 | 17 | 19 | 19 | 21 | 24 |
下面的希望大家能不厌其烦的把下面两段看完,(以前我连图都看不懂,现在菜鸡的我终于能够看懂图啦,还能懂一点它们的过程)
(第一行和第一列为序号,其数值为0)
如m[2][6],在面对第二件物品,背包容量为6时我们可以选择不拿,那么获得价值仅为第一件物品的价值8,如果拿,就要把第一件物品拿出来,放第二件物品,价值10,那我们当然是选择拿。m[2][6]=m[1][0]+10=0+10=10;依次类推,得到m[6][12]就是考虑所有物品,背包容量为C时的最大价值。
随便从里面抽一个数,比如m[4][8]=17怎么来的?先看m[3][8]=16,再看m[3][6]=14加上第四个物体的重量3就是17啦
也就是说遍历到那一个物体的时候,先在背包中腾出它所要的空间(上述的例子第四个物体需要2个空间才能装),剩下的空间保持在最大的价值(上述的例子就是m[3][6]=14),把这个物体再装进去,计算它的总价值(上述例子中也就是17)那这个装了这个物体的价值与未装这个物体的最大价值进行比较(也就是上述例子中的16)(当然它们比较的前提是相同容积状态下进行的比较)这样一直到最后最后一个肯定是最大价值啦
#include <iostream>
#include <cstring>
using namespace std;
const int N=15;
int main()
{
int v[N]={0,8,10,6,3,7,2};
int w[N]={0,4,6,2,2,5,1};
int m[N][N];
int n=6,c=12;
memset(m,0,sizeof(m));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c;j++)
{
if(j>=w[i])
m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
m[i][j]=m[i-1][j];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=c;j++)
{
cout<<m[i][j]<<' ';
}
cout<<endl;
}
return 0;
}
由二维降为一维又怎样理解
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++){
for(int j=v; j>=vol[i]; j--){
dp[j] = max(dp[j], dp[j-vol[i]]+val[i])
}
}
如何理解二维降一维呢?对于外层循环中的每一个i值,其实都是不需要记录的,在第i次循环时,所有的dp[0…v]都还未更新时,dp[j]还记录着前i-1个物品在容量为j时的最大价值,这样就相当于还记录着dp[i-1][j]和dp[i-1][j-vol[i]]+val[i]。
为什么要从v开始递减遍历?我举个例子,假设一个物品GG价值1000,体积为2,那么假设我们按【0…..v】这个顺序遍历,那么在j=2时,dp[2] = max(dp[2], dp[0]+1000),那么dp[2] = 1000,当j=4时,dp[4]=max(dp[4], dp[2]+1000), dp[4] = 2000,这时我们再思考一下,GG将被放进背包两次!!,如果我们逆序遍历,就可以避免这种结果。
此外,这里可以进行一个常数优化,将j>=vol[i]写进for循环中。
以下是具体的背包总结
题意: n个物品,v[]表示体积,w[]表示价值,m表示背包容量。求背包可得最大价值
初始化:
若要求为恰好装满,则初始化f[0] = 0,f[1...n] = INT_MIN,求答案时需要遍历最后一行取最大值。
若不要求,则f[0...n] = 0,答案为f[m]。
1.01背包问题
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1005;
int v[N],w[N];
int f[N][N];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 1;i <= n; i ++) cin>>v[i]>>w[i];
for(int i = 1;i <= n ;i ++){
for(int j = 1; j <= m; j ++){
if(j>=v[i])
f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
else{
f[i][j] = f[i-1][j];
}
}
}
cout<<f[n][m]<<endl;
return 0;
}
空间一维优化(第二层循环逆序m....v[i])
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1005;
int v[N],w[N];
int f[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 1;i <= n; i ++) cin>>v[i]>>w[i];
for(int i = 1;i <= n ;i ++){
for(int j = m; j >= 1 && j>=v[i]; j --){
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
2.完全背包问题
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1005;
int v[N],w[N];
int f[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 1;i <= n; i ++) cin>>v[i]>>w[i];
for(int i = 1;i <= n ;i ++){
for(int j = v[i]; j <=m; j ++){ // 逆序
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
3.多重背包问题
1.朴素做法(01背包变形,复杂度n^3)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 105;
int v[N],w[N],s[N],f[N];
int main()
{
int n,m;
cin>>n>>m;
for(int i = 1;i <= n;i ++) cin>>v[i]>>w[i]>>s[i];
for(int i = 1; i <= n; i ++){
for(int j = m; j >= v[i]; j--){
for(int k = 1; k * v[i] <= j && k <= s[i]; k ++){
f[j] = max(f[j],f[j - k * v[i]] + k*w[i]);
}
}
}
cout<<f[m]<<endl;
return 0;
}
2.二进制优化
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 2050;
int f[N];
struct Goods{
int v,w;
};
int main()
{
int n,m,v,w,s;
vector<Goods> goods;
cin>>n>>m;
for(int i = 0 ; i < n; i ++){
cin>>v>>w>>s;
for(int j = 1 ; j <= s ; j *= 2){
s -= j;
goods.push_back({v*j,w*j});
}
if(s > 0) goods.push_back({v*s,w*s});
}
for(int i = 0 ; i < goods.size(); i ++){
for(int j = m ;j >= goods[i].v; j-- ){
f[j] = max(f[j],f[j - goods[i].v] + goods[i].w);
}
}
cout<<f[m]<<endl;
return 0;
}
4.混合背包问题
tips:先归类为01背包和完全背包,再进行动态规划
s:代表物品种类,若s == -1,为01背包,s == 0,为完全背包,s > 0 ,为多重背包
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1010;
int f[N];
struct Goods{
int s;
int v,w;
};
int main()
{
vector<Goods> goods;
int n,m;
cin>>n>>m;
int s,v,w;
for(int i = 0; i < n; i ++ ){
cin>>v>>w>>s;
if(s == -1){
goods.push_back({-1,v,w});
}
else if( s == 0){
goods.push_back({0,v,w});
}
else{
for(int k = 1 ; k <= s; k *= 2){
s -= k;
goods.push_back({-1, k*v,k*w});
}
if(s > 0) goods.push_back({-1,s*v,s*w});
}
}
for(int i = 0; i < goods.size(); i ++){
if(goods[i].s == -1){
for(int j = m; j >= goods[i].v; j--) f[j] = max(f[j],f[j - goods[i].v]+goods[i].w);
}
else{
for(int j = goods[i].v ;j <= m; j ++) f[j] = max(f[j],f[j - goods[i].v]+goods[i].w);
}
}
cout<<f[m]<<endl;
return 0;
}
5.二维费用的背包问题
f[i][j] 代表体积为i,质量为j的状态的最优解
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int f[N][N];
int main()
{
int n,V,M;
cin>>n>>V>>M;
for(int i = 0;i < n; i ++){
int v,m,w;
cin>>v>>m>>w;
for(int j = V;j >= v;j --){
for(int k = M;k >= m; k-- ){
f[j][k] = max(f[j][k],f[j-v][k-m] + w);
}
}
}
cout<<f[V][M]<<endl;
return 0;
}