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

· · 题解

这道题看起来比较难,但是如果整理好思路,非常简单。

基本思路

既然物品分为主件和附件两类,且每个主件最多包含两个附件,那么我们不妨枚举所有的主件。那么,对于每次枚举,会有五种情况:

只要把这五种情况最终的价值算出来,取最大值就可以了。

如何开数组?

建立两个二维数组 V_{65,3}P_{65,3},含义如题目描述。V_{i, j}P_{i, j} 分别表示第 i 个物品的第 j 个附件的价格和重要度(当 j = 0 时,表示主件)。

如何预处理数组?

动态转移方程

我们可以用 F_j 表示花费钱数为 j 时,价格与重要度乘积的最大值。 易知:

列出了状态转移方程,套背包公式就好了

代码简化

难道你真的要在 \texttt{for} 循环内部写如此麻烦的数组下标吗?不怕写晕自己吗?

对于这种数组下标很乱的状态转移方程,最好把下标写短一点。怎么写短呢?

观察一下刚才列出的状态转移方程,发现 V_{i, j1} + V_{i, j2} 等下标被写了很多遍。不妨使用函数来计算这些值,在下标里面写函数调用。不妨:

于是,状态转移方程可以简化成这样:

这样就显得好多了。

完整 AC 代码

//【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;
}