【题目地址-luogu】
题意见原题面。
这个题暴力的方法就是求出所有的连续和然后异或起来,通过预处理前缀和,可以在 O ( n 2 ) O(n^2) O(n2)的时间内求出。
一种优化是用 F F T FFT FFT直接暴力降复杂度,但是很不好写还很慢。
我们可以看,它只要求求最后的异或和,并没有要求求所有连续和,所以我们通过二进制位来考虑,一位异或出来是1当且仅当所有连续和里面有奇数个这一位为1,0不会影响所以不管0。
通过前缀和的思想,我们知道任意一段连续和都可以表示为两个前缀和相减,所以我们一位一位考虑,当什么情况两个相减时该位为1什么时候该位为0。
举个例子可以发现,如果当前考虑位为 1 1 1,那么分两种情况:
- 前面要减去的那个数的那一位也为1,那么当前这个1减去1后还要为1,只有前面不够减,向高位借位的时候才可能为1,所以要要减去的那个数后面的位大于当前这个数后面的位。如 ( 110 ) 2 − ( 101 ) 2 = ( 001 ) 2 , ( 1101 ) 2 − ( 0110 ) 2 = ( 0111 ) 2 (110)_2-(101)_2=(001)_2,(1101)_2-(0110)_2=(0111)_2 (110)2−(101)2=(001)2,(1101)2−(0110)2=(0111)2(当前考虑从右往左第3位)
- 前面要减去的那个数的那一位为0,那么只要不借位,当前这位就直接是1-0=1了,所以要求要减去的那个数后面的位小于等于当前这个数后面的位。如 ( 101 ) 2 − ( 001 ) 2 = ( 100 ) 2 , ( 101 ) 2 − ( 011 ) 2 = ( 010 ) 2 (101)_2-(001)_2=(100)_2,(101)_2-(011)_2=(010)_2 (101)2−(001)2=(100)2,(101)2−(011)2=(010)2(当前考虑从右往左第3位)
如果当前这个数这位是0的话,同样分两种情况考虑即可,是差不多的。
- 要减去的那个数当前这个位为1,那么由于肯定要借位,只需后面的小于等于当前这个数的后面的位即可。
- 为0的话,为了要借位,只需大于即可。
所以我们用个权值树状数组,每次就可以快速统计有多少个1了。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define lowbit(a) ((a)&-(a))
using namespace std;
const int M=1e5+10,N=1e6+10;
int n;ll A[M],maxv;
ll now[M];
struct Bit_Tree{
int B[N];
void clear(){memset(B,0,sizeof(B));}
void add(int a){for(;a<=maxv;a+=lowbit(a))++B[a];}
int query(int a){int res=0;for(;a;a-=lowbit(a))res+=B[a];return res;}
int back(int a){return query(maxv)-query(a);}
}B1,B0;
ll ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){scanf("%lld",&A[i]);A[i]+=A[i-1];if(A[i]>maxv)maxv=A[i];}
for(int lg=0,rec;(1ll<<lg)<maxv;++lg){
B1.clear();B0.clear();
rec=0;B0.add(1);
for(int i=1,id,q;i<=n;i++){
id=A[i]&(1ll<<lg);
if(id) q=B0.query(now[i]+1)+B1.back(now[i]+1);
else q=B1.query(now[i]+1)+B0.back(now[i]+1);
if(q&1)rec^=1;id>0?B1.add(now[i]+1):B0.add(now[i]+1);
now[i]|=id;
}
if(rec)ans|=(1ll<<lg);
} printf("%lld\n",ans);
return 0;
}