信息学奥林匹克竞赛初赛精讲精练
上QQ阅读APP看书,第一时间看更新

第三节 位运算

基础知识

程序中的所有数在计算机内存中都是以二进制的形式存储的,位运算就是直接对整数在内存中的二进制位进行操作(补码且符号位也参与运算,有关补码的知识见第二章的第二节),位运算符及其运算规则如下。

按位与(&)

两个都是1才是1。例如:


00101        5
11100        28
& ---------------
00100        4

&运算通常用于二进制数的取位操作,例如一个数“& 1”的结果就是取二进制数的最末位。这可以用来判断一个整数的奇偶性,二进制的最末位为0表示该数为偶数,最末位为1表示该数为奇数。对于一个数a,a=a & (a-1)的作用是将a的二进制数最右边的1变为0。

按位或(|)

只要有一个为1就为1。例如:


00101        5
11100        28
| ---------------
11101        29

|运算通常用于二进制数特定位上的无条件赋值,例如一个数“| 1”的结果就是把二进制数最末位强行变成1。如果需要把二进制数最末位变成0,对这个数“| 1”之后再减1就可以了,其实际意义就是把这个数强行变成最接近的偶数。

按位异或(^)

相同为0,不同为1(或理解为“半加”,也就是不进位的二进制数加法)。例如:


00101        5
11100        28
^ ---------------
11001        25

^运算可以用于简单的加密,比如我想告诉朋友我的QQ密码是123456,但又怕别人知道,所以我和朋友约定用19491001作为密钥,123456^19491001=19434233,我就把19434233告诉朋友,然后朋友再计算19434233^19491001=123456,就得到我的QQ密码了。

在密码学中,上述过程的123456称为明文,19434233称为密文,19491001称为密钥,明文经过加密过程转变为密文并传输给目标对象,目标对象接收后将密文经过解密过程转变为明文,如此便可实现安全的传输,即便被别人截获了传输的密文,没有密钥也无法得知明文。

加法的逆运算是减法,乘法的逆运算是除法,^运算的逆运算是它本身。由此可写出一个不需要中间变量的swap过程。


int a = 10,b = 5;
// 原交换方法
int t = a;
a = b;
b = t;
// 借助互逆运算
a = a + b;
b = a - b;
a = a - b;
// 借助^
a = a ^ b;
b = a ^ b;
a = a ^ b;

注意:^的交换方法不能用于一个数的自我交换,会导致数据变为0。

按位取反(~)

单目运算符,1变成0,0变成1,例如:


00101        5
~ ---------------
11010        -6

注意:11010为补码。

使用~运算时要格外小心,需要注意整数类型有没有符号。如果~的对象是无符号整数(unsigned),那么得到的值就是它与该类型上界的差。

左移(<<)

a<<b就表示把a转为二进制后左移b位(在后面添加b个0)。例如100的二进制数为1100100,而110010000转成十进制数是400,那么100<<2=400。可以看出,a<<b的值实际上就是a乘以2的b次方,因为在二进制数后添加一个0就相当于该数乘以2。

通常认为a<<1比a*2更快,因为前者是更底层一些的操作。因此程序中乘以2的操作请尽量用左移一位来代替。

定义一些常量可能会用到<<运算。你可以方便地用1<<16-1来表示65535。很多算法和数据结构要求数据规模必须是2的幂,此时可以用<<来定义Max_N等常量。

右移(>>)

与<<相似,a>>b表示把a转为二进制数后右移b位,低位舍弃b位,高位补b位符号位(正数补0、负数补1)。

右移运算对于正数,相当于a除以2的b次方(取商),想办法用>>代替除法运算可以使程序效率大大提高。

在其他编程语言(如Java)中,右移运算符分为带符号(>>)和无符号(>>>)两种。二者的区别在于舍弃低位后高位所补的数字,若带符号,则补符号位(正0负1);若无符号,则固定补0。

位运算符优先级

速记:从高到低,反、移、与、异、或,四则运算在反、移之间。

赛题训练

1.二进制数11101110010111和01011011101011进行逻辑或运算的结果是( )。

A. 11111111011111

B. 11111111111101

C. 10111111111111

D. 11111111111111

2.二进制数11101110010111和01011011101011进行逻辑与运算的结果是( )。

A. 01001010001011

B. 01001010010011

C. 01001010000001

D. 01001010000011

3.为了统计一个非负整数的二进制形式中1的个数,代码如下:


int CountBit(int x)
{
    int ret = 0;
    while (x)
    {
        ret++;
        ___________;
    }
    return ret;
}

则空格内要填入的语句是( )。

A. x>>=1

B. x &=x-1

C. x |=x>>1

D. x<<=1

4.二进制数00101100和01010101异或的结果是( )。

A. 00101000

B. 01111001

C. 01000100

D. 00111000