lowbit 与 highbit

jxyanglinus / 2025-02-18 / 原文

lowbit

lowbit 在竞赛中还是很常见的,比如树状数组就必须要用 lowbit

lowbit 的原理是利用原码, 反码, 补码的性质来获得数字在二进制下最低位的 \(1\)。理解了原码,反码,补码,就不难理解 lowbit 了。

lowbit 代码如下:

inline int lowbit(int x) {  
    return x & -x;  
}

以一个例子来理解上面的代码(假设你已经理解了原码,反码,补码):

  • 我们假设 \(n = 8\)\(8\) 的二进制为 : \(00001000\)
  • \(8\) 取反的二进制为:\(11110111\)
  • 再对取反 \(8\) 加一(补码)的二进制为:\(11111000\)
  • 进行与操作之后:\(00001000\)
    就得到了我们的 \(lowbit(8) = 8\)

highbit

这个相对来说要少见些。

顾名思义,它干的事与 lowbit 相反,求的是数字在二进制下最高位的 \(1\)

其原理是将一个数二进制下最高位的 \(1\) 及其右面的每一位都补充为 \(1\),然后把最高位右面的都减掉,剩下就是最高位的 \(1\)。代码如下:

int highbit(int x){  
    x |= x >> 1;  
    x |= x >> 2;  
    x |= x >> 4;  
    x |= x >> 8;  
    x |= x >> 16;  
    return x - (x >> 1);  
}

为什么这样写呢?

我们假设 \(x\) 在二进制下只有最高位是 \(1\),其余位都是 \(0\)。通过 highbit 函数的第一行,我们通过右移 \(1\) 位并与原来的 \(x\) 异或,将 \(x\) 最高位往右一位填充为了 \(1\),现在总共有连续的两个 \(1\)。接着通过第二行,可以继续向右填充两位,现在统共有 \(4\)\(1\) 了。接下来的代码以此类推,我们就可以把右面全部填充为 \(1\) 了,最后 x - (x >> 1) 就可以得到我们想要的 highbit 了。

推广一下,如果最高位右面还有 \(1\),上面的代码依旧是正确的。

上面的函数返回值是 int 型的,如果用别的类型,可以自行调整右移位数。