题目描述
给定一个长度为 n (1≤n≤500000) 的非负整数序列 a[1..n]。
你每次可以花费 1 的代价给某个 a[i] (0<a[i]≤109) 加1或者减1。
求最少需要多少代价能将这个序列变成一个不上升序列。
题解
首先想到的是最朴素的DP:
dp[i][j]表示前i个数排成不上升序列,最小的数为j,所需的最小代价;
dp[1][j]=|j-a[1]|
枚举k,(k>=a[i])
dp[i+1][j]=min(dp[i][k])+|j-a[i]|;
令函数fi(x)表示dp[i][x],则fi(x)一定是这种形状:(下凸包)
由于每次转移时,总是取大于等于某个值的区域里的最小值,所以左半边递减的图像没有任何用处,直接忽略掉:
此时,fi(x)即为递增函数,那么min(fi(k))=fi(a[i]) | (k>=a[i])
还可以发现,这个函数的斜率一定是整数,当x增加1时,相当于数组a至少1个的元素需要更改值,花费自然也是整数,所以斜率也就为整数。
仔细观察,还可以知道,斜率一定是不下降的:当x每增加1,数组a中需要更改的元素个数不可能比以前少,所以增加的代价一定比x小的时候多,斜率也就不下降了。
继续研究转移:
令函数gi(x)=|x−a[i]|,那么函数fi+1(x)=fi(x)+gi(x)。
函数gi(x)图像:
令p=fi(x)最小时的x
如果a[i]≤p,像这样,则合成后,相当于fi(x)的所有斜率+1,并添加a[i]到p的一条斜率为1的线段:
如果
同样,图上fi+1(x)斜率为0的一段我们可以扔掉,注意到此时fi+1(x)最小值相比于fi(x)增加了(a[i]−p)
实现
实现时,使用一个优先队列(堆)来存储当前fi上的斜率变化的折点位置,折点位置在堆中的排名表示这个折点后的线段的斜率。
用ans表示此时fi最小值,即为答案。
每添加一个新的a[i],获取p (p=fi(x)最小时的x),p=堆顶
如果a[i]≤p,则需要将堆中所有折点的斜率+1,并添加a[i]折点。
只需将a[i]插入堆中即可
如果p≤a[i],则将堆顶弹出扔掉(因为这个点斜率-1为0,不再需要考虑),ans加上(a[i]−p),然后插入2个a[i],因为后面的斜率会比之前的大2(后面+1,前面-1)。
最后从1~n枚举i,最后的ans即为答案
代码
#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
priority_queue< int,vector<int>,greater<int> > Q;
int main()
{
int n,a;
long long ans=0LL;
scanf("%d",&n);
scanf("%d",&a);
Q.push(a);
for(int i=2;i<=n;i++)
{
scanf("%d",&a);
Q.push(a);
int mn=Q.top();
if(mn<a)
{
Q.pop();
ans+=1LL*(a-mn);
Q.push(a);
}
}
printf("%lld\n",ans);
return 0;
}