判断异或运算后二进制下1个数为奇数的个数

问题

时限:1.5s
空间:64M

给你 n ≤ 5 × 1 0 7 n\leq5\times10^7 n5×107个数字,每个数字 ≤ 1 0 9 \leq 10^9 109,问你有多少对数字异或起来的值在二进制下的 1 1 1的个数为奇数个。


  • 暴力

这个直接 O ( n 2 ) O(n^2) O(n2)枚举计算就可以了,但是是肯定不行的,所以我们要考虑其他方法。


  • 观察

对于两个数字,某一位异或为 1 1 1的话那么原来的两个数字的那一位必须不一样,如果异或为 0 0 0的话,原来的两个数字的两位肯定一样。

所以我们发现,它某一位从原来的 1 1 1变成 0 0 0的话,必须是同时都为 1 1 1,那么最后两个数字的 1 1 1减少的个数和肯定为偶数,所以我们只需要原来的 1 1 1的个数和为奇数,那么最后消除后剩下的 1 1 1的个数肯定还是奇数,而只有两个数的 1 1 1的个数分别为一奇一偶,加起来才为奇数个。

  • 栗子如下:

110010   x o r   010001 = 100011 110010\ \mathbf{xor}\ 010001=100011 110010 xor 010001=100011
原来有 3 + 2 = 5 3+2=5 3+2=5 1 1 1,而其中的顺数第二位,两个数字都为 1 1 1,所以消去了,剩下 3 3 3 1 1 1,那么就为奇数。

11001   x o r   00111 = 11110 11001\ \mathbf{xor}\ 00111=11110 11001 xor 00111=11110
原来有 3 + 3 = 6 3+3=6 3+3=6 1 1 1,而顺数最后一位都为 1 1 1,消去,剩下 4 4 4 1 1 1,所以不为奇数。
所以只有原来的 1 1 1的个数为奇数的,最后才能成为答案。

所以最后统计一下奇数个 1 1 1的个数,那么答案就是奇数的和偶数的组合,令奇数个 1 1 1的个数为 t t t个,那么答案就为 t × ( n − t ) t\times(n-t) t×(nt)


  • 分析

如果直接统计的话,复杂度为 O ( n l o g v ) O(nlogv) O(nlogv)的,而 l o g v logv logv可以达到 30 30 30,那么复杂度最大为三亿,显然 1.5 s 1.5s 1.5s是跑不过的,所以考虑优化:

  1. 我们按照位进行分治统计,复杂度降为 O ( n l o g l o g v ) O(nloglogv) O(nloglogv).
  2. 我们预处理 15 15 15位的所有数的二进制位的 1 1 1的个数,那么一个数字可以看做两个 15 15 15位的数字拼接而成,所以拆开计算和即可,复杂度降为 O ( 2 15 + n ) O(2^{15}+n) O(215+n)
  • 小技巧

对于统计 2 15 2^{15} 215内的数的二进制位的个数,我们不用 15 × 2 15 15\times 2^{15} 15×215枚举统计,我们可以用类似 D P + l o w b i t DP+lowbit DP+lowbit的方式统计( l o w b i t ( i ) = i & ( − i ) lowbit(i)=i\&(-i) lowbit(i)=i&(i)为找到二进制最低位的 1 1 1),转移如下:
b i t c n t [ i ] = b i t c n t [ i   x o r   l o w b i t ( i ) ] + 1 bitcnt[i]=bitcnt[i\ \mathbf{xor}\ lowbit(i)]+1 bitcnt[i]=bitcnt[i xor lowbit(i)]+1

我们发现它的实质是每次消除最低位的 1 1 1,所以贡献 + 1 +1 +1即可。
那么复杂度即为 O ( 2 15 ) O(2^{15}) O(215)


原题题目地址【IN-LuoGu

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define RG register
#define ll long long
#define lowbit(x) ((x)&(-x))
using namespace std;
const int S=1<<15|1,s=(1<<15)-1;
ll n,a,b,c,d,x;
ll t1,bit[S];
void init(){
    for(RG int i=1;i<S;i++){bit[i]=bit[i^lowbit(i)]+1;}
}
void calc(int a){
    int a1=a&s,a2=a>>15;
    int cnt1=bit[a1]+bit[a2];
    if(cnt1&1)++t1;
}
int main(){
    init();
    scanf("%lld%lld%lld%lld%lld%lld",&n,&a,&b,&c,&d,&x);
    for(RG int i=1;i<=n;i++){x=(a*x%d*x%d+b*x%d+c)%d;calc(x);}
    printf("%lld\n",1ll*(n-t1)*t1);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

VictoryCzt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值