题解:洛谷 AT_abc470_c Inc, Dec, Xor
【题目来源】
洛谷:AT_abc470_c [ABC470C] Inc, Dec, Xor
【题目描述】
有一个长度为 \(N\) 的整数序列 \(A = (A_1, A_2, \dots, A_N)\)。初始时,\(A\) 的所有元素均为 \(0\)。
按顺序处理 \(Q\) 个查询,查询有以下两种类型:
1 x:将 \(A_x\) 的值增加 \(1\)。2:对于所有 \(i = 1, 2, \dots, N\),如果 \(A_i \ge 1\),则将 \(A_i\) 的值减少 \(1\)。
请输出每个查询处理完成后,\(A_1, A_2, \dots, A_N\) 的按位异或(XOR)值。
按位异或运算的定义:
对于非负整数 \(A, B\),其按位异或 \(A \oplus B\) 定义如下:
- 在 \(A \oplus B\) 的二进制表示中,第 \(k\) 位(\(k \ge 0\))为 \(1\),当且仅当 \(A\) 和 \(B\) 的第 \(k\) 位中恰好有一个为 \(1\);否则为 \(0\)。
例如,\(3 \oplus 5 = 6\)(二进制:\(011 \oplus 101 = 110\))。
一般地,\(k\) 个非负整数 \(p_1, p_2, \dots, p_k\) 的按位异或定义为 \((\dots((p_1 \oplus p_2) \oplus p_3) \oplus \dots \oplus p_k)\),可以证明其结果与计算顺序无关。
【输入】
输入第一行包含两个整数 \(N, Q\)。
接下来 \(Q\) 行,每行描述一个查询,格式为以下两种之一:
1 x2
【输出】
输出 \(Q\) 行,第 \(i\) 行输出第 \(i\) 个查询处理完成后,\(A_1, A_2, \dots, A_N\) 的按位异或值。
【输入样例】
2 5
1 2
1 2
1 1
2
2
【输出样例】
1
2
3
1
0
【核心思想】
-
问题分析:给定长度为 \(N\) 的序列 \(A\)(初始全为 \(0\)),支持两种操作:单点 \(+1\)、全局大于 \(0\) 的元素 \(-1\)。每次操作后输出全序列的异或和。直接模拟全局减 \(1\) 会达到 \(O(NQ)\),需要优化。核心观察是:操作 \(2\) 相当于对所有非零元素执行 \(-1\),可用哈希表维护非零元素集合,仅遍历非零元素进行更新。
-
算法选择:
- 哈希表维护非零集合:用
map记录当前值 \(> 0\) 的元素下标,操作 \(2\) 时只遍历这些元素 - 异或增量更新:利用 \(ans \oplus= old \oplus new\) 的性质,\(O(1)\) 维护异或和
- 哈希表维护非零集合:用
-
关键步骤:
- 初始化:\(ans = 0\),\(A[1..N] = 0\),
mp为空 - 操作 \(1\)(单点 \(+1\)):
- \(ans \oplus= A[x]\)(移除旧值)
- \(A[x]++\)
- \(ans \oplus= A[x]\)(加入新值)
mp[x] = 1(标记非零)
- 操作 \(2\)(全局 \(-1\)):
- 遍历
mp中所有非零元素 \(x\):- \(ans \oplus= A[x]\)(移除旧值)
- \(A[x]--\)
- \(ans \oplus= A[x]\)(加入新值)
- 若 \(A[x] = 0\):记录待删除
- 批量删除变为 \(0\) 的下标
- 遍历
- 每次操作后输出 \(ans\)
- 初始化:\(ans = 0\),\(A[1..N] = 0\),
-
时间/空间复杂度:
- 时间复杂度:\(O(Q \times K)\),其中 \(K\) 为操作 \(2\) 时的非零元素个数,均摊接近 \(O(Q)\)(每个元素最多被加减有限次)
- 空间复杂度:\(O(N)\),存储序列和哈希表
-
哈希表优化的核心思想:
- 惰性全局操作:不真正对所有 \(N\) 个元素执行 \(-1\),而是维护"哪些元素需要被减",将 \(O(N)\) 降为 \(O(K)\)
- 异或的增量性质:\(ans = \bigoplus_{i=1}^{N} A_i\),修改 \(A_x\) 时只需 \(ans \oplus= old \oplus new\),无需重新计算全序列异或
- 集合动态维护:操作 \(2\) 后元素可能变为 \(0\),需从集合中移除;操作 \(1\) 产生新的非零元素,需加入集合
- 适用于带全局批量操作、需要维护聚合值的序列问题
【算法标签】
普及 #哈希表
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 500005; // 最大序列长度
int n, q, ans; // n:序列长度, q:查询数量, ans:当前所有元素的异或和
int a[N]; // a[i]:第i个元素的当前值
map<int, int> mp; // mp:记录当前值大于0的元素下标集合(值为1表示该位置非零)
int main()
{
cin >> n >> q; // 读入序列长度和查询数量
while (q--) // 循环处理每个查询
{
int op, x; // op:操作类型, x:操作参数
cin >> op; // 读入操作类型
if (op==1) // 操作1:将A_x增加1
{
cin >> x; // 读入要增加的位置x
ans ^= a[x]; // 先将a[x]的旧值从异或和中移除
a[x]++; // a[x]的值增加1
ans ^= a[x]; // 将a[x]的新值加入异或和
mp[x] = 1; // 标记该位置值大于0(用于操作2时遍历)
cout << ans << endl; // 输出当前异或和
}
else // 操作2:将所有大于0的元素减1
{
vector<int> v; // 临时存储操作后变为0的元素下标
for (auto t : mp) // 遍历所有当前值大于0的元素
{
ans ^= a[t.first]; // 先将该元素的旧值从异或和中移除
a[t.first]--; // 该元素的值减1
ans ^= a[t.first]; // 将该元素的新值加入异或和
if (a[t.first]==0) // 如果减1后变为0
v.push_back(t.first); // 记录该下标,后续从map中删除
}
for (int i=0; i<v.size(); i++) // 删除所有变为0的元素下标
mp.erase(v[i]);
cout << ans << endl; // 输出当前异或和
}
}
return 0;
}
【运行结果】
2 5
1 2
1
1 2
2
1 1
3
2
1
2
0
浙公网安备 33010602011771号