题解:AcWing 256 最大异或和
【题目来源】
AcWing:256. 最大异或和 - AcWing题库
【题目描述】
给定一个非负整数序列 \(a\),初始长度为 \(N\)。
有 \(M\) 个操作,有以下两种操作类型:
A x:添加操作,表示在序列末尾添加一个数 \(x\),序列的长度 \(N\) 增大 \(1\)。Q l r x:询问操作,你需要找到一个位置 \(p\),满足 \(l≤p≤r\),使得:\(a[p]\ xor\ a[p+1]\ xor\ …\ xor\ a[N]\ xor\ x\) 最大,输出这个最大值。
【输入】
第一行包含两个整数 \(N\),\(M\),含义如问题描述所示。
第二行包含 \(N\) 个非负整数,表示初始的序列 \(A\)。
接下来 \(M\) 行,每行描述一个操作,格式如题面所述。
【输出】
每个询问操作输出一个整数,表示询问的答案。
每个答案占一行。
【输入样例】
5 5
2 6 4 3 6
A 1
Q 3 5 4
A 4
Q 5 7 0
Q 3 6 6
【输出样例】
4
5
6
【核心思想】
-
问题分析:给定一个动态增长的非负整数序列,支持在末尾添加元素和区间查询操作。查询要求在区间 \([l, r]\) 内找到一个位置 \(p\),使得 \(a[p] \oplus a[p+1] \oplus \ldots \oplus a[N] \oplus x\) 最大。利用前缀异或和转化后,问题变为:在区间 \([l-1, r-1]\) 内找到一个前缀异或和 \(s[p-1]\),使得 \(s[p-1] \oplus (s[N] \oplus x)\) 最大。这是一个可持久化字典树(Persistent Trie)问题,核心在于维护历史版本以支持区间限制下的最大异或查询。
-
算法选择:
- 前缀异或和转化:令 \(s[i] = a[1] \oplus a[2] \oplus \ldots \oplus a[i]\),则 \(a[p] \oplus \ldots \oplus a[N] = s[p-1] \oplus s[N]\),查询转化为求 \(\max_{l-1 \leq p-1 \leq r-1} \{s[p-1] \oplus C\}\),其中 \(C = s[N] \oplus x\)
- 可持久化字典树(Persistent Trie / 主席树):每个版本对应一个前缀异或和集合,通过路径复制实现 \(O(\log V)\) 的版本创建(\(V\) 为数值范围)
- 贪心高位优先:在字典树上从高位到低位优先走与目标值当前位不同的分支,使异或结果最大
-
关键步骤:
- 前缀异或和预处理:\(s[i] = s[i-1] \oplus a_i\),其中 \(s[0] = 0\)
- 问题转化:查询 \(Q(l, r, x)\) 等价于在集合 \(\{s[0], s[1], \ldots, s[r-1]\}\) 中,找到下标 \(\geq l-1\) 的元素与 \(C = s[N] \oplus x\) 异或最大
- 可持久化字典树构建:
- 第 \(i\) 个版本 \(root[i]\) 包含前缀异或和 \(s[0], s[1], \ldots, s[i]\)
- 插入 \(s[i]\) 时,从高位到低位(第 \(23\) 位到第 \(0\) 位),复制旧版本未修改的分支,创建新节点存储当前位
- 每个节点维护 \(max\_id\):以该节点为根的子树中最大的下标编号
- 查询操作:
- 从版本 \(root[r-1]\) 的根节点出发(保证只考虑下标 \(\leq r-1\) 的元素)
- 从高位到低位贪心:若与 \(C\) 当前位不同的分支中存在 \(max\_id \geq l-1\) 的节点,则走该分支
- 否则走相同分支,确保所选元素下标 \(\geq l-1\)
- 添加操作:序列长度 \(N\) 增加,计算新的 \(s[N]\),基于 \(root[N-1]\) 创建新版本 \(root[N]\)
-
时间/空间复杂度:
- 时间复杂度:\(O((N + M) \log V)\),每次插入和查询均为 \(O(\log V)\),\(V\) 为数值范围(本题中 \(V = 2^{24}\),故为 \(O(24)\))
- 空间复杂度:\(O((N + M) \log V)\),每个版本新增 \(O(\log V)\) 个节点
-
可持久化字典树的核心思想:
- 版本化历史快照:每个版本 \(root[i]\) 保存前 \(i\) 个前缀异或和的完整字典树结构,通过路径复制实现 \(O(\log V)\) 的空间增量
- 路径复制策略:插入新值时只修改从根到叶子的一条路径上的节点,其余分支直接引用旧版本节点,实现空间高效的历史版本维护
- 下标约束的贪心查询:利用 \(max\_id\) 数组在遍历过程中实时判断某个分支是否存在满足下标限制的元素,将区间限制转化为版本选择 + 分支可行性判断
- 前缀异或的区间转化:将区间异或查询通过前缀和差分转化为单点查询,使得字典树可以处理动态区间最大异或问题
- 适用于动态数组的区间最大/最小异或查询、带历史版本的集合查询类问题
【算法标签】
主席树
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 600005, M = N * 25; // N: 最大序列长度; M: 字典树最大节点数
int n, m; // n: 序列长度; m: 操作次数
int s[N]; // s[i]: 前缀异或和,s[i] = a[1] xor a[2] xor ... xor a[i]
int tr[M][2]; // tr[p][0/1]: 字典树节点 p 的左右子节点(0/1 分支)
int max_id[M]; // max_id[p]: 以节点 p 为根的子树中,最大的下标编号
int root[N]; // root[i]: 第 i 个版本的字典树根节点
int idx; // idx: 字典树节点计数器
void insert(int i, int k, int p, int q) // 可持久化字典树插入,i: 下标,k: 当前位,p: 旧版本节点,q: 新版本节点
{
if (k < 0) // 已处理完所有位(到达叶子)
{
max_id[q] = i; // 记录该路径对应的最大下标
return;
}
int v = s[i] >> k & 1; // 取出 s[i] 的第 k 位
if (p) // 如果旧版本存在,复制旧版本的另一分支
tr[q][v ^ 1] = tr[p][v ^ 1];
tr[q][v] = ++idx; // 创建新版本当前位的子节点
insert(i, k - 1, tr[p][v], tr[q][v]); // 递归插入下一位
max_id[q] = max(max_id[tr[q][0]], max_id[tr[q][1]]); // 更新当前节点的最大下标
}
int query(int root, int C, int L) // 查询操作,root: 版本根节点,C: 目标异或值,L: 下标下限
{
int p = root; // 从版本根节点开始
for (int i = 23; i >= 0; i--) // 从高位到低位贪心,优先走与 C 当前位不同的分支
{
int v = C >> i & 1; // 取出 C 的第 i 位
if (max_id[tr[p][v ^ 1]] >= L) // 如果异或分支(v^1)中存在下标 >= L 的数
p = tr[p][v ^ 1]; // 走异或分支,使结果更大
else
p = tr[p][v]; // 否则走相同分支
}
return C ^ s[max_id[p]]; // 返回 C 与最优前缀异或和的结果
}
int main()
{
cin >> n >> m; // 读入初始序列长度和操作次数
max_id[0] = -1; // 初始化空节点的 max_id 为 -1
root[0] = ++idx; // 创建第 0 个版本(空序列)
insert(0, 23, 0, root[0]); // 插入 s[0] = 0
for (int i = 1; i <= n; i++) // 读入初始序列并建立可持久化字典树
{
int x; // x: 当前读入的数
cin >> x;
s[i] = s[i - 1] ^ x; // 计算前缀异或和
root[i] = ++idx; // 创建第 i 个版本
insert(i, 23, root[i - 1], root[i]); // 基于上一版本插入 s[i]
}
char op; // op: 操作类型
int l, r, x; // l, r: 区间; x: 操作数值
while (m--) // 循环处理 m 次操作
{
cin >> op; // 读入操作类型
if (op == 'A') // 操作 A:在序列末尾添加一个数
{
cin >> x;
n++; // 序列长度加一
s[n] = s[n - 1] ^ x; // 更新前缀异或和
root[n] = ++idx; // 创建新版本
insert(n, 23, root[n - 1], root[n]); // 基于上一版本插入
}
else // 操作 Q:询问操作
{
cin >> l >> r >> x;
// 查询版本 root[r-1] 中,满足下标 >= l-1 且与 (s[n]^x) 异或最大的值
cout << query(root[r - 1], s[n] ^ x, l - 1) << endl;
}
}
return 0;
}
【运行结果】
5 5
2 6 4 3 6
A 1
Q 3 5 4
4
A 4
Q 5 7 0
5
Q 3 6 6
6
浙公网安备 33010602011771号