位运算里有一种十分基础的运算:lowbit运算。
lowbit(n)定义为非负整数n在二进制表示下“最低为的1及其后边所有的0”构成的数值。例如n=10的二进制表
示为(2)1010, 则lowbit(n)=2=(2)10。
————摘自《算法竞赛进阶指南》
lowbit(n)的公式为:lowbit(n) = n & (~n + 1) = n & (-n)。下面事推导过程:首先将n的二进制数去反,则原来是1的位置就变成了0,原来是0的位置就变成了1。然后再将取反的数加一。我们知道,二进制数加一,则是一个“遇0变1,遇1进位”过程。那么原来的n的末尾的1以及1后面的一串0,取反变为一个0后面跟一串1时,再加一则会将这一串二进制数的1变成0,最前方的0变为1.比如:~101000 + 1 = 010111 + 1 = 011000。然后得到的数除了lowbit的那一串,其它部分都与原数n不同,所以我们再将n&得到的数,那些不同的部分就会全部变为0,而相同的部分则保持不变,这些不变的部分就是我们要求的lowbit(n)。
lowbit运算如何输出整数二进制下所有为1的位?我们只需要求出lowbit(n),然后在n=0之前不断把n赋值位n-lowbit(n),再将表示当前lowbit(n)的log以2为底的n的对数输出,便可以完成了。不过c++里并没有进行对数运算的函数(除了自然对数),所以我们需要进行预处理,将log以2为底的2的k次方的对数存起来,然后每次lowbit运算时只需要查一下数组就可以了。为了优化时间复杂度,我们可以使用hash来将2的k次方映射到数组的一个下标,查询的复杂度就可以优化位O(1)了。最直接的hash做法就是在hash[2^k]=k,不过空间复杂度就会特别大。这里可以用一个有技巧的做法,∀k∈[0, 35],st.2^k mod 37互不相等,且恰好取遍整数1~36(来自《算法竞赛进阶指南》)。
附上代码:
#include <cstring>
#include <cstdio>
using namespace std;
int main(){
int hash[37], n;
for(register int i = 0; i < 36; i++) hash[(1ll << i) % 37] = i;
while(scanf("%d", &n) == 1){
while(n > 0){
printf("%d ", hash[(n & -n) % 37]);
n -= n & -n;
}
printf("\n");
}
return 0;
}