【题解】小奇挖矿

题目描述

小奇要开采一些矿物,它驾驶着一台带有钻头(初始能力值 (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 提出意见。感谢大家的资瓷哦!

原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-20200221-explo.html