题目描述
题意:给你一个长度为n的整数序列A。把它变成一个不降的序列。求改动的最小代价,代价为更改前后的数字大小的差值。
就是没有了原题中改动次数最小的限制。
题解
考试时看到这到题就以为是数字序列了…
不说废话了。
既然没有操作次数最小的要求,那么就只用考虑最小代价了(废话)
怎么使代价最小呢?对于一个序列,如果它是单调不降的,我们显然不用去单独管它。
如果有减小的呢? 若单独考虑考虑一段递减序列,那么自然是变成它们的中位数。为什么呢?求函数f(x)=|x-h1|+|x-h2|+|x-h3|….+|x-hn|的最小值总会吧,一根数轴即可搞定。
但我们显然不能只考虑一段区间,对于我们分出的每一段区间,必须要他们的中位数不降,否则不满足题意。对于这样的区间,可以用一个栈来维护。
那么只用考虑在栈顶前后两个区间不合法时如何合并两段区间了。
这里似乎只要能求出合并后的中位数即可,其他的数据(统计答案时要用,具体看代码)都可以直接开数组记录。
那么讨论如何求中位数,看出来是个动态求中位数了吧,是不是想到用两个堆,保证他们的size差不大于1。这道题要支持合并的话就左偏树。
但观察到这道题还有一个性质,因为只有在后面的区间的中位数小于前一个区间时才会合并,那么合并后两者的中位数必然相对与前一个减小,那么开个大根堆的话就只用在合并之后保留一半多一个元素即可,堆顶就是中位数了!
统计答案的话暴力O(n)就可以了。
至于这样做正确性的证明,可以参考 国家集训队2005论文集_黄源河 的论文(orz)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<queue>
#include<cmath>
using namespace std;
inline int read()
{
int x=0;char ch=getchar();int t=1;
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
return x*t;
}
const int N=1e5+10;
typedef long long ll;
const ll INF=1e18;
ll h[N];
int n;
#define __ NULL
#define Dis(a) (a==__? -1:a->dis)
#define Size(a) (a==__? 0:a->size)
inline ll Abs(ll a){return a>0? a:-a;}
struct node{
node* ls;node* rs;int x;int dis;int id;int size;
void clear(){ls=rs=__;x=0;dis=0;id=0;size=1;}
}*tr[N];
node pool[N];int cnt=0;
int st[N];
int si[N];
int top=0;
inline void updata(node* p)
{
if(p==__) return;
if(Dis(p->ls)<Dis(p->rs)) swap(p->ls,p->rs);
p->size=1+Size(p->ls)+Size(p->rs);
p->dis=Dis(p->rs)+1;
return ;
}
inline node* merge(node* p,node* q)
{
if(p==__) return q;
if(q==__) return p;
if(p->x<q->x) swap(p,q);
p->rs=merge(p->rs,q);
updata(p);
return p;
}
inline void Maintain()
{
while(233){
if(top<2||tr[st[top]]->x>=tr[st[top-1]]->x) return;
register int S=si[top]+si[top-1];
register node* p=merge(tr[st[top]],tr[st[top-1]]);
top--;si[top]=S;
while(p->size>S/2+1) {
register node* q=p;p=merge(p->ls,p->rs);
q->ls=q->rs=__;
}
st[top]=p->id;
}
}
int main()
{
n=read();
for(register int i=1;i<=n;i++){
h[i]=1ll*read();tr[i]=&pool[++cnt];tr[i]->clear();tr[i]->x=h[i];tr[i]->id=i;
}
st[1]=top=1;si[1]=1;
for(register int i=2;i<=n;i++){
st[++top]=i;si[top]=1;
Maintain();
}
int now=1;
ll ans=0;register int H=0;
while(now<=top&&H<n){
for(register int i=1;i<=si[now]&&H<n;i++){
H++;
ans+=Abs(1ll*(h[H]-tr[st[now]]->x));
}
now++;
}
printf("%lld\n",ans);
}