bzoj1367 [Baltic2004]sequence [左偏树]

Description

给定一个序列 t1,t2,...,tn ,求一个递增序列 z1<z2<...<zn , 使得 R=|t1z1|+|t2z2|+...+|tnzn| 的值最小。本题中,我们只需要求出这个最小的R值。

Input

第1行为一个整数 n(1<=n<=106) , 第2行到第n + 1行,每行一个整数,第k + 1行为 tk(0<=tk<=2109) .

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

Solution

论文:左偏树的特点及其应用

现学了一波左偏树orz.

先考虑只要求 z1<=z2<=...<=zn

两个特殊情况:

- t[1]<=t[2]<=...<=t[n] ,此时z[i] = t[i].

- t[1]>=t[2]>=...>=t[n] ,此时z[i]=x,x为序列t的中位数.

于是可以将原数列划分成m个区间,每一段的解为该区间的中位数。

实现:

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

如何维护中位数:当堆的大小大于区间长度的一半时删除堆顶元素,则堆中的元素一定是该区间内较小的一半元素,堆顶元素即为该区间的中位数。

这只是 z1<=z2<=...<=zn 的情况。。

然而要求递增只需要将原本的t[i]改成t[i] - i,再按照上述做法做就行了orz。

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 1000005;

int n;
int arr[MAXN];
int tot = 0;
int v[MAXN], tr[MAXN][2], dist[MAXN], siz[MAXN];
int root[MAXN];
int l[MAXN], r[MAXN];

int merge(int x, int y) {
    if (!x || !y) return x + y;
    if (v[x] < v[y]) swap(x, y);
    tr[x][1] = merge(tr[x][1], y);
    siz[x] = siz[tr[x][0]] + siz[tr[x][1]] + 1;
    if (dist[tr[x][1]] > dist[tr[x][0]]) swap(tr[x][0], tr[x][1]);
    dist[x] = dist[tr[x][1]] + 1;
    return x;
}

int top(int x) {
    return v[x];
}

int size(int x) {
    return siz[x];
}

void pop(int &x) {
    x = merge(tr[x][0], tr[x][1]);
}

int newNode(int x) {
    v[++tot] = x;
    siz[tot] = 1;
    tr[tot][0] = tr[tot][1] = dist[tot] = 0;
    return tot;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &arr[i]);
        arr[i] -= i;
    }
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        cnt++;
        root[cnt] = newNode(arr[i]);
        l[cnt] = r[cnt] = i;
        while (cnt > 1 && top(root[cnt]) < top(root[cnt - 1])) {
            cnt--;
            root[cnt] = merge(root[cnt], root[cnt + 1]);
            r[cnt] = r[cnt + 1];
            while (size(root[cnt]) * 2 > r[cnt] - l[cnt] + 2)
                pop(root[cnt]);
        }
    }
    long long ans = 0;
    for (int i = 1; i <= cnt; i++) {
        int t = top(root[i]);
        for (int j = l[i]; j <= r[i]; j++) {
            ans += abs(t - arr[j]);
        }
    }
    printf("%lld\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值