南昌不翻车 ACM-ICPC 2017 Asia Xi'an Sum of xor sum(思维好题)

题目链接:

https://nanti.jisuanke.com/t/A1613

题目大意:

    n的数组,q次询问,L-R区间内所有子段的异或和之和。

题目思路:

     考虑每一位算贡献。每个数位单独拿出来的话,对于一个区间的贡献就是

      ans += 【1-R】的贡献 【1 - L-1】的贡献 -  左端点在【1,L-1】范围内的,右端点在【L -R 】范围内的。

       先考虑前缀贡献怎么算,假如有了前i-1个数的贡献,那么第i的前缀贡献就是,包含第i个位置的所有子段的贡献,那么怎么计算这个东西呢,这个东西出来了,前缀贡献就出来了。

       我们可以处理一个前缀异或和,如果到第i位的前缀异或和为0,那么前边【0,i-1】有多少个前缀异或和为1,这个 i 这一位就贡献了几次(一定要包含第0个位置,因为这是类似于相减的东西,如果不算0的话就会漏 1 - (i -1)的情况

       通过上边我们还发现需要处理一个前缀异或和上0有多少个,1有多少个的数组就是代码中的one 和 zero。

       接下来就是怎么减去左端点在【1,L-1】范围内的,右端点在【L -R 】范围内的贡献了,这个又需要一个减法的思想。

如果右端点在【L,R】中,并且前缀异或和为1,为了使得某个区间异或和为1,就必须减去【1,L-2】中前缀异或和为0的。

同理,如果前缀异或和为0,为了使得某个区间异或和为1,就必须减去【1,L-2】中前缀异或和为1的。

(为啥是L-2呢,如果是L-1的话,用大区间一减这个小区间,那么就完全包含在【L-R】里头了)

数组含义:

perX【j】【i】:从第一个数字 到 第i个数字   第j位 的前缀异或和  。 (非0即1)

one【j】【i】: 从第一个数字 到 第i个数字    第j位 的前缀异或和中有多少个1。(【1 , i】前缀异或和1个数)

zero【j】【i】:从第一个数字 到 第i个数字    第j位 的前缀异或和中有多少个0。(【1 , i】前缀异或和0个数)

sum【j】【i】:从第一个数字 到 第i个数字    第j位 的所有子段异或和贡献。

 

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 1e5+5;
const int mod = 1e9+7;
int preX[25][MAXN],a[MAXN];
ll sum[25][MAXN],zero[25][MAXN],one[25][MAXN];
int main()
{
   int t;
   scanf("%d",&t);
   while(t--)
   {
       int n,q;
       scanf("%d%d",&n,&q);
       for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
       }
       for(int i=0;i<20;i++){
            preX[i][0] = 0;one[i][0] = 0;zero[i][0] = 1;
            for(int j=1;j<=n;j++){
                if(a[j] & (1<<i))
                    preX[i][j] = preX[i][j-1] ^ 1;
                else
                    preX[i][j] = preX[i][j-1];
                one[i][j] = one[i][j-1];zero[i][j] = zero[i][j-1];
                if(preX[i][j])
                    one[i][j] ++ ;
                else zero[i][j] ++;
            }
       }
       for(int i=0;i<20;i++){
            sum[i][0] = 0;
            for(int j=1;j<=n;j++){
                sum[i][j] = sum[i][j-1];
                if(preX[i][j])
                    sum[i][j] = (sum[i][j] + zero[i][j-1]*(1<<i))%mod;
                else
                    sum[i][j] = (sum[i][j] + one[i][j-1] *(1<<i))%mod;
            }
       }
       while(q--)
       {
           int L,R;
           scanf("%d%d",&L,&R);
           ll ans = 0;
           for(int i=0;i<20;i++){
                ans += sum[i][R] - sum[i][L-1];
                ans %= mod;
                if(L>=2){
                    ans -= (1<<i) * zero[i][L-2]*(one[i][R]-one[i][L-1])%mod;
                    ans -= (1<<i) * one[i][L-2] * (zero[i][R]-zero[i][L-1])%mod;
                    ans %= mod;
                }
           }
           printf("%lld\n",(ans+mod)%mod);
       }
   }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值