• 【题解】小奇挖矿


    题目描述

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

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

    1. 资源型:含矿物质量 (a_i),若选择开采,则得到 (a_i imes p) 的金钱,之后钻头损耗 (k\%),即 (pleftarrow p imes (1-k\%))
    2. 维修型:维护费用 (b_i),若选择维修,则支付 (b_i imes p) 的金钱,之后钻头修复 (c\%),即 (pleftarrow p imes (1+c\%))

    (p) 为钻头当前能力值)

    注:维修后钻头的能力值可以超过初始值。

    请你帮它决策最大化这个收入。

    输入格式

    第一行 (4) 个整数 (n)(k)(c)(w)

    以下 (n) 行,每行 (2) 个整数 (type)(x)

    • (type)(1) 则代表其为资源型星球,(x) 为其矿物质含量 (a_i)
    • (type)(2) 则代表其为维修型星球,(x) 为其维护费用 (b_i)

    输出格式

    输出一行一个实数 (保留两位小数),表示要求的结果。

    数据范围

    测试时间限制 (1000 extrm{ms}),空间限制 (256 extrm{MiB})

    • 对于 (30\%) 的数据 (nle 100)
    • 对于 (50\%) 的数据 (nle 1000)(k=100)
    • 对于 (100\%) 的数据 (nle 10^5)(0le k,c,w,a_i,b_ile 100)

    保证答案不超过 (10^9)

    分析

    这道题的部分分设计不当,我们来考虑一下 Author 为什么这么设部分分。

    (30; mathtt{pts})

    注意到这题的钻头和金钱是两个变量,我们一定要控制一个。但是都是实数不好表示。怎么办?

    再次注意到这题的钻头耐久机制是乘法。由于乘法满足交换律和结合律,所以我们只要记录下开了几次矿,修了几次就行了。

    定义 (f_{i,j,k}) 为修理 (i) 次,开矿 (j) 次,目前已经到达第 (k) 个星球的最大金钱数。

    递推也好得到:(f_{i,j,k}=egin{cases}max{f_{i,j,k-1},f_{i,j-1,k-1}+a_i imes calc(i,j-1)}&k; ext{号星球是资源型}\max{f_{i,j,k-1},f_{i-1,j,k-1}-b_i imes calc(i-1,j)}&k; ext{号星球是修理型}end{cases})

    边界值:(f_{0,0,0}=0),其余赋值 (-infty)。最终求 (max{f_{i,j,n}})

    复杂度 (Theta(n^3))

    是不是已经有点不明所以,不知道如何优化了?接下来就会更加令人疑惑了。

    (50;mathtt{pts})

    这是福利的部分分,却是 AC 的坑。

    我们来看一下这 (50\%) 的数据。最引人注目的就是那个闪闪发光的——

    [Hugecolor{lightblue}{k=100} ]

    好了这波福利部分分应该怎么拿呢?

    考虑到这是用乘法进行计算,并且只要挖一次,钻头就会彻底报废。

    那么我们只要枚举修理的次数就可以了。复杂度可以降到 (Theta(n^2))

    (100;mathtt{pts})

    完了,现在没有福利保障了,(n) 的范围也大增,怎么搞呢?

    如果你有被部分分骗了的感觉,那你就能感受到此题 Author 的丑恶。

    我们回过头去想最开始的那个 DP。

    记得不?那个 (Theta(n^3)) 的 DP。

    这个复杂度为什么这么高?因为有钻头的变量在影响着我们。

    那么,我们要做一个大胆的操作,那就是把钻头的影响消除。

    这个怎么搞呢?我们来想一想:

    下面为了方便考虑,我们定义数列 ({c_i}=egin{cases}a_i&i; ext{号星球是资源型}\-b_i&i; ext{号星球是维修型}end{cases})

    (u_i=egin{cases}1-k\%&i; ext{号星球是资源型}\1+c\%&i; ext{号星球是维修型}end{cases})

    (r_i= ext{最优解是否选择第 }i ext{ 个。选了就是 }1 ext{,否则就是 }dfrac{1}{u_i})


    假设在最优解的情况下,经过第 (i) 个星球后,钻头的耐久成为了 (w_i)

    那么总共的钱数 (M=sum^n_{i=1}(w_i imes c_i))

    而对于 (w_i=w imes prod^i_{j=1}(u_j imes r_j))

    注意到只有 (r_i) 是不确定的。而不确定的 (r_i) 只会影响 (w_k(1le kle i))

    也就是说,任意一个星球的决策不会影响后面所有的决策

    本着 DP 没有后效性的原则,我们自然而然想出了一个操作,那就是——

    [Huge{ ext{从后往前做 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
    ", ans * wei);
    	
    	return 0;
    }
    

    后记

    我考场上时也没有想到正解。虽然朦朦胧胧知道要利用乘法这个性质。

    后来看到 solution 时真是自愧不如。

    又过了一阵子,我好不容易脑补出来这么一个想法的过程。自我感觉讲得很清楚。

    如果这篇题解对你有帮助,欢迎点赞、留言,给 5ab 提出意见。感谢大家的资瓷哦!

  • 相关阅读:
    可汗学院公开课:统计学
    libsvm 之 easy.py(流程化脚本)注释
    机器学习概览
    学习资源
    libsvm-3.21使用文档
    Machine Learning
    Machine Learning
    MySQL 5.7半同步复制after sync和after commit详解【转】
    网站架构设计【转】
    httpd功能配置之虚拟主机【转】
  • 原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-20200221-explo.html
Copyright © 2020-2023  润新知