题解 P1064 【金明的预算方案】

Anguei

2018-02-23 19:59:32

Solution

### 这道题看起来比较难,但是如果整理好思路,非常简单。 ----- ## 基本思路 既然物品分为主件和附件两类,且每个主件最多包含两个附件,那么我们不妨枚举所有的主件。那么,对于每次枚举,会有五种情况: + 什么都不买 + 只买主件 + 买主件和第一个附件 + 买主件和第二个附件 + 买主件和两个附件 只要把这五种情况最终的价值算出来,取最大值就可以了。 ----- ## 如何开数组? 建立两个二维数组 $V_{65,3}$ 和 $P_{65,3}$,含义如题目描述。$V_{i, j}$ 和 $P_{i, j}$ 分别表示第 $i$ 个物品的第 $j$ 个附件的价格和重要度(当 $j = 0$ 时,表示主件)。 ----- ## 如何预处理数组? + 如果是主件,则令 $V_{i, 0}, P_{i, 0} = \_v, \_p$。 + 如果是物品 $\_q$ 的第 $j$ 个附件,则令 $V_{q,j}, P_{q,j} = \_v, \_p$。 ----- ## 动态转移方程 我们可以用 $F_j$ 表示花费钱数为 $j$ 时,价格与重要度乘积的最大值。 易知: + 当 $F_j \geqslant V_{i, 0}$ 时,$F_j = \max\{F_j, F_{j - V{i, 0}} + V_{i, 0} \times P_{i, 0}\}$ + 当 $F_j \geqslant (V_{i, 0} + V_{i, 1})$ 时,$F_j = \max\{F_j, F_{j - V{i, 0} - V_{i, 1}} + V_{i, 0} \times P_{i, 0} + V_{i, 1} \times P_{i, 1}\}$ + 当 $F_j \geqslant (V_{i, 0} + V_{i, 2})$ 时,$F_j = \max\{F_j, F_{j - V{i, 0} - V_{i, 2}} + V_{i, 0} \times P_{i, 0} + V_{i, 2} \times P_{i, 2}\}$ + 当 $F_j \geqslant (V_{i, 0} + V_{i, 1} + V_{i, 2})$ 时,$F_j = \max\{F_j, F_{j - V{i, 0} - V_{i, 1} - V_{i, 2}} + V_{i, 0} \times P_{i, 0} + V_{i, 1} \times P_{i, 1} + V_{i, 2} \times P_{i, 2}\}$ 列出了状态转移方程,套背包公式就好了 ----- ## 代码简化 **难道你真的要在 $\texttt{for}$ 循环内部写如此麻烦的数组下标吗?不怕写晕自己吗?** 对于这种数组下标很乱的状态转移方程,最好把下标写短一点。怎么写短呢? 观察一下刚才列出的状态转移方程,发现 $V_{i, j1} + V_{i, j2}$ 等下标被写了很多遍。不妨使用函数来计算这些值,在下标里面写函数调用。不妨: + 定义 $\texttt{cost2(int x, int y)}$ 计算 $V_{i, x} + V_{i, y}$、 + 定义 $\texttt{cost3(int x, int y, int z)}$ 计算 $V_{i, x} + V_{i, y} + V_{i, z}$、 + 定义 $\texttt{rpp(int x)}$ 计算 $V_{i, x} \times P_{i, x}$ 于是,状态转移方程可以简化成这样: + 当 $F_j \geqslant V_{i, 0}$ 时,$F_j = \max\{F_j, F_{j - V{i, 0}} + \texttt{rpp(0)}\}$ + 当 $F_j \geqslant \texttt{cost2(0, 1)}$ 时,$F_j = \max\{F_j, F_{j - \texttt{cost2(0, 1)}} + \texttt{rpp(0)} + \texttt{rpp(1)}\}$ + 当 $F_j \geqslant \texttt{cost2(0, 2)}$ 时,$F_j = \max\{F_j, F_{j - \texttt{cost2(0, 2)}} + \texttt{rpp(0)} + \texttt{rpp(2)}\}$ + 当 $F_j \geqslant \texttt{cost3(0, 1, 2)}$ 时,$F_j = \max\{F_j, F_{j - \texttt{cost3(0, 1, 2)}} + \texttt{rpp(0)} + \texttt{rpp(1)} + \texttt{rpp(2)}\}$ 这样就显得好多了。 ----- ## 完整 AC 代码 ```cpp //【P1064】金明的预算方案 - 洛谷 - 100 #include <iostream> #include <algorithm> const int kMaxN = 32000, kMaxM = 60; // 常量名前带 k,符合命名规范 int v[kMaxM + 5][3], p[kMaxM + 5][3]; int f[kMaxN + 5]; int main() { int n, m; std::cin >> n >> m; for (int i = 1; i <= m; ++i) { int _v, _p, _q; std::cin >> _v >> _p >> _q; if (!_q) { // 是主件 v[i][0] = _v; p[i][0] = _p; } else { if (v[_q][1] == 0) { // 是第一个附件 v[_q][1] = _v; p[_q][1] = _p; } else { // 是第二个附件 v[_q][2] = _v; p[_q][2] = _p; } } } for (int i = 1; i <= m; ++i) for (int j = n; j >= 0; --j) { // 用 lambda 表达式代替函数定义(C++11 偷懒专用) auto cost2 = [v, p, i](int x, int y) { return v[i][x] + v[i][y]; }; auto cost3 = [v, p, i](int x, int y, int z) { return v[i][x] + v[i][y] + v[i][z]; }; auto rpp = [v, p, i](int x) { return v[i][x] * p[i][x]; }; if (j >= v[i][0]) // 够买主件 f[j] = std::max(f[j], f[j - v[i][0]] + rpp(0)); if (j >= cost2(0, 1)) // 还够买第一个附件 f[j] = std::max(f[j], f[j - cost2(0, 1)] + rpp(0) + rpp(1)); if (j >= cost2(0, 2)) // 还够买第二个附件 f[j] = std::max(f[j], f[j - cost2(0, 2)] + rpp(0) + rpp(2)); if (j >= cost3(0, 1, 2)) // 还够买两个附件 f[j] = std::max(f[j], f[j - cost3(0, 1, 2)] + rpp(0) + rpp(1) + rpp(2)); } std::cout << f[n] << std::endl; } ```