不少人在代码里经常见到这样一行代码:
#define lowbit(x) x&(-x)
或是:
int lowbit(x){return x&(-x);}
这看似简单的一行代码,实则包含了很多知识,也是树状数组这种数据结构的基础。
首先,不得不提的便是二进制了。
平时我们见到的数,比如 114 , 514 , 998244353 114,514,998244353 114,514,998244353 之类,基本上都是十进制,即满十进一,每一位数字最大为 9 9 9。
二进制,顾名思义,即满二进一,每一位数字最大为 1 1 1。
由于计算机的存储方式为真/假(即true/false或者说是1/0),二进制便是计算机的存储方式。因此,学计算机,二进制是不可能不学的了。
我们平常用的都是十进制,那么如何将一个十进制数转换为二进制数呢?
可根据以下步骤来操作:
记该十进制数为 n n n。
例如,我们现在要计算 114 114 114 所对应的二进制数:
我们可以这样用短除法(最右边为余数):
这下应该清楚多了吧。
练习1:写出 514 514 514 对应的二进制。
取该数的小数部分,每次乘上 2 2 2,取整数部分。
练习2:写出 0.114 0.114 0.114 的二进制。
二进制跟十进制一样,也有正有负。在计算机当中,存储符号相当复杂,那么是怎样存储的呢?
首先,我们在二进制串中引入一个定义:符号位。由1/0表示, 1 1 1表示负数, 0 0 0 表示正数。
例如, 114 114 114 的二进制为 1110010 1110010 1110010,那么它的原码就是 01110010 01110010 01110010。
那么, − 114 -114 −114 的原码就是 11110010 11110010 11110010。
我们发现, 114 + ( − 114 ) 114+(-114) 114+(−114) 应该等于 0 0 0,但是 01110010 + 1110010 = 01100100 01110010+1110010=01100100 01110010+1110010=01100100,并不是 0 0 0。
因此我们引入了反码的概念。
正数不变,负数除符号位外都取反。
例如, 114 114 114 的二进制为 1110010 1110010 1110010,那么它的反码就是 01110010 01110010 01110010。
那么, − 114 -114 −114 的反码就是 10001101 10001101 10001101。
正数不变,负数除符号位外都取反再加 1 1 1。
例如, 114 114 114 的二进制为 1110010 1110010 1110010,那么它的反码就是 01110010 01110010 01110010。
那么, − 114 -114 −114 的反码就是 10001110 10001110 10001110。
正数不变,负数用 − 114 -114 −114 来举例:
原码 | 反码 | 补码 |
---|---|---|
11110010 11110010 11110010 | 10001101 10001101 10001101 | 10001110 10001110 10001110 |
定义 lowbit(x)=x&(-x)
,举几个例子:
这里的负数取的是补码。
还可以发现 2 2 2 的幂次在 lowbit
操作后仍是原数。
我们可以发现, 114 114 114 的二进制为 01110010 01110010 01110010, 2 2 2 正是这个数最右边的 1 1 1 的位置对应的数。
这是因为 − 114 -114 −114 的补码是 10001110 10001110 10001110,最右边的 1 1 1 之前的数都被反过来了,总有 1 1 1 位为 0 0 0 ,与操作后结果全为 0 0 0。因此最后剩下的便是最右边的 1 1 1 的位置对应的数。
请大家记住一点:
不能因为代码简单,不懂原理就放过。
阅读量超过 1000 1000 1000 更新下一篇:树状数组!
感谢大家的大力支持!