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$ 步来还原的排列数量为 $egin{bmatrix} n \ n - j end{bmatrix}$!

也就是说,题目的答案依次是
$$
egin{bmatrix} n \ n - 1 end{bmatrix}, egin{bmatrix} n \ n end{bmatrix} + egin{bmatrix} n \ n - 2 end{bmatrix}, egin{bmatrix} n \ n - 1 end{bmatrix} + egin{bmatrix} n \ n - 3 end{bmatrix} cdots
$$
但正常根据递推式来求第一类斯特林数是 $O(n^2)$ 的,有没有更快的方法呢?

注意到虽然 $n$ 很大,但是 $k le 200$,考虑利用这个性质。

其实当 $n - m$ 很小的时候,有一种比较快的求值公式:

$$ (-1)^{n-m}egin{bmatrix} n \ m end{bmatrix} = sum_{k = 0}^{n - m} (-1)^k inom{n - 1 + k}{m - 1} inom{(n - m) + n}{(n - m) - k} egin{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;
}
原文地址:https://www.cnblogs.com/syksykCCC/p/CF1516E.html