洛谷 P14359 [CSP-J 2025] 异或和
洛谷 P14359 [CSP-J 2025] 异或和
题目大意
原题链接:洛谷 P14359 [CSP-J 2025] 异或和
有一个长度为 \(n\) 的非负整数序列 \(a_1, a_2, \dots, a_n\)。定义一个区间 \([l, r]\) (\(1 \leq l \leq r \leq n\)) 的权值为 \(a_l, a_{l+1}, \dots, a_r\) 的二进制按位异或和,即 \(a_l \oplus a_{l+1} \oplus \dots \oplus a_r\),其中 \(\oplus\) 表示二进制按位异或。选择序列中尽可能多的不相交的区间,使得每个区间的权值均为 \(k\)。两个区间 \([l_1, r_1], [l_2, r_2]\) 相交当且仅当两个区间同时包含至少一个相同的下标,即存在 \(1 \leq i \leq n\) 使得 \(l_1 \leq i \leq r_1\) 且 \(l_2 \leq i \leq r_2\)。输出能选出的区间数量的最大值。
题意分析
异或的定义
异或是一种逻辑运算或位运算,记作 \(a \oplus b\),C++中写作 a^b。
当异或作为逻辑运算时,表示两个条件是否刚好一个为真、另一个为假。其真值表如下:
| \(a\) | \(b\) | \(a \oplus b\) |
|---|---|---|
| \(0\) | \(0\) | \(0\) |
| \(0\) | \(1\) | \(1\) |
| \(1\) | \(0\) | \(1\) |
| \(1\) | \(1\) | \(1\) |
当异或作为位运算时,表示将两个数按位对齐,在每一位上进行逻辑异或运算得到的结果。显然题目中的异或运算指的是后者。
异或的性质
异或运算有两个特殊的性质。
- 若 \(a \oplus b = c\),则 \(a = c \oplus b\)。即异或的逆运算还是异或。
- \(a \oplus a = 0\),且 \(0 \oplus a = a\)。
思路分析
这道题可以使用类似前缀和的方法来做。
设 \(s_i = a_1 \oplus a_2 \oplus \dots \oplus a_i\)。特别的,\(s_0 = 0\)。则可以得到递推式 \(s_i = s_{i-1} \oplus a_i\)。
显而易见,对于区间 \([l, r]\) 的异或和 \(a_l \oplus a_{l+1} \oplus \dots \oplus a_r = s_r \oplus s_{l-1}\)。若 \(s_r \oplus s_{l-1} = k\),则 \(s_{l-1} = s_r \oplus k\)。所以可以枚举右端点 \(r\),并计算出 \(s_{l-1}\) 的值,取它最后一个出现的位置。如果这个位置没有被上一个区间包含,则将它作为左端点。
为什么是最后一个位置呢?如果最后一个位置都被上一个区间包含了,那么前面的位置也会被包含;如果最后一个区间没有被包含,前面的位置也没有被包含,那么不管选择哪一个位置,区间总数量都是一样的。
如何实现呢?可以使用一个 pos 数组,pos[i] 表示元素 i 在 s 数组中最后一次出现的位置。使用变量 lts 记录上一个区间的右端点。如果 pos[i]<lts,则说明左端点被上一个区间包含。注意是小于,因为计算的左端点是 \(l-1\),所以这是左开右闭区间,即使 pos[i]==lts,i 也是合法的左端点。
pos 数组的长度取决于 s[i] 的大小,而 s[i] 一定小于 \(2^{20} = 1048576\),所以数组开的比它稍大就可以了。
AC 代码
#include <cstdio>
#include <cstring>
using namespace std;
int n, k;
int a[500010], s[500010];
int pos[1050000];
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
s[i] = s[i - 1] ^ a[i];
}
memset(id, -1, sizeof(id));
pos[0] = 0;
int lts = 0, ans = 0;
for (int r = 1; r <= n; r++) {
int x = s[r] ^ k;
if (pos[x] >= lts) {
ans++;
lts = r;
}
pos[s[r]] = r;
}
printf("%d\n", ans);
return 0;
}

浙公网安备 33010602011771号