背包问题
一、01背包问题
-
问题描述
有(N)件物品和一个容量是(V)的背包。每件物品只能使用一次。第(i)件物品的体积是(v_{i}),价值是(w_{i})。求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。 -
二维DP解法
状态表示:(f[i][j]) 表示前(i)个物品,总体积是(j)的情况下,总价值最大时多少。(res = max{f[n][0],....,f[n][v]})。
状态计算:(f[i][j] = max left{ f[i-1][j],f[i-1][j-v_i] + v_i ight})。直接不选第(i)个物品或者在能选第i个物品的情况下,选择第(i)个物品。
初始化:(f[0][0] = 0)
复杂度分析: 状态数是(n^2,), 转移是(Oleft(1 ight)),每个状态数只需要进行常数次计算。总共的时间复杂度是(Oleft(n^2 ight)),即(NV)的。空间的复杂度也是(NV)。 -
c++ 实现
#include<iostream>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N]; // 体积、价值
int dp[N][N];
int main(){
cin>>n>>m;
for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
for(int i = 1; i <= n; i ++) {
for(int j = 0; j <= m; j ++) {
dp[i][j] = dp[i-1][j];
if(j >= v[i]) dp[i][j] = max(dp[i][j],dp[i-1][j-v[i]] + w[i]);
}
}
int res = 0;
for(int i = 0; i <= m; i ++) res = max(res,dp[n][i]);
cout << res << endl;
return 0;
}
-
优化
由于(f[i][j])只与(f[i-1][..]) 相关,所有可以使用滚动数组。但实际上不需要滚动数组,只需要一维数组也可以实现。
(f[i])表示体积是(i)的情况下,最大价值是多少。 -
错误示例:
for(int i = 1; i <= n; i ++) {
for(int j = 0; j <= m; j ++) {
if(j >= v[i]) dp[j] = max(dp[j],dp[j-v[i]] + w[i]);
}
}
上面的错误在dp[j] = max(dp[j],dp[j-v[i]] + w[i])
这段代码。dp[j-v[i]]
表示的是dp[i][j-v[i]]
的状态而不是dp[i-1][j-v[i]]
的状态。那么需要思考如何才能状态是正确的。解决方法是体积(j)从大到小枚举。
- 优化后c++实现
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N],dp[N];
int main()
{
cin>>n>>m;
for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
for(int i = 1; i <= n; i ++) {
for(int j = m; j >= v[i]; j --) {
dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout << dp[m] << endl;
return 0;
}
不需要对(dp[j])进行遍历寻找(max),因为每一个(dp[j]都是由dp[j-v_i])转移而来,每一个(dp[j])保证的当前体积小于等于(j)下的最优,且(dp[j] >= dp[j-v_i]), 所以(dp[m]) 就是全局最优的答案。
如果要求解体积恰好是m的情况下,最大价值是多少。初始化需要f[0] = 0, f[1~m] = -INF