【BZOJ1367】sequence

1367: [Baltic2004]sequence

Time Limit: 20 Sec Memory Limit: 64 MB
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
为了方便,我们先解决所求数列不递减的情况。
首先由这么一件事情:对若干数确定一个数v,当v时这些数的中位数(为了方便,n个数的中位数我们定义为它们从小到大排序后第 n2 个数),这些数与v的差的绝对值之和是最小的。关于证明可以将这些数画在数轴上,利用绝对值的几何意义即可。
再来考虑这样一件事情:对于序列中连续的两段,若这两段的中位数分别对应这两段的最优解(设为u和v),那么当u<=v的时候,平安无事;否则,我们要将这两段进行合并。直觉告诉我们,合并之后的新区间的最优解应该是新区间的中位数。 事实也是如此。证明就留给读者思考了=w=那么算法便不难构思了:我们把解相同一段序列划作一段。当加进来一个数之后,把先令它的位置对应的解等于它自己本身,之后不断地与前一段进行比较,若当前段的值小于前一段,则需要将它们合并,并将新的解设为它们的中位数。这样我们便能得到最优解的一个可行解。
可以看出我们需要一个能够支持合并两个相邻区间并维护中位数的数据结构。使用平衡树显然是可以的,但是由于常数问题,在这道题的数据规模下难以通过。继续优化:我们可以只保存区间中较小一半的(即前 n2 )的数,查询中位数便变成了查询最大值。并且不难发现,当且仅当合并的两个区间的长度都为奇数时,我们需要删掉最大的数。显然可并堆是一个很好的选择。相比编程复杂度较高的二项堆和斐波那契堆,我采用的是性价比较高的左偏树。
上面解决了所求序列不递减的问题。如何做到递增呢?有一个巧妙的办法:对于初始的数列,我们统一减去它的序号即可(即ai赋值为ai-i)。

#include<cstdio>
#include<iostream>
#include<algorithm>
#define maxn 1000000
using namespace std;
struct leftisttree{
    struct treenode{
        int v,l,r,d;
        treenode(){d=l=r=v=0;}
        bool operator<(const treenode&a)const{return v<a.v;}
    };
    treenode t[maxn+10];
    int merge(int a,int b){
        if(a==0||b==0)return a+b;
        if(t[a]<t[b])swap(a,b);
        t[a].r=merge(t[a].r,b);
        if(t[t[a].l].d<t[t[a].r].d)swap(t[a].l,t[a].r);
        t[a].d=t[a].r?t[t[a].r].d+1:0;
        return a;
    }
    int root[maxn+10],m,sz[maxn+10];
    void solve(int n){
        t[0].d=-1;
        for(int i=1;i<=n;i++){
            scanf("%d",&t[i].v);
            t[i].v-=i;
            root[++m]=i;
            sz[m]=1;
            while(m>1&&t[root[m]].v<t[root[m-1]].v){
                int k=sz[m]&sz[m-1]&1;
                root[m-1]==merge(root[m-1],root[m]);
                if(k)root[m-1]=merge(t[root[m-1]].l,t[root[m-1]].r);
                sz[m-1]+=sz[m];
                m--;
            }
        }
        int cur=1;
        long long ans=0;
        for(int i=1;i<=m;i++)
            for(int j=1;j<=sz[i];j++,cur++)ans+=abs(t[root[i]].v-t[cur].v);
        cout<<ans<<endl; 
    }
}ltt;
int main(){
    int n;
    scanf("%d",&n);
    ltt.solve(n);       
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值