题目描述
给定一个长度为 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;
}