题目描述
在加里敦中学的小明最近爱上了数学竞赛,很多数学竞赛的题都是与序列的连续和相关的。所以对于一个序列,求出它们所有的连续和来说,小明觉得十分的简单。但今天小明遇到了一个序列和的难题,这个题目不仅要求你快速的求出所有的连续和,还要快速的求出这些连续和的异或值。小明很快的就求出了所有的连续和,但是小明要考考你,在不告诉连续和的情况下,让你快速求是序列所有连续和的异或值。
题解:
洛谷上面的那个题解写得挺不错的,我的代码上也有少量注释,可以将就着看看。
代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100010;
int n,a[maxn],s[3][1000010],sum[maxn],b[maxn];
//b[i]为当前sum[i]的值(也就是sum[i]的右边若干位的值)
void add(int o,int x,int y){for(;x<=1000000;x+=(x&-x))s[o][x]+=y;}
int getsum(int o,int x){int re=0;for(;x;x-=(x&-x))re+=s[o][x];return re;}
/*
如果当前扫描到的s[i]的二进制第k位为1,那么对这一位的答案有贡献的只有那些第k位为1且第k位向右的数比s[i]第k位向右的数大的
或者第k位为0且第k位向右的数不比s[i]第k位向右的数大的。
为什么呢?
因为如果第k位都为1的话,那么只有后面那些位的和大于s[i]的数,s[i]减去它之后第k位才能出现1
(因为s[i]比它小的话需要向更高位借数,就和小学学的横式减法差不多),从而对答案作出贡献;
如果第k位为0的话,如果后面再比s[i]大的话,s[i]第k位的1就需要借给低一位的了,所以后面必须不比s[i]大。
*/
int main()
{
int ans=0;
scanf("%d",&n);sum[0]=0;
for(int i=1;i<=n;i++)scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
for(int i=0;i<=20;i++)
{
if((1<<i)>sum[n])break;
int cnt=0;
memset(s,0,sizeof(s));
add(0,1,1);//处理sum[0](sum[0]=0)
for(int j=1;j<=n;j++)
{
int t=(sum[j]&(1<<i)),o;
//这位的数 1的个数
if(t)o=getsum(1,1000000)-getsum(1,b[j]+1)+getsum(0,b[j]+1);
else o=getsum(0,1000000)-getsum(0,b[j]+1)+getsum(1,b[j]+1);
if(o&1)cnt^=1;
add((t>0)?1:0,b[j]+1,1);
b[j]|=t;
}
if(cnt)ans|=(1<<i);
}
printf("%d",ans);
}