UVALive8518 Sum of xor sum
题目链接:https://vjudge.net/problem/UVALive-8518
题目大意:
给定一个长度为 $N$ 的数字序列 $A$,进行 $Q$ 次询问,每次询问 $[L,R]$,需要回答这个区间内的子序列的所有子序列的异或和之和。
$1 \le N,Q \le 100000$
$0 \le A[i] \le 1000000$
知识点: 前缀和
解题思路:
将序列中的每一个数转成二进制(不超过 $20$ 位),逐位考虑。
根据序列中的数字用二进制表示时在该位上为 $1$ 或 $0$,我们可以用 $20$ 个 $01$ 序列来表示序列 $A$。现在只考虑二进制位中的某一位,其他位做类似处理即可:
先预处理出异或前缀和 $preXOR$(很明显,它是个 $01$ 序列),用 $zero[][i]$ 和 $one[][i]$ 表示在 $[1,i]$ 这个区间中的 $preXOR$ 有多少个 $0$ 和 $1$。然后用 $sum[][i]$ 表示 $[1,i]$ 这个区间的答案。易知 $sum[][i] = sum[][i-1] + (所有以 A[i] 为右边界的子序列对答案的贡献)$,即 $sum[j][i] = sum[j][i-1] + 2^j *(preXOR[j][i]:zero[j][i-1]?one[j][i-1])$,因为只有当左边界的左边一位的异或前缀和和右边界的异或前缀和的异或和为 $1$ 时该区间才对答案有贡献。
查询 $[L,R]$ 的时候,对于第 $j$ 位,对答案的贡献为:$sum[j][R]-sum[j][L-1]-zero[j][L-2]*(one[j][R]-one[j][L-1])*2^j -one[j][L-2]*(zero[j][R]-zero[j][L-1])*2^j$.
后半部分其实是减掉那些左边界在 $[1,L-1]$ 而右边界在 $[L,R]$ 的区间对答案的影响,还是一样的道理: 只有当左边界的左边一位的异或前缀和和右边界的异或前缀和的异或和为 $1$ 时该区间才对答案有贡献。
"为什么是l-2,因为左端点要<=l-1,如果是l-1的话就表示从l开始了。"
AC代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long LL; 5 const int MAXN=100000+5; 6 const int MOD=1e9+7; 7 8 int A[MAXN]; 9 int preXOR[24][MAXN]; 10 LL sum[24][MAXN],zero[24][MAXN],one[24][MAXN]; 11 int main(){ 12 int T; 13 scanf("%d",&T); 14 while(T--){ 15 int N,Q; 16 scanf("%d%d",&N,&Q); 17 for(int i=1;i<=N;i++) scanf("%d",&A[i]); 18 for(int i=0;i<20;i++){ 19 preXOR[i][0]=0; 20 one[i][0]=0,zero[i][0]=1; 21 for(int j=1;j<=N;j++){ 22 if(A[j]&(1<<i)) 23 preXOR[i][j]=preXOR[i][j-1]^1; 24 else 25 preXOR[i][j]=preXOR[i][j-1]; 26 one[i][j]=one[i][j-1],zero[i][j]=zero[i][j-1]; 27 if(preXOR[i][j]) one[i][j]++; 28 else zero[i][j]++; 29 } 30 } 31 for(int i=0,now=1;i<20;i++,now<<=1){ 32 sum[i][0]=0; 33 for(int j=1;j<=N;j++){ 34 sum[i][j]=sum[i][j-1]; 35 if(preXOR[i][j]) 36 sum[i][j]=(sum[i][j]+zero[i][j-1]*now)%MOD; 37 else 38 sum[i][j]=(sum[i][j]+one[i][j-1]*now)%MOD; 39 } 40 } 41 while(Q--){ 42 int L,R; 43 scanf("%d%d",&L,&R); 44 LL ans=0; 45 for(int i=0,now=1;i<20;i++,now<<=1){ 46 ans=(ans+sum[i][R]-sum[i][L-1])%MOD; 47 if(L>=2) 48 ans=(ans-zero[i][L-2]*(one[i][R]-one[i][L-1])*now%MOD 49 -one[i][L-2]*(zero[i][R]-zero[i][L-1])*now%MOD)%MOD; 50 } 51 if(ans<0) ans+=MOD; 52 printf("%lld\n",ans); 53 } 54 } 55 return 0; 56 }