Vexoben
Sep 16, 2018
题目链接: 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;
}