题目描述:
给定一个长度为 n 的非负整数序列 a[1..n]。
你每次可以花费 1 的代价给某个 a[i] 加1或者减1。
求最少需要多少代价能将这个序列变成一个不上升序列。
分析:
我们设DP(i,j)表示前i个数已经满足不上升的情况下,第i个数为j的最小代价
转移式很好想:
DP(i,j)=min(DP(i−1,k))(k≥j)+|ai−j|
D
P
(
i
,
j
)
=
m
i
n
(
D
P
(
i
−
1
,
k
)
)
(
k
≥
j
)
+
|
a
i
−
j
|
显然我们答案就是
min(DP(n,i)0≤i)
m
i
n
(
D
P
(
n
,
i
)
0
≤
i
)
当然,我们不可能这样去裸跑DP的(时空均炸完)
我们来分析一下这个函数的图像:
DP(1)://即第一维固定,下图中的横坐标表示第二维的参数,纵坐标表示函数值
这个图像很显然,但并没有很明显的特征,我们继续研究DP(2):
首先我们考虑
min(DP(i−1,k))(k≥j)
m
i
n
(
D
P
(
i
−
1
,
k
)
)
(
k
≥
j
)
的取值:
很显然是这样一个样子的。
在这个基础上,我们再套上一个和
DP(1)
D
P
(
1
)
一样的图像,就会出现两种情况:
以及:
其实就是根据A1和A2的大小来区分的。
到这里,你应该也看出来一些特征了:
这个图像无论经过多少次变化,一定会是一个单谷图像
现在,我们将一个转折点右边的线的斜率作为这个点的权值。
再分析每加入一个点后的变化:
每次
min(DP(i−1,k))(k≥j)
m
i
n
(
D
P
(
i
−
1
,
k
)
)
(
k
≥
j
)
的图像必然是一个递增的图像,因此,所有权值小于等于0的点都会被消除,在加入一个新的折线后,折线零点(即
Ai
A
i
的值)左边的所有点权值-1,右边的所有点权值+1。并且当前零点的权值也要+1。
结合这一点看,我们每次消除的点最多只有1个,即使没有消除点,那么最左边的点的权值必然也会-1,可以看做在同一个位置上有多个点,删去其中一个。
这样一来就很简单了,我们可以用优先队列存储每个点的位置,每次加入一个新点,可以在当前放两个点,再从最左边删去一个点,而我们的代价,就是删去的点和加入的点的距离。
所以代码相当的短:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define SF scanf
#define PF printf
#define MAXN 1000010
using namespace std;
int n;
long long ans,x;
priority_queue<long long,vector<long long>,greater<long long> > q;
int main(){
SF("%d",&n);
for(int i=1;i<=n;i++){
SF("%lld",&x);
q.push(x);
if(q.top()<x){
ans+=x-q.top();
q.pop();
q.push(x);
}
}
PF("%lld",ans);
}