1 条题解
-
0
好的,我们先一步步分析题意与高效做法。
题意整理
- 给定数组 (a[1..n]),每次查询区间 ([l, r])。
- 问在该区间内,有多少个子数组(连续子序列)的异或和等于 (k)。
- 即求满足 (l \le x \le y \le r) 且 (a_x \oplus a_{x+1} \oplus \dots \oplus a_y = k) 的 ((x, y)) 对数。
第一步:转化为前缀异或
设
[ s_i = a_1 \oplus a_2 \oplus \dots \oplus a_i ] 且 (s_0 = 0)。
那么区间 ([x, y]) 的异或和
[ a_x \oplus \dots \oplus a_y = s_y \oplus s_{x-1} ]条件 (s_y \oplus s_{x-1} = k) 等价于
[ s_y = s_{x-1} \oplus k ] 且 (x \le y),即 (x-1 < y)。
对于查询 ([l, r]),我们要找的 ((x, y)) 满足:
- (l \le x \le y \le r)
- (s_y = s_{x-1} \oplus k)
等价于:
对每个位置 (p \in [l-1, r-1])(这是 (x-1) 的取值范围),找在它右边且下标在 ([l, r]) 中的位置 (q)(即 (q \in [\max(p+1, l), r]))使得 (s_q = s_p \oplus k)。
第二步:离线查询——莫队算法
由于 (n, m \le 10^5),我们需要 (O((n+m)\sqrt{n})) 或更优的做法。
这是经典的区间子数组异或和等于 k 的计数问题,可以用莫队算法离线处理。
莫队思路
我们维护当前区间 ([L, R]) 内,每个前缀异或值 (s) 出现的次数 (cnt[s])。
当加入一个位置 (i)(即扩展右端点或左端点),实际上我们是加入 (s_i) 这个前缀异或值。
但是注意:子数组 ([x, y]) 的异或和 = (s_y \oplus s_{x-1})。
我们需要的配对是:在 ([L-1, R]) 范围内(前缀异或下标范围),对于每个 (p)(作为 (x-1)),找 (q)(作为 (y))满足 (q > p) 且 (s_q = s_p \oplus k)。
技巧:
在莫队移动端点时,我们实时维护的区间是 ([L, R]),我们考虑所有 (p \in [L-1, R-1]),(q \in [L, R]) 且 (q > p),(s_q = s_p \oplus k) 的数量。但更简单地,我们可以直接维护:
当前区间 ([L, R]) 的答案 = (\sum_{p=L-1}^{R-1} \sum_{q=p+1}^{R} [s_q = s_p \oplus k])。
移动端点的更新
设当前答案为 (ans),当前区间为 ([L, R]),(cnt[v]) 表示 (s_{L-1}, s_L, \dots, s_R) 中值 (v) 出现的次数。
1. 右端点 (R) 增加 1 到 (R+1):
新加入的前缀异或是 (s_{R+1}),它作为可能的 (y)(即 (q)),要找 (p) 满足 (s_p = s_{R+1} \oplus k) 且 (p \in [L-1, R])。
符合条件的 (p) 的个数是 (cnt[s_{R+1} \oplus k])。
所以: [ ans \ += cnt[s_{R+1} \oplus k] ] 然后 (cnt[s_{R+1}]) 增加 1。
2. 左端点 (L) 减少 1 到 (L-1):
新加入 (s_{L-2})(注意 (L) 减少 1 时,区间变成 ([L-1, R]),新加入的是 (s_{L-2})? 小心索引)我们维护的 (p) 范围是 (L-1) 到 (R-1),(q) 范围是 (L) 到 (R)。
当 (L) 减少到 (L-1),新增的 (p = L-2)(因为现在 (p) 从 (L-2) 开始),新增的 (q) 是原来的 (L) 到 (R)。实际上更好统一:
设我们维护的区间是 ([L, R]) 对应的前缀异或下标集合 ({s_{L-1}, s_L, \dots, s_R})。
那么答案就是在这个集合中,统计有多少对 ((p, q)) 满足 (p < q) 且 (s_q = s_p \oplus k)。这样移动时:
- 增加 (R):新加入 (s_R)(注意 (R) 增加 1 时,对应新 (s_{R+1}) 吗?要统一,我们用下标表示集合里的元素是 (s_{L-1}) 到 (s_R))。
我重新统一表示:
重新整理
定义 (S = {s_{L-1}, s_L, s_{L+1}, \dots, s_R}),大小 (R - (L-1) + 1 = R - L + 2)。
答案 (ans = #{(p, q) \mid p, q \in S, p < q, s_p \oplus s_q = k})。这样,当 (R) 增加 1(新区间 ([L, R+1])),集合 (S) 增加 (s_{R+1})。
新增的配对:(s_{R+1}) 与 (S) 中已有的元素 (v) 满足 (s_{R+1} \oplus v = k),即 (v = s_{R+1} \oplus k)。
所以新增配对数是 (cnt[s_{R+1} \oplus k])。
然后 (cnt[s_{R+1}]) 加 1。
当 (L) 减少 1(新区间 ([L-1, R])),集合 (S) 增加 (s_{L-2})(因为 (s_{L-2}) 是新区间的左端前一个前缀异或)。
新增配对:(s_{L-2}) 与 (S) 中已有元素 (v) 满足 (s_{L-2} \oplus v = k),即 (v = s_{L-2} \oplus k),已有元素是原来集合 (S)(不含 (s_{L-2}))中的所有值。
所以新增配对数是 (cnt[s_{L-2} \oplus k])。
然后 (cnt[s_{L-2}]) 加 1。
删除时((R) 减少或 (L) 增加)类似,先减少 (cnt),再减去配对数。
具体:
- 删除 (R)(从 ([L, R]) 到 ([L, R-1])):集合移除 (s_R)。
先 (cnt[s_R]) 减 1,然后答案减少 (cnt[s_R \oplus k])(因为 (s_R) 与集合中剩余元素的配对数)。 - 删除 (L)(从 ([L, R]) 到 ([L+1, R])):集合移除 (s_{L-1})。
先 (cnt[s_{L-1}]) 减 1,然后答案减少 (cnt[s_{L-1} \oplus k])。
第三步:莫队实现步骤
- 读入 (n, m, k),读入数组 (a),计算前缀异或 (s[0..n])。
- 读入查询 ([l, r]),注意我们实际需要维护的集合对应 (s_{l-1} \dots s_r)。
- 对查询按块排序(莫队排序)。
- 初始区间 (L=1, R=0) 表示空集?小心,初始 (L=1, R=0) 时集合 (S = {s_0})(因为 (l=1) 时,我们需要 (s_0) 到 (s_r))。 初始时 (cnt[s_0]=1),答案 (ans=0)。
- 按顺序移动端点,更新答案。
- 输出每个查询的答案。
样例验证
输入:
4 5 1 1 2 3 1 1 4 1 3 2 3 2 4 4 4(s = [0, 1, 3, 0, 1])
查询 [1,4]:找 (p \in {s_0..s_3}, q \in {s_1..s_4}, p<q, s_q = s_p \oplus 1)。
列出 (s):0:0, 1:1, 2:3, 3:0, 4:1。
配对: (p,q)
(0,1): s0=0, s1=1, 0⊕1=1=k ✅
(0,4): s0=0, s4=1 ✅
(1,2): 1⊕3=2≠1
(1,4): 1⊕1=0≠1
(2,3): 3⊕0=3≠1
(2,4): 3⊕1=2≠1
(3,1): p>q 不满足 p<q
(3,4): s3=0, s4=1 ✅
另外 (1,?) 1⊕?=1 → ?=0,即 s_q=0,q=3,但 p=1, q=3, s1=1,s3=0, 1⊕0=1 ✅ 这个也成立,前面漏了。我们系统数一下 p<q:
p=0 (值0): 找值1 → q=1,4 ✅✅
p=1 (值1): 找值0 → q=3 ✅
p=2 (值3): 找值2 → 无
p=3 (值0): 找值1 → q=4 ✅一共 4 个,与输出一致。
第四步:复杂度
- 莫队移动:(O((n+m)\sqrt{n}))。
- 每个端点移动 (O(1)) 更新。
- 可过 (10^5)。
最终算法流程
- 计算前缀异或数组 (s)。
- 将查询按莫队顺序排序。
- 维护 (cnt) 数组和当前答案 (ans)。
- 对每个查询移动端点并记录答案。
- 按原顺序输出答案。
关键公式:
当前维护集合 (S = {s_{L-1}, \dots, s_R}),
答案 = 满足 (p<q) 且 (s_p \oplus s_q = k) 的 ((p,q)) 对数。移动:
- 增加 (s_x) 时:(ans \ += cnt[s_x \oplus k]),然后 (cnt[s_x]++)。
- 移除 (s_x) 时:先 (cnt[s_x]--),然后 (ans \ -= cnt[s_x \oplus k])。
这样即可高效解决本题。
- 1
信息
- ID
- 6111
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者