位运算与bitset
&运算
将两个数转化为二进制后,对应的位置上相同即取,通常取1,所以&通常情况下可以用来枚举子集
设x为表示集合的整数,那么这个整数有如下性质:
x的子集整数y在数值上不会比x大。因为x的子集y只是保留了x某些位置上的1,所以y总可以加上一个非负的整数z等于x,相当于把没选的1补上。
根据这个性质可知,可以通过枚举所有比x小的数p并判断,p是否只含x对应位上的1,如果是则p是x的子集,否则不是。这样时间复杂度是严格的x。有没有更快的呢,有的。
上诉枚举p是通过减一操作,并且我们知道减一操作一定是正确的,那么在枚举的时候如何快速的去掉多余的状态,答案就是和x进行&(与)运算。与运算可以快速跳到下一个子
集。
&运算本质就是保留p在x对应位为1的数值,而根据二进制减法可知减一操作都是把p最低位的1消去,在那一位后全补上1,如果在x对应位为0的地方产生了1其实是无效的,
后续的减一操作也会把它消掉,所以直接&运算可以快速去掉多余的状态。时间复杂度是x的子集数。
for(int i=x;i;) i=(i-1)&x;
快速判断一个数是不是2的整数幂
bool fun(int n) { return (!(n&(n-1))) && n; }
改位
#define set_bit(x,ith,bool) ((bool)?((x)|(1<<(ith))):((x)&(~(1<<ith)))) //设置x的从第ith位开始连续k位位bol int mset(int x,int ith,int k,int bol) { while(k--) x=set_bit(x,ith+k,bol); return x; }
|运算
两个相应的二进制位中只要有一个为1,该位的结果值为1。所以通常用来求并集
^运算
若参加运算的两个二进制位值相同则为0,否则为1,常用来取反
交换a,b;
void swap(int &a,int &b) { a^=b,b^=a,a^=b; }
~运算
一元运算符,用于求整数的二进制反码,即分别将操作数各二进制位上的1变为0,0变为1。
<<运算
左移运算符是用来将一个数的各二进制位左移若干位,移动的位数由右操作数指定(右操作数必须是非负 值),其右边空出的位用0填补,高位左移溢出则舍弃该高位。
>>运算
右移运算符是用来将一个数的各二进制位右移若干位,移动的位数由右操作数指定(右操作数必须是非负值),移到右端的低位被舍弃,对于无符号数,高位补0。对于有符号数某些机器将对左边空出的部分用符号位填补(即“算术移位”),而另一些机器则对左边空出的部分用0填补(即“逻辑移位”)。
bitset的用法
bitset 是STL库中的二进制容器,根据C++ reference 的说法,bitset可以看作bool数组,但优化了空间复杂度和时间复杂度,并且可以像整形一样按位与或。
bitset申明长度
bitset<length>b;
赋值
bitset重载了[]运算符,可以像数组一样使用
b[0]=1;
bit的常用函数
处理的数组只有0和1的变化,此时就可以使用bitset优化。比如求两个集合的交集可以使用按位与运算,求并集可以使用按位或运算
b.any() //b中是否存在置为1的二进制位? b.none()// b中不存在置为1的二进制位吗? b.count()//b中置为1的二进制位的个数 b.size() //b中二进制位数的个数 b[pos] //访问b中在pos处二进制位 b.test(pos)// b中在pos处的二进制位置为1么? b.set() //把b中所有二进制位都置为1 b.set(pos) //把b中在pos处的二进制位置为1 b.reset() //把b中所有二进制位都置为0 b.reset(pos) //把b中在pos处的二进制位置置为0 b.flip() //把b中所有二进制位逐位取反 b.flip(pos) //把b中在pos处的二进制位取反 b.to_ulong() //把b中同样的二进制位返回一个unsigned