0x01 位运算
前言
在我刚开始刷题的时候,觉得位运算很简单,只不过是一些与或非等等的一些操作罢了。现在看来,是我目光短浅,见识少的缘故,导致我无法窥得位运算的精妙之处。
位运算有四种基本操作:
| 与 | 或 | 非 | 异或 |
|---|---|---|---|
| and, & | or, | | not, ~ | xor, ^ |
此外,在 m m m位二进制数中,通常称最低位为第0位,从右到左以此类推,最高位为第 m − 1 m-1 m−1位。
补码
以C++的 int 和 unsigned int 类型为例。
- 32位无符号整数 unsigned int:原码与补码相同。
- 32位有符号整数 int:最高位为符号位,0表示非负数,1表示负数。最高位为0时,直接可以当作32位二进制数,最高位为1时,需要将其按位取反并+1,得到负数的值。
| 32位补码表示 | 无符号整数 | 有符号整数 |
|---|---|---|
| 000000…000000 | 0 | 0 |
| 011111…111111 | 2147483647 | 2147483647 |
| 100000…000000 | 2147483648 | -2147483648 |
| 111111…111111 | 4294967295 | -1 |
移位运算
左移
二进制下每一位数字同时向左移动,低位填充0,高位越界后舍弃。
1
≪
n
=
2
n
,
n
≪
1
=
2
n
1 \ll n = 2^n, n \ll 1= 2n
1≪n=2n,n≪1=2n
算术右移
二进制补码表示下每一位数字同时向右移动,高位以符号位填充,低位越界后舍弃。
n
≫
1
=
⌊
n
2.0
⌋
n \gg 1 = \lfloor \frac{n}{2.0} \rfloor
n≫1=⌊2.0n⌋
逻辑右移
在二进制补码表示下每一位数字同时向右移动,高位以0填充,地位越界后舍弃。
例题
1、 a b a^b ab
求a的b次方对p取模的值,其中 1 ≤ a , b , p ≤ 10 9 1\le a,b,p \le 10^9 1≤a,b,p≤109。
思路:我们知道,每一个正整数都可以通过二进制数来表示。假设b在二进制表示下有k位,因此, b b b可以表示为:
b = c k − 1 2 k − 1 + c k − 2 2 k − 2 + . . . + c 0 2 0 b=c_{k-1}2^{k-1} + c_{k-2}2^{k-2} +...+c_02^0 b=ck−12k−1+ck−22k−2+...+c020
其中, c i ( 0 ≤ i ≤ k ) c_i(0 \le i \le k) ci(0≤i≤k)表示 b b b在二进制表示下第 i i i位的数字,取值为0或者1。因此:
a b = a c k − 1 ∗ 2 k − 1 ∗ a c k − 2 ∗ 2 k − 2 ∗ . . . ∗ a c 0 ∗ 2 0 a^b =a^{c_{k-1}*2^{k-1}}*a^{c_{k-2}*2^{k-2}}*...*a^{c_0*2^0} ab=ack−1∗2k−1∗ack−2∗2k−2∗...∗ac0∗20
因为 k = ⌈ l o g 2 ( b + 1 ) ⌉ k=\lceil log_2(b+1) \rceil k=⌈log2(b+1)⌉,所以上式乘积项最多为 ⌈ l o g 2 ( b + 1 ) ⌉ \lceil log_2(b+1) \rceil ⌈log2(b+1)⌉个。又因为:
a 2 i = ( a 2 i − 1 ) 2 a^{2^i}=(a^{2^{i-1}})^2 a2i=(a2i−1)2
所以我们可以通过k次递推求出每个乘积项。
代码:
int power(int a, int b, int p) { int ans = 1 % p; for(; b > 0; b >>= 1){ // 如果该位为1 if(b & 1){ ans = (long long)ans * a % p; // 通过long long强制转换,防止越界 } a = (long long)a * a % p; } return ans; }算法时间复杂度: O ( l o g 2 b ) O(log_2b) O(log2b)。
如果 1 ≤ a , b , c ≤ 10 18 1 \le a,b,c \le 10^{18} 1≤a,b,c≤1018,那么 long long 的强制类型转换不再可行,并且不存在一个可供强制类型转换的128位整型类型。因此,我们需要一些特殊的处理办法。
2、64位整数乘法
求a乘b对p取模的值,其中 1 ≤ a , b , c ≤ 10 18 1 \le a,b,c \le 10^{18} 1≤a,b,c≤1018。
思路:采用快速幂的思想。把整数 b 用二进制表示,即 b = c k − 1 2 k − 1 + c k − 2 2 k − 2 + . . . + c 0 2 0 b=c_{k-1}2^{k-1}+c_{k-2}2^{k-2}+...+c_02^0 b=ck−12k−1+ck−22k−2+...+c020,那么 a ∗ b = c k − 1 ∗ a ∗ 2 k − 1 + c k − 2 ∗ a ∗ 2 k − 2 + . . . + c 0 ∗ a ∗ 2 0 a*b=c_{k-1}*a*2^{k-1}+c_{k-2}*a*2^{k-2}+...+c_0*a*2^0 a∗b=ck−1∗a∗2k−1+ck−2∗a∗2k−2+...+c0∗a∗20。
因为 a ∗ 2 i = ( a ∗ 2 i − 1 ) ∗ 2 a*2^{i}=(a*2^{i-1})*2 a∗2i=(a∗2i−1)∗2,如果已经求出 a ∗ 2 i − 1 a*2^{i-1} a∗2i−1%p,那么预算过程中每一步的结果都不会超过 2 ∗ 10 18 2*10^{18} 2∗1018,仍然在64位整数 long long 的表示范围内。所以可以通过 k 次递推求出每个乘积项。
代码:
long long mul_64(long long a, long long b, long long p) { long long ans = 0; for(; b > 0; b >>= 1){ if(b & 1){ ans = (ans + a) % p; } a = a * 2 % p; } }时间复杂度: O ( l o g 2 b ) O(log_2b) O(log2b)

551

被折叠的 条评论
为什么被折叠?



