前言
通过一道例题来阐述一下我对于0/1背包问题的想法,0/1背包问题是动态规划思想中一个非常经典的问题。应用非常广泛,主要解决了在复杂条件下的最大价值问题,下面来结合一道例题介绍一下这种思想。
一、背包问题概述
有多个物品,重量不同、价值不同,以及一个容量有限的背包,选择一些物品到背包中,这些物品不能被分割,只能完整地装入背包中,问怎样装才能使装进背包的物品总价值最大。
比如现在有4个物品,重量依次是2,3,6,5,价值分别是6,3,5,4,背包容量为9
0/1背包问题中最重要的数据结构就是一个二维表dp[num][value],其中num表示提供前num种物品选择,而value则表示在该选择下,对应的背包大小,综合起来看,dp[num][value]就表示在前num种物品选择条件下,在背包容量为value的情况下能够产生的最大价值。
那么这个数组是如何进行赋值的呢?
这里我总结为两个for循环加一个状态转移方程。程序的核心结构如下:
for(int i=1;i<=num;i++) //物品种类
{
for(int j=weight[i];j<=v;j++) //背包容量
{
//装入当前这个物品,或者不装,选取能够产生价值最大的一种情况
dp[i][j]=max(dp[i-1][j-weight[i]]+value[i],dp[i-1][j]); //状态转移方程
}
}
外层循环控制物品的可用种类,也就是前述的num,表示第1~i种物品都是可用的,但不一定都要装入,内层循环控制背包的容量,背包的容量从第 i 种物品的重量开始,因为背包容量小于第 i 中物品的重量时,肯定是无法装入第 i 种物品的,因此没有意义要跳过。那么状态转移方程则会进行比较,比较在装入第 i 种物品后,还是不装入第 i 种物品哪一个会使背包的价值更大,这就是动态控制背包价值的关键。
比如 dp[1][2]=max(dp[0][2-weight[1]]+value[1],dp[0][2]),其中weight[1]=2,value[1]=6,显然dp[1][2]=6
那么 dp[2][3]=max(dp[1][3-weight[2]]+value[2],dp[1][3]),其中weight[2]=3,value[2]=3,而dp[1][3]=6,dp[1][0]=0,那么综合比较后,因为6>3,最终会选择不装物品2,装物品1,即dp[2][3]=6,这就是动态规划的核心,循环结束后一定能够保证背包的每一个状态都是最大价值。
二、一道例题
取自hdu 2602 “骨头收集者”
“骨头收集者”带着体积为V的背包去捡骨头,已知每个骨头的体积和价值,求能装进背包的最大价值。N≤1000,V≤1000。
输入:第1行是测试数量,第2行是骨头数量和背包体积,第3行是每个骨头的价值,第4行是每个骨头的体积。
1
5 10
1 2 3 4 5
5 4 3 2 1
输出:最大价值
14
三、具体代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin>>n;
while(n--)
{
int num,v;
cin>>num>>v;
int bone_value[100]; //骨头对应的价值
int bone_weight[100]; //骨头对应的重量
int dp[100][100]; //dp数组,行坐标代表当前骨头的种类,列坐标代表当前背包的容量,数组存放最大的价值
for(int i=1;i<=num;i++)
{
cin>>bone_value[i];
}
for(int i=1;i<=num;i++)
{
cin>>bone_weight[i];
}
for(int i=1;i<=num;i++)
{
for(int j=bone_weight[i];j<=v;j++)
{
//装入当前这个骨头,或者不装,选取能够产生价值最大的一种情况
dp[i][j]=max(dp[i-1][j-bone_weight[i]]+bone_value[i],dp[i-1][j]); //状态转移方程
}
}
cout<<dp[num][v]<<endl;
}
return 0;
}