CF1375H Set Merging
题目大意
原题题面写的很好,建议自己去看。
本题题解
朴素的做法是,把区间 ([l_i, r_i]) 里的数值从小到大排序,依次合并。共需要 (mathcal{O}(qn)) 次合并,太多了。
优化上述做法,考虑对值域分块。设块的大小为 (B),分出了 (lceilfrac{n}{B} ceil) 个块。我们在每一块内,把元素按照原序列里的出现位置排序。也就是说,每个块都是原序列的一个子序列。
考虑查询 ([l_i, r_i]) 时,我们从小到大遍历所有块,把每一块里位置在 ([l_i, r_i]) 内的元素提取出来,显然每一块里提取出的一定是一段连续的元素。假设它们的集合是已知的,那么只需要把所有块对应的集合依次合并即可。单次询问需要 (mathcal{O}(frac{n}{B})) 次合并操作。在每个块里提取区间时,可能需要二分,所以总时间复杂度是 (mathcal{O}(qfrac{n}{B}log B))。
那么问题转化为,预处理出每个块内所有区间对应的集合。这样的区间共有 (mathcal{O}(frac{n}{B}cdot B^2) = mathcal{O}(nB)) 个。
每个块单独预处理。在值域上分治。具体来说,我们会发现,原序列的一段区间,在某一段值域里的数,可以表示为该区间的一个子序列。在分治的第一层,这个子序列就是我们的当前块,设它为 (p_1, p_2, dots ,p_{|p|})。下面把值域一分为二:([l, m], [m + 1, r])。那么子序列 (p),又会对应地划分为两个子序列:(u_1, u_2, dots ,u_{|u|}) 和 (v_1, v_2, dots, v_{|v|})。其中 (lleq a_{u_i}leq m),(m < a_{v_i}leq r),并且 (|u| = m - l + 1),(|v| = r - m),(u cup v = p)。
我们有 (mathcal{O}(B^2)) 个区间需要求答案(这个“答案”就是指构造出的一个集合)。递归下去。对每个区间,求出它在 (u) 序列上的答案,和在 (v) 序列上的答案,然后用一次操作合并起来,就能得到它在 (p) 序列上的答案。
朴素实现所需的操作次数是 (mathcal{O}(Blog Bcdot B^2)),因为总共递归地调用了 (mathcal{O}(Blog B)) 次函数,每次对 (mathcal{O}(B^2)) 个集合更新答案。这样太多了。考虑某个需要求答案的区间 ([i, j])(注意这里 (i, j) 指的是位置,即原序列里的下标,而不是数值。(l, r, m) 这些都是数值,我们在值域上做的分治),它在 (p) 序列上的答案,等同于区间 ([p_{i'}, p_{j'}]) 的答案。其中 (p_{i'}) 是 (p) 序列里第一个 (geq i) 的元素,(p_{j'}) 是 (p) 序列里最后一个 (leq j) 的元素。因此我们只需要对 (mathcal{O}(|p|^2)) 个区间求答案(而不是原来的 (mathcal{O}(B^2)) 个)。分析现在的操作次数,设某次调用分治函数时,(r - l + 1 = L),则操作次数为 (T(L) = 2T(frac{L}{2}) + mathcal{O}(L^2)),解得 (T(L) = mathcal{O}(L^2))。所以现在操作次数被优化到 (mathcal{O}(B^2)),可以接受。
还有一个小问题就是找 (i', j')。可以用主席树查询,但没必要。我们从前往后、从后往前扫描两遍 (p) 序列,就能对所有 (i,j in p),推出它们对应的 (i', j')。所以整个分治的时间复杂度也是 (mathcal{O}(B^2))。
对每个块都预处理一次,总共所需操作次数和总时间复杂度都是:(mathcal{O}(frac{n}{B}cdot B^2) = mathcal{O}(nB))。
综上,分析一下所需的操作次数,是 (mathcal{O}(nB + qfrac{n}{B}))。显然取 (B = sqrt{q} = 2^{8}) 时是最优的。操作次数是 (mathcal{O}(nsqrt{q}))。
时间复杂度 (mathcal{O}(nB + qfrac{n}{B}log B) = mathcal{O}(nsqrt{q}log(sqrt{q})))。
总结
上述做法的本质是:将一个序列按值域范围划分成若干子序列之后,原序列的每个连续区间都可以表示成划分成的子序列中的连续区间的并。
分块和分治都是对这样本质的具体实现(分出的块是这样的子序列,分治时处理的 (p) 数组也是这样的子序列)。将它们结合起来,就能得到最优的做法。
参考代码
实际提交时建议使用读入、输出优化,详见本博客公告。
// problem: CF1375H
#include <bits/stdc++.h>
using namespace std;
#define mk make_pair
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 1 << 12;
const int MAXQ = 1 << 16;
const int BLOCK_SIZE = 1 << 8, BLOCK_NUM = MAXN / BLOCK_SIZE;
const int MAXOP = 2200000;
int n, q, a[MAXN + 5];
pii qs[MAXQ + 5];
int ans[MAXQ + 5];
int cnt_block, bel[MAXN + 5];
int cnt_set;
pii op[MAXOP + 5];
int merge(int s1, int s2) {
op[++cnt_set] = mk(s1, s2);
return cnt_set;
}
int rev[MAXN + 5];
int nxtL[MAXN + 5], nxtR[MAXN + 5], preL[MAXN + 5], preR[MAXN + 5];
int tmp_res[BLOCK_SIZE + 5][BLOCK_SIZE + 5];
struct Block {
int L, R; // 值域上的
int pos[BLOCK_SIZE + 5], cnt_pos;
int res[BLOCK_SIZE + 5][BLOCK_SIZE + 5]; // 位置区间 [i,j] 的集合编号
void solve(int l, int r, const vector<int>& p) { // 值域区间 [l, r]
assert(SZ(p) == r - l + 1);
if (l == r) {
res[p[0]][p[0]] = pos[p[0]]; // 这是初始时就有的 n 个大小为 1 的集合之一
return;
}
int mid = (l + r) >> 1;
vector<int> pl, pr;
for (int _i = 0; _i < SZ(p); ++_i) {
int i = p[_i];
assert(a[pos[i]] >= l + L - 1 && a[pos[i]] <= r + L - 1);
if (a[pos[i]] <= mid + L - 1) {
pl.push_back(i);
} else {
pr.push_back(i);
}
}
solve(l, mid, pl);
solve(mid + 1, r, pr);
for (int _i = 0; _i < SZ(p); ++_i) {
int i = p[_i];
if (a[pos[i]] <= mid + L - 1) {
preL[_i] = _i;
preR[_i] = (_i == 0 ? -1 : preR[_i - 1]);
} else {
preR[_i] = _i;
preL[_i] = (_i == 0 ? -1 : preL[_i - 1]);
}
}
for (int _i = SZ(p) - 1; _i >= 0; --_i) {
int i = p[_i];
if (a[pos[i]] <= mid + L - 1) {
nxtL[_i] = _i;
nxtR[_i] = (_i == SZ(p) - 1 ? SZ(p) : nxtR[_i + 1]);
} else {
nxtR[_i] = _i;
nxtL[_i] = (_i == SZ(p) - 1 ? SZ(p) : nxtL[_i + 1]);
}
}
for (int _i = 0; _i < SZ(p); ++_i) {
int i = p[_i];
for (int _j = _i; _j < SZ(p); ++_j) {
int j = p[_j];
tmp_res[i][j] = res[i][j];
}
}
for (int _i = 0; _i < SZ(p); ++_i) {
int i = p[_i];
for (int _j = _i; _j < SZ(p); ++_j) {
int j = p[_j];
#define x p[_x]
#define y p[_y]
if (a[pos[i]] <= mid + L - 1 && a[pos[j]] <= mid + L - 1) {
int _x = nxtR[_i];
int _y = preR[_j];
if (_x <= _y) {
res[i][j] = merge(tmp_res[i][j], tmp_res[x][y]);
}
} else if (a[pos[i]] > mid + L - 1 && a[pos[j]] > mid + L - 1) {
int _x = nxtL[_i];
int _y = preL[_j];
if (_x <= _y) {
res[i][j] = merge(tmp_res[x][y], tmp_res[i][j]);
}
} else if (a[pos[i]] <= mid + L - 1 && a[pos[j]] > mid + L - 1) {
int _x = nxtR[_i];
assert(_x <= _j);
int _y = preL[_j];
assert(_y >= _i);
res[i][j] = merge(tmp_res[i][y], tmp_res[x][j]);
} else if (a[pos[i]] > mid + L - 1 && a[pos[j]] <= mid + L - 1) {
int _x = nxtL[_i];
assert(_x <= _j);
int _y = preR[_j];
assert(_y >= _i);
res[i][j] = merge(tmp_res[x][j], tmp_res[i][y]);
}
#undef x
#undef y
}
}
}
void build(int _L, int _R) {
L = _L;
R = _R;
cnt_pos = 0;
for (int i = 1; i <= n; ++i) {
if (a[i] >= L && a[i] <= R) {
pos[++cnt_pos] = i;
rev[a[i]] = cnt_pos;
}
}
assert(cnt_pos == R - L + 1);
vector<int> p;
for (int i = 1; i <= cnt_pos; ++i) {
p.push_back(i);
}
solve(1, cnt_pos, p);
}
int query(int l, int r) {
int x = lower_bound(pos + 1, pos + cnt_pos + 1, l) - pos;
int y = upper_bound(pos + 1, pos + cnt_pos + 1, r) - pos - 1;
if (x > y) return 0;
return res[x][y];
}
Block() {}
} blo[BLOCK_NUM + 5];
int main() {
cin >> n >> q;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
cnt_set = n; // 初始时有 n 个集合
for (int i = 1; i <= n; i += BLOCK_SIZE) {
int j = min(i + BLOCK_SIZE - 1, n);
++cnt_block;
for (int k = i; k <= j; ++k) {
bel[k] = cnt_block;
}
blo[cnt_block].build(i, j);
}
for (int i = 1; i <= q; ++i) {
cin >> qs[i].fi >> qs[i].se;
int l = qs[i].fi, r = qs[i].se;
ans[i] = 0;
for (int b = 1; b <= cnt_block; ++b) {
int s = blo[b].query(l, r);
if (!s)
continue;
if (!ans[i]) {
ans[i] = s;
} else {
ans[i] = merge(ans[i], s);
}
}
}
cout << cnt_set << endl;
for (int i = n + 1; i <= cnt_set; ++i) {
cout << op[i].fi << " " << op[i].se << endl;
}
for (int i = 1; i <= q; ++i) {
cout << ans[i] << "
"[i == q];
}
return 0;
}