斯特林数是组合数学中一类特殊的数,有着广泛的应用,本文主要讨论第二类斯特林数的推导,性质与应用。
组合问题最基础的模型就是球盒问题了。球盒问题即为 n n 个球放入 m m 个盒子的方案数。众所周知,球盒问题有 2×2×2=8 2 × 2 × 2 = 8 种类型,分别为:
大量组合数学问题都源于这 8 8 种基础的模型,让我们一种一种讨论。
这个问题可以转化为整数的 m m 拆分数,即把 n n 分解成 m m 个数相加的方案数,记为 pk(n) p k ( n ) 。 有如下递推式成立:
类似上一种情况,我们也可以用整数拆分数来表示。
我们枚举拆分出数的个数,可以得到下式:
这是一种比较简单的情况。我们可以用隔板法解决,转化为在 n n 个球中间 n−1 n − 1 个空隙插入 m−1 m − 1 个隔板。方案数为:
类似上一种情况,我们也可以用隔板法,不同的是,这种情况隔板可以相邻。我们有两种思路,一种是预先在每个盒子种放入一个球(即球数增加 m m 个)转化为不可以有空盒子的情况,另一种方案是考虑一个 01 01 序列, 0 0 代表球, 1 1 代表隔板,隔板共 m−1 m − 1 个。即在长 n+m−1 n + m − 1 的 01 01 序列选出 m−1 m − 1 个位置放 1 1 , 其余放 0 0 的方案数。两种思路都可以得到下式:
这个问题即为 n n 个元素划分为 k k 个等价集合的方案数,这就是我们今天的重点——第二类斯特林数的定义。
第二类斯特林数通常记作 S(n,m) S ( n , m ) 或 {nm} { n m } 。
其递推式为:
对于这一种情况,我们可以枚举几个盒子非空,即为:
对于这一种情况,我们发现,在第 5 5 种情况里的每一个方案,我们把每一个盒子标号,就对应着 m! m ! 种方案。所以这种情况的答案为:
终于到了最后一种情况,也是最简单的一种。
对于这种情况,我们可以枚举每一个球放在哪个盒子里,就得到了答案:
以上我们解决了球盒问题的所有 8 8 种情况,现在看来,是不是很 naive n a i v e 呢?
第二类斯特林数是 n n 个元素划分为 k k 个等价集合的方案数。也就是之前介绍的球盒问题的第 5 5 种类型。
第二类斯特林数除了解决组合问题,还有一些神奇的性质。
第二类斯特林数可以使用下降幂来表示数的幂,即满足下式:
我们还可以利用这个性质,再次得到第二类斯特林数的递推公式:
转自riteme的博客
现在,你应该对第二类斯特林数有了深入的理解了。
根据第二类斯特林数的递推式,我们已经可以以 O(n2) O ( n 2 ) 的复杂度求第二类斯特林数了。那么对于 n,m n , m 比较大的情况,我们应该怎么办呢?这里介绍一个技巧——斯特林反演。
斯特林反演的公式如下:
给定一棵 n n 个点的树和一个常数 k k , 对于每个 i i , 求
常见套路:先大力把幂转为下降幂,再转为组合数:
#include
#include
#include
#include
#include
#include
#include
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const ll mod = 10007;
struct edge{
int to, nxt;
}e[100005];
int h[50005], cnt;
void addedge(int x, int y){
cnt++; e[cnt].to = y; e[cnt].nxt = h[x]; h[x] = cnt;
cnt++; e[cnt].to = x; e[cnt].nxt = h[y]; h[y] = cnt;
}
ll f[50005][151];
int n, k, L;
void dfs(int x, int fa){
f[x][0] = 1;
for(int i = h[x]; i; i = e[i].nxt){
if(e[i].to == fa) continue;
dfs(e[i].to, x);
f[x][0] = (f[x][0] + f[e[i].to][0]) % mod;
for(int j = 1; j <= k; j ++){
f[x][j] = (f[x][j] + f[e[i].to][j] + f[e[i].to][j - 1]) % mod;
}
}
}
void dfs2(int x, int fa){
for(int i = h[x]; i; i = e[i].nxt){
if(e[i].to == fa) continue;
for(int j = k; j >= 2; j --){
f[e[i].to][j] = (f[x][j] + f[x][j - 1] - 2 * f[e[i].to][j - 1] - f[e[i].to][j - 2] + mod * 3) % mod;
}
f[e[i].to][1] = (f[x][1] + f[x][0] - 2 * f[e[i].to][0] + mod + mod) % mod;
f[e[i].to][0] = f[x][0];
dfs2(e[i].to, x);
}
}
ll S[205][205];
ll fac[205];
int main(){
scanf("%d%d%d", &n, &k, &L);
int now, a, b, q;
scanf("%d%d%d%d", &now, &a, &b, &q);
for(int i = 1; i < n; i ++){
now = (now * a + b) % q;
int tmp=((iint u=i-now%tmp; int v=i+1;
addedge(u, v);
}
dfs(1, 0);
dfs2(1, 0);
S[0][0] = 1;
for(int i = 1; i <= k; i ++){
S[i][0] = 0;
for(int j = 1; j <= i; j ++){
S[i][j] = (S[i - 1][j - 1] + j * S[i - 1][j]) % mod;
}
}
fac[0] =1;
for(int i = 1; i <= k; i ++){
fac[i] = fac[i - 1] * i % mod;
}
for(int i = 1; i <= n; i ++){
ll ans = 0;
for(int j = 0; j <= k; j ++){
ans = (ans + S[k][j] * fac[j] % mod * f[i][j])% mod;
}
printf("%lld\n", ans);
}
return 0;
}
定义一个无向图的权值为所有节点的度数的 k k 次方之和. 求所有 n n 个点的简单无向图(无重边无自环)的权值之和, 对 998244353 998244353 取模。
n≤109,k≤2×105 n ≤ 10 9 , k ≤ 2 × 10 5 。
由于每个点是等价的,我们不妨考虑一个点对答案的贡献,最后乘 n n 即为答案。
我们枚举这个点连出去的 n−1 n − 1 条边是否存在,其他的部分(一个 n−1 n − 1 个点的无向图)与这个点的度数无关,所以再乘上 n−1 n − 1 个点的简单无向图的数量。所以答案为:
#include
#define MAXN 524288
using namespace std;
typedef long long ll;
const ll mod = 998244353;
ll qpow(ll a, ll b){
ll ret = 1;
for(; b; b >>= 1, a = a * a % mod){
if(b & 1) ret = ret * a % mod;
}
return ret;
}
ll inv3, pw3[MAXN + 1], ipw3[MAXN + 1];
ll fac[MAXN], ifac[MAXN];
int N;
void pre(){
for(int i = 1; i <= N; i <<= 1) pw3[i] = qpow(3, (mod - 1) / i);
inv3 = qpow(3, mod - 2);
for(int i = 1; i <= N; i <<= 1) ipw3[i] = qpow(inv3, (mod - 1) / i);
fac[0] = 1;
for(int i = 1; i < N; i ++) fac[i] = fac[i - 1] * i % mod;
ifac[N - 1] = qpow(fac[N - 1], mod - 2);
for(int i = N - 1; i >= 1; i --) ifac[i - 1] = ifac[i] * i % mod;
}
void bit_reverse(ll *r, int N){
for(int i = 0, j = 0; i < N; i ++){
if(i < j) swap(r[i], r[j]);
for(int l = N >> 1; (j ^= l) < l; l >>= 1);
}
}
void NTT(ll *r, int N, int flag){
bit_reverse(r, N);
for(int i = 2; i <= N; i <<= 1){
int m = i >> 1;
for(int j = 0; j < N; j += i){
ll w = 1, wn = pw3[i];
if(flag == -1) wn = ipw3[i];
for(int k = 0; k < m; k ++){
ll z = w * r[j + k + m] % mod;
r[j + k + m] = (r[j + k] - z + mod) % mod;
r[j + k] = (r[j + k] + z) % mod;
w = w * wn % mod;
}
}
}
if(flag == -1){
ll invn = qpow(N, mod - 2);
for(int i = 0; i < N; i ++){
r[i] = r[i] * invn % mod;
}
}
}
ll a[MAXN], b[MAXN];
int n, k;
int main(){
scanf("%d%d", &n, &k);
N = 1;
while(N < 2 * k) N <<= 1;
pre();
for(int i = 0; i <= k; i ++){
if(i & 1) a[i] = mod - 1;
else a[i] = 1;
a[i] = a[i] * ifac[i] % mod;
b[i] = qpow(i, k) * ifac[i] % mod;
}
NTT(a, N, 1);
NTT(b, N, 1);
for(int i = 0; i < N; i ++) a[i] = a[i] * b[i] % mod;
NTT(a, N, -1);
n--;
ll ans = 0, tmp = qpow(2, n) % mod, inv2 = qpow(2, mod - 2);
for(int i = 0; i <= k; i ++){
ans = (ans + tmp * a[i]) % mod;
tmp = tmp * (n - i) % mod * inv2 % mod;
}
n++;
ans = ans * qpow(2, 1ll * (n - 1) * (n - 2) / 2) % mod * n % mod;
printf("%lld\n", ans);
return 0;
}
参考资料:
斯特灵数 - 维基百科
差分序列与Stirling数 - riteme的博客