你必须知道的基本位运算技巧(状压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)也连有一条边 ...
随机推荐
- UVA - 1213 Sum of Different Primes (不同素数之和)(dp)
题意:选择k个质数,使它们的和等于n,问有多少种方案. 分析:dp[i][j],选择j个质数,使它们的和等于i的方法数. #pragma comment(linker, "/STACK:10 ...
- UVA - 714 Copying Books (抄书)(二分+贪心)
题意:把一个包含m个正整数的序列划分成k个(1<=k<=m<=500)非空的连续子序列,使得每个正整数恰好属于一个序列(所有的序列不重叠,且每个正整数都要有所属序列).设第i个序列的 ...
- windows driver 分配内存
UNICODE_STRING str = {0}; wchar_t strInfo[] = {L"马上就是光棍节了"}; str.Buffer = (PWCHAR)ExAlloca ...
- Java算法练习—— Z 字形变换
题目链接 题目描述 将一个给定字符串根据给定的行数,以从上往下.从左到右进行 Z 字形排列. 比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下: L ...
- 2020牛客寒假算法基础集训营4 G音乐鉴赏
题目描述 作为“音乐鉴赏”课的任课老师,你的课程作为刷学分好课一直受到广泛欢迎.但这一学期,学校制定了新的标准,你的课的优秀率(分数超过90分的人数)被限制在10%以下! 为了应对这个调整,你要求所有 ...
- 详解contextConfigLocation|Spring启动过程详解(转)
原文链接:https://blog.csdn.net/qw222pzx/article/details/78191670 spring的应用初始化流程一直没有搞明白,刚刚又碰到了相关的问题.决定得好好 ...
- 吴裕雄--天生自然JAVA SPRING框架开发学习笔记:Spring IoC容器BeanFactory和ApplicationContext
IoC 是指在程序开发中,实例的创建不再由调用者管理,而是由 Spring 容器创建.Spring 容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,控制权由程序代码转移到了 Spring ...
- AT1983 BBQ Hard 解题报告
题意 求\(\sum_{i=1}^{n} \sum_{j=i+1}^{n} \dbinom{a_i+a_j}{a_i+b_i+a_j+b_j}\) 解法 考虑\(\dbinom{a_i+a_j}{a_ ...
- HTML与CSS结合的四种方式
HTML与CSS结合的四种方式: 方式一:每个标签加一个属性: 例如:<div style="background-color:red; color: green"> ...
- Q4:Median of Two Sorted Arrays
4. Median of Two Sorted Arrays 官方的链接:4. Median of Two Sorted Arrays Description : There are two sort ...