位运算介绍:
位运算是一种对二进制数的位进行操作的运算方式。
它直接对二进制数的每一位进行逻辑操作,而不考虑整个数的数值大小,一般情况下,位运算中每一位都相互独立,各自运算得出结果(左右移除外)
在计算机科学和编程中,位运算常用于优化算法、位掩码操作、位字段处理等领域;
在竞赛中,位运算经常考察异或的性质、状态压缩、与位运算有关的特殊数据结构、构造题等
注意:位运算只能应用于整数,且一般为非负整数,不能应用于字符、浮点等类型。
位运算是对二进制经行操作的基础,我把它分为位运算基础和位运算技巧:
1,<位运算基础>
位运算基础有与(&),或(|),异或(^),按位取反(~),按位左移(<<),按位右移(>>);
//位运算
//位运算--与&
//只有当两个位都为1时结果位才为1,否则为0;
//位运算--或|
//只要两个位其中有一个为1,结果位就为1,否则为0;
//位运算--异或^
//当两个位不同时,结果位为1,否则为0;
//位运算--按位取反~
//通常用于无符号整数(unsigned)避免符号位取反造成干扰
//即0变1,1变0;
//位运算--按位左移<<
//移动后低位补0;
//左移操作相当于对原数经行乘以2的幂次方的操作。
2,<位运算技巧>
位运算技巧分为判断数字奇偶性,获取二进制数的某一位,修改二进制中的某一位为1,快速判断一个数字是否为2的幂次方,获取二进制位中最低位的1;
//位运算技巧--判断数字奇偶性
//公式--x&1
//如果结果为1,说明是奇数,结果为0说明是偶数
//位运算技巧--获取二进制数的某一位
//公式:x>>i&1
//结果必然为0或1,表示x的二进制表示中的第i位;
//位运算技巧--修改二进制中的某一位为1;
//公式:x|(1<<i)
//将x的第i位或上1,则下x[i ]变为1,其他位上或上0,没有影响。
//快速判断一个数字是否为2的幂次方;
//公式:x&(x-1)
//如果x为2的幂次方,则x的二进制表示中只有一个1,x-1就有很多个连续的1,并且和x的1没有交集,两则与运算一定为0,可以证明其他情况必然不为0;
// x=001000
//x-1=000111
//位运算技巧--获取二进制位中最低位的1;
//公式:lowbit(x)=x&-x
//如果x=(010010),则lowbit(x)=(000010) ;
//常用于数据结构树状数组中。
下面直接开始上栗子:
eg1:lanqiao OJ 1331 二进制中的1
题目分析:
需要判断二进制里面1的个数,方法其实有很多,这里我们展示的是用或运算计算结果,每次循环判断最后一位是不是1,是的话就累加;
看代码:
//位运算-- lanqiaoOJ 1331
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int main()
{
//注意这里的int类型要用unsigned
unsigned int x;cin>>x;
int ans=0;
while(x)
{
if(x&1)
ans++;
//向右移动一位
x>>=1;
}
cout<<ans;
return 0;
}
eg2:位运算--lanqiao OJ 3691 区间域
题目分析:
这道题我觉得其实是有一定难度的,但是蓝桥云给它的难度判定是简单哈哈哈!
直接看题,题目大概意思是说给我们一个区间[l,r],然后我们再把l到r的数进行或运算,思路很简单,题目数列给的数我们可以把它看成一个32位的二进制数,肯定是这32位1和0来经行或运算,这里我们可以联想到前缀和,从第一位开始,若相加大于0,这或运算之后一定等于1;
看代码;
//位运算--lanqiao OJ 3691 区间域
//需要用到几个前面讲到的位运算技巧;
//
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int a[N];
//前缀和;
int prefix[35][N];
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n,q;cin>>n>>q;
int a[n+1];
for(int i=1;i<=n;i++)
cin>>a[i];
for(int w=0;w<=30;w++)
{
for(int i=1;i<=n;i++)
{
prefix[w][i]=prefix[w][i-1]+(a[i]>>w&1);
}
}
while(q--)
{
int l,r;
cin>>l>>r;
int ans=0;
//w代表的是多少第多少位数
for(int w=0;w<=30;w++)
{
//r---l-1注意一下
//(1<<w)代表2的w次幂;
ans+=(1<<w)*((prefix[w][r]-prefix[w][l-1])?1:0);
}
cout<<ans<<endl;
}
return 0;
}
eg3:lanqiao OJ 3400 异或森林
题目解析:
这道题就比较难了,他不仅运用到位运算技巧,同时还需要你有一定的逻辑思考能力;
我们一层一层的分析一下题目:
首先如何判断一个数的因数是不是偶数个:
用图画画的,有一点简单,但是我们其实很明显可以发现,一个数的因数在√x(x代表这个数)两侧是成对存在的,所以我们只需要判断√x是不是整数就行了,再换位思考一下,如果√x是整数的话,不就代表这个数是可以开平方根吗,所以我们得出结论,若因数个数为偶数,则该数为完全平方数;即1,4,9,16等等;
本题题意是要找出因数个数为偶数的数,也就是找出非完全平方数,但是非完全平方数肯定是远远多与完全平方数的,找多不如找少,所以我们何不换位思考一下,找出完全平方数,然后用总数减去该数,不就得出答案了吗;
下面看代码:
//位运算--lanqiao OJ 3400 异或森林
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+9;
int a[N];
int perfix[N];
int ant[N];
int main()
{
//数组长度n;
int n;
cin>>n;
//输入数组
for(int i=1;i<=n;i++)
cin>>a[i];
//这里同样运用了前缀和的原理:
for(int i=1;i<=n;i++)
{
perfix[i]=perfix[i-1]^a[i];
}
ant[0]=1;
//总数(子区间的数量)
int ans=n*(n+1)/2;
for(int i=1;i<=n;i++)
{
//异或运算不改变长度,题目给出:a[i]最大为10^4,a[i]^a[i+1]<=2*10^4;2*10^4开方约运算为200;
for(int j=0;j<=200;j++)
{
int sq=j*j;
//这里很有意思,这里用了异或计算的一个技巧:
//a^b=c;
//a^b^b=c^b;
//a=c^b;
//所以同理,这里我们就可以perfix[i]^sq看作是完全平方数;
ans-=ant[perfix[i]^sq];
}
//这里用的很妙,自行体会;
ant[perfix[i]]++;
}
cout<<ans;
return 0;
}
位运算的介绍就暂时到这里吧