一. 概念:位运算是指按二进制进行的运算,只能对整形树进行操作。
二. 基本运算符:
(1) & 与:两个相应的二进制位都为一,则该位结果为1,否则为0 如:0111 & 1011 = 0011
(2)| 或:两个相应的二进制位有一个为1,则该位结果为1,否则为0 如: 0111 | 1011 = 1111
(3)^ 异或:两个相应的二进制位相同则为零,否则为1 如: 0111^1011 = 1100
(4)~ 取反 : 一元运算符,将二进制位取反,即1变为0,0变为1 如:~0111 = 1000
(5)<<左移运算符:n<<k,将n的二进制左移k位,低位补0(n*2^k) 如:0111<<1 = 1110
(6)>>右移运算符:n>>k,将n的二进制右移k位,低位被舍弃,高位(左边)补0(n/2^k) 如:0111>>1 = 0011
注:其中~的结合方向自右至左,且优先级高于算术运算符,其余运算符的结合方向都是自左至右,且优先级低于关系运算符。所以在运算时,注意括号的利用。
三. 二进制奇技淫巧
1.集合应用
(1)在集合中: &表示交集,| 表示并集, ^ 表示对称差集,(1<<n)-1表示n的全集
(2)s&(1<<i) 是否为0表示 i是否是子集s中的一个元素(全集从0~n-1); 表示i+1是否是子集s中的一个元素(全集从1~n)
(3)for(int k = (n-1)&n;k;k = (k-1)&n) 从集合角度取n的所有子集
(4)求n个元素的所有子集
while(cin>>n) { for(int i = 0; i<(1<<n); i++)//枚举所有子集 { for(int j = 0; j<n; j++)//枚举所有元素判断是否在子集里 { if(s&(1<<i))printf("%d",i+1); } printf("\n"); } } return 0;
(5)求集合或者现有子集的所有子集
while(cin>>n){ for(int i = 0;i<(1<<n);i++){//所有子集 cout<<"集合 "<<i<<" 的所有子集 :"<<endl; for(int k = i;k>0;k = (k-1)&i){//所有 现在子集 的 所有子集 for(int j = 0;j<n;j++){//判断元素 if(k&(1<<j))cout<<j+1; } cout<<endl; } } }
(6)求不相邻元素的所有集合
for(int i = 0;i<(1<<n);i++){ if((i>>1)&i)continue;//i里面含有相邻元素,退出 //对集合的处理 }
(7)必须含有n个指定位置集合 的所有集合
while(cin>>n){ int m,k; int sum = 0; cin>>m; while(m--){//求最小指定集合 cin>>k; sum = sum|(1<<(k-1)); } for(int i = sum;i<(1<<n);i = (i+1)|sum){//在sum基础上求所有集合 //操作 } }
(8)必须不含有n个指定位置集合 的所有集合
while(cin>>n){ int m,k; int sum = 0; cin>>m; while(m--){ cin>>k; sum = sum|(1<<(k-1)); } sum = sum^((1<<n)-1);//^取最大集 //cout<<sum<<endl; for(int i = sum;i>=0;i = (i-1)&sum){ cout<<i<<endl; if(i==0)break;//一定要有,不然出现负数的位运算导致循环不断进行 } }(9) 找出二进制中恰好含有 k个1的所有数
for (int mask = 0; mask < 1 << n; ) {
int tmp = mask & -mask;
mask = (mask + tmp) | (((mask ^ (mask + tmp)) >> 2) / tmp);
}
2.其他应用
(1)O(1)时间检查n是否为2的幂次
while(cin>>n){ if((n&(n-1))==0)cout<<"yes"<<endl; else cout<<"no"<<endl; }(2)一个数组里,所有的数字都出现两次,只有一个数字出现一次,找出这个数字
while(cin>>n){ int sum,a; for(int i = 0;i<n;i++){ cin>>a; if(i==0)sum = a; else sum = sum^a;//所有数字异或起来-〉异或的性质:自己异或自己为0,0异或n = n; } cout<<sum<<endl; }