Codeforces 1516E Baby Ehab Plays with Permutations
逆向考虑问题。首先,考虑计算有多少个排列它恰好需要 $j$ 次 swap 来排序。
考虑一个排列它至少需要多少次 swap 才能排序呢?我们将这个排列进行循环分解,对于一个大小为 $k$ 的循环,显然我们可以通过依次交换相邻两项的方式,在 $k - 1$ 步还原这个循环。
所以,如果一个长度为 $n$ 的排列 $p$ 中有 $c$ 个循环,它就需要 $n - c$ 步来还原。
而,需要 $j$ 次 swap 的排列数量,也就是 $n - c = j \Rightarrow c = n - j$ 的那些排列。
那么,根据第一类斯特林数的定义,我们得出了一个重要的结论:需要 $j$ 步来还原的排列数量为 $\begin{bmatrix} n \\ n - j \end{bmatrix}$!
也就是说,题目的答案依次是
$$
\begin{bmatrix} n \\ n - 1 \end{bmatrix}, \begin{bmatrix} n \\ n \end{bmatrix} + \begin{bmatrix} n \\ n - 2 \end{bmatrix}, \begin{bmatrix} n \\ n - 1 \end{bmatrix} + \begin{bmatrix} n \\ n - 3 \end{bmatrix} \cdots
$$
但正常根据递推式来求第一类斯特林数是 $O(n^2)$ 的,有没有更快的方法呢?
注意到虽然 $n$ 很大,但是 $k \le 200$,考虑利用这个性质。
其实当 $n - m$ 很小的时候,有一种比较快的求值公式:
$$ (-1)^{n-m}\begin{bmatrix} n \\ m \end{bmatrix} = \sum_{k = 0}^{n - m} (-1)^k \binom{n - 1 + k}{m - 1} \binom{(n - m) + n}{(n - m) - k} \begin{Bmatrix} (n - m) + k \\ k \end{Bmatrix} $$
https://mathworld.wolfram.com/StirlingNumberoftheFirstKind.html 中的 (21) 式,做了一些优美的变形。
其实,如果把等号左边的第一类斯特林数改为第二类斯特林数,等号右边的第二类斯特林数改为第一类斯特林数,等式仍然成立。
于是 $O(k^2)$ 递推第二类斯特林数,按照这个式子即可求解。
时间复杂度 $O(k^3)$。要线性预处理逆元。
#include <bits/stdc++.h> #define int long long using namespace std; const int MOD = 1e9 + 7, K = 405; int S[K][K], ans[K], inv[K]; int C(int n, int m) { if(n < m || n < 0 || m < 0) return 0; if(m > n - m) m = n - m; int res = 1; for(int i = 1; i <= m; i++) res = res * (n - i + 1) % MOD * inv[i] % MOD; return res; } int s(int n, int m) { if(m < 0) return 0; int res = 0; for(int k = 0; k <= n - m; k++) { int val = C(n - 1 + k, m - 1) * C(n - m + n, n - m - k) % MOD * S[n - m + k][k] % MOD; if(k & 1) res = (res - val + MOD) % MOD; else res = (res + val) % MOD; } return res; } int n, k; signed main() { cin >> n >> k; inv[1] = 1; for(int i = 2; i < K; i++) inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD; S[0][0] = 1; for(int i = 1; i <= k * 2; i++) for(int j = 1; j <= i; j++) S[i][j] = (S[i - 1][j - 1] + j * S[i - 1][j] % MOD) % MOD; for(int i = 0; i <= min(n, k); i++) if(i & 1) ans[i] = MOD - s(n, n - i); else ans[i] = s(n, n - i); for(int i = 1; i <= k; i++) { if(i - 2 >= 0) ans[i] = (ans[i] + ans[i - 2]) % MOD; printf("%lld ", ans[i]); } return 0; }