题目描述
小奇要开采一些矿物,它驾驶着一台带有钻头(初始能力值 \(w\))的飞船,按既定路线依次飞过喵星系的 \(n\) 个星球。
星球分为 \(2\) 类:资源型和维修型。
- 资源型:含矿物质量 \(a_i\),若选择开采,则得到 \(a_i\times p\) 的金钱,之后钻头损耗 \(k\%\),即 \(p\leftarrow p\times (1-k\%)\)
- 维修型:维护费用 \(b_i\),若选择维修,则支付 \(b_i\times p\) 的金钱,之后钻头修复 \(c\%\),即 \(p\leftarrow p\times (1+c\%)\)
(\(p\) 为钻头当前能力值)
注:维修后钻头的能力值可以超过初始值。
请你帮它决策最大化这个收入。
输入格式
第一行 \(4\) 个整数 \(n\),\(k\),\(c\),\(w\)。
以下 \(n\) 行,每行 \(2\) 个整数 \(type\),\(x\):
- \(type\) 为 \(1\) 则代表其为资源型星球,\(x\) 为其矿物质含量 \(a_i\);
- \(type\) 为 \(2\) 则代表其为维修型星球,\(x\) 为其维护费用 \(b_i\)。
输出格式
输出一行一个实数 (保留两位小数),表示要求的结果。
数据范围
测试时间限制 \(1000 \textrm{ms}\),空间限制 \(256 \textrm{MiB}\)。
- 对于 \(30\%\) 的数据 \(n\le 100\)
- 对于 \(50\%\) 的数据 \(n\le 1000\),\(k=100\)
- 对于 \(100\%\) 的数据 \(n\le 10^5\),\(0\le k,c,w,a_i,b_i\le 100\)
保证答案不超过 \(10^9\)。
分析
这道题的部分分设计不当,我们来考虑一下 Author 为什么这么设部分分。
\(30\; \mathtt{pts}\)
注意到这题的钻头和金钱是两个变量,我们一定要控制一个。但是都是实数不好表示。怎么办?
再次注意到这题的钻头耐久机制是乘法。由于乘法满足交换律和结合律,所以我们只要记录下开了几次矿,修了几次就行了。
定义 \(f_{i,j,k}\) 为修理 \(i\) 次,开矿 \(j\) 次,目前已经到达第 \(k\) 个星球的最大金钱数。
递推也好得到:\(f_{i,j,k}=\begin{cases}\max\{f_{i,j,k-1},f_{i,j-1,k-1}+a_i\times calc(i,j-1)\}&k\;\text{号星球是资源型}\\\max\{f_{i,j,k-1},f_{i-1,j,k-1}-b_i\times calc(i-1,j)\}&k\;\text{号星球是修理型}\end{cases}\)。
边界值:\(f_{0,0,0}=0\),其余赋值 \(-\infty\)。最终求 \(\max\{f_{i,j,n}\}\)。
复杂度 \(\Theta(n^3)\)。
是不是已经有点不明所以,不知道如何优化了?接下来就会更加令人疑惑了。
\(50\;\mathtt{pts}\)
这是福利的部分分,却是 AC 的坑。
我们来看一下这 \(50\%\) 的数据。最引人注目的就是那个闪闪发光的——
\[
\Huge\color{lightblue}{k=100}
\]
好了这波福利部分分应该怎么拿呢?
考虑到这是用乘法进行计算,并且只要挖一次,钻头就会彻底报废。
那么我们只要枚举修理的次数就可以了。复杂度可以降到 \(\Theta(n^2)\)。
\(100\;\mathtt{pts}\)
完了,现在没有福利保障了,\(n\) 的范围也大增,怎么搞呢?
如果你有被部分分骗了的感觉,那你就能感受到此题 Author 的丑恶。
我们回过头去想最开始的那个 DP。
记得不?那个 \(\Theta(n^3)\) 的 DP。
这个复杂度为什么这么高?因为有钻头的变量在影响着我们。
那么,我们要做一个大胆的操作,那就是把钻头的影响消除。
这个怎么搞呢?我们来想一想:
下面为了方便考虑,我们定义数列 \({c_i}=\begin{cases}a_i&i\;\text{号星球是资源型}\\-b_i&i\;\text{号星球是维修型}\end{cases}\);
\(u_i=\begin{cases}1-k\%&i\;\text{号星球是资源型}\\1+c\%&i\;\text{号星球是维修型}\end{cases}\);
\(r_i=\text{最优解是否选择第 }i\text{ 个。选了就是 }1\text{,否则就是 }\dfrac{1}{u_i}\)。
假设在最优解的情况下,经过第 \(i\) 个星球后,钻头的耐久成为了 \(w_i\)。
那么总共的钱数 \(M=\sum^n_{i=1}(w_i\times c_i)\)
而对于 \(w_i=w\times \prod^i_{j=1}(u_j\times r_j)\)
注意到只有 \(r_i\) 是不确定的。而不确定的 \(r_i\) 只会影响 \(w_k(1\le k\le i)\)。
也就是说,任意一个星球的决策不会影响后面所有的决策。
本着 DP 没有后效性的原则,我们自然而然想出了一个操作,那就是——
\[
\Huge{\text{从后往前做 DP}}
\]
好了,现在正解已经呼之欲出了。
定义 \(f_i\) 为 后 \(i\) 个星球的金钱最大值。
我们只需要假定当前钻头耐久为 \(1\),完成这一轮决策后再把这一轮的钻头初始耐久设为 \(1\)。
如此这般,最后再乘上 \(w\),就是答案。
当然,我们可以把 \(f_i\) 浓缩成一个数,节省空间。
Code
日常放代码。虽然这一次的题目已经没有什么代码难度了。
#include <cstdio>
#include <cctype>
using namespace std;
constexpr int max_n = 100000;
double val[max_n];
bool is_fix[max_n];
inline int read()
{
int ch = getchar(), n = 0, t = 1;
while (isspace(ch)) { ch = getchar(); }
if (ch == '-') { t = -1, ch = getchar(); }
while (isdigit(ch)) { n = n * 10 + ch - '0', ch = getchar(); }
return n * t;
}
inline double my_max(double x, double y) { return (x > y)? x:y; }
int main()
{
int n = read(), cost = read(), exp = read(), wei = read(), type, tmp;
double ans = 0;
for (int i = 0; i < n; i++)
{
scanf("%d%d", &type, &tmp);
val[i] = tmp;
is_fix[i] = (type == 2);
}
for (int i = n - 1; i >= 0; i--)
{
if (is_fix[i])
ans = my_max(ans, ans * (1 + exp / 100.0) - val[i]);
else
ans = my_max(ans, ans * (1 - cost / 100.0) + val[i]);
}
printf("%.2lf\n", ans * wei);
return 0;
}
后记
我考场上时也没有想到正解。虽然朦朦胧胧知道要利用乘法这个性质。
后来看到 solution 时真是自愧不如。
又过了一阵子,我好不容易脑补出来这么一个想法的过程。自我感觉讲得很清楚。
如果这篇题解对你有帮助,欢迎点赞、留言,给 5ab 提出意见。感谢大家的资瓷哦!
原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-20200221-explo.html