[BZOJ4888][洛谷3760]异或和 树状数组

题目描述

在加里敦中学的小明最近爱上了数学竞赛,很多数学竞赛的题都是与序列的连续和相关的。所以对于一个序列,求出它们所有的连续和来说,小明觉得十分的简单。但今天小明遇到了一个序列和的难题,这个题目不仅要求你快速的求出所有的连续和,还要快速的求出这些连续和的异或值。小明很快的就求出了所有的连续和,但是小明要考考你,在不告诉连续和的情况下,让你快速求是序列所有连续和的异或值。

题解:

洛谷上面的那个题解写得挺不错的,我的代码上也有少量注释,可以将就着看看。

代码:

#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);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值