[BZOJ1367][Baltic2004]sequence(可并堆+中位数)

题目:

我是超链接

题解:

这道题让我想起了曲神的胡策,把一堆数字变成一样的数字要的最小代价是什么呢?就是全都变成中位数啦

那这个题目是什么意思呢?把一堆数字变成递增的需要的最小代价

先考虑 z1<=z2<=z3<=...<=zn z 1 <= z 2 <= z 3 <= . . . <= z n 下的最优解

我们的整体思路就是划分成m个片段,每个片段将所有的数字变成中位数,这样的解是最小的

假设已经求出了前k个数的最优解,被划分成了m个区间,每段区间的最优解为 w[i](w[1]<=w[2]<=...<=w[m]) w [ i ] ( w [ 1 ] <= w [ 2 ] <= . . . <= w [ m ] ) ,现在考虑第k + 1个数,先将a[k + 1]单独看作一个区间,最优解为w[m+1],此时假如 w[m]>w[m+1] w [ m ] > w [ m + 1 ] ,则合并区间m,m + 1,然后找出新区间的解(中位数),重复上述过程直到 w[m]<=w[m+1] w [ m ] <= w [ m + 1 ]

如何维护一段区间的中位数?我们使用左偏树,当size>空间长度的一半时,我们弹掉最大值,这样堆顶就是这个区间的中位数

这是 z1<=z2<=z3<=...<=zn z 1 <= z 2 <= z 3 <= . . . <= z n 情况下的
如何不让他们有可能相等呢?转化a[i]->a[i]-i就行了,这样转化后相等的话考虑到转化前也不会相等至少差1

代码:

#include <cstdio>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
const int N=1000005;
int ls[N],rs[N],dis[N],l[N],r[N],size[N],tot[N],root[N];LL a[N];
int merge(int x,int y)
{
    if (!x || !y) return x+y;
    if (a[x]<a[y]) swap(x,y);
    rs[x]=merge(rs[x],y);
    size[x]=size[ls[x]]+size[rs[x]]+1;//堆中还在的元素个数 
    if (dis[ls[x]]<dis[rs[x]]) swap(ls[x],rs[x]);
    if (!rs[x]) dis[x]=0;else dis[x]=dis[rs[x]]+1;
    return x;
}
int main()
{
    int n,m=0;scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]-=i;
    for (int i=1;i<=n;i++)
    {
        ++m;l[m]=r[m]=i;
        root[m]=i;size[i]=tot[m]=1;
        while (m>1 && a[root[m]]<a[root[m-1]])
        {
            m--;
            r[m]=r[m+1];
            tot[m]+=tot[m+1];//范围内总元素个数 
            root[m]=merge(root[m],root[m+1]);
            while (size[root[m]]*2>tot[m]+1) //保证堆顶为中位数 
              root[m]=merge(ls[root[m]],rs[root[m]]);
        }
    }
    LL ans=0;
    for (int i=1;i<=m;i++)
      for (int j=l[i];j<=r[i];j++)
        ans+=(LL)abs(a[j]-a[root[i]]);
    printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值