随记 – 五月二日 – 初探格雷码

格雷码(Gray code)是一种任意两相邻项有且仅有一位不同的二进制编码。

直观来看,其构造方式如下:假如我们构造出了格雷码g0,1,,2m1g_{0, 1, \cdots, 2^m-1},现在考虑构造m+1m+1位格雷码。我们将前2m2^m位复制一遍,然后反转在原序列中的位置,也即g2m+i=g2m1i,i[0,2m)g’_{2^m+i}=g_{2^m-1-i}, i \in [0, 2^m);然后在最后2m2^m项格雷码的左侧分别添上11。由此有g2m+i=g2m1i+2mg_{2^m+i}=g_{2^m-1-i}+2^m

实际操作时,可以采用相邻两项的异或值与前缀异或和快速构造。假若有gxg_xgx1g_{x-1}的异或值序列s1,2,,2m1s_{1, 2, \cdots, 2^m-1},那么便有s2m+i=si,i[1,2m)s_{2^m+i}=s_{i}, i \in [1, 2^m),同时有s2m+1=g2m+1g2m=2ms_{2^m+1}=g_{2^m+1} \oplus g_{2^m}=2^m

例如,前8项格雷码的二进制表示为:{0b000,0b001,0b011,0b010,0b110,0b111,0b101,0b100}\{\text{0b}000, \text{0b}00{\color{blue}1}, \text{0b}0{\color{blue}1}1, \text{0b}01{\color{blue}0}, \text{0b}{\color{blue}1}10, \text{0b}11{\color{blue}1}, \text{0b}1{\color{blue}0}1, \text{0b}10{\color{blue}0}\},相邻两项异或值为{1,2,1,4,1,2,1}\{1,2,1,4,1,2,1\}

下面给出一个构造nn位格雷码的简单实现。更高效的实现和证明参见OI Wiki

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
inl void Gray (int n, int g[]) { for (int w = 0; w < n; ++w) { g[1<<w] = 1<<(w<<1); for (int i = 1; i < 1<<w; ++i) g[i + (1<<w)] = g[i]; } for (int i = 1; i < 1<<n; ++i) g[i] ^= g[i - 1]; }

格雷码主要有以下两个重要性质:

  • 构成一个0,1,,2m10, 1, \cdots, 2^m-1全排列
  • 相邻两项恰有11位不同

假若我们需要构造一个002m12^m-1的全排列,并且考虑相邻两项的异或的算术总和最小,那么最优解一定是格雷码。同时,2m2^m项格雷码的遍历仅需要作2m12^m-1次二进制位的变换;而由于异或具有结合律与相异性,可以利用其构造互不相同的状态的表示,因此容易出现在与异或、最小异或代价等的构造题目中。

题目:

  • 2022年5月2日