现在小明有一个超大容量的箱子。有n个具有容量v,价值w的物品,他想要装下物品容量和不超过
箱子的前提下,使得总价值最大。你能帮他解决这个问题吗?请你输出这个最大值
输入格式
第一行一个正整数n
接下来n行,每行两个正整数v,w
输出格式
一行,一个正整数表示最大总价值
样例输入
5 20
8 87
7 10
2 10
15 76
13 45
样例输出
107
这是一个经典的dp问题,首先想到的是用dp思想来解决
第一种方法二维dp
状态f[i][j]定义:前i个物品,背包容量g下的最优解(最大价值)
当前的状态依赖于之前的状态,可以理解为从初始状态f[0][0] = 0开始决策,有 N
件物品,则需要 N次决 策,每一次对第 i件物品的决策,状态f[i][j]不断由之前的状态更新而来。
2.当前背包容量不够(j < v[i]),没得选,因此前 i个物品最优解即为前 i−1 个物品最优解:
对应代码:f[i][j] = f[i - 1][j]。
3.当前背包容量够,可以选,因此需要决策选与不选第 i个物品:
选:f[i][j] = f[i - 1][j - v[i]] + w[i]。
不选:f[i][j] = f[i - 1][j] 。
我们的决策是如何取到最大价值,因此以上两种情况取 max() 。
代码如下
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100;
const int NN = 100010;
int v[N];
int w[NN];
int dp[N][NN];
int main() {
int n, m;
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 = 1; j <= m; j++) {
if (j < v[i]) {
dp[i][j] = dp[i - 1][j];
}
else {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
}
}
}
cout << dp[n][m] << endl;
return 0;
}
此种方法时间复杂度和空间复杂度维nV,会超时,因此只能过60%的数据
第二种解法
我们可以对二维数组进行优化为一维数组
将状态f[i][j]优化到一维f[j],实际上只需要做一个等价变形。
为什么可以这样变形呢?我们定义的状态f[i][j]可以求得任意合法的i与j最优解,但题目只需要求得最终状态f[n][m],因此我们只需要一维的空间来更新状态。
(1)状态f[j]定义:N件物品,背包容量j下的最优解。
(2)注意枚举背包容量j必须从m开始。
(3)为什么一维情况下枚举背包容量需要逆序?在二维情况下,状态f[i][j]是由上一轮i - 1的状态得来的,f[i][j]与f[i - 1][j]是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。
(4)例如,一维状态第i轮对体积为3的物品进行决策,则f[7]由f[4]更新而来,这里的f[4]正确应该是f[i - 1][4],但从小到大枚举j这里的f[4]在第i轮计算却变成了f[i][4]。当逆序枚举背包容量j时,我们求f[7]同样由f[4]更新,但由于是逆序,这里的f[4]还没有在第i轮计算,所以此时实际计算的f[4]仍然是f[i - 1][4]。
(5)简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。
状态转移方程为:f[j] = max(f[j], f[j - v[i]] + w[i] 。
代码如下
#include<bits/stdc++.h>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
vector<int>f(m + 1);
for (int i = 1; i <= n; i++) {
int v, w;
cin >> v >> w; // 边输入边处理
for (int j = m; j >= v; j--)
f[j] = max(f[j], f[j - v] + w);
}
cout << f[m] << endl;
return 0;
}
此种方法时间和空间复杂度都为V,但空间复杂度8*10的七次方换算后大约为134MB,大于题目所给的空间。只能过90%的数据
第三种解法可以用DFS
定义一个backtrack函数接收三个参数:
1.idx表示当前考虑到第几个物品,cur_v表示当前已经装载的物品容量,cur_w表示当前已经获得的总价值。
2.对于每个物品,我们可以选择不装载或者装载,然后递归调用backtrack函数,继续考虑下一个物品。
3.当考虑完所有物品之后,如果当前已经装载的物品容量不超过箱子容量V,那么更新最大价值。最后输出最大价值即可。
代码如下
#include <iostream>
#include <vector>
using namespace std;
int n, V;
vector<int> v, w;
int ans = 0;
void backtrack(int idx, int cur_v, int cur_w) {
if (idx == n) {
if (cur_v <= V) {
ans = max(ans, cur_w);
}
return;
}
backtrack(idx + 1, cur_v + v[idx], cur_w + w[idx]);//选当前物品
backtrack(idx + 1, cur_v, cur_w); // 不选当前物品
}
int main() {
cin >> n >> V;
v.resize(n);
w.resize(n);
for (int i = 0; i < n; i++) {
cin >> v[i] >> w[i];
}
backtrack(0, 0, 0);
cout << ans << endl;
return 0;
}
时间复杂度为2的n次方空间复杂度为1;