例题:
解释:
首先这里要求连续异或,所以存储前缀异或和数组。首先的话,我们只考虑前r个版本的Trie,所以以root[r]为根节点的Trie就是1到r位置数。但是,还有一个l左端点,所以我们对于每一个节点来说,额外存储一下max_id,表示的就是以当前节点为子树的最大的i。那这个有什么用呢?
这里我们都是动态开点,也就是假设当前我们的数是1,所以我们要向下遍历找到一个0,由于我们是有限制条件的,所以对于每一个节点,加入他的0的这个儿子的max_id大于我们的限制条件,也就是这个子树里面存在一个s{i],它的下标大于或等于我们的限制条件,所以我们就可以往0这个方向走。
动态开点思路:对于每一个s[i]我们都去开这个数的01串,也就是一个链子,刚开始是我们的根节点,也就是我们的版本(第几版)。假设当前数是(二进制:101010),我们先开了一个根节点(第几版),于是我们继续开一个1这个节点,如果它的上一个版本中的根节点有0这个儿子,那么我们就让上一个版本的0这个儿子指向当前的根节点(也就是直接拷贝)。然后继续开点,开0这个节点,只要遇到不同的,就直接拷贝,如果上一个版本存在当前的链子的前半部分,我们也是必须开的,只有不同的我们才会去复制。
那么这样执行下来,就会存下每一个版本的Trie,也就是我们的可持久化Trie,那么这到底有什么用呢?下面就用上面这个例题来介绍它的用处!
由于当前是由区间限制的,不像一组数据中找两个数他们的异或值最大,没有区间限制的话,用我们的Trie就可以了。首先如果我们只考虑1到r这个条件的话,那么直接从第r个版本的Trie开始找一个数就可以了。然后在考虑数必须大于等于l这个条件,由于我们存储了max_id,也就是下一个节点的max_id,只要大于等于限制条件,就可以遍历这个节点。假设max_id是3,限制条件是2,表示的是这个字数有一个数它的下标是3,所以就可以遍历这个节点,反之,就不可以遍历,因为每一个节点它的下标大于限制条件,都是l前面的数,所以就不能遍历。
所以,直接上代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 600010,M = N*25;
int n, m;
int tr[M][2], idx;
int root[N];
int s[N];
int max_id[M];
void insert(int i, int k, int p, int q)
{
if (k < 0)
{
max_id[q] = i;
return ;
}
int u = s[i] >> k & 1;
if (p)tr[q][u^1] = tr[p][u^1];
tr[q][u] = ++idx;
insert(i, k - 1, tr[p][u], tr[q][u]);
max_id[q] = max(max_id[tr[q][0]],max_id[tr[q][1]]);
}
int query(int root, int limit, int c)
{
int p = root;
for (int i = 23; i >= 0; i--)
{
int u = c >> i & 1;
if (max_id[tr[p][!u]] >= limit && tr[p][!u])p = tr[p][!u];
else p = tr[p][u];
}
return c ^ s[max_id[p]];
}
int main()
{
cin >> n >> m;
root[0] = ++idx;
insert(0,23,0,root[0]);
for (int i = 1; i <= n; i++)
{
int x;
scanf("%d", &x);
root[i] = ++idx;
s[i] = s[i - 1] ^ x;
insert(i, 23, root[i - 1], root[i]);
}
char op[2]; int l, r, x;
while (m--)
{
scanf("%s", op);
if (*op == 'A')
{
scanf("%d", &x);
n++;
root[n] = ++idx;
s[n] = s[n - 1] ^ x;
insert(n, 23, root[n - 1], root[n]);
}
else
{
scanf("%d%d%d", &l, &r, &x);
printf("%d\n", query(root[r - 1], l - 1, s[n] ^ x));
}
}
}