当前位置:   article > 正文

c++之位运算(详解,初学者绝对能看懂)_c++位运算

c++位运算

目录

一  位运算符号

移位运算:

二  常用技巧:

三  运算符号优先级:

四    位运算常用技巧

1   判断奇偶性

2  求a的b次方

3  找处未重复的数

4  用O(1)时间检测整数n是否是2的幂次.

5  计算在一个 32 位的整数的二进制表示中有多少个 1

6 快速幂

7 二进制状态压缩

8 二进制优化递归

9 一道经典题


一  位运算符

&
按位与
如果两个相应的二进制位都为1,则该位的结果值为1,否则为0
|
按位或
两个相应的二进制位中只要有一个为1,该位的结果值为1
^
按位异或
若参加运算的两个二进制位值相同则为0,否则为1
~
取反
~是一元运算符,用来对一个二进制数按位取反,即将0变1,将1

举例

     1000101             1000101         1000101         1000101

&   0101100           |  0101110      ~                     ^ 0101110

=   0000100           = 1101111       = 0111010         1101011

移位运算

<<
左移
用来将一个数的各二进制位全部左移n位,低位以0补充,高位越界后舍弃。

        1左移n位:                    1 << n=2^n(这里指2的n次方)

         n左移1位:                    n << 1=2*n

举例

       short a=9115         0010001110011011   (9115的二进制表示)

       a << 1 =18230       0100011100110110   (注意高位越界后舍弃一个0,低位填充一个0)

       a << 2  = -29076    1000111001101100   (注意高位越界后舍弃两个0,低位填充两个0)

注意:左移是做乘2的运算,但这是在符号位(原码将最高位符号以0表示正,1表示负eg:0010001110011011中的最高位0就是符号位,表示是整数;而1000111001101100最高位是1,表示是负数)不变的情况下。如果符号位发生了改变,说明已经不能做乘2的运算了,否则会溢出,得到的值不是乘2的结果。

注意:short是Java中的表示,它定义的也是整形,不过是16字节(这里为了阐述符号位的改变而使用),而int是32字节

>>
右移
将一个数的各二进制位右移N位,移到右端的低位被舍弃,高位以符号位填充

        n左移1位 :                     n >> 1=|n/2.0| 

        算术右移等于除以2向下取整,(-3)>> 1 = -2       ,3 >> 1 = 1

        值得一提的是,“整数/2”在c++中实现为“除以2向零取整”,(-3)/  2  =  -1,3 / 2 = 1

举例

short b = 9115                  0010001110011011 

(9115 >> 1) = 4557          0001000111001101(注意低位越界后舍去了一个1,高位补一0) 

(9115 >> 2) = 2278          0000100011100110  (注意低位越界后舍去了两个1,高位补两0)

short c=-32766(负数符号位为1) 1000000000000010

-32766 >> 1 = -16383                 1100000000000001(注意低位越界后舍弃一个0,高位补1)

-32766 >> 2 = -8192                   1110000000000000(注意低位越界后舍弃0和1,高位补俩1)

二  常用技巧:

 在m位二进制数中,通常称最低为为第0位,从右到左依次类推,最高位为第m-1位

(n >> k) & 1   求n二进制下的第k位是0还是1,若结果为真,是1,若结果为假,是0。因为1的二进制数中只有第0位数是1,其余位数都是0。

n^=1,,即n=n^1,能让n变成与原来相反的数(0或1)

n | (1 << k),能把n的第k位变成1

a^b^b=a

x=x&(x-1)用于消去x的最后一位

三  运算符号优先级:

加减优先级最高,位或优先级最低,从左往右优先级递减

加减    移位      比较大小      位与     异或        位或

+,-    <<,>>    >,<,==,!=        &          ^               |

四    位运算常用技巧

1   判断奇偶性

如果把 n 以二进制的形式展示的话,其实我们只需要判断最后一个二进制位是 1 还是 0 就行了,如果是 1 的话,代表是奇数,如果是 0 则代表是偶数,所以采用位运算的方式的话,代码如下:

  1. if(n&1){ //若n&1==1(为真)
  2. //n是奇数
  3. }
  4. if(!(n&1)){ //若n&1!=1(为假)
  5. //n是偶数
  6. }

2  求a的b次方

本题涉及数论数论数论,在次不详细解释,有兴趣的话可以自己了解.

  1. int pow(int a,int b){
  2. int ans = 1;
  3. while(b != 0){
  4. if(b & 1 == 1){
  5. ans *= a;
  6. }
  7. a *= a;
  8. b = b >> 1;
  9. }
  10. return ans;
  11. }

注意,若数据过大,应改为long  long

举例: b = 13,则 b 的二进制表示为 1101, 那么 a 的 13 次方可以拆解为:

a^1101 = a^0001 * a^0100 * a^1000。

我们可以通过 & 1和 >>1 来逐位读取 1101,为1时将该位代表的乘数累乘到最终结果。

这样时间复杂度是O(logn),比库里的pow函数O(n)快

3  找处未重复的数

数组中,只有一个数出现奇数次,剩下都出现偶数次,找出出现奇数次的。

思路:两个相同的数异或的结果是 0,一个数和 0 异或的结果是它本身,所以我们把这一组整型全部异或一下。也就是说,那些出现了偶数次的数异或之后会变成0,那个出现奇数次的数,和 0 异或之后就等于它本身。

  1. #include<iostream>
  2. using namespace std;
  3. int main(){
  4. int a[9]={4,3,2,2,2,2,5,4,3};
  5. int ans=0;
  6. for(int i=0;i<9;i++){
  7. ans^=a[i];
  8. }
  9. cout<<ans;
  10. return 0;
  11. }

4  用O(1)时间检测整数n是否是2的幂次.

思路:

  • n如果是2的幂次,则:
    1.n>0
    2.n的二进制表示中只有一个1
    一位n的二进制表示中只有一个1,所以使用n&(n-1)将唯一的一个1消去。
    如果n是2的幂次,那么n&(n-1)得到结果为0,即可判断。
  • eg: 8的二进制位1000,7的二进制位0111,8&7==0,所以8是2的幂次
    1. #include<iostream>
    2. using namespace std;
    3. int main(){
    4. int n;
    5. cin>>n;
    6. if(!(n&(n-1))) cout<<"YES";
    7. if(n&(n-1)) cout<<"NO";
    8. return 0;
    9. }

5  计算在一个 32 位的整数的二进制表示中有多少个 1

思路:

由 x & (x-1) 消去x最后一位知。循环使用x & (x-1)消去最后一位1,计算总共消去了多少次即可

  1. #include<iostream>
  2. using namespace std;
  3. int get(int num) {
  4. // write your code here
  5. int count =0;
  6. while(num){
  7. count ++;
  8. num = num&(num-1);
  9. cout<<num<<endl;
  10. }
  11. return count;
  12. }
  13. int main(){
  14. int n;
  15. cin>>n;
  16. cout<<get(n);
  17. return 0;
  18. }

6 快速幂

  1. int ksm(int a,int b)//快速幂函数
  2. {
  3. int ans=1;
  4. a%=Mod;
  5. while(b)
  6. {
  7. if (b&1)
  8. ans=ans%Mod*a;
  9. a=a%Mod*a%Mod;
  10. b>>=1;
  11. }
  12. return ans;
  13. }

7 二进制状态压缩

       二进制状态压缩是指讲一个长度为m的bool数组用一个m位二进制整数表示并存储的方法。利用下列位运算操作可以实现bool数组对应下标元素的存取。

取出整数n在二进制表示下的第k位                              ( n >> k ) & 1

取出整数n在二进制表示下的第0~k-1位(后k位)      n & ( ( 1 << k ) - 1 )

把整数n在二进制表示下的第k位取反                          n ^  (1 << k)

对整数n在二进制表示下的第k位赋值1                        n | ( 1 << k )

对整数n在二进制表示下的第k位赋值0                        n & ( ~ ( 1 << k ) )

      最短Hamilton路径

给定一张 nn 个点的带权无向图,点从 0∼n−1 标号,求起点 0 到终点 n−1 的最短 Hamilton 路径。

Hamilton 路径的定义是从 0 到 n−1不重不漏地经过每个点恰好一次。

输入格式

第一行输入整数 n。

接下来 n行每行 n个整数,其中第 i 行第 j个整数表示点 i 到 j 的距离(记为 a[i,j])。

对于任意的 x,y,z,数据保证 a[x,x]=0,a[x,y]=a[y,x]并且 a[x,y]+a[y,z]≥a[x,z]。

输出格式

输出一个整数,表示最短 Hamilton 路径的长度。

数据范围

1≤n≤20
0≤a[i,j]≤10^7

输入样例:

  1. 5
  2. 0 2 4 5 1
  3. 2 0 6 5 3
  4. 4 6 0 8 3
  5. 5 5 8 0 5
  6. 1 3 3 5 0

输出样例:

18

思路:在任意时刻如何表示哪些点已经被经过,哪些点没有哦被经过?可以使用一个n位二进制数,若其第i位(0<=i<n)为1,则表示第i个点已经被经过,反之未被经过。在任意时刻还需要知道当前所处的位置,因此我们可以使用f[i[[j](0<=i<2^n,0<=j<n)表示“点被经过的状态”对应的二进制数为i,且目前处于点j时的最短路径。总的时间复杂度是 O(n^2*2n)

  1. #include<iostream>
  2. #include<cstring>
  3. #include<algorithm>
  4. using namespace std;
  5. const int N=20,M=1<<N;//220次方,数据最坏n是20
  6. //一个点都有用和不用(10)两种情况,n=20时就是2^20
  7. int f[M][N],w[N][N];//w表示的是无权图
  8. //状态:1.哪些点被用过 2.目前停在哪些点上 所以用二维
  9. //eg:点0 1 4 M=10011(第0个点存在,第1个点存在,第23个点不存在,第4个点存在)
  10. int main(){
  11. int n;
  12. cin>>n;
  13. for(int i=0;i<n;i++)
  14. for(int j=0;j<n;j++)
  15. cin>>w[i][j];
  16. memset(f,0x3f,sizeof(f));//因为要求最小值,所以初始化为无穷大
  17. f[1][0]=0;//因为零是起点,所以f[1][0]=0;
  18. for(int i=0;i<1<<n;i++)//i表示所有的情况,i表示的集合,哪些点是否被用
  19. for(int j=0;j<n;j++)//j表示走到哪一个点
  20. if(i>>j&1)//i的第j位(二进制)是1
  21. for(int k=0;k<n;k++)//k表示走到j这个点之前,以k为终点的最短距离
  22. if(i>>k&1)//第k位有点存在
  23. //f[state][j]=f[state_k][k]+w[k][j];
  24. //state_k表示state除去j后的集合,k表示停在第k个点上,state_k要包含k
  25. f[i][j]=min(f[i][j],f[i-(1<<j)][k]+w[k][j]);
  26. //更新最短距离,枚举state_k这个集合里面的所有点
  27. cout<<f[(1<<n)-1][n-1]<<endl;//表示所有点都走过了,且终点是n-1的最短距离
  28. //位运算的优先级低于'+'-'所以有必要的情况下要打括号
  29. return 0;
  30. }


8 二进制优化递归

从 1∼n这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式

输入一个整数 n。

输出格式

每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围

1≤n≤15

输入样例:

3

输出样例:

  1. 3
  2. 2
  3. 2 3
  4. 1
  5. 1 3
  6. 1 2
  7. 1 2 3

用一个二进制数表示选了哪些数,替代之前的a[20]数组
其中 state |= 1 << (i - 1) 代表状态的改变,选了i这个数
state ^= 1 << (i - 1) 代表状态的还原,还原没选i这个数的状态

  1. #include <iostream>
  2. using namespace std;
  3. int n;
  4. bool vis[20];
  5. void dfs(int pos, int start, int tar, int state) {
  6. if (pos == tar + 1) {
  7. for (int j = 0; j < n; j ++ ) {
  8. if (state >> j & 1) cout << j + 1 << " ";
  9. }
  10. cout << endl;
  11. return ;
  12. }
  13. for (int i = start; i <= n; i ++) {
  14. if (!vis[i]) {
  15. vis[i] = true; state |= 1 << (i - 1);
  16. dfs (pos + 1, i + 1, tar, state);
  17. vis[i] = false;state ^= 1 << (i - 1);
  18. }
  19. }
  20. }
  21. int main() {
  22. cout << endl;
  23. cin >> n;
  24. for (int i = 1; i <= n; i ++ )
  25. dfs(1, 1, i, 0);
  26. return 0;
  27. }

当然,这道题也可以用二进制状态压缩来写

思路:题目要求的结果是2^n个
这2^n个选择情况,对应于一个n位的2进制数的各个位取0或取1的情况。
例中n=3,即
000 -> \n
001 -> 1
010 -> 2
100 -> 3
011 -> 1 2
101 -> 1 3
110 -> 2 3
111 -> 1 2 3

  1. #include <iostream>
  2. using namespace std;
  3. int main() {
  4. int n;
  5. cin >> n;
  6. // state 是每一个状态
  7. for (int state = 0; state < 1 << n; state ++ ) {
  8. // 用指针j遍历二进制数state中的每一位
  9. for (int j = 0; j < n; j ++ ) {
  10. if (state >> j & 1) cout << j + 1 << " ";
  11. }
  12. cout << endl;
  13. }
  14. return 0;
  15. }

9  一道经典题

起床困难综合症:

21 世纪,许多人得了一种奇怪的病:起床困难综合症,其临床表现为:起床难,起床后精神不佳。

作为一名青春阳光好少年,atm 一直坚持与起床困难综合症作斗争。

通过研究相关文献,他找到了该病的发病原因: 在深邃的太平洋海底中,出现了一条名为 drd 的巨龙,它掌握着睡眠之精髓,能随意延长大家的睡眠时间。

正是由于 drd 的活动,起床困难综合症愈演愈烈, 以惊人的速度在世界上传播。

为了彻底消灭这种病,atm 决定前往海底,消灭这条恶龙。

历经千辛万苦,atm 终于来到了 drd 所在的地方,准备与其展开艰苦卓绝的战斗。

drd 有着十分特殊的技能,他的防御战线能够使用一定的运算来改变他受到的伤害。

具体说来,drd 的防御战线由 n扇防御门组成。

每扇防御门包括一个运算 op 和一个参数 t,其中运算一定是 OR,XOR,AND 中的一种,参数则一定为非负整数。

如果还未通过防御门时攻击力为 x,则其通过这扇防御门后攻击力将变为 x op t。

最终 drd 受到的伤害为对方初始攻击力 x 依次经过所有 n 扇防御门后转变得到的攻击力。

由于 atm 水平有限,他的初始攻击力只能为 0到 m 之间的一个整数(即他的初始攻击力只能在 0,1,…,m中任选,但在通过防御门之后的攻击力不受 m 的限制)。

为了节省体力,他希望通过选择合适的初始攻击力使得他的攻击能让 drd 受到最大的伤害,请你帮他计算一下,他的一次攻击最多能使 drd 受到多少伤害。

输入格式

第 1 行包含 2 个整数,依次为 n,m,表示 drd 有 n 扇防御门,atm 的初始攻击力为 0 到 m 之间的整数。

接下来 n 行,依次表示每一扇防御门。每行包括一个字符串 op 和一个非负整数 t,两者由一个空格隔开,且 op 在前,t 在后,op 表示该防御门所对应的操作,t 表示对应的参数。

输出格式

输出一个整数,表示 atm 的一次攻击最多使 drd 受到多少伤害。

输入样例:

  1. 3 10
  2. AND 5
  3. OR 6
  4. XOR 7

输出样例:

1

样例解释

atm可以选择的初始攻击力为 0,1,…,10

假设初始攻击力为 44,最终攻击力经过了如下计算

  1. 4 AND 5 = 4
  2. 4 OR 6 = 6
  3. 6 XOR 7 = 1

类似的,我们可以计算出初始攻击力为 1,3,5,7,9时最终攻击力为 0,初始攻击力为 0,2,4,6,8,10 时最终攻击力为 1,因此 atm 的一次攻击最多使 drd 受到的伤害值为 1。

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<string>
  4. #include<cstring>
  5. using namespace std;
  6. const int N=1e5+10;
  7. pair<string ,int> a[N];
  8. int n,m;
  9. int calc(int bit,int now){// 代表现在运算的是二进制的第几位, now有两种情况,需要运算过后返回
  10. for(int i=1;i<=n;i++){
  11. int x=a[i].second>>bit&1;
  12. //求参数的第k位是0还是1 首先取出第i次运算中的第几位进行二进制运算
  13. if(a[i].first=="AND") now=now&x;
  14. else if(a[i].first=="OR") now=now|x;
  15. else now=now^x;
  16. }
  17. return now;// 现在返回的就是运算过后的二进制位
  18. }
  19. /*本题思路
  20. 如果该位填 1 后,所得到的数大于 m,那么该位填 !u
  21. (已经填好的更高位构成的数值加上1<<k (k表示数的第k位),以后不超过m)
  22. 否则如果该位填 1 后,所得到的数对 n 个数都运算之后,
  23. 结果小于等于该位填 0 后得到的结果,那么为了让剩下能填的数更大,该位填 0
  24. 否则该位填 1
  25. */
  26. int main(){
  27. cin>>n>>m;
  28. for(int i=1;i<=n;i++){
  29. int x;
  30. char str[5];
  31. scanf("%s%d",str,&x);
  32. a[i]=make_pair(str,x);
  33. }
  34. int val=0,ans=0;
  35. for(int bit=29;bit>=0;bit--){
  36. //因为本题中 m 最大是 10 ^ 9,log2(10 ^ 9) = 3log2(10 ^ 3) < 3 * 10 = 30
  37. //所以每次 i 从 29 往后枚举就可以了m最多有29个二进制位,直接从高位到低位枚举每一位
  38. int res0=calc(bit,0);
  39. int res1=calc(bit,1);
  40. if(val+(1<<bit)<=m&&res0<res1) // 满足填1的条件,直接填1
  41. val=val+(1<<bit),ans=ans+(res1<<bit);
  42. else// 不能填1,直接填0
  43. ans=ans+(res0<<bit);
  44. }
  45. cout<<ans<<endl;
  46. return 0;
  47. }

参考《算法竞赛进阶指南》

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号