你必须知道的基本位运算技巧(状压DP、搜索优化都会用到)
一. 位操作基础
基本的位操作符有与、或、异或、取反、左移、右移这6种,它们的运算规则如下所示:
| 符号 | 描述 | 运算规则 | 
| & | 与 | 两个位都为1时,结果才为1 | 
| | | 或 | 两个位都为0时,结果才为0 | 
| ^ | 异或 | 两个位相同为0,相异为1 | 
| ~ | 取反 | 0变1,1变0 | 
| << | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0。1<<n等于2的n次方 | 
| >> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)。n>>2 等于 n/2的n次方 | 
注意:
1. 在这6种操作符,只有~取反是单目操作符,其它5种都是双目操作符。
2. 位操作只能用于整形数据,对float和double类型进行位操作会被编译器报错。
二. 常用位操作小技巧
简单的位运算
1.’&’符号,x&y,会将两个十进制数在二进制下进行与运算,然后
返回其十进制下的值。例如3(11)&2(10)=2(10)。
2.’|’符号,x|y,会将两个十进制数在二进制下进行或运算,然后
返回其十进制下的值。例如3(11)|2(10)=3(11)。
3.’^’符号,x^y,会将两个十进制数在二进制下进行异或运算,然
后返回其十进制下的值。例如3(11)^2(10)=1(01)。
4.’<<’符号,左移操作,x<<2,将x在二进制下的每一位向左移动两位,最右边用0填充,x<<2相当于让x乘以4。
相应的,’>>’是右移操作,x>>1相当于给x/2,去掉x二进制下的最有一位。
三.技巧总结
1. 判断一个整数是奇数还是偶数
if ((x & 1) == 0) {
  x is even
}
else {
  x is odd
}
我十分确信每个人都见过这个技巧。核心思路是如果整数的最低位 b0 是 1,那么这个数就是奇数。这是因为 ‘x’ 的二进制表示中,b0 位只是能是 1 或者 0。将 ‘x’ 和 1 进行与运算可以消除除了 b0 以外的其他位。如果这个操作的结果是 0,那么 ‘x’ 就是偶数,因为 b0 是 0
,否则 ‘x’ 是奇数。
我们来看一些例子。我们选择整数 43,它是一个奇数,二进制表示是 00101011,它的最低位 b0 是 1(重点)。现在将它和 1 相与:
    00101011
&   00000001   (注释: 1 的二进制是 00000001)
    --------
    00000001
看到与运算是如何消除高位 b1-b7 而留下 b0 的吗?结果是 1,这告诉我们整数是奇数。
现在我们看一下 -43。温馨提示一下,获取一个数的负数的二进制补码的快速方法是用该数的绝对值取反加 1(注意符号位,正数省略了前面的 0)。所以 -43 的二进制是 11010101,可以看到它的最低位仍是 1,并且这个数是奇数(敲黑板:如果我们不使用补码的话,就不是这样)。
我们再来看下 98,它的二进制码为 1100010。
    01100010
&   00000001
    --------
    00000000
与运算后的结果是 0,这表示 98 的 b0 位是 0,所以是偶数。
再试一下负数 -98,二进制码为 10011110,b0 位是 0,与运算后的结果是 0,说明 -98 是偶数。
2. 判断第 n 位是否是 1
if (x & (1<<n)) {
  n-th bit is set
}
else {
  n-th bit is not set
}
在上一个技巧中,我们通过 x&1 判断第一位是否是 1。这个技巧对此进行了改善,可以判断任意位是否是 1。它的原理是,将 1 左移 n 位,然后与给定的数进行与运算,这将会消除除了第 n 位之外的其他位。
下面是 1 进行左移的结果:
1         00000001    (same as 1<<0)
1<<1      00000010
1<<2      00000100
1<<3      00001000
1<<4      00010000
1<<5      00100000
1<<6      01000000
1<<7      10000000
如果我们将 ‘x’ 与左移 n 位的 1 进行与运算,就可以有效的排除 ‘x’ 中除了第 n 位之外的其他位。如果运算的结果是 0,说明第 n 位是 0,否则是 1。
我们来看一些具体例子。
122 的第三位是 1 吗?我们要执行的操作是:122 & (1<<3)。
122 的二进制码是 01111010,1<<3 的结果是 00001000。
    01111010
&   00001000
    --------
    00001000
可以看到结果不是 0,说明 122 的第三位是 1。
注意:在我的文章中,位从 0 开始计数。
那 -33 呢?它的第 5 位是 1 吗?
    11011111      (-33 in binary)
&   00100000     (1<<5)
    --------
    00000000
结果是 0,所以第 5 位不是 1。
3. 将第 n 位设为 1
y = x | (1<<n)
这个技巧同样使用了移位运算(1<<n)来将第 n 位置为1,只是将前面的 & 替换成了 |。将一个变量和第 n 位为 1 的数进行或运算的结果是该变量的第 n 位会被置为 1。这是因为任何数与 0 相或,结果是 0;与 1 相或结果位 1()。让我们来看看这是如何工作的:
假设我们有一个数是 120,并且想把它的第 2 位置为 1。
    01111000    (120 in binary)
|   00000100    (1<<2)
    --------
    01111100
如果将 -120 第 6 位置为 1 呢?
    10001000   (-120 in binary)
|   01000000   (1<<6)
    --------
    11001000
4. 将第 n 为设为 0
y = x & ~(1<<n)
这个技巧的核心是 ~(1<<n),它除了第 n 位是 0,其他位都是 1。
它看起来像这样:
~1        11111110  (same as ~(1<<0))
~(1<<1)   11111101
~(1<<2)   11111011
~(1<<3)   11110111
~(1<<4)   11101111
~(1<<5)   11011111
~(1<<6)   10111111
~(1<<7)   01111111
将其与变量 ‘x’ 相与的结果是 ‘x’ 的第 n 位会置 0。无论这个数的第 n 位是 0 还是1,和 0 相与的结果都是 0。
这是一个例子,让我们把 127 的第 4 位设为 0:
    01111111    (127 in binary)
&   11101111    (~(1<<4))
    --------
    01101111
5. 将第 n 位的值取反
y = x ^ (1<<n)
这个技巧仍然是“设置第 n 位的值”,但是这次是与变量 ‘x’ 进行异或运算。如果两个数的每一位都相同,异或的结果是 0,否者是 1。那它是如何使第 n 位取反的呢?如果第 n 位是 1,那么与 1 进行异或运算的结果是 0;相反地,如果第 n 位是 0,那么和 1 进行异或的结果是 1。看到没,位会被翻转。
下面这个例子将 01110101 的第 5 位取反:
    01110101
^   00100000
    --------
    01010101
如果其他位相同,但是第 5 位是 0 呢?
    01010101
^   00100000
    --------
    01110101
注意到了吗?执行两次异或运算后返回最迟的值。这个漂亮的异或运算用来计算 RAID 矩阵的奇偶性和简单的加密计算,更多内容见其他文章。
6. 将最右边的 1 设为 0
y = x & (x-1)
现在终于变得更加有趣!!!说实话前面 5 个技巧有点无聊。
这个技巧将将最右边的 1 设为 0。例如,给出一个整数 00101010(最右边的 1 被加粗),结果变为 00101000。或者将 00010000 变为 0,因为它只有一个 1。
下面是更多的例子:
    01010111    (x)
&   01010110    (x-1)
    --------
    01010110
    01011000    (x)
&   01010111    (x-1)
    --------
    01010000
    10000000    (x = -128)
&   01111111    (x-1 = 127 (with overflow))
    --------
    00000000
    11111111    (x = all bits 1)
&   11111110    (x-1)
    --------
    11111110
    00000000    (x = no rightmost 1-bits)
&   11111111    (x-1)
    --------
    00000000
为什么这样可以呢?
如果你仔细观察并思考上面的例子,可以发现有两种情况:
- 最右边存在值为 1 的位。在这种情况下,减一会使比该位低的位的值变为 1,而自身变为 0(所以如果你加一,可以得到原来的值)。这一步已经屏蔽了最右边的 1,接着将它与原来的值进行与运算,最右边的 1 就被设为了 0。
- 最右边不存在值为 1 的位(全都是 0)。这种情况下减一,值会溢出,所有的位被设位 1,将其和 0 相与,结果为 0。
7. 分离最右边的 1
y = x & (-x)
这个技巧找出最右边的 1 并将其他为设为 0。例如:01010100 会变为 00000100。
下面是一些例子:
    10111100  (x)
&   01000100  (-x)
    --------
    00000100
    01110000  (x)
&   10010000  (-x)
    --------
    00010000
    00000001  (x)
&   11111111  (-x)
    --------
    00000001
    10000000  (x = -128)
&   10000000  (-x = -128)
    --------
    10000000
    11111111  (x = all bits one)
&   00000001  (-x)
    --------
    00000001
    00000000  (x = all bits 0, no rightmost 1-bit)
&   00000000  (-x)
    --------
    00000000
这个技巧的利用了二进制补码。在二进制补码系统中,-x 等于 ~x+1。现在我们来检查两种可能的情况:
- 最右边存在值为 1 的位 b(i)。我们将所有的位分成两部分,b(i)右边位b(i-1),b(i-2),b(0),左边位 b(i+1),...,b(n)。现在我们来计算 -x:首先将 x 取反,则 b(i) 为 0 ,b(i-1),...,b(0) 为 1,并反转了 b(i+1),...,b(n)。接着执行加 1,则 b(i-1),b(i-2),...,b(0) 变为 0,b(i) 变为 1。最后,与原始的 x 相与,则 bi 左边全部为 0 (高位取反),bi右边也全为 0,只有 bi 为 1。
- 最右边不存在 1。值为 0 ,0 的二进制补码仍是 0,0&0=0,没有位会被设为 1。
8. 将最右边的 1 后面都设位 1
y = x | (x-1)
例如:01010000 变为 01011111。
这个技巧并不完善,因为 0 会变成 1,而 0 中没有 1。
    10111100  (x)
|   10111011  (x-1)
    --------
    10111111
    01110111  (x)
|   01110110  (x-1)
    --------
    01110111
    00000001  (x)
|   00000000  (x-1)
    --------
    00000001
    10000000  (x = -128)
|   01111111  (x-1 = 127)
    --------
    11111111
    11111111  (x = -1)
|   11111110  (x-1 = -2)
    --------
    11111111
    00000000  (x)
|   11111111  (x-1)
    --------
    11111111
- 不存在 1。这种情况下 x=0,并且x-1等于 -1。-1 的二进制补码为 11111111,将其与 0 相或得到的结果为 1。
- 存在 1。参考第 6 个技巧,x-1会将最右边的 1 设为 0,并将该位后面的位设位 1,这里和原来的值进行或运算,该位后面的位也会被设位 1,并且因为原本该位是 1 ,现在是 0,或运算后结果还是 1。
9. 分离最右边的 0
y = ~x & (x+1)
这个技巧和第 7 个是相反地,它找到最右边的 0,将其值设为 1 并将其他为设置 0。
    10111100  (x)
    --------
    01000011  (~x)
&   10111101  (x+1)
    --------
    00000001
    01110111  (x)
    --------
    10001000  (~x)
&   01111000  (x+1)
    --------
    00001000
    00000001  (x)
    --------
    11111110  (~x)
&   00000010  (x+1)
    --------
    00000010
    10000000  (x = -128)
    --------
    01111111  (~x)
&   10000001  (x+1)
    --------
    00000001
    11111111  (x = no rightmost 0-bit)
    --------
    00000000  (~x)
&   00000000  (x+1)
    --------
    00000000
    00000000  (x)
    --------
    11111111  (~x)
&   00000001  (x+1)
    --------
    00000001
证明:假设最右边存在 0。~x 将最右边的 0 设置 1,x+1 也一样。接着将 ~x 和 x+1 相与,比该位高的都会被设为 0 (因为取反了,高位变了,而x+1操作不会修改高位),x+1 将比该位低的都设位 0,然后将该位设为 1,所以相与后,比该为低的都会被设为 0。
10. 将最右边的 0 设为 1
y = x | (x+1)
例如:10100011 变成 10100111。
    10111100  (x)
|   10111101  (x+1)
    --------
    10111101
    01110111  (x)
|   01111000  (x+1)
    --------
    01111111
    00000001  (x)
|   00000010  (x+1)
    --------
    00000011
    10000000  (x = -128)
|   10000001  (x+1)
    --------
    10000001
    11111111  (x = no rightmost 0-bit)
|   00000000  (x+1)
    --------
    11111111
    00000000  (x)
|   00000001  (x+1)
    --------
    00000001
证明:将 x 和 x+1 相或不会丢失任何信息。x+1使最右边的 0 变成 1,最右边 0 右边的结果为 0。两者相或的结果为 max{x, x+1},而 x+1最右边的 0 被设为了 1。
你必须知道的基本位运算技巧(状压DP、搜索优化都会用到)的更多相关文章
- CodeForces165E 位运算 贪心 + 状压dp
		http://codeforces.com/problemset/problem/165/E 题意 两个整数 x 和 y 是 兼容的,如果它们的位运算 "AND" 结果等于 0,亦 ... 
- ACM位运算技巧
		ACM位运算技巧 位运算应用口位运算应用口诀位运算应用口诀 清零取反要用与,某位置一可用或 若要取反和交换,轻轻松松用异或 移位运算 要点 1 它们都是双目运算符,两个运算分量都是整形,结果也是整形. ... 
- ACM中的位运算技巧
		听说位运算挺好玩的,那这节总结一下ACM中可能用到的位运算技巧. XOR运算极为重要!!(过[LC136](只出现一次的数字 - 力扣(LeetCode)):数组中每个数字都出现两次,只有一个出现一次 ... 
- 状压DP详解(位运算)
		前言: 状压DP是一种非常暴力的做法(有一些可以排除某些状态的除外),例如dp[S][v]中,S可以代表已经访问过的顶点的集合,v可以代表当前所在的顶点为v.S代表的就是一种状态(二进制表示),比如 ... 
- 关于C/C++中的位运算技巧
		本篇文章讲述在学习CSAPP位运算LAB时的一些心得. 移位运算的小技巧 C/C++对于移位运算具有不同的策略,对于无符号数,左右移位为逻辑移位,也就是直接移位:对于有符号数,采用算术移位的方式,即左 ... 
- 趣题: 按二进制中1的个数枚举1~2^n (位运算技巧)
		; ; k <= n; k++){ << k)-,u = << n; s < u;){ ;i < n;i++) printf(-i)&); print ... 
- 状压dp之位运算
		## 一.知识 1.我们知道计算机中数据由二进制数存储,一个二进制数的一位就是计算机中数据的最小单位bit,我们有一种运算符可直接对二进制数进行位运算,所以它的速度很快. 2.C++中的位运算符有6种 ... 
- 【BZOJ】1072: [SCOI2007]排列perm(状压dp+特殊的技巧)
		http://www.lydsy.com/JudgeOnline/problem.php?id=1072 首先无限膜拜题解orz表示只会暴力orz 数据那么小我竟然想不到状压! orz 这种题可以取模 ... 
- 【BZOJ4676】Xor-Mul棋盘 拆位+状压DP
		[BZOJ4676]Xor-Mul棋盘 Description 一个n*m的棋盘,左上角为(1,1),右下角为(n,m).相邻的2点之间有连边(如下图中实线)特殊地,(1,i)与(n,i)也连有一条边 ... 
随机推荐
- POJ 3984:迷宫问题 bfs+递归输出路径
			迷宫问题 Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 11844 Accepted: 7094 Description ... 
- POJ 1845:Sumdiv 快速幂+逆元
			Sumdiv Time Limit: 1000MS Memory Limit: 30000K Total Submissions: 16466 Accepted: 4101 Descripti ... 
- 【pwnable.kr】 shellshock
			pwnable从入门到放弃,第五题. ssh shellshock@pwnable.kr -p2222 (pw:guest) 这题主要涉及了一个关于bash的CVE漏洞. 首先还是下载源代码审计一下, ... 
- 18 —— node 热部署工具 — supervisor /nodemon 。
			1,全局安装: npm install -g supervisor 2,使用: —————————————————————————————— nodemon 和 supervisor 流程一致. 
- matlab中自带的sobol的函数提供的sobol序列
			clc; clear all; close all; M=;% 维度,几个参数 nPop=; VarMin=[0.6, 0.10, 0.002, 0.02, 0.17, 0.0, 0.17, 0.0, ... 
- DBUtils模版CRUD
			准备:导包 1.创建c3p0-config.xml配置文件放在src下 <?xml version="1.0" encoding="UTF-8"?> ... 
- c#  数据库操作,多数据库操作、数据库操作异常报错等问题
			1.引入相关的命名空间 在C#中要操作数据库,一般情况需要引入两个命名空间,在三种连接模式中都要引入下面的命名空间: System.Data;描述与数据源连接的当前状态. // // 摘要: // 连 ... 
- 布局基础<kotlin>2,自定义控件(整理自网络)
			引导页 传送门 Android vector标签 PathData 画图 ViewPager 代码清单 activity_main.xml <?xml version="1.0&quo ... 
- Postgres psql: 致命错误:  角色 "postgres" 不存在
			问题再现 当前环境: postgresql: 11.5 windows 10 企业版LTSC 64位 当运行"C:\Program Files\PostgreSQL\11\scripts\r ... 
- matplotlib画图--Line Color
			1.线形 2.标记 3.颜色 
