大致题意: 给定一个序列(a_i),求一个递增序列(b_i)使得(sum_{i=1}^n|a_i-b_i|)最小。
递增( ightarrow)不降
显然这题中递增序列这个条件让人很难受。
因此,我们对于每个(a_i),给它减去一个(i)((b_i)同理),这样一来(b_i)的递增条件就可以改为不降了。
中位数
考虑最终的答案肯定是由一段段相同的数组成的。
对于每一段,我们根据初一的数学知识可以知道,对于若干数,要找出一个数使得差值绝对值之和最小,必然是其中位数。
于是我们就有一个大致思路了:每次加入一个数,我们记下它的中位数(初始是它自身),然后如果这个中位数小于前一段的中位数(不满足递增条件了),我们就合并两段,并记录下新的中位数。
如此不断搞下去,我们就会发现这相当于是单调栈的过程。
但是,该如何维护一段数的中位数呢?
左偏树
考虑我们只要开个大根堆存储区间中前一半的数,则堆顶自然就是中位数了。
然后合并两段数,我们只要合并两个堆,然后若此时堆中数的数量大于一半,就弹出堆顶,即可实现中位数的维护。
而要合并堆,自然就需要左偏树啦!
可以说这题思想还是挺不错的吧。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1000000
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,a[N+5],S[N+5],P[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
Tp I void write(Ty x,Con char& y) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);pc(y);}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
#undef D
}F;
class LeftistTree//左偏树
{
private:
struct node {int Sz,D,S[2];}O[N+5];
public:
I int operator [] (CI x) Con {return O[x].Sz;}
I void Init() {for(RI i=1;i<=n;++i) O[i].Sz=1;}
I int Merge(RI x,RI y)//合并
{
if(!x||!y) return x|y;a[x]<a[y]&&swap(x,y),
O[O[x].S[1]=Merge(O[x].S[1],y)].D>O[O[x].S[0]].D&&swap(O[x].S[0],O[x].S[1]);
return O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz+1,O[x].D=O[O[x].S[1]].D+1,x;
}
I int Pop(CI x) {return Merge(O[x].S[0],O[x].S[1]);}//弹出堆顶
}L;
int main()
{
RI i,j;for(F.read(n),i=1;i<=n;++i) F.read(a[i]),a[i]-=i;//减去下标以改递增为不降
RI k,T=0;for(L.Init(),i=1;i<=n;++i)
{
k=i;W(T&&a[k]<a[S[T]]) k=L.Merge(k,S[T--]),L[k]>(i-P[T]+1>>1)&&(k=L.Pop(k));//单调栈,用左偏树维护中位数
S[++T]=k,P[T]=i;//加入栈中
}
long long ans=0;for(i=j=1;i<=n;++i) ans+=abs(a[i]-a[S[j]]),P[j]==i&&++j;
return printf("%lld",ans),0;
// 洛谷需要输出b。。。
// for(F.write(ans,'
'),i=j=1;i<=n;++i) F.write(a[S[j]]+i,"
"[i==n]),P[j]==i&&++j;
// return F.clear(),0;
}