OIer应该知道的二进制知识
计算机使用\(2\)进制,这是众所周知的。在学习\(OI\)的过程中,\(2\)进制也显得尤为重要。有时候,细节决定成败,所以我想总结一下容易被遗忘和误解的关于\(2\)进制的知识。
1、运算符
&:与。1&1=1,1&0=0,0&0=0;(同真为真)
|:或。1|1=1,1|0=1,0|0=0;(一真俱真)
^:异或。1 ^ 1=0,1 ^ 0=1, 0 ^ 0=0;(阴阳协调为好,极阴或极阳皆为坏)
num>>len:将num左移len位,低位溢出舍弃,高位不足补0。比如1010>>3就等于0001
num<<len:将num右移len位,高位溢出舍弃,低位不足补0。比如1010<<3就等于0000
~:全位取反。比如 ~ 1010=0101
位运算优先级要比加减乘除等运算优先级低,所以用的时候记得加括号。
2、原码,补码,反码
对于这三个东西我并不是很清楚他们分别是什么意思,在\(OI\)中,我觉得记住下面几点就可以了。
二进制数的第一位表示符号,\(0\)为整数,\(1\)为负数。\(int\)作为一个\(32\)位整形数据类型可以保存\([-2^{31}-1,2^{31}-1]\)的所有整数。因为第一位存符号去了,所以只是到\(2^{31}\)而不是\(2^{32}\)。\(unsigned\) \(int\)不保存符号位就可以到\(2^{32}\)。\(long\) \(long\)同理。所谓溢出就是数值超过\(32\)位二进制数能存的范围(\(64\)位同理,我们以\(int\)为例)。比如\(01111111111111111111111111111111\)(十进制下是\(2147483647\))也就是\(int\)能保存的最大的数字。加上一个\(1\)之后理应是\(2147483648\),二进制下就是\(10000000000000000000000000000000\)。但是因为第一位是符号位,所以就变成负数了。而每个数逐位取反都是自己的相反数\(-1\),所以\(~2147483647=-2147483648\)。再举几个例子:
比如\(-1(11111111111111111111111111111111)\)取反之后就是\(0(000000000000000000000000)\)。所以我们也可以由这一点快速求出一个数的相反数,就是逐位取反之后再\(+1\),也就是\(~num+1=-num\)
比如~\(1(00000000000000000000000000000001)+1=-1(11111111111111111111111111111111)\)
3、lowbit
\(lowbit(i)\)表示\(i\)在二进制下从低位到高位第一个\(1\)与它后边跟着的\(0\)是多大。比如\(lowbit(24[11000])=8[1000]\)
显然,我们会一种\(O(logn)\)的求法求\(lowbit(i)\)。但是运用上面的知识,我们可以\(O(1)\)求\(lowbit(i)\)
假设某个数二进制表示下后面一段是\(1000..000\),前面我们不管。把它逐位取反就变成了前面一段取反加上\(011111...111\)。这个时候两个数&起来是等于\(0\)的,因为没有哪个位置同为\(1\)。假设我们把取反之后的数字\(+1\)。那么就会变成原数前面一段取反加\(100....000\)。这个时候&起来,前面是反的,全部都会变成\(0\),刚刚好\(lowbit(i)\)所求的部分会被保存下来,所以\(lowbit(i)=i\)&\(((\)~\(i)\)+1\()\)。也就是\(lowbit(i)=i\)&\((-i)\)
4、memset
在很久以前我就疑惑过,为啥\(int\)类型占\(4\)字节,\(long\) \(long\)占\(8\)字节。我们可以灵性的理解一波,在二进制下,每八位占一个字节。而\(memset\),可以将某个数的每八位全部赋值成一样的数字。比如\(memset(a,1,sizeof(a))\)。就是把\(a\)数组里的每一个值都赋值成\(00000001000000010000000100000001\)。\(memset(a,255,sizeof(a))\)就是把\(a\)数组里的每一个数值都赋值成\(111111111111111111111111\)。\(0x7F\),是一个十六进制数,表示\(16^1*7+16^0*15=127\)。\(0x\)放在前面起声明这是个十六进制数的作用。由于\(2\)位十六进制数相当于\(8\)位二进制数,所以我们一般在\(memset\)里写两位十六进制数。\(memset(a,0x7F,sizeof(a))\)是将\(a\)数组全部赋值成为\(01111111011111110111111101111111\),这是\(memset\)能赋值出的最大的有符号类型整数。\(0x3F\)=\(16^1*3+16^0*15=63\),相对于\(0x7F\),这个数字更加不容易加爆,所以后面涉及加法的情况下,我们还是有\(memset(a,0x3f,sizeof(a))\)比较好。