位运算技巧总结

目录

一. 位运算概述

二. 位运算应用

1. 集合应用

2. 其他应用

三. 异或运算

1. 异或运算规则

2. 异或运算常用性质

3. 异或运算的应用

4. 例题分析


一. 位运算概述

        程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。常见的位运算包括与运算、或运算、异或运算、左移运算、右移运算等,其具体内容如下:

(1)& :与运算。若两个相应的二进制位都为1,则该位结果为1,否则为0。比如 0111 & 1011 = 0011

(2)|:或运算。若两个相应的二进制位有一个为1,则该位结果为1,否则为0。比如 0111 | 1011 = 1111

(3)^:异或运算。若两个相应的二进制位相同则为零,否则为1。比如 0111^1011 = 1100

(4)~:取反运算 。一元运算符用于将二进制位取反,即1变为0,0变为1。比如 ~0111 = 1000

(5)<<:左移运算。n<<k将n的二进制左移k位,低位补0(n*2^k)。比如 0111<<1 = 1110

(6)>>:右移运算。n>>k将n的二进制右移k位,低位被舍弃,高位(左边)补符号位,无符号数变为 n/2^k。比如 0111>>1 = 0011

        注意其中~的结合方向自右至左,且优先级高于算术运算符,其余运算符的结合方向都是自左至右,且优先级低于关系运算符。所以在运算时,注意括号的利用。

二. 位运算应用

1. 集合应用

        将数值n转化为二进制表示集合,每个二进制位的0和1表示该元素是否在集合中。则在集合中可以通过 & 表示交集,| 表示并集, ^ 表示对称差集,(1<<n)-1 表示n所有元素的全集。更复杂一些,s&(1<<i) 是否为0可以表示元素i是否是子集s中的一个元素(全集从0~n-1时),或者表示元素i+1是否是子集s中的一个元素(全集从1~n时)。其他集合应用技巧如下:

(1)从集合角度取n的所有子集

for(int k = (n-1)&n;k;k = (k-1)&n){
    //do...
}

(2)求n个元素的所有子集

while(cin>>n)
{
    for(int i = 0; i<(1<<n); i++)//枚举所有子集
    {
        for(int j = 0; j<n; j++)//枚举所有元素判断是否在子集里
        {
            if(s&(1<<i))printf("%d",i+1);
        }
        printf("\n");
    }

}

(3)求集合或者现有子集的所有子集

    while(cin>>n){
        for(int i = 0;i<(1<<n);i++){//所有子集
            cout<<"集合 "<<i<<" 的所有子集 :"<<endl;
            for(int k = i;k>0;k = (k-1)&i){//所有 现在子集 的 所有子集
                for(int j = 0;j<n;j++){//判断元素
                    if(k&(1<<j))cout<<j+1;
                }
                cout<<endl;
            }
        }
    }

(4)求不相邻元素的所有集合

for(int i = 0;i<(1<<n);i++){
    if((i>>1)&i)continue;//i里面含有相邻元素,退出
    //对集合的处理
}

(5)必须含有n个指定位置子集的所有可能

    while(cin>>n){
        int m,k;
        int sum = 0;
        cin>>m;
        while(m--){//求最小指定集合
            cin>>k;
            sum = sum|(1<<(k-1));
        }
        for(int i = sum;i<(1<<n);i = (i+1)|sum){//在sum基础上求所有集合
               //操作
        }
    }

(6)必须不含有n个指定位置子集的所有可能

    while(cin>>n){
        int m,k;
        int sum = 0;
        cin>>m;
        while(m--){
            cin>>k;
            sum = sum|(1<<(k-1));
        }
        sum = sum^((1<<n)-1);//^取最大集
        //cout<<sum<<endl;
        for(int i = sum;i>=0;i = (i-1)&sum){
            cout<<i<<endl;
            if(i==0)break;//一定要有,不然出现负数的位运算导致循环不断进行
        }
    }

(7)找出二进制中恰好含有 k个1的所有数

for (int mask = 0; mask < 1 << n; ) {
    int tmp = mask & -mask;
    mask = (mask + tmp) | (((mask ^ (mask + tmp)) >> 2) / tmp);
}

2. 其他应用

(1)O(1) 时间检查n是否为2的幂次

    while(cin>>n){
            if((n&(n-1))==0)cout<<"yes"<<endl;
            else cout<<"no"<<endl;
    }

(2)一个数组里所有的数字都出现两次,只有一个数字出现一次,请找出这个数字

    while(cin>>n){
            int sum,a;
            for(int i = 0;i<n;i++){
                cin>>a;
                if(i==0)sum = a;
                else sum = sum^a;//所有数字异或起来-〉异或的性质:自己异或自己为0,0异或n = n;
            }
        cout<<sum<<endl;
     }

三. 异或运算

1. 异或运算规则

交换律:A^B = B^A

结合律:A^B^C = A^C^B

恒等律:A^0 = A

归零率:A^A = 0

2. 异或运算常用性质

(1)自反性:A^B^B = A^0 = A

(2)A^11111... = ~A(取反)

(3)若A^B = C,则A^C = B

(4)异或结果为0的位置表示两数字该位相同,为1的位置表示两数字该位不同

(5)若GCD(A,B) = A^B = C , 则C = A-B

3. 异或运算的应用

(1)一个数组里,所有的数字都出现两次,只有一个数字出现一次,找出这个数字

(2)一个数组里面,所有数字都出现两次,只有两个数字出现一次,找出这两个数字

4. 例题分析

一个数组里面,所有数字都出现两次,只有两个数字出现一次,找出这两个数字

        假设这两个数字为A和B。那么将所有数字异或以后的结果为A^B,由于这两个数字不相同,所以异或结果肯定不为零。那么A^B的结果中,为0的位表示AB该位相同,为1的位表示AB该位不同。只要我们找到第一个为1的位(也就是AB不同的位),然后能将原数组分为两组,一组该位为1,一组该位为0。能确定的是,出现两次的数一定在同一组,AB一定在不同的组,所以我们将其中一组全部异或结果就是A或B,而A = A^B^B即可求出另一个元素。注意 n&~(n - 1) 可以找到n中最低的1位。

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000 + 7;
int num[maxn];
int getFirstOfOne(int n){
   return n&~(n-1);
}
void Solve(int len){
   int aXORb = 0;
   for(int i = 0;i<len;i++)aXORb^=num[i];
   int ans = getFirstOfOne(aXORb);
   int a = 0;
   for(int i = 0;i<len;i++){
      if(ans&num[i]){
         a^=num[i];
      }
   }
   int b = aXORb^a;
   printf("A = %d , B = %d\n",a,b);
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        int n;
        scanf("%d",&n);
        for(int i = 0;i<n;i++){
            scanf("%d",&num[i]);
        }
        Solve(n);
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿阿阿安

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值