一:问题解析
有一个容量为W的背包,总共有N个物品,每个物品有两个属性,重量w[i[和价值v[i],需要选择一些物品放入背包,每个物品只能选择一次,使得在不超过背包容量的情况下,物品的总价值最大;
与完全背包的不同:每个物品只能选择一次;
二:二维dp数组实现
思路:
1.定义二维数组dp[i][j]:
i表示在前i个物品中选择,j表示此时背包的容量为j,dp[i][j]表示此状态下,背包能获得的最大价值;
2.构建状态转移方程:
(1):无法选择第i个物品(背包剩余容量不足(j<w[i])):
当前状态为:比较i个物品,背包容量为j;因为不选择该物品,所以当前状态最大价值等于上一个状态的最大价值;而上一个状态比当前状态物品少一个(i-1),容量相同(j),所以为dp[i][j]=dp[i-1][j];
(2):可以选第i个物品:如果不选:(同上)dp[i][j]=dp[i-1][j];
如果选:当前状态为:比较i个物品,背包容量为j;因为选择该物品,所以当前状态的最大价值等于上一个状态的最大价值,加上第i个物品的价值(v[i]);而上一个状态比当前状态物品少一个(i-1),背包容量为选择后的剩余容量(j-w[i]),所以dp[i][j]=dp[i-1][j-w[i]]+v[i];
(注意是j-w[i]不是j+w[i]啊啊啊啊!!!!!!!!!!!!!!!!!!)
然后 用max函数判断哪个情况的价值更大;
则得到状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]);
3.初始化:
在没有任何物品时(i=0),与背包容量为空(j=0)时,价值为0;
注:dp,w,v三个数组大小都开为n+1,这样可以直接用dp[0]来表示背包容量为0的最大价值,不用再对边界特殊处理,遍历时直接从i=1,j=1开始即可!
代码:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int N = 0, W = 0;
cin >> N >> W;
// n:物品数量 w:背包容量
vector<int> v(N + 1, 0), w(N + 1, 0);
// v:物品价值 w:物品重量
for (int i = 1; i <= N; i++)
{
cin >> v[i] >> w[i];
}
vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0));
for(int i = 1; i <= N; i++)
{
for (int j = 1; j <= W; j++)
{
if (j >= w[i])
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
else
dp[i][j] = dp[i - 1][j];
}
}
cout << dp[N][W] << endl;
return 0;
}
三:一维dp数组优化:
优点:节省空间;
思路:
1:构建一维数组dp[j]:j:背包剩余容量;dp[j]:此状态最大价值;
2:双层for循环:第一层循环遍历每一个物品,第二层倒序遍历背包容量;
3:状态转移方程:如果不选择该物体:与遍历到该物体之前的最大价值相同:dp[j]=dp[j];
如果选择该物体:当前最大价值等于选择该物体前,背包容量减少(j-w[i])时的最大价值相同;
所以:dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
重点:倒序遍历,确保每个物体只选择一次;
因为j-w[i]可能会与正在判断或已经判断的j值相同,导致dp[j]重复更新,即物品被重复选择;
代码:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int N = 0, W = 0;
cin >> N >> W;
// n:物品数量 w:背包容量
vector<int> v(N + 1, 0), w(N + 1, 0);
// v:物品价值 w:物品重量
for (int i = 1; i <= N; i++)
{
cin >> v[i] >> w[i];
}
vector<int> dp(W + 1, 0);
for (int i = 1; i <= N; i++)
{
for (int j = W; j >= w[i]; j--)
{
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
cout << dp[W] << endl;
return 0;
}