位运算
位运算
- 位运算的速度快于乘除运算,除了运算速度快之外,还有一些位运算的技巧和应用。这里进行简单的总结
常见的位运算有:
运算符 | 含义 | 解释说明 |
---|---|---|
& | 按位与 | 两个二进制的位置同为1该位结果为1,否则为0 |
| | 按位或 | 两个二进制的位置有只要一个为1该位结果为1,否则为0 |
^ | 按位异或 | 两个二进制的位置相同为0,不同为1 |
~ | 按位取反 | 对二进制每一个数位取反,0变1,1变0 |
<< | 左移 | 整个二进制全部向左移动,右边补0(左移n位相当于乘2^n) |
>> | 右移 | 整个二进制全部向右移动,低位舍弃(右移n位相当于除2^n) |
负数的二进制表示是它的相反数按位取反加1。例如-5 = ~5 + 1
1、判断奇偶
判断整数n的奇偶性
要知道,二进制下的奇数末位是1,偶数是0,可以用1去按位与操作
#include <iostream>
using namespace std;
int main () {
int n;
cin >> n;
if (n & 1)
cout << "odd";
else
cout << "even";
return 0;
}
2、两个数交换
位运算中异或的特点就是相同为0,不同为1。
#include <iostream>
using namespace std;
int main () {
int a, b;
cin >> a >> b;
a ^= b;
b ^= a;
a ^= b;
cout << a << " " << b;
return 0;
}
分析
a = a ^ b
b = b ^ a = b ^ a ^ b = a (b被赋值为a)
a = a ^ b = a ^ b ^ a = b (a被赋值为b)
3、只出现一次的数
整型数组中有奇数个整数,有一个整数出现了一次,其他的数都出现了两次,找出这个只出现一次的数。
需要知道一点,0与任何一个数异或操作结果都是那个数本身, 相同两个数异或结果为1。
#include <iostream>
#define N 1005
using namespace std;
int a[N];
int main () {
int n, ans = 0;
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++)
ans ^= a[i];
cout << ans;
return 0;
}
4、1的统计
计算一个数的二进制表示中有多少个1
技巧在于,取出一个数的二进制中最右边的位数可以通过和1进行与操作。
#include <iostream>
using namespace std;
int main () {
int n, cnt = 0;
cin >> n;
while (n) {
cnt += (n & 1);
n >>= 1;
}
cout << cnt;
return 0;
}
上述方法假定只有一个\(1\),右边全是\(0\),一个一个判断效率低下。实际上还有更快捷的方式
对于\(x\) & $(x-1) $表示的是消除x右边第一个不为0的1。于是可写成
#include <iostream>
using namespace std;
int main () {
int n, cnt = 0;
cin >> n;
while (n) {
n &= n-1;
cnt ++;
}
cout << cnt;
return 0;
}
5、2的n次幂
判断一个数是不是\(2\)的\(n\)次方
一个数如果是\(2\)的\(n\)次方,那么这个数的二进制表示中只有唯一的一个\(1\),减\(1\)后,\(1\)的位置变\(0\),右边的数位全部变为\(1\)。
#include <iostream>
using namespace std;
int main () {
int n;
cin >> n;
if (n > 0 && (n & n-1) == 0)
cout << "Yes";
else
cout << "No";
return 0;
}
6、4的n次幂
判断一个数是不是\(4\)的\(n\)次方
首先一个数如果是\(2\)的\(n\)次方,那么这个数一定是\(4\)的\(n\)次方。
其次观察所有\(4\)的\(n\)次方的二进制数可以得到,\(100、10000、1000000\)...唯一的\(1\)右边的\(0\)的个数是偶数个,那么右移的时候可以每次移动\(2\)位
#include <iostream>
using namespace std;
bool judge (int x) { //判断x是否是4的n次幂
if (x <= 0 || (x & x- 1) != 0) // 不是2的n次幂
return false;
while (x) {
if (x & 1)
return true;
x >>= 2;
}
return false;
}
int main () {
int n;
cin >> n;
cout << judge (n);
return 0;
}
7、快速幂
快速幂是利用了二进制的技巧来进行,是一种非常基础有效的方法。
假定计算\(x^p\)。常规方法是将\(x\)乘以\(p\)次,复杂度为\(O(n)\)。但是考虑二进制的方法
\(x^p\),\(p\)可以用二进制来表示分解。例如\(3^{14}\)可以表示成$3{(21+22+23)} $。相当于\(3^2 * 3^4 * 3^8\)
\(14\)转化为\(2\)进制可以表示成\(1110\),可以发现在位数为\(1\)的部分需要进行乘法,参照下表对比
位 | 1 | 1 | 1 | 0 |
---|---|---|---|---|
基 | \(3^8\) | \(3^4\) | \(3^2\) | \(3\) |
明显,考虑上表的对应关系,有\(1\)的部分乘,只需要乘\(3\)次。那么可以对幂\(p\)进行每次右移\(1\)位的操作,看最后一位数是\(1\)还是\(0\),而对应的基数变化,每次\(x = x*x\)即可。程序如下:
#include <iostream>
using namespace std;
int quick_pow (int x, int p) {
int ans = 1;
while (p) {
if (p & 1)
ans *= x;
x *= x;
p >>= 1;
}
return ans;
}
int main () {
int n, p;
cin >> n >> p;
cout << quick_pow(n, p) << endl;
return 0;
}
矩阵加速也应用的相同思想。
小结
运算符优先级
优先级按照从上到下排列,最下方优先级最低。
运算符 | 解释 |
---|---|
* / % | 乘,除,取模 |
+ - | 加,减 |
<< >> | 左移,右移 |
& | 按位与 |
^ | 按位异或 |
| | 按位或 |
用好位运算基本小技巧,例如x & x-1等。合适情况下,可以使用移位操作代替乘除。