目录

位运算的常用几种方法

lowbit函数

原码反码补码lowbit原理的补充

二进制的相关概念

位运算的符号

按位与&

按位或|

按位异或^

交换律结合律

按位取反

左移右移

规律记忆

知识巩固

真题改编


首先感谢2022.6.13杨超凡giegie带来的位运算算法讲解 

位运算的常用几种方法

lowbit函数

int lowbit(int x){return x&(-x);}

lowbit函数可以用于查找一个数字的二进制表示形式当中的最右边的1,lowbit函数返回的值为x的最右边1所代表的十进制整数。例如10的二进制形式是1010,lowbit(10)返回的二进制形式也就是10,转化为十进制也就是2。因此,lowbit函数常常用来解决求某个十进制整数当中含有多少个1的问题

原码反码补码lowbit原理的补充

-x = ~x + 1

我们假设x为10,则可获得其原码反码补码

原码即为其二进制形式:1010

反码即为其二进制形式的相反:0101

补码则为反码加一:0110

那么lowbit函数的实质我们也就可以进行参透了

首次对于lowbit函数进行运算:(1010)&(0110)= 10(二进制形式)

减去第一次运算的结果之后,(1010) -> (1000) ,而后使用第二次lowbit运算

(1000) & (0111 + 1) -> (1000) & (1000) -> 1000(二进制形式)

以上即为lowbit函数原理的理解补充式子

具体应用

二进制当中1的个数

题目描述

给定一个长度为n的数列,请你求出数列中每个数的二进制表示中1的个数。

输入格式

第一行包含整数n。第二行包含n个整数,表示整个数列。

输出格式

共一行,包含n个整数,其中的第i个数表示数列中的第i个数的二进制表示中1的个数。

数据范围

1<=n<=100000

0≤数列中元素的值≤10^9

输入样例

5
1 2 3 4 5 

输出样例

1 1 2 1 2

源代码

#include <iostream>
using namespace std;
const int N = 1000000+10;
int a[N];
int lowbit(int x)
{return x&(-x);
}
int main()
{int n;cin >> n;for(int i = 1;i <= n;i ++ )cin >> a[i];for(int i = 1;i <= n;i ++ ){int ans = 0;while(a[i]){a[i] -= lowbit(a[i]);ans ++ ;}cout << ans <<' ';}return 0;
}

求取数的二进制的某一位的值与二进制枚举

求取n的二进制形式第k位的值:n>>k&1

例如10的二进制形式是1010

int a = n >> k & 1;a的值为0

int b = n >> k & 1;b的值为1

int c = n >> k & 1;c的值为0

int d = n >> k & 1;d的值为1

本方法常常与二进制枚举连用

用二进制枚举的情况往往都能够用DFS来解决,二者的共同特点都是暴力且有两个分支。在二进制枚举过程当中。用0和1来代表两种不同的情况,二进制枚举的模板如下所示

for(int i = 0;i < (1ll >> n);i ++ )
{for(int j = 0;j < n;j ++ ){int num = i >> j & 1;//分情况进行相应的处理}
}

具体应用 

凑数

题目描述

给定n个正整数,请判断能否用这n个数且每个数只能用一次,凑出来s
ps:建议使用二进制枚举

输入格式

第一行两个数n,s
第二行n个正整数

输出格式

如果能凑出输出YES,否则输出NO

样例输入

5 12
2 4 6 8 10

样例输出

YES

源代码 

#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1000000+10;
int a[N];
int main()
{ll n,s,sum,flag;cin >> n >> s;for(int i = 0;i < n;i ++ )cin>>a[i];for(int i = 0;i < (1ll << n);i ++ ){sum = 0,flag = 0;for(int j = 0;j < n;j ++ ){int num = i >> j & 1;if (num == 1)sum += a[j];}if(sum == s){flag = 1;  break;}}if(flag == 1)cout<<"YES";else if(flag == 0)cout<<"NO";return 0;
}

二进制的相关概念

只有0和1,逢二进一

二进制枚举的意义:利用二进制进行状态枚举,对于每一次的状态枚举0和1分别代表不同的状况

位运算的符号

按位与&

1&1=1 1&0=0 0&1=0 0&0=0          &&逻辑且常用于条件判断

例如6&7 实际为 110 和 111 的运算,根据按位与的运算法则结果为 110 也就是6

口诀为全一则一

常常用来判断奇偶,n&1

任何数按位与运算1结果是其本身

按位或|

1|1=1 1|0=1 0|1=1 0|0=0      ||逻辑或常用于条件判断

例如6|7 实际为 110 和 111 的运算,根据按位或的运算法则结果为 111 也就是7

口诀为有一则一

任何数按位或运算0结果是其本身

按位异或^

1^1=0 1^0=1 0^1=1 0^0=0

例如6^7 实际为 110 和 111 的运算,根据按位异或的运算法则结果为 001 也就是1

口诀为同零异一

任何数按位异或运算0结果是其本身

交换律结合律

按位与、按位或、按位异或遵守交换律与结合律

按位取反

按位取反可以求取一个数的负数减一,即~x=(-x-1)

左移右移

因c++运算符的重载,插入流也有着左移的功能,输出流也有着右移的功能

n>>k n除2的k次方   n<<k n乘2的k次方

规律记忆

同一个数对自身进行按位与和按位或都是其本身,对自身按位异或运算为0

a|(b&c) = (a|b)&(a|c)    a^(b&c) = (a^b)&(a^c)    a&(b^c) = (a&b)^(a&c)

根据交换率和结合律可自行继续推演,此处显示的公式仅仅为示例

知识巩固

麻烦的运算(按位&的规律题)

题目描述

小明刚刚学习了位运算中按位与的运算法则,想计算从n到m之间的数连续进行与运算之后的结果是多少,但是小明觉得一个一个的计算实在是太麻烦了,你能帮助他解决吗?

输入格式

两个整数n和m

输出格式

输出区间[n,m]之间的数进行与运算的结果

数据范围

0<=n<=m<=1e9

样例输入

11 13

样例输出

8

源代码 

当从L到R连续进行按位与运算的话会使时间超限,因此我们需要找规律进行优化,显而易见的是L是一定小于等于R的,根据十进制整数转换为二进制整数的规律来看,当L的二进制形式位数只会小于等于R的二进制形式位数。因此问题简化为三个分支进行讨论:

若L与R的二进制位数不相等时,证明从L到R一定发生了进位,一旦有进位情况的发生那么就意味着在R的二进制形式下匹配不到L的二进制的位数全为0,而能够匹配到的也因为从L到R之间的连续按位与运算也为0,因此此情况只有一个答案那就是0

若L与R的二进制位数相等时

当L完全与R相等,根据运算律可以得到一个数对自身按位与运算还是那个数本身,因此答案为L(R)

当L与R不相等时,从最高位查找关键点(A[i] != B[i]时的下标i),关键点也就是发生进位的点,因此后面的数字全部换为0,再将处理过后的二进制数转化为十进制即为最终答案

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
int main()
{//数据记得开long long,因最后一个测试点是十位数 ll l,r;cin>>l>>r;//如果二者相等那么等于其本身 if(l == r){cout<<l;//或者cout<<r; return 0;}//A,B用于装l,r转化为二进制之后的字符串 vector<int> A,B;//转换方法为不断对2取余再取倒 while(l > 0){int num = l % 2;l = l / 2;A.push_back(num);}reverse(A.begin(),A.end());//注意取倒 while(r > 0){int num = r % 2;r = r / 2;B.push_back(num); }reverse(B.begin(),B.end());//注意取倒//当A的长度小于B的时候证明一定发生了进位,因此答案为0 if(A.size() < B.size())cout<<'0';//二者长度相等,则从最高位开始查找关键点 else if(A.size() == B.size()){//关键点即为A[i] ! = B[i]是idx为i的坐标点//说明i的下标位置发生了进位,因此从i往后的数全部为0 vector<int> C;int flag = 0;for(int i = 0;i < A.size();i ++ )//或者i < B.size()也可 {if(A[i] == B[i] && flag == 0)C.push_back(A[i]);//未到关键点时正常存储 else if( A[i] != B[i] && flag == 0)//关键点时存0并打标记 {flag = 1;C.push_back(0);}else if(flag == 1)C.push_back(0);//关键点之后利用标记继续存0 }//将C中存储的ans的二进制转换为十进制 ll ans = 0,w = 1;for(int i = C.size() - 1;i >= 0;i -- ){ans += w * C[i];w = w * 2;}cout<<ans;return 0;}
}

公式计算(交换律与结合律的推导)

题目描述

对于n个数a1,a2,a3.....an,计算公式[(a1&a1)|(a1&a2)|(a1&a3)|...(a1&an)]^[(a2&a1)|(a2&a2)|(a2&a3)...|(a2&an)]^...[(an&a1)|(an&a2)|(an&a3)...|(an&an)]

输入格式

第一行一个整数
第二行n个整数

输出格式

输出公式的结果

样例输入

2
1 1

样例输出

0

源代码 

若是暴力运算会使时间超限,所以必然存在优化的方法,也就是对于公式的简化。

根据结合律将每一个按位异或运算的式子看作一个个模块

那么可以发现第一个模块为a1&(a1|a2|a3|a4|a5......|an)

由此推出第n个模块为an(a1|a2|a3|a4|a5......|an)

好了现在由于(a1|a2|a3|a4|a5......|an)太长我们把它简化为s1

那么公式经过第一次简化过后我们可以发现(a1&s1)^(a2&s1).......(an&s1)

再进行合并可得s1&(a1^a2^a3.........^an)

我们再把(a1^a2^a3.........^an)简化为s2,因此整个公式我们就进行简化完毕了

ans=s1&s2; s1=(a1|a2|a3|a4|a5......|an); s2=(a1^a2^a3.........^an);

#include <iostream>
using namespace std;
const int N = 1000000+10;
int a[N];
typedef long long ll;
int main()
{int n;cin >> n;for(int i = 1;i <= n;i ++ )cin>>a[i];ll head = a[1];ll s = head;ll s1 = s,s2 = s;for(int i = 2;i <= n;i ++ ){s1 = (s1)|(a[i]);s2 = (s2)^(a[i]);}ll ans = (s1)&(s2);cout << ans;return 0;
}

真题改编

改编自蓝桥杯省赛李白打酒题目

汽车加油

题目描述

陈老师经常开车大街上行走,假设刚开始油箱里有T升汽油,每看见加油站陈老师就要把汽油的总量
翻倍(就是乘2);每看见十字路口气油就要减少1升;最后的时候陈老师的车开到一个十字路口,然
后车就没油了------就熄火了,陈老师好痛苦啊~~~!
然后他就开始回忆,一路上一共遇到n个加油站,m个十字路口,问造成这种惨烈的境遇有多少种可能?

输入格式

三个正整数T,n,m;

输出格式

输出有多少种可能

数据范围

1<=T=100
0<n+m<=18

样例输入

1 5 10

样例输出

10

源代码 
幂集:所谓幂集(Power Set), 就是原集合中所有的子集(包括全集和空集)构成的集族。可数集是最小的无限集; 它的幂集和实数集一一对应(也称同势),是不可数集。 不是所有不可数集都和实数集等势,集合的势可以无限的大。如实数集的幂集也是不可数集,但它的势比实数集大。 设X是一个有限集,|X| = k,根据二项式定理,X的幂集的势为2的k次方.

#include <iostream>
using namespace std;
typedef long long ll;
int main()
{ll t,n,m,ans = 0;cin >> t >> n >> m;//总共会有m+n个元素,也就是2^(m+n)-1种情况 for(int i = 0;i < (1ll << (n + m - 1));i ++ )//开始对于相应幂集的情况进行暴力枚举 {int sum = t;//临时变量转存t int sumn = 0,summ = 0;//存路过加油站和路过十字路口的次数  for(int j = 0;j < n + m - 1;j ++ )//对于前m+n-1位的情况进行二进制枚举 {int num = i >> j & 1;if(num == 1)//加油站 {sum = sum * 2;//油箱翻倍 sumn ++ ;//加油站次数加一 }else if(num == 0)//十字路口 {sum -= 1;//油减去一升 summ ++ ;//十字路口次数加一 }}//当在前m+n-1个情况当中,有n次路过加油站,有m-1次路过十字路口,且油箱仅有一升油,那么就意味着最后一次必是十字路口且油耗尽 if(sum == 1&&sumn == n&&summ == m - 1)ans ++;} cout << ans;//输出答案 return 0;
}

巧妙算法之位运算的应用相关推荐

  1. Java数据结构和算法:位运算

    位运算因为是CPU直接支持的操作指令,也是基于二进制的操作,所以具有相当高的效率,在一些场合,合理应用位运算将具有很高的性能.通常在一些加密算法,图型算法中都会使用到位运算. 移位运算符 运算符 含义 ...

  2. 算法:位运算加减乘除

    今日做leetCode的371题算法题,用位运算计算两个数之和.借此机会,将位运算的加减乘除做一整理. package Algorithm;public class Code371 {public s ...

  3. 算法优化——位运算的优化技巧

    1 致谢 感谢赵老师的讲授! 2 前言 今天在学习算法与数据结构~ 赵老师给我们讲了一点位运算的使用技巧,感觉还挺有意思的,这里记录一下~ 3 位运算的优化技巧 n&1判断奇偶性 使用n&am ...

  4. C++ 算法篇 位运算

    学习目标 1. 理解与掌握 C++ 中的位运算.  2. 灵活应用位运算优化程序. 任何信息在计算机中都是采用二进制表示的,数据在计算机中是以补码形式存储的,位运算就是直接对整数在内存中的二进制位进行 ...

  5. 【基本算法】 位运算:二进制状态压缩

    二进制状态压缩 二进制状态压缩,是指将一个n位的 bool 数组用 n 位的二进制数表示的方法. OP 运算 取出 n 在二进制表示下的第k位 (n >> k) & 1 取出 n ...

  6. 【算法】位运算——左移右移

    1.左移运算符(<<) 定义:将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0). 设 a=1010 1110,a = a<< 2 将a的二进制位左移2位 ...

  7. 剑指offer 算法 (位运算)

    题目描述 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. class Solution { public:int NumberOf1(int n) {//方法一:(包含二进制负数不成 ...

  8. 一篇搞定位运算——java位运算详解

    java位运算详解 前言 一.位运算符 &:按位与 |:按位或 ~:按位非 ^:按位异或 <<:左位移运算符 >>:右位移运算符 <<<:无符号右移运 ...

  9. java位运算原理及应用

    日常开发中位运算不是很常用,但是巧妙的使用位运算可以大量减少运行开销,优化算法.举个例子,翻转操作比较常见,比如初始值为1,操作一次变为0,再操作一次变为1.可能的做法是使用三木运算符,判断原始值为1 ...

  10. 有趣的二进制—高效位运算

    2019独角兽企业重金招聘Python工程师标准>>> 上一篇<有趣的二进制>我们讲到二进制的一些基础知识,但没有讲到位运算,有同学大呼不过瘾,那这一篇主要讲解下位运算的 ...

最新文章

  1. Windows下Python 3.6 安装BeautifulSoup库
  2. HTTP中Accept与Content-Type区别
  3. solidworks activator未响应_SolidWorks之初识工程图
  4. AI 引领产业变革:相关岗位起薪33w
  5. 从ffmpeg源代码分析如何解决ffmpeg编码的延迟问题(如何解决编码 0 延时)
  6. 国内创业活动平台,创业赛事报名网站
  7. 火狐(FireFox)
  8. Opportunity Sales org F4 value help data source
  9. 鼠标的计算机基础知识,2、使用鼠标--电脑基础知识
  10. input发送a.jax_Java EE 7 / JAX-RS 2.0:具有自定义HTTP标头的简单REST API身份验证和授权...
  11. 熵值法确定权重(matlab附代码)
  12. C++语言实现hello world代码
  13. 浮动元素遇到标准流元素 会发生转角遇到爱
  14. 海思EC6108V9盒子DIY固件打包方法
  15. make~warning: Clock skew detected. Your build may be incomplete.
  16. 笔记本电脑亮度调节按钮不起作用
  17. 2017年下半年中学综合素质问答题
  18. model(**inputs)备忘
  19. 关键帧提取——帧差法提取关键帧(2)
  20. Android 原生webview传递header前端H5如何接收

热门文章

  1. 山东理工ACM【1009】Elevator
  2. 触发器(数据库原理术语)
  3. 为什么我的背景图片加载不出来
  4. libdmtx结合OpenCV识别DataMatrix二维码
  5. 彻底理解Linux的各种终端类型以及概念
  6. 树莓派之安装Vulkan
  7. 微信突破版本限制永久设置透明/半透明头像
  8. USB转串口电路之CH340G
  9. icon、png网页开发中所需要的小图标
  10. uva10827 - Maximum sum on a torus(最大子矩阵和变形)