01背包问题
有 N件物品和一个容量是 V的背包。每件物品只能使用一次。
第 i件物品的体积是 v[i],价值是 w[i]。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。 输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 v[i],w[i],用空格隔开,分别表示第 i件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
输入样例:
4 5
1 2
2 4
3 4
4 5
输出样例:
8
首先要明确状态转移方程
dp[i][j]表示从前i个物品中选择,使得总体积不超过j的最大价值
我们要考虑如何将目前的状态用前面已经计算过的状态来更新
对于第i个物品,有两种状态:选与不选
如果不选第i个物品,那么dp[i][j] = dp[i-1][j]
如果选择第i个物品, 那么dp[i][j] = dp[i-1][j-v[i]] + w[i] (要判断if(j>v[i]))
#include <iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int n,V;
int dp[N][N]; //dp[i][j]表示从前i个物品中选择,总体积不超过j时,最大的价值
int main()
{
cin>>n>>V;
for(int i=1 ; i<=n ; i++)
{
cin>>v[i]>>w[i];
}
for(int i=1 ; i<=n ; i++)
for(int j=0 ; j<=V ; j++)
{
dp[i][j] = dp[i-1][j]; //不选第i个物品
if(j>=v[i]) dp[i][j] = max(dp[i][j] , dp[i-1][j-v[i]]+w[i]);
}
cout<<dp[n][V];
return 0;
}
在上面的代码的第17、18行中我们发现 i 的状态只和 i-1 的状态有关,可以考虑将数组优化为一维
考虑一下这个循环,我们需要将它优化为一维
for(int i=1 ; i<=n ; i++)
for(int j=0 ; j<=V ; j++)
{
dp[i][j] = dp[i-1][j]; //不选第i个物品
if(j>=v[i]) dp[i][j] = max(dp[i][j] , dp[i-1][j-v[i]]+w[i]);
}
那我们直接将第一维删掉会怎样?
for(int i=1 ; i<=n ; i++)
for(int j=0 ; j<=V ; j++)
{
if(j>=v[i]) dp[j] = max(dp[j] , dp[j-v[i]]+w[i]);
}
当我们枚举到 i 的时候,会枚举所有的 0<=j<=V , 也就是数组dp[i][j] , 此时i固定,那就只考虑j, 也就是dp[j]
当枚举到 i 的时候,dp[j] (j从0到V),存的是什么呢, 是枚举i-1时计算的值, 也就是dp[i-1][j]
那么上面的第二层循环就有问题了
计算dp[j]的时候需要用到dp[j-v[i]],在二维的时候是计算dp[i][j]需要用到dp[i-1][j-v[i]]
每次枚举j,就意味着dp[j]被更新为了从前 i 个物品中选择,也就是dp[i][j].
如果我们从小到大枚举j,那么前面的dp[j]就被更新了, 而后面的dp[j]是需要用i-1状态下的dp[j-v[i]]更新的, 此时的dp[j-v[i]]被更新为了i状态下,因此j的枚举应该从大到小.
#include <iostream>
using namespace std;
const int N = 1010;
int v[N],w[N];
int n,V;
int dp[N]; //dp[j]在每次枚举i时,表示从前i个物品中选择,总体积不超过j时,最大的价值
int main()
{
cin>>n>>V;
for(int i=1 ; i<=n ; i++)
{
cin>>v[i]>>w[i];
}
for(int i=1 ; i<=n ; i++)
for(int j=V ; j>=0 ; j--)
{
if(j>=v[i]) dp[j] = max(dp[j] , dp[j-v[i]]+w[i]);
}
cout<<dp[V];
return 0;
}