Problem
Problem 1367. – [Baltic2004]sequence
Solution
这个题目在2005国家集训队论文 黄源河 左偏树的特点及其应用稍微修改后作为例题出现,具体证明可见论文集[左偏树的应用]
首先,为了便于分析,我们将题目转化
将
t1,t2,⋯,tn
t
1
,
t
2
,
⋯
,
t
n
变为
t1−1,t2−2,⋯,tn−n
t
1
−
1
,
t
2
−
2
,
⋯
,
t
n
−
n
,即可将
z
z
化为不下降序列
先考虑简单情况
1. 若序列为不下降序列,那么当
zi=ti
z
i
=
t
i
时取到最小值
2. 若序列
t
t
为单调下降序列,那么当的中位数时取到最小值
情况1非常的显然,这里不做过多分析
情况2我们将其转化为在平面上有n个点,取一条平行于
x
x
轴的直线使这些点到这条直线的距离和最小
将直线从无限远的地方缓缓靠近,在接触到第一个点之前每次移动一个单位距离答案减小
在接触到第
m
m
个点是每次移动一个单位距离答案减少,增加
m
m
,共减少
显然在
n>2m
n
>
2
m
的时候就一直挪好啦
所以将线挪到经过
⌊n2⌋
⌊
n
2
⌋
,也就是取到
t
t
中中位数时,取得最小值
而且因为数组单调下降,所以说要是不使用一条直线,可以将其”掰”成一条直线,前半部分更优了,后半部分也更优了
好的,回到原题
将每个单调下降的部分分成一块,每一块都是一个单调下降子序列.当然,某个子序列的长度可能为
1
1
这些子段的答案都可以处理出来
现在的问题是如何将这些子段答案合并
设子段答案为x
若,这是合法的,可同时去最优解
若
x1>x2
x
1
>
x
2
,这是非法的,需要合并
这就化为了我们之前讨论过的情况2,任意有斜线的一边我们都可以将其”掰”回来
那么最优点就肯定是其中位数啦
我们需要支持取子段中位数,所以用左偏树来维护即可
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int dis[N],key[N],ch[N][2],tot,n,cnt;
int arr[N];
int rt[N],l[N],r[N],siz[N];
int read() {
int ans=0,flag=1;
char ch=getchar();
while( (ch>'9' || ch<'0') && ch!='-' ) ch=getchar();
if(ch=='-') {flag=-1;ch=getchar();}
while(ch>='0' && ch<='9') {ans=ans*10+ch-'0';ch=getchar();}
return ans*flag;
}
int merge(int a,int b) {
if(!a || !b) return a+b;
if(key[a]<key[b]) swap(a,b);
ch[a][1]=merge(ch[a][1],b);
siz[a]=siz[ch[a][0]]+siz[ch[a][1]]+1;
if(dis[ch[a][1]]>dis[ch[a][0]]) swap(ch[a][0],ch[a][1]);
dis[a]=dis[ch[a][1]]+1;
return a;
}
void pop(int &a) {a=merge(ch[a][0],ch[a][1]);}
int create(int x) {
key[++tot]=x;
siz[tot]=1;
dis[tot]=0;
return tot;
}
int main() {
n=read();
memset(dis,-1,sizeof(dis));
for(int i=1;i<=n;++i) {arr[i]=read()-i;}
for(int i=1;i<=n;++i) {
rt[++cnt]=create(arr[i]);
l[cnt]=r[cnt]=i;
while(cnt>1 && key[rt[cnt]]<key[rt[cnt-1]]) {
rt[cnt-1]=merge(rt[cnt-1],rt[cnt]);
r[cnt-1]=r[cnt];
--cnt;
while(siz[rt[cnt]]*2>r[cnt]-l[cnt]+1+1) pop(rt[cnt]);
}
}
long long ans=0;
for(int i=1;i<=cnt;++i) {
long long t=key[rt[i]];
for(int j=l[i];j<=r[i];++j)
ans+=abs(t-arr[j]);
}
printf("%lld\n",ans);
return 0;
}