抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

题意

原题链接

给定一个长度为\(n\)的序列,\(q\)次询问\(l,r\),求原序列\([l,r]\)中每个数的出现次数组成的集合\(S\)\(\text{MEX}\)

集合\(S\)\(\text{MEX}\)是指最小的没有在集合\(S\)中出现的正整数。

\(n,q\le 1.5\times 10^5\)

题解

一个简单的暴力实现

首先离散,对于每个询问,按题意求出每个数的出现次数,再求出每个出现次数是否出现,然后枚举求出答案即可。

时间复杂度\(\mathcal O(nq)\)

基于暴力思想的分块做法

显然答案不会超过\(\sqrt{2n}\),那么一个显然的优化是只需要记录所有\(\le \sqrt{2n}\)的出现次数。

于是就可以很自然地想到分块了。分成\(\sqrt{n}\)个块(在代码中为了卡常、卡内存可以作微调),直接暴力求出两块之间所有数的出现次数的出现次数(注意不是是否出现),记为\(V[x][y][i]\),表示\(x\)块到\(y\)块中出现次数\(i\)的出现次数。这个可以很轻松的在扫的过程中求出来(--c[b[a[i]]],++b[a[i]],++c[b[a[i]]]\(b[i]\)表示数\(i\)的出现次数,\(c[i]\)表示出现次数\(i\)的出现次数。即把原来的出现次数去掉,出现次数加一,把新的出现次数加入)。这一部分时间复杂度是\(\mathcal O(n\sqrt{n})\)的。

可是我们发现,只求出这个好像还是有点难回答询问。为什么?因为这只求出了整块的答案,而旁边的两小块没有被处理。

我们再求出\(L[x][y][i]\)表示\(x\)块到\(y\)块中,\(x\)块向左\(i\)个位置的数出现了多少次,同理\(R[x][y][i]\)表示\(x\)块到\(y\)块中,\(y\)块向右\(i\)个位置的数出现了多少次。这两个数组也很好求,可以与之前的\(V\)数组放在一起处理。

处理出这三个数组后,相当于持续了之前的\(b,c\)数组。直接在两边的小块扫一遍即可。对于\(l,r\)在同一块中的情况,直接暴力扫一遍即可。单次询问时间复杂度\(\mathcal O(\sqrt{n})\)

注意\(b[i]\)的下标是\(\mathcal O(n)\)级别,不能每次清零,可以额外记一下\(vis[i]\)表示\(b[i]\)的使用情况,若\(vis[i]=0\),则表示未使用,否则\(vis[i]\)表示\(b[i]\)在哪一次询问被使用,若是当前询问则可以直接加,否则需要用\(L\)\(R\)数组中的值覆盖。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
int read(){
register int x = 0;
register char f = 1, ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f ^= 1;
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ '0');
return f ? x : -x;
}
#define N 150005
const int B = 420, C = 360, P = 550;
int n, q, k, a[N], b[N], c[N], vis[N];
int L[365][365][425], R[365][365][425], V[365][365][555];
void pre(){
for (register int i = 0; i < n; i += B){
for (register int j = 0; j < n; ++j) b[j] = 0;
for (register int j = 0; j < P; ++j) c[j] = 0;
for (register int j = i; j < n; ++j){
--c[b[a[j]]], ++b[a[j]], ++c[b[a[j]]];
if ((j + 1) % B == 0){
register int bi = i / B, bj = j / B;
for (register int k = 0; k < P; ++k) V[bi][bj][k] = c[k];
for (register int k = 0; k < B && i - k - 1 >= 0; ++k) L[bi][bj][k] = b[a[i - k - 1]];
for (register int k = 0; k < B && j + k + 1 < n; ++k) R[bi][bj][k] = b[a[j + k + 1]];
}
}
}
}
int solve(int q, int l, int r){
register int bl = (l - 1) / B + 1, br = (r + 1) / B - 1;
register int pl = B * bl, pr = B * (br + 1) - 1;
if (bl <= br){
for (register int i = 0; i < P; ++i) c[i] = V[bl][br][i];
for (register int i = 0; i < pl - l; ++i){
register int j = pl - i - 1;
if (vis[a[j]] != q) vis[a[j]] = q, b[a[j]] = L[bl][br][i];
--c[b[a[j]]], ++b[a[j]], ++c[b[a[j]]];
}
for (register int i = 0; i < r - pr; ++i){
register int j = pr + i + 1;
if (vis[a[j]] != q) vis[a[j]] = q, b[a[j]] = R[bl][br][i];
--c[b[a[j]]], ++b[a[j]], ++c[b[a[j]]];
}
}
else{
for (register int i = 0; i < P; ++i) c[i] = 0;
for (register int i = l; i <= r; ++i){
if (vis[a[i]] != q) vis[a[i]] = q, b[a[i]] = 0;
--c[b[a[i]]], ++b[a[i]], ++c[b[a[i]]];
}
}
for (register int i = 0; i < P; ++i) if (!c[i]) return i;
return -1;
}
int main(){
freopen("mex.in", "r", stdin);
freopen("mex.out", "w", stdout);
n = read(), q = read(), k = read();
for (register int i = 0; i < n; ++i) a[i] = b[i] = read();
std :: sort(b, b + n);
int m = std :: unique(b, b + n) - b;
for (register int i = 0; i < n; ++i)
a[i] = std :: lower_bound(b, b + m, a[i]) - b;
pre();
int ans = 0, l, r;
for (register int _ = 1; _ <= q; ++_){
l = (read() ^ (k * ans)) - 1, r = (read() ^ (k * ans)) - 1;
ans = solve(_, l, r), printf("%d\n", ans);
}
}

评论