常用技巧:位运算的奇技淫巧
整理一些位运算的常用操作。基础知识点提醒:
1.~与-的不同含义。~是逐位取反,而-是算术运算取负。
2.>>右移运算,对于无符号数填充0;有符号数则填充当前符号
重要!运算符优先级的整理!涉及到位运算一定要考虑好优先级的问题,要不然会出现各种奇怪的问题功亏一篑。为保证正确性,建议严格使用括号
- ()、[]、.、->
- ~(逐位取反)、-(取负)、!、*(取值)、&(取地址)、++、--、sizeof
- /、*、%
- +、-
- <<、>>
- >、<、>=、<=
- ==、!=
- &
- ^
- |
- &&
- ||
- ?:
- =、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=
接下来是位运算常见的应用。不过个人感觉除过零散的数学题,位运算很多是被应用于状压DP,所以状压DP相关的位运算会在状压DP部分也整理一份
1.乘除法:左移右移实现2^k的乘除操作
2.通过位操作交换两数
void swap(int &a,int &b){ a=a^b; b=a^b; a=a^b; }
3.位操作判断奇偶数
x&1==0
4.求绝对值
int abs(int x){ int i=x>>31; //i取值为0或-1 return i==0?a:~a+1; //或者return ((a^i)-i),对于i=0,a^i=a^0=a;对于i=-1,a^i=a^-1=a^0xffff=~a }
5.高低位交换
比如说,给定一个 16 位的无符号整数,将其高 8 位与低 8 位进行交换,求出交换后的值。
unsigned shoet x=3450; x=(x<<8)|(x>>8)
6.找到二进制中1出现的最低位
a&-a //也就是lowbit函数,树状数组要用
7.统计二进制中1的个数
int count=0; while(x){ x=x&(x-1); //每计算一次,x的二进制中的低位就少一个1 count++; }
同样的思路,该技巧还可以用于处理:
1)O(1)判断一个数是不是2的幂次:判断数中是不是只有一个“1”
2)计算要将A转化为B需要改变几位:C=AxorB,再看C中有几个“1”
8.二进制进行子集枚举
使用一个正整数二进制,第i位是1还是0代表集合的第i个数取或者不取。所以从0到2^n-1总共2^n个整数,正好对应集合的2^n个子集。对此进行拓展有下列操作:
1)要求集合中不能有两个相邻的元素
if ((mask >> 1) & mask) continue;
2)在限定必须不取某些元素的前提下枚举子集
// mask的第x位为0表示x必须不在子集中(原集合中不含这个元素) for (int mask1 = mask; mask1 >= 0; mask1 = (mask1 - 1) & mask)
3)在限定必须取某些元素的前提下枚举子集
// mask的第x位为1表示x必须在子集中 for (int mask1 = mask; mask1 < (1 << m); mask1 = (mask1 + 1) | mask)
9.利用a^b^b=a
1)数组中,只有一个数出现一次,剩下都出现两次,找出出现一次的数
数组中所有数依次异或,最后结果即为答案
2)数组中,只有两个数出现一次,剩下都出现两次,找出出现一次的
假设这两个数分别是x和y.将数组全部异或,最终结果res=x^y。因为x!=y,res!=0。查看res,找出其二进制某一位为1,该位即为a。根据数组中每个数的a位是否为1可分为两组,采用1)中的方法分别求出两组的结果即可。
参考链接:https://www.zhihu.com/question/38206659