组合意义
记 (S(n,m)) 表示,把 (n) 个不同的小球,放在 (m) 个相同的盒子里,且每个盒子至少有 (1) 个球,的方法数。
记 (S(n,m)) 表示,把 (n) 个不同的元素,划分为 (m) 个非空集合的方法数。
显然 (S(0,0)=1) ,而 (S(n,0)) 当 (ngeq 1) 时显然是不合法的方案, (S(0,m)) 当 (mgeq 1) 时因为至少有 (1) 个空盒子所以也显然是不合法的方案。
递推
(S(n,m)=S(n-1,m-1)+m*S(n-1,m))
含义是,多一个新的球,那么假如再新加一个盒子去装,显然是一种办法,否则要把这个球放在先前已有的 (m) 个盒子中的任意一个,由于新加的这个小球是全新的,所以这个和组合数的递推公式不一样。
使用递推去求解需要 (O(nm))。
ll S[1005][1005];
void GetS(int n, int m) {
memset(S, 0, sizeof(S));
S[0][0] = 1;
for(int i = 1; i <= n; ++i) {
int cj = min(i, m);
for(int j = 1; j <= cj; ++j)
S[i][j] = (S[i - 1][j - 1] + 1ll * j * S[i - 1][j]) % MOD;
}
for(int i = 1; i <= n; ++i) {
int cj = min(i, m);
for(int j = 1; j <= cj; ++j)
printf(" %lld", S[i][j]);
puts("");
}
return;
}
通项
(S(n,m)=frac{1}{m!}(sumlimits_{i=0}^{m}(-1)^i*C(m,i)*(m-i)^n))
使用通项去求解只需要 (O(mlogn))。
ll qpow(ll x, int n) {
ll res = 1;
if(x >= MOD)
x %= MOD;
while(n) {
if(n & 1) {
res = res * x;
if(res >= MOD)
res %= MOD;
}
x = x * x;
if(x >= MOD)
x %= MOD;
n >>= 1;
}
return res;
}
ll S(int n, int m) {
ll sum = 0;
sum += qpow(m, n);
ll fac1 = 1, fac2 = 1;
for(int i = 1; i <= m; ++i) {
fac1 *= (m + 1 - i);
if(fac1 >= MOD)
fac1 %= MOD;
fac2 *= i;
if(fac2 >= MOD)
fac2 %= MOD;
ll tmp = (i & 1) ? (MOD - 1) : (1);
tmp *= fac1;
if(tmp >= MOD)
tmp %= MOD;
tmp *= qpow(fac2, MOD - 2);
if(tmp >= MOD)
tmp %= MOD;
tmp *= qpow(m - i, n);
if(tmp >= MOD)
tmp %= MOD;
sum += tmp;
if(sum >= MOD)
sum -= MOD;
}
sum *= qpow(fac1, MOD - 2);
if(sum >= MOD)
sum %= MOD;
return sum;
}
卷积
使用卷积可以求出同一个 (n) 在不同的 (m) 取值下的所有的斯特林数。详见下面博客。