其实位运算很好理解,又比较好用,状态分明,易于想象,反正不是1就是0
还算轻松的开头
基础算术位运算
与 and &:两者都为1则为1,其他情况都为0
或 or |:两者都为0则为0,其他情况都为1
非 not ~:对于一个二进制数,每位取反,0变为1,1变为0
亦或 xor ^:两者相同则为0,不同则为1
补码
unsigned int:32位无符号整数 可以直接把32位编码看成32位二进制数
int:32位有符号整数 最高位表示正负,0为正,1为负
对于int型数,正数补码为它本身,负数补码为除符号位外所有位取反后再+1
所以对于一个int数x,全部位取反后得到的数为-1-x
已知补码求原码:
如果补码的符号位为0,表示是一个正数,其原码就是补码。
如果补码的符号位为1,表示是一个负数,那么求给定的这个补码的补码就是要求的原码。
反码
正数的原码、补码、反码都相同,都等于它本身
负数的反码是,符号位不变,其余各位求反
负数反码末位加上1就是补码
注意点:
对一个数取反是对这个数的补码取反
电脑保存的二进制数都是它的补码,在补码下每个数都有唯一表示方式
两个数值做加减法运算,实质上是它们的补码做最高位不进位的二进制加减法运算
表示
在写程序的时候,常用十六进制来表示一个数,共8个字符,每个字符代表二进制中4个位
用“0x”开头,声明进制
后面跟上代表四位二进制数的字符(0~9,A~F)
例如:3 -> 000……0011 -> 0x00000003(0011对应的十六进制为3)
虽然int型最大正整数应该是0x7FFFFFFF,但一般用0x3F3F3F3F来表示最大值
移位运算
左移
在二进制表示下所有位的数同时向左移动,低位0填充,高位如果超过范围就舍弃
易得:1<<n=2n n<<1=2*n
右移
在二进制表示下所有位的数同时向右移动,低位超过范围舍弃,高位以符号位填充
n>>1=n/2
等于除以2向下取整,但整数/2在c++中实现为向零取整
所以,正数右移1位和除以2相同,但负数就不一样。例:(-3)>>1==-2,(-3)/2=-1
二进制状态压缩
用二进制数表示状态,典型运用状态压缩dp
取出整数n在二进制表示下的第k位 (n>>k)&1
取出整数n在二进制表示下的第0~k-1位(即后k位) n&((1<<k)-1)
把整数n在二进制表示下的第k位取反 n^(1<<k)
把整数n在二进制表示下的第k位赋值为1 n|(1<<k)
把整数n在二进制表示下的第k位赋值为0 n&(~(1<<k))
lowbit运算
lowbit(x)指非负整数x在二进制表示下 最低位的1及后面所有的0构成的数
lowbit(x)=x&(~x+1)=x&(-x)
推导过程:
设x的最低位1在第k位,则第0~k-1位都为0
把x取反,则第k位为0,0~k-1位都为1,第k+1~最高位与之前相反
再+1后,由于进位,第0~k-1位再次变为0,第k位变为1
而+1操作对第k+1~最高位无影响,所以恰好与原数相比相反
这个时候把操作后的数与x进行 和运算,则从第k+1~最高位都为0,得到答案
即lowbit(x)=x&(~x+1)
又由补码表示下可得~x+1=-n,所以lowbit(x)=x&(-x)
找出整数二进制下所有是1的位
用lowbit运算+Hash
求lowbit(x)可以求出最低位为1的位置,再把x赋值为x-lowbit(x),可以求出下一个为1的位,反复直到x=0
求出的lowbit(x)为一个最高位为1,其余位都为0的数,这个数一定是2的n次方,而这个n就是它的最高位数,即原x中的1在第n位中(注:k位二进制数,这里最低位表示为第0位,最高位表示为第k-1位)
要确定n,用Hash存储,可以实现O(1)的询问
预处理Hash数组,把2n存储为n,即Hash( 2n )=n
举个例子:要求十进制数x=10的二进制下所有是1的位
x=10=(1010)2 -> lowbit(x)=(10)2=2 -> Hash( lowbit(x) )=Hash( 2 )=1 -> x=x-lowbit(x) 即x=8=(1000)2 -> lowbit(x)=lowbit(8)=(1000)2=8 -> Hash( lowbit(x) )=Hash( 8 )=3 -> x=x-lowbit(x)=0 ->停止
用Hash求出的标红的两个数1、3即为所求
程序实现
for(int i=0;i<=20;i++) Hash[1<<i]=i; while(x) { printf("%d ",Hash[x&(-x)]); x-=x&(-x); }
大致就是这些,位运算一般都是题目中的一部分辅助工具,不太会单独出现。
参考 李煜东的《算法竞赛进阶指南》