关于lowbit的那些事

lowbit是树状数组的前置知识。

lowbit(x)返回的是x的二进制表达式中最低位的1所对应的值。

e.g.

6的二进制是 0000 0110

从右往左数,第一个1出现在第2位上,对应的数是2^1也就是2,因此lowbit(6)=2

24的二进制是 0001 1000

从右往左数,第一个1出现在第4位上,对应的数是2^3也就是8,因此lowbit(24)=8

 

那么怎么用函数实现呢?

这就扯到了一个特别玄学的东西:补码

 

相关知识详见这里(戳我)

 

代码如下:

 

#include<iostream>
using namespace std;

int a;

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

int main()
{
    cin>>a;
    cout<<lowbit(a);
    return 0;
}

 

 

 

 

额  (x)&(-x)是个啥?

它就是:将x的补码按位与-x的补码

我们来验证一下:

lowbit(6)=(6)&(-6)

6的补码=6的原码=0000 0110

-6的原码=1000 0110

-6的反码=1111 1001

-6的补码=1111 1010

6的补码&-6的补码=

 0000 0110

&

1111 1010

=

0000 0010

=2

结果正确。

代入其他数值同样能得到正确的结果(但必须是正整数)

 

很玄学对不?

是不是感觉正好凑巧了?(我也是这么认为的)

所以我就打算不用补码的知识,另辟蹊径表示出lowbit函数。

(结果好像更玄学了。。。)

 

代码如下:

 

#include<iostream>
#include<cmath>
using namespace std;

int a;

int judge(int x)
{
    int t=1;
    while(!(x%(int)pow(2,t))){t++;}
    return t-1;
}

int lowbit(int x)
{
    return !(x%(int)pow(2,(int)floor(log2(x))))?pow(2,(int)floor(log2(x))):((x%2)?1:(int)pow(2,judge(x)));
}

signed main()
{
    cin>>a;
    cout<<lowbit(a);
    return 0;
}

 

 

 

 

不要害怕,我们试图理解一下……

 

中间的那一坨返回值,我们可以分为三部分:

!(x%(int)pow(2,(int)floor(log2(x))))

pow(2,(int)floor(log2(x)))

((x%2)?1:(int)pow(2,judge(x)))

这里为了节省空间,用了三目运算符(相当于if-else)[注:A?B:C的意思是如果A非0则返回B,A为0则返回C]

先判断第一部分是否为0,不为0返回第二部分,为0就返回第三部分

 

 

先看第一部分。

!(x%(int)pow(2,(int)floor(log2(x))))

floor(log2(x))就是小于等于x的2n的最大的n。

[注:log2(x)返回以2为底数,x为真数的对数;floor(x)为对x下取整]

e.g.

floor(log2(8))=floor(3)=3

floor(log2(19))=floor(4.2479)=4   (24=16<19<25=32)

floor(log2(1))=floor(0)=0  (但计算lowbit(x)时,x切忌为0!否则程序会运行错误!!!)

 

而加上pow,pow(2,floor(log2(x)))返回的即为小于等于x的最大的2n

e.g.

pow(2,floor(log2(6)))=4

pow(2,floor(log2(16)))=16

pow(2,floor(log2(17)))=16

 

因此,x%pow(2,floor(log2(x)))返回值为0时就表示x能被小于等于x的最大的2n整除

!x%pow(2,floor(log2(x)))返回值非0时就表示x能被小于等于x的最大的2n整除

e.g.

!5%pow(2,floor(log2(5))) = !5%4 = !(非0) = 0    因为5不能被小于等于5的最大的2n(也就是4)整除

!32%pow(2,floor(log2(32))) = !32%32 = !0 = 非0  因为32能被小于等于32的最大的2n(也就是32)整除

 

至于!(x%(int)pow(2,(int)floor(log2(x))))中,为什么要在pow前和floor前加(int),是因为floor(* double)返回的是浮点数,要强制类型转换为int,否则计算的就是2a(a是一个小数)。而pow本身返回的也是浮点数,若不强制类型转换为整型,程序就无法通过编译。(a%b,a和b必须为整型)下同。

 

 

理解了这里后,再来看第二部分:

pow(2,(int)floor(log2(x)))

第二部分是第一部分非0时,整个表达式的返回值。

就是说,lowbit函数在第一部分非0时,返回值为第二部分,也就是小于等于x的最大的2n

重新理一下思路:

第一部分非0,意味着x能被小于等于x的最大的2n整除,这时lowbit(x)返回小于等于x的最大的2n,符合lowbit的定义

(因为lowbit(2n)=2n

 

 

okay,接下来是第三部分

x不能被小于等于x的最大的2n整除时(如x=3,x=5,x=6等),lowbit返回值就为第三部分:

((x%2)?1:(int)pow(2,judge(x)))

这又是一个判断:

若x为奇数返回1,    [注:因为lowbit一个奇数返回的一定是1(奇数表示成二进制,最后一位就是1)]

若x为偶数返回(int)pow(2,judge(x))

再来看judge函数。

 

int judge(int x)
{
    int t=1;
    while(!(x%(int)pow(2,t))){t++;}
    return t-1;
}

 

 

 

 

因为x是偶数,所以就从t=1开始枚举2t看看t等于几时x不能被2t整除,返回的t-1即为能被x整除的最大的2t的t的值。

对应到二进制,即为从右往左数的第一个1,这个1所在位所对应的2n的n的值。

e.g.

x = 10 = 0000 1010  t=1时10可被2t也就是21=2整除,t++;t=2时10不可被22整除,循环结束,t=t-1=1。对应的,10的二进制表示中从右往左数的第一个1所在位是第2位,所对应的数=22-1=21=2t

x = 28 = 0001 1100  t=1时28可被2t也就是21=2整除,t++;t=2时28可被2t也就是22=4整除,t++;t=3时28不可被23整除,循环结束,t=t-1=2。对应的,28的二进制表示中从右往左数的第一个1所在位是第3位,所对应的数=23-1=22=2t

 

回到第三部分来看,x为偶数返回(int)pow(2,judge(x)),也就是2t,如x=10时t=1,(int)pow(2,judge(10))=2t=2=lowbit(10),答案正确。

 

 

至此,我们验证了没有利用补码的lowbit的正确性。

感谢你这么耐心看到这里~

 

posted @ 2021-08-20 11:45  凌云_void  阅读(404)  评论(1编辑  收藏  举报