位运算
位操作
二进制中1的个数
在计算机底层的世界里,只有0和1,我们通过对二进制的操作来巧妙的进行运算。
位操作符号
- ”»” 这个符号是右移
- ”«” 这个符号是左移
- “&” 这个符号是与运算
-
” ” 这个符号是或运算 - ”^” 这个符号是异或运算
左移右移代表计算机的乘2和除以2,与运算法则是一假全假,或运算法则是一真全真,异或运算法则是不进位的加法,也可以这样计算,同0异1。
现在我们回归正题,怎么利用位运算来判断一个正整数 $(0 \le x \le 10^9)$ 的二进制里有多少个1。
我们先介绍一个补码:
- 计算机里存的数都是以补码的形式存在
- 正数的补码还是自己本身
- 负数的补码是最高位符号位不变,其余变反码,再+1
有了这个性质我们可以再研究下去
正整数自然不必纠结,而负整数它首先要除最高位其他位都要按位取反,我们发现如果现在正数和添负号的反码两个做与运算,发现一个规律,结果所有位都变成了0,负数的补码要在反码的基础上+1,这是最关键的一点,反码+1之后,就发现正数末尾1之后的都和补码末尾1之后的一样了,这点就起了至关重要的作用末尾1前面的所有位都经过与运算变为了0,而末尾1之后的不变,与运算之后我们就得到了末尾的1。
\[x \& (-x)\]以上就是二进制中1的个数算法的精髓
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int a[N];
int n;
// low_bit函数
int low_bit(int x)
{
return x & (-x);
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < n; i++)
{
int x= a[i];
int j = 0;
// 每次low_bit得到低位的1就删去,计数,直到这个数为0。
while (x)
{
x -= low_bit(x);
j++;
}
printf("%d ", j);
}
return 0;
}