2617 小O与小X的比赛(二)

题目链接

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值