2020牛客寒假算法基础集训营4.D——子段异或【前缀】

题目传送门


题目描述

输入一个数列a,你需要输出其中异或值为0的不同子段的数量。一个子段 [ l , r ] ( 1 l r n ) [l,r] (1 \le l \le r \le n) 的异或值为 a l a l + 1 a l + 2 a r a_l \oplus a_{l+1} \oplus a_{l+2} \oplus \ldots\oplus a_r ,其中 \oplus 符号代表异或运算。
两个子段被视为相同的,当且仅当其开始和结束位置均对应相同。


输入描述:

第一行一个整数 n ,代表数列长度。
第二行 n 个整数,代表数列。

输出描述:

输出一个整数,代表答案。


输入

5
1 2 3 2 1


输出

2


说明

子段 [1,3] 和子段 [3,5] 是合法子段。


备注:

n 200000 , 0 a i 2 30 1 n \le 200000, 0 \le a_i \le 2^{30}-1


题解

  • 很容易想到记录前缀和,但是记录之后肯定不是暴力 n 2 n^2 的,我们要考虑如何简化这过程。
  • 如果区间 [ l , r ] [l,r] 是合法的,那么前缀中 p r e [ r ] p r e [ l 1 ] = 0    & &    p r e [ l 1 ] = p r e [ r ] pre[r] \oplus pre[l-1]=0 \ \ \&\& \ \ pre[l-1]=pre[r]
  • 那么我们就可以从前到后 O ( n ) O(n) 求一边异或前缀和,然后对于每个前缀和,从前到后,记录当前异或的结果
  • 如果当前结果出现过,那么说明满足上述条件,加上这个前缀和出现过的次数即可。
  • 当然既然所有操作之和之前的出现过的情况有关,我们可以将两步操作合并。

AC-Code

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5 + 7;

int a[maxn];
int main() {
	int n;	  cin >> n;
	ll ans = 0;
	int num = 0;
	map<int, int> mp;
	mp[0] = 1;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
		num ^= a[i]; // 记录前缀和
		ans += mp[num]; // 加上之前前缀和是num的次数
		mp[num]++; // 次数++
	}
	cout << ans << endl;
	return 0;
}

发布了179 篇原创文章 · 获赞 109 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Q_1849805767/article/details/104269101