BZOJ1367【Baltic2004】sequence

题面

Description

Description

Input

Input

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

我们首先来考虑另一个问题: 给定一个数列\(\{a_n\}\), 求一个单调不下降的\(\{b_n\}\), 使得\(\sum |b_n - a_n|\)最小.
考虑两种较为特殊情况:

  • \(a_1 \le a_2 \le ... \le a_n\), 此时\(b_n = a_n\)
  • \(a_1 \ge a_2 \ge ... \ge a_n\), 此时\(b_n = \{a_n\}的中位数\)

不难发现, 假如我们把\(\{a_n\}\)单调不下降的情况看作是一个数一段, 则它与\(\{a_n\}\)单调不上升的情况是等价的.
因此, 这道题目的做法就是: 从前往后分段, 对于\(a_n\)这一个数, 开始时我们把它单独作为一段, 假如这一段的中位数比上一段要小, 则把当前一段和上一段合并. 直至当前\(a_n\)所在段的中位数大于等于上一段的中位数或只剩下一段. 然后考虑数列上的下一个数.
回到原题, 由于原题要求\(z_n < z_{n + 1}\), 我们把读入的\(t_n\)变成\(b_n - n\), 再按照上述方法求解即可.
考虑如何动态维护中位数: 比较简单的想法就是启发式合并平衡树, 时间复杂度\(O(n \log^2 n)\), 会TLE. 下面是代码.

#include <cstdio>
#include <cctype>
#include <algorithm>
#include <cmath>

namespace Zeonfai
{
    inline int getInt()
    {
        int a = 0, sgn = 1;
        char c;
 
        while(! isdigit(c = getchar()))
            if(c == '-')
                sgn *= -1;
 
        while(isdigit(c))
            a = a * 10 + c - '0', c = getchar();
 
        return a * sgn;
    }
}

const int N = (int)1e6;
int a[N], cnt;

struct section
{
    int L, R, w;
}sec[N];

struct splayTrees
{
    struct node
    {
        int suc[2], pre, sz, w;
    }nd[N];

    int rt[N];

    inline void newNode(int u, int w)
    {
        nd[u].w = w, nd[u].suc[0] = nd[u].suc[1] = nd[u].pre = -1, nd[u].sz = 1;
    }

    inline void update(int u)
    {
        nd[u].sz = 1;

        for(int i = 0; i < 2; ++ i)
            if(~ nd[u].suc[i])
                nd[u].sz += nd[nd[u].suc[i]].sz;
    }

    inline int getRelation(int u)
    {
        if(! (~ nd[u].pre))
            return -1;

        return u == nd[nd[u].pre].suc[1];
    }

    inline void rotate(int u)
    {
        int pre = nd[u].pre, prepre = nd[pre].pre, k = getRelation(u);
        
        if(~ nd[u].suc[k ^ 1])
            nd[nd[u].suc[k ^ 1]].pre = pre;
        
        nd[pre].suc[k] = nd[u].suc[k ^ 1];
        nd[u].suc[k ^ 1] = pre;
        nd[u].pre = nd[pre].pre;

        if(~ prepre)
            nd[prepre].suc[getRelation(pre)] = u;

        nd[pre].pre = u;
        update(pre), update(u);
    }

    inline void splay(int a, int u)
    {
        while(~ nd[u].pre)
        {
            int pre = nd[u].pre;

            if(~ nd[pre].pre)
                rotate(getRelation(pre) == getRelation(u) ? pre : u);

            rotate(u);
        }

        rt[a] = u;
    }

    void _insert(int a, int u, int v)
    {
        ++ nd[u].sz;

        if(nd[v].w < nd[u].w)
        {
            if(! (~ nd[u].suc[0]))
            {
                newNode(v, nd[v].w);
                nd[v].pre = u;
                nd[u].suc[0] = v;
                splay(a, v);
            }
            else 
                _insert(a, nd[u].suc[0], v);
        }
        else
        {
            if(! (~ nd[u].suc[1]))
            {
                newNode(v, nd[v].w);
                nd[v].pre = u;
                nd[u].suc[1] = v;
                splay(a, v);
            }
            else
                _insert(a, nd[u].suc[1], v);
        }
    }

    inline void insert(int a, int u)
    {
        _insert(a, rt[a], u);
    }

    void _merge(int a, int u)
    {
        int suc[2] = {nd[u].suc[0], nd[u].suc[1]};
        insert(a, u);

        for(int i = 0; i < 2; ++ i)
            if(~ suc[i])
                _merge(a, suc[i]);
    }

    inline int merge(int a, int b)
    {
        if(nd[rt[a]].sz < nd[rt[b]].sz)
            std::swap(rt[a], rt[b]);

        _merge(a, rt[b]);
    }

    int get(int u, int k)
    {
        if(~ nd[u].suc[0])
        {
            if(nd[nd[u].suc[0]].sz > k)
                return get(nd[u].suc[0], k);
            else
                k -= nd[nd[u].suc[0]].sz;
        }

        if(! k)
            return nd[u].w;
        
        return get(nd[u].suc[1], k - 1);
    }

    inline int getMedian(int a)
    {
        return get(rt[a], nd[rt[a]].sz >> 1);
    }
}trees;

int main()
{
    #ifndef ONLINE_JUDGE
    freopen("BZOJ1367.in", "r", stdin);
    freopen("BZOJ1367.out", "w", stdout);
    #endif

    using namespace Zeonfai;

    int n = getInt();

    for(int i = 0; i < n; ++ i)
        trees.newNode(i, a[i] = getInt() - i);

    cnt = 0;

    for(int i = 0; i < n; ++ i)
    {
        trees.rt[cnt] = i;
        sec[cnt].w = a[i], sec[cnt].L = i, sec[cnt ++].R = i;

        for(; cnt > 1 && sec[cnt - 2].w > sec[cnt - 1].w; -- cnt)
            trees.merge(cnt - 2, cnt - 1), sec[cnt - 2].R = sec[cnt - 1].R, sec[cnt - 2].w = trees.getMedian(cnt - 2);
    }

    long long ans = 0;

    for(int i = 0; i < cnt; ++ i)
        for(int j = sec[i].L; j <= sec[i].R; ++ j)
            ans += abs(a[j] - sec[i].w);

    printf("%lld", ans);
}

正解是左偏树维护中位数, \(O(n \log n)\)

因为我们合并的前提是:中位数(i)>中位数(i+1),那么对于合并后的i而言,中位数肯定是不升的
根据这个性质我们又可以用可并堆了,堆顶元素表示该序列中的中位数
当堆的元素个数*2>序列长度+1的时候就可以弹出堆顶

懒得写代码了.

转载于:https://www.cnblogs.com/ZeonfaiHo/p/6882341.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值