【可並堆】BZOJ1367(Baltic2004)[sequence]題解

題目概述

給出 {an} ,選擇一個上升序列 {bn} 使得 ni=1|aibi| 最小,求最小值。

解題報告

對於這道題,我們可以將 {an} 改成 {ann} ,然後就轉化爲求不下降序列 {bn}

如果 {an} 遞減,那麼 {bn} 顯然取中位數最好。如果不遞增,我們可以把序列劃爲若干段,每個段都取該段中位數(中位數不下降)。可以證明最優的 {bn} 一定是這樣的形式。

對於 i ,我們先將其單獨成段,然後檢查前一段的中位數是不是 ai ,如果不是,那麼就把兩段合併,求出新的中位數,直到滿足。由此可見我們需要能快速求中位數,快速合併的數據結構來維護。由於前一段的中位數 ai ,所以新的中位數不會比 ai 小,那麼我們就可以用可並堆,只需要維護 sinum2+1 ,堆頂就是中位數。

示例程序

#include<cstdio>
#include<cctype>
#include<algorithm>
typedef long long LL;
using namespace std;
const int maxn=1e6;

int n,v[maxn+5],ro[maxn+5],son[maxn+5][2],dis[maxn+5];
int tot,L[maxn+5],R[maxn+5],si[maxn+5];LL ans;

#define Eoln(x) ((x)==10||(x)==13||(x)==EOF)
inline char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF;return *l++;
}
inline int readi(int &x){
    int tot=0,f=1;char ch=readc(),lst='+';
    while (!isdigit(ch)) {if (ch==EOF) return EOF;lst=ch;ch=readc();}
    if (lst=='-') f=-f;
    while (isdigit(ch)) tot=(tot<<3)+(tot<<1)+ch-48,ch=readc();
    return x=tot*f,Eoln(ch);
}
int Merge(int a,int b){
    if (!a||!b) return a+b;if (v[a]<v[b]) swap(a,b);
    int &l=son[a][0],&r=son[a][1];
    r=Merge(r,b);if (dis[l]<dis[r]) swap(l,r);
    return dis[a]=dis[r]+1,a;
}
#define Pop(ID) ro[ID]=Merge(son[ro[ID]][0],son[ro[ID]][1]),si[ID]--
inline int absi(int x) {if (x<0) return -x;return x;}
int main(){
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    readi(n);dis[0]=-1;for (int i=1;i<=n;i++) readi(v[i]),v[i]-=i;
    for (int i=1;i<=n;i++){
        ro[++tot]=i;L[tot]=i;R[tot]=i;si[tot]=1;
        while (tot>1&&v[ro[tot-1]]>v[ro[tot]]){
            tot--;ro[tot]=Merge(ro[tot],ro[tot+1]);
            R[tot]=R[tot+1];si[tot]+=si[tot+1];
            while (si[tot]>(R[tot]-L[tot]>>1)+1) Pop(tot);
        }
    }
    for (int t=1;t<=tot;t++)
        for (int i=L[t];i<=R[t];i++)
            ans+=absi(v[i]-v[ro[t]]);
    return printf("%lld\n",ans),0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章