面对如此问题:给定容器和物品求其容器中装入物品的最大价值。
我们通常想到的是贪心,但是贪心有局限,比如我们只能装入整块物体不能切割并且受到容器大小限制时贪心就会不适用。
这时DP就可以解决,我们称此类问题是背包问题
今天就从最为朴素的0-1背包开始。
已知N个不可拆分的物品,价值为v,体积为w,一个背包容量为m。
求背包中装入物品价值最大是多少。
首先来看,不可拆分明确是背包问题,对于背包问题中要放入背包的没一个物品都有“选”,“不选”两种选择。
不选的情况:
1.极端情况,体积过大放入超出背包容积
2.普遍情况,能装下,但需要将背包中前些个个物体给腾出去,你选择腾出去这个物体比你即将装入物体的价值大。
分析出如下规律
if(w>j)
dp[i][j]=dp[i-1][j-1]
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
关于:dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
这个式子需要好好说明
意思是当我们选择放入第i个物品时,则会牵扯到前i-1个物品(多个,并非第i-1一个),这时后我们要判断,是将前i-1个物品放在容量为j的背包中价值大,还是将前i-1个物品放入容量为j-w[i]的背包的价值再加上v[i]的价值大
关于标黄的这句话,其中将前i-1个物品放入容量为j-w[i]的背包中的价值
。注意,这个价值并不一定或者说绝大多数情况下,都不等同于前i-1个物品的价值!这实际上是指j-w[i]容量的背包能容纳的最大价值
==也正是在这里体现了动态规划的子问题之间答案的互相作用。
代码实现就是
#include <bits/stdc++.h>
using namespace std;
int n,m,dp[1000][1000];
struct obj
{
int v;
int w;
}obj[1000];
int key()
{
for(int i=0;i<n;i++)
{
for(int j=0;j<=m;j++)
/*居然有人问我为什么可以等于m,你买个包还带1个公摊面积是吧*/
{
if(obj[i].w>j)
{
dp[i][j]=dp[i-1][j-1];
}
else
{
dp[i][j]=max(dp[i-1][j],dp[i-1][j-obj[i].w]+obj[i].v);
}
}
}
return dp[n-1][m];
}
int main()
{
memset(dp,sizeof(dp),0);
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>obj[i].v>>obj[i].w;
}
return 0;
}
我们发现,dp实现的过程种总是在牺牲空间换取时间,那么我们怎么优化空间呢。
我们来自习分析一下
在放第i个东西牵扯的是前i-1个,而前i-1个物品的状态记录在dp[i-1][]里,现在需要的状态在dp[i][]里,那么我们是否需要的就只是第i-1行和第i行的空间呢?
我们发现,实际上虽然我们说是用的dp[i-1][j]的数据,但是如果转换成一维数组dp[j],那么不难发现我们上一层的数据其实就存储在dp[j]中,所以我们当前要求的数据利用dp[j]即可,于是就有了dp[j]=max(dp[j],dp[j-obj[i].w]+obj[i].v)
意思为:dp[j]为将目前背包中物品装入容量为j的背包价值大,还是装入j-obj[i].w容量的背包之后再加上obj[i].v的价值大。注意标黄的这句话,可以按照上面讲解二位0-1背包的内容理解。
#include <bits/stdc++.h>
using namespace std;
int n,m,dp[1000];
struct obj
{
int v;
int w;
}obj[1000];
int key()
{
for(int i=0;i<n;i++)
{
for(int j=m;j>=obj[i].w;j++)
/*居然有人问我为什么可以等于m,你买个包还带1个公摊面积是吧*/
{
dp[j]=max(dp[j],dp[j-obj[i].w]+obj[i].v);
}
}
return dp[m];
}
int main()
{
memset(dp,sizeof(dp),0);
cin>>n>>m;
for(int i=0;i<n;i++)
{
cin>>obj[i].v>>obj[i].w;
}
return 0;
}