蓝桥杯第一章——位运算的奇巧淫技

关于位运算

  1. "&",与运算
    0和1,仅有两数均为1时为1,其余都为0;
a b &
0 0 0
0 1 0
1 0 0
1 1 1
  1. "|",或运算
    有1为1,无1为0;
a b or
0 0 0
0 1 1
1 0 1
1 1 1
  1. "^",异或运算
    相同为0,相反为1;
    可以比作不进位的二进制加法。
a b ^
0 0 0
0 1 1
1 0 1
1 1 0
  1. ">>" 右移(符号位填充高位),"<<" 左移,">>>"右移(0填充高位)
    用法均为 value >>> num,value:想要操作的数,num:移动的位数
    注意:低位舍去

8 byte区分“>>”和">>>":-5为10000101, -5 >> 1 答案为 11000010,但 -5>>>1 答案为01000010(使用符号位与0填充的区别)
关于左移:左移n位相当于原数乘上 2n ,比如 3 << 2 相当于 00000011 变成 00001100 = 12 = 3*22
关于右移:当操作数为正数时,右移与无符号右移作用一样,右移 n 位相当于原数除以 2n 取整

  1. “~”逐位取反
    二进制为1取0,二进制为0取1
    eg.10101001取反为01010110

例题:

  1. 一个1001位的数组arr,元素为1—1000之间的数,仅有一个数k出现两遍,求这个数。

思路1:暴力破解,再开一个大小为1000的数组num,初始值为0,遍历arr数组,
num[arr[i]]++,当num[arr[i]]为2时直接输出arr[i];

#include 

using namespace std;

int main() {
     
    int arr[1001] = {
     0};
    for(int i = 0; i < 1000; i++)
        arr[i] = i + 1;
    arr[1000] = rand();
    while(arr[1000] > 1000 || arr[1000] < 1)
        arr[1000] = rand();
    //printf("随机数是:%d\n",arr[1000]);
    int num[1002] = {
     0};
    for(int i = 0; i <= 1000; i++) {
     
        num[arr[i]]++;
        if(num[arr[i]] == 2) {
     
            printf("%d", arr[i]);
            break;
        }
    }
    return 0;
}

思路2:将arr数组所有元素异或(nor),再将1——1000所有的数异或(mo),将nor与mo异或,就可以直接得到k。
(异或可以去重)

#include 

using namespace std;

int main() {
     
    int arr[1001] = {
     0};
    int nor = 1;
    for(int i = 0; i < 1000; i++)
        arr[i] = i + 1;
    arr[1000] = rand();
    while(arr[1000] > 1000 || arr[1000] < 1)
        arr[1000] = rand();					//这个while控制重复的k在1——1000之间
    //printf("随机数是:%d\n", arr[1000]);		//打印k
    for(int i = 0; i <= 1000; i++)
        nor ^= arr[i];
    //printf("nor=%d\n", nor);					//nor是arr数组的异或
    int mo = 1;
    for(int i = 1; i < 1001; i++)
        mo ^= i;
    //printf("mo=%d\n", mo);					//mo是1——1000的异或
    int ans = mo ^ nor;							//nor和mo异或得答案
    printf("%d", ans);
    return 0;
}


思路一暴力解法更加好理解,但是多开了一个数组,但是当理解了异或位运算可以去重之后,思路二其实更加简单。
自我理解,异或可以去掉偶数个重复的数,剩下奇数个。

例题2:
给一个数,判断这个数的二进制中有几个1。

思路:该数为x,x与x-1的效果为去掉二进制码最后的那个1,x再等于x与x-1的结果。
例:x=9,
9的二进制为1001,8的二进制为1000,9&8=1000,此时x=1000=8
7的二进制为0111,8&7=0000
当x=0时终止循环。

代码:

#include 

using namespace std;

int main() {
     
    int x;
    while(~scanf("%d", &x)) {
     
        int ans = 0;
        int y = x;
        while(x != 0) {
     
            x &= (x - 1);
            ans++;
        }
        printf("%d的二进制中有%d个1\n", y, ans);
    }
    return 0;
}

改编:判断一个数是不是2的整数次方

思路:
一个数是2的整数次方的条件是它的二进制只有一个1,那么就在之前的代码上进行改动,当只有一个1时,这个数就是2的整数次方

代码:

#include 

using namespace std;

int main() {
     
    int x;
    while(~scanf("%d", &x)) {
     
        int ans = 0;
        int y = x;
        while(x != 0) {
     
            x &= (x - 1);
            ans++;
        }
        if(ans == 1)
            printf("%d是2的整数次方\n", y);
        else
            printf("%d不是2的整数次方\n", y);
    }
    return 0;
}

例题3:给定一个介于0到1之间的浮点数,打印它的二进制,如果该数无法用32位以内的二进制精确表示,则打印“ERROR”

思路:小数转化为n进制,用该数乘n,得数的整数部位为小数的位数,小数部分仍然乘n
例:0.625转换:
1:0.625 * 2 =1.25
2:0.25 * 2=0.5
3:0.5 * 2=1.0
故0.625的二进制表示为0.101

代码:

#include 

using namespace std;

int main() {
     
    double x;
    while(~scanf("%lf", &x)) {
     
        int cnt = 0;
        int byte[50]={
     0};
        bool flag = 0;
        while(x != 0) {
     
            if(cnt == 32) {
     
                flag = 1;
                break;
            }
            cnt++;
            x *= 2;
            if(x >= 1) {
     
                byte[cnt] = 1;
                x -= 1;
            } else
                byte[cnt] = 0;
        }
        if(flag)
            printf("ERROR");
        else {
     
            printf("0.");
            for(int i = 1; i <= cnt; i++)
                printf("%d", byte[i]);
        }
        printf("\n");
    }
    return 0;
}

例题4:给一个数,将它的二进制奇偶位互换

思路:
将这个数与01010101010101010101010101010101做与运算,保留该数的奇数位;
再将原数与10101010101010101010101010101010做与运算,保留该数的偶数位;
再将偶数位右移一位,奇数位左移一位,做异或运算
最后就得到了奇偶位互换的答案
因为之前那两串32位二进制太长,所以用16进制表示位0x55555555和0xaaaaaaaa

代码:

#include 

using namespace std;

int main() {
     
    int x;
    while(~scanf("%d", &x)) {
     
        int odd = x & 0xaaaaaa;
        int even = x & 0x555555;
        int ans = (odd >> 1) ^ (even << 1);
        printf("%d\n", ans);
    }
}

——2019 / 5 / 9

之后待补充

你可能感兴趣的:(蓝桥杯,蓝桥杯,位运算,acm,新手)