前言
本文应室友间的交流学习而生
如果有什么漏洞恳请大家指出
基础位运算
& 同1取1
1) n & 1 // 判断奇偶性
2) n & (n - 1) // 去除最低位 1 用途:求 1 的个数, 判断是否为 2 的整数次幂
3) n & -n // lowbit 取出最低位 1 00...
4) n >> k & 1 // 取出 n 的第 k 位
5)n & ((1 << k) - 1) // 取出前 k 位
6) n & (~(1 << k)) // 清零第 k 位
| 同0取0
1)n | 1 << k // 赋值 n 的第 k 位为 1
~取反
~0 = 1; ~1 = 0
^同0异1
性质 : a ^ a = 0 ;
a ^ 0 = a ;
a ^ b = b ^ a ;
a ^ b ^ c = (a ^ b) ^ c = a ^ (b ^ c) ;
a ^ b ^ a = b ;
应用 : 取反
1) n ^ 1 // 取反 n 的最低位
2) n ^ 1 << k // 取反 n 的第 k 位
<< 和 >>
1)1 << n // 2 的 n 次幂
2) n << k // n * (2 的 k 次幂), 最低位后面 添 0
3) n << k + 1 // 最低位后面 添 1
4) n >> k // n / (2 的 k 次幂), 去除最低位
如果你很勤劳那么快背完它 如果你像我一样懒那就背完大字部分(知道各个位运算符神马意思就行)就跳转至第三个大标题
位运算常用技巧
超好用
n >> k & 1 // 取出整数 n 在二进制下的第 k 位
n & ((1 << k) - 1) // 取出整数 n 在二进制下的前 k 位
n ^ 1 << k // 把整数 n 在二进制下的第 k 位取反
n | 1 << k // 对整数 n 在二进制下的第 k 位赋值 1
n & (~(1 << k)) // 对整数 n 在二进制下的第 k 位赋值 0
n & (n ^ (n-1)) //去掉右起第一个1的左边
(n ^ (n+1)) >> 1其余取右边连续的1
更多
(<<shl ; >> shr ; | or ; ^ xor ; & and ; ! not)
功能 | 示例 | 位运算 |
去掉最后一位 | (101101->10110) | x shr 1 |
在最后加一个0 | (101101->1011010) | x shl 1 |
在最后加一个1 | (101101->1011011) | x shl 1+1 |
把最后一位变成1 | (101100->101101) | x or 1 |
把最后一位变成0 | (101101->101100) | x or 1-1 |
最后一位取反 | (101101->101100) | x xor 1 |
把右数第k位变成1 | (101001->101101,k=3) | x or (1 shl (k-1)) |
把右数第k位变成0 | (101101->101001,k=3) | x and not (1 shl (k-1)) |
右数第k位取反 | (101001->101101,k=3) | x xor (1 shl (k-1)) |
取末三位 | (1101101->101) | x and 7 |
取末k位 | (1101101->1101,k=5) | x and (1 shl k-1) |
取右数第k位 | (1101101->1,k=4) | x shr (k-1) and 1 |
把末k位变成1 | (101001->101111,k=4) | x or (1 shl k-1) |
末k位取反 | (101001->100110,k=4) | x xor (1 shl k-1) |
把右边连续的1变成0 | (100101111->100100000) | x and (x+1) |
把右起第一个0变成1 | (100101111->100111111) | x or (x+1) |
把右边连续的0变成1 | (11011000->11011111) | x or (x-1) |
其实本蒟蒻都没背过 平时做题就打开记事本 Ctrl+c+v 考试主打的就是个乱猜
关于如何做题(考试)
其实真要说怎么做题 e...这个板块纯属胡扯
首先呢你需要判断这个题该不该使用位运算:
1.位运算在实战中的应用,大概率是搜索和状态压缩DP方面的题目(我可爱的室友们知道往谁写的文章上看了吧)
2.或者说你把能想到用的都想了一遍还是不知道用啥
3.时间超限TLE 有很多数学运算 主要用于卡常
其次呢你要考虑怎么用:
这边呢建议用位运算的时候多新建一个源代码试试规律主要靠运气拿分
当然 像异或这种常用常考的还是需要学习的 So下面有一个异或专区
最后呢就是练习练习再练习:
我说这句话有着承上启下的作用不过分吧
练习题目:
众所周知的一句话 实践出真知 下面我们来看几道题
首先是
1.给定一个包含从 1、2、...、n 中取的 n-1 个不同数字的数组,找到数组中缺少的数字。例如,给定 nums = [0, 1, 3] 返回 2。(你的良知告诉你你不可以通过数学来水到此题
思路:
因为限制了不能使用数学水题,自然我们就只能使用它的好盆友位运算
心里默念一下几个位运算的实现 & 同1取1 | 同0取0 ~取反 ^同0异1 << >>左移右移
很明显啊想找缺失的数是肯定不能选处理后相同的运算符(歪门邪道) 左移右移更不可能
所以考虑取反与异或。。。
根据经验先重新打开一个源代码or草稿本于是我得到以下结论:
任何数字和0异或都是他本身,
任何数字和他本身异或都是0;
所以eg. 0,1,2,3,4,6
1~6的异或为: 0^1^2^3^4^5^6
这个数组异或为:0^1^2^3^5^6
这两个结果进行异或得到的就是缺少的数字:
0^1^2^3^4^5^6 ^ 0^1^2^3^5^6 = 0^0^1^1^2^2^3^3^5^5^6^6^4 = 0^4 = 4
代码如下:
#include<bits/stdc++.h>
using namespace std;
int n,a[100000000],ret;
int main(){
cin>>n;
for(int i=1;i<n;i++){
cin>>a[i];
ret^=i;
ret^=a[i];
}
ret=ret^n;
cout<<ret;
return 0;
}
是不是很简单?那我们趁热打铁
再来一道
吉姆有一个平衡和N个砝码。(1≤N≤20)天平只能判断不同侧面的物体是否重量相同。
砝码可以任意放在左侧或右侧。
请告知天平是否可以测量重量为M的物体。
第一行是整数 T(1≤T≤5),表示 T 测试用例。
对于每个测试用例:第一行是N,表示权重的数量。
第二行是 N 个数字,第 i 个数字 wi(1≤wi≤100) 表示第 i 个
权重的权重是 wi。第三行是数字M.M是被测量物体的重量您应该输出“Yes”或“No”。
思路:
因为砝码只有20
个,所以我们可以考虑一下枚举子集,枚举出砝码所有可以组成的情况,并且标记一下,然后就可以O(1)的时间复杂度查询了
如何枚举子集?
假设我们现在有5个小球,上面分别标号了0,1,2,3,4代表这些小球的权值,现在要像你求出这些小球的权值可以组成的所有情况。
我们用二进制的思维来考虑这个问题,因为有5个小球,所以我们用5个比特位来分别标记小球存在还是不存在,对于这样一种情况,比如我们现在要选择3个小球,分别是0,3,4号小球,那么我们用二进制1表是当前的小球存在,用0表示当前小球不存在
二进制下标 | 4 | 3 | 2 | 1 | 0 |
二进制 | 1 | 1 | 0 | 0 | 1 |
小球状态 | 存在 | 存在 | 不存在 | 不存在 | 存在 |
我们可以用5个比特位来表示这种情况,如果小球全部选择的话那么二进制表示就是11111,二进制的11111转化为十进制数字就是31,这个数字正好就是
,那么我们可以用从0~()
这些数表示完所有的选取状态(因为这个范围内的二进制数情况正好包括了这些选取状况).
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define inf 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
int a[100001],vis[200001];
int main() {
int t,n,m,x;
scanf("%d",&t);
while(t--) {
mem(vis,0);
scanf("%d",&n);
for(int i=0; i<n; i++) {
scanf("%d",&a[i]);
}
//枚举子集
for(int i=0; i<(1<<n); i++) { //二进制有n位
int cur=0;
for(int j=0; j<n; j++) {
if(i&(1<<j))
cur+=a[j];
}
vis[cur]=1;
for(int j=0; j<n; j++) {
if(cur-a[j]>=0)
vis[cur-a[j]]=1;
}
}
scanf("%d",&m);
while(m--) {
scanf("%d",&x);
if(vis[x])cout<<"Yes";
else cout<<"No";
}
}
return 0;
}
我们继续
!
思路:
-
将序列(数组)等价于集合,对于每个子序列,它要么为空,要么也在集合中(因为唯一性)
-
如果两个互相补充的子集看做一组,那么每组中至少一个集>=h/2,另一个集<=h/2(h为元素权值的和)
-
也就是说,所有子序列的权值在⌊h/2⌋左右两边是对称的。
-
不考虑空序列所以这个序列的中位数就是权值小于等于h/2的子集中权值最大的,所以可以用DP来解(01背包)
-
不算时间复杂度就知道肯定超时,可以用bitset优化。因为dp[i]=max(dp[i],dp[i-ai]),所以左移就可以做到这个目的
-
bitset一定要算空间大小!!!否则你将收获彩虹题与渐变分(不要我问怎么知道的)
-
左移可以做到这个目的 eg:
101011(43),ai=3,101011<<3=101011000(344),101011000|101011=10111011(187) 就相当于用ai更新了一次。
代码如下:
#include <bits/stdc++.h>
using namespace std;
bitset<1000001>b;
int n,a[2001],h,ans;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
h+=a[i];
}
b[0]=1;
for(int i=1;i<=n;i++){
b|=(b<<a[i]);
}
ans=h+1>>1;
while(b[ans]<=0)
ans++;
cout<<ans;
return 0;
}
最后一练
监狱有连续编号为 1…N的 N 个房间,每个房间关押一个犯人,有 M 种宗教,每个犯人可能信仰其中一种。如果相邻房间的犯人的宗教相同,就可能发生越狱,求有多少种状态可能发生越狱。
思路:
(逆序思维):求相同不太好求,求不同较为好求
相同的可能性+不同的可能性=总可能性
相同的可能性=总可能性-不同的可能性
假设我们先选1号房间 1号房间就有m种可能
然后选2号房间 就有 m−1 种可能(去掉1号房间)
接着是3号
仍然是 m−1 种(去掉2号房间,不去一号房间)
···
最后是n号房间 是 m−1 种(去掉 m−1 号房间)
So...不同的总数就是 m∗(m−1)∗(m−1)∗···∗(m−1) (共n-1个m-1)
即 而总和则是
相同的个数就是后者减去前者
但数据范围。。。所以用快速幂(位运算版)呀!
快速幂报怎么写的出门左转上百度去
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,mod=1e5+3;
long long q(long long a,long long p) {
long long res=1;
while(p) {
if(p&1) res=(res*a)%mod;
p>>=1;
a=(a*a)%mod;
}
return res;
}
long long n,m;
int main() {
cin>>m>>n;
m%=mod;
long long ans;
ans=(q(m,n)-m*q(m-1,n-1))%mod;
cout<<((ans+mod)%mod);
return 0;
}
异或专场
异或的性质
- 交换律:a ^ b = b ^ a;
- 结合律:a ^ b ^ c = a ^ ( b ^ c )
- 对于任何数x:都有x ^ x = 0, x ^ 0 = x;
- 自反性:a ^ b ^ b = a ^ 0 = a
简单速记应用
交换两个元素的值
int a , b;
cin >> a >> b;
a = a ^ b;
b = b ^ a;
a = a ^ b;
a ^ b = (~a & b) | (a & ~b)
使用异或判断一个数的奇偶性
题目分析: 根据前面的结论,异或1的结果只有两种,要么为奇数,要么为偶数;由这一结论我们可以让当前数异或1之后的结果去取余2,就能得出结果。
找出1-n中唯一成对的数,不能使用辅助空间,每个数据只能访问一次
题目分析: 根据前面的结论,异或自己的结果为0,异或0的结果为自己,所以我们只需要异或1-n,再拿这个结果去异或1-n包含重复数据的数
我们使用异或来判断一个二进制数中1的数量的奇偶例如:求 10100001
中1的数量是奇数还是偶数; 答案:1 ^ 0 ^ 1 ^ 0 ^ 0 ^ 0 ^ 0 ^ 1 = 1
,结果为 1
就是奇数个1,结果为 0
就是偶数个1;
后记
因能力有限所以内容略显粗糙与浅显 望谅解蒟蒻orz orz orz OTZ OTZ OTZ
还有就是我想要一个小小的好评...(戳戳戳戳戳