位运算的概述
我们知道,计算机中的数在内存中都是以二进制形式进行存储的 ,而位运算就是直接对整数在内存中的二进制位进行操作,因此其执行效率非常高,在程序中尽量使用位运算进行操作,这会大大提高程序的性能。
那么,涉及位运算的运算符如下表所示:
另外还有 >>> 它的行为于普通的右移运算符类似.
对于正数,>> 和 >>>效果相同
但是对于负数,>> 和 >>> 效果不一样
>> 是在左边用符号位补位
>>> 是在左边都是用0补位
位运算的优先级
优先级需要弄清楚,如果不太清楚可以加小括号确保是想要的运算顺序,这里只是相对优先级,即只是和一些常用的算术运算符做比较。
负数的位运算
首先,我们要知道,在计算机中,运算是使用的二进制补码,而正数的补码是它本身,负数的补码则是符号位不变,其余按位取反,最后再 + 1得到的, 例如:
15 原码:00001111 补码:00001111
−15,原码:10001111补码:11110001
那么对于负数的位运算而言,它们的操作都是建立在补码上的,得到的运算结果是补码,最后将补码结果转化成一个普通的十进制数结果。但需要注意的是,符号位是需要参与运算的,而在左移右移操作中,负数右移补1,左移右边补0。例如对于 −15 ,其补码为11110001 , 右移一位(−15 >>1)得到的是11111000 ,即−8,其他的同理
注意:-1的补码为 11111111, 按位取反就变为00000000,这实际上就是0。
运算的应用
位运算实现乘除法
拓展异或
异或的性质
和常见的位运算性质
位运算习题
交换两个值
这效率非常高,我们来剖析其原理,对于a = a ∧ b则 b = b ∧ ( a ∧ b ) 根据交换律以及异或性质,得b = b ∧ b ∧ a = 0 ∧ a = a 同理 a = ( a ∧ b ) ∧ a = 0 ∧ b = b 这样就实现了交换操作。
比较两个值的大小
先记录a和b的差值,再通过c的符号来判断a和b的大小(因为如果c为负数,则c的二进制补码首位为1,如果是非负数则是0)
找到缺少的数字
ac代码:
本题使用了异或的第四条性质
找出只出现一次的数组
数组中一种数出现了奇数次,其他的数都出现了偶数次,找到出现了奇数次的数
本题这个性质
找出出现了奇数次的2种数
数组中有2种数出现了奇数次,其他的数都出现了偶数次,返回这2种出现了奇数次的数
最后的ac代码:
找回出现少于m次的数
数组中只有1种数出现次数少于m次,其他数都出现了m次,返回出现少于m次的数
思路:
最后的ac代码:
关于异或
将 n 个数任意分成若干组,定义每个组的权值为该组所有数的异或和,求最小权值之和
思路:
由于 a ⊕ b < = a + b所以任意两个数,不分组比分组更优,即所有数全在一个组时最优。
最后的ac代码:
判断一个整数是不是2的幂
思路:首先该整数不能是负数。
当该整数大于0时,只有当该整数的二进制数里只有一个1
所以通过Brian Kernighan算法,提取出最右侧的1的状态,如果它和原本的状态相等,则说明它是2的幂。
最后的ac代码
找到大于等于n的最小的2某次方
已知n是非负数,返回大于等于n的最小的2某次方
思路:
以下是模拟实现
最后再+1遍可以求得答案
最后的ac代码
返回所有数字 & 的结果
求区间【left,right】内所有数字 & 的结果
思路:
最后的ac代码
01背包,但是bit
本题表面是个01背包,但是对于放入物品的体积的计算是进行按位或运算,所以本题其实是位运算题。
对于涉及到位运算的题一定要把数字当成二进制串来看
并且不存在进位意味着:位于位之间是不相互影响的。
思路:
设m的第x位为1
设选中的物品的wi做或运算的结果是sum
如果sum的第x位为0,那么第x位之后的那些位就是任意的
因此我们去枚举这个第x位,m的第x位要为1
而选择的物品的第x位是0,并且选择物品的高位应该是m的高位的子集合
就是wi(高位) | m == m
低位随意
代码:
# include <stdio.h>
struct node
{
int v;
int w;
};
int max(int a, int b)
{
if (a > b)
return a;
else
return b;
}
int main()
{
int t;
scanf("%d", &t);
int n, m;
int ans;
int ans1;
int mx;
struct node a[10];
while (t)
{
ans1 = 0;
ans = 0;
mx = 0;
scanf("%d %d", &n, &m);
for (int i=0; i<n; ++i)
scanf("%d %d", &a[i].v, &a[i].w);
for (int i=0; i<n; ++i)
if ((a[i].w | m )== m)
ans = ans + a[i].v; //特殊情况,wi和m相同,或者是m的子集
for (int i=31; i>=0; --i)
{
if (m >> i & 1 ) //确保第x位为1
{
ans1 = 0;
mx=(((m>>(i-1))<<i)|(1<<i)-1); //此处可以动手画一画,就是把第x位变为0,低位全
部变为1
for (int j=0; j<n; ++j)
if ((mx | a[j].w )== mx)
ans1 = ans1 + a[j].v;
ans = max(ans1, ans);
}
}
printf("%d\n", ans);
t = t - 1;
}
}