zoukankan      html  css  js  c++  java
  • Bits

    先%SY...

    课件链接

    求1的个数

    以32位整数为例子,最暴力的方法就是一位一位的数,但是这样太不优美...

    以下是优美的方法...

    这个问题其实就是二进制求和...

    我们考虑分治的思想...每一次都把当前数字分成两部分,把通过二进制移位来实现两部分数字的相加...这样就可以达到$O(log_{2}N)$的复杂度...

    No.1

    以2bit为一段,取出所有奇数位,右移一位,和偶数位相加...

    No.2

    以4bit为一段,取出所有第奇数位,右移两位,和第偶数位相加...

    ......

    inline int pro1(u32 x){
    	x=((x&0xAAAAAAAAu)>>1 )+(x&0x55555555u);
    	x=((x&0xCCCCCCCCu)>>2 )+(x&0x33333333u);
    	x=((x&0xF0F0F0F0u)>>4 )+(x&0x0F0F0F0Fu);
    	x=((x&0xFF00FF00u)>>8 )+(x&0x00FF00FFu);
    	x=((x&0xFFFF0000u)>>16)+(x&0x0000FFFFu);
    	return x;
    }
    

     但是还是可以优化...

    inline int pro2(u32 x){
    	x-=((x&0xAAAAAAAAu)>>1);
    	x=((x&0xCCCCCCCCu)>>2)+(x&0x33333333u);
    	x=((x>>4)+x)&0x0F0F0F0Fu;
    	x=((x>>8)+x)&0x00FF00FFu;
    	x=((x>>16)+x)&0x0000FFFFu;
    	return x;
    }
    

    首先看第一行:

    x=(x&0xAAAAAAAAu)+(x&0x55555555u);

    y=((x&0xAAAAAAAAu)>>1)+(x&0x55555555u);

    x-y=(x&0xAAAAAAAAu)>>1;

    y=x-(x&0xAAAAAAAAu)>>1;

    然后这样奇数偶数位已经对齐,第二行不变...

    第三行:直接将整个数取出,右移相加,然后只区有用的那些位置...

    但是大神们还是觉得这样不够优美,所以接着优化...

    inline int pro3(u32 x){
    	x-=((x&0xAAAAAAAAu)>>1);
    	x=((x&0xCCCCCCCCu)>>2)+(x&0x33333333u);
    	x=((x>>4)+x)&0x0F0F0F0Fu;
    	x=(x*0x01010101u)>>24;
    	return x;
    }
    

    最后一行是毛线??

    我们先来看一看0x01010101是个什么东西...

    转化成二进制之后这个数的第1位第8位第16位和第24位是1,其他位都是0...

    我们看x在经历了前三次计算之后是什么样子的:x=(a<<24)+(b<<16)+(c<<8)+(d<<0)...a+b+c+d就是我们想要的结果...

    那么好了,x*0x01010101之后是什么?

    x=

    ((a)<<48)+
    ((a+b)<<40)+
    ((a+b+c)<<32)+
    ((a+b+c+d)<<24)+
    ((b+c+d)<<16)+
    ((c+d)<<8)+
    ((d)<<0)

    前三个直接溢出...后三个>>24之后变成0...所以x>>24=a+b+c+d...

    这样我们就可以快速求出单个数的1的个数...

    如果有很多询问呢?我们是不是可以打表处理呢?

    对于预处理操作最直观的想法是递推...f[0]=0,f[i]=f[i>>1]+i&1...

    但是貌似表有点大...

    所以考虑分段的思想...预处理出所有16位整数的答案,然后每一次查询拆成前16位和后16位的答案和...单次查询复杂度稳稳的$O(2)$...

    inline void prework(void){
    	f[0]=0;
    	for(int i=1;i<(1<<16);i++)
    		f[i]=f[i>>1]+(i&1);
    }
    
    inline int pro4(u32 x){
    	int lala=x>>16;
    	return f[lala]+f[x^((lala<<16))];
    }
    

     求1的个数的奇偶性

    最简单直接的方法就是计算出1的个数然后判断奇偶性...但是太不优美了...

    还是考虑分治的思想...每一次把数字分成两部分,这两部分^一下不改变奇偶性,所以我们就不断缩小位数直到只有1位...

    inline int pro(u32 x){
    	x^=x>>16;
    	x^=x>>8;
    	x^=x>>4;
    	x^=x>>2;
    	x^=x>>1;
    	return x&1;
    }
    

    对于很多次查询的问题我们还是可以打表预处理...

    inline void prework(void){
    	f[0]=0;
    	for(int i=1;i<(1<<16);i++)
    		f[i]=f[i>>1]^(i&1);
    }
    
    inline int pro2(u32 x){
    	int lala=x>>16;
    	return f[lala]^f[x^(lala<<16)];
    }
    

     其实以上两个问题GCC都有内建函数...但是比赛请慎重...

    翻转位序

    还是以32位整数为例...

    翻转位序就是第0位与第31位交换,第1位与第30位交 换,……第i位与第31-i位交换,……第15位与第16位交换...

    还是分治的思想...

    将数分为两个部分,两个部分分别翻转,然后交换位置...

    inline u32 pro1(u32 x){
    	x=((x&0xAAAAAAAAu)>>1 )|((x&0x55555555u)<<1 );
    	x=((x&0xCCCCCCCCu)>>2 )|((x&0x33333333u)<<2 );
    	x=((x&0xF0F0F0F0u)>>4 )|((x&0x0F0F0F0Fu)<<4 );
    	x=((x&0xFF00FF00u)>>8 )|((x&0x00FF00FFu)<<8 );
    	x=((x&0xFFFF0000u)>>16)|((x&0x0000FFFFu)<<16);
    	return x;
    }
    

    对于多次询问我们依旧采用查表的方法...把数拆成两个部分,预处理所有16位整数的答案,然后计算...

    inline void prework(void){
    	f[0]=0;
    	for(int i=1;i<(1<<16);i++)
    		f[i]=(f[i>>1]>>1)|((i&1)<<15);
    }
    
    inline u32 pro2(u32 x){
    	int lala=x>>16;
    	return (f[x^(lala<<16)]<<16)|f[lala];
    }
    

    求前缀0/后缀0的个数

    二分查找...

    inline int LeadingZeroCount(u32 x){
    	int ans=0;
    	if(x>>16) x>>=16; else ans|=16;
    	if(x>>8 ) x>>=8 ; else ans|=8 ;
    	if(x>>4 ) x>>=4 ; else ans|=4 ;
    	if(x>>2 ) x>>=2 ; else ans|=2 ;
    	if(x>>1 ) x>>=1 ; else ans|=1 ;
    	ans+=!x;
    	return ans;
    }
    
    inline int TrailingZeroCount(u32 x){
    	int ans=0;
    	if(!(x&((1<<16)-1))) x>>=16,ans|=16;
    	if(!(x&((1<<8 )-1))) x>>=8 ,ans|=8 ;
    	if(!(x&((1<<4 )-1))) x>>=4 ,ans|=4 ;
    	if(!(x&((1<<2 )-1))) x>>=2 ,ans|=2 ;
    	if(!(x&((1<<1 )-1))) x>>=1 ,ans|=1 ;
    	ans+=!x;
    	return ans;
    }
    

    然后此题依旧可以查表解决...判断第一个1出现在前一段还是后一段,然后查询...

    (我打赌这绝对是我起过的最长的函数名QAQ...)

    inline void LeadingZeroCountPrework(void){
    	fl[0]=16;
    	for(int i=1;i<(1<<16);i++)
    		fl[i]=fl[i>>1]-1;
    }
    
    inline void TrailingZeroCountPrework(void){
    	ft[0]=16;
    	for(int i=1;i<(1<<16);i++){
    		if(i&1)
    			ft[i]=0;
    		else
    			ft[i]=ft[i>>1]+1;
    	}
    }
    
    inline int LeadingZeroCount2(u32 x){
    	int lala=x>>16;
    	if(lala)
    		return fl[lala];
    	return fl[x^(lala<<16)]+16;
    }
    
    inline int TrailingZeroCount2(u32 x){
    	int lala=x>>16;
    	if((x^(lala<<16))>0)
    		return ft[x^(lala<<16)];
    	return ft[lala]+16;
    }
    

    依旧有内建函数...但是要慎重...

    求第k个1的位置

    依旧是二分的思想...

    求第k个1的位置可以转化为求最大的pos使得第0位到第pos-1位中1的个数等于k-1...

    我们之前已经有了快速求解一个数中1的个数的方法,可以直接拿来用...

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    //by NeighThorn
    #define u32 unsigned int
    using namespace std;
    
    const int maxn=(1<<16)+5;
    
    int f[maxn];
    
    inline void prework(void){
    	f[0]=0;
    	for(int i=1;i<(1<<16);i++)
    		f[i]=f[i>>1]+(i&1);
    }
    
    inline int pro(u32 x,int k){
    	int ans=0;
    	if(f[x&65535u]<k)
    		k-=f[x&65535u],ans|=16,x>>=16;
    	if(f[x&255u  ]<k)
    		k-=f[x&255u  ],ans|=8 ,x>>=8 ;
    	if(f[x&15u   ]<k)
    		k-=f[x&15u   ],ans|=4 ,x>>=4 ;
    	if(f[x&3u    ]<k)
    		k-=f[x&3u    ],ans|=2 ,x>>=2 ;
    	if(f[x&1u    ]<k)
    		k-=f[x&1u    ],ans|=1 ,x>>=1 ;
    	return ans;
    }
    
    signed main(void){
    	u32 x=12819;prework();
    	cout<<pro(x,4)<<endl;
    	return 0;
    }//Cap ou pas cap. Cap.
    

    提取末尾连续的1

    因为1是连续的,所以我们把x+1之后,右边连续的1会变成连续的0,最右边的0会变成1,其他位不变...

    inline u32 pro(u32 x){
    	return x&(x^(x+1));
    }
    

    集合:

    集合的表示

    把一个集合映射到[0,n-1],用二进制整数表示集合中元素的存在情况,0代表不存在,1代表存在,因为二进制位宽最大为w,所以表示一个集合要被分成$n⁄w$块,每一块是一个w位的二进制整数...第i块表示第i*w~w*(i+1)-1个数...

    交集:&,并集:|,补集:!,差集:x-y的差集,${a|a∈x&&a∉y}$,x^(x&y) ...

    统计元素的个数

    运用之前的求一个数1的个数对每一块分别统计相加...

    遍历集合元素

    遍历每一块的所有1...不断lowbit并且删去lowbit...

    求集合中第k小的元素

    首先找出第k小元素所在的块,然后二分查找具体位置...

    求集合中大于x的最小元素(upper_bound)

    从x所在的块开始向后遍历,找到第一个存在元素的块,用求后缀0的方式确定具体位置...

    未完待续...

  • 相关阅读:
    sha256 in C language
    制作带动画效果的状态栏
    带进度条的任务栏
    在状态栏中显示当前系统时间
    在状态栏中显示当前操作员
    在状态栏中显示复选框
    设计浮动工具栏
    可以拉伸的菜单
    任务栏托盘菜单
    带历史信息的菜单
  • 原文地址:https://www.cnblogs.com/neighthorn/p/6337989.html
Copyright © 2011-2022 走看看