题解:P7949 [✗✓OI R1] 左方之地

· · 题解

大战本题 45 min 终于玩出来了。

本文简称 \text{popcount}(x) 为 pc(x)。

首先 k 为偶数时无解,因为 pc(x) + pc(y) \equiv pc(x \oplus y)\pmod2,那么对于任意相邻两项,pc(a_i) \equiv pc(a_{i-1})\pmod 2,这显然是不可能构成一个排列的。

设原问题为 f(n,k),考虑 f(n,k) 能否规约到

把值域分为两个部分:$[0, 2^{n-1}), [2^{n-1}, 2^n)$,左半部分的最高位都是 $0$,右半部分都是 $1$,这样左右就分别规约成了 $f(n - 1, k)$,只要分割点处异或起来满足要求就可以。 - $n<k$ 肯定是不行的,因为位数不够。 - $n=k$ 只能在两个数 $x$ 和 $2^n-1-x$ 之间来回跳,因此只有 $n=k=1$ 是可以的。 因此在 $n>k$ 的可行情形下,递归下降时遇到的最小 $n$ 是 $n=k+1$,将其作为 base case。 问题转化成了,构造一个首项为指定值 $x$ 的,$[0, 2^n)$ 的排列,使得 $pc(a_i\oplus a_{i-1}) = n-1$。 观察发现,这个东西非常像格雷码。我们可以在格雷码的基础上,把奇数下标上 $a_i$ 的 01 翻转,偶数下标上的不变。 具体地,原格雷码序列为 $g_i=x\oplus i \oplus \lfloor \frac{i}{2} \rfloor$,$i\in[0,2^n)$。 现令: $$ a_i=\begin{cases} g_i, & i \equiv 0 \pmod 2\\ g_i\oplus (2^n-1), & i \equiv 1 \pmod 2 \end{cases} $$ 这样,肯定满足了 $pc(a_i\oplus a_{i-1}) = n-1$ 的条件,但是,它还是一个排列吗? :::success[证明] 我们已经限制了 $k$ 为奇数,因此 $n$ 为偶数。由于 $pc(x) + pc(y) \equiv pc(x \oplus y)\pmod2$,所以,$pc(g_i)$ 的奇偶性是交替出现的,即:$pc(g_i) \equiv 1 - pc(g_{i-1}) \pmod2$。 奇数下标上的数的 popcount 奇偶性相同,翻转后奇偶性仍然不变且相同。所以此时仍然构成 $[0, 2^n)$ 的排列。 ::: 在 base case 的时候,构造一个奇数下标翻转了的格雷码序列;每次递归时,在分割点处,可以直接令右半部分的首项,等于左半部分的末项的最低 $k-1$ 位翻转后的值(注意是 $k-1$ 不是 $k$,因为最高位已经不同)。 代码: ```cpp #include <bits/stdc++.h> using namespace std; vector<int> grey(int x, int n){ vector<int> ans; for (int i = 0; i < (1 << n); i++){ int cur = x ^ i ^ (i >> 1); if (i & 1) ans.emplace_back(cur ^ (1 << n) - 1); else ans.emplace_back(cur); } return ans; } vector<int> solve(int n, int k, int x){ if (n == k + 1) return grey(x, n); vector<int> ans = solve(n - 1, k, x); int p = ans.back() ^ (1 << k - 1) - 1; vector<int> b = solve(n - 1, k, p); for (int x : b) ans.emplace_back(x ^ (1 << n - 1)); return ans; } int32_t main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, k; cin >> n >> k; if (n == 1 && k == 1) return cout << "1\n0 1\n", 0; if (k % 2 == 0 || k >= n) return cout << "0\n", 0; cout << "1\n"; vector<int> ans = solve(n, k, 0); for (int x : ans) cout << x << " "; cout << endl; return 0; } ```