下篇会通过例题多介绍一些位运算的小技巧。首先回顾一下上篇最后的思考题:
- popcount在
复杂性类里吗?答案是不在,所以我们目前的算法达不到
也是情有可原的。证明不难,因为我们可以把parity(求输入中含有奇数还是偶数个1)归约到popcount,而parity不在
复杂性类里的证明很多计算理论的课都会提到,原始论文参见[1]。
- reverse bits的更好算法?我好像不会... 但是这个操作显然在
里。大家有好的想法可以在评论区里告诉我。
Total Hamming Distance
题意是给你n个二进制数,求他们两两之间的hamming distance之和。链接在这里:
https://leetcode.com/problems/total-hamming-distance/leetcode.com![8573a5860df6952b33927c24c407a010.png](https://img-blog.csdnimg.cn/img_convert/8573a5860df6952b33927c24c407a010.png)
容易看出可以对每位分别统计,只要对每位求出所有输入数字中该位为0的个数和该位为1的个数就行。那么就有一个显然的
下面介绍如何用位运算做到
思路还是对每位维护一个计数器,表示(部分)输入的数中有多少个该位上为1。我在计算理论课上见过一个类似的题:设计一个电路把
void
原理是从低位到高位一位位加起来,其中
可以发现不需要改代码就可以用位运算做到对位并行计算,相当于我们用了
解递归式可得,整个电路的复杂度是
我的代码在这里:477. Total Hamming Distance.cpp
![225a8c2ac3e7fb7e64fdd5107095999d.png](https://img-blog.csdnimg.cn/img_convert/225a8c2ac3e7fb7e64fdd5107095999d.png)
p.s. 这题还有一个
bitset高精度
我在知乎看到过这个问题:
如何使用bitset实现高精度?www.zhihu.com抱着猎奇的心态找了一下实现,比如 高精度wu-kan。我修正了链接中写法的一些问题并优化了一下复杂度。下面介绍一下各种运算的实现方式:
位运算(&, |, ^, ~, <<, >>):bitset原生支持,复杂度
加法:和之前提到的写法有一点点类似,用位运算处理进位,每轮把a+b归约到a^b和(a&b)<<1的加法,其中a^b是不考虑进位的和,a&b是进位部分,需要左移1位。因为最坏情况下会做
Bint
当然也可以用正常高精度加法的写法做到
减法:归约到加法,在补码表示下取反+1就可以得到
Bint
但这里有个小小的问题:因为一个很小的数取反之后高位会有一串前导1,归约到加法之后平均复杂度的分析失效了。为了达到
Bint
比较:一位位比就行,复杂度
bool
乘法:用竖式乘法,归约到
Bint
除法、取模:用长除法,归约到
pair
输出:如果要十进制结果的话需要做一次
完整代码如下:
#define N 2048
总结:用bitset实现高精度的代码会比朴素的正常写法短一些,运算速度比正常写法慢一点(大概差几十倍,但极端情况下会被卡),优点是因为在
a+b
题意是计算
![8573a5860df6952b33927c24c407a010.png](https://img-blog.csdnimg.cn/img_convert/8573a5860df6952b33927c24c407a010.png)
(一般来讲这种限制不能用xx操作的面试题没什么意思。但这可是著名难题
刚刚已经介绍了一个
用简单的分治策略可以做到
因为补码表示的性质,不妨假设
对每块算出最低位上是否会接收右边传来的进位之后,块之间的加法结果就互相独立了,可以并行计算。用高精度bitset的加法算法就行,每轮把a+b归约到a^b和(a&b)<<1的加法,但用&来避免跨块进位。在
还有一种更巧妙的做法:又又又是利用乘法。根据卷积的意义有
uint
DFA
Single Number II
题意:输入的
题解:一个很自然的想法是对每位分别处理,设计一个包含
BZOJ2908:又是nand (权限题,想看原题面可以戳这里)
题意:定义A nand B=not(A and B)。给出一棵树,树上每个点都有点权,定义树上从a到b的费用为0与路径上的点的权值顺次nand的结果。需要支持以下操作:
1. Replace a b: 将点a (
2. Query a b: 输出点a到点b的费用。
题解:注意到nand不满足交换律和结合律。但是如果把每位分开计算,可以用一个DFA(或者2*2的矩阵)来表示一段链的左边(右边同理)来了一个0或1之后依次nand链上所有点权得到的0/1值,这样就有结合律了。然后可以用树链剖分/动态树维护。直接做的话每位的答案都要单独计算,复杂度
用刚刚讲的位运算模拟DFA转移的方法可以把复杂度除一个
struct
p.s. 以前我在校内模拟赛给别人出过这个题,结果当时造数据的时候用了随机权值(树的形态不是随机的),被一堆人水过了。大家可以思考一下这个情况下的简单做法。
矩阵转置
先介绍一对用乘法可以做到的小技巧:
1. (pack) 假设一个
这个数乘
2. (unpack) 即上面那个的逆操作,把
这两个操作可以用来对一个64位整数表示的4*4矩阵(每个数4bit)进行快速转置,只需取
![7518c67010d55c52eaa5d32a63f41cd2.png](https://img-blog.csdnimg.cn/img_convert/7518c67010d55c52eaa5d32a63f41cd2.png)
代码:
void
p.s. 转置也可以用类似reverse bits的方法做,不需要乘法。
印象中pack/unpack在另外一些问题中也有用,什么时候记起来了再放个例题。
update.
- 翻书看到了The Art of Computer Programming[2]的7.1.3节中介绍了一个在
时间内对二进制位任意重排列顺序的算法,并且不需要用到乘法。更一般地,我们可以在
时间内对二进制位作任意映射
,其中
为任意
的映射[3]。上文提到的reverse bits/矩阵转置可以看成是这个一般化结论的特殊情况。
2. 同一章中给出了一些lower bounds,例如reverse bits在不使用乘法的情况下需要
References
[1] Furst M, Saxe J B, Sipser M. Parity, circuits, and the polynomial-time hierarchy[J]. Mathematical systems theory, 1984, 17(1): 13-27.
[2] Knuth D E. The art of computer programming, volume 4A: combinatorial algorithms, part 1[M]. Pearson Education India, 2011.
[3] Chung K M, Wong C K. Construction of a Generalized Connector with 5.8 n log 2 n Edges[J]. IEEE Transactions on Computers, 1980 (11): 1029-1032.