1 条题解

  • 0
    @ 2025-12-11 13:58:25

    好的,我们先一步步分析题意与高效做法。


    题意整理

    • 给定数组 (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)) 满足:

    1. (l \le x \le y \le r)
    2. (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])。

    第三步:莫队实现步骤

    1. 读入 (n, m, k),读入数组 (a),计算前缀异或 (s[0..n])。
    2. 读入查询 ([l, r]),注意我们实际需要维护的集合对应 (s_{l-1} \dots s_r)。
    3. 对查询按块排序(莫队排序)。
    4. 初始区间 (L=1, R=0) 表示空集?小心,初始 (L=1, R=0) 时集合 (S = {s_0})(因为 (l=1) 时,我们需要 (s_0) 到 (s_r))。 初始时 (cnt[s_0]=1),答案 (ans=0)。
    5. 按顺序移动端点,更新答案。
    6. 输出每个查询的答案。

    样例验证

    输入:

    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)。

    最终算法流程

    1. 计算前缀异或数组 (s)。
    2. 将查询按莫队顺序排序。
    3. 维护 (cnt) 数组和当前答案 (ans)。
    4. 对每个查询移动端点并记录答案。
    5. 按原顺序输出答案。

    关键公式
    当前维护集合 (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
    上传者