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

heidoudou

2018-08-20 20:46:27

Solution

0-1 背包问题的一个变种。 但还是遵循背包问题动态规划的思路。 关键的关键是理解 0-1 背包问题的递归方程: $dp(n, w) = \max{(dp(n - 1, w), dp(n - 1, w - w_n) + v_n)}$ 1. 需要对所有可能的 $1...W$ 计算,而不仅仅是对题目中的约束 $W$ 计算。 2. 一旦确定了 $dp(n, w)$, 那么就可以确定,在 $(n, w)$ 这个子问题中的最优解就是 $dp(n, w)$。 为什么强调第二点,是因为这一题中有一些迷惑人的地方:买了主件之后,到底是买附件呢?还是买附件呢?还是买附件呢? 如何考虑呢? 如果第 $n$ 个物品是一个主件,需要确定在各种 $w$ 情况下的最优解。 同时 每个 $dp(n, w)$ 有很多种选择 0. 不买, $dp(n - 1, w)$ 1. 只买主件 $n$, $dp(n - 1, w - w_n) + v_n$ 2. 买一个主件 + 1 个附件, (a, b, c, d....) 3. 买一个主件 + 2 个附件, (a, b) (a, c) (a, d) (b, c) ... 找出来所有这些情况的最大值,也就是最优解。买不买附件由最优解决定。这个最优解告诉我们,在约束为 $w$ 的情况下, 从 $n$ 个物品中选择的最优解就是 $dp(n, w)$,**已经考虑了所有的情况**。 关于这个,想吐槽一下题目出的其实不严谨,或者可以更难一些: 谁规定每个主件如果有附件,则只有两个附件了? 可以只有一个附件,或者有七八个附件。 对于这种组合问题,有一个很好的解决方法: 1. 开始的时候只有一个主件,一种组合: `{1}`; 2. 有一个附件 2,组合实际上就是, `{1}, {1,2}` 3. 又有一个附件 3, 组合是 `{1}, {1, 2}, {1, 3}, {1, 2, 3}`。 什么规律? 其实就是在上一个组合集中,每种组合添加一个 3。 4. 同理如果有更多的附件4, 对上一个组合集,每个组合中添加 4,增加4个组合, 构成 8个组合。 当然可以在添加过程中,限制每种组合的数量。 所以我用了 `vector` 变长数组来保存每个组合集。每次遇到新的附件,在原基础上添加就行了。很方便。 剩下的就是背包模板套路了。 最后注意一个优化问题: 背包动规的算法复杂度为 $O(nW)$, 既然题目中所有 $w$ 是 10的倍数,那么所有 $w$ 自除10, 就可以将时间降低一个数量级!!实测很有效: 从 100多ms变成 37ms。 ```cpp using namespace std; struct object { int v; int vw; int num; object(int v, int vw, int n) : v(v), vw(vw), num(n) {} }; void attach(vector<object> &combo, int v, int vw) { int n = combo.size(); for (int i = 0; i < n; ++i) if (combo[i].num < 2) combo.push_back(object(combo[i].v + v, combo[i].vw + vw, combo[i].num + 1)); } void update_dp(int dp[], vector<object> &combo, int n) { vector<object>::iterator it; int i, best; for (i = n; i > 0; --i) { best = dp[i]; for (it = combo.begin(); it != combo.end(); ++it) if (i >= it->v) best = max(best, dp[i - it->v] + it->vw); dp[i] = best; } } int main() { const int MAX_M = 70; // 最大物品数量 const int MAX_N = 3201; // 最大钱数 / 10 vector<object> objs[MAX_M]; int n, m; // n 钱数上限; m 物品数量 scanf("%d %d", &n, &m); n /= 10; int v, w, id; for (int i = 1; i <= m; ++i) { scanf("%d %d %d", &v, &w, &id); if (!id) objs[i].push_back(object(v / 10, v / 10 * w, 0)); else attach(objs[id], v / 10, v / 10 * w); } int dp[MAX_N] = { 0 }; for (int i = 1; i <= m; ++i) if (objs[i].size() > 0) update_dp(dp, objs[i], n); printf("%d", dp[n] * 10); return 0; } ```