【题解】小奇挖矿

题目描述

小奇要开采一些矿物,它驾驶着一台带有钻头(初始能力值 \(w\))的飞船,按既定路线依次飞过喵星系的 \(n\) 个星球。

星球分为 \(2\) 类:资源型和维修型。

  1. 资源型:含矿物质量 \(a_i\),若选择开采,则得到 \(a_i\times p\) 的金钱,之后钻头损耗 \(k\%\),即 \(p\leftarrow p\times (1-k\%)\)
  2. 维修型:维护费用 \(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 提出意见。感谢大家的资瓷哦!

猜你喜欢

转载自www.cnblogs.com/5ab-juruo/p/solution-20200221-explo.html