P3760 [TJOI2017]异或和

思路

二进制肯定拆位考虑
然后思考每个位上多少个连续和的贡献次数
前缀和一下,假设求得第k位,问题变成了求解对一个i,有多少个s[i]-s[j]的对应位是1
因为有减法,所以考虑一下借位的思想
假设这个数对应位是1,就是求有多少个数对应位是0且右侧的二进制位的大小小于等于这个数右侧的二进制位(不借掉前面的1)和多少个数对应位是1且右侧的二进制位的大小大于这个数右侧的二进制位(借掉前面的1),对应位是0则相反,如果有奇数个,就对答案的对应位产生贡献,否则不产生贡献
然后总和小于1e6,上权值树状数组即可

代码

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int MAXW;
int s[100100],num[100100],n;
struct BIT{
    int w[1000100];
    int lowbit(int x){
        return x&(-x);
    }
    void add(int pos){
        pos++;
        while(pos<=MAXW){
            w[pos]++;
            pos+=lowbit(pos);
        }
    }
    int query(int pos){
        pos++;
        int ans=0;
        while(pos){
            ans+=w[pos];
            pos-=lowbit(pos);
        }
        return ans;
    }
    void init(void){
        memset(w,0,sizeof(w));
    }
}B0,B1;
int main(){
    int ans=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&s[i]),s[i]+=s[i-1],MAXW=max(MAXW,s[i]);
    for(int i=0;i<21;i++){
        B0.init();B1.init();
        B0.add(0);
        for(int j=1;j<=n;j++){
            int mid=0;
            if((s[j]>>i)&1){
                mid=B1.query(MAXW-1)-B1.query(num[j])+B0.query(num[j]);
                B1.add(num[j]);
                num[j]|=(1<<i);
            }
            else{
                mid=B0.query(MAXW-1)-B0.query(num[j])+B1.query(num[j]);
                B0.add(num[j]);
            }
            if(mid%2)
                ans^=(1<<i);
        }  
    }
    printf("%d\n",ans);    
    return 0;
}
posted @ 2019-03-15 16:28  dreagonm  阅读(182)  评论(0编辑  收藏  举报