本例涉及到摊还分析,动态规划中有一个很常见的相关技巧就是未来费用提前计算
-
首先很容易想到对 $[1\cdots n]$ 的排序代价 $C$ 满足
$$ n-1 \leqslant C \leqslant \frac{n(n+1)}{2} - 1 $$ -
可以反着来构造,最开始令序列有序 $a = \{1, 2, \cdots n\}$
$d(i)$ 表示从 $i$ 出发,找到最小元素下标为 $j$,需要走的距离,即 $\text{len}[i \cdots j]$
我们是反着构造的,所以实际上 $d(i)=\text{len}[i \cdots j]$,$j$ 为从 $i \to n$ 最大元素的下标
初始阶段,令 $C \leftarrow C-(n-1) \quad d(i) = 1$ -
如果 $C > 0$,就说明 $\text{len}[i \cdots j] > 1$,产生了额外代价
-
于是可以贪心构造,如果在排序的某个阶段,$\forall i \in [n-2 \to 0]$
$[i \cdots j]$,让 $a_j$ 为最小,$a_i$ 为第 $2$ 小
反着构造就是 $[i \cdots j]$,$a_i$ 为当前最小,$a_j$ 为第 $2$ 小,反转 $[i \cdots j]$
此时代价就是每一段 $[i \cdots j]$ 能够取到的最坏代价 $C’$ -
$\textbf{for} \ \forall i \in [n-2 \to 0]$,$i$ 能往前走的最长距离是 $\max d(i) = n-1-i$
只要 $C > 0$,执行
$$ \textbf{for} [1 \cdots n-1-i]: \ d(i) += 1, \ C -= 1 $$
即让 $i$ 尽可能往前走,贪心地让 $[i\cdots n-1]$ 中最小的元素尽可能一直位于 $a_{n-1}$ 处
$d(i)$ 初始时为 $1$,$\max d(i) = n-i$
此时需要反转的区间是 $[i, j] = [i \cdots i+d(i)-1]$ -
反转 $[i, j] = [i, i+d(i)-1]$,在第 $i-1$ 轮迭代中
$$ \begin{gathered} a_{i-1} < a[i \cdots j], \quad \text{对于区间} [i-1\cdots j] \\\ a_{i-1} \text{为最小,} \quad a_{j} \text{第 2 小} \end{gathered} $$
$C > 0$,第 $i-1$ 阶段,$[i\cdots n-1]$ 中最小的元素尽可能一直位于 $a_{n-1}$ 处
又因为 $a_{i-1} < \forall \ a[i \cdots n-1]$
$a_{i-1}$ 最小,$a_{n-1}$ 第 $2$ 小,反转 $a[i-1, n-1]$ 即可 -
反着迭代 $n-1$ 次,即可得到原序列
最后对于不足以填充 $[i \cdots n-1]$ 的 $d(i)$,也不会影响结果
因为此时 $[i \cdots i+d(i)-1]$ 一定是最后一次迭代的区间,反转之后对应原序列第 $i$ 次迭代
恰好满足 $a_{i+d(i)-1}$ 是 $[i \cdots i+d(i)-1]$ 区间最小元素,对 $i-1$ 及之前的迭代没有影响
因为前面的元素 $a[0 \cdots i-1] < a[i \cdots n]$,$\forall j \in [0, i-1], d(j) = 1$
#define _for(i, l, r) for (int i = (l); i < (r); i++)
using namespace std;
const int maxn = 100 + 10;
int n, C;
void solve() {
vector<int> d(n-1, 1);
C -= (n-1);
for (int i = n-2; i >= 0; i--) {
for (int _ = 1; _ <= n-1-i; _ ++) {
if (C > 0) {
C -= 1, d[i] += 1;
}
}
}
vector<int> a(n);
for (int i = 0; i < n; i++) a[i] = i+1;
for (int i = n-2; i >= 0; i--) {
reverse(a.begin()+i, a.begin()+i+d[i]);
}
for (auto x : a) printf("%d ", x);
printf("\n");
}
int main() {
freopen("input.txt", "r", stdin);
int T, kase = 0;
cin >> T;
while (T--) {
printf("Case #%d: ", ++kase);
cin >> n >> C;
// impossible
int lo = n-1, hi = n*(n+1) / 2 - 1;
if (C < lo || C > hi) {
puts("IMPOSSIBLE");
continue;
}
// solve
solve();
}
}