Description
在又一次失败后,小O深刻反省了自己本身,他感到自己是十分的菜,他想要自己的水平能够迅速提高起来,于是他向小X求助,小X在听了他的诉说后,决定先考考小O的水平如何,于是说道: “我有n个数字,我可以知道任意区间内任意两个数字异或和的和,你可以吗?”小O苦思冥想之后给出了答案。你能知道他是怎样解答的吗?
Input
先输入一个整数组数T,表示数据的组数,每组数据输入一个n,m表示共n个整数,m个区间,下面先输入n个整数a1…an,编号从1到n,然后输入m个区间,每个区间包括两个数l,r (T = 10 , 1<=n,m<=10^5, 1 <=l<=r<= n , 1 <= ai <= 10^6)
Output
对于每个区间,输出其区间内任意两个数字异或和的总和,对1000000007取余。
Sample Input
1
5 3
1 1 1 2 3
1 3
3 5
2 2
Sample Output
0
6
0
HINT
对于第二组数据,任意两个数异或情况共3种, 1 ^ 2 = 3 , 1 ^ 3 = 2 ,2 ^ 3 = 1 因此答案为6
题意: 看到上面的提示后,题意就显而易见了。
思路: 刚看到这道题的时候一点思路都没有,后来听学长说这题是看每一位数的贡献度,也可以用莫队写,但是会T掉。
样例中的第二个数据:
1: 01
2: 10
3: 11
1的第0位与2的0位异或为1,与3中的0位异或为0。及当前贡献为1
2的0位与3的0位异或为1,及当前贡献为1
所以将其用十进制表达后,2 * 2 ^ 0=2.
同理,2 * 2 ^ 1=4.
2+4=6及为答案。
因为1e6最多也就2 ^ 20,所以我们可以预处理1~n个数中 每一位数0和1的个数,然后对于区间[L,R]中每一位num0 * num1 * 2 ^ i(当前第几位)。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN = 110000;
const int mod = 1e9+7;
int num0[MAXN][32];
int num1[MAXN][32];
int Pow[30];
void init()
{
Pow[0]=1;
for(int i=1;i<=20;i++) Pow[i]=Pow[i-1]*2;
return ;
}
int main()
{
init();
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
for(int j=0;j<=20;j++){
if((x>>j)&1){
num1[i][j] = num1[i-1][j]+1;
num0[i][j] = num0[i-1][j];
}
else{
num0[i][j] = num0[i-1][j]+1;
num1[i][j] = num1[i-1][j];
}
}
}
while(m--){
int l,r;
ll ans=0;
scanf("%d%d",&l,&r);
for(int i=0;i<=20;i++){
int s0=num0[r][i]-num0[l-1][i];
int s1=num1[r][i]-num1[l-1][i];
ans+=(1ll*s0*s1*Pow[i])%mod;
ans%=mod;
}
printf("%lld\n",ans%mod);
}
}
return 0;
}