与:and ,&
或:or , |
非:not ,~
异或:xor , ^
m位2进制数:从右往左依次为0~m-1位
补码
移位运算
左移:1<<n = 2n ; n<<1 = 2n
(低位以0填充,高位越界舍弃)
算术右移:n>>1 = (n/2.0)向下取整
PS:与除法区别,整数除法是向0取整
(在二进制补码表示下把数字同时向右移动,高位以符号位填充,低位越界舍弃)
逻辑右移:高位以0填充,低位越界舍弃
一般编译器默认算术右移
应用:
快速幂:
int quickpow(int a,int b,int p)
{
int res=1%p;
while(b)
{
if(b&1) res=(long long)res*a%p;
a=(long long)a*a%p;
b>>=1;
}
return res;
}
快速乘:
O(log2b)
int quickmul(long long a,long long b,long long p)
{
long long res=1%p;
while(b)
{
if(b&1) res=(res+a)%p;
a=a*2%p;
b>>=1;
}
return res;
}
O(1)
typedef unsigned long long ull;
ull quickmul(ull a,ull b,ull p)
{
a%=p;b%=p;
ull c=(long double)a*b/p;
ull x=a*b,y=c*p;
long long res=(long long)(x%p)-(long long)(y%p);
if(res<0) res+=p;
return res;
}
二进制状态压缩
用一个m位二进制整数表示并存储一个长为m的bool数组
相关二进制位运算操作
操作 | 运算 |
---|---|
取出整数n在二进制表示下第k位 | (n>>k)&1 |
取出整数n在二进制表示下后k位(0~k-1) | n&( (1<<k)-1) |
把整数n在二进制表示下第k位取反 | n xor (1<<k) |
对整数n在二进制表示下第k位赋值1 | n I (1<<k) |
对整数n在二进制表示下第k位赋值0 | n&(~(1<<k)) |
例题
最短Hamilton路径
分析:
Hamilton 路径要求从 0 到 n−1 不重不漏地经过每个点恰好一次。可用一个n位二进制整数表示某路径各点访问状态,每一位表示第i个点是否被访问过,访问过置1,未被访问过置0,故Hamilton 路径即为(1<<n -1),加上在任意时刻当前所处的位置,于是使用F[i][j]表示:路径中各点是否被经过的状态所对应的二进制数为i,且当前处于点j时的最短路径。
为求最短路径,将F数组中所有数初始化一个最大值
- 起点:F[1][0]=0:只经过起点且当前仍位于起点的最短路长度为0
- 终点:F[(1<<n)-1][n-1]:经过所有点且处于终点的最短路
- 任意时刻状态:若((i>>j)&1)=1即当前路径经过j点,且处于j点,必然是刚刚经过j点,枚举其上一时刻的状态:j点必然未被访问,j位为0,假设上一时刻经过k点再从k->j,
即F[i][j]=F[i xor (1<<j)][k]+weight[k][j],在枚举过程中找到F[i][j]最小值
F[i][j]=min{F[i xor (1<<j)][k]+weight[k][j],F[i][j]}
代码:
#include<bits/stdc++.h>
using namespace std;
int F[1<<20][20];
int weight[20][20];
int main()
{
int n;cin>>n;
for(int i=0;i<n;++i){
for(int j=0;j<n;++j)
{
cin>>weight[i][j];
}
}
memset(F,0x3f,sizeof(F));
F[1][0]=0;
for(int i=1;i<(1<<n);++i)
{
for(int j=0;j<n;++j)
{
if((i>>j)&1)
{
for(int k=0;k<n;++k)
{
if((i^(1<<j))>>k&1)
F[i][j]=min(F[i][j],F[(i^(1<<j))][k]+weight[k][j]);
}
}
}
}
cout << F[(1<<n)-1][n-1];
}
起床困难综合征
分析:
本题简化题意后即为:选择0-m之间一个整数,对其进行给定n次的位运算,使结果res最大
首先:位运算的主要特点是在二进制表示下不进位,也即在二进制位表示下的整数X的每一位分别与给定给定数字的该位进行位运算,各位运算之间互不影响,因此我们枚举整数X的每一位两种可能0或1,对每种情况进行n次二进制运算,选出符合题目条件的最优解。
为使结果最大化我们选择从最高位开始枚举,因为m的数据范围,所以枚举30位二进制,29~0
对于每一次枚举:
计算该位取0和1两种情况下,经过n次位运算的结果
当且仅当该位置1时所得整数小于等于m且取1运算优于取0运算时,该位置1,res自加上该位取1运算结果
否则该位置0,res自加上该位取0运算结果
代码:
#include<bits/stdc++.h>
using namespace std;
pair<string,int>op[100005];
int n,m;
int cal(int bit,int now)
{
for(int i=0;i<n;++i)
{
int t=(op[i].second>>bit)&1;
if(op[i].first=="AND") now&=t;
else if(op[i].first=="OR") now|=t;
else now^=t;
}
return now;
}
int main()
{
cin>>n>>m;
for(int i=0;i<n;++i)
{
string s;int x;
cin>>s>>x;
op[i]=make_pair(s,x);
}
int res=0,ans=0;
for(int i=29;i>=0;--i)
{
int res1=cal(i,1),res0=cal(i,0);
if(ans+(1<<i)<=m&&res1>res0)
{
ans+=(1<<i);res+=(res1<<i);
}
else res+=(res0<<i);
}
cout<<res<<endl;
}
lowbit运算
lowbit(n)定义为非负整数n在二进制表示下“最低位的1及其后边所有的0"构成的数值。
lowbit(n)=n&(~n+1)=n&(-n)
lowbit运算配合Hash可以找出整数二进制下所有是1的位
int H[37];
for(int i=0;i<36;++i) H[(1ll<<i)%37]=i;
while(cin>>n)
{
while(n>0)
{
cout<<H[(n&-n)%37]<<' ' ;
n-=n&-n;
}
cout<<endl;
}
一些相关的内置函数(比赛中不建议使用)
int _builtin_ctz(unsigned int x)
int _builtin_ctzll(unsigned long long x)
返回x的二进制表示下最低位的1后面有多少个0
int _builtin_popcount(unsigned int x)
int _builtin_popcountll(unsigned long long x)
返回x的二进制表示下有多少位为1