经典的凸包优化,运用到 slope trick。感觉还是挺妙的。
考虑朴素 DP。设 表示第 个数变为 的最小花费,状态转移方程为:
相当于先进行一次前缀 ,再加上绝对值。将 视作关于 的函数,容易发现这是一个凸函数。因为初始函数 是凸函数 ,而我们每次进行前缀取 以及加上一个凸函数,都能够保证得到的新函数保持凸性,并且其中线段斜率为整数。
如何维护这个凸包?我们考虑维护每一个转折点,考虑一个序列,序列中每个转折点 表示再在位置 斜率增加 。那么我们加入一个绝对值函数 ,相当于插入两个转折点 。由于前缀取 的存在,我们只需要保留斜率大于 的部分即可。而对于当前最优解就是最右边的端点。这用堆很容易维护,于是我们对于 进行如下操作:
- 在堆中加入两个转折点 。
- 取出此时最右边的端点,设为 。因为前面加入了两个端点, 的转折点斜率全部减 , 之后的直线斜率变为 。此时 即为最优。所以答案直接加上 。
- 因为 之后的直线斜率变为 ,我们直接将 扔掉即可。
重复以上过程即可,时间复杂度 。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include<iostream> #include<cstdio> #include<queue> using namespace std; template<class T> inline void read(T &x) { x=0;int f=0; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();} while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); if(f)x=~x+1; } template<class T,class ...T1> inline void read(T &x,T1 &...x1) { read(x),read(x1...); } int n; long long ans; priority_queue<int>q; int main() { read(n); while(n--) { int x; read(x); q.push(x); if(x<q.top())ans+=q.top()-x,q.pop(),q.push(x); } printf("%lld\n",ans); return 0; }
|
Gitalk 加载中 ...