## [$AcWing801$. 二进制中$1$的个数 ](https://www.acwing.com/problem/content/description/803/) ### 一、题目描述 给定一个长度为 $n$ 的数列,请你求出数列中每个数的二进制表示中 $1$ 的个数。 **输入格式** 第一行包含整数 $n$。 第二行包含 $n$ 个整数,表示整个数列。 **输出格式** 共一行,包含 $n$ 个整数,其中的第 $i$ 个数表示数列中的第 $i$ 个数的二进制表示中 $1$ 的个数。 **数据范围** $1≤n≤100000,0≤数列中元素的值≤10^9$ **输入样例**: ```cpp {.line-numbers} 5 1 2 3 4 5 ``` **输出样例:** ```cpp {.line-numbers} 1 1 2 1 2 ``` ### 二、前导知识 位运算中有**两个重要性质**: #### 一、$x\&(-x)$ #### 保留二进制下**最后出现**的$1$的位置,其余位置置$0$ 演示一下 $x\&(-x)$ 的计算过程: 对于 $x = 10$ (二进制:$00001010$): 1. 求 $-x$ 的过程: ``` x = 00001010 取反(~x) = 11110101 加1 = 11110110 (-x的最终值) x&(-x) = 00001010 & 11110110 = 00000010 (结果) ``` $x = 10$ 的二进制表示中,最右边的$1$在第$2$位(从右往左数,从$1$开始) $x\&(-x) = 2$,正好是这个位置对应的值。 封装函数: ```cpp {.line-numbers} int lowbit(int x) { return x & (-x); } ``` #### 二、$x\&(x-1)$ #### 消除二进制下**最后出现**$1$的位置,其余保持不变 假设 $x = 52$,其二进制表示为: ``` x = 52 = 00110100 x-1 = 51 = 00110011 x & (x-1) = 00110000 = 48 ``` **工作原理**: 当我们将一个数减$1$时,从最右边的$1$开始,这一位变成0,而它右边的所有$0$都变成$1$ 当我们将原数和减$1$后的数做与运算时,最右边的$1$及其右边的所有位都会变成$0$,而左边的位保持不变 这就是为什么这个技巧常用于: - 计算二进制中$1$的个数(每操作一次消除一个$1$) - 判断一个数是否是$2$的幂(如果是$2$的幂,执行一次后就变成$0$) ```c++ while (x) { x = x & (x - 1); // 每次操作消除最右边的1 cnt++; // 记录操作次数,就是1的个数 } ``` 这个方法比直接遍历$32$位要高效,因为它的循环次数等于二进制中$1$的个数,而不是固定的$32$次。 ### 三、练习$x\&(x-1)$ #### 求下面函数的返回值 (微软) ```cpp {.line-numbers} int func(x) { int countx = 0; while(x){ countx ++; x = x & (x-1); } return countx; } ``` 功能:将$x$转化为$2$进制,看含有的$1$的个数。 注: 每执行一次$x = x\&(x-1)$,会将$x$用二进制表示时最右边的一个$1$变为$0$,因为$x-1$将会将该位($x$用二进制表示时最右边的一个$1$)变为$0$。 #### 判断一个数$x$是否是$2$的$n$次方 ```cpp {.line-numbers} #include int func(int x){ if((x & (x-1)) == 0) return 1; else return 0; } int main(){ int x = 8; printf("%d\n", func(x)); return 0; } ``` **思路** 如果一个数是$2$的$n$次方,那么这个数用二进制表示时其最高位为$1$,其余位为$0$。 ### 四、利用 $x\&-x$ ```cpp {.line-numbers} #include using namespace std; int lowbit(int x) { return x & (-x); } int main() { int n; cin >> n; while (n--) { int x; cin >> x; int res = 0; while (x) x -= lowbit(x), res++; printf("%d ", res); } return 0; } ``` ### 五、利用 $x \& (x-1)$ ```cpp {.line-numbers} #include using namespace std; int main() { int n; cin >> n; while (n--) { int cnt = 0; int x; cin >> x; while (x) { x = x & (x - 1); cnt++; } printf("%d ",cnt); } return 0; } ``` ### 六、遍历每一个二进制位 ```cpp {.line-numbers} #include using namespace std; int main() { int n; cin >> n; while (n--) { int cnt = 0; int x; cin >> x; for (int i = 0; i < 32; i++) if (x >> i & 1) cnt++; cout << cnt << " "; } return 0; } ```