关于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的正确性。
感谢你这么耐心看到这里~