2023 C++ A 组 T8. 异或和之和

前言

「算法题解」系列致力于分享有价值的题目、探讨更优秀的解法。

这是本系列的第 19 篇题解,更多题解关注 Somnia1337@力扣。

题目描述

本题目的在线练习:

  • 洛谷
https://www.luogu.com.cn/problem/P9236
  • Dotcpp
https://www.dotcpp.com/oj/problem3147.html

洛谷:普及+/提高

Dotcpp:中等

给定一个数组 a,分别求其每个子数组的异或和,并输出它们的和。

「输入」

第一行包含一个整数 n

第二行包含 n 个整数 a[i],相邻整数之间使用一个空格分隔。

「输出」

一个整数,表示答案。

「样例」

输入:

5
1 2 3 4 5

输出:

39

解题思路

位运算具有自反性,即

类比前缀和,对 求前缀异或和,记 ,则子数组 的异或和为

对两个前缀异或和 ,按位对齐之后,异或为 的位不会对答案产生贡献,为 的位产生 的贡献,其中 为该位所在位数。

但是,不能遍历每对前缀和(复杂度 过高),而需要按位遍历。

对每一位 ,遍历 ,计数 的数量,用组合数学可得该位对答案的贡献为

实现时,只需使用 的前缀和,在 中原地前缀化即可。

复杂度

  • 时间复杂度:,按位遍历产生常数
  • 空间复杂度:

代码

Java
import java.util.*;
import java.io.*;
 
public class Main {
    public static void main(String... args) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        PrintWriter out = new PrintWriter(System.out);
		
        // 输入
        int n = Integer.parseInt(in.readLine());
        long[] a = new long[n + 1]; // 多开 1, 原地前缀化
        StringTokenizer st = new StringTokenizer(in.readLine());
        // 输入的同时求前缀异或和
        for (int i = 1; i <= n; i++) a[i] = Integer.parseInt(st.nextToken()) ^ a[i - 1];
		
        // 处理
        long ans = 0;
        // 按位遍历, 最多 20 位
        for (int d = 0; d <= 20; d++) {
            // 计数 0 和 1
            int[] cnt = new int[2];
            for (int i = 0; i <= n; i++) cnt[(int) (a[i] >> d & 1)]++;
            // 累加贡献
            ans += (1L << d) * cnt[0] * cnt[1];
        }
		
        // 输出
        out.println(ans);
		
        out.flush();
        out.close();
    }
}

最后

更多系列「外源推文」&「生活分享」关注公众号: