神奇的位运算

本文主要记录算法竞赛中简单的位运算知识.

异或

性质

  1. 自反律:aa=0a \oplus a = 0a0=aa \oplus 0 = a;

  2. 交换律:ab=baa \oplus b = b \oplus a;

  3. 结合律:(ab)c=a(bc)(a \oplus b) \oplus c = a \oplus (b \oplus c);

  4. ⭐ 三个对象 aabbaba \oplus b 地位等价:

    c=aba=cbb=cac = a \oplus b \quad \Leftrightarrow \quad a = c \oplus b \quad \Leftrightarrow \quad b = c \oplus a

  5. ⭐ 加法 \ge 异或:a+baba + b \ge a \oplus b;

    加法会进位,而异或不会,因此上式成立.
    何时取等?只要不进位就取等.
    由于三者地位等价,因此 ab+baa \oplus b + b \ge aa+abba + a \oplus b \ge b,故 ababa \oplus b \ge |a - b|,于是得到异或的大致范围:

    ababa+b|a - b| \le a \oplus b \le a + b

  6. 相邻异或和:

    12345=1(23)(45)1 \oplus 2 \oplus 3 \oplus 4 \oplus 5 \oplus \cdots = 1 \oplus (2 \oplus 3) \oplus (4 \oplus 5) \oplus \cdots

    nn 为偶数时,n(n+1)=1n \oplus (n + 1) = 1;

异或和

对于序列 {ai}\{a_i\}{bj}\{b_j\}1im1 \le i \le m1jn1 \le j \le n,求

i=1mj=1n(aibj)\sum_{i=1}^m\sum_{j=1}^n(a_i \oplus b_j)

cij=aibjc_{ij} = a_i \oplus b_j 无非就是

cij,0+cij,12+cij,222++cij,k2k+c_{ij,0} + c_{ij,1} \cdot 2 + c_{ij,2} \cdot 2 ^2 + \cdots + c_{ij,k} \cdot 2 ^k + \cdots

因此根据乘法分配律,(可以看成上面的式子混入了最终的结果,++ 号被解离

i=1mj=1n(aibj)=k=063(i=1mj=1ncij,k)2k\sum_{i=1}^m\sum_{j=1}^n(a_i \oplus b_j) = \sum_{k = 0}^{63} \left(\sum_{i=1}^m\sum_{j=1}^nc_{ij,k}\right)\cdot 2^k

核心就是求 i=1mj=1ncij,k\sum\limits_{i=1}^m\sum\limits_{j=1}^nc_{ij,k},但 cij,kc_{ij,k} 从哪来?从 ai,ka_{i, k}bj,kb_{j, k} 来:

cij,k=ai,kbj,kc_{ij, k} = a_{i, k} \oplus b_{j, k}

这里 ai,ka_{i, k}bj,kb_{j,k}cij,kc_{ij,k} 要么是 00,要么是 11.因此从序列 {ai}\{a_i\}{bj}\{b_j\} 单独拎出比特位第 kk 位即可得到新序列 {ai,k}\{a_{i, k}\}{bj,k}\{b_{j, k}\} ,这些新序列其实都是 0101 序列.

说是求两组数的异或和,其实要干这样一件事:只单独看某一比特位,两组数都变成了 0101 序列,我们要求的是从这两个 0101 序列中分别取出一个比特,能组成 (1,0)(1, 0) 或者 (0,1)(0, 1) 对的方案数,最后将这个方案数安置在最终结果比特位第 kk 位上.

例如序列 {ai}={1,2,,10}\{a_i\} = \{1, 2, \cdots, 10\},要求

i=19j=i+110(aiaj)\sum_{i=1}^{9}\sum_{j=i+1}^{10}(a_i \oplus a_j)

关注比特位第 00 位到第 33 位,分别有

{ai,0}={1,0,1,0,1,0,1,0,1,0}{ai,1}={0,1,1,0,0,1,1,0,0,1}{ai,2}={0,0,0,1,1,1,1,0,0,0}{ai,3}={0,0,0,0,0,0,0,1,1,1}\begin{align*} \{a_{i,0}\} &= \{1, 0, 1, 0, 1, 0, 1, 0, 1, 0\} \\ \{a_{i,1}\} &= \{0, 1, 1, 0, 0, 1, 1, 0, 0, 1\} \\ \{a_{i,2}\} &= \{0, 0, 0, 1, 1, 1, 1, 0, 0, 0\} \\ \{a_{i,3}\} &= \{0, 0, 0, 0, 0, 0, 0, 1, 1, 1\} \\ \end{align*}

{ai,0}\{a_{i,0}\} 拎出两个比特,能组成 (0,1)(0, 1) 无序对的方案有 5×5=255 \times 5 = 25 种;

{ai,1}\{a_{i,1}\} 拎出两个比特,能组成 (0,1)(0, 1) 无序对的方案有 5×5=255 \times 5 = 25 种;

{ai,2}\{a_{i,2}\} 拎出两个比特,能组成 (0,1)(0, 1) 无序对的方案有 6×4=246 \times 4 = 24 种;

{ai,3}\{a_{i, 3}\} 拎出两个比特,能组成 (0,1)(0, 1) 无序对的方案有 7×3=217 \times 3 = 21 种;

把这 44 个数安置在最终答案的比特位里:

ans=25+25×2+24×22+21×23=339\text{ans} = 25 + 25 \times 2 + 24 \times 2^2 + 21 \times 2^3 = 339

参考代码:

cpp
#define int long long;
constexpr int MAXB = 32;
constexpr int MOD = 1e9 + 7;

int xor_sum(vector<int> &a, vector<int> &b, int n, int m) {
  // 前缀优化
  vector<vector<int>> a0pre(MAXB, vector<int>(n + 1));
  vector<vector<int>> a1pre(MAXB, vector<int>(m + 1));
  vector<vector<int>> b0pre(MAXB, vector<int>(n + 1));
  vector<vector<int>> b1pre(MAXB, vector<int>(m + 1));
  auto _get = [](vector<int> &arr, int L, int R) {
    return arr[R] - arr[L - 1];
  };
  for (int k = 0; k < MAXB; k++) {
    for (int i = 1; i <= n; i++) {
      if ((a[i - 1] >> k) & 1) {
        a1pre[k][i] = a1pre[k][i - 1] + 1; // 统计第 k 位是 1 的个数的前缀和
        a0pre[k][i] = a0pre[k][i - 1];
      } else {
        a0pre[k][i] = a0pre[k][i - 1] + 1; // 统计第 k 位是 0 的个数的前缀和
        a1pre[k][i] = a1pre[k][i - 1];
      }
    }
    for (int j = 1; j <= m; j++) {
      if ((b[j - 1] >> k) & 1) {
        b1pre[k][i] = b1pre[k][i - 1] + 1;
        b0pre[k][i] = b0pre[k][i - 1];
      } else {
        b0pre[k][i] = b0pre[k][i - 1] + 1;
        b1pre[k][i] = b1pre[k][i - 1];
      }
    }
  }
  // 多次查询
  int q;
  cin >> q;
  while (q--) {
    int aL, aR, bL, bR;
    cin >> aL >> aR >> bL >> bR;
    int ans = 0;
    for (int k = 0; k < MAXB; k++) {
      int tmp;
      tmp = _get(a0pre[k], aL, aR) * _get(b1pre[k], bL, bR); // a, b 的第 k 位分别是 0, 1
      tmp %= MOD;
      tmp <<= k; // 容易漏
      tmp %= MOD;
      ans += tmp;
      ans %= MOD;
      tmp = _get(a1pre[k], aL, aR) * _get(b0pre[k], bL, bR); // a, b 的第 k 位分别是 1, 0
      tmp %= MOD;
      tmp <<= k; // 容易漏
      tmp %= MOD;
      ans += tmp;
      ans %= MOD;
    }
    cout << ans << endl;
  }
}

判定

  1. 判断是否为 22 的幂:x & (x - 1)
  2. 截取第 kk 位是否为 11(x >> k) & 1
  3. 树状数组中的 lowbit(x)x & -x

其他

  1. ab=a&b+aba \mid b = a \:\&\: b + a \oplus b

  2. a+b=ab+a&ba + b = a \mid b + a \:\&\:b

  3. a+b=(ab)+((a&b)1)a + b = (a \oplus b) + ((a \:\&\: b) \ll 1)

  4. a+b2=((ab)1)+(a&b)\left\lfloor\dfrac{a + b}{2}\right\rfloor = ((a \oplus b) \gg1) + (a \:\&\: b)

Vector Space (LADR Chapter 1)
简单数论