VEXOBEN
Vexoben
Sep 16, 2018
It takes 2 minutes to read this article.

题目链接: codeforces713C Sonya and Problem Wihtout a Legend

(刷新以获取数学公式)

题意

给定一组数,每次可以用的代价将一个数,求将数列变为严格上升的最小代价。

n ≤ 3000

题解

首先将变为就可以将题目转化为不严格上升。

然后注意到一个数变肯定变到原来有的中,直接上的DP就可以通过了

但这样没有什么技术含量。这题是有的做法的。

定义函数为将前个数变为严格上升,且保证的最小代价。我们有:

显然,是一个斜率为非正整数的下凸包。

是最小的使得取到最小值。那么就是斜率从的转折点,我们可以得到:

我们不能直接贪心的原因是不能维护。

那么考虑维护这个下凸包。

假设我们已经得到了,现在加入。可以发现,对于的直线,加入会使斜率,对于的直线,加入会使斜率(除非它的斜率已经是,那么就不再增加)。

我们用一个priority_queue维护它。堆中第大的元素,是斜率由转为的转折点的横坐标。

根据这个定义,弹出堆顶使堆中所有元素斜率,加入一个元素使小于它的所有元素斜率

如果,直接将丢进堆即可;

否则,我们会发现加入会使斜率发生断层。

比如,我们将插入到斜率为的直线中间。本应使右边一段斜率变为了,而左边那段斜率变成了

但是事实上,我们将插入,使得左边的斜率。又弹出了,使所有的斜率。这样,原本斜率应该变为的一段斜率还是

于是就有一个小trick:只需要把再插入一次就可以了。

事实证明这样做是正确的。总复杂度是

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e5 + 10;

int n, x;
LL res;
priority_queue<int> Q;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &x);
		x -= i;
		Q.push(x);
		if (Q.top() > x) {
			res += Q.top() - x;
			Q.pop();
			Q.push(x);
		}
	}
	cout << res << endl;
}