题目链接: 最大异或和
2021.7.30 重写了题解, 并且翻新了代码.
解题思路
思维
我们首先考虑查询操作:
假设原序列为a[], 我们可以拆开来看: 题目相当于要求 (a[p] ^ a[p + 1] ^ … ^ a[r]) ^ (a[r + 1] ^ a[r + 2] ^ … ^ a[n] ^ x)最大, 相当于我们把操作拆成了两部分, 我们发现后面这部分实际上是一个定值.
我们不难联想到最大异或对那道题. 区别就是, 前面这部分他是一个区间, 而不是一个整数. 如果是一个整数, 我们直接在trie树上查找即可.
考虑转化: 我们不妨处理出原序列的前缀和s[]. 这样题目的要求我们可以进而改写为: 找到一个p, 使得s[p - 1] ^ s[n] ^ x
最大. 我们发现s[n] ^ x实际上是一定值, 此时如果trie树中维护s[], 这就变成了最大异或对题目的形式.
但唯一的区别就是, 我们并不能在整个trie树上进行查询, 我们要求这个s[p - 1]必须位于[l - 1, r - 1]区间.
可持久化01trie
想想我们怎么处理的区间第k大数? 把权值线段树可持久化即可. 对于本题也是这样.
我们用root[i]去记录[1, i]版本的trie树信息. 这样我们可以保证右端点在查询区间内.
对于左端点, 由于信息并不能进行线性的加减, 我们不能用主席树的思路来处理.
我们考虑对于trie树上的每一个节点记录一个时间戳, 来记录这个节点是在哪个位置被更新的. 这样我们每次用当前节点的时间戳与左端点进行比较即可.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 6E5 + 10, M = N * 24, B = 24;
int root[N], t[M][2], ind;
int L[M]; // 时间戳
void insert(int a, int c, int p, int x) {
for (int i = B - 1; i >= 0; --i) {
int w = c >> i & 1;
t[x][w] = ++ind, L[ind] = a;
t[x][w ^ 1] = t[p][w ^ 1];
p = t[p][w], x = t[x][w];
}
}
int query(int c, int l, int x) {
int res = 0;
for (int i = B - 1; i >= 0; --i) {
int w = c >> i & 1;
if (L[t[x][w ^ 1]] >= l) {
x = t[x][w ^ 1];
res += 1 << i;
}
else x = t[x][w];
}
return res;
}
int main()
{
int n, m; cin >> n >> m;
L[0] = -1; // 空节点的时间戳设为一个较小值即可.
// (如果不设置, 在查询的时候需要进行判断是否存在这个节点.)
root[0] = ++ind, insert(0, 0, 0, root[0]); //0号版本必须初始化.
// 因为(p - 1) ∈ [l - 1, r - 1], 我们是可以取到s[0]的, 即: 我们选取了整个序列.
// 如果我们不放这个0值, 当我们最终p取1时, 会出现未定义性错误.
int sum = 0; //前缀和记录
rep(i, n) {
int x; scanf("%d", &x);
sum ^= x;
root[i] = ++ind;
insert(i, sum, root[i - 1], root[i]);
}
rep(i, m) {
char s[2]; scanf("%s", s);
if (*s == 'A') {
int c; scanf("%d", &c);
sum ^= c;
root[++n] = ++ind, insert(n, sum, root[n - 1], root[n]);
}
else {
int l, r, c; scanf("%d %d %d", &l, &r, &c);
printf("%d\n", query(sum ^ c, l - 1, root[r - 1]));
}
}
return 0;
}