0
点赞
收藏
分享

微信扫一扫

01背包问题详解


01背包问题

 

最近看了好多关于01背包问题的优化,没找到一篇通俗易懂的,大概是我太笨了,所以根据自己的理解简单写一写,(如有错误,欢迎大牛指出)

0-1 背包问题:给定 N 种物品和一个容量为 V 的背包,第 i 个 物品 的重量是 wi,其价值为 vi 。

问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

01背包,0和1 就是表示在面对一种决策( 这里是指选择拿不拿物品的一种决策 )的时候,可以选择拿物品 i  ,也可以不拿物品 i ;

它的 状态转移方程为 :      

什么意思呢? 

01背包问题详解_#include

表示的是 只看前 i 个物品 ,当背包最大可以装载的体积为 j 时的最大价值 , 那么它是由我们决策之后的出来的

也就是说,当我们面对第 i  个物品时 (此时我们背包里已经放了 i-1 个物品),我们考虑到底放不放第i个物品? 如果放,会不会使得价值增大 .

        显然, 如果不放第 i 个物品 ,那么 背包里不就是还是 i-1 个物品么,当然最大可装载体积为 j ,价值方程为  :  

01背包问题详解_#include_02

;                  如果选择放第 i 个物品 ,那么 背包的价值可能会增大(前提是背包能装下第i 个物品) 所以 

01背包问题详解_01背包_03

的意思就是在背包中                  腾出 v[i] 个体积来放第i 的物品 . 所以价值方程为 : 

01背包问题详解_01背包_04

.  我们考虑最大价值 :   

 

 

题目 

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤10000<N,V≤1000
0<vi,wi≤10000<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std ;
const int MAX = 1005 ;
int dp[MAX][MAX] ;
int w[MAX],v[MAX] ;
int main()
{
int N , V ;
scanf("%d%d",&N,&V);
for(int i = 1 ; i<=N ;i++)
{
scanf("%d%d",&v[i],&w[i]);
}
for(int i = 1 ;i<=N ; i++)
{
for(int j = 0; j<=V ;j++)
{

if(j>=v[i])
{

dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
}
else
{
dp[i][j] = dp[i-1][j] ;
}

}
}
printf("%d\n",dp[N][V]);


return 0 ;
}


其实我们可以对01背包进行优化 , 当然是空间上的优化,我们用滚动数组 , 因为通过 状态方程 我们可以看出dp[i][j] 它只和dp[i-1][j]的状态有关 ,换句话说 dp[i][j] 是 由 它上一个状态转移来的 .

01背包问题详解_i++_05

由图表结合转移方程可以看出 , 比如 第2 行 第 2列的 4 是由 第1 行的 第2 列 和 第1 行的第1 列转移来的 ,所以我们只要保留第i -1 行的数据 和第 i 行的数据即可 .

得到 

01背包问题详解_i++_06

   , 不理解没关系, dp[j] 表示的是背包最大装载的体积为j 时的最大体积 ,我们这个方程是根据二维数组的状态方程写出来的 , 它们其实是一一对应的 状态转移方程右边的  dp[j] 就是 i-1 状态下的值, 左边是 i 状态面对决策的状态量,它还没更新哦 ,一定要注意 (等式两边的 dp[j] 代表的不一样  ,我在强调一遍 ,左边是 i 状态下的值,它还没更新 ,而右边的dp 是 i -1 状态的值 ,它已经更新过了 ,这样也就可以解释 i-1 状态向 i 状态 转移 ), 缓一缓 .

重点来了 , 在二维数组的代码中 我们第二层循环 是从 0 开始枚举背包容量 ,但是在一维下 要改成 从 V 递减枚举 到 j >=v[i] ,这是为什么,其实我们可以打个表 , 

4 5
1 2
2 4
3 4
4 5
dp[ 5 ] : 0 | dp[ 5 - 1 ] : 2 ** dp[ 5 ] : 2 | dp[ 5 - 1 ] : 2
dp[ 4 ] : 0 | dp[ 4 - 1 ] : 2 ** dp[ 4 ] : 2 | dp[ 4 - 1 ] : 2
dp[ 3 ] : 0 | dp[ 3 - 1 ] : 2 ** dp[ 3 ] : 2 | dp[ 3 - 1 ] : 2
dp[ 2 ] : 0 | dp[ 2 - 1 ] : 2 ** dp[ 2 ] : 2 | dp[ 2 - 1 ] : 2
dp[ 1 ] : 0 | dp[ 1 - 1 ] : 2 ** dp[ 1 ] : 2 | dp[ 1 - 1 ] : 2

dp[ 5 ] : 2 | dp[ 5 - 2 ] : 6 ** dp[ 5 ] : 6 | dp[ 5 - 2 ] : 6
dp[ 4 ] : 2 | dp[ 4 - 2 ] : 6 ** dp[ 4 ] : 6 | dp[ 4 - 2 ] : 6
dp[ 3 ] : 2 | dp[ 3 - 2 ] : 6 ** dp[ 3 ] : 6 | dp[ 3 - 2 ] : 6
dp[ 2 ] : 2 | dp[ 2 - 2 ] : 4 ** dp[ 2 ] : 4 | dp[ 2 - 2 ] : 4

dp[ 5 ] : 6 | dp[ 5 - 3 ] : 8 ** dp[ 5 ] : 8 | dp[ 5 - 3 ] : 8
dp[ 4 ] : 6 | dp[ 4 - 3 ] : 6 ** dp[ 4 ] : 6 | dp[ 4 - 3 ] : 6
dp[ 3 ] : 6 | dp[ 3 - 3 ] : 4 ** dp[ 3 ] : 6 | dp[ 3 - 3 ] : 4

dp[ 5 ] : 8 | dp[ 5 - 4 ] : 7 ** dp[ 5 ] : 8 | dp[ 5 - 4 ] : 7
dp[ 4 ] : 6 | dp[ 4 - 4 ] : 5 ** dp[ 4 ] : 6 | dp[ 4 - 4 ] : 5

8

我这里 " | " 符合前后是 更新dp[j] 前的值  ,  "**" 是 更新dp[j] 后的值 ,

我再手写一下哈 , 如果从 v[i] 到 V 递增枚举 ,(这里 V 代表 背包总容量)

比如 在外层循环到 2 的时候  ( i= 2 ) , 

 

v[i] = 2 ; (看excel表上的也行)

j = 2 , dp[2] = max(dp[2] ,dp[0]+ w[2] )  , 其中等式 右边的dp[2] 表示 i-1 状态下的

j = 3 , dp[3] = max(dp[3] ,dp[1] + w[2] ) ,

j = 4 , dp[4] = max(dp[4] ,dp[2] + w[2]) ,发现什么了吗 , dp[2] 刚刚更新完,是第 i 状态下的, 所以 dp[4] 就不是从 第 i -1 状态下转移的,所以错在这 . 

正确的是 ,  枚举V 到 v[i] 

例题代码:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std ;
const int N = 1005 ;

int dp[N] ;
int w[N] ,v[N] ;

int main()
{
int N , V ;
scanf("%d%d",&N,&V);
for(int i = 1 ;i<=N ;i++)
{
scanf("%d%d",&v[i],&w[i]);
}
for(int i = 1 ;i<= N ;i++)
{
for(int j = V ;j>=v[i] ;j--)
{
dp[j] = max(dp[j],dp[j-v[i]]+w[i]) ;
}

}
printf("%d",dp[V]);

return 0 ;
}

 

 

 

举报

相关推荐

0 条评论