一.题目
给定一个长度为 nn 的整数数组 a1,a2,…,an.
请你统计一共有多少个数组 a 的非空连续子数组能够同时满足以下所有条件:
- 该连续子数组的长度为偶数。
- 该连续子数组的前一半元素的异或和等于其后一半元素的异或和。
例如,当给定数组为 [1,2,3,4,5]时,满足条件的连续子数组只有 11 个:[2,3,4,5]。
输入格式
第一行包含整数 n。
第二行包含 n 个整数 a1,a2,…,an。
输出格式
一个整数,表示满足条件的连续子数组的数量。
数据范围
前三个测试点满足 2≤n≤102≤n≤10。
所有测试点满足 2≤n≤3×1052≤n≤3×105,0≤ai<2200≤ai<220。
输入样例1:
5
1 2 3 4 5
输出样例1:
1
输入样例2:
6
3 2 2 3 7 6
输出样例2:
3
输入样例3:
3
42 4 2
输出样例3:
0
二.思路
求出所有连续的子序列(满足连续异或和为0,且该序列中元素个数为偶数)的个数。
这里给两种时间复杂度求解思路
O(n^2)思路:对所有的子序列暴力求解,算出每一个子序列的的异或和,是不是为0。当然这肯定过不了。
O(n)思路:
先看一个简单的子问题:怎么O(n)时间复杂度求出一个数组中子序列的异或和为0的个数?
先遍历一遍数组a,求出所有前i个元素的异或和-b[i]。好的,我们再来看两个式子:
a ^ 0 = a
a ^ b ^ b = a
a ^ (b ^ b) = a
我们可以看出异或在这里的性质啦,当一个数a与一个连续子序列(异或和为0)异或时,结果为a本身。所以我们只需要求出所有b[i] == b[j],有多少个就OK啦
回到原问题本身先遍历一遍数组a,求出所有前i个元素的异或和-b[i]。再然后我们将其分为偶数,奇数位置,对应放进f[2][11000005]中(0代表偶数,1代表奇数);这里为什么要分为奇数偶数位置呢?因为题目说了,子序列元素个数得为偶数。a[1]到a[4],中间只有三个元素(a2,a3,a4)
a[1]到a[5],中间就有四个元素(a2,a3,a4,a5)(可以按照题目的第一个样例输出比对)
f[0][j]代表偶数位置上,b[x] == j有多少个
最后将偶数中最特殊的f[0][0]给加一。
下面看ac代码
#include <bits/stdc++.h>
//#include <iostream>
using namespace std;
//#define push() push_back()
//#define endl '\n'
//#define ull unsigned long long
//#define int long long
//const int N=5e3;
//int a[N+10][N+10];
#define rep(i, a, b) for (int i = a; i < b; i++)
#define rrep(i,a,b) for (int i = a; i > b; i--)
int a[300005],f[2][11000005],b[300005];
void solve(){
int n;
long long ans=0;
cin>>n;
rep(i,1,n+1){
cin>>a[i];
}
b[0]=0;
rep(i,1,n+1){
b[i] = b[i-1] ^ a[i];
}
f[0][0]++;
for(int i=1;i<=n;i++){
ans+=f[i%2][b[i]];
f[i%2][b[i]]++;
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
// ofstream cout("Eout.txt");
solve();
return 0;
}
三.总结
求连续子序列异或和方法,可以先遍历一遍元素组,得到所有前i个异或和bi,再看bi==bj有多少个