目录
(二)优化一维
一、01背包问题
例题
首先分析这个题目,用y总的背包问题分析图解
(一)二维
然后我们来看这个题目,它有i个物品,物品体积为v[i],价值为w[i],
用f[i][j]来表示,从前i种物品中选择体积不大于 j 的物品,使得价值达到最大,f[i][j]存储的就是这个最大值。
然后我们来看怎么去计算,即怎么区划分最大值
每个物品都是不同的,并且注意它们任何一个都只能被选择一次,
那我们来看看,目前的的集合是如何由上一个集合转换过来的
设上一个集合为f[i-1][j]
那么现在的集合是f[i][j]
对于第i个物品,由于只能选择一次,那么我就只有选或者不选
(1)如果不选取第i个物品
那么f[i][j]就是f[i-1][j],两者表示的最大值就是相同的
(2)如果选择第i个物品
那么现在的f[i][j] 就是f[i-1][j-v[i]]+w[i]
用j-v[i]直接将v[i]的体积减去,就表示必定选择,注意要加上i的价值w[i]
最后只要比较(1)和(2)的大小就可以求解了
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i])
但是有一个问题,就是我能选择第i个物品的前提是我能放得下,
就是j-v[i]>=0
所以在(2)的前面要加上条件j>=v[i]
那么我们的代码就是
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int v[N],w[N];//分别表示第i物品的体积和价值
int f[N][N];//表示前i个物品,在体积不超过j的情况下的最大值
int main()
{
int n,m;//输入n个物品 总体积为m
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++)//对n个物品从1到n一次枚举
for(int j=0;j<=m;j++)//体积在每个j下 能达到价值的最大值
{
if(j<v[i]) f[i][j]=f[i-1][j];
else f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
printf("%d",f[n][m]);
return 0;
}
(二)优化
如何去优化呢?
那就去看如何在划分集合的时候做优化
这是我们现在的代码
for(int i=1;i<=n;i++)//对n个物品从1到n一次枚举
for(int j=0;j<=m;j++)//体积在每个j下 能达到价值的最大值
{
if(j<v[i]) f[i][j]=f[i-1][j];
else f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
}
从二维到一维 那我们去掉第一维也就是和i有关的,那么就是
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
{
if(j<v[i]) f[j]=f[j];
else f[j]=max(f[j],f[j-v[i]]+w[i]);
}
现在可以发现f[j]=f[j]就是一个恒等式,可以直接去掉
现在就剩下这个 f[j]=max(f[j],f[j-v[i]]+w[i]);执行的条件是j>=v[i],
那我们把条件放到循环上去就得到
for(int i=1;i<=n;i++)
for(int j=v[i];j<=m;j++)
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
可是现在我们再去看循环发现了一个问题,就是j-v[i]小于j[i],那么j-v[i]会先被执行
那会发生什么样的问题呢,
我们来看一开始对f[i][j]的更新方式
f[i][j]是由f[i-1][j]更新而来,即使上一个状态更新而来,
f[i][j]与f[i-1][j]的区别就是第i个物品
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
里面的f[i-1][j-v[i]]+w[i]]代表的就是对第i个物品的更新
如果表示成f[j]=max(f[j],f[j-v[i]]+w[i])代表的就是对第i个物品的更新 (1)
但是现在f[j]=max(f[j],f[j-v[i]]+w[i]);
j-v[i] < j
那么如果从小到大遍历
f[j-v[i]]就会先被更新,再用来更新f[j];
设现在k=j-v[i]
f[k]=max(f[k],f[k-v[i2])
也就是说k由i2更新而来 即 f[j-v[i]]代表对第i2个物品的更新与(1)矛盾
那怎么办呢
我们发现只要让f[j-v[i]]后于f[j]更新即可
所以只要在遍历j的时候从大到小遍历即可
得到最终代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
int v[N],w[N];
int f[N];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d%d",&v[i],&w[i]);
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--)//在这里让遍历逆序
{
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
printf("%d",f[m]);
return 0;
}
题目来源acwing
模板来源:闫学灿
链接:https://www.acwing.com/activity/content/code/content/57785/
来源:AcWing