题目描述
难度分:1404
输入n(1≤n≤2×105)和长为n的数组a(0≤a[i]<220)。
a有多少个非空连续子数组,满足元素和等于元素异或和?
输入样例1
4
2 5 4 6
输出样例1
5
输入样例2
9
0 0 0 0 0 0 0 0 0
输出样例2
45
输入样例3
19
885 8 1 128 83 32 256 206 639 16 4 128 689 32 8 64 885 969 1
输出样例3
37
算法
双指针
异或运算本身就是不进位的加法(二进制下的不进位),因此异或和不会超过元素和,有如下的单调性成立:
- 如果一个子数组满足异或和等于元素累加和,那么把这个子数组缩短肯定也满足。
- 如果一个子数组不满足异或和等于元素累加和,那么把这个子数组扩大肯定也不满足(因为不相等已经意味着元素和进位了,再继续扩大元素和也不可能相等的)。
这样我们就可以枚举子数组的右端点r∈[1,n],初始化左指针l=0,对于一个刚好满足l<r且s[r]−s[l]=eor[r]⊕eor[l]的l(s表示a的前缀和数组,eor表示a的前缀异或和数组),对于任意l<j≤r,肯定都有子数组[j,r]的异或和等于元素累加和,产生了r−l个符合条件的子数组,在遍历r的过程中统计所有右端点对答案的贡献即可。
复杂度分析
时间复杂度
整个过程是一个经典的双指针算法,子数组右边界指针r∈[1,n],由于左指针l随着r的右移不会回退,因此算法整体的时间复杂度就是O(n)。
空间复杂度
开辟了前缀和数组s以及前缀异或和数组eor,和原数组a的规模相同,因此额外空间复杂度为O(n)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>
using namespace std;
typedef long long LL;
const int N = 200010;
LL s[N];
int n, a[N], eor[N];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
s[i] = s[i - 1] + a[i];
eor[i] = eor[i - 1]^a[i];
}
LL ans = 0;
int l = 0;
for(int r = 1; r <= n; r++) {
while(l < r && (eor[l]^eor[r]) != s[r] - s[l]) l++;
ans += r - l;
}
printf("%lld\n", ans);
return 0;
}