P4912 帕秋莉的魔法 题解
本题是处理 含有负值的01背包 的【模板】
题目翻译:
\(n\) 个物品,\(m\) 的初始资金,对于每个物品 \(i\) 有 \(cost[i]\) 的花费与 \(value[i]\) 的收益,而且我们只能按照输入的先后顺序选择物品,并且对于每次选择物品,会获得 \(w[i][j]\) 的增益,一并加在价值当中。
我们显然可以用 \(f[i][j]\) 唯一确定一种状态:考虑前 \(i\) 个物品时,用 \(j\) 的代价得到的最大收益表示为 \(f[i][j]\)。这个只要是做了背包的同学都没有问题。(应该没有人把水紫当成 采药 来做吧)
每个物品只有选或不选两种可能。不选则保持原状态。选则考虑从谁转移而来。题目要求按输入的顺序,所以在第 \(i\) 个的时候可以考虑 \([0,i)\) 的情况,我们用变量 \(k\) 实现枚举。所以 \(f[i][j]\) 可以由 \(f[k][j']\) 得来,而 \(j'+cost[i]=j\) 所以得到如下状态转移方程:
\(f[i][j] = \max(f[i][j], f[k][j-cost[i]]+value[i]+w[k][i])\) 。
重点来了:处理负数 。
由于 冰冷昂贵入云涉水的 Cpp 语言不支持数组下标为负,所以使用平移的方式转正,平移的量取决于最小的负值:\(-2500\) ,所以设 :
cosnt int mov = 2501; // move 为一个 Cpp 函数
。
-
转移边界。本身边界在 \(f[0][0]\) 位置,表示 没有物品没有花费 的情况,应该初始化为 \(0\),但现在平移了 \(mov\),所以 \(f[0][mov]=0\)。
-
循环条件。现在把 \(mov\) 看成零,因为 \(cost\) \(value\) \(w\) \(m\) 都有可能是负值,所以我们必须考虑很小到很大的值。在转移中有 \(j-cost[i]\) 这一步,所以 \(j>=cost[i]\) 这是起码的。\(j\) 的最大值就是当 \(m==2500\) 的时候可以取到 \((mov<<1)+1\) 的值。所以调的最久的
for(int j=cost[i];j<=(mov<<1)+1;++j)
已经考虑完毕了。
-
初始化以及输出。状态转移中使用了 \(\max(f[i][j],...\) ,所以 \(f[i][j]\) 必须初始化为一个极小值,个人喜欢
const ll INF = 0x3f3f3f3f3f3f3f3f
,但其实不开 龙龙 用 \(int\) 都没有关系。输出的时候我们要找的是所有花费为 \(m+mov\) 的情况中收益最高的,所以用 \(ans=-INF\) 然后遍历即可。
那么本篇题解就...
还没完呢!小子!
在循环条件中有一个必不可少的句子:
if(f[k][j-cost[i]]!=-INF)
为什么?因为他此时依靠的\(f[k][j-cost[i]]\) 必须是有意义的。他可能后来被 \(f[0][mov]\) 间接地赋上有意义的值,但是此时他的 -INF
并不是可以利用的,所以要排除掉。(本蒟蒻在这里 WA了两三次 ... )
顺带提一下,虽然用 vector 又慢又要手动处理边界,但是 vector 提供的 .at(i) 数组访问形式,在功能上和 [i] 一致,并且在数组越界的时候可以抛出异常,在 Run Time Error 的时候值得一试。
#include<bits/stdc++.h>
#define ll long longusing namespace std;const ll mov = 2501;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
vector<ll> cost, value;
vector<vector<ll>> w, f;void in() {scanf("%lld%lld", &n, &m);cost.resize(n + 1), value.resize(n + 1);w.resize(n + 1, vector<ll>(n + 1));f.resize(n + 1, vector<ll>(mov * 2 + 5000, -INF));for (int i = 1; i <= n; ++i)scanf("%lld%lld", &cost.at(i), &value.at(i));for (int i = 1; i <= n; ++i)for (int j = 1; j <= n; ++j)scanf("%lld", &w.at(i).at(j));return;
}
ll dp() {f.at(0).at(mov) = 0;for (int i = 1; i <= n; ++i)for (int j = cost[i]; j <= (mov<<1)+1; ++j)for (int k = 0; k < i; ++k)if (f[k][j - cost[i]] != f[0][0])f[i][j] = max(f[i][j], f[k][j - cost[i]] + value[i] + w[k][i]);ll ans = f[0][0];for (int i = 0; i <= n; ++i)ans = max(ans, f.at(i).at(m + mov));if (ans == f[0][0]) ans = -1;return ans;
}int main() {in();cout << dp();return 0;
}
华丽结束,希望能排到你的 WA 点。