异或和之和

异或和之和

P9236 [蓝桥杯 2023 省 A] 异或和之和 - 洛谷 | 计算机科学教育新生态


问题

给定一个数组 A i A_i Ai,分别求其每个子段的异或和,并求出它们的和。或者说,对于每组满足 1 ≤ L ≤ R ≤ n 1 \leq L \leq R \leq n 1LRn L , R L,R L,R,求出数组中第 L L L 至第 R R R 个元素的异或和。然后输出每组 L , R L,R L,R 得到的结果加起来的值。

输入格式

输入的第一行包含一个整数 n n n

第二行包含 n n n 个整数 A i A_i Ai,相邻整数之间使用一个空格分隔。

输出格式

输出一行包含一个整数表示答案。

样例 #1

样例输入 #1
5
1 2 3 4 5
样例输出 #1
39

提示

【评测用例规模与约定】

对于 30 % 30 \% 30% 的评测用例, n ≤ 300 n \leq 300 n300

对于 60 % 60 \% 60% 的评测用例, n ≤ 5000 n \leq 5000 n5000;

对于所有评测用例, 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1n105 0 ≤ A i ≤ 2 20 0 \leq A_i \leq 2^{20} 0Ai220


我的解答(未AC)

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
int a[100001];
int b[100001] = {0};
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        b[i] = b[i - 1] ^ a[i];
        ans += b[i];
    }
    for (int i = n; i >= 1; i--)
    {
        for (int j = 1; j < i; j++)
        {
            if (b[i] == b[j])
            {
                continue;
            }
            else
            {
                ans += (b[i] ^ b[j]);
            }
        }
    }
    cout << ans << endl;
}

通过异或前缀和数组暴力枚举算出ans,时间复杂度为O(n²),只能拿到60分


优化

要对前缀异或数组中元素两两异或的时间复杂度 O ( n 2 ) O(n^2) O(n2)进行优化,考虑到异或的性质,我们可以选择将整数转为二进制,按位运算:

for (int j = 0; j <= 20; ++j) {
    a[i][j] = (x >> j) & 1;
    a[i][j] ^= a[i - 1][j];
}

考虑到数字的范围 2 20 2^{20} 220,我们开a[20]来存每一个元素的每一位

  • a[i][j] = (x >> j) & 1;的作用是求出x二进制的每一位,方法是先右移再和1做位与运算
  • a[i][j] ^= a[i - 1][j];即在每一位上求异或前缀和

现在求得了所有元素的异或前缀和数组,这样我们可以在每一位上两两异或并累加求得该位的区间异或和
这里我们使用空间换时间的方式,将 n 2 n^2 n2压缩到 n n n

	for (int j = 0; j <= 20; ++j) {
		map<int, int> m;
		m[0]++;
		for (int i = 1; i <= n; ++i) {
			int x = m[a[i][j] ^ 1];
			ans += 1LL * (1 << j) * x;
			m[a[i][j]]++;
		}
	}

这里对代码进行逐行解释:

int x = m[a[i][j] ^ 1]
  • a[i][j] ^ 1是对该位取反,属于异或运算的基本应用
  • 如果异或和区间在该位上的元素(0、1)想对最终结果做出贡献,就必须让 a i ⊕ a i + 1 ⊕ . . . ⊕ a m a_i\oplus a_{i+1}\oplus...\oplus a_m aiai+1...am的结果为 1 1 1,在异或前缀和上就是$b_{i-1}\oplus b_{m} $的结果为 1 1 1:由于这里只有 0 、 1 0、1 01两个元素,某个元素的异或就是另一个元素,我们使用map(vector[2]同理)来存储0和1的出现次数,以空间换时间;
  • x即与a[i][j]异或值为1的元素个数
ans += 1LL * (1 << j) * x;
m[a[i][j]]++;
  • (1 << j) * x;对1左移 j j j位再乘上x,即异或前缀和元素在该位上两两异或为1总次数,并将其还原成十进制计算结果

  • m[a[i][j]]++记录该位,用于后续该位元素的计算

通过这种方法,我们将时间复杂度 O ( n 2 ) O(n^2) O(n2)压缩到了 O ( 20 n ) O(20n) O(20n),成功AC


总结

这道题的思路在于,题目给出了 A i A_i Ai元素的范围 2 20 2^{20} 220,提示了我们可以将他转换成二进制数,也就是拆位

又因为每一位只可能是1或0,我们可以借助长度为2的数组对记录元素出现的次数,避免了双重循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值