P8118 Mystery 题解
题解:P8118 Mystery
前言
朋友在 PJudge 上做题时看到了这道题的原题(或者这是那道题的原题?)并让我看一看,当时我的第一反应就是:这题绝对是贪心。思考良久,他却告诉我这道题有更加优雅的解法,而且这个 trick 我们都见过,它就是:Slope Trick。
前置知识:Slope Trick
介绍
这个 trick 是一种用来维护函数的方法,简单来说,它一般包括存储分段一次凸 / 凹函数最左边或最右边一段的函数和用数据结构(一般是堆)来维护分段一次凸 / 凹函数的分段点:如果一个分段点
举例而言,对于一个分段一次凸函数
我们用向堆中插入
乍一看似乎没有什么作用,它的能力其实建立在分段一次凸 / 凹函数的优秀性质上(以下全部用凸函数,凹函数具有同样或类似的性质):
- 两个凸函数相加还是凸函数,并且对于分段一次凸函数而言,它们的和的函数的分段点集合为两个函数分段点集合的并集,即设分段一次函数
F(i) 和G(i) 的分段点集合分别为S_F 和S_G ,则它们的和H(i) 的分段点集合S_H 为S_F \cup S_G 。 - 凸函数加一次函数还是凸函数。
这样我们就可以发现 Slope Trick 的一些作用了,比如说,它维护的正是分段一次函数的分段点,而且这么维护斜率的变化对于分段一次凸函数之间的相加十分方便。所以被它用来维护的函数一般有一下要求:
- 是连续的。
- 是分段一次函数。
- 是凸函数或凹函数。
操作
- 相加:直接把最左边的一次函数相加,分段点集合合并。
- 函数最值:用大根堆维护斜率为
0 的段左侧的分段点,即斜率为正数(凹函数为负数)时的分段点,用小根堆维护右侧的分段点,即斜率为负数(凹函数为正数)时的分段点。所以大根堆中元素个数为最左边一段一次函数的斜率的绝对值,并且如果一个凸函数最左边的一次函数斜率不为正,那大根堆就是空的,小根堆以及凹函数的情况同理。如果一个点a 的左侧斜率为x(x>0) ,右侧斜率为-y(y>0) ,那么大根堆存储x 个a ,小根堆存储y 个a 即可,凹函数同理。
更多的操作可以参考 @CCComfy 大佬的 这篇博客,有了这两条操作,我们已经可以做这道题(以及许多 Slope Trick 的题)了。
思路简述
这道题其实和 Slope Trick 的一道经典题(不算本题就有 5 倍经验)十分类似,我们将每个
接下来分析这道经典题的做法:
考虑 dp,设
由于取了前缀
插入之后,由于
代码
#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int N = 1e6 + 5;
typedef long long ll;
int n, k, t;ll ans, a[N];
priority_queue <ll> heap;
int main()
{
scanf("%d%d", &n, &k);
for(int i = 1;i <= n;i++)scanf("%lld", &a[i]), a[i] -= 1ll * i * k;
scanf("%d", &t);
for(int i = 1;i <= n;i++)
{
heap.push(a[i]);
if(a[i] < heap.top())
ans += heap.top() - a[i], heap.pop(), heap.push(a[i]);
if(!t)printf("%lld\n", ans);
}
if(t)printf("%lld\n", ans);
return 0;
}
鸣谢:CCComfy 大佬的 Slope Trick 详解。