每日一题:异或和之和

来源:2023蓝桥杯省赛大学A组

P9236 异或和之和 - 洛谷

异或和之和 - 蓝桥云课

题解

  • 问题简化

    直接枚举所有子数组的时间复杂度是 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;
}