直接上题
先说朴素的想法
我认为动态规划的题目,都是要用到每一步的答案,将每一步的答案作为解题的线索
那么题目让我们考虑有四个物品,分别告诉了你四个物品的体积和价值,且每个物品的数量只有一个
让你利用这些条件找出一种物品序列,使其放入容量为5的背包后的价值最大
那我直接搬出朴素的解法
就是创建一个二维数组f[ i ][ j ]
i表示物品的编号,j表示背包的容量,每一个f的值就代表往容量为j的背包中放入1~i号物品所能达到的最大价值
那么创建好这个二维数组之后,就要开始对它进行赋值
赋值的顺序是从左到右,从上到下
赋值的方法就是比如你现在准备给f[1][2]赋值
就是往容量为2背包中放编号为1的物品最大能达到的价值是多少
那么你首先就要考虑1的体积是否要比2大,显然如果比2还大,那么就放不下,f[1][2]的值就为0,如果放得下,那么就是往里放一个1号物品,此时背包总价值为2,且因为1号物品已经用完,没有物品可放,所以得到容量为2背包中放编号为1的物品最大能达到的价值就是2,将
f[1][2]的值置为2即可
那么这样一来,通过这样的方法我们就能将第一行中的所有空位全部赋值成功
也就是说,我们得到了容量从0~5背包放入编号为1的物品分别所能达到的最大价值
接下来,从第二行继续
假设我们现在想赋值的是f[2][2],我们发现此时背包容量可以放入物品2,那么问题就来了,我们到底是放入物品2使得背包的价值最大,还是不放入物品2背包的价值最大呢?
这里我们注意一个点,如果我们此时选择放入物品2,那么我们该如何使得放入物品2之后的背包价值最大呢?
根据我们之前说的,我们每一个f[i][j],意思都是当前容量下放0~i号物品所能达到的最大价值,那么也就是说,我们放入物品2之后,背包的容量此时应该变为减去物品2的体积,我们用
f[2-1][j-物品2的体积]就可以表示在放入物品2之后剩余背包的所能达到的最大价值了,那么放入物品2所能达到的最大价值就是——物品2的价值+f[2-1][j-物品2的体积]([2-1]是因为物品2只有一个,已经被用掉了,接下来只剩下物品1了)
这样一来,我们用 物品2的价值+f[2-1][j-物品2的体积] 与不放入物品2背包的最大价值——也就是f[2-1][j]相比,两者谁更大,f[2][2]就等于谁
那么朴素思想差不多就这样,最大的价值显然就是二维数组的最左下角的一项f[n][m]
#include<stdio.h>
struct bag{
int v,w;
}number[1005];
int main()
{
/*创建一个二维数组,这个二维数组的横坐标用来表示物品
编号,纵坐标用来表示背包容量*/
int i,j;
scanf("%d%d",&i,&j);
int a[i+1][j+1];
int b[i+1];
number[0].v = number[0].w = 0;
for(int i_ = 1;i_ < i+1;i_++)
scanf("%d%d",&number[i_].v,&number[i_].w);
for(int i_ = 0;i_ < i+1;i_++){
for(int j_ = 0;j_ < j+1;j_++)
a[i_][j_] = 0;
}
/*对数组进行赋值,从左至右,从上至下*/
for(int i_ = 0;i_ < i+1;i_++){
for(int j_ = 0;j_ < j+1;j_++){
if(i_ == 0||j_ == 0)
a[i_][j_] = 0;
else if(number[i_].v > j_){//当前装入物品装不下
a[i_][j_] = a[i_-1][j_];
}else{//装得下
if((number[i_].w+a[i_-1][j_-number[i_].v])>(a[i_-1][j_]))
a[i_][j_] = number[i_].w+a[i_-1][j_-number[i_].v];
else
a[i_][j_] = a[i_-1][j_];
}
}
}
printf("%d",a[i][j]);
}
说完朴素法,我们有没有方法对其进行优化呢?
显然是有的,我们可以将二维数组压缩成一维数组
为什么可以这样,因为我们发现
在对二维数组进行赋值的时候,我们如果不放入i物品,那么空就等于当前背包容量下放0 ~ i-1号物品的最大价值,如果我们决定放i物品,那么空就等于背包容量减去i后放0~i-1号物品的最大价值+当前物品的价值
即我们进行赋值时,压根就没有用到当前空的上一排之外的数据,所以我们可以将二维数组设置成一个滚动的一维数组,节省空间
#include<stdio.h>
int value[1000],volume[1000],dp[1000];
int main()
{
int n,m,i,j;
scanf("%d%d",&n,&m);
for(i = 1;i <= n;i++)
scanf("%d%d",&volume[i],&value[i]);
for(i = 1;i <= n;i++)
for(j = m;j >= volume[i];j--){
if(dp[j-volume[i]]+value[i] > dp[j])
dp[j] = dp[j-volume[i]]+value[i];
}
printf("%d",dp[m]);
}
注意一下for(j = m;j >= volume[i];j - -)
为什么需要逆序更新滚动数组呢j = m
想一想,因为如果你正序更新,首先如果放不下i物品,那么前面背包容量小于i物品体积的值都为头顶上第i-1行的值,那么一旦背包容量增大到可以装下物品i,判断是否需要更新数值的不等式用到的数据就是你已经更新过的数据(也就是第i行的数据),显然与我们用第i-1行数据的想法相违背
那么如果逆序更新数组,那么用到的就都是第i-1行的数据进行更新判断了
j >= volume[i]
意思就是如果背包容量都装不下i物品,那么数组值就是不放i物品的值(也就是等于第i-1行的值),不需要更新了