P3992 [BJOI2017]开车

题目链接

题目描述

n n n 辆车,分别在 a 1 , a 2 , … , a n a_1, a_2, \ldots , a_n a1,a2,,an 位置和 n n n 个加油站,分别在 b 1 , b 2 , … , b n b_1, b_2, \ldots ,b_n b1,b2,,bn 位置。

每个加油站只能支持一辆车的加油,所以你要把这些车开到不同的加油站加油。一个车从 x x x 位置开到 y y y 位置的代价为 ∣ x − y ∣ |x-y| xy,问如何安排车辆,使得代价之和最小。

同时你有 q q q 个操作,每次操作会修改第 i i i 辆车的位置到 x x x,你要回答每次修改操作之后最优安排方案的总代价。

输入格式

第一行一个正整数 n n n

接下来一行 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2,\ldots,a_n a1,a2,,an

接下来一行 n n n 个整数 b 1 , b 2 , … , b n b_1, b_2,\ldots,b_n b1,b2,,bn

接下来一行一个正整数 q q q ,表示操作的个数。

接下来 q q q 行,每行有两个整数 i i i 1 ≤ i ≤ n 1\leq i\leq n 1in)和 x x x,表示将 i i i这辆车开到 x x x 位置的操作。

所有的车和加油站的范围一直在 0 0 0 1 0 9 10^9 109 之间。

输出格式

q + 1 q+1 q+1 行,第一行表示一开始的最优代价。

接下来 q q q 行,第 i i i 行表示操作 i i i 之后的最优代价。

输入输出样例

输入 #1

2
1 2
3 4
1
1 3

输出 #1

4
2

说明/提示

【样例解释】

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 5 × 1 0 4 1\leq n\leq 5\times 10^4 1n5×104 0 ≤ q ≤ 5 × 1 0 4 0\leq q\leq 5\times 10^4 0q5×104

首先显然将数组 a , b a,b a,b 排序,此时 ∑ i = 1 n ∣ a i − b i ∣ \sum\limits_{i=1}^n|a_i-b_i| i=1naibi 一定是最优解。考虑怎么动态维护这个值。
这里用到了均分纸牌问题的证明思想。
先将数据中出现过的位置离散化,设 w i w_i wi 表示位置 i i i 到位置 i + 1 i+1 i+1 的距离;设 n u m a i , n u m b i numa_i,numb_i numai,numbi 分别表示数组 a , b a,b a,b 中小于等于 i i i 的元素数量,则会有 ∣ n u m a i − n u m b i ∣ |numa_i-numb_i| numainumbi 数量的车经过位置 i i i 和位置 i + 1 i+1 i+1 ,即对答案的贡献为 ∣ n u m a i − n u m b i ∣ ⋅ w i |numa_i-numb_i|\cdot w_i numainumbiwi 。 因此答案为 ∑ i = 1 n ∣ n u m a i − n u m b i ∣ ⋅ w i \sum\limits_{i=1}^n|numa_i-numb_i|\cdot w_i i=1nnumainumbiwi
对于修改操作,将 a i a_i ai 修改为 x x x 仅对上式中 n u m a numa numa 造成影响:

  • 如果 a i > x a_i>x ai>x ,则 n u m a x ∼ a i − 1 numa_{x\sim a_i-1} numaxai1 会增加 1 1 1
  • 如果 a i < x a_i<x ai<x ,则 n u m a a i ∼ x − 1 numa_{a_i\sim x-1} numaaix1 会减少 1 1 1

因此需要使用一种数据结构维护 n u m a 1 numa_1 numa1 的区间加法和 ∑ i = 1 n ∣ n u m a i − n u m b i ∣ ⋅ w i \sum\limits_{i=1}^n|numa_i-numb_i|\cdot w_i i=1nnumainumbiwi 的快速计算。
这里考虑按位置分块,假定块的大小为 T T T
然后维护动态当前 ∑ i = 1 n ∣ n u m a i − n u m b i ∣ ⋅ w i \sum\limits_{i=1}^n|numa_i-numb_i|\cdot w_i i=1nnumainumbiwi 的值 a n s ans ans

每次区间修改:

  • 对于存在于区间中的完整的块 x x x ,将标记 a d d x add_x addx 加上修改的值。由于事先将块中的 n u m a i − n u m b i numa_i-numb_i numainumbi 排序,并按排序后的顺序求出 ( n u m a 1 − n u m b i ) ⋅ w i (numa_1-numb_i)\cdot w_i (numa1numbi)wi w i w_i wi 的前缀和 s u m i , p r e w i sum_i,prew_i sumiprewi ,因此可以先通过二分以 O ( log ⁡ T ) O(\log T) O(logT) 复杂度快速找出 s u m a i + a d d x − s u m b i suma_i+add_x-sumb_i sumai+addxsumbi 的正负分界线,从而计算出该块对答案的贡献值,更新 a n s ans ans 。由于最坏情况需要更新 O ( n T ) O(\frac{n}{T}) O(Tn) 个块,因该部分时间复杂度为 O ( n T log ⁡ T ) O(\frac{n}{T}\log T) O(TnlogT)
  • 对于不完整的块暴力计算更新 n u m a i numa_i numai 并更新 a n s ans ans ,然后在块中按照新的 n u m a i − n u m b i numa_i-numb_i numainumbi 排序,从而更新 s u m sum sum p r e w prew prew 。该部分时间复杂度为 O ( T log ⁡ T ) O(T\log T) O(TlogT)

总时间复杂度为 O ( n log ⁡ n + q log ⁡ T ( n T + T ) ) O(n\log n+q\log T (\frac{n}{T}+T)) O(nlogn+qlogT(Tn+T)) ,令 T = n T=\sqrt n T=n ,则时间复杂度为 O ( n log ⁡ n + q n log ⁡ n ) O(n\log n+q\sqrt n\log \sqrt n) O(nlogn+qn logn )

#include <bits/stdc++.h>

#define get(x) (numa[id[x]]-numb[id[x]])
using namespace std;
typedef long long ll;
const int N = 5e4 + 10, M = 400;
int n, m, q, t, len, now;
int L[M], R[M], add[M], a[N], b[N];
int pos[N * 3], numa[N * 3], numb[N * 3], w[N * 3], id[N * 3];
ll sum[N * 3], prew[N * 3], ans, cur[M];
vector<int> seq;
struct {
    int i, x;
} p[N];

inline ll calc(int x) {
    if (get(L[x]) + add[x] >= 0)return sum[R[x]] + add[x] * prew[R[x]];
    if (get(R[x]) + add[x] < 0) return -(sum[R[x]] + add[x] * prew[R[x]]);
    int l = L[x], r = R[x];
    while (l < r) {
        int mid = (l + r) >> 1;
        get(mid) + add[x] >= 0 ? (r = mid) : (l = mid + 1);
    }
    return sum[R[x]] - (sum[l - 1] << 1) + (prew[R[x]] - (prew[l - 1] << 1)) * add[x];
}

inline void update(int x, int l, int r, int v) {
    for (int i = l; i <= r; i++) {
        ans -= abs(1ll * (numa[i] - numb[i] + add[x]) * w[i]);
        cur[x] -= abs(1ll * (numa[i] - numb[i] + add[x]) * w[i]);
        numa[i] += v;
        ans += abs(1ll * (numa[i] - numb[i] + add[x]) * w[i]);
        cur[x] += abs(1ll * (numa[i] - numb[i] + add[x]) * w[i]);
    }
    sort(id + L[x], id + R[x] + 1, [&](int i, int j) {
        return numa[i] - numb[i] < numa[j] - numb[j];
    });
    for (int i = L[x]; i <= R[x]; i++) {
        sum[i] = (i == L[x] ? 0 : sum[i - 1]) + 1ll * get(i) * w[id[i]];
        prew[i] = (i == L[x] ? 0 : prew[i - 1]) + w[id[i]];
    }
}

inline void change(int l, int r, int x) {
    int bl = pos[l], br = pos[r];
    if (bl == br) update(bl, l, r, x);
    else {
        update(bl, l, R[bl], x), update(br, L[br], r, x);
        for (int i = bl + 1; i <= br - 1; i++)
            add[i] += x, ans -= cur[i], ans += (cur[i] = calc(i));
    }
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)scanf("%d", &a[i]), seq.push_back(a[i]);
    for (int i = 1; i <= n; i++)scanf("%d", &b[i]), seq.push_back(b[i]);
    scanf("%d", &q);
    for (int i = 1; i <= q; i++)scanf("%d%d", &p[i].i, &p[i].x), seq.push_back(p[i].x);
    sort(seq.begin(), seq.end()),seq.erase(unique(seq.begin(), seq.end()), seq.end());
    for (int i = 1; i <= n; i++) {
        a[i] = lower_bound(seq.begin(), seq.end(), a[i]) - seq.begin() + 1;
        b[i] = lower_bound(seq.begin(), seq.end(), b[i]) - seq.begin() + 1;
    }
    for (int i = 1; i <= q; i++)
        p[i].x = lower_bound(seq.begin(), seq.end(), p[i].x) - seq.begin() + 1;
    for (int i = 1; i <= n; i++)numa[a[i]]++, numb[b[i]]++;
    m = (int) seq.size(), len = (int) sqrt(m);
    for (int i = 1; i <= m; i++) {
        if (i != m)w[i] = seq[i] - seq[i - 1];
        numa[i] = numa[i - 1] + numa[i];
        numb[i] = numb[i - 1] + numb[i];
    }
    while (now + len <= m)L[++t] = now + 1, R[t] = now + len, now += len;
    if (now < m)L[++t] = now + 1, R[t] = m;
    iota(id + 1, id + 1 + m, 1);
    for (int i = 1; i <= t; i++) {
        sort(id + L[i], id + R[i] + 1, [&](int x, int y) {
            return numa[x] - numb[x] < numa[y] - numb[y];
        });
        for (int j = L[i]; j <= R[i]; pos[j++] = i) {
            sum[j] = (j == L[i] ? 0 : sum[j - 1]) + 1ll * get(j) * w[id[j]];
            prew[j] = (j == L[i] ? 0 : prew[j - 1]) + w[id[j]];
            cur[i] += abs(1ll * (numa[j] - numb[j]) * w[j]);
        }
        ans += cur[i];
    }
    printf("%lld\n", ans);
    for (int i = 1; i <= q; i++) {
        if (a[p[i].i] > p[i].x)change(p[i].x, a[p[i].i] - 1, 1);
        else change(a[p[i].i], p[i].x - 1, -1);
        a[p[i].i] = p[i].x;
        printf("%lld\n", ans);
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_sky123_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值