人造奇迹——二进制位运算的运用

最后更新:2014年4月30日

1、位运算包括:

这个我觉得大家都会我就随便说下:

位与&,如 101 & 110 = 100

位或|,如 100 | 110 = 110

位非~,如 ~101 = 010

位异或^,如 101 ^ 110 = 011

左移<<,如 011 << 1 = 110

右移>>,如 110 >> 1 = 011

其中,负数位运算的时候,用的是补码而不是原码请注意。

左移的时候,高位溢出的将会被舍弃,低位补0。如 11111111 << 3 = 11111000

右移的时候,低位溢出的将会被舍弃,高位补符号位。如 10000001 >> 1 = 11000000

2、gcc内置函数:

gcc中有为处理二进制而诞生的内置函数,如下:

int __builtin_ffs (unsigned int x)
返回从右往左数第一个1。其中x = 0返回0。
int
__builtin_clz (unsigned int x)
返回前导0的个数。
int __builtin_ctz (unsigned int
x)
返回末尾0的个数。
int __builtin_popcount (unsigned int x)
返回1的个数。
int
__builtin_parity (unsigned int x)
返回1的个数的奇偶性。

3、常用技巧:

(1)lowbit = x & -x

x & -x,它把正整数x的1除了它最后一个1,都变成了0,也可以说成是取出x的最后一个1。

如lowbit(01001010) = 00000010

简单地说,对于一个数,比如01001010,它的相反数的补码为10110110,位与的结果为00000010。

原理很简单,某个正整数x的相反数的补码,等于x所有位取反,然后加1。

设y = ~x +
1,比较x和y,x的最后一个1右边都是0,y的同一个位也是1,右边也全是0(x取反加1后,最后一个1变成0,后面的0都变成1,加1后,进位,又变回来了)。而在x的最后一个1的左边和y完全相反。那么x
& y就剩下x的最后一个1的位置有1,其余全是0。

这个位运算技巧在树状数组中基本上都要用到。

(2)x & (x-1) == 0 → x是2的倍数

有且只有00010000这种数,减一之后是00001111,与原数每一个位置都不同。

这个位运算的技巧可用于初始化RMQ用的Sparse Table算法。

(3)int fast_max(int x, int y) { return (((x - y) >> 31) & (x ^ y))
^ x;}

  int fast_min(int x, int y) { return (((y - x) >> 31) & (x ^ y)) ^
y;}

可用于快速得到x和y的最大值,比较的条件运算的速度是比较慢的(注意这个是32位有符号整数才能用,否则要修改)。

首先令z = (x - y) >> 31,若x < y,有x - y < 0,那么x -
y的最高位为1,右移31位,得到32个位都是1的z。同理若x ≥ y,那么z = 0。

令 p = x ^ y,那么p的某一位为1当且仅当x和y的那一位不x同为1或不同为0,通俗地说,p就是x和y的“差异”。那么可以得到x ^ p = y,y
^ p = x。

那么,若x < y,有z & p = p,返回值便是x ^ p = y。否则,z & p = 0,返回值为x ^ 0 = x。

4、状态压缩

所谓状态压缩,即把一个集合{0,1,……,n-1},其中选用1表示,不选用0表示,合起来就可以用一个int的二进制表示。那么比如集合{‘a‘, ‘b‘,
‘c‘, ‘d‘},其子集{‘b‘, ‘d‘}就可以用二进制数0101即十进制数5表示。状态压缩DP必备。

(1)测试第 i 个元素是否被选上:

bool check(int state, int i) {

  return (state >> i) & 1;

}

(2)把第 i 个元素设为选择(即1):

int set1(int state, int i) {

  return state | (1 << i);

}

(3)把第 i 个元素设置为不选择(即0):

int set0(int state, int i) {

  return state & ~(1 << i);

}

(4)检查是否有两位选择的元素相邻:

bool check(int state) {

  return (state & (state >> 1)) == 0;

}

5、枚举状态

  用二进制来枚举状态,要比写一个dfs来枚举要简单快速地多。

(1)枚举{0,1,……,n-1}的所有子集:

for(int s = 0; s < 1 << n; ++s) {/*对子集s进行处理*/}

(2)枚举某个集合sup(如01101101)的子集:

像上面那样枚举判断的话,会用很多重复状态,太浪费了。

从大到小枚举,令sub = sup。每次对sub减1,然后位与sup,就能得到比原sub恰好小1的sup的子集。

int sub = sup;

do {

  //对子集sub进行处理

  sub = (sub - 1) & sup;

} while(sub != sup);//处理完sup = 0后,会有sup = -1

(3)枚举集合{0,1,……,n-1}的所包含的大小为k的子集:

首先得到字典序最小的子集comb = (1<<k)-1。

每次循环,取出最低位x = comb & -comb,令y = comb +
x。此时x为comb的最低位的1,y把comb最低位的1开始,往左连续的1变成的0,这些1左边的第一个0变成了1。

那么~y和comb位与就得到了comb从最后一位1开始,往左的所有连续的1组成的数。

令z = ~y & comb,将z的1右移到最低位,这个用z/x可以得到,然后再右移一位,删掉最后的一个1。

最后再位与y,就把comb的最后面的连续的1的前面的0变成了1,其余的1删掉一个以后,移到最右边,就可得到comb的下一个集合。

int comb = (1 << k) - 1;

while(comb < 1 << n) {

  //对子集comb进行处理

  int x = comb & -comb, y = comb + x;

  comb = ((comb & ~y) / x >> 1) | y;

}

时间: 2024-07-29 11:24:46

人造奇迹——二进制位运算的运用的相关文章

[C++基础]原码/反码/补码、二进制位运算

原码/反码/补码 编码 定义 实例 原码 最高位为符号位,"0"表示正,"1"表示负,其余位表示数值的大小. [+100]原=01100100 [+0]原=00000000 [-100]原=11100100 [-0]原=10000000注意:在原码中,零有两种表示形式. 反码 正数的反码与其原码相同:负数的反码是对其原码逐位取反,但符号位除外. [+100]反=01100100 [+0]反=00000000 [-100]反=10011011[-0]反=1111111

二进制位运算

1.  数的原码补码转换 计算机运算中,数都是由补码表示的.正数的补码就是原码:负数的补码就是各位(包括符号位)取反,再加上1.假设数是8位的,最高位为符号位.1的补码是0x00000001,-3的补码是0xFFFFFFFD. 如果由补码转化为原数:正数不变:负数的补码是各位(包括符号位)取反,再加上1得到负数的绝对值,再贴上符号.补码0x00000010的数是1,补码0xFFFFFFE4的数的绝对值是000111002=2810,则该数是-28. 下面代码的输出是-30: 1 #include

七、 二进制位运算

判断一个 字符串在不在一个列表里 1 name_list = [1,2,3,4,5,'a','b',] 2 name_list2 = ['q','w','e','r',] 3 #判断 4 是否在name_list2这个列表里 4 w = 4 in name_list2 5 e = 4 in name_list 6 #如果存在则 返回Ture 如果不存在则返回 Fales 7 print(w) 8 print(e) 9 10 ###加上判断包含的话 11 if 4 in name_list: 12

二进制位运算几个常用的技巧

1. 奇数偶数 对于一个正整数的二进制,如果是偶数,那么最低位一定是0,相反如果是奇数,最低位一定是1.比如4(0100),3(0011)等等.因此通过对一个整数的二进制最低位的可以判断其是奇数还是偶数. 对正整数a,判断奇偶可以通过(a&0x1)来判断,是1则是奇数,是0则是偶数. 将一个奇数转成比他小的偶数,(a&-2). 2. 右移1(>>1) a>>1其实就是a/2:

2015.8.5 循环与函数、位运算

注意点: 1.只有整形才可以用switch. 2.case后面的常量不可以重复 3.default语句可以放在任意位置,但是后面不可以省略break: while: 1.用来处理一些重复执行的操作 2.确定循环终止的条件 (1)在循环开始之前,需要定义一个循环控制变量并将其初始 (2)确定循环的约束条件 (3)在每一次循环结束的时候,在循环中,要去改变循环控制变量的值,让它不间断接近约束条件 注意点: 1.在c语言总任何树枝都有真假性,只有0为 假,其它都为真 2.注意不把==和=混淆了,为了避

POJ--2570--Fiber Network【floyd+位运算】

题意:一些公司决定搭建一些光纤网络,单向的,如果从第一点到第二点,有ab两个公司可以搭建,第二点到第三点有ac两个公司可以搭建,第一点到第三点有d公司可以搭建,则第一点到第三点有a.d两个公司可以搭建,a是通过第二点,d是直接连接两点.现在给你这么一个光纤网络,问某两点之间有哪些公司可以搭建起网络. 首先这题是个多源点的,有点像最短路的思想,如果让我做我肯定硬着头皮找相同的字母,不过我看到图论书里的想法很好,就写上来了. 因为公司是用一个小写字母来标识的,且每个公司的标识互不相同,也就是说最多只

位运算 F Alice and Bob

题目传送门 1 /* 2 题意: 求(a0*x^(2^0)+1) * (a1 * x^(2^1)+1)*.......*(an-1 * x^(2^(n-1))+1) 式子中,x的p次方的系数 3 二进制位运算:p = 2 ^ i + 2 ^ j + 2 ^ k + ...,在二进制表示下就是1的出现 4 例如:10 的二进制 为1010,10 = 2^3 + 2^1 = 8 + 2,而且每一个二进制数都有相关的a[i],对p移位运算,累计取模就行了 5 */ 6 #include <cstdio

二进制运算基础

# 一.二进制位运算 1. 按位与(&) ??位运算实质是将参与运算的数字转换为二进制,而后逐位对应进行运算. ??按位与运算为:两位全为1,结果为1,即1&1=1,1&0=0,0&1=0,0&0=0. ??例如51 & 5 -> 00110011 & 00000101 = 00000001 -> 51 & 5 = 1 ??特殊用法: ??(1)与0相与可清零. ??(2)与1相与可保留原值,可从一个数中取某些位.例如需要取101

java笔记2

1      关键字 1.1    关键字的概述 Java的关键字对java的编译器有特殊的意义,他们用来表示一种数据类型,或者表示程序的结构等,关键字不能用作变量名.方法名.类名.包名. 1.2    常见的关键字 备注:不必死记硬背,如果使用关键字作为标识符,编译器能提示错误. goto 是java的保留关键字,意思是java并没有使用goto,以后是否使用未定. 2      标识符 2.1    什么是标识符 就是程序员在定义java程序时,自定义的一些名字,例如helloworld 程