【可并堆】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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值