BZOJ 1367 [Baltic2004] sequence(左偏树)

Problem

Problem 1367. – [Baltic2004]sequence

Solution

这个题目在2005国家集训队论文 黄源河 左偏树的特点及其应用稍微修改后作为例题出现,具体证明可见论文集[左偏树的应用]

首先,为了便于分析,我们将题目转化
t1,t2,,tn t 1 , t 2 , ⋯ , t n 变为 t11,t22,,tnn t 1 − 1 , t 2 − 2 , ⋯ , t n − n ,即可将 z z 化为不下降序列

先考虑简单情况
1. 若序列t为不下降序列,那么当 zi=ti z i = t i 时取到最小值
2. 若序列 t t 为单调下降序列,那么当zi=t的中位数时取到最小值
情况1非常的显然,这里不做过多分析
情况2我们将其转化为在平面上有n个点,取一条平行于 x x 轴的直线使这些点到这条直线的距离和最小

将直线从无限远的地方缓缓靠近,在接触到第一个点之前每次移动一个单位距离答案减小n
在接触到第 m m 个点是每次移动一个单位距离答案减少nm,增加 m m ,共减少n2m
显然在 n>2m n > 2 m 的时候就一直挪好啦
所以将线挪到经过 n2 ⌊ n 2 ⌋ ,也就是取到 t t 中中位数时,取得最小值
而且因为t数组单调下降,所以说要是不使用一条直线,可以将其”掰”成一条直线,前半部分更优了,后半部分也更优了

好的,回到原题
将每个单调下降的部分分成一块,每一块都是一个单调下降子序列.当然,某个子序列的长度可能为 1 1

这些子段的答案都可以处理出来
现在的问题是如何将这些子段答案合并

设子段答案为x
x1x2,这是合法的,可同时去最优解
x1>x2 x 1 > x 2 ,这是非法的,需要合并

这就化为了我们之前讨论过的情况2,任意有斜线的一边我们都可以将其”掰”回来
那么最优点就肯定是其中位数啦

我们需要支持取子段中位数,所以用左偏树来维护即可

代码如下:

#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int dis[N],key[N],ch[N][2],tot,n,cnt;
int arr[N];
int rt[N],l[N],r[N],siz[N];
int read() {
    int ans=0,flag=1;
    char ch=getchar();
    while( (ch>'9' || ch<'0') && ch!='-' ) ch=getchar();
    if(ch=='-') {flag=-1;ch=getchar();}
    while(ch>='0' && ch<='9') {ans=ans*10+ch-'0';ch=getchar();}
    return ans*flag;
}
int merge(int a,int b) {
    if(!a || !b) return a+b;
    if(key[a]<key[b]) swap(a,b);
    ch[a][1]=merge(ch[a][1],b);
    siz[a]=siz[ch[a][0]]+siz[ch[a][1]]+1;
    if(dis[ch[a][1]]>dis[ch[a][0]]) swap(ch[a][0],ch[a][1]);
    dis[a]=dis[ch[a][1]]+1;
    return a;
}
void pop(int &a) {a=merge(ch[a][0],ch[a][1]);}
int create(int x) {
    key[++tot]=x;
    siz[tot]=1;
    dis[tot]=0;
    return tot;
}
int main() {
    n=read();
    memset(dis,-1,sizeof(dis));
    for(int i=1;i<=n;++i) {arr[i]=read()-i;}
    for(int i=1;i<=n;++i) {
        rt[++cnt]=create(arr[i]);
        l[cnt]=r[cnt]=i;
        while(cnt>1 && key[rt[cnt]]<key[rt[cnt-1]]) {
            rt[cnt-1]=merge(rt[cnt-1],rt[cnt]);
            r[cnt-1]=r[cnt];
            --cnt;
            while(siz[rt[cnt]]*2>r[cnt]-l[cnt]+1+1) pop(rt[cnt]);
        }
    }
    long long ans=0;
    for(int i=1;i<=cnt;++i) {
        long long t=key[rt[i]];
        for(int j=l[i];j<=r[i];++j)
            ans+=abs(t-arr[j]);
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值