01背包
题目描述
核心思路
最大价值是物品数量i和背包容量j的函数。
设函数 f [ i ] [ j ] f[i][j] f[i][j]表示前i件物品放入容量为j的背包的最大价值。
那么最终的最大价值就是物品数量i从0增长到 n n n,背包容量j从0增长到 m m m时 f [ n ] [ m ] f[n][m] f[n][m]的值。
问题:为什么这里的i和j都是从1开始呢?
因为由表可知,当i和j为0时,表示没有一件物品,背包容量为0,所以值为0。因此,当i和j为0时,可以不用看,它的值肯定为0,又由于我们定义的f数组是全局的,所以初始化本来为0了。
填表过程如下:
当i=1时:
当i=2时:
当i=3时:
这里的时间复杂度和空间复杂度都是 O ( n m ) O(nm) O(nm),但是由图表可知,第i层的数据只会用到第i-1层的数据,而不会用到第i-1层之前的数据了。
我们可以去掉第一维,用一维数组 f [ j ] f[j] f[j]只记录一行的数据,考虑如果让j顺序循环,顺序更新 f [ j ] f[j] f[j]会怎么样呢?
如下图,可以发现当i=1时,j=6时,想要更新f[6]。其中f[6]=f[3]+5,但是这里用的f[3]是第i=1层时已经被更新过的f[3]=5,而不是用上一层第i-1层级第0层的f[3]=0,因此此时得到的f[6]=5+5=10。但是由左图可知,但i=1,j=6时,f值应该为5而不是10,所以当j顺序循环时得到的f值是错误的!
错误原因就在于j-w[i]<j,因此f[j-w[i]]会比f[j]先得到更新,由右边写法的else语句可知,那么就可能会用已经更新过后的f[j-w[i]]去更新f[j]的值,那么就会出错。
但是对比左侧的else写法可知,第i层的f值是由第i-1层的f值来更新的,因此如果去掉第一维后,如果j是顺序循环,那么就是用第i层已经被更新的f[j-w[i]]去更新第i层的f[j],这就错了。
正确做法:
终极写法:
这里把if语句去掉了,但是第二个for循环中,条件就是 j ≥ w [ i ] j\geq w[i] j≥w[i]了,不然 j − w [ i ] j-w[i] j−w[i]可能回是负数。
代码
朴素写法:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
//v是物品的体积 w是物品的价值
int v[N],w[N];
//f[i][j]表示前i件物品放入背包容量为j时的最大价值
int f[N][N];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&v[i],&w[i]);
//从第一件物品循环第n件物品
for(int i=1;i<=n;i++)//枚举物品的数量
{
for(int j=1;j<=m;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\n",f[n][m]);
return 0;
}
降为一维后:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
//v是物品的体积 w是物品的价值
int v[N],w[N];
int f[N];
int n,m;
int main()
{
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>=1;j--)//逆序更新
{
if(j<v[i])
f[j]=f[j];
else
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
printf("%d\n",f[m]);
return 0;
}
降为一维后的终极代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
//v是物品的体积 w是物品的价值
int v[N],w[N];
int f[N];
int n,m;
int main()
{
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\n",f[m]);
return 0;
}