2025真题 | 异或和 luogu-P14359

2025真题 | 异或和 luogu-P14359

CSP-J 2025真题- 异或和,动态规划考点,适合GESP六级考生练习,难度⭐⭐⭐☆☆,洛谷难度等级普及/提高−

P14359 [CSP-J 2025] 异或和

题目要求

题目描述

小 R 有一个长度为 nn 的非负整数序列 a1,a2,,ana_1, a_2, \dots, a_n。定义一个区间 [l,r][l, r] (1lrn1 \leq l \leq r \leq n) 的权值为 al,al+1,,ara_l, a_{l+1}, \dots, a_r 的二进制按位异或和,即 alal+1ara_l \oplus a_{l+1} \oplus \dots \oplus a_r,其中 \oplus 表示二进制按位异或。

小 X 给了小 R 一个非负整数 kk。小 X 希望小 R 选择序列中尽可能多的不相交的区间,使得每个区间的权值均为 kk。两个区间 [l1,r1],[l2,r2][l_1, r_1], [l_2, r_2] 相交当且仅当两个区间同时包含至少一个相同的下标,即存在 1in1 \leq i \leq n 使得 l1ir1l_1 \leq i \leq r_1l2ir2l_2 \leq i \leq r_2

例如,对于序列 [2,1,0,3][2, 1, 0, 3],若 k=2k = 2,则小 R 可以选择区间 [1,1][1, 1] 和区间 [2,4][2, 4],权值分别为 22103=21 \oplus 0 \oplus 3 = 2;若 k=3k = 3,则小 R 可以选择区间 [1,2][1, 2] 和区间 [4,4][4, 4],权值分别为 12=31 \oplus 2 = 333

你需要帮助小 R 求出他能选出的区间数量的最大值。

输入格式

输入的第一行包含两个非负整数 n,kn, k,分别表示小 R 的序列长度和小 X 给小 R 的非负整数。

输入的第二行包含 nn 个非负整数 a1,a2,,ana_1, a_2, \dots, a_n,表示小 R 的序列。

输出格式

输出一行一个非负整数,表示小 R 能选出的区间数量的最大值。

输入输出样例 #1

输入 #1
4 2
2 1 0 3
输出 #1
2

输入输出样例 #2

输入 #2
4 3
2 1 0 3
输出 #2
2

输入输出样例 #3

输入 #3
4 0
2 1 0 3
输出 #3
1

说明/提示

【样例 1 解释】

小 R 可以选择区间 [1,1][1, 1] 和区间 [2,4][2, 4],异或和分别为 22103=21 \oplus 0 \oplus 3 = 2。可以证明,小 R 能选出的区间数量的最大值为 22

【样例 2 解释】

小 R 可以选择区间 [1,2][1, 2] 和区间 [4,4][4, 4],异或和分别为 12=31 \oplus 2 = 333。可以证明,小 R 能选出的区间数量的最大值为 22

【样例 3 解释】

小 R 可以选择区间 [3,3][3, 3],异或和为 00。可以证明,小 R 能选出的区间数量的最大值为 11。注意:小 R 不能同时选择区间 [3,3][3, 3] 和区间 [1,4][1, 4],因为这两个区间同时包含下标 33

【样例 4】

见选手目录下的 xor/xor4.inxor/xor4.inxor/xor4.ansxor/xor4.ans

该样例满足测试点 4,54, 5 的约束条件。

【样例 5】

见选手目录下的 xor/xor5.inxor/xor5.inxor/xor5.ansxor/xor5.ans

该样例满足测试点 9,109, 10 的约束条件。

【样例 6】

见选手目录下的 xor/xor6.inxor/xor6.inxor/xor6.ansxor/xor6.ans

该样例满足测试点 14,1514, 15 的约束条件。

【数据范围】

对于所有测试数据,保证:

  • 1n5×1051 \leq n \leq 5 \times 10^5, 0k<2200 \leq k < 2^{20};
  • 对于所有 1in1 \leq i \leq n,均有 0ai<2200 \leq a_i < 2^{20}
测试点编号nn \leqkk特殊性质
1122=0=0A
2210101\leq 1B
3310210^2=0=0A
4,54, 510210^21\leq 1B
686 \sim 810210^2255\leq 255C
9,109, 1010310^3255\leq 255C
11,1211, 1210310^3<220< 2^{20}
13132×1052 \times 10^51\leq 1B
14,1514, 152×1052 \times 10^5255\leq 255C
16162×1052 \times 10^5<220< 2^{20}
17175×1055 \times 10^5255\leq 255C
182018 \sim 205×1055 \times 10^5<220< 2^{20}

特殊性质 A: 对于所有 1in1 \leq i \leq n,均有 ai=1a_i = 1

特殊性质 B: 对于所有 1in1 \leq i \leq n,均有 0ai10 \leq a_i \leq 1

特殊性质 C: 对于所有 1in1 \leq i \leq n,均有 0ai2550 \leq a_i \leq 255


题目分析

本题考查 异或性质前缀和思想 以及 动态规划 (DP)

1. 核心数学性质:前缀异或和

题目关心的区间 [l,r][l, r] 的异或和。对于区间求和问题,最常用的技巧是 前缀和。同样地,对于区间异或,我们可以使用 前缀异或和

定义 pre[i]pre[i] 为序列前 ii 个元素的异或和:

pre[i]=a1a2aipre[i] = a_1 \oplus a_2 \oplus \dots \oplus a_i

特别地,pre[0]=0pre[0] = 0

根据异或的性质,特别是 归零律xx=0x \oplus x = 0)和 结合律,我们可以推导出区间异或和的公式:

XOR(l,r)=pre[r]pre[l1]XOR(l, r) = pre[r] \oplus pre[l-1]

推导过程如下:

  1. 展开前缀异或和定义

    • pre[r]pre[r] 表示从第 11 个到第 rr 个元素的异或和: pre[r]=a1a2al1alarpre[r] = a_1 \oplus a_2 \oplus \dots \oplus a_{l-1} \oplus \mathbf{a_l \oplus \dots \oplus a_r}
    • pre[l1]pre[l-1] 表示从第 11 个到第 l1l-1 个元素的异或和: pre[l1]=a1a2al1pre[l-1] = a_1 \oplus a_2 \oplus \dots \oplus a_{l-1}
  2. 进行异或运算:我们将 pre[r]pre[r]pre[l1]pre[l-1] 进行异或:

    pre[r]pre[l1]=(a1al1公共前缀alar目标区间)(a1al1公共前缀)pre[r] \oplus pre[l-1] = (\underbrace{a_1 \oplus \dots \oplus a_{l-1}}_{\text{公共前缀}} \oplus \underbrace{a_l \oplus \dots \oplus a_r}_{\text{目标区间}}) \oplus (\underbrace{a_1 \oplus \dots \oplus a_{l-1}}_{\text{公共前缀}})
  3. 利用结合律和交换律:将相同的项放在一起(即 a1a_1a1a_1,…,al1a_{l-1}al1a_{l-1})。由于异或运算满足交换律和结合律,我们可以随意调整运算顺序:

    =(a1a1)(a2a2)(al1al1)(alar)= (a_1 \oplus a_1) \oplus (a_2 \oplus a_2) \oplus \dots \oplus (a_{l-1} \oplus a_{l-1}) \oplus (a_l \oplus \dots \oplus a_r)
  4. 利用归零律消去:因为任何数异或自己都等于 0(xx=0x \oplus x = 0),前面的公共部分全部抵消变为 0:

    =000(alar)= 0 \oplus 0 \oplus \dots \oplus 0 \oplus (a_l \oplus \dots \oplus a_r)

    =alar= a_l \oplus \dots \oplus a_r

这正是区间 [l,r][l, r] 的异或和。这个性质非常重要,它让我们能够在 O(1)O(1) 的时间内通过两个前缀值的异或,快速求出任意子区间的异或和,类似于前缀和求子区间和(Sum(l,r)=pre[r]pre[l1]Sum(l, r) = pre[r] - pre[l-1])。


题目要求找到最多的不相交区间,使得每个区间的异或和都为 kk。 也就是说,如果我们要选一个以 rr 结尾的区间 [l,r][l, r],必须满足:

pre[r]pre[l1]=kpre[r] \oplus pre[l-1] = k

根据异或的交换律,这等价于:

pre[l1]=pre[r]kpre[l-1] = pre[r] \oplus k

这意味着,当我们遍历到位置 rr 时,我们只需要在它前面找到一个位置 l1l-1(即 jj),满足 pre[j]=pre[r]kpre[j] = pre[r] \oplus k,那么区间 [j+1,r][j+1, r] 就是一个符合条件的区间。

2. 动态规划 (DP) 设计

这是一个典型的线性 DP 问题。设 dp[i]dp[i] 表示在前 ii 个数中,最多能选出多少个符合条件的不相交区间。

对于当前位置 ii,我们有两种选择:

  1. 不选以 ii 结尾的区间:此时我们只需继承前面的最优结果,即 dp[i]=dp[i1]dp[i] = dp[i-1]

  2. 选一个以 ii 结尾的区间 [j+1,i][j+1, i]: 这就要求区间异或和为 kk,即 pre[j]=pre[i]kpre[j] = pre[i] \oplus k(其中 0j<i0 \le j < i)。 为了让总区间数最大,我们应该选择一个满足条件的 jj,使得 dp[j]dp[j] 尽可能大。 此时状态转移方程为:

    dp[i]=max(dp[i],dp[j]+1)dp[i] = \max(dp[i], dp[j] + 1)

综合起来,状态转移方程为:

dp[i]=max(dp[i1],maxj<i,pre[j]=pre[i]k{dp[j]+1})dp[i] = \max(dp[i-1], \max_{j < i, pre[j] = pre[i] \oplus k} \{ dp[j] + 1 \})

为了更直观地理解上述 DP 转移过程,我们以 样例 1 为例进行手动模拟:

场景设定:

  • 序列a = [2, 1, 0, 3] (下标 141 \sim 4)
  • 目标异或值k=2k = 2
  • 前缀异或数组 pre
    • pre[0]=0pre[0] = 0
    • pre[1]=2pre[1] = 2
    • pre[2]=21=3pre[2] = 2 \oplus 1 = 3
    • pre[3]=30=3pre[3] = 3 \oplus 0 = 3
    • pre[4]=33=0pre[4] = 3 \oplus 3 = 0

DP 模拟过程:

  1. i=1i=1 (数值 2):

    • 继承dp[1]dp[1] 至少为 dp[0]=0dp[0]=0
    • 尝试选区间:需找 j<1j < 1 使得 pre[j]=pre[1]k=22=0pre[j] = pre[1] \oplus k = 2 \oplus 2 = 0
    • 发现 pre[0]=0pre[0]=0,符合条件!构成区间 [1,1][1, 1]
    • 决策dp[1]=max(dp[0],dp[0]+1)=1dp[1] = \max(dp[0], dp[0]+1) = 1
  2. i=2i=2 (数值 1):

    • 继承dp[2]dp[2] 至少为 dp[1]=1dp[1]=1
    • 尝试选区间:需找 j<2j < 2 使得 pre[j]=pre[2]k=32=1pre[j] = pre[2] \oplus k = 3 \oplus 2 = 1
    • 此前 prepre 值为 {0,2}\{0, 2\},无 11。无法构成新区间。
    • 决策dp[2]=1dp[2] = 1
  3. i=3i=3 (数值 0):

    • 继承dp[3]dp[3] 至少为 dp[2]=1dp[2]=1
    • 尝试选区间:需找 j<3j < 3 使得 pre[j]=pre[3]k=32=1pre[j] = pre[3] \oplus k = 3 \oplus 2 = 1
    • 此前 prepre 值为 {0,2,3}\{0, 2, 3\},无 11
    • 决策dp[3]=1dp[3] = 1
  4. i=4i=4 (数值 3):

    • 继承dp[4]dp[4] 至少为 dp[3]=1dp[3]=1
    • 尝试选区间:需找 j<4j < 4 使得 pre[j]=pre[4]k=02=2pre[j] = pre[4] \oplus k = 0 \oplus 2 = 2
    • 发现 pre[1]=2pre[1]=2,符合条件!这意味着区间 [2,4][2, 4] (即 a2a4a_2 \dots a_4) 的异或和为 kk
    • 决策dp[4]=max(dp[3],dp[1]+1)=max(1,1+1)=2dp[4] = \max(dp[3], dp[1] + 1) = \max(1, 1+1) = 2

最终答案: 22

通过这个过程可以看到,DP 的核心在于:在每一个位置 i,我们既要“守成”(继承 dp[i-1]),也要“进取”(查找是否存在 pre[j] 满足条件,从而接上一个新的区间)

🤔 核心疑问解答:如何保证区间不重叠?

很多同学可能会问:“为什么直接用 dp[j]+1dp[j] + 1 就可以?怎么保证 dp[j]dp[j] 里统计的那些区间,和当前新选的区间 [j+1,i][j+1, i] 不冲突?”

答案就在下标上

  • dp[j]dp[j] 的含义是:在下标 1j1 \sim j 的范围内,最多能选多少个不相交区间。也就是说,这里面所有被选中的区间,结束位置都 j\le j
  • 我们新选的区间是 [j+1,i][j+1, i],它的起始位置是 j+1j+1
  • 因为 j<j+1j < j+1,所以新区间 [j+1,i][j+1, i]1j1 \sim j 范围内的任何区间在物理位置上都是完全错开的,绝对不可能有重叠。这就是线性 DP 的妙处,通过下标的自然分割保证了“无后效性”和“无重叠”。

3. 优化 DP (贪心 + 桶/哈希表)

直接套用上面的 DP 方程,对于每个 ii 都要回头遍历找 jj,时间复杂度是 O(N2)O(N^2),这对于 N=5×105N=5 \times 10^5 的数据规模是会超时的。我们需要优化查找过程。

观察转移方程:maxj<i,pre[j]=pre[i]k{dp[j]}\max_{j < i, pre[j] = pre[i] \oplus k} \{ dp[j] \}。 我们需要快速找到满足 pre[j]=targetpre[j] = \text{target} 的所有 jj 中,dp[j]dp[j] 最大的那个值。

我们可以维护一个数组(桶)max_val[v],用来记录:在前缀异或和为 v 的所有位置中,最大的 DP 值是多少。 即 max_val[v] = max(dp[j]),其中 pre[j] == v

这样,当我们处理到位置 ii 时:

  1. 计算当前前缀异或和 curr = pre[i]
  2. 计算我们需要寻找的目标前缀异或值 target = curr ^ k
  3. 查找 max_val[target]。如果 max_val[target] 存在(即之前出现过满足条件的前缀和),说明我们可以形成一个新区间,此时候选答案为 max_val[target] + 1
  4. 同时,不要忘了 dp[i]dp[i] 至少可以继承 dp[i1]dp[i-1](即代码中的 dp 变量在循环开始时就保留了上一轮的值)。
  5. 更新桶:计算出 dp[i]dp[i] 后,用它来更新 max_val[curr],供后面使用。

4. 算法流程总结

  1. 初始化数组 max_val 为 -1(表示该异或值从未出现过),特别地 max_val[0] = 0(表示还未开始时,前缀异或为 0,区间数为 0)。
  2. 维护一个变量 dp,表示当前位置的最大区间数。
  3. 遍历序列,维护当前的前缀异或和 curr_xor
  4. 对于每个位置:
    • 计算需要寻找的目标前缀值 target = curr_xor ^ k
    • 如果 max_val[target] 有效,尝试更新 dp = max(dp, max_val[target] + 1)
    • 更新当前前缀异或值的记录:max_val[curr_xor] = max(max_val[curr_xor], dp)
  5. 最后输出 dp 即为答案。

这种做法的时间复杂度是 O(N)O(N),空间复杂度是 O(2M)O(2^M)(取决于数值范围,题目中 ai<220a_i < 2^{20})。


示例代码

#include <algorithm>
#include <iostream>
#include <vector>

const int MAX_VAL = 1 << 20;  // 2^20 = 1048576, 题目限制 a_i < 2^20, k < 2^20
int max_dp[MAX_VAL];

int main() {

    // 优化 I/O 速度,避免大量输入输出导致超时
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    int n;
    int k;
    std::cin >> n >> k;

    // 初始化 max_dp 数组
    // max_dp[v] 存储的是:当某个位置的前缀异或和为 v
    // 时,该位置及之前能得到的最大区间数
    for (int i = 0; i < MAX_VAL; ++i) {
        max_dp[i] = -1;
    }
    // 初始状态:还没有读取任何数时,前缀异或和为 0,区间数为 0
    max_dp[0] = 0;

    int current_xor = 0;  // 当前的前缀异或和 (pre[i])
    int dp = 0;           // 当前位置的最大区间数 (dp[i])

    for (int i = 1; i <= n; ++i) {
        int a;
        std::cin >> a;
        current_xor ^= a;  // 计算到当前位置 i 的前缀异或和

        // 步骤 A: 尝试以当前位置 i 作为区间的结尾
        // 我们需要找一个之前的断点 j-1,使得 pre[j-1] ^ pre[i] == k
        // 即 pre[j-1] == pre[i] ^ k
        int target = current_xor ^ k;

        // 如果之前存在某个位置的前缀异或和等于 target
        if (max_dp[target] != -1) {
            // 尝试以当前位置为结尾构成一个新区间
            // dp[i] = max(dp[i-1], max_dp[target] + 1)
            // 注意:这里的 dp 变量实际上在迭代过程中一方面代表
            // dp[i-1],更新后代表 dp[i] 因为 dp[i] 至少是
            // dp[i-1],所以直接比较即可
            dp = std::max(dp, max_dp[target] + 1);
        }

        // 更新当前前缀异或值的最大 dp 值
        // 如果当前 dp 值比之前记录的更大,则更新
        if (dp > max_dp[current_xor]) {
            max_dp[current_xor] = dp;
        }
    }

    std::cout << dp << std::endl;

    return 0;
}

本文由coderli.com原创,按照CC BY-NC-SA 4.0 进行授权

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

luogu-”系列题目可在 洛谷题库 在线评测。

bcqm-”系列题目可在 编程启蒙题库 在线评测。

GESP/CSP认证交流QQ群: 688906745

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
最后更新于