原题链接
一些题外话
这是我做的第一道 Ynoi,做完这题后我深深感受到了 lxl 题目的毒瘤。
做题时曾受神犇 rui_er 题解的启发。
题意
给定一个长为 $n$ 的序列 $a$。
有 $m$ 个询问,每次询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。
注意这里删掉指的是一个一个删,不是把等于这个值的数直接删完,比如三个区间是 $[1,2,2,3,3,3,3]$,$[1,2,2,3,3,3,3]$ 与 $[1,1,2,3,3]$,就一起扔掉了 $1$ 个 $1$,$1$ 个 $2$,$2$ 个 $3$。
数据范围:$1\leq n , m \leq 10^5$,$1 \leq a_i\leq 10^9$,$1\leq l_1,r_1,l_2,r_2,l_3,r_3\leq n$,$l_1\leq r_1$,$l_2\leq r_2$,$l_3\leq r_3$。
时间限制:$3.0$ 秒
空间限制:$512\text{MiB}$
题解
首先 $a_i \le 10^9$,但是我们只在意 $a_i$ 的种类,因此先将其离散化。
显然我们不必同时求出这 $3$ 个区间内的所有数字。我们可以将 $3 \times m$ 个区间顺序打乱,以较快的速度将这堆区间一并求出。
看到这里,应该都知道这时用莫队来解决问题就可以了。至于每个区间的答案,我们可以用 bitset
存储,
我们可以先将三个区间的长度和相加,那么对于每个询问,答案就是三个区间的长度和,减去 $3$ 倍的三个可重集的交集。
如果每个子区间内所有元素互不相等,那这很好办,我们把数字变成下标,放入一个 bitset
内,求交集时直接做与运算,时间复杂度是 $\mathcal{O}(\frac{nm}{ω})$
但是实际上元素会重复,我们需要想出一种新的办法,将每个数字映射到 bitset
内,使得每个数字都可能被记录到。
发现相同的元素不收到插入顺序的影响。因此有一种映射的办法,举例如下:
- 先将元素排序,例如 $[1,2,2,3,3,3,3]$。
- 记录每个不同元素的初始位置:$1; \ 2; \ 4$。
- 现在我们记录数字 $1$,此时在
bitset
中就在第 $1$ 个位置标记。 - 之后记录数字 $2$,此时在
bitset
中就在第 $2$ 个位置标记。 - 接着记录数字 $3$,此时在
bitset
中就在第 $4$ 个位置标记(注意第 $3$ 个位置是 $2$ 的地盘,不能碰)。 - 接下来,我们再记一个 $3$。注意到第 $4$ 个位置被占据了,且
bitset
中之前存了 $1$ 次 $3$,那么这次将 $3$ 存入位置 $4+1=5$。 - 最后如果再插入一个 $3$,因为之前 $3$ 出现了 $2$ 次,所以这次存入位置 $4+2=6$。
流程大概就是这样,莫队算法过程中删除数字的步骤也同理。
我们得到了每个区间的 bitset
,就可以拿来取交集了。这个步骤复杂度是有保证的,为 $\frac{n}{ω}$,$ω$ 为 $32$ 或 $64$。
但是还剩了一个问题——记录所有询问的 bitset
需要空间为 $\frac{n \times m}{8}$ 字节,存不下!
这时我们就需要考虑重复利用 bitset
了。观察到不同的询问不会互相干扰,因此我们可以将 $m$ 个询问分成 $S$ 个小组,每小组询问分别跑一次莫队。这里 $S=3$ 或 $4$ 就可以了。
这样就解决了空间不够的问题。假设不考虑分组而浪费的时间,时间复杂度为 $\mathcal{O}(\frac{nm}{ω}+m \sqrt{n})$,$ω$ 为 $32$ 或 $64$。
实现代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int INF = 1e9, N = 100010; struct S{ int l, r, id, stp; }s[N]; int n, M, a[N], bb[N], cnt, fir[N], sz[N], siz, SZ, blk[N], ans[N], len[N], vis[N]; bitset <100010> b[20010], now, res; void add(int x){ x = a[x]; now[fir[x] + sz[x]] = 1; sz[x]++; } void del(int x){ x = a[x]; now[fir[x] + sz[x] - 1] = 0; sz[x]--; } bool cmp(S x, S y){ if(blk[x.l] == blk[y.l]) return x.r > y.r; return blk[x.l] < blk[y.l]; } void solve(int m){ for(int i = 1; i <= m; i++){ scanf("%d%d%d%d%d%d", &s[i * 3 - 2].l, &s[i * 3 - 2].r, &s[i * 3 - 1].l, &s[i * 3 - 1].r, &s[i * 3].l, &s[i * 3].r); s[i * 3 - 2].id = i, s[i * 3 - 2].stp = 0; s[i * 3 - 1].id = i, s[i * 3 - 1].stp = 1; s[i * 3].id = i, s[i * 3].stp = 2; } now.reset(), siz = (int)sqrt(m * 3); for(int i = 1; i <= m * 3; i++) len[i] = vis[i] = 0, blk[i] = (i - 1) / siz + 1; for(int i = 1; i <= M; i++) sz[i] = 0; sort(s + 1, s + m * 3 + 1, cmp); for(int i = 1, L = 1, R = 0; i <= 3 * m; i++){ while(L > s[i].l) L--, add(L); while(R < s[i].r) R++, add(R); while(L < s[i].l) del(L), L++; while(R > s[i].r) del(R), R--; if(!vis[s[i].id]) b[s[i].id] = now; // 这里不分开记 3 个区间的 bitset,而是记住初始编号,之后在同一个 bitset 内与运算,节省空间 else b[s[i].id] &= now; vis[s[i].id] = 1; len[s[i].id] += s[i].r - s[i].l + 1; } for(int i = 1; i <= m; i++) printf("%d\n", len[i] - 3 * b[i].count()); } int main(){ int m; scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) scanf("%d", &a[i]), bb[i] = a[i]; sort(bb + 1, bb + n + 1); for(int i = 1; i <= n; i++) if(bb[i] != bb[i - 1]) fir[++cnt] = i; M = unique(bb + 1, bb + n + 1) - bb - 1; for(int i = 1; i <= n; i++) a[i] = lower_bound(bb + 1, bb + M + 1, a[i]) - bb; while(m >= 1) solve(m > 20000 ? 20000 : m), m -= 20000; // 这里最多分 5 组处理 return 0; }