位运算
整数类型变量(正数),系统会先将数字转换为二进制形式再运算
计算机存储数字的补码,正数的补码就是原码,负数补码是符号位不变,原码每一位取反加一
常用位运算为左移(<<)
右移(>>)
:
x<<k
表示为x转换为二进制后,左移k位,末尾补k个0
x>>k
表示x转换为二进制后,右移k位,前面补k个0
不越界的情况下,x<<k
等价于\(x*2^k\) ,x>>k
等价于\(x/2^k\) 。
位运算技巧:
集合枚举:
位运算还经常被用于状态压缩时的集合枚举。
比如现在有n件物品,编号1到n,用n位二进制数表示每个物品取或不取状态,二进制从右往左第k位的01状态表示编号位k的物品取了还是没取(1 取,0 未取)
集合的子集和超集
在枚举集合进行运算过程中,经常遇到枚举当前集合的子集或超集的情况
- 子集:状态用二进制表示时为0的位数必须为0,为1的位数可以是0也可以是1
- 超集:状态用二进制表示时为0的位数可以是0也可以是1,为1的位数必须为0
例:
假设\(n=5\),当前状态\(x=(11001)_2\)
所有子集的二进制表示为: \(11001,11000,10001,10000,01001,01000,00001,00000\)
超集二进制表示:\(11001,11011,11101,11111\)
枚举所有状态的非空子集
:
for(int i=1; i< 1<<n)
for(int j=i; j; j=(j-1)&i)
{
//j便利了i的所有非空子集
}
时间复杂度\(O(3^n)\)
枚举超集:
for(int i=0; i< 1<<n; i++)
for(int j=i; ; j = (j+1) | i){
//j遍历了i所有超集
if(j==(1<<n)-1)
break;
}
时间复杂度\(3^n\)
二进制中含1的个数
有时需要根据一个数的二进制中含有1的个数来进行集合枚举。例如含有3个1的5个bit的正数(不含符号位)表示如下: \(00111,01011,01101,01110,10011,10101,10110,11001,11010,11100\)
如何求出01序列的下一个排列,代码如下:
int x,y;//x为当前的数,y为要求的下一个数
int t=(x|(x-1))+1;//最后一段连续的1变成0,并将前一位变成1
y=t|((((t& -t) / (x & -x))>> 1)-1); //补上少了的1的个数
例题
分析:`^`有一个性质两个相同的数`^`互相抵消,这时将所有数异或所得的到temp的一定为非0(因为两个不一样的数),此时temp找到最高位不为0的位置c,将数组按照位置c为0/1分为两组,分别异或,位置c为0的那一组异或出的一定是较小的,为1的异或出的一定是较大的。 因为按照01分类,由于只有两个不同所以位置c分组两个不同的一定被分到不同组,但是相同组里只有一个只出现一次。将相同组的数字异或得到的一定是所求结果
```cpp
#include
#include
#include
#include
#include
#include
#include
#include