(在计算机中,数都是二进制来存储的,所以二进制的一些运算要比普通的等价运算(+,-,*,/)更快,更简单,所以知道他们的技巧显得尤其重要!)
异或运算:
首先 异或(^) 表示当两个数的二进制表示,进行异或运算时,当前位的两个二进制表示不同则为 1 相同则为 0 .该方法被广泛推广用来统计一个数的1的位数!
所以二进制中 对应二进制位数进行异或操作时, 与0异或对应该二进制位数值不变, 与 1 异或时对应该二进制位数值相反(即 1 变成 0 , 0 变成 1).
参与运算的两个值,如果两个二进制相应位相同,则结果为0,否则(不同)为1。
即:
0^0 = 0,
1^0 = 1,
0^1 = 1,
1^1 = 0
按位异或的3个特点:
(1) 0^0=0,0^1=1 0异或任何数=任何数 (这里的任何数是指二进制位上的那个数,即任何数要么为0 ,要么为1 .)
(2) 1^0=1,1^1=0 1异或任何数-任何数取反 (同理)
(3) 任何数异或自己=把自己置0
按位异或的几个常见用途:
(1) 使某些特定的位翻转
例如对数10100001的第2位和第3位翻转(不管是0 还是 1 都可以,因为上面也说了 任何数与1 异或 任何数取反 ! 所以可以进行翻转!),则可以将该数与00000110进行按位异或运算
10100001^00000110 = 10100111
(2) 实现两个值的交换,而不必使用临时变量。
例如交换两个整数a=10100001,b=00000110的值,可通过下列语句实现:
a = a^b; //a=10100111
b = b^a; //b=10100001
a = a^b; //a=00000110
(一句话: a=a^b^(b=a) ; )
再次 & (与) 二进制对应位上都是 1 结果才是1 . 否则就是 0 . (和电路开关很像,串联,同时开才开,否则就是关.)
技巧 1 : 把一个整数减去1,再和原整数做与运算,会把该整数的二进制中的最右边一个1变成0 , 那么一个整数的二进制表示中有多少个1,就可以进行多少次这样的操作.
技巧 2 : 二进制的最低位是1就是奇数,是0就是偶数.
(原因 : 二进制的位数(由低到高)分别代表着1,2,4,8,16,32,64,128,256,512,1024.只有最低位的这个是1或0,所以二进制最低位为1时,就是奇数(偶数由上面的这些基本的组成嘛!) )
技巧 3 :算一个数的二进制最低位是多少, 直接 x&1 = x的最低位是多少.
例 1: 如何用一条语句表示判断一个整数是不是2的整数次方.
解 : 因为是整数次方,所以该数的二进制中必定只含一个 1 . 所以结合上面的技巧,就有 !(x & (x-1) ) ;
例 2 : 写一个函数,判断一个数的二进制中多少个 1 .
int check(int x)
{
int num=0;
while(x)
{
x &= (x-1);
num++;
}
return num;
}
例 3 : 输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n .
思路: 结合异或的性质运算,首先让 m^n 得到一个数,则这个数二进制中有多少个1 , 则m,n就有多少位不同.所以统计得到的这个数中又多少个1就行了.
int check(int m,int n)
{
int x=m^n;
int num=0;
while(x)
{
x &= (x-1);
num++;
}
return num;
}
例 4:判断一个数的奇偶性(结合技巧)
if(x&1)
是奇数.
else
是偶数.
例 5 :算一个数二进制的补位数(比如说5的二进制是101, 所以补位数就是010, 即2,当然不能直接用~)
int sum=0,s=1;
while(t){
int m=((t&1)^1);
sum += s*m; //是1才算值, 是零就不管.
t >>= 1; //不断除二.
s <<= 1; //s表示2的几次方.
}
二进制的一些操作:
1:将一个十进制的数分解为二进制表示.
while(t){
int m=t&1;
a[k++]=m;
t >>= 1;
}
2:将一个十进制数转化成二进制的同时再算出转化后的这个数十进制表示为多少.
int s=1,sum=0;
while(t){
int m=t&1;
sum += s*m;
s <<= 1;
t >>= 1;
}
3:取某个数的第 i 个 二进制位数.
int Getbit(int x,int i)
{
return (x >> i) & 1;
}
4:把 x 的第 i 位设置成 v .
void setbit(int & x,int i,int v) //这样引用的话,形参改变对应实参也会改变.
{
if(v ){
c |= (1 << i);
}
else
c &= ~(1 << i);
}
5:把x的第 i 位翻转.(1 翻为0 , 0 翻为 1).
void flipbit(char & c ,int i)
{
c ^= (1 << i) ;
}
一大波操作 :
去掉最后一位 |(101101->10110) | x >> 1
在最后加一个0 |(101101->1011010) | x < < 1
在最后加一个1 |(101101->1011011) | x < < 1+1
把最后一位变成1 |(101100->101101) | x | 1
把最后一位变成0 |(101101->101100) | x | 1-1
最后一位取反 |(101101->101100) | x ^ 1
把右数第k位变成1 | (101001->101101,k=3) | x | (1 < < (k-1)) //k>=1, 即第一位算的是1
把右数第k位变成0 | (101101->101001,k=3) | x & ~ (1 < < (k-1))
右数第k位取反 | (101001->101101,k=3) | x ^ (1 < < (k-1))
取末三位 | (1101101->101) | x & 7
取末k位 | (1101101->1101,k=5) | x & ((1 < < k)-1)
取右数第k位 | (1101101->1,k=4) | x >> (k-1) & 1
把末k位变成1 | (101001->101111,k=4) | x | (1 < < k-1)
末k位取反 | (101001->100110,k=4) | x ^ (1 < < k-1)
把右边连续的1变成0 | (100101111->100100000) | x & (x+1)
把右起第一个0变成1 | (100101111->100111111) | x | (x+1)
把右边连续的0变成1 | (11011000->11011111) | x | (x-1)
取右边连续的1 | (100101111->1111) | (x ^ (x+1)) >> 1
去掉右起第一个1的左边 | (100101000->1000) | x & (x ^ (x-1))
判断奇数 (x&1)==1
判断偶数 (x&1)==0
取右边第一个1所在位置 x&-x
将右边第一个1变为0. x &= (x-1)
移位运算要点
1: 它们都是双目运算符,两个运算分量都是整形,结果也是整形。
2: "<<" 左移:右边空出的位上补0,左边的位将从字头挤掉,其值相当于乘2。
3: ">>"右移:右边的位被挤掉。对于左边移出的空位,如果是正数则空位补0,若为负数,可能补0或补1,这取决于所用的计算机系统。
4: ">>>"运算符,右边的位被挤掉,对于左边移出的空位一概补上0。
位运算符的应用 (源操作数s 掩码mask)
(1) 按位与-- &
1: 清零特定位 (mask中特定位置0,其它位为1,s=s&mask)
2: 取某数中指定位 (mask中特定位置1,其它位为0,s=s&mask)
(2) 按位或-- |
常用来将源操作数某些位置置为1,其它位不变。 (mask中特定位置1,其它位为0 s=s|mask)
(3) 位异或-- ^1
1: 使特定位的值取反 (mask中特定位置1,其它位为0 s=s^mask)
2: 不引入第三变量,交换两个变量的值(a = a^b^(b==a); || a ^= b; b ^= a; a ^= b;)
二进制补码运算公式:(不懂负数的点击最下方)
-x = ~x + 1 = ~(x-1)
~x = -x-1
-(~x) = x+1
~(-x) = x-1
比较应用举例
(1)整数的平均值对于两个整数x,y,如果用 (x+y)/2 求平均值,会产生溢出,因为 x+y 可能会大于INT_MAX,
但是我们知道它们的平均值是肯定不会溢出的,我们用如下
int average(int x, int y) //返回X,Y 的平均值
{
return (x&y)+((x^y)>>1);
}
(2) x 的 相反数 表示为 (~x+1)
bitset: 二进制封装的容器
记住它的每一位的类型为bool型.
bitset之间的操作只能用bitset来判断, 比如说判断是否等于0. 那么应该先定义bitset<10>bit(0); 然后判 (注意优先级)(bitset[5] & bitset[28] )== bit ? 不能直接写 == 0 . !!!
bitset<10>bit(6): (直接用cout << bit << endl输出为): 0000000110
(或者bitset<10>bit = n || bitset<10>bit(n)).
bitset中一些比较有用的操作:
bit.any() //bit中是否存在为1的二进制位? (存在就是1)
bit.count() //bit中二进制位为1的个数
bit.set() //把bit中所有的二进位置为1
bit.reset() //把bit中所有的二进位置为0
然后由于一些题目中需要用到的一些bieset的操作是没有库函数的, 所以此时手写bitset就派上用场了, 这样手写以后对应的操作的复杂度也就可以变成maxn/30了, 而不是maxn了. 所以在此存一个板子:
(如果要想实现其他功能, 那么就自己写进去, 这样只是存了一个大概的样式)
struct Bitset {
int len = 3339;
int a[3339] = {0};
// Bitset() {
// for (int i = 0; i < len; i++) a[i] = 0;
// }
Bitset operator | (const Bitset &rhs) {
Bitset tmp;
for (int i = 0; i < len; i++) {
tmp.a[i] = a[i] | rhs.a[i];
}
return tmp;
}
Bitset operator & (const Bitset &rhs) {
Bitset tmp;
for (int i = 0; i < len; i++) {
tmp.a[i] = a[i] & rhs.a[i];
}
return tmp;
}
int get() { // 取当前bieset中最高位的1所在的下标
for (int i = len - 1; i >= 0; i--) {
if (a[i] > 0) {
for (int j = 29; j >= 0; j--) {
if ((1 << j) & a[i]) {
return i * 30 + j;
}
}
}
}
}
void set(int x) { // 把对应位置1
int p = x / 30, q = x % 30;
a[p] |= (1 << q);
}
};