bzoj4888 [Tjoi2017]异或和

http://www.elijahqi.win/archives/3123
Description

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

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

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

3
1 2 3
Sample Output

0
HINT

Source

树状数组:

仍然预处理前缀和

考虑按位维护答案 如果只考虑这一位的话 那么我考虑枚举每个前缀作为终点 然后再去枚举前面的前缀作为起点的情况 那考虑竖式加减法的操作 考虑我当前这一位如果是1 那么我就需要在前面找一个当前这一位是0的位置 并且(0~k-1)位所构成的数需要小于等于我(0~k-1)位所构成的数 或者 找前面第k位是1 并且(0~k-1)位构成的数比我(0~k-1)位所构成的数要大 都是答案

另外一种情况如果我这一位是1 同理

那么我就需要在前面找一个当前这一位是1的位置 并且(0~k-1)位所构成的数需要小于等于我(0~k-1)位所构成的数 或者 找前面第k位是0 并且(0~k-1)位构成的数比我(0~k-1)位所构成的数要大 都是答案

考虑快速维护这个有多少个 就使用权值树状数组即可 可能会出现0等情况 提前判断+1即可

注意存在前缀和为0的情况..


#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(!isdigit(ch)) {if (ch=='-') f=-1;ch=gc();}
    while(isdigit(ch)) x=x*10+ch-'0',ch=gc();
    return x*f;
}
const int N=1e6+100;
int s[2][N],ans,sum[N],lim,n;
inline void add(int op,int x,int v){++x;
    while(x<=lim+5) s[op][x]+=v,x+=x&-x;
}
inline int query(int op,int x){static int t;++x;
    t=0;while(x) t+=s[op][x],x-=x&-x;return t&1;
}
inline int get(int op,int x,int y){return (query(op,y)-query(op,x)+2)&1;}
inline void clear(){
    for (int owo=0;owo<=1;++owo)
        for (int i=1;i<=lim+5;++i) s[owo][i]=0;
}
int main(){
    freopen("bzoj4888.in","r",stdin);
    n=read();for (int i=1;i<=n;++i) sum[i]=sum[i-1]+read();
    lim=sum[n];int t=0;while(lim) ++t,lim>>=1;lim=sum[n];
    for (int k=0;k<t;++k){static int bin,lst,cnt;
        bin=1<<k;cnt=0;
        for (int i=0;i<=n;++i){lst=sum[i]&(bin-1);
            if (sum[i]&bin){
                cnt+=query(0,lst);cnt+=get(1,lst,bin-1);add(1,lst,1);
            }else{
                cnt+=query(1,lst);cnt+=get(0,lst,bin-1);add(0,lst,1);
            }cnt&=1;
        }ans+=cnt*bin;clear();
    }printf("%d\n",ans);
    return 0;
}

考虑每种权值出现的情况 设sn表示所有权值的前缀和 那么这个显然可以用所有前缀表示所有的区间和 那么设 A(x)=1+x1+x2+...xsn A ( x ) = 1 + x 1 + x 2 + . . . x s n B(x)=1+x1+x2+...xsn B ( x ) = 1 + x 1 + x 2 + . . . x s n 最后答案是 j=1ni=1snja[i]b[i+j] ∑ j = 1 n ∑ i = 1 s n − j a [ i ] ∗ b [ i + j ] 于是参考快速傅里叶2 那么直接将其中一个串反过来ntt即可 但是蒟蒻我没有优秀的常数bzoj过不去..

#include<cstdio>
#include<cctype>
#include<algorithm>
#define ll long long
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(!isdigit(ch)) {if (ch=='-') f=-1;ch=gc();}
    while(isdigit(ch)) x=x*10+ch-'0',ch=gc();
    return x*f;
}
const int mod=998244353;
const int g=3;
const int N=1e6+20;
int a[N<<2],b[N<<2],R[N<<2];
int s[110000],nn,n,m;
ll inv;
inline int ksm(ll b,int t){static ll tmp;
    tmp=1;for (;t;b=b*b%mod,t>>=1)if (t&1) tmp=tmp*b%mod;return tmp;
}
inline int inc(int x,int v){x+=v;return x>=mod?x-mod:x;} 
inline int dec(int x,int v){x-=v;return x<0?x+mod:x;}
inline void ntt(int *x,int f){
    for (int i=0;i<n;++i) if (i<R[i]) swap(x[i],x[R[i]]);
    for (int i=1;i<n;i<<=1){
        int wn=ksm(g,f==1?(mod-1)/(i<<1):mod-1-(mod-1)/(i<<1));
        for (int j=0;j<n;j+=i<<1){
            ll w=1,t1,t2;int tmp1,tmp2;
            for (int k=0;k<i;++k,w=w*wn%mod){
                t1=x[j+k],t2=w*x[j+k+i]%mod;
                tmp1=inc(t1,t2);tmp2=dec(t1,t2);
                x[j+k]=tmp1;x[j+k+i]=tmp2;
            }
        }
    }
    if (f==-1) for (int i=0;i<n;++i) x[i]=inv*x[i]%mod;
}
int main(){
    freopen("bzoj4888.in","r",stdin);
    nn=read();
    for (int i=1;i<=nn;++i) s[i]=s[i-1]+read();
    for (int i=0;i<=nn;++i) ++a[s[i]],++b[s[nn]-s[i]];
    m=s[nn]<<1;int l=0;for (n=1;n<m;n<<=1,++l);inv=ksm(n,mod-2);
    for (int i=0;i<n;++i) R[i]=(R[i>>1]>>1)|(i&1)<<l-1;
    ntt(a,1);ntt(b,1);
    for (int i=0;i<n;++i) a[i]=(ll)a[i]*b[i]%mod;
    ntt(a,-1);int ans=0;
    for (int i=s[nn];i<=s[nn]<<1;++i) if (a[i]&1)  ans^=i-s[nn];
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值