异或盒子(01-Trie + 全局 +1)
异或盒子
一、题目描述
有一个包含 n 个元素的盒子,每个元素有权值 v_i。需要支持三种操作:
op = 0:将v_x修改为pop = 1:将所有元素权值+1op = 2:查询所有元素的异或和
二、思路分析
暴力做法的问题
- 如果每次
op = 1都遍历所有元素加 1,单次 O(n),总复杂度 O(nq),会超时。 - 需要一种结构,既能快速查询整体异或和,又能高效处理「全局 +1」。
关键观察:全局 +1 在二进制下的表现
对于一个二进制数 x,x + 1 的效果是:
- 从最低位开始,把连续的 1 全部变成 0
- 把第一个遇到的 0 变成 1
例如:
x = 1011
x+1= 1100
x = 0111
x+1= 1000
这个操作可以看作:从低位到高位,遇到 1 就翻转成 0 并继续进位,遇到 0 就翻转成 1 并停止。
用 Trie 维护
把这 n 个数按从低位到高位的顺序插入一棵 01-Trie。
这样一来,全局 +1 就等价于在 Trie 上从根开始:
- 交换左右儿子(相当于把当前位的 0 和 1 互换)
- 然后递归到新的「0 子树」(原来的 1 子树)继续处理进位
这是因为:
- 原来这一位是 1 的数,加 1 后这一位变成 0,并产生进位
- 原来这一位是 0 的数,加 1 后这一位变成 1,不再进位
维护每个节点的 bitcnt
为了快速查询所有数的异或和,我们在每个 Trie 节点上维护一个数组:
bitcnt[i] = 该节点子树内,第 i 位为 1 的元素个数
那么整体异或和第 i 位的结果就是:
bitcnt[i] % 2
因为异或和就是所有数按位异或,某一位为 1 当且仅当这一位为 1 的元素个数是奇数。
查询
直接从根节点的 bitcnt 数组读:
for (int i = 0; i < MAXB; i++)
if (trie[0].bitcnt[i] % 2) res |= (1 << i);
三、操作详解
1. 插入 insert(val)
从根向下,每一位:
- 更新当前节点的
num和bitcnt - 如果对应子节点不存在,就新建
- 走到子节点后继续更新
2. 删除 erase(val)
和插入相反,沿路径把 num 和 bitcnt 减回去。
3. 全局 +1:add(u, pos)
void add(int u, int pos)
{
if (u == -1 || pos >= MAXB) return;
swap(trie[u].ch[0], trie[u].ch[1]); // 交换 0/1 子树
int L = trie[u].ch[0]; // 原来的 1 子树(进位方向)
int R = trie[u].ch[1]; // 原来的 0 子树(不产生进位)
add(L, pos + 1); // 递归处理进位
if (L != -1) trie[L].bitcnt[pos] = 0; // 这一位原来为 1,现在变成 0
if (R != -1) trie[R].bitcnt[pos] = trie[R].num; // 这一位原来为 0,现在变成 1
// 重新汇总当前节点的 bitcnt
for (int k = 0; k < MAXB; k++)
{
trie[u].bitcnt[k] = 0;
if (trie[u].ch[0] != -1) trie[u].bitcnt[k] += trie[trie[u].ch[0]].bitcnt[k];
if (trie[u].ch[1] != -1) trie[u].bitcnt[k] += trie[trie[u].ch[1]].bitcnt[k];
}
}
关键点:
- 交换左右儿子,相当于把「当前位为 0/1」的集合互换
- 递归处理原来的 1 子树(会产生进位)
- 原来的 0 子树不产生进位,只需要把当前位的
bitcnt更新为全部元素 - 最后重新汇总当前节点的
bitcnt
4. 修改 op = 0
erase(v[x] + base);
insert(p);
v[x] = p - base;
用 base 记录全局加了多少次 1,插入时用 v[x] + base 还原真实值,删除后插入新值 p,并把 v[x] 保存为 p - base。
四、AC 代码
#include<bits/stdc++.h>
using namespace std;
const int MAXB = 20;
const int N = 5e5 + 5;
int v[N];
struct Node
{
int ch[2];
int num;
int bitcnt[MAXB];
Node()
{
ch[0] = ch[1] = -1;
num = 0;
memset(bitcnt, 0, sizeof(bitcnt));
}
};
vector<Node> trie;
int createNode()
{
trie.emplace_back(Node());
return (int)trie.size() - 1;
}
void insert(int val)
{
int u = 0;
trie[u].num++;
for (int i = 0; i < MAXB; i++)
if (val & (1 << i)) trie[u].bitcnt[i]++;
for (int i = 0; i < MAXB; i++)
{
int bit = (val >> i) & 1;
if (trie[u].ch[bit] == -1)
trie[u].ch[bit] = createNode();
u = trie[u].ch[bit];
trie[u].num++;
for (int k = 0; k < MAXB; k++)
if (val & (1 << k)) trie[u].bitcnt[k]++;
}
}
void erase(int val)
{
int u = 0;
trie[u].num--;
for (int i = 0; i < MAXB; i++)
if (val & (1 << i)) trie[u].bitcnt[i]--;
for (int i = 0; i < MAXB; i++)
{
int bit = (val >> i) & 1;
u = trie[u].ch[bit];
trie[u].num--;
for (int k = 0; k < MAXB; k++)
if (val & (1 << k)) trie[u].bitcnt[k]--;
}
}
void add(int u, int pos)
{
if (u == -1 || pos >= MAXB) return;
swap(trie[u].ch[0], trie[u].ch[1]);
int L = trie[u].ch[0];
int R = trie[u].ch[1];
add(L, pos + 1);
if (L != -1) trie[L].bitcnt[pos] = 0;
if (R != -1) trie[R].bitcnt[pos] = trie[R].num;
for (int k = 0; k < MAXB; k++)
{
trie[u].bitcnt[k] = 0;
if (trie[u].ch[0] != -1) trie[u].bitcnt[k] += trie[trie[u].ch[0]].bitcnt[k];
if (trie[u].ch[1] != -1) trie[u].bitcnt[k] += trie[trie[u].ch[1]].bitcnt[k];
}
}
int query()
{
int res = 0;
for (int i = 0; i < MAXB; i++)
if (trie[0].bitcnt[i] % 2) res |= (1 << i);
return res;
}
int main()
{
int n, q;
int base = 0;
cin >> n >> q;
trie.push_back(Node());
for (int i = 1; i <= n; i++)
{
cin >> v[i];
insert(v[i]);
}
for (int i = 1; i <= q; i++)
{
int op;
cin >> op;
if (op == 0)
{
int x, p;
cin >> x >> p;
erase(v[x] + base);
insert(p);
v[x] = p - base;
}
else if (op == 1)
{
add(0, 0);
base++;
}
else if (op == 2) cout << query() << endl;
}
return 0;
}
五、易错点
- Trie 要按 从低位到高位 插入(与普通 01-Trie 相反),因为进位是从低位开始的
base记录全局加 1 的次数,插入/删除时要用v[x] + base还原真实值add递归中要重新汇总bitcnt,不能只更新当前位- 交换左右子树后,原来的 1 子树会变成新的 0 子树(进位方向)
六、总结
| 要点 | 内容 |
|---|---|
| 核心结构 | 按低位到高位插入的 01-Trie |
| 关键技巧 | 全局 +1 = 在 Trie 上交换左右子树 + 递归进位 |
| 查询方式 | 维护每个节点的 bitcnt 数组,按位统计奇偶 |
| 复杂度 | 单次操作 O(MAXB²) |

浙公网安备 33010602011771号