zoukankan      html  css  js  c++  java
  • P4932 浏览器(统计二进制1的个数)

    P4932 浏览器

    (n)个数,(x_1,x_2,cdots,x_n),问你有多少对((u,v)),使得(x_uoperatorname{xor}x_v)的二进制表示中有奇数个(1)

    输入六个整数,(n,a,b,c,d,x_0)
    每个点的权值需要用如下的方式生成。
    (x_i = (ax_{i-1}^2 + bx_{i-1} + c) mod d)

    (nle 10^7,max(x_i)le 10^9,a,b,c,d,x_0)(int)范围


    位运算的题,可以先考虑运算前后的数的某些量有何关系
    比如这题,可以发现,只有(x_u,x_v)的二进制中(1)的个数一奇一偶,(x_uoperatorname{xor}x_v)的二进制用才有奇数个(1)
    可以设(x_u,x_v)的二进制(1)(k)位重合,也就是说这(k)位上两个数都是(1)
    对于异或运算,只有某一位两数不同才是(1),它们有( ext{偶数}+ ext{奇数}-2k)个数位不同,其实就是分别(1)的位数减去共同都是(1)的那(k)
    那么即为( ext{偶数}+ ext{奇数}- ext{偶数}= ext{奇数})


    那么现在考虑如何快速地求一个数二进制中(1)的数量

    首先有一种很显然的方法

    inline int cnt1(int x){
    	reg int ret=0;
    	while(x) ret+=x&1,x>>=1;
    	return ret;
    }
    

    就是一位一位的数,复杂度(O(log x))
    如果此题用这个方法,(O(nlog x)),算下来是(3cdot 10^8),但是 1.5s 仍然跑不出最后两个点,可能是常数过大


    接下来有一种稍有优化的方法

    inline int cnt2(int x){
    	reg int ret=0;
    	while(x){
    		ret++;
    		x^=(x&(-x));
    	}
    	return ret;
    }
    

    每次结果加一,然后去掉最后一个二进制中的(1)
    复杂度和(x)二进制中数的个数有关,最坏也是(O(log x))
    对于如何去掉的最后一个(1),和计算机数的储存有关

    原码

    原码就是一个数的二进制表示,加上符号位,符号位是(0)代表整数,否则是负数
    但这样在表示负数时,每增加一个二进制位,数的值反而会减少,不能完成加法操作
    当然也可以算它的绝对值再取符号之类的,但是对于加法这样计算机中最基础的运算,会显得太麻烦

    反码

    正数的反码是其本身,负数的反码是除了符号位,每一位取反的结果
    这样就解决了正负数各自的加法的问题,说“各自”是因为不能跨过(0)
    因为(+0)表示为(0000),而(-0)表示为(1111),所以运算时,每跨过一次(0),都会使结果少一,自己举两个例子用反码表示试试就知道

    补码

    于是有了补码,正数的补码还是其本身,负数的补码是它的反码加一
    然后就完美的解决了加法的问题
    而且(1111)这一位就没有数了(我们以四位二进制数为例),所以就让这一位表示(-2^3)
    这也是为什么大部分数据类型表示的范围是([-2^n,2^n))

    扯完这些就能理解上面那种方法了,变成负数以后,相当于给每一位取了反,然后加一
    假设这个(x=cdots 100cdots),写出来的这个(1)就是最后一个,也就是要去掉的(1),那么取反以后变成(cdots011cdots)
    因为取反结果后面全是(1),加一,都进位,就变成了(cdots100cdots)
    那么和原数做与运算,就得出了那一位(1),用异或去掉就行

    这种方法已经能通过此题


    但还有一种更妙的方法

    inline int cnt3(reg int x){    
        x=(x&0x55555555)+((x>>1)&0x55555555);
        x=(x&0x33333333)+((x>>2)&0x33333333);
        x=(x&0x0f0f0f0f)+((x>>4)&0x0f0f0f0f);
        x=(x&0x00ff00ff)+((x>>8)&0x00ff00ff);
        x=(x&0x0000ffff)+((x>>16)&0x0000ffff);
        return x;   
    }
    inline int cnt4(reg LL x){    
        x=(x&0x5555555555555555ll)+((x>>1)&0x5555555555555555ll);
        x=(x&0x3333333333333333ll)+((x>>2)&0x3333333333333333ll);
        x=(x&0x0f0f0f0f0f0f0f0fll)+((x>>4)&0x0f0f0f0f0f0f0f0fll);
        x=(x&0x00ff00ff00ff00ffll)+((x>>8)&0x00ff00ff00ff00ffll);
        x=(x&0x0000ffff0000ffffll)+((x>>16)&0x0000ffff0000ffffll);
        x=(x&0x00000000ffffffffll)+((x>>32)&0x00000000ffffffffll);
        return x;
    }
    

    cnt3是处理(int)的,cnt4是处理(longspace long)
    可以看出,这种方法是(O(loglog x))
    其实还有一种看起来更接近(O(1)),但是用到取模运算,所以真正跑起来可能每这个快,也比这个更难理解

    以一个8为二进制数为例,( exttt{10111001}),其实更多位数也一样
    ( exttt{0x55})的二进制是( exttt{01010101})

    所以,和它与,就保留了(1,3,5,7)位上的(1),就是( exttt{00010001})
    如果把这个二进制数左移一位,再和它与,那么肯定是保留了(2,4,6,8)为上的(1),然后把它分别放到了(1,3,5,7)位上
    左移一位再与以后的结果:( exttt{01010100})
    和刚才那个( exttt{ 00010001 })加完以后的结果:( exttt{01 10 01 01})
    这里把它两位一断,就能很容易的发现,对于每两位来说,这两位的二进制数,就是这两位上(1)的个数

    然后继续观察,发现( exttt{0x33})的二进制是( exttt{0011 0011})
    那么( exttt{01 10 01 01}operatorname{and} exttt{00 11 00 11}= exttt{00 10 00 01})
    这个什么意思?当然是如果每两位分一段的话,保留(1,3)段中的(1)
    同样,左移两位再与,就是保留(2,4)段的(1)并放在(1,3)段上
    再加起来,结果就是( exttt{0011 0010})
    此时,把它四位一段,前四位的二进制数是表示前四位有多少(1),后四位也一样

    现在差不多就明白了,其实这个方法就是不断把相邻位的(1)的个数合并到一个更大的区间去,最后,就是整个(x)表示(x)(1)的个数
    返回(x)

    然而这个比上一种方法的总时间也就快了不到半秒

    放上完整代码

    #include<cstdio>
    #include<algorithm>
    #include<iostream>
    #include<cmath>
    #include<iomanip>
    #include<cstring>
    #define reg register
    #define EN std::puts("")
    #define LL long long
    inline int read(){
    	register int x=0;register int y=1;
    	register char c=std::getchar();
    	while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
    	while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
    	return y?x:-x;
    }
    int n;
    inline int cnt1(int x){
    	reg int ret=0;
    	while(x) ret+=x&1,x>>=1;
    	return ret;
    }
    inline int cnt2(int x){
    	reg int ret=0;
    	while(x){
    		ret++;
    		x^=(x&(-x));
    	}
    	return ret;
    }
    inline int cnt3(reg int x){    
        x=(x&0x55555555)+((x>>1)&0x55555555);
        x=(x&0x33333333)+((x>>2)&0x33333333);
        x=(x&0x0f0f0f0f)+((x>>4)&0x0f0f0f0f);
        x=(x&0x00ff00ff)+((x>>8)&0x00ff00ff);
        x=(x&0x0000ffff)+((x>>16)&0x0000ffff);
        return x;   
    }
    inline int cnt4(reg LL x){    
        x=(x&0x5555555555555555ll)+((x>>1)&0x5555555555555555ll);
        x=(x&0x3333333333333333ll)+((x>>2)&0x3333333333333333ll);
        x=(x&0x0f0f0f0f0f0f0f0fll)+((x>>4)&0x0f0f0f0f0f0f0f0fll);
        x=(x&0x00ff00ff00ff00ffll)+((x>>8)&0x00ff00ff00ff00ffll);
        x=(x&0x0000ffff0000ffffll)+((x>>16)&0x0000ffff0000ffffll);
        x=(x&0x00000000ffffffffll)+((x>>32)&0x00000000ffffffffll);
        return x;
    }
    int main(){
    	n=read();reg int a=read(),b=read(),c=read(),d=read(),x=read();
    	a%=d;b%=d;c%=d;x%=d;
    	reg int even=0,odd=0;
    	while(n--){
    		x=((1ll*a*x%d*x%d)+(1ll*b*x%d)+c)%d;
    		(cnt3(x)&1)?odd++:even++;
    	}
    	std::printf("%lld",1ll*odd*even);
    	return 0;
    }
    
  • 相关阅读:
    js对select动态添加和删除OPTION
    文本框textarea实时提示还可以输入多少文字
    JavaScript中统计Textarea字数并提示还能输入的字符
    inupt textarea提示文字(点击消失,不输入恢复)
    inupt textarea提示文字(点击消失,不输入恢复)及限制字数
    Server.MapPath()获取本机绝对路径
    cocos基础教程(12)点击交互的三种处理
    cocos基础教程(9)声音和音效
    cocos进阶教程(2)多分辨率支持策略和原理
    cocos基础教程(10)纹理缓存技术
  • 原文地址:https://www.cnblogs.com/suxxsfe/p/12651569.html
Copyright © 2011-2022 走看看