最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
一共有 $n$ 种奖品,$m$ 元现金
对于第 $i$ 种奖品,其 价格 为 $v_i$,价值 为 $w_i$,数量 为$s_i$
制定一个购买 方案,使得该方案的总价值 最大
分析
物品个数为 $n$,总体积为$m$,初步识别是一个 背包问题
观察到每个物品有 数量限制,断定该题是 多重背包问题
本题是一道 多重背包 的裸题
本篇题解,只展示 多重背包 的朴素优化和分析,关于 单调队列优化 可以看这篇博客
AcWing 6. 多重背包问题 III【单调队列优化+图示】
不多废话,我们直接上 闫氏DP分析法
闫氏DP分析法
初始状态:f[0][0]
目标状态:f[n][m]
Code(朴素写法)
时间复杂度:$O(n \times m \times s)$
空间复杂度:$O(n \times m)$
#include <iostream>
using namespace std;
const int N = 510, M = 6010;
int n, m;
int v[N], w[N], s[N];
int f[N][M];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++ i) cin >> v[i] >> w[i] >> s[i];
for (int i = 1; i <= n; ++ i)
{
for (int j = 0; j <= m; ++ j)
{
for (int k = 0; k <= s[i]; ++ k)
{
if (j - k * v[i] >= 0)
{
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
}
}
}
}
cout << f[n][m] << endl;
return 0;
}
朴素优化方式
同 01背包 ,对于第 i 阶段的状态更新只会用到第 i-1 阶段的状态
因此可以采用 滚动数组 或者根据状态更新的顺序,直接压缩成 一维 的优化方式
时间复杂度:$O(n \times m \times s)$
空间复杂度:$O(m)$
一维优化方式
#include <iostream>
using namespace std;
const int N = 510, M = 6010;
int n, m;
int v[N], w[N], s[N];
int f[M];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++ i) cin >> v[i] >> w[i] >> s[i];
for (int i = 1; i <= n; ++ i)
{
for (int j = m; j >= v[i]; -- j)
{
for (int k = 0; k <= s[i]; ++ k)
{
if (j - k * v[i] >= 0)
{
f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
}
}
}
}
cout << f[m] << endl;
return 0;
}
还是有个小小的疑问,就是如何保证买到的物品数量恰好是n呢?
没要求吧
状态转移分析的时候 不选i的时候还是f[i-1,j] 到了写代码的时候就是f[i,j]了。。。
因为在k == 0的时候就已经把f[i - 1][j]的值给f[i][j]了
因为在k == 0的时候就已经把f[i - 1][j]的值给f[i][j]了
因为在k == 0的时候就已经把f[i - 1][j]的值给f[i][j]了
因为在k == 0的时候就已经把f[i - 1][j]的值给f[i][j]了
Orz
请教一下,为什么二维的j 需要从0开始,不可以从v[i]开始呢?而一维的却是到v[i]结束?
第一个问题,和转移需要的状态有关,这个不知道的话,可以复习一下01背包优化
第二个问题,因为选 $0$ 个可以不用更新,所以到
v[i]
结束谢谢巨巨大佬,我去复习下~
第一个问题 因为 dp[i][0]到v[i]还需要被dp[i-1][0]给赋初值
第二个问题,因为一维数组的状态每一轮都是由上一轮先更新然后就不需要在更新那些小于v[i]的情况
铅笔哥 tql!
光头哥 tql!