前言
斯特林数是计数问题中的重要的特殊数,并且可以用来转化多项式,十分好用,这里整理一下相关的重要知识点。
定义
第一类斯特林数
[kn] 指的是使
n 个不同元素排成
k 个按顺序的不同环的方法数。
第二类斯特林数
{kn} 指的是将
n 个不同元素放入
k 个相同容器的方法数。
斯特林数的递推式
第一类斯特林数:首先
[0n]=[n=0],对于
[kn],考虑:
- 由于图有若干个换组成,再对环按指定方向定向,使每个点恰有一个入读和一个出度,我们可以将每个元素的唯一出边指向的点记下来使环映射到一个排列 pi 上。
- 若 pn=n 意味着第 n 个元素自成一环,那么剩下 n−1 个元素有 [k−1n−1] 种可能。
- 否则若 pn=a,pb=n,考虑在删除 n 后改 pb=a 得到 n−1 个元素,k 个环的状态,有 [kn−1] 种可能,而 pn 有 n−1 种情况,每种情况对称。
得出
[kn]=(n−1)[kn−1]+[k−1n−1]。
对于第二类斯特林数,
{0n}=[n=0],第
n 个元素要么放一个新盒子要么放老盒子,知
{kn}=k{kn−1}+{k−1n−1}。
第一类斯特林数和
公式:
∑k=0n[kn]=n!。
证明:类似于转移公式上的方法,
将每种
n 个元素组环的方法映射到排列上,由于有
n! 个排列故而
∑k=0n[kn]=n!。
第二类斯特林数通项公式
公式:
{kn}=k!∑t=0k(−1)t(tk)(k−t)n
证:
由于
kn=∑t=0k(tk)t!{tn},由二项式反演得到
k!{kn}=∑t=0k(−1)t(tk)(k−t)n,故
{kn}=k!∑t=0k(−1)t(tk)(k−t)n。
斯特林数与数的幂
公式一:
xn=∑k=1n{kn}xk。
证:将
n 个元素任意放到
x 个不同桶中有
xn 种情况。我们还可以将
n 个元素先分
k 组在放到
k 个不同的桶中得出同样的效果,有
∑k=1n{kn}xk 种,由此得证。
公式二:
xkˉ=∑k=0n[kn]xk。
证:我们设
Fn(x)=∑k=0n[kn]xk,由
[kn]=(n−1)[kn−1]+[k−1n−1] 得:
Fn(x)=(n−1+x)Fn−1(x)=∏i=1n(i+x−1)F0(x)=∏i=1n(i+x−1)=xkˉ。
公式三:
xn=∑k=1n(−1)n−k[kn]xk。
证:我们设
Gn(x)=∑k=0n(−1)n−k[kn]xk,由
[kn]=(n−1)[kn−1]+[k−1n−1] 得:
Gn(x)=(x+1−n)Gn−1(x)=∏i=1n(i+x−1)G0(x)=∏i=1n(i+x−1)=xk。
公式四:
xn=∑k=1n(−1)n−k{kn}xkˉ。
证:
(−1)kxkˉ=(−x)k。则:
(−x)n(−1)nxnxn=k=1∑n{kn}(−x)k=k=1∑n{kn}(−1)kxkˉ=k=1∑n{kn}(−1)kxkˉ=k=1∑n{kn}(−1)k−nxkˉ=k=1∑n{kn}(−1)n−kxkˉ
简记:
斯特林反演
我们由
xn=∑k=1n(−1)n−k{kn}xkˉ=∑1≤m≤k≤n(−1)n−k{kn}[mk]xm 知
∑m≤k≤n(−1)n−k{kn}[mk]=[n=m],再由
xn=∑k=1n(−1)n−k[kn]xk=∑1≤m≤k≤n(−1)n−k[kn]{mk}xk 知
∑m≤k≤n(−1)n−k[kn]{mk}=[n−m]。
根据这两个式子推出:
fn=i=1∑n[in]gifn=i=1∑n{in}gi⟹gn=i=1∑n(−1)n−i{in}fi⟹gn=i=1∑n(−1)n−i[in]fi
得到斯特林反演公式。
例题集
1. 洛谷 P4609
求长为
n(n≤50000) 排列的总数,使得对于维护单调递减的单调栈,从右往左插入时栈大小为
A(A≤100),从左往右是栈大小为
B(B≤100)。
解:这意味着最大值左边
A−1 组,右边
B−1 组。每组任意排列要保证最大值在一边。这时每组可以是做环,转为序列时从最大元素开始向逆时针转遍历,得到
[A+B−2n−1](A−1A+B−2)。
CPP#include<bits/stdc++.h>
using namespace std;
int T,n,A,B,c[209][209],s[500009][209];
const int mod = 1000000007;
int main(){
scanf("%d",&T);
c[0][0] = s[0][0] = 1;
for(int i = 1; i <= 200; i ++)
{
c[i][0] = 1;
for(int j = 1; j <= i; j ++){
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
}
for(int i = 1; i <= 50000; i ++){
s[i][0] = 0;
for(int j = 1; j <= i && j <= 200; j ++){
s[i][j] = (1ll * s[i - 1][j] * (i - 1) % mod + s[i - 1][j - 1]) % mod;
}
}
while(T--){
scanf("%d %d %d",&n,&A,&B);
printf("%d\n",1ll * c[A + B - 2][A - 1] * s[n - 1][A + B - 2] % mod);
}
return 0;
}
2. 洛谷 P8143
给定
n(n≤106),求
∑k∣2[kn]。
解:
对于
n=1 答案显然为
0。
否则:
- ∑k=1n[kn]=n!;
- ∑k∣2[kn]−∑k∤2[kn]=∑k(−1)k[kn]=(−1)kˉ;
在
n>1 时
(−1)kˉ=0,此时答案为
2n!。
3. CF932E
给定
n,k(n≤109,k≤5000),求
∑i=1n(in)ik。
解:
i=1∑n(in)ik=i=1∑nj=1∑k(in)[jk]ij=i=1∑nj=1∑k[jk]i!(n−i)!n!(i−j)!i!=i=1∑nj=1∑k[jk](n−i)!(i−j)!n!=j=1∑ki=j∑n[jk](n−i)!(i−j)!n!=j=1∑ni=j∑n[jk]nj(n−i)!(i−j)!(n−j)!=j=1∑n[jk]nji=j∑n(n−i)!(i−j)!(n−j)!=j=1∑n[jk]nji=j∑n(i−jn−j)=j=1∑n[jk]nj2n−j
得到
O(k2) 的算法。