BJOI2017 开车

总复杂度 \(O(n\sqrt{n})\) 的算法,跑到落谷 \(rk2\)

一道题调了一年系列。

题目链接

Description

\(n\) 个车的坐标 \(a_i\)\(n\) 个加油站的坐标 \(b_i\)\(m\) 次修改操作。

  • 每次修改,将第 \(i\) 辆车的位置修改到 \(x\)

  • 在初始情况及每次修改后:要求不重不漏给每一个车匹配一个停车场(即可以将 \(a\) 数组打乱顺序),最小化 \(\sum_{i=1}^{n} |a_i - b_i|\),输出这个最小值。

Solution

比较显然的是车和加油站其实在询问的时候是对称的,具有对称美。

暴力思想

这种最小化曼哈顿距离和的问题我们比较熟悉的做法是把 \(a, b\) 都暴力 \(\text{sort}\) 一遍,对位相减,这里用邻项交换的放法给出一个理性的证明:

\(a, b\ (a \le b)\) 作为两个车坐标 ,\(c, d\ (c \le d)\) 作为两个加油站的坐标,即证明 \(a \Rightarrow c, b \Rightarrow d\) 第一种匹配方式的答案不会比第二种匹配方式 \(a \Rightarrow d, b \Rightarrow c\) 差,这样见到一个无序数组我们将其排序答案不会变差,即完成证明。

  1. \(a \le b \le c \le d\) 时:

    • 第一种方式代价:\(c - a + d - b = -a-b+c+d\)
    • 第二种方式代价:\(d - a + c - b = -a-b+c+d\)
    • 这种情况下两者代价相等
  2. \(a \le c \le b \le d\) 时:

    • 第一种方式代价:\(c - a + d - b = -a-b+c+d\)
    • 第二种方式代价:\(d - a + b - c = -a+b-c+d\)
    • \(c \le b\) 可知 \(-b+c \le 0 \le b - c\),故前者一定不差于后者。
  3. \(c \le d \le a \le b\) 时:

    • 第一种方式代价:\(a - c + b - d = a + b - c - d\)
    • 第二种方式代价:\(a - d + b - c = a + b - c - d\)
    • 这种情况下两者代价相等
  4. \(c \le a \le d \le b\) 时:

    • 第一种方式代价:\(a - c + b - d = a + b - c - d\)
    • 第二种方式代价:\(d - a + b - c = - a + b - c + d\)
    • \(a \le d\) 可知 \(a - d \le 0 \le -a +d\),故前者一定不差于后者。

已经不是简要证明了,以后还是记住这个性质吧

感性理解一下,当车与加油站没有交叉时,怎么搞都无所谓,因为总要从一个车出发扑通扑通跑到一个停车场,而坐标是定值所以相等。当有交叉时,抽象的理解为一个车向右走(或者车库向右是对称问题),遇到的第一个停车场就停下来,否则如果继续跑,但总有一个车要匹配这个停车场,所以如果右边的车匹配过来就不是最优了。(感觉还是图比较好理解,但是我懒得画了)。

暴力每次修改要 \(\text{sort}\) 一次,所以复杂度是 \(O(nq)\) 的,需要优化。

优化

考虑对于一次修改,原来位置和现在位置所形成的区间内的车都循环移位了,如果要用数据结构维护 \(|a[i] - b[i]|\),似乎不太可能,因为是对位相减取绝对值,不符合结合律,也没法用分块之类的毒瘤数据搞(就是说对于每一组数据都要看他的正负性然后取值,但是呢一一看就是暴力了。。。)。

算贡献!

考虑换一种统计答案的方式:算贡献。用每条边的贡献(经过这条边的车的数量)来进行计算答案。

将数据离散化(设该数组为 \(d\),设 \(W_i = d_{i + 1} - d_i\),可理解为从 \(i\) 走到 \(i + 1\) 的实际距离 )后,考虑先处理两个:\(SumA_i, SumB_i\),分别表示在 \([1, i]\) 位置的区间内用多少个车 \(/\) 停车场(即前缀和数组),令 \(S_i = |SumA_i - SumB_i|\),答案就是 \(\sum S_i W_i\),或者说 \(\sum |(SumA_i - SumB_i) \times W_i|\)

怎么理解这个东西呢?\(S_i\) 的实际意义是走 \(i \Rightarrow i + 1\) 这条边的车的数量。感性理解一下,有 \(\min(SumA_i,SumB_i)\) 已经在 \(i\) 位置之前完成了互相匹配,但是存在 \(|SumA_i - SumB_i|\) 在之前无车 / 加油站匹配,所以一定要经过这条边,到 \(i\) 位置之后寻找匹配。

经过这部转化,考虑修改一次的影响(设原位置为 \(x\) ,新位置为 \(y\)):

  • \(x < y\),即给 \(SumA\)\([x, y - 1]\) 区间带来 \(-1\) 偏移量
  • \(x > y\) 即给 \(SumA\)\([y, x - 1]\) 区间带来 \(+1\) 的偏移量

那么问题转化为了一个数据结构,支持:

  • \(A\) 数组区间加和修改
  • 对应位置询问 \(|A_i-B_i|\)

不会做。不能用线段树维护的原因在于,每次加入一个数时,你无法区分每个数的数值 \(\ge 0\) 还是 \(< 0\),你又没发把数组排序做到有序然后可以二分这个东西。(即没有区间零界限的能力,因为数值是不断增加的)。。

经过上面的思考加上 \(n, q \le 50000\) 的提示,应该这是个分块。秉承大块朴素,小块暴力的思想,考虑在存在 \(\text{tag}\) 标记的情况下,如何快速找到 \(0\) 界点呢?我们有两个思路:

  • 修改的时候,大块打 \(\text{tag}\),小块按照下方进行暴力重构。每个块里面预处理按原值 \(A_i - B_i\)\(\text{sort}\) 一下,之后查询二分 \(0\) 的位置,前面取反,后面不变,相加即可。这也是目前大多题解的做法,时间复杂度 \(O(n\sqrt{n} \log n)\)
  • 修改的时候,大块打 \(\text{tag}\),小块按照下方进行暴力重构。每个块里,把值域当下标搞一个桶

由于大多数题解都是第一种写法这里也就说说 & 写写第二种方法。

\((SumA_i - SumB_i)\) 当做下标,值 \((SumA_i - SumB_i) \times W_i\) 打进桶里,把桶再搞个前缀和,即 \(cnt_i\) 表示 \(\le i\) 的数的和 。同理,把边权单独搞出来弄前缀和形成 \(g_i\)(这个东西为了计算 \(\text{tag}\) 的应先规定)。询问枚举每个块,设当前的 \(\text{tag}\) 标记为 \(x\),答案即 \(-cnt[-x] - x \times g[-x] + (cnt[INF] - cnt[x]) + x \times (g[INF] - G[-x])\)\(0\) 前面取反,两部分加和,在各自记录。

但是由于这题内存紧,所以开不下 \(n\sqrt{n}\) 的数组大小,不妨考虑每次重构块时的有值域的区间平移一下,用 \(\text{vector}\) 搞即可,有一种感性的理解,这样搞总空间是 \(O(n)\) 的。因为考虑只有一个位置加入车 / 停车场,值域才会 \(+1/-1\),而全数组最多加减 \(n\) 次,所以总空间 \(O(n)\)

时空复杂度

\(O(n\sqrt{n})\)

Tips

  • 这题中间出现的位置可能之前没有导致离散化找不到,所以可以离线先读进来离散化,再搞搞。

  • 由于有负数的值,搞桶的时候搞个偏移量。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <vector>
#include <algorithm>
#define rint register int
using namespace std;

typedef long long LL;

const int N = 50005, S = 390;

int n, Q, t, a[N], b[N], d[N * 3], tot;
int pos[N * 3], L[S], R[S], len[S], Min[S], Max[S], tag[S], sum[N * 3];

// sum[i] = sumA[i] - sumB[i]

vector<LL> cnt[S];
vector<int> g[S];

struct Q{
	int i, x;
} q[N];

int inline get(int x) {
	return lower_bound(d + 1, d + 1 + tot, x) - d;
}

// 建立 / 重构块为 i 的 id
void inline build(int id) {
	Min[id] = 2e9, Max[id] = 0;
	for (rint i = L[id]; i <= R[id]; i++) Min[id] = min(Min[id], sum[i]), Max[id] = max(Max[id], sum[i]);
	len[id] = Max[id] - Min[id] + 1;
	cnt[id].clear(); g[id].clear();
	cnt[id].resize(len[id], 0); g[id].resize(len[id], 0);
	for (rint i = L[id]; i <= R[id]; i++) {
		int v = sum[i] - Min[id];
		cnt[id][v] += (LL)sum[i] * (d[i + 1] - d[i]);
		g[id][v] += d[i + 1] - d[i];
	}
	for (rint i = 1; i < len[id]; i++) 
		cnt[id][i] += cnt[id][i - 1], g[id][i] += g[id][i - 1];
}

// 修改操作
void inline change(int l, int r, int k) {
	if (pos[l] == pos[r]) {
		for (rint i = l; i <= r; i++) sum[i] += k;
		build(pos[l]);
		return;
	}
	int p = pos[l], q = pos[r];
	for (rint i = p + 1; i < q; i++) tag[i] += k;
	for (rint i = l; i <= R[p]; i++) sum[i] += k;
	for (rint i = L[q]; i <= r; i++) sum[i] += k;
	build(p); build(q);
}

// 查询操作
LL inline calc() {
	LL res = 0;
	for (rint i = 1; i <= t; i++) {
		int x = min(len[i] - 1, max(-1, - Min[i] - tag[i]));
		if (x != -1) res += abs(cnt[i][x] + (LL)tag[i] * g[i][x]);
		if (x != -1) res += abs((cnt[i][len[i] - 1] - cnt[i][x]) + (LL)tag[i] * (g[i][len[i] - 1] - g[i][x]));
		else res += abs(cnt[i][len[i] - 1] + (LL)tag[i] * g[i][len[i] - 1]);
	}
	return res;
}

int main() {
	// 读入 + 离散化
	scanf("%d", &n);
	for (rint i = 1; i <= n; i++) scanf("%d", a + i), d[++tot] = a[i];
	for (rint i = 1; i <= n; i++) scanf("%d", b + i), d[++tot] = b[i];
	scanf("%d", &Q);
	for (rint i = 1; i <= Q; i++)
		scanf("%d%d", &q[i].i, &q[i].x), d[++tot] = q[i].x;
	sort(d + 1, d + 1 + tot);
	tot = unique(d + 1, d + 1 + tot) - d - 1;
	d[tot + 1] = d[tot];
	for (rint i = 1; i <= n; i++) sum[a[i] = get(a[i])]++, sum[b[i] = get(b[i])]--;
	for (rint i = 2; i <= tot; i++) sum[i] += sum[i - 1];
	// 分块
	t = sqrt(tot);
	for (rint i = 1; i <= t; i++) L[i] = (i - 1) * t + 1, R[i] = i * t;
	if (R[t] < tot) R[t] = tot;
	for (rint i = 1; i <= t; i++) {
		for (rint j = L[i]; j <= R[i]; j++) pos[j] = i;
		build(i);
	}
	printf("%lld\n", calc());
	for (rint i = 1; i <= Q; i++) {
		int id = q[i].i, x = a[id], y = get(q[i].x);
		if (x < y) change(x, y - 1, -1);
		else if (x > y) change(y, x - 1, 1);
		a[id] = y;
		printf("%lld\n", calc());
	}
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/dmoransky/p/12578757.html