[洛谷P3760][TJOI2017]异或和

题目描述

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

输入输出格式

输入格式

第一行输入一个n,表示这序列的数序列 第二行输入n个数字a1,a2...an代表这个序列
0<=a1,a2,...an,0<=a1+a2...+an<=10^6

输出格式

输出这个序列所有的连续和的异或值

输入输出样例

输入样例

3
1 2 3

输出样例

0

说明

【样例解释】

序列1 2 3有6个连续和,它们分别是1 2 3 3 5 6,则1 xor 2 xor 3 xor 3 xor 5 xor 6 = 0

【数据范围】

对于20%的数据,1<=n<=100
对于100%的数据,1<=n <= 10^5


想法

这个题还挺有意思的。

最初的想法是记录前缀和sum[],枚举每个区间,计算异或和。复杂度O(\(n^2\))
但这样明显过不了100%的数据,所以不能枚举每个区间。

依旧使用前缀和,每段区间和为sum[i]-sum[j]
注意到sum[n] \(\leq\) \(10^6\) ,于是可以一位位考虑区间对答案的贡献。
假设考虑到答案从右往左的第k位,当前区间和s=sum[i]-sum[j]
当s的第k位为1时对答案有贡献
s的第k位为1有这么几种可能:
sum[i]第k位为1,sum[j]第k位为0,且相减时不会在第k位借位(即sum[i]的前k-1为组成的数>sum[j]的前k-1位组成的数)
sum[i]第k位为1,sum[j]第k位为1,且相减时在第k位借位
sum[i]第k位为0,sum[j]第k位为1,且相减时不会在第k位借位
sum[i]第k位为0,sum[j]第k位为0,且相减时在第k位借位

将区间从左往右扫一遍,树状数组分别维护第k位为0或1的sum的前k-1位组成的数
按上边那几种可能计算对当前sum[i]有多少sum[j]满足sum[i]-sum[j]的第k位为1
cnt加一下
对于答案第k位,若cnt为偶数,则为0,否则为1


代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;

const int N = 1000005;

int sum[100005];

struct Bit{
    int c[N],size;
    void clear(){
        memset(c,0,sizeof(c));
        size=0;     
    }
    int lowbit(int x) { return x&(-x); }
    void add(int x){
        size++;
        while(x<N){
            c[x]++;
            x+=lowbit(x);           
        }
    }
    int query(int x){
        int ret=0;
        while(x){
            ret+=c[x];
            x-=lowbit(x);         
        }
        return ret;
    }
}p,q; //p:0 q:1

int n;

int main()
{
    int a;
    scanf("%d",&n);
    sum[0]=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&a);
        sum[i]=sum[i-1]+a;
    }
    
    int ans=0,cur,w;
    for(int i=0;i<20;i++){
        cur=0;
        p.clear(); q.clear();
        for(int j=0;j<=n;j++){
            w=sum[j]&((1<<i)-1);
            if(sum[j]&(1<<i)){
                cur+=p.query(w+1)+q.size-q.query(w+1);          
                q.add(w+1);
            }
            else{
                cur+=p.size-p.query(w+1)+q.query(w+1);
                p.add(w+1); 
            }
        }
        if(cur&1) ans+=(1<<i);
    }
    printf("%d\n",ans);
    
    return 0;    
}

转载于:https://www.cnblogs.com/lindalee/p/8393239.html

根据引用[1]中的代码,这段代码计算了一个数列的异或结果。首先,代码判断输入的数n是否为偶数,如果是偶数,则将n减1,并将结果赋给n。然后,代码计算了奇数的个数,如果奇数的个数为奇数,则将结果赋给ans,否则将0赋给ans。最后,如果n是偶数,则将ans与(n-1)进行异或运算,否则直接输出ans。这段代码的目的是计算数列的异或结果。[1] 根据引用[2]中的代码,这段代码也是计算了一个数列的异或结果。首先,代码判断输入的数n是否为偶数,如果是偶数,则将n减1,并将结果赋给n,并将even设置为true。然后,代码判断(n-1)右移1位后的结果是否为奇数,如果是奇数,则将1赋给ans,否则将0赋给ans。最后,如果n是偶数,则将ans与(n-1)进行异或运算,否则直接输出ans。这段代码的目的也是计算数列的异或结果。[2] 根据引用[3]中的代码,这段代码同样是计算了一个数列的异或结果。代码首先读取输入的数n,并计算n对4取余的结果赋给m。然后,根据m的值,分别计算不同情况下的ans的值。如果m为0,则将n赋给ans;如果m为1,则将1赋给ans;如果m为2,则将(n-1)赋给ans;如果m为3,则将0赋给ans。最后,输出ans。这段代码的目的也是计算数列的异或结果。[3] 综上所述,这些代码的目的都是计算数列的异或结果,只是实现的方式略有不同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值