题解:洛谷 P14359 异或和
【题目来源】
洛谷:P14359 [CSP-J 2025] 异或和 / xor(民间数据) - 洛谷
【题目描述】
小 R 有一个长度为 \(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\) 表示二进制按位异或。
小 X 给了小 R 一个非负整数 \(k\)。小 X 希望小 R 选择序列中尽可能多的不相交的区间,使得每个区间的权值均为 \(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\)。
例如,对于序列 \([2, 1, 0, 3]\),若 \(k = 2\),则小 R 可以选择区间 \([1, 1]\) 和区间 \([2, 4]\),权值分别为 \(2\) 和 \(1 \oplus 0 \oplus 3 = 2\);若 \(k = 3\),则小 R 可以选择区间 \([1, 2]\) 和区间 \([4, 4]\),权值分别为 \(1 \oplus 2 = 3\) 和 \(3\)。
你需要帮助小 R 求出他能选出的区间数量的最大值。
【输入】
输入的第一行包含两个非负整数 \(n, k\),分别表示小 R 的序列长度和小 X 给小 R 的非负整数。
输入的第二行包含 \(n\) 个非负整数 \(a_1, a_2, \dots, a_n\),表示小 R 的序列。
【输出】
输出一行一个非负整数,表示小 R 能选出的区间数量的最大值。
【输入样例】
4 2
2 1 0 3
【输出样例】
2
【核心思想】
-
问题分析:给定长度为 \(n\) 的非负整数序列 \(a_1, \dots, a_n\) 和目标值 \(k\),求最多能选出多少个互不相交的区间,使得每个区间的异或和等于 \(k\)。本质上是前缀异或和 + 贪心扫描问题:利用异或前缀和将区间异或转化为两点异或,贪心地在找到合法区间后立即分割,为后续区间创造空间。
-
算法选择:
- 前缀异或和:\(sa[i] = a_1 \oplus a_2 \oplus \dots \oplus a_i\),则区间 \([l, r]\) 的异或和为 \(sa[r] \oplus sa[l-1]\)
- 哈希表定位:用
map记录每个前缀异或和最后一次出现的位置,快速查找满足 \(sa[r] \oplus sa[l-1] = k\) 即 \(sa[l-1] = sa[r] \oplus k\) 的分割点 - 贪心分割:一旦找到合法区间,立即计数并清空哈希表重新开始,保证区间互不相交且数量最大化
-
关键步骤:
- 读取数据:读入 \(n, k\) 和序列 \(a[1..n]\)
- 计算前缀异或和:\(sa[i] = sa[i-1] \oplus a[i]\)
- 初始化:
mp[0] = 0(前缀和为 \(0\) 的位置在索引 \(0\)),last_r = 0,ans = 0 - 贪心扫描(\(r = 1\) 到 \(n\)):
- \(t = sa[r] \oplus k\)(目标前缀异或和)
- 若
mp中存在 \(t\) 且mp[t] >= last_r:- 找到合法区间 \([mp[t]+1, r]\),
ans++ last_r = r(更新最后一个区间的右端点)mp.clear(),mp[sa[r]] = r(清空后重新开始)
- 找到合法区间 \([mp[t]+1, r]\),
- 否则:
mp[sa[r]] = r(记录当前前缀和位置)
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n)\),每次
map操作 \(O(\log n)\) - 空间复杂度:\(O(n)\),哈希表存储
- 时间复杂度:\(O(n \log n)\),每次
-
贪心 + 前缀异或的核心思想:
- 异或前缀和转化:\(a_l \oplus \dots \oplus a_r = sa[r] \oplus sa[l-1]\),将区间查询转化为两点查询
- 贪心最优性:找到合法区间后立即分割,避免该区间与后续区间重叠,这是最大化数量的关键(区间越长越可能阻塞后续区间,但贪心取最早可行的分割点已足够)
- 清空策略的必要性:清空哈希表保证后续区间严格在
last_r之后开始,确保互不相交 - \(mp[0] = 0\) 的初始化:处理从第 \(1\) 个元素开始的区间,\(sa[0] = 0\) 作为虚拟前缀
- 适用于区间划分、异或性质、贪心最大化类问题
【算法标签】
普及 #贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
/*
* 算法思路:
* 贪心策略:如果当前区间满足要求,就直接单独成段,为后面有用的格子创建空间
* 异或和优化:使用前缀异或和来避免重复计算
* 核心原理:异或和的奇偶性由1的个数决定(奇数个1,异或和为1;偶数个1,异或和为0)
* 查找技巧:利用异或的交换性,查找满足 sa[r] ^ sa[l-1] = k 的区间
*/
#define int long long // 使用长整型防止溢出
const int N = 500005; // 定义最大数组长度
int n, k; // n:数组长度, k:目标异或值
int a[N]; // 原始数组
int sa[N]; // 前缀异或和数组,sa[i] = a[1]^a[2]^...^a[i]
int last_r; // 记录上一个分段的右端点
int ans; // 分段数量
map<int, int> mp; // 哈希表,记录前缀异或和最后一次出现的位置
signed main() // 使用signed代替int(因为定义了#define int long long)
{
// 输入数组长度和目标异或值
cin >> n >> k;
// 计算前缀异或和
for (int i = 1; i <= n; i++)
{
cin >> a[i];
sa[i] = sa[i - 1] ^ a[i]; // 计算前缀异或和
}
// 初始化:前缀和为0的位置在索引0处
mp[0] = 0;
// 遍历数组,寻找最优分段
for (int r = 1; r <= n; r++)
{
// 计算需要查找的前缀和:sa[r] ^ k = sa[l-1]
int t = sa[r] ^ k;
// 检查是否存在满足条件的分割点
if (mp.count(t) && mp[t] >= last_r)
{
// 找到有效的分段点
ans++; // 分段数量加1
last_r = r; // 更新上一个分段的右端点
// 清空哈希表,重新开始记录(贪心策略)
mp.clear();
// 记录当前前缀和的位置
mp[sa[r]] = r;
}
else
{
// 未找到分段点,记录当前前缀和的位置
mp[sa[r]] = r;
}
}
// 输出最大分段数量
cout << ans << endl;
return 0;
}
【运行结果】
4 2
2 1 0 3
2
【算法真传】
浙公网安备 33010602011771号