理解01背包问题主要看这篇博客01背包详解。
(个人认为解释最好的博客!)
看懂这篇博客就没有什么要说的了。
但是这里有三个点我怕我以后会忘掉。
第一,为什么要这样列表?
这里的0 ~ 6好解释,0~12指的是此时背包的总质量(也不妨说是此时背包还能装多大的质量!!!)。
那么为什么要这样列表呢?为什么要考虑在每一个物品在背包能装载的0 ~ 12时的情形?
原因:现在我们假设物品的规模为3(1、2、3号3个物品),总质量仍为12.
当包的总质量为12时,当然将1、2号物品都放进去(前提是都装得下),能获得最大的价值。
但是为了最终能达到最大的价值,那我怎么知道我要不要把3号放入呢?
1,如果我不放3号的话,我要知道此时在轮完1、2号之后(总质量为12)的最大价值。
2,如果我放3号的话,就要要求3号被放入之前有充足的质量使3号能够放入,也就是 12 - w(3) ,这时我就要知道在轮2号的时候,在 12 - w(3) 这个总质量下的最大价值,也就是我要知道 12 - w(3) 下的最大价值是多少。
3,也就是要从1,2两种情况下求最大值:
dp[3][12] = max ( dp[2][12] , dp[2][ 12 - w(3) ] )
反过来看,当我遇到6号的时候,如果我不放的话,dp[5][12] 是多少,如果我放的话,dp[5][11] 是多少。那么肯定我要知道 dp[5][12] 和 dp[5][11] 的最优解是多少。
那么就回到了5号的时候,如果我不放的话,dp[4][12] 是多少,如果我放的话,dp[4][7] 是多少。那么肯定我要知道 dp[4][12] 和 dp[4][7] 的最优解是多少。
…
所以我们要知道dp[1][0~12]下的解,然后求出dp[2][0 ~12]下的解
…
也就是说,每当我遇到一个物品的时候,我都求出来m从0~12的所有情况,每当我遇到下一个物品的时候我同样求出来m从0 ~12的所有情况。
我们的程序也是这样写的。
第二点,为什么我们能将二维数组转化为一维数组从而减小了内存占用,为什么我们在转化为一维数组之后要从12开始而不是从0开始算起呢?
由上面的解释我们可以知道在遇到一个新的物品时,在我们求其在m从0到12下的最优解时都是通过上一个物品在m从0到12下的情况的出来的。( dp[3][12] = max ( dp[2][12] , dp[2][ 12 - w(3) ] ) )。
所以我们可以再求下一个物品在m从0~12的情况时覆盖掉上一个物品的所有情况,然后接着求接着覆盖…
那么我们为什么要从后往前呢?
是因为当我们求这个物品在m时的最大价值时,要么是取其正上方的数(也就是不放这个物品),或者是取其左上方的数(也就是放这个物品)。当我们转化为一维数组的时候,就变成了要么不变(不放),要么取其左边的一个数(放)。所以如果我们从左更新的话,就可能用到已经更新的数(就不是上一个物品的时的情况了,而是这一个物品的情况)。所以我们需要从右开始更新,这样当我们更新一个数的时候用到其左边或是其本身都不会是已经更新过的数了,而是上一个物品时还未更新的数。
第三点,以上求的都是在不超过背包最大质量的情况下求得的,那么在正好等于背包质量时应该怎样做?
如果是二维数组的情况,我们需要将除了dp[i][0] = 0(第一列)外,其他全为负无穷。
如果是一维数组的情况,我们需要将除了dp[0] = 0,其他全为负无穷。
这是为什么呢?
这是因为假如说背包完全装满,从后往前看,12 - 每个物品的质量,最后一定等于0,也就是说:
12 - w(6) - w(5) - w(3) - w(1) 一定等于0,这就要求开始的时候必须要从第一列(0)某一行开始。
如果装完后总质量是11,那么12 - w(5) - w(4) - w(3) - w(1)最后一定等于1,这就说明开始的时候从第二列开始的(1),所以 只有从第一列开始的最后才会被计算成正值,其余的都会被计算成负值。
最后dp[12]如果是负值的话,就说明没有可能能够将背包完全装满。(其他的没有将背包装满,但取得的价值有可能比装满情况下还大的价值的情况,肯定会被计算为负值。因为他一定是从非0列开始去的,这里都被设成了负无穷)。
那么怎么会有在0质量下的数呢?
其实并没有,这是因为我们在求当包的质量正好等于这一物品的质量时,
我们用到了这一公式:
dp[i][m] = max ( dp[i-1][m] , dp[i-1][0] )
显然等于 = dp[i-1][0] = 0.
这不就用到了某 i 行第0列的0了嘛!而且只有用到某一行第0列的序列才能最后得到正值,从dp[i][1、2、3、…] 开始的序列最后得到的一定是负值(对应的实际总质量为11,10,9…)(这是因为我们每次都严格地减去每个物品的质量,所以如果没有达到12的话,用12减的话最终一定不是0 )。
代码见下:
未优化:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
//dp[ i ][ j ](意思是:前 i 件物品放到一个容量为剩为j的背包中可以获得的最大价值,
int W,N,w[100],v[100],dp[100][100];
cin>>W>>N;
for(int i=1;i<=N;i++) cin>>w[i];
for(int i=1;i<=N;i++) cin>>v[i];
memset(dp,0,sizeof(dp));//初始化为0
for(int i=1;i<=N;i++){
for(int j=1;j<=W;j++){
if(j<w[i])
dp[i][j]=dp[i-1][j];//不装,因为装不下
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]); //在可以装得前提下, 看看装还是不装哪一种情况可以使得价值更大;
}
}
cout<<dp[N][W]<<endl;
return 0;
//12 6
//4 6 2 2 5 1
//8 10 6 3 7 2
//24
}
优化:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
//dp[ i ][ j ](意思是:前 i 件物品放到一个容量为剩为j的背包中可以获得的最大价值,
int W,N,w[100],v[100],dp[100];
cin>>W>>N;
for(int i=1;i<=N;i++) cin>>w[i];
for(int i=1;i<=N;i++) cin>>v[i];
memset(dp,0,sizeof(dp));//初始化为0
for(int i=1;i<=N;i++){
for(int j=W;j>=w[i];j--){//j >= w[i] 保证可以装的下
dp[j]=max(dp[j],dp[j-w[i]]+v[i]); //在可以装得前提下, 看看装还是不装哪一种情况可以使得价值更大;
}
}
cout<<dp[W]<<endl;
return 0;
//12 6
//4 6 2 2 5 1
//8 10 6 3 7 2
//24
}
保证背包完全装满:
#include<iostream>
#include<cstring>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int main()
{
//dp[ i ][ j ](意思是:前 i 件物品放到一个容量为剩为j的背包中可以获得的最大价值,
int W,N,w[100],v[100],dp[100];
cin>>W>>N;
for(int i=1;i<=N;i++) cin>>w[i];
for(int i=1;i<=N;i++) cin>>v[i];
//memset(dp,0,sizeof(dp));//初始化为0
/*
dp[100][100];
for(int i=0;i<=N;i++){//要从第0行开始
for(int j=1;j<=W;j++){
dp[i][j]=-INF;
}
dp[i][0]=0;
}
*/
dp[0]=0;
for(int i=1;i<=W;i++)
dp[i]=-INF;
for(int i=1;i<=N;i++){
for(int j=W;j>=w[i];j--){//j >= w[i] 保证可以装的下
dp[j]=max(dp[j],dp[j-w[i]]+v[i]); //在可以装得前提下, 看看装还是不装哪一种情况可以使得价值更大;
}
}
if(dp[W]<0)
cout<<"NO"<<endl;
else
cout<<dp[W]<<endl;
return 0;
//12 6
//4 6 2 2 5 1
//8 10 6 3 7 2
//24
}