每日一题:异或和之和
来源:2023蓝桥杯省赛大学A组
题解
-
问题简化
直接枚举所有子数组的时间复杂度是 O(n²),对于 n=10^5 会超时。代码通过按位独立计算来优化:
- 将每个数字看作 21 位二进制数(因为 A_i ≤ 2^20)
- 计算每个二进制位对最终答案的贡献
- 因为异或运算在不同位之间是独立的,可以分别计算
-
核心算法
// 构建前缀异或数组 for (int i = 1; i <= n; i++) { int t; cin >> t; for (int j = 0; j <= 20; j++) { a[i][j] = (t >> j) & 1; // 获取第j位的值 a[i][j] = a[i][j] ^ a[i - 1][j]; // 前缀异或和 } }a[i][j]表示:前 i 个元素的第 j 位的前缀异或值(0 或 1)。
-
关键推理
对于第 j 位:
-
子数组 [L, R]的异或和在该位为 1
-
当且仅当:a[R][j] ^ a[L-1][j] = 1
-
即:a[L-1][j]和 a[R][j]不同
所以:
-
对于每个位置 i,找到前面有多少个位置的前缀异或值与当前不同
-
这些位置到 i 组成的子数组,在第 j 位上的异或值为 1
以样例 [1, 2, 3, 4, 5]的第0位(最低位)为例: 原始二进制最低位:[1, 0, 1, 0, 1] 前缀异或(第0位):(0)[1, 1, 0, 0, 1] 统计过程: i=1: 当前前缀=1,之前前缀=0的个数=1 → 1个子数组 i=2: 当前前缀=1,之前前缀=0的个数=1 → 1个子数组 i=3: 当前前缀=0,之前前缀=1的个数=2 → 2个子数组 i=4: 当前前缀=0,之前前缀=1的个数=2 → 2个子数组 i=5: 当前前缀=1,之前前缀=0的个数=3 → 3个子数组 合计:1+1+2+2+3=9个子数组在第0位为1 贡献:9 × 2º = 9 -
完整代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
vector<vector<int>> a(n + 1, vector<int>(25));
for (int i = 1; i <= n; i++)
{
int t;
cin >> t;
for (int j = 0; j <= 20; j++)
{
a[i][j] = (t >> j) & 1; // 获取第j位的值
a[i][j] = a[i][j] ^ a[i - 1][j]; // 前缀异或和
}
}
int ans = 0;
for (int j = 0; j <= 20; j++)
{
map<int, int> mp; // 统计前缀异或和为0或1的个数
mp[0]++; // 空数组前缀异或和为0
for (int i = 1; i <= n; i++)
{
int cnt = mp[a[i][j] ^ 1]; // 前缀异或值与当前不同的个数
ans += cnt * (1LL << j); // 这些子数组贡献 2^j
mp[a[i][j]]++;
}
}
cout << ans << '\n';
return 0;
}
浙公网安备 33010602011771号