前言
C/C++ 中的位运算是对操作数的二进制位进行操作的一种运算方式。它可以直接操作整型数据的二进制表示,提供了一些灵活、高效的操作方法。
以下是 C/C++ 中常用的位运算符和操作:
- 位与(&):将两个操作数的对应位进行与运算,只有当两个位都为 1 时结果才为 1。
- 位或(|):将两个操作数的对应位进行或运算,只要两个位中有一个为 1,结果就为 1。
- 位异或(^):将两个操作数的对应位进行异或(相同为 0,不同为 1)运算,得到的结果表示两个操作数对应位是否相等。
- 位取反(~):对操作数的所有位进行取反,即 0 变为 1,1 变为 0。
- 左移(<<):将操作数的二进制位向左移动指定的位数,左移 n 位相当于乘以 2 的 n 次方。
- 右移(>>):将操作数的二进制位向右移动指定的位数,右移 n 位相当于除以 2 的 n 次方。
除了这些基本的位运算符,C/C++ 还提供了一些位操作的库函数和位操作的技巧,例如使用位运算来进行位掩码运算、判断奇偶性、交换变量值等。位运算可以在一些特定的场景下提供高效的解决方案,如位压缩、位图操作、位字段等。
简单算法的位运算优化
位运算的效率非常高,所以很多算法可以改成位运算来提高效率。
除以二
这是一个广为人知的优化,即a/2等同于a>>1。
CODE
#include <cstdio>
#include <iostream>
using namespace std;
unsigned int a;
int main()
{
cin>>a;
// cout<<a/2;
cout<<(a>>1);
return 0;
}
2的n次方
同样比较简单:a*pow(2,n)等同于a<<n。
CODE
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
unsigned long long a,n;
int main()
{
cin>>a>>n;
// cout<<a*pow(2,n)<<endl;
cout<<(a<<n);
return 0;
}
判断奇偶
判断奇偶除了用a%2以外,还可以用a&1。
CODE
#include <cstdio>
#include <iostream>
using namespace std;
unsigned int a;
int main()
{
cin>>a;
// cout<<a%2;
cout<<(a&1);
return 0;
}
取绝对值
对于int类型的a,有abs(a)=a ^ (a >> 31) - (a >> 31),其中^是按位异或。
CODE
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
int a;
int main()
{
cin>>a;
// cout<<abs(a);
cout<<(a^(a >> 31)-(a>>31));
return 0;
}
复杂问题的位运算求解
问题:连续自然数按位与
题目描述
给出两个自然数 L,R,试求 L&(L+1)&(L+2)&…&(R-1)&R 的值。
其中&是按位与运算符。
数据范围:
0<=L<=R<=10^18。
思路与实现
由于数据可能很大,直接模拟是不可行的,我们需要考虑优化。
既然是按位与,那么只要L与R之间某一个数的某一位为0,则答案的该位也为0。
或者说,如果R减去L的值超过了2^n,则答案的第n低位(从第0位开始数)必然为0,因为此时[L,R]区间内对于这一位一定有0。
如果还不理解可以看看下表:
纵向观察你就会发现规律。
这是一种情况,当然还有一些情况。还有一种情况是:R减去L的值小于等于2^n,此时似乎不能直接判断答案的第n低位是0还是1,但是我们可以查看L、R这一位的值,若都为1,说明[L,R]区间对于这一位完全位于连续的一段1上,比如L=10,R=13,此时对于n=3时就是这种情况,此时答案的这一位为1,其他情况答案的第n低位位0。(多观察一下那个表就明白了)
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
unsigned long long L,R,ans;
int main()
{
cin>>L>>R;
for(int i=0;i<=ceil(log2(R));i++)
if((R-L<=(1ll<<i))&&((L>>i)&(R>>i)&1))
ans|=(1ll<<i);
cout<<ans;
return 0;
}
总结
C++的位运算是非常实用和重要的。如果您能完全理解和消化本篇文章的所有内容,那么相信您一定能提升不少!