Codeforces 2157H - Keygen 3
高端题目。
记 \(\text{LIM} = 2000\)。
首先需要注意,这题并不需要知道 \(k\) 的准确值,只需要知道 \(\min(k, \text{LIM})\),如果没发现的话可以先不往下看了/续标识。
首先考虑一些能做的 \((n, m)\),因为题目说明了这个排列得是单峰的,从大到小考虑每个值,这个值只能放在当前序列的最左侧和最右侧,于是只会有 \(2^{n - 1}\) 种单峰排列,此时暴力 \(\mathcal{O}(n)\) 求环数判断是否和 \(m\) 相同,就可以做到 \(\mathcal{O}(2^n n)\) 的复杂度。
但是这个做法只能用在 \(n\) 较小的情况,但是感受一下,因为 \(\text{LIM}\) 并不大,所以当 \(n\) 在大一点的时候,或许有很多组 \((n, m)\) 都能有 \(\text{LIM}\) 种合法排列。
这启发我们尝试一些增量构造,即若这一组 \((n, m)\) 已经有 \(\text{LIM}\) 个合法排列了,尝试让每一个排列扩展到 \((n + x, m + y)\),这样就能构造出 \((n + x, m + y)\) 的 \(\text{LIM}\) 个合法排列了。
为了方便表述,接下来把合法排列的单峰条件转为单谷条件。
一个合法的单谷排列 \(p\) 转化到合法的单峰排列 \(p\) 只需要把 \(p_x\) 变为 \(n - p_x + 1\) 并 reverse 整个序列,因为这相当于是把一条 \(i\to p_i\) 的边的端点都按 \(x = \frac{n + 1}{2}\) 对称。
那么首先尝试一些简单的增量。
\((n, m)\to (n + 1, m + 1)\),发现只需要在末尾加入一个自环,即令 \(p_{n + 1} = n + 1\) 即可。重复进行便可以做到 \((n, m)\to (n + x, m + x)(x\ge 0)\)。
接下来继续尝试,刚刚的构造 \(n - m\) 的值始终没有改变,当 \(n - m\) 较大的时候就无法从 \(n\) 小的情况扩展了,即我们想要尝试 \((n, m)\to (n + x, m + y)(0\le y\le x)\)。
但这个形式并不是很好看,因为中间的过程不太好进行一些微调,于是可以让步骤变为 \((n, m)\to (n + x, m + x)\to (n + x, m + x - y)(0\le y\le x)\)。
那么同样的,可以先尝试 \((n + x, m + x)\to (n + x, m + x - 1)\) 再重复操作。
那么只减少一个环,就可以考虑通过 \(\operatorname{swap}(p_x, p_y)\) 合并两个环(要满足 \(x, y\) 不在一个环里),发现如果 \(x, y\) 都在谷的同一侧就会破坏单谷的结构,于是只能让 \(x, y\) 在谷的两侧。
为了尽可能不打乱结构,尝试选一些比较特殊的值,比如说令 \(x = 1\),分析 \(p_1\),发现序列的末尾一定是 \(p_1 + 1, p_1 + 2, \cdots, n + x - 1, n + x\),即 \(p_1 + 1\sim n + x\) 都是在序列末尾的自环,因为这些值只能在谷的右侧并且要满足递增关系。
那么就可以考虑令 \(x = 1, y = p_1 + 1\),这样合并后就把 \(p_1 + 1\) 的自环合并进了 \(1\) 的环,并且 \(p_1\) 只会 \(+1\),因为初值 \(p_1\le n\),所以可以知道构造到 \((n + x, m + x - y)\) 时一定有 \(p_1\le n + y\le n + x\),并不会出问题。
那么只要存在较小的 \((n, m)\) 满足合法排列数 \(\ge \text{LIM}\),对于 \(n'\ge n, m'\ge m, n' - m'\ge n - m\) 的 \((n', m')\) 就做完了。
那么就只需要考虑 \(n - m\) 较小的情况了。
考虑 \(n - m\) 较小就意味着排列应当存在许多个 \(p_i = i\) 的自环。
在最极端的情况下分析,即非自环的环都是二元环时,也可以知道至多能有 \(n - m\) 个二元环,也就是说至少有 \(2m - n\) 个点都是 \(p_i = i\) 的自环。
考虑把 \((i, p_i)\) 画在平面上,并画出 \(x = y\) 这一条直线。
根据前面的分析,会知道 \(p_1 + 1\sim n\) 肯定都是在这个平面上的,并且 \((p_1, p_{p_1})\) 肯定不在这条直线上(\(p_{p_1} < p_1\)),那么记谷的位置在 \(x\),就可以知道 \(x\sim p_1\) 的位置都不可能在平面上。
继续考虑 \(1\sim x\) 的部分,即谷的左侧,会发现此时 \(x\) 坐标递增 \(y\) 坐标递减,这说明这部分与 \(x = y\) 这条直线也至多只有 \(1\) 个交点。
这说明如果固定了 \(p_1\),这个排列的自环数至多为 \(n - (p_1 + 1) + 1 + 1 = n - p_1 + 1\),而这个值需要 \(\ge 2m - n\),解不等式可得 \(p_1\le 2n - 2m + 1\)。
并且因为 \(p_1 + 1\sim n\) 都是自环,这说明对于每个排列只需要关心 \(1\sim p_1\) 的部分,此时跑暴力就有一个 \(\mathcal{O}(4^{n - m}(2n - 2m))\) 的做法了。
平衡一下,会发现当 \(n = 18\) 时 \(1\le m\le 8\) 的 \((n, m)\) 都至少有 \(\text{LIM}\) 个合法排列,于是考虑:
- 当 \(n\le 18\) 时,跑 \(\mathcal{O}(2^n n)\) 的暴力。
- 当 \(n - m\ge 10\) 时,先 \(\mathcal{O}(2^{18}\times 18)\) 跑出 \(\text{LIM}\) 组 \(n' = 18\) 的方案,再 \(\mathcal{O}(n\times \text{LIM})\) 增量构造。
- 当 \(n - m\le 9\) 时,跑 \(\mathcal{O}(4^{n - m}(2n - 2m))\) 的暴力做法。
#include <bits/stdc++.h>
using seq = std::vector<int>;
constexpr int LIM = 2000;
inline void print(int n, const std::vector<seq> &ans) {
printf("%zu\n", ans.size());
for (seq p : ans) {
std::reverse(p.begin(), p.end());
for (int x : p) {
printf("%d ", n - x);
}
puts("");
}
}
inline std::vector<seq> brute_force(int n, int m) {
std::vector<seq> ans;
for (int s = 0; s < (1 << n - 1) && (int)ans.size() < LIM; s++) {
seq p(n);
for (int i = 0, pl = 0, pr = n - 1; i < n; i++) {
if (s >> i & 1) {
p[pl++] = n - 1 - i;
} else {
p[pr--] = n - 1 - i;
}
}
std::vector<int> vis(n, 0);
int cnt = 0;
for (int i = 0; i < n; i++) {
if (! vis[i]) {
for (int j = i; ! vis[j]++; ) {
j = p[j];
}
cnt++;
}
}
if (cnt == m) {
ans.push_back(p);
}
}
return ans;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
if (n <= 18) {
print(n, brute_force(n, m));
return 0;
}
const int delta = n - m;
if (delta >= 10) {
const int _n = 18;
const int _m = std::max(_n - delta, 1);
const int sub = delta - (_n - _m);
std::vector<seq> ans = brute_force(_n, _m);
for (seq &p : ans) {
while ((int)p.size() < n) {
p.push_back((int)p.size());
}
const int l = p[0] + 1, r = p[0] + sub;
for (int &x : p) {
x -= l <= x && x <= r;
}
p[0] = r;
}
print(n, ans);
return 0;
}
std::vector<seq> ans = brute_force(delta * 2 + 1, delta + 1);
for (seq &p : ans) {
while ((int)p.size() < n) {
p.push_back((int)p.size());
}
}
print(n, ans);
return 0;
}
浙公网安备 33010602011771号