位运算

位运算

  • 位运算的速度快于乘除运算,除了运算速度快之外,还有一些位运算的技巧和应用。这里进行简单的总结

常见的位运算有:

运算符 含义 解释说明
& 按位与 两个二进制的位置同为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等。合适情况下,可以使用移位操作代替乘除。

posted @ 2020-09-14 23:18  S_K_P  阅读(190)  评论(0)    收藏  举报