这神秘序列,为何每个数都和前一个差1,却无重复?

摘要:背景 昨日写题的时候,偶遇一道神奇的构造题,题目是这样的: 构造一个长度为(2^n)的序列(p_0,p_1,…,p_{2^n−1}),使得相邻两项异或值之和最小。 这道题很容易就可以看出,如果要构造一个使得相邻两项异或值之和最小的序
背景 昨日写题的时候,偶遇一道神奇的构造题,题目是这样的: 构造一个长度为\(2^n\)的序列\(p_0,p_1,…,p_{2^n−1}\),使得相邻两项异或值之和最小。 这道题很容易就可以看出,如果要构造一个使得相邻两项异或值之和最小的序列,就要保证每个元素与相邻元素间在二进制下仅有一位的差距,而这样的序列显然会按照有效二进制位数升序排列,因此我很快就想到了这样一个贪心策略:首先将0放进排列尾部,然后每次从小到大尝试改变尾部的二进制位,如果得到的是新数就更新排列,这里的从小到大尝试改变尾部的二进制位指的是:当数已经存在于生成的答案序列中时,尝试改变更高一位的二进制位。举个例子: 0 0 1 (尝试改变0的倒数1位,得到1,成功) 0 1 3 (尝试改变1的倒数1位,得到0,但0在排列中,失败,尝试改变1的倒数第2位,得到3,成功) 0 1 3 2 (尝试改变3的倒数1位,得到2,成功) 0 1 3 2 6 (尝试改变2的倒数1位,得到3,失败,尝试改变2的倒数第2位,得到0,失败,尝试改变2的倒数第3位,得到6,成功) 0 1 3 2 6 7 5 4 这就是我所想到的贪心思路,它是正确的,代码如下: #include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; int m = 1 << n; vector<int> ans; vector<bool> vis(m, false); int cur = 0; ans.push_back(cur); vis[cur] = true; while ((int)ans.size() < m) { for (int i = 0; i < n; i++) { int nxt = cur ^ (1 << i); if (!vis[nxt]) { cur = nxt; ans.push_back(cur); vis[cur] = true; break; } } } for (int i = 0; i < m; i++) { cout << ans[i] << " \n"[i == m - 1]; } return 0; } 转折 当我完成这道题后,还没来得及欣喜,就在看题解时发现了这道题原来另有说法,这道题目所要求构造的序列其实是一个非常经典的序列,该序列构造的规则是一个叫做格雷码的二进制数字系统,是由贝尔实验室的Frank Gray于20世纪40年代提出。它具有许多优点。 格雷码 基本定义 在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code) 性质 单位距离性 相邻格雷码之间的二进制仅有一位不同 循环性 任意连续\(2^k\)个格雷码其首尾仍符合单位距离性,即首位间二进制仅有一位不同 子立方体封闭性 在 n 维格雷码序列中,任意连续 \(2^k\) 项(从 \(2^k\) 的整数倍位置开始)恰好构成一个 k 维子立方体的格雷码。
阅读全文