第一类斯科特数
第一类斯科特数 表示 将个不同元素排列成恰好个”循环”的排列数
对于循环,我们可以理解为与 与均为一类循环,因为将这些数无限写下去的形式上是一致的
例子
- ,即所有排列都在一个大循环中,此时有两种方案{(1,2,3),(1,3,2)}
- ,此时有 (1)(2,3) | (2)(1,3) | (3)(1,2) 这样的组合方式
- ,此时每个元素各自组成一个循环
第一类斯科特数递推公式
其中递推的初始条件我们规定为
上面为无符号第一类斯科特数的递归方式 如果想获得有符号类的可以使用 方式得到
应用:
- 在排列的循环结构或者有向图的环的计数中使用
- 在阶乘多项式展开中使用
- 其中 即有符号的第一类斯科特数
第二类斯科特数
第二类斯科特数 表示将 个不同元素划分为恰好 个非空无序集合的方案数
对与三个不同元素{1,2,3}划分
- ,即所有元素处于一个集合中
- :
对第二类斯特林数有递推公式
- 初始条件我们规定
对与第二类斯特林数,我们有显式公式:
可以在复杂度下计算出其值,可以结合快速组合数与快速幂计算,在最后给出板子
第一类欧拉数
i64 ksm(i64 a, i64 b, i64 mod = MOD)
{
i64 res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
struct Stirling
{
vector<i64> fac, inv_fac;
i64 MAX_N;
Stirling(i64 MAXK)
{
MAX_N = MAXK;
fac.resize(MAX_N);
inv_fac.resize(MAX_N);
init();
}
// 预处理 [0, MOD-1] 阶乘及逆元
void init()
{
fac[0] = 1;
for (i64 i = 1; i < MAX_N; i++) fac[i] = fac[i - 1] * i % MOD;
inv_fac[MAX_N - 1] = ksm(fac[MAX_N - 1], MOD - 2);
for (i64 i = MAX_N - 2; i >= 0; i--) inv_fac[i] = inv_fac[i + 1] * (i + 1) % MOD;
}
// 计算 C(n, k) mod MOD,要求 n, k < MOD
inline i64 C_small(i64 n, i64 k)
{
if (k < 0 || k > n) return 0;
return fac[n] * inv_fac[k] % MOD * inv_fac[n - k] % MOD;
}
// Lucas 定理递归求解大组合数
i64 C(i64 n, i64 k)
{
if (k < 0 || k > n) return 0;
if (n < MOD && k < MOD) return C_small(n, k);
return C(n / MOD, k / MOD) * C_small(n % MOD, k % MOD) % MOD;
}
i64 A(int n, int k) const
{
if (k < 0 || k > n) return 0;
return fac[n] * inv_fac[n - k];
}
// 第二类斯特林数的显式表示
i64 get_Stirling(int n, int k)
{
if (k < 0 || k > n) return 0;
i64 res = 0;
for (int i = 0; i <= k; ++i) {
i64 term = C(k, i) * ksm(k - i, n) % MOD;
// 符号: (-1)^i,根据公式 S(n,k) = 1/k! * sum_{i=0..k} (-1)^i C(k,i) (k-i)^n
if (i & 1)
res = (res - term + MOD) % MOD;
else
res = (res + term) % MOD;
}
res = res * inv_fac[k] % MOD; // 除以 k!
return res;
}
// 第二类欧拉数计算
i64 sec_Eulr(int n, int k)
{
return (get_Stirling(n, k) * fac[k]) % MOD;
}
};
其实就是在快速组合数上进行了添加