可持久化 trie 树 (例题:AcWing 256. 最大异或和)
AcWing 256. 最大异或和
我们需要求的是
\[ans = \max_{l\ \le k\ \le r} (a[k] \oplus a[k + 1] \oplus a[k + 2] \oplus ... \oplus a[N] \oplus x)
\]
令
\[s[i] = a[1] \oplus a[2] \oplus ... \oplus a[i]
\]
容易看出当 \(i < j\) 时,
\[\begin{aligned}
s[i] \oplus s[j] &= a[1] \oplus a[2] \oplus ... \oplus a[i] \oplus a[1] \oplus ... \oplus a[i] \oplus ... \oplus a[j] \\
&= a[i + 1] \oplus a[i + 2] \oplus ... \oplus a[j]
\end{aligned}
\]
所以我们可以替换为求
\[\begin{aligned}
ans &= \max_{l\ \le k\ \le r} (a[k] \oplus a[k + 1] \oplus a[k + 2] \oplus ... \oplus a[N] \oplus x)\\
&= \max_{l\ \le k\ \le r} (s[k - 1] \oplus s[N] \oplus x)
\end{aligned}
\]
因此,我们仅需要保存每个点的异或前缀和,然后考虑使用一种可以保证我们检索时,可以将区间限制为 \(l \le k \le r\) 这个区间即可。这就是我们要使用的 可持久化 trie 树 。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <ctime>
using namespace std;
const int N = 6e5 + 10, M = N * 25;
int n, m, sum[N];
int tree[M][2], id[M], root[N], idx = 0;
// 由于初始数据有30w,新增数据也有30w,而且10 ^ 6 < 2 ^ 24
// 再加上根节点,也就是每个数最多需要25位
template<typename T>
void read(T& x)
{
char a, b;
for (a = 0; !isdigit(a); a = getchar()) b = a;
for (x = 0; isdigit(a); a = getchar()) x = x * 10 - '0' + a;
if (b == '-') x = -x;
}
void insert(int i, int k, int p, int q)
{
if (k < 0) // 如果纪录结束了
{
id[q] = i; // 纪录当前节点所可以到达的最大范围 i
return ;
}
int v = sum[i] >> k & 1;
if (p) tree[q][v ^ 1] = tree[p][v ^ 1];
tree[q][v] = ++ idx;
insert(i, k - 1, tree[p][v], tree[q][v]);
id[q] = max(id[tree[q][0]], id[tree[q][1]]);
}
int query(int root, int c, int limit)
{
// 选用合适的 root,就是第 r - 1 个节点作为 root
// 然后根据异或的前缀和性质才能保证在 r 左边
int p = root;
for (int i = 23; i >= 0; i --)
{
// c 是s[n] ^ x,从高位到低位逐位检索二进制上能跟 c 异或结果最大的数字
int v = c >> i & 1;
// 自带判空功能如果没有点, id 会为0, 那就肯定不能满足 >= l
// 而 id 又同时可以限制当前的点是在 [l,r] 区间内
// 另外,如果tree[p][v ^ 1]为空,那么tree[p][v]就肯定不为空,并在l r区间
// 因为根据插入的代码, 每个节点至少有一条当前 s[i] 的完整路径
// 而如果tree[p][v ^ 1]不为空但 id 小于 l, 同理也能选取到 tree[p][v]
if (id[tree[p][v ^ 1]] >= limit) p = tree[p][v ^ 1];
else p = tree[p][v];
}
return c ^ sum[id[p]];
}
int main()
{
#ifdef FIO
freopen("E:/Code/In.in", "r", stdin);
freopen("E:/Code/Out.out", "w", stdout);
#endif
read(n), read(m);
id[0] = -1;
root[0] = ++ idx;
insert(0, 23, 0, root[0]);
for (int i = 1; i <= n; i ++)
{
int x;
read(x);
sum[i] = sum[i - 1] ^ x; // 前缀和序列
root[i] = ++ idx;
insert(i, 23, root[i - 1], root[i]);
}
char op[2];
int l, r, x;
while (m --)
{
scanf("%s", op);
if (op[0] == 'A')
{
read(x);
n ++;
sum[n] = sum[n - 1] ^ x;
root[n] = ++ idx;
insert(n, 23, root[n - 1], root[n]);
}
else
{
read(l), read(r), read(x);
// 至少要包住第 r 个点,所以用 r - 1,否则会因为异或把 root[r] 抵消掉
// l 同理
printf("%d\n", query(root[r - 1], sum[n] ^ x, l - 1));
}
}
return 0;
}