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模乘算法")

由于作者时间有限,这里不再赘述。在以后更新中,我会添加一些个人见解和心得。

**本文由“庞加莱复现”原创,未经允许,严禁转载!**

**如果觉得本文对你有用,不妨点赞收藏一下吧!**

posted @ 2026-07-20 14:48  庞加莱复现  阅读(1)  评论(0)    收藏  举报