0%

ABC-470-C - Inc, Dec, Xor 增减异或(注意题目条件限制:初始时 A 的所有元素均为 0,以及操作的摊还分析)

题目大意

Inc, Dec, Xor

有一个长度为 NN 的整数序列 A=(A1,A2,,AN)A=(A_1,A_2,\ldots,A_N)初始时 AA 的所有元素均为 00

给定 QQ 个操作,请按顺序依次处理。操作共有两种,格式如下:

  • 1 x:将 AxA_x 的值增加 11

  • 2:对每个 i=1,2,,Ni=1,2,\ldots,N,若 Ai1A_i \geq 1,则将 AiA_i 的值减少 11

请求出每次操作处理完毕后 A1,A2,,ANA_1,A_2,\ldots,A_N 的按位异或值。

关于按位异或
非负整数 A,BA,B 的按位异或 ABA \oplus B 定义如下:ABA \oplus B 在二进制表示下 2k2^kk0k \geq 0)位上的数字,当 A,BA,B 在二进制表示下 2k2^k 位上的数字恰有一个为 11 时为 11,否则为 00
例如 35=63 \oplus 5 = 6(二进制表示为 011101=110011 \oplus 101 = 110)。
一般地,kk 个非负整数 p1,p2,,pkp_1,p_2,\ldots,p_k 的按位异或定义为 (((p1p2)p3)pk)(\dots((p_1 \oplus p_2) \oplus p_3) \oplus \dots \oplus p_k),可以证明其结果与 p1,p2,,pkp_1,p_2,\ldots,p_k 的顺序无关。

输入从标准输入按以下格式给出:

1
2
3
4
5
N Q
query_1
query_2

query_Q

其中每个操作为以下两种格式之一:

1
1 x
1
2

输出 QQ 行。

ii 行(1iQ1\le i\le Q)输出第 ii 个操作处理完毕后 A1,A2,,ANA_1,A_2,\ldots,A_N 的按位异或值。

  • 1N5×1051\le N\le 5\times 10^5

  • 1Q5×1051\le Q\le 5\times 10^5

  • 1xN1\le x\le N

  • 输入的所有值均为整数

1
2
3
4
5
6
2 5
1 2
1 2
1 1
2
2
1
2
3
4
5
1
2
3
1
0

处理第 11 个操作后 A=(0,1)A=(0,1)0,10,1 的按位异或为 11,故第 11 行输出 11

处理第 22 个操作后 A=(0,2)A=(0,2)0,20,2 的按位异或为 22,故第 22 行输出 22

处理第 33 个操作后 A=(1,2)A=(1,2)1,21,2 的按位异或为 33,故第 33 行输出 33

处理第 44 个操作后 A=(0,1)A=(0,1)0,10,1 的按位异或为 11,故第 44 行输出 11

处理第 55 个操作后 A=(0,0)A=(0,0)0,00,0 的按位异或为 00,故第 55 行输出 00

1
2
3
4
5
6
7
8
9
3 8
1 2
1 3
1 1
1 2
1 1
2
1 3
1 1
1
2
3
4
5
6
7
8
1
0
1
2
1
0
1
2

思路讲解

那么这道题目的关键点就是这个,初始时 AA 的所有元素均为 00,以及我们只能增加一个元素啊,我们只要实时维护一个这个正数的这个集合遇到操作二暴力遍历就可以解决了。

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
int main() {
int n = readInt(), q = readInt();

/* alive:保存所有满足 A[i] >= 1 的下标,且保证【互不重复】。
* 维护方式:
* - A[x] 由 0 变 1 时 push_back(x)(这是唯一的“从零变非零”时机)
* - A[i] 由 1 变 0 时把它从 alive 中删除
* 因为 A[i] = 0 对异或没有贡献,所以只关心非零元素即可。
*/
vector<int> alive;
alive.reserve(min(n, q)); // 非零元素个数不会超过 min(N, 操作1次数)

int cur = 0; // cur = A[1] ^ A[2] ^ ... ^ A[n],实时维护

while (q--) {
int op = readInt();

if (op == 1) {
/* -------- 操作 1:A[x] += 1 -------- */
int x = readInt();

if (A[x] == 0) alive.push_back(x); // 从 0 变成非零,加入集合(不会重复)

cur ^= A[x]; // 先把旧值的贡献异或掉(旧值为 0 时是空操作)
++A[x];
cur ^= A[x]; // 再把新值的贡献异或上去
} else {
/* -------- 操作 2:所有非零元素 -1 --------
* 直接遍历 alive 暴力做。单次代价 = |alive|,
* 而这次操作让 sum(A) 恰好减少 |alive|,
* 由于 sum(A) 的总增量 <= 操作 1 的次数 <= Q,
* 所以所有操作 2 的总代价 <= Q,均摊 O(1)。
*/
int m = 0; // m 是“原地压缩”后新数组的长度
for (int idx : alive) {
cur ^= A[idx]; // 去掉旧值贡献
--A[idx];
cur ^= A[idx]; // 加上新值贡献(变成 0 时异或 0,无影响)

if (A[idx] > 0) alive[m++] = idx; // 仍非零则保留;归零的自然被覆盖删除
}
alive.resize(m); // 截断,完成删除
}

IO::writeInt(cur); // 每次操作后输出当前全局异或值
}

IO::flushOut();
return 0;
}

AC代码

https://atcoder.jp/contests/abc470/submissions/78274919

心路历程(WA,TLE,MLE……)