Sum of xor sum
这两题是一样的,但后面那个数据为空的,你输出“下次一定”都能过。
我们要求的就是给你任意区间[L,R],能得出这么一个东西
,直接从数的本身下手,是没有想法的。异或这个位操作有关的,我们可以从二进制位来考虑,如果我们知道了每一位对答案的贡献,那么最后直接把所有位的答案再加起来即可。
这里我们对j位进行讨论,用sum[i-1][j]来表示,前i-1个数在二进制位置j的答案前缀和,当在加多一个数a[i],怎么得到sum[i][j]呢?
首先,当加多a[i]之后,多的区间自然是[1,i],[2,i]...[3,i]以i为右边界的区间,这时我们再看哪些区间对答案有贡献。
用xr[i][j]表示前i个数在j位置的异或和,那对答案有贡献的区间情况无非两种,设区间为[k,i],便是xr[k][j]=0,xr[i][j]=1跟xr[k][j]=1,xr[i][j]=1。
因为xr[k][j]跟xr[i][j]相同的话,不就表示着j位在(k,i]区间内有偶数个1,这偶数个1异或为0,那(k,i]也就是[k+1,i]区间对答案自然没有贡献。
由此我们可知,当xr[i][j]=1时,对答案有贡献的区间左端点k便是xr[k][j]=0,而xr[i][j]=0时则相反。
所以这时候我们要用cnt0[i][j],来记录前i个数在j位置的异或和为0的数有多少个,cnt1[i][j]同理。明显初始值,cnt0[0][j]=1,cnt1[0][j]=0。
那么sum[i][j]=sum[i-1][j]+2j*(xr[i][j] ? cnt0[i-1][j] : cnt1[i-1][j]); (2j为j为的权重),也就是在[0,i-1]中找一个左端点k,使得(k,i]中有奇数个1。
要求区间[L,R]内的答案,自然是对每一位求ans+=sum[R]-sum[L-1],j位区间右端点在[L,R]范围中的答案
但此时还多算了点答案,也就是左端点在[0,L-1],而右端点在[L,R]中的答案,所以还得把这部分减去
ans-=2j*cnt0[L-2][j]*(cnt1[R][j]-cnt1[L-1][j]),ans-=2j*cnt1[L-2][j]*(cnt0[R][j]-cnt0[L-1][j])
为什么是L-2呢,看在[0,i-1]中找一个左端点k,使得(k,i]中有奇数个1那里,我们的区间是左开右闭的。
如果是L-1的话,就变成了(L-1,L~R]=[L,L~R]多减去了左端点为L的情况。
#include<bits/stdc++.h> using namespace std; const int N=1e5+11,M=21,md=1000000007; int cf2[N],sum[N][M],xr[N][M],cnt[3][N][M]; int main(){ int t,n,q,x; cf2[0]=1; for(int i=0;i<M;i++){ if(i) cf2[i]=cf2[i-1]<<1; cnt[0][0][i]=1; cnt[1][0][i]=0; } scanf("%d",&t); while(t--){ scanf("%d%d",&n,&q); for(int i=1;i<=n;i++){ scanf("%d",&x); for(int j=0;j<M;j++){ int pw=(x>>j)&1; pw=xr[i][j]=xr[i-1][j]^pw; cnt[0][i][j]=cnt[0][i-1][j]+(pw^1); cnt[1][i][j]=cnt[1][i-1][j]+pw; sum[i][j]=sum[i-1][j]+1ll*cf2[j]*cnt[pw^1][i-1][j]%md; if(sum[i][j]>=md) sum[i][j]-=md; } } int l,r; while(q--){ scanf("%d%d",&l,&r); long long ans=0; for(int i=0;i<M;i++){ ans+=sum[r][i]-sum[l-1][i]; ans=(ans%md+md)%md; if(l>=2){ ans-=1ll*cf2[i]*cnt[0][l-2][i]%md*(cnt[1][r][i]-cnt[1][l-1][i])%md; ans=(ans%md+md)%md; ans-=1ll*cf2[i]*cnt[1][l-2][i]%md*(cnt[0][r][i]-cnt[0][l-1][i])%md; ans=(ans%md+md)%md; } } printf("%lld\n",ans); } } return 0; }