Problem
Problem 1367. – [Baltic2004]sequence
Solution
這個題目在2005國家集訓隊論文 黃源河 左偏樹的特點及其應用稍微修改後作爲例題出現,具體證明可見論文集[左偏樹的應用]
首先,爲了便於分析,我們將題目轉化
將 變爲 ,即可將 化爲不下降序列
先考慮簡單情況
1. 若序列 爲不下降序列,那麼當 時取到最小值
2. 若序列 爲單調下降序列,那麼當 的中位數時取到最小值
情況1非常的顯然,這裏不做過多分析
情況2我們將其轉化爲在平面上有n個點,取一條平行於 軸的直線使這些點到這條直線的距離和最小
將直線從無限遠的地方緩緩靠近,在接觸到第一個點之前每次移動一個單位距離答案減小
在接觸到第 個點是每次移動一個單位距離答案減少 ,增加 ,共減少
顯然在 的時候就一直挪好啦
所以將線挪到經過 ,也就是取到 中中位數時,取得最小值
而且因爲 數組單調下降,所以說要是不使用一條直線,可以將其”掰”成一條直線,前半部分更優了,後半部分也更優了
好的,回到原題
將每個單調下降的部分分成一塊,每一塊都是一個單調下降子序列.當然,某個子序列的長度可能爲
這些子段的答案都可以處理出來
現在的問題是如何將這些子段答案合併
設子段答案爲x
若 ,這是合法的,可同時去最優解
若 ,這是非法的,需要合併
這就化爲了我們之前討論過的情況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;
}