位运算的常用技巧

位运算的常用技巧

  1. 取出x的第i位。
    y = (x>>(i-1))&1;
    
    例如:x = 22; ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 ,假如判断第2位是否为1,令 i = 2 i = 2 , 则 y = ( x > > ( 2 1 ) ) & 1 y = (x>>(2-1)) \& 1 x x 右移一位变成 ( 00001011 ) 2 (00001011)_2 1 1 进行 & \& 运算。变成1。 所以 x = 22 x=22 的第 2 2 位上是 1 1
    注意:这里都假设一个数占8位。
  2. 将x的第i位取反。
    x ^= 1<<(i-1);
    
    例如:x = 22; ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 ,假如将第2位取反。令 i = 2 i = 2 ,则 1 < < ( 2 1 ) 1<<(2-1) 等于 ( 2 ) 10 (2)_{10} = ( 00000010 ) 2 (00000010)_2 ,然后与 ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 进行异或操作,相同取0,不同取1。 ( 00010110 ) 2 (00010110)_2 ^ ( 00000010 ) 2 (00000010)_2 = ( 00010100 ) 2 (00010100)_2
  3. 将x的第i位变为1。
    x |= 1<<(i-1);
    
    例如:x = 22; ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 ,假如将第1位取反。令 i = 1 i = 1 ,则 1 < < ( 1 1 ) 1<<(1-1) 等于 ( 1 ) 10 (1)_{10} = ( 00000001 ) 2 (00000001)_2 ,然后与 ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 进行或操作,有 1 1 1 1 ,都不为 1 1 则是 0 0 ( 00010110 ) 2 (00010110)_2 | ( 00000001 ) 2 (00000001)_2 = ( 00010111 ) 2 (00010111)_2
  4. 将x的第i位变为0。
    x &= ~(1<<(i-1));
    
    例如:x = 22; ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 ,假如将第2位取反。令 i = 2 i = 2 ,则 ( 1 < < ( 2 1 ) ) (1<<(2-1)) 等于 ( 2 ) 10 (2)_{10} = ( 00000010 ) 2 (00000010)_2 ,然后将 ( 10 ) 2 (10)_2 取反变成 ( 11111101 ) 2 (11111101)_2 ,再与 ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 进行与操作,有 0 0 0 0 ,都不为 0 0 则是 1 1 ( 00010110 ) 2 (00010110)_2 & ( 11111101 ) 2 (11111101)_2 = ( 00010100 ) 2 (00010100)_2
  5. 将x最靠右的1变成0。
    x = x&(x-1);
    
    例如:x = 22; ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 x 1 = 21 x-1 = 21 ; ( 21 ) 10 (21)_{10} = ( 00010101 ) 2 (00010101)_2 ( 00010110 ) 2 (00010110)_2 & ( 00010101 ) 2 (00010101)_2 = ( 00010100 ) 2 (00010100)_2 ,可以看出最右侧的1变成了0。
    可以用这个语句判断x内有几个1。代码:
    int cnt = 0;//记录n的二进制形式中1的个数
    while(n)
    {
        n = n&(n-1);
        cnt++;
    }
    
  6. 取出x最靠右的1。
    y = x&(-x);
    
    例如:x = 22; ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 ( 22 ) 10 (-22)_{10} = ( 11101010 ) 2 (11101010)_2 ( 00010110 ) 2 (00010110)_2 & ( 11101010 ) 2 (11101010)_2 = ( 00000010 ) 2 (00000010)_2 ,取出最右侧的 1 1 在第二位。
  7. 把最靠右的0变为1。
    x|=x+1
    
    例如:x = 22; ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 x + 1 = 23 x+1 = 23 ; ( 23 ) 10 (23)_{10} = ( 00010111 ) 2 (00010111)_2 ( 00010110 ) 2 (00010110)_2 | ( 00010111 ) 2 (00010111)_2 = ( 00010111 ) 2 (00010111)_2 ,最右侧第1位的 0 0 变成了 1 1
  8. 判断是否有两个连续的1。
    if(x&(x<<1)) cout << "YES";
    
    例如:x = 22; ( 22 ) 10 (22)_{10} = ( 00010110 ) 2 (00010110)_2 ( x < < 1 ) (x<<1) = ( 00101100 ) 2 (00101100)_2 ( 00010110 ) 2 (00010110)_2 & ( 00101100 ) 2 (00101100)_2 = ( 00000100 ) 2 (00000100)_2 ( 00000100 ) 2 (00000100)_2 = 4 10 4_{10} 为真, 存在两个连续的 1 1 . 注意:结果不能表示在第几位上有连续的1。

C++的使用

如果位移操作在C语言项目下不能使用,可以创建一个C++项目,在C++项目下一定可以使用。下面写一下C++项目的创建与使用。

C++项目的创建

C++项目的创建与C语言的不同:在选择语言的时候选择C++
在这里插入图片描述

C++项目的使用

C++项目的使用与C语言的不同:在头文件下加一句“using namespace std;” 使用这个命名空间。
在这里插入图片描述
其他的都像C语言一样使用就可以了。

训练学习第五天题解集

A.Binary Numbers

#include<cstdio>
using namespace std;

int main()
{
 int t;
 scanf("%d", &t);
 while(t--)
 {
     int n;
     scanf("%d", &n);
     bool flag = true;
     for(int i = 1; i <= 23; i++)
     {
         if((n>>(i-1))&1) //判断第i位是否是1
         {
             if(flag)
             {
                 flag = false;
                 printf("%d", i-1);
             }
             else
                 printf(" %d", i-1);
         }
     }
     printf("\n");
 }
 return 0;
}

B.Lowest Bit

#include<cstdio>
#include<algorithm>
using namespace std;

int main()
{
    int n;
    while(scanf("%d", &n)&&n)
    {
        int ans = n&(-n);//取出n最靠右的1.
        printf("%d\n", ans);
    }
    return 0;
}

C.Secret Origins

题意:给你一个数n,要求你找到一个数m,m的二进制数内 1 1 的个数和n的二进制数内 1 1 的个数一样且大于n的最小的数。
题解:找到一个数的二进制数的第 i i 位是 1 1 和第 i + 1 i+1 位是 0 0 的位置。将第 i + 1 i+1 位变成1,将第 i i 位变成 0 0 .然后查看第 i i 位之前 1 1 的数量,分别将其放到不为1的最低位。例如:n=46, ( 46 ) 10 (46)_{10} = ( 00101110 ) 2 (00101110)_2 ,在第 4 4 位是 1 1 ,第 5 5 位是 0 0 ,将第 5 5 位变成1,将第 4 4 位变成 0 0 .变成 ( 00110110 ) 2 (00110110)_2 ,在第 2 2 位、第 3 3 位上有 1 1 ,分别将其放到不为1最低位,变成 ( 00110011 ) 2 (00110011)_2 = ( 51 ) 10 (51)_{10} .

#include<cstdio>
#include<algorithm>
using namespace std;

int main()
{
    int t;
    scanf("%d", &t);
    int cas = 0;
    while(t--)
    {
        int n;
        scanf("%d", &n);
        int inde = -1;
        for(int i = 1; i <= 31; i++)
        {
            if(((n>>(i-1))&1)&&!((n>>(i))&1)) //找到第i位是1,第i+1位是0
            {
                inde = i;
                break;
            }
        }
        n |= 1<<(inde); //将第i+1位变成1
        int cnt = 0;
        for(int i = 1; i <= inde; i++)
        {
            if((n>>(i-1))&1)
            {
                cnt++; //在第inde位以及之前1的个数
                n &= ~(1<<(i-1)); //将第i位变成0
            }
        }
        for(int i = 1; i < cnt;i++)
        {
            n |= 1<<(i-1); //将cnt-1个低位变成1
        }
        printf("Case %d: %d\n", ++cas,n);
    }
    return 0;
}

D.Parity

#include <cstdio>
using namespace std;

int main()
{
    int t;
    scanf("%d",&t);
    int cas=0;
    while(t--)
    {
        int n;
        scanf("%d",&n);
        int cnt = 0;
        while(n)
        {
            cnt++;
            n = n&(n-1);
        }
        if(cnt&1)
            printf("Case %d: odd\n",++cas);
        else
            printf("Case %d: even\n",++cas);
    }
    return 0;
}

E.Nim游戏

Nim游戏经典博弈题。
结论:异或为 0 0 ,则先手败,否则后手败。
简单推理:假如异或结果ans为 0 0 (记为状态1),那么先手取出多少,后手就取出多少,两个人取出的石子个数的异或是 0 0 ,则剩下的石子的个数的异或也是 0 0 (记为状态2),可以发现状态1和状态2的结果一样,可以得出异或为 0 0 的情况下,先手必败。那么假如异或结果ans不为 0 0 (记为状态3),先手可以首先取出ans个石子,剩下的石子异或结果为 0 0 (记为状态4),可以发现状态4和状态1是一样的。所以后手必败,则先手必胜。

#include <cstdio>
using namespace std;

int main()
{
    int n,x,ans = 0;
    scanf("%d",&n);
    for(int i = 0; i < n; i++)
    {
        scanf("%d", &x);
        ans ^= x;
    }
    if(ans)
    {
        printf("A\n");
    }
    else
    {
        printf("B\n");
    }
    return 0;
}

F.合法整数集

题解:经过分析可以发现x的第i为如果是 0 0 ,yi 的第i位为 1 1 ,那么yi 一定是不用删除的数,把这类数去除。剩下的数按二进制位分解计数,答案就是x的二进制位为1的最小值。

#include<cstdio>
#include<algorithm>
using namespace std;

int main()
{
    int n, x,y;
    int used[40];
    for(int i = 0; i <= 35; i++)used[i] = 0;
    scanf("%d%d", &n, &x);
    for(int i = 0; i < n ; i++)
    {
        scanf("%d", &y);
        bool flag = true;
        for(int i = 1; i <= 31; i++)
        {
            if((!((x>>(i-1))&1))&&((y>>(i-1))&1)) //x的第i位是0,yi的第i位是1
            {
                flag = false;
                break;
            }
        }
        if(flag)
            for(int i = 1; i <=31; i++)
            {
                if((y>>(i-1))&1)//按二进制位分解计数
                    used[i]++;
            }
    }
    int ma = 100;//n最大为50
    for(int i = 1; i <= 31; i++)
    {
        if((x>>(i-1))&1)//判断x第i位为1
        {
            ma = min(ma, used[i]);//找出最小值,既是要删除的最少个数。
        }
    }
    printf("%d\n", ma);
    return 0;
}

G.Magic Grid

题解:这题是个特判,答案不唯一。所以自己设计一个符合题意得答案即可。当n=4,答案可以是 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \begin{matrix} 0 & 1 & 2 & 3 \\ 4 & 5 & 6 & 7 \\ 8 & 9 & 10 & 11\\ 12 & 13 & 14 & 15 \end{matrix} 每行每列的异或和都为 0 0
当n=8,答案可以是 0 1 2 3 16 17 18 19 4 5 6 7 20 21 22 23 8 9 10 11 24 25 26 27 12 13 14 15 28 29 30 31 32 33 34 35 48 49 50 51 36 37 38 39 52 53 54 55 40 41 42 43 56 57 58 59 44 45 46 47 60 61 62 63 \begin{matrix} 0 & 1 & 2 & 3 &16 & 17 & 18 & 19 \\ 4 & 5 & 6 & 7 & 20 & 21 & 22 & 23\\ 8 & 9 & 10 & 11 & 24 & 25 & 26 & 27\\ 12 & 13 & 14 & 15 &28 & 29 &30&31\\ 32&33&34&35&48&49&50&51 \\ 36&37&38&39&52&53&54&55\\ 40&41&42&43&56&57&58&59\\ 44&45&46&47&60&61&62&63 \end{matrix} 每行每列的异或和都为 0 0
可以看出以4*4的方格为一个单位进行填充。

#include<cstdio>
#include<algorithm>
using namespace std;

int ans[1010][1010];

int main()
{
    int n;
    scanf("%d", &n);
    int num = 0;
    for(int i = 0; i<n;i+=4)
    {
        for(int j = 0; j < n; j+=4)
        {
            for(int x = 0; x < 4; x++)
            {
                for(int y = 0; y < 4;y++)
                {
                    ans[i+x][j+y] = num++;
                }

            }
        }
    }
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            printf("%d ", ans[i][j]);
        }
        printf("\n");
    }
    return 0;
}
发布了27 篇原创文章 · 获赞 13 · 访问量 1712

猜你喜欢

转载自blog.csdn.net/weixin_43855330/article/details/102987197