【题目来源】
https://www.luogu.com.cn/problem/P14359
【题目描述】
小 R 有一个长度为 n 的非负整数序列 a1,a2,…,an。定义一个区间 [l,r] (1≤l≤r≤n) 的权值为 al,al+1,…,ar 的二进制按位异或和,即 al⊕al+1⊕⋯⊕ar,其中 ⊕ 表示二进制按位异或。
小 X 给了小 R 一个非负整数 k。小 X 希望小 R 选择序列中尽可能多的不相交的区间,使得每个区间的权值均为 k。两个区间 [l1,r1],[l2,r2] 相交当且仅当两个区间同时包含至少一个相同的下标,即存在 1≤i≤n 使得 l1≤i≤r1 且 l2≤i≤r2。
例如,对于序列 [2,1,0,3],若 k=2,则小 R 可以选择区间 [1,1] 和区间 [2,4],权值分别为 2 和 1⊕0⊕3=2;若 k=3,则小 R 可以选择区间 [1,2] 和区间 [4,4],权值分别为 1⊕2=3 和 3。 你需要帮助小 R 求出他能选出的区间数量的最大值。
【输入格式】
输入的第一行包含两个非负整数 n,k,分别表示小 R 的序列长度和小 X 给小 R 的非负整数。
输入的第二行包含 n 个非负整数 a1,a2,…,an,表示小 R 的序列。
【输出格式】
输出一行一个非负整数,表示小 R 能选出的区间数量的最大值。
【输入样例】
4 3
2 1 0 3
【输出样例】
2
【数据范围】
对于所有测试数据,保证:
1≤n≤5×10^5, 0≤k<2^20;
对于所有 1≤i≤n,均有 0≤ai<2^20。
【算法分析】
● 前缀异或和(preXor)定义为:s[i]=a[0]^a[1]^a[2]^a[3]^a[4]^...^a[i]。其中,s[0]=0,s[i]=s[i-1]^a[i]
● 若定义前缀异或和 s[i]=a[0]^a[1]^a[2]^…^a[i],则子区间 [le,ri] 的异或和等于 s[ri]^s[le−1]。要找到 [le, ri] 使得 s[ri]^s[le-1]=k,等价于 s[ri]=k^s[le-1]。
证明过程详见:https://www.cnblogs.com/triwa/p/19185952
● pos[]:记录每个前缀异或值最后一次出现的位置(下标)。初始化为 -1。
● pre:记录上一个被选中的子数组的右端点,确保后续子数组不与之重叠。
● 算法核心思想:根据前缀异或和的性质,要找区间 [le+1, ri] 的异或和等于 k,等价于判定 s[ri]^s[le]=k,也即 s[le]=s[ri]^k。
固定每个右端点 ri,计算目标值 t=s[ri]^k。如果存在位置 j 使得 s[j]=t,那么子数组 [j+1, ri] 的异或和就是 k。检查 pos[t](即前缀异或为 t 的最后位置)是否大于等于 pre(上一个被选中的子数组的右端点)?如果满足,就选择这个子数组,更新 pre=ri,答案加 1。更新 pos[s[ri]]=ri,记录当前前缀异或的最新位置。
【算法代码】
#include <bits/stdc++.h>
using namespace std;const int maxn=5e5+5;
int a[maxn],s[maxn];
int pos[1<<20];
int n,k;int main() {memset(pos,-1,sizeof pos);cin>>n>>k;for(int i=1; i<=n; i++) {cin>>a[i];s[i]=s[i-1]^a[i];}pos[0]=0;int ans=0,pre=0;for(int ri=1; ri<=n; ri++) {int t=k^s[ri];if(pos[t]>=pre) {ans++;pre=ri;}pos[s[ri]]=ri;}cout<<ans;return 0;
}/*
in:
4 3
2 1 0 3out:
2
*/
【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/154310120
https://www.luogu.com.cn/problem/solution/P14359