BZOJ[1367][Baltic2004]Sequence 可并堆

4 篇文章 0 订阅

题目链接http://www.lydsy.com/JudgeOnline/problem.php?id=1367

Description
这里写图片描述

Input
这里写图片描述

Output
一个整数R

Sample Input
7
9
4
8
20
14
15
18

Sample Output

13

HINT
所求的Z序列为6,7,8,13,14,15,18.
R=13


先考虑两种特殊情况:
①若 t1<t2<t3<<tn
则对于每一个 zi=ti ,此时最小 R 0
②若 t1>t2>t3>>tn
则对于每一个 zi=(t1tn) 的中位数,此时 R 最小,
下面是我瞎证明的过程:
这里写图片描述
对于情况②,我们对每一个zi取中位数(红色部分),则 R 为这些绿色部分

这里写图片描述
如改变所取的值(如图,取较大于中位数的数),则会发现前面的数减少了棕色部分,后面的数减小了紫色部分,发现这两部分事实上是相等的,前后的总和并没有改变,但R值却多了中间的那一部分(画圈部分),得证,偶数情况与其类似,在此不做赘述

我们可以将原序列分割成许多单调递减的序列,这些序列所有数都取他们的中位数
但是我们要求的是上升序列而不是不下降序列,所以需要将输入的ti处理成 tii

首先我们把 ak+1 作为一个新区间直接加入队尾,令 wm+1=ak+1 ,然后不断检查队尾两个区间的中位数 wm wm+1 ,如果 wm>wm+1 ,我们需要将最后两个区间合并,并找出新区间的最优解(也就是序列a中,下标在这个新区间内的各项的中位数)。重复这个合并过程,直至 w1w2wm 时结束,然后继续处理下一个数。

代码如下:

#include<algorithm>
#include<cstdlib>
#include<cstdio>
#define N 1000600
using namespace std;
int size[N],ch[N][2],a[N],root[N],l[N],r[N],tot[N],top,n;
long long ans=0ll;
int merge(int x,int y){
    if(!x) return y;if(!y) return x;
    if(a[x]<a[y]) swap(x,y);
    ch[x][1]=merge(ch[x][1],y);
    size[x]=size[ch[x][1]]+size[ch[x][0]]+1;
    swap(ch[x][0],ch[x][1]);
return x;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",a+i),a[i]-=i;//要求单调递增
    for(int i=1;i<=n;i++){//root:这段区间的中位数
        l[++top]=r[top]=root[top]=i;//l:这段区间包含的最左边的点,r:这段区间包含的最右边的点
        tot[top]=size[i]=1;//tot:这个区间包含多少个数 size:
        while(top>1&&a[root[top-1]]>a[root[top]]){
            r[--top]=r[top+1]; tot[top]+=tot[top+1];
            root[top]=merge(root[top],root[top+1]);//将这个点合并到上一个序列里
            //下面的过程是将中位数调到堆顶(不断删除堆顶)
            while(size[root[top]]*2>tot[top]+1) root[top]=merge(ch[root[top]][0],ch[root[top]][1]);
        }
    }
    for(int i=1;i<=top;i++)
        for(int j=l[i];j<=r[i];j++)
            ans+=(long long)abs(a[j]-a[root[i]]);
return printf("%lld",ans),0;
}


附:不同可并堆的效率比较:
从上到下:斜堆,左偏树,随机堆(19260817)
左偏树还是稳定啊(笑)
这里写图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值