0x01 位运算(2)
## 0x01 位运算(2)
本节主要内容:
第一部分:补码反码、移位运算、快速幂、快速乘法
第二部分:二进制状压、成对变换、lowbit运算,Barrett模乘(补充)
### 五、 二进制状压
说起状压,我们更熟悉的一个名词是**状压DP**
DP,即动态规划,是一种以空间换时间的算法(准确来说是思想)
当时间复杂度进入预期范围内,我们有时会发现,空间复杂度超出预期,于是,我们需要压缩空间
以课本习题[最短Hamilton路径](https://www.acwing.com/problem/content/93/ "最短Hamilton路径")为例,在压缩空间时,我们将“点被经过的状态”与二进制数码0/1对应,n个点的状态压缩在一个n为二进制数中,需要访问时移位计算取出状态。从而,我们用这个二进制数表示全局状态,并可以将其作为dp数组的下标使用,写出更清晰的状态转移方程。
### 六、 成对变换
通过计算可以发现
$$
\\begin{aligned} 0\\quad xor\\quad 1 &= 1\\\\ 1\\quad xor\\quad 1 &= 0\\\\ &\\dots\\\\ \\\\ n\\quad xor\\quad 1 &= n+1\\quad n为偶数\\\\ n\\quad xor\\quad 1 &= n-1\\quad n为奇数\\\\ \\end{aligned}
$$
该结论可用于图论,用于存储一对无向边
### 七、lowbit运算
何为lowbit,顾名思义,与“最低位”有关
下面给出几个算式(后缀B表示该数为二进制数,逗号为隔位符便于)
$$
\\begin{aligned} lowbit(01011000B) &= 00001000B \\quad 即lobit(88)=8\\\\ lowbit(01001100B) &= 00000100B \\quad 即lowbit(76)=4\\\\ \\end{aligned}
$$
显然,lowbit的作用可以形象地认为就是**保留数字在二进制下的最后一个1及后面的0**
那么,怎样实现lowbit运算呢?
#### 1\. 原理及实现
为了使说明更加形象,我们通过例子模拟lowbit的运算过程。
现在随意取一个n,不妨设
n =1010100**10000**
现在对n取反
~n = 0101011**01111**
我们发现,**n后面有多少个连续的0配上一个1,~n后面就有多少个1配上一个0**
于是,我们将~n+1,得到
~n+1 =0101011**10000**
我们发现,**n和~n+1在尾段得到了相同的值,前段得到了相反的值**
| 表达式 | 值 |
| --- | --- |
| n | 1010100**10000** |
| ~n | 0101011**01111** |
| ~n+1 | 0101011**10000** |
于是,我们想到可以计算n&(~n+1),得到
n&(~n+1) = 0000000**10000**
于是,我们实现了lowbit运算:
$$
lowbit(n) \\equiv n\\&(~n+1)
$$
运算较为简单,具体代码略
#### 2\. 进阶运用与优化
lowbit的可运用于找出一个整数在二进制下所有为1的位
##### ① 找出整数所有是1的位(暴力)
不断把n赋值为n-lowbit(n),配合log2()得到所有n为1的位
```cpp
double log2(double x){
return log(x)/log(2); //log以e为底,用换底公式转化为以2为底
}
```
该方法可能存在double精度问题,理论成立,实践时并不适宜
##### ② 找出整数所有是2的位(Hash)
建立一个Hash数组,令
$$
H\[2^k\] \\equiv k
$$
即可
##### ③ 找出整数所有是2的位(Hash+数学)
考虑到Hash数组过大,使用数学上的一个结论
$$
对任意k\_1,k\_2\\in\[0,35\],若k\_1\\neq k\_2,则2^{k\_1}\\neq2^{k\_2}
$$
于是我们可以令
$$
H\[2^k\\quad mod\\quad37\] \\equiv k
$$
于是,我们通过巧妙选取模,节约了大量空间,将Hash数组的容量限制在37个,即H\[0..36\]。
#### 3\. 一些内置函数
```cpp
int __builtin_ctz(unsigned int x) //返回x的二进制表示下最低位1后面有多少0,参数为unsigned
int __builtin_ctzll(unsigned long long x) //返回x的二进制表示下最低位1后面有多少0,参数为ull
int __builtin_popcount(unsigned int x) //返回x的二进制表示下有多少位为1,参数为unsigned
int __builtin_popcountll(unsigned long long x) //返回x的二进制表示下有多少位为1,参数为ull
```
这些内置函数较为高效,但其与编译器版本有关,且部分竞赛禁用,请慎用。(引用自《算法竞赛进阶指南》)
### 八、Barrett模乘(补充)
在《算法竞赛进阶指南》补充的习题[增加模数——ACWing](https://www.acwing.com/problem/content/5582/ "增加模数——ACWing")中,题目对时间复杂度的要求较高
除了通过快读快写等方式优化常数外,还需要优化取模算法
CPP通常的取模运算算法较为低效,时间较长,可以自己确定算法进行取模
参考文章:[Barrett与Montgomery模乘算法](https://blog.csdn.net/wxkhturfun/article/details/112252257 "Barrett与Montgomery模乘算法")
由于作者时间有限,这里不再赘述。在以后更新中,我会添加一些个人见解和心得。
**本文由“庞加莱复现”原创,未经允许,严禁转载!**
**如果觉得本文对你有用,不妨点赞收藏一下吧!**

浙公网安备 33010602011771号