bzoj 5059: 前鬼后鬼的守护

本文介绍了一种使用可并堆解决序列平滑问题的方法,通过将序列分割成多个相同数值的区间,并利用左偏树维护这些区间,最终计算出使序列变为非递减所需的最小改动成本。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:一个序列,把其中一个数 a 改成b的代价是 |ab| ,求使序列不下降的最小代价。
题解:可并堆
最后的序列可以分成许多个区间,一个区间里的每个值都是一样的(两两不同时每个区间的长度为1)。如果一个区间要改成一个值,那么一定是它们的中位数。每次加入一个数就把它当成一个单独的区间,然后比较最后两个区间的中位数,假如前一个的比后一个的大,就把这两个区间合并,继续往前比较。用左偏树维护。具体看黄源河的论文里的例题。论文中把原题修改了一下,正好改成这题。
代码:

#include<bits/stdc++.h>
using namespace std;

int n,a[500010],num=0;
struct tree
{
    int c,l,r,dis,sz;
    tree()
    {
        c=l=r=sz=0;
        dis=-1;
    }
}tr[500010];
struct que
{
    int l,r,c;
}q[500010];
long long ans=0;

int merge(int x,int y)
{
    if(x==0)
    return y;
    if(y==0)
    return x;
    if(tr[x].c<tr[y].c)
    swap(x,y);
    tr[x].r=merge(tr[x].r,y);
    if(tr[tr[x].l].dis<tr[tr[x].r].dis)
    swap(tr[x].l,tr[x].r);
    tr[x].dis=tr[tr[x].r].dis+1;
    tr[x].sz=tr[tr[x].l].sz+tr[tr[x].r].sz+1;
    return x;
}
int pop(int x)//返回新根 
{
    return merge(tr[x].l,tr[x].r);
}
int top(int x)
{
    return tr[x].c;
}
int ins(int x)
{
    int i=++num;
    tr[i].c=x;
    tr[i].l=tr[i].r=tr[i].dis=0;
    tr[i].sz=1;
    return i;
}
int md(int x)
{
    return x-x/2;
}
int main()
{
    scanf("%d",&n);
    int ed=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        q[++ed]={i,i,ins(a[i])};
        while(ed>=2&&top(q[ed-1].c)>top(q[ed].c))
        {
            q[ed-1].c=merge(q[ed-1].c,q[ed].c);
            q[ed-1].r=q[ed].r;
            while(tr[q[ed-1].c].sz>md(q[ed-1].r-q[ed-1].l+1))
            q[ed-1].c=pop(q[ed-1].c);
            ed--;
        }
    }
    for(int i=1;i<=ed;i++)
    {
        for(int j=q[i].l;j<=q[i].r;j++)
        ans+=abs(top(q[i].c)-a[j]);
    }
    printf("%lld",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值