BZOJ 1367 [Baltic2004]sequence

左偏树

左偏树论文题。左偏树的优越性在于它可以不断找出树高最短的一段,因此能一直保持O(logn)的合并复杂度。

对于这题,先转成不降序列来做,只需-i

我们将序列先分成n段,则每一段的最优解z[i]=t[i],从左到右一段一段做,考虑合并。

若z[i] <= t[i],不需要合并。去做下一段。

若z[i] > t[i],易证(感性地想,或者去看论文)两段的最优解应当是两段所有数字的中位数。于是合并两段并改最优解为中位数。去做下一段。

维护中位数的过程需要左偏树。

#include<cstdio>
#include<algorithm>
#define N 1000005
using namespace std;
namespace ziqian
{
    int a[N], hcnt, cnt[N], num[N], l[N], r[N], tot, root[N];
    struct heap
    {
        int l, r, v, dis;
    }h[N];
    int new_heap(int v)
    {
        h[++hcnt] = (heap){0,0,v,0};
        return hcnt;
    }
    int merge(int x, int y)// big root
    {
        if(!x || !y)return x?x:y;
        if(h[x].v < h[y].v)
            swap(x, y);
        h[x].r = merge(h[x].r,y);
        if(h[h[x].l].dis < h[h[x].r].dis)
            swap(h[x].l, h[x].r);
        h[x].dis = h[h[x].l].dis + 1;
        return x;
    }
    int pop(int x)
    {
        return merge(h[x].l,h[x].r);
    }
    int abs(int x){return x>0?x:-x;}
    void main()
    {
        int n;
        scanf("%d",&n);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d",&a[i]);
            a[i]-=i;
        }
        for(int i = 1; i <= n; i++)
        {
            ++tot;
            root[tot] = new_heap(a[i]);
            l[tot] = r[tot] = i;
            cnt[tot] = num[tot] = 1;
            while(tot > 1 && h[root[tot-1]].v > h[root[tot]].v)
            {
                --tot;
                root[tot] = merge(root[tot], root[tot+1]);
                cnt[tot] += cnt[tot+1];
                num[tot] += num[tot+1];
                r[tot] = r[tot+1];
                while(num[tot] > (cnt[tot]+1)/2)
                { 
                    root[tot] = pop(root[tot]);
                    num[tot]--; 
                } 
            }
        }
        long long ans = 0;
        for(int i = 1; i <= tot; i++)
        {
            int tmp = h[root[i]].v;
            for(int j = l[i]; j <= r[i]; j++)
                ans += abs(a[j] - tmp);
        }
        printf("%lld\n",ans);
    }
}
int main()
{
    ziqian::main();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值