前言

感觉这道题的结论好神。。。这里介绍两种做法 QwQ。

法一

法一就是用左偏树,好了咱们继续。

Solution

首先有一个奇妙的处理:由于我们的 b 序列是严格递增的,为了方便之后的计算,在输入的时候先将 \(\mathtt{a[i]}\) 减去 i。

为什么呢?我们发现,a 数组的每个元素都比自己前面一位的元素多减去 1。所以现在每个 \(\mathtt{a[i]}\) 对应的 \(\mathtt{b[i]}\) 其实都是减了 i 的。想一想,如果在没减 i 之前,有 \(\mathtt{b[i]==b[i+1]}\),那么减去 i 之后就有 \(\mathtt{b[i]==b[i+1]+1}\),由于我们保证有 \(\mathtt{b[i]<b[i+1]}\),这种情况也就自然地规避了。

然后这里放上黄源河的论文《左偏树的特点及其应用》节选。


如果看懂了可以直接往下跳,如果没看懂,就来看看我的口胡吧。毫无防备地流下了属于真正弱者的眼泪。

先放图。(注意这是证明情况二)

假设我们将 a 数组分成了这样几个点(相同颜色代表是一组)。如果我们选取中位数的话,那所需的花费不就是所有的两个一样的颜色之间的距离吗?

我们考虑 b 数组递增的情况(b 数组相同的话就是初一的知识了 QwQ)。

如果在整个大区间里,显然这是没有中位数优的。(当然可以有相等的情况,如果对对相等且位置对头,花费和中位数实际上是一样的)

在外面晃荡就更不可能了。

如果是两者合并的话也可以用同样的方式证明。

Code

因为套了模板,代码有点长。。。

#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;

const int N = 1e6 + 5;

int n, head[N], dot[N << 1], nxt[N << 1], cnt, a[N];

struct LT {
    int f[N], son[N][2], dis[N], siz[N], l[N], r[N], tp;
    ll val[N];
    void init(const int n) {
        for(int i = 1; i <= n; ++ i) {
            son[i][0] = son[i][1] = dis[i] = 0;
            f[i] = i; siz[i] = 1; l[i] = r[i] = i;
        }
    }

    int Find(const int x) {return x == f[x] ? x : f[x] = Find(f[x]);}

    bool cmp(const int x, const int y) {return Find(x) == Find(y);}

    int unite(int x, int y) {
        if(! x || ! y) return x + y;
        if(a[x] < a[y]) swap(x, y);
        son[x][1] = unite(son[x][1], y); f[son[x][1]] = x;
        if(dis[son[x][0]] < dis[son[x][1]]) swap(son[x][0], son[x][1]);
     }

    int del(const int x) {
        int l = son[x][0], r = son[x][1];
        son[x][0] = son[x][1] = dis[x] = 0;
        f[l] = l; f[r] = r;
        return unite(l, r);
    }

    void solve() {
        ll ans = 0;
        for(int i = 1; i <= n; ++ i) {
            ++ tp;
            f[tp] = l[tp] = r[tp] = i; siz[tp] = 1; val[tp] = a[i];
            while(tp != 1 && val[tp - 1] > val[tp]) {
                -- tp; f[tp] = unite(f[tp], f[tp + 1]);
                siz[tp] += siz[tp + 1]; r[tp] = r[tp + 1];
                while(siz[tp] > (r[tp] - l[tp] + 2) / 2)//因为只有后面的中位数小于前面的才合并,所以大于中位数的值没有用了!!!
                    -- siz[tp], f[tp] = del(f[tp]);
                val[tp] = a[f[tp]];
            }
        }
        tp = 1;
        for(int i = 1; i <= n; ++ i) {
            if(r[tp] < i) ++ tp;
            ans += abs((double) val[tp] - a[i]);
        }
        printf("%lld\n", ans);
        tp = 1;
        for(int i = 1; i <= n; ++ i) {
            if(r[tp] < i) ++ tp;
            printf("%lld ", val[tp] + i);
        }
    }
}T;

ll read() {
    ll x = 0, f = 1; char s;
    while((s = getchar()) < '0' || s > '9') if(s == '-') f = -1;
    while(s >= '0' && s <= '9') {x = (x << 1) + (x << 3) + (s ^ 48); s = getchar();}
    return x * f;
}

int main() {
    n = read();
    for(int i = 1; i <= n; ++ i) a[i] = read() - i;
    T.solve();
    return 0;
}

法二

看到大佬用的 STL,写一写。留坑待填

\(\text{Update on 2022.8.16}\):就是 \(\text{Slope Trick}\) 啦,暂时设置了密码,想看可以私信找我要~

posted on 2020-04-03 13:47  Oxide  阅读(165)  评论(0编辑  收藏  举报