位运算就是把整数转换为二进制后，每位进行相应的运算得到结果。

常用的运算符共 6 种，分别为与（`&`）、或（`|`）、异或（`^`）、取反（`~`）、左移（`<<`） 和右移（`>>`）。

## 与、或、异或

与（`&`）或（`|`）和异或（`^`）这三者都是两者间的运算，因此在这里一起讲解。

表示把两个整数分别转换为二进制后各位逐一比较。

<table><tr>
    <td style="text-align:center;"><code>&</code></td><td>只有在两个（对应位数中）都为 1 时才为 1。</td>
</tr><tr>
    <td style="text-align:center;"><code>|</code></td><td>只要在两个（对应位数中）有一个 1 时就为 1。</td>
</tr><tr>
    <td style="text-align:center;"><code>^</code></td><td>只有两个（对应位数）不同时才为 1。</td>
</tr></table>

`^` 运算的逆运算是它本身，也就是说两次异或同一个数最后结果不变，即 `(a ^ b) ^ b = a`。

> 举例：
>
> $$
> \begin{aligned}
> &5&=&&(101)_2\\
> &6&=&&(110)_2\\
> &5\tt\,\&\,6\rm&=&&(100)_2&=\ 4\\
> &5\tt\,|\,\rm6&=&&(111)_2&=\ 7\\
> &5\tt\,\text{^}\,\rm6&=&&(011)_2&=\ 3\\
> \end{aligned}
> $$

## 取反

取反是对 1 个数 $num$ 进行的计算。

`~` 把 $num$ 的补码中的 0 和 1 全部取反 （0 变为 1，1 变为 0）。

补码——正数的补码为其（二进制）本身，负数的补码是其（二进制）取反后 $+1$。

> 举例：
>
> $$
> \begin{aligned}
> 5=(0000\ 0101)_2\\
> 5\ \text{的补码} =(1111\ 1010)_2\\
> \tt\ \text{~}\rm5=(1111\ 1010)_2
> \end{aligned}
> $$

## 左移和右移

与前面的 4 种运算相似，这两种运算仍是把整数转换为二进制后进行操作。

左移（`<<`） 将转化为二进制后的数字整体向左移动。

> `num << i`  // 表示将 $num$ 转换为二进制后向左移动 $i$ 位（所得的值）

右移（`>>`） 将转化为二进制后的数字整体向右移动。

> `num >> i`  // 表示将 $num$ 转换为二进制后向左移动 $i$ 位（所得的值）
>
> 举例：
>
> $$
> \begin{aligned}
> &5&&=&&(00000101)_2\\
> &5\tt\,<<\,\rm1&&=&&(00001010)_2\!\!\!&=&&\!\!\!20\\
> &5\tt>>\rm1&&=&&(00000010)_2&=&&2
> \end{aligned}
> $$

在 C++ 中，右移操作中右侧多余的位将会被舍弃。而左侧较为复杂：对于无符号数，会在左侧补 0；而对于有符号数，则会用最高位的数补齐（Replicate most significant bit on left）。

注意：

1. 左移和右移是有返回值的，并非对 $num$ 本身进行操作。
2. 左移和右移的优先级低于四则运算符，例如 $x<<1+1$ 会被解释为 $x<<(1+1)$ ，所以必要的时候，要使用括号。

* * *

## 位运算的应用

如果 $num$ 是正数，`num << i` 相当于 $num$ 乘以 2 的 $i$ 次方，而 `num >> i` 相当于 $num$ 除以 2 的 $i$ 次方。 (位运算比 `%` 和 `/` 操作快得多)
(据 2018JSOI 夏令营，效率可以提高 60%)

!!! warning
    为什么要强调是正数呢？考虑一下 `-1 >> 3`

`num * 10 = (num<<1) + (num<<3)`

`num & 1` 相当于取 $num$ 二进制的最末位，可用于判断 $num$ 的奇偶性，二进制的最末位为 0 表示该数为偶数，最末位为 1 表示该数为奇数。

```cpp
//利用位运算的快捷的 swap 代码
void swap(int a, int b){
	a = a ^ b;   
	b = a ^ b;  
	a = a ^ b;
}
```

一个数的二进制表示可以看作是一个集合（0 表示不在集合中，1 表示在集合中）。比如集合 `{1, 3, 4, 8}`，可以表示成 `0b00000000000000000000000100011010`，十进制就是 $2^8+2^4+2^3+2^1=282$。

而对应的位运算也就可以看作是对集合进行的操作。

| 操作  |            集合表示 |  位运算语句  |
| --- | --------------: | :-----: |
| 交集  |      $a \cap b$ | `a & b` |
| 并集  |      $a \cup b$ | `a | b` |
| 补集  |       $\bar{a}$ |   `~a`  |
| 差集  | $a \setminus b$ |   `~a`  |
| 对称差 |  $a\triangle b$ | `a ^ b` |

* * *

## 位运算的常用方法

-   乘以 2 运算。

    ```cpp
    int mulTwo(int n) { // 计算n*2
      return n << 1;
    }
    ```

-   除以 2 运算。

    ```cpp
    int divTwo(int n) { // 负奇数的运算不可用
      return n >> 1; // 除以2
    }
    ```

-   乘以 2 的 $m$ 次方。

    ```cpp
    int mulTwoPower(int n, int m) { // 计算n*(2^m)
      return n << m;
    }
    ```

-   除以 2 的 $m$ 次方。

    ```cpp
    int divTwoPower(int n, int m) { // 计算n/(2^m)
      return n >> m;
    }
    ```

-   判断一个数的奇偶性。

    ```cpp
    boolean isOddNumber(int n) {
      return (n & 1) == 1;
    }
    ```

-   取绝对值（某些机器上，效率比 `n > 0 ? n : -n` 高）。

    ```cpp
    int abs(int n) {
      return (n ^ (n >> 31)) - (n >> 31);
      /* n>>31 取得n的符号，若n为正数，n>>31等于0，若n为负数，n>>31等于-1
         若n为正数 n^0=0,数不变，若n为负数有n^-1 需要计算n和-1的补码，然后进行异或运算，
         结果n变号并且为n的绝对值减1，再减去-1就是绝对值 */
    }
    ```

-   取两个数的最大值（某些机器上，效率比 `a > b ? a : b` 高）。

    ```cpp
    int max(int a, int b) {
      return b & ((a - b) >> 31) | a & (~(a - b) >> 31);
      /* 如果a>=b,(a-b)>>31为0，否则为-1 */
    }
    ```

-   取两个数的最小值（某些机器上，效率比 `a > b ? b : a` 高）。

    ```cpp
    int min(int a, int b) {
      return a & ((a - b) >> 31) | b & (~(a - b) >> 31);
      /* 如果a>=b,(a-b)>>31为0，否则为-1 */
    }
    ```

-   判断符号是否相同。

    ```cpp
    boolean isSameSign(int x, int y) { // 有0的情况例外
      return (x ^ y) >= 0; // true 表示x和y有相同的符号，false 表示x,y有相反的符号。
    }
    ```

-   计算 2 的 $n$ 次方。

    ```cpp
    int getFactorialofTwo(int n) { // n > 0
      return 2 << (n - 1); // 2的n次方
    }
    ```

-   判断一个数是不是 2 的幂。

    ```cpp
    boolean isFactorialofTwo(int n) {
      return n > 0 ? (n & (n - 1)) == 0 : false;
      /* 如果是2的幂，n一定是100... n-1就是1111....
         所以做与运算结果为0 */
    }
    ```

-   对 2 的 $n$ 次方取余。

    ```cpp
    int quyu(int m, int n) { //n为2的次方
      return m & (n - 1);
      /* 如果是2的幂，n一定是100... n-1就是1111....
         所以做与运算结果保留m在n范围的非0的位 */
    }
    ```

-   求两个整数的平均值。

    ```cpp
    int getAverage(int x, int y) {
      return (x + y) >> 1;
      ｝
    ```

-   遍历一个集合的子集
    ```cpp
    int b = 0;
    do {
      // process subset b
    } while (b = (b - x) & x);
    ```

## 题目推荐

[CODEVS 2743 黑白棋游戏](http://codevs.cn/problem/2743/)

## 参考

位运算技巧：<https://graphics.stanford.edu/~seander/bithacks.html>
