1 条题解
-
0
这是一道 CF 2040C - Ordered Permutations 的构造题。
下面给出详细题解。
题目大意
定义长度为 的排列 的分数:
[ S(p) = \sum_{l=1}^{n} \sum_{r=l}^{n} \min(p_l, p_{l+1}, \dots, p_r) ]
在 最大的所有排列中,按字典序从小到大排列,输出第 个排列。
关键结论(分析部分)
1. 最大 的排列结构
可以证明, 取最大值的排列是 单峰排列(先递增后递减),并且最大值 在中间。
更精确地说:- 将数字 依次放入排列中。
- 每个新数字 必须放在当前已放数字区间的 最左空位 或 最右空位。
- 这样形成的排列,其形状是:先递增到 ,再递减(或者说“山脉形”)。
原因:
当放入 时,它成为所有包含它的区间的最小值,贡献 次最小值,这种放法最大化总 。
2. 排列数量
由于每个数字 (除了 )都有两种选择(放最左或最右),所以 。
例如 , 个最大排列:
, , ,
(验证示例中 和 未出现,因为它们的 更小)
3. 字典序与二进制编码
从左到右处理数字 :
- 放左边 对应二进制位 (加入左序列)
- 放右边 对应二进制位 (加入右序列)
数字 最后放在中间。
这样,从 到 的二进制数唯一对应一个最大排列,且按二进制顺序就是字典序顺序(需要验证,结论成立)。因此:
- 第 个(从 开始计数)对应的二进制是 ( 位)。
算法步骤(对应标程)
- 读入 。
- 如果 且 ,输出 (情况不足)。
- 令 (转为 -based 索引)。
- 将 转换成二进制,不足 位前面补 。
- 从左到右扫描二进制位(实际代码是从高位到低位):
- 若某位 :当前数字放入左序列
a - 若某位 :当前数字放入右序列
b
- 若某位 :当前数字放入左序列
- 输出:左序列
a→ 数字 → 右序列b(逆序输出,以保证构造正确)。
示例验证
示例 1:
位,,二进制
01(高位到低位)。处理 :
- 左对 (i=1, ) 位
0→ 左[1] - 右对 (i=2, ) 位
1→ 右[2]
输出:左
[1],然后3,然后右逆序[2]→1 3 2✅
示例 2:
,二进制
10。,位
1→ 右[1]
,位0→ 左[2]输出:左
[2]→3→ 右逆序[1]→2 3 1✅
示例 3:
, → 输出
-1✅
复杂度
- 每个测试 时间
- 总 和 ,可以通过
- 注意 可能溢出,这里只处理 时比较
代码复现(加注释)
#include <bits/stdc++.h> using namespace std; int main() { int tt; cin >> tt; while (tt--) { int n; long long k; // k up to 1e12 cin >> n >> k; // 总排列数 = 2^(n-1) if (n <= 60 && (1ll << (n - 1)) < k) { cout << -1 << endl; continue; } k--; // 0-based index // 得到二进制表示(低位在 vector 前面) vector<int> d; while (k) { d.push_back(k % 2); k /= 2; } // 补足 n-1 位 while (d.size() < n - 1) d.push_back(0); // 现在 d[0] 是最高位?实际是低位在左,我们要从高位(大索引)处理 // 标程从 i = n-2 往下走,正是从高位向低位 vector<int> a, b; for (int i = n - 2, j = 1; i >= 0; i--, j++) { if (d[i] == 0) a.push_back(j); else b.push_back(j); } reverse(b.begin(), b.end()); for (int x : a) cout << x << ' '; cout << n << ' '; for (int x : b) cout << x << ' '; cout << endl; } return 0; }
总结思想
- 最大 的排列是 单峰形(先升后降)。
- 构造时数字 到 依次选左或右, 在中间。
- 字典序对应二进制编码 。
- 解为:左段(升序) + + 右段(降序)。
- 1
信息
- ID
- 6997
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 5
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者