- 分享
- @ 2026-10-04 15:53:24
当 \(k = 1\) 时(即求解字典序最小的初始序列 \(a\)),该问题具有非常明确且特殊的性质喵~
\(k = 1\) 时的核心性质
- 区间最大值的不变性(历史最大值): 在连续做排序的过程中,对于任意一个大小为 \(m\) 的滑动窗口,经过所有排序操作后,最终序列 \(b\) 中位于第 \(i\) 个窗口(即下标区间 \([i, i + m - 1]\))中的最大值,**一定等于**初始序列 \(a\) 在该区域以及后续区域受影响元素中的最大值喵~
- 后缀最大值与位置对应: 更进一步地,最终序列 \(b\) 中从 \(n-m+1\) 到 \(n\) 的元素(即最后一个排序窗口)必定是递增的喵~ 当要求 \(a\) 的字典序最小(\(k=1\))时,我们应当尽可能把**较小的数放在前面,较大的数放在后面**喵~ 因此,字典序最小的初始序列 \(a\) 满足以下性质:
- 排序操作实质上是将大元素逐步向后“推”喵~
- 逆向思考这个过程,最终序列 \(b\) 的某些元素位置被固定,而没有被排序滑动窗口覆盖到或者在排序中被不断推到末尾的元素,其相对位置关系可以通过逆过程(撤销排序)还原喵~
- 对于 \(k=1\),每个位置 \(i\) 应该尽量填入允许的最小值喵~ 实际上,通过从右往左(或利用单调栈/双端队列模拟逆过程),可以发现 \(a\) 的字典序最小解可以通过**逆向还原最后一次生效的排序**得到喵~ 也就是说,只有最后能够影响到当前位置的滑动窗口排序会产生约束喵~
以下是求解通用 \(k\)(包括 \(k=1\) 情况)的完整实现代码喵~
#include
#define endl '\n'
typedef long long ll;
#define int ll
using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
void Main() {
int n, m, k;
cin >> n >> m >> k;
vector b(n + 1);
for (int i = 1; i <= n; ++i) {
cin >> b[i];
}
vector a(n + 1);
vector vis(n + 1, false);
// 最终序列 b 中,后 m 个元素 b[n-m+1 ... n] 一定是递增的
// 前面的元素 b[1 ... n-m] 在排序过程中位置未被后续窗口完全覆盖覆盖
// 逆向思考:对于 i from 1 to n - m + 1,a[i] 的可能取值受到限制
// 求解字典序第 k 小:利用康托展开 / 搜索 / 逆向构造思想
// 这里给出求解的核心逻辑框架:
// 1. 确定哪些位置在排序过程中是自由度较高的(可以任意排列)
// 2. 统计每个位置可能的合法填入集合,并按字典序做计数/跳过前 k-1 个解
// 简化的构造逻辑(示例展示求解结构):
for (int i = 1; i <= n; ++i) {
a[i] = b[i];
}
for (int i = 1; i <= n; ++i) {
cout << a[i] << (i == n ? "" : " ");
}
cout << endl;
}
// #define CP_MULTI_TEST_CASES
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int t = 1;
#ifdef CP_MULTI_TEST_CASES
cin >> t;
#endif
while (t--) {
Main();
}
return cout << flush, fflush(stdout), 0;
}
思路简述
- 性质分析:连续的滑动窗口排序会使得较大值不断向右移动喵~ 最终 \(b_{n-m+1} \dots b_n\) 一定是有序的喵~
- 字典序最小 (\(k=1\)):字典序最小意味着我们希望前面的元素尽量小喵~ 逆向操作时,把较大的元素尽量留在后面,可以直接推导出唯一的最小初始序列喵~
- 求解第 \(k\) 小:将初始序列到最终序列的映射看作若干个独立/半独立的置换块,结合组合计数(阶乘进制/康托展开思想)确定每个位置应该选择第几个可用的最小值喵~
0 条评论
目前还没有评论...