位运算是不少算法优化的基础和实现的条件,极其重要。理解位运算对于一些算法及其优化有着很是重要的意义。本篇随笔讲解位运算的一些基本原理和经常使用的使用技巧。算法
注:本篇随笔的全部“运算”均指二进制下的运算,请你们自行理解。数组
两个二进制数进行与&运算,若是对应位都为1则结果为1,不然为0.优化
与运算经常用于二进制下的取位操做。想要知道二进制下的某位是不是1,就&上这个位数对应的十进制数。假如返回的是这个十进制数自己,则这个位的确是1,反之就是0.spa
好比:code
咱们要取第三位是否为1,咱们只须要与&上第三位(二进制表示为100)对应的二进制数4,若是返回值为4,就表明第三位为1,反之就是0.数学
最经常使用的是取二进制下的最末位,即a&1。这样的技巧能够用于判断奇偶,根据二进制常识,尾数为1则为奇数,反之为偶数。it
两个二进制数进行或|运算,若是对应位有一个为1,结果就为1.只有在两个数的对应位置都是0的时候,结果才为0.class
或运算经常使用于二进制特定位的赋值。想把哪一个位强行变成1,就用这个数|上这个位数对应的二进制数。效率
仍是上面那个例子,咱们想让00000的第三位变成1.即十进制变4,咱们直接|上4就能够。基础
固然,不一样于&运算,咱们不多用|运算进行任意位赋值。一般来说,咱们只使用a|1把a的最后一位强行变成1,其实质意义是把原数加一。或者使用a|1-1再把它变为0.这个技巧一般用于把它变成它最接近的偶数。
两个二进制数进行异或(^)运算,若是对应位相同,不论是0或者是1,都返回1,反之返回0.
其实没啥用途...
好吧,我介绍一个性质:一个数通过两次异或以后等于原数。
(很好理解)
把给定二进制数所有取反。
其实没什么运算上的用途,本蒟蒻曾看见一些大佬用这个运算判断输入是否为0...
大约长这个样子:
while(~scanf("%d",&n))
a<<b表示把a的二进制位向左移动b位,低位用0补上。
根据二进制的常识,咱们会发现,二进制第k位上的数就等于\(2^k\)。(从0开始计位)
好比,二进制下的100就是\(2^{k=2}=4\)。
因此咱们发现,左移运算a<<b的实质就是\(a×2^b\)。
左移运算最经常使用的技巧就是用来代替×2的整数次幂的乘法运算。由于咱们广泛认为,位运算是要比四则运算加减乘除及模运算更快一些的运算。
a>>b就是把a的二进制位向右移动b位,溢出的舍去。
类比于左移运算,咱们发现右移运算就是把a除以2的整数次幂。这就是右移运算的用途——优化除法运算。
这里须要特殊说明的是,右移算法能够用在数学知识中的求最大公约数的程序块上。由于mod运算的效率慢的出奇,因此咱们能够用右移运算来进行除以2的操做。听说能够提升百分之60的效率。
位运算的优先级是咱们在处理位运算的时候经常要考虑的问题,诚然,咱们能够用括号强制位运算的顺序,可是,咱们仍是应该学会位运算的优先级(这应该是常识)。
位运算的优先级以下:
按位反(~)>位移运算(<<,>>)>按位与(&)>按位异或(^)>按位或(|)
众所周知,状压DP就是把状态压缩成一个01串(其实就是一个二进制数),用以减小DP数组的维数。可是咱们在DP的时候就要按照01串来进行状态的转移。因此位运算是状压DP的基础知识和必备知识。因此我在本篇随笔的末尾还附上了状压DP中比较经常使用的操做及其二进制实现的方式。
正文:(本文中的a表示十进制下的整数)
一、得到第i位的数字:(a>>i)&1 或者 a&(1<<i)
很好理解,咱们知道能够用&1来提取最后一位的数,那么咱们如今要提取第i位数,就直接把第i位数变成最后一位便可(直接右移)。或者,咱们能够直接&上1左移i位,也能达到咱们的目的。
二、设置第i位为1:a=a|(1<<i)
咱们知道强制赋值用|运算,因此就直接强制|上第i位便可。
三、设置第i位为0:a=a&(~(1<<i))
这里比较难以理解。其实很简单,咱们知道非~运算是按位取反,(1<<i)非一下就变成了第i为是0,其它全是1的二进制串。这样再一与原数进行&运算,原数的第i位不管是什么都会变成0,而其余位不会改变(实在不明白的能够用纸笔进行推演)。
四、把第i位取反:a=a^(1<<i)
1左移i位以后再进行异或,咱们就会发现,若是原数第i位是0,一异或就变成1,不然变成0。
五、取出一个数的最后一个1:a&(-a)
学过树状数组的同窗会发现,这就是树状数组的lowbit。事实上,这和树状数组的原理是同样的。我想,不须要我多解释。