位运算
程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。
位运算的用处很多,也很强大。
首先再学习位运算之前,我们先复习以下,编码知识
在计算机中,正数是直接用原码表示的,如5,在计算机中就表示为:0000 0101。
负数用补码表示,如-5,在计算机中表示为1111 1011。
原码 反码 补码问题
原码:将一个整数,转换成二进制,就是其原码。
如:3的二进制是 0000 0011 原码就是0000 0011 -3的原码是 1000 0011
反码:正数的反码就是其原码;负数的反码是将原码中,除符号位以外,每一位取反。
如 3的反码是 0000 0011 -3的反码是 1111 1100
补码:正数的补码就是其原码;负数的反码+1就是补码。
如 3的补码是 0000 0011 -3的补码是1111 1101
位运算:
& 按位与操作
相同位的两个数字都为1,则为1;若有一个不为1,则为0。
3&5 3: 0000 0011
5: 0000 0101
&————————
3&5 0000 0001
| 按位或操作
相同位只要一个为1即为1。
3|5 3: 0000 0011
5: 0000 0101
|————————
3|5: 0000 0111
^按位异或操作
规则:两个数对应位相同 运算结果为0 对应位不同 运算结果为1
3^5 3: 0000 0011
5: 0000 0101
^————————
3^5 0000 0110
性质:异或运算满足结合律和交换律
满足消去律 a^b=b^c 则a=c
a^a=0 (a^b)^b=0
所以 交换两个数的方法可以这么写:
void swap(int a,int b)
{
a=a+b;
b=a-b;
a=a-b;
}
void swap(int a,int b)
{
a=a^b;
b=a^b;
a=a^b;
}
~按位取反
~是一个单目运算符 作用是对二进制每一位数取反
3 0000 0011
~3 1111 1100
*例如 -1在计算机里的补码是 1111 1111
所以 ~(-1)=0000 0000 =0
所以ACM中经常看到 while(~scanf("%d",&a)) 代表 scanf返回-1(即EOF)时结束
<<左移运算符 >>右移运算符
<<左移 按照指定的位数将一个数的二进制数值向左移位 左移后 低位补0 移除的高位舍弃
>>右移 按照指定的位数将一个数的二进制数值向右移位 移除的低位舍弃 如果是无符号数则高位补0 若有符号 则高位补符号或者补0(不同处理器不一样)
如 a=-8 内存中补码为1111 1000
则a>>2=-2 右移两位后为 1111 1110 为-2
状态压缩
这是位运算的一个重要应用
比如 一个迷宫 1代表墙 0代表可以走 通常我们用二维数组存迷宫 做BFS或DFS
那么 迷宫不大的时候 我们可以用二进制数来表示这个迷宫
我们可以把n*m的迷宫 每一行表示为一个m位的二进制数 那么n个整数 就可以表示这个迷宫了
同样的 如果n*m不大的话 我们也可以把整个迷宫 表示为一个n*m位的二进制数
而迷宫的各种操作 我们可以借助位运算来完成 如下表
(切记 位运算的优先级很低! 使用的时候 一定记得带上括号)
去掉最后一位 | (101101->10110) | x >> 1 |
在最后加一个0 | (101101->1011010) | x << 1 |
在最后加一个1 | (101101->1011011) | (x << 1)+1 |
把最后一位变成1 | (101100->101101) | x | 1 |
把最后一位变成0 | (101101->101100) | (x | 1)-1 |
最后一位取反 | (101101->101100) | x ^ 1 |
把右数第k位变成1 | (101001->101101,k=3) | x | (1 << (k-1)) |
把右数第k位变成0 | (101101->101001,k=3) | x & ~(1 << (k-1)) |
右数第k位取反 | (101001->101101,k=3) | x ^ (1 << (k-1)) |
取末k位 | (1101101->1101,k=5) | x & (1 << (k-1)) |
取右数第k位 | (1101101->1,k=4) | (x >> (k-1)) & 1 |
把末k位变成1 | (101001->101111,k=4) | x ^ (1 << (k-1)) |
末k位取反 | (101001->100110,k=4) | x^ (1 << (k-1)) |
把右边连续的1变成0 | (100101111->100100000) | x & (x+1) |
把右起第一个0变成1 | (100101111->100111111) | x | (x+1) |
把右边连续的0变成1 | (11011000->11011111) | x | (x-1) |
取右边连续的1 | (100101111->1111) | (x ^ (x+1)) >> 1 |