第一 / 二类 Stirling 数的一些平庸求法


综述

看到洛谷上有求 Stirling 数的神奇板子, 于是就想写 = =

虽然不知道会有什么用处, 其实是不想写题又不敢颓废吧

大概是一些式子推推推然后拉板子算算算…

第一类 Stirling 数

本文采用 $\begin{bmatrix} n \ m \end{bmatrix}$ 表示第一类 Stirling 数.

Part 1 行

我们知道, $x$ 的 $n$ 次上升幂就是第 $n$ 行第一类 Stirling 数的生成函数, 即

论据如下, 归纳法易证

此时通过分治 FFT 可以在 $O(n \log^2 n)$ 的时间复杂度内计算.

但是用倍增可以做到 $O(n \log n)$.

考虑倍增, 有

写成这个形式鬼知道怎么推, 于是

设 $f(x) = \prod\limits_{i=0}^{n-1} (x+i) = \sum\limits_{i=0}^n a_i x^i$, 如果能根据 $f(x)$ 算 $f(x+n)$ 就可以了.

交换 $i$, $j$ 枚举顺序, 得

展开, 有

最后一个和式的卷积形式并不显然, 还要将其中一项翻转… 不过还是很套路的

代码实现

UPD on 2020.5.22 重写了这部分的代码

> 点击显示 / 隐藏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Luogu P5408
// DeP
#include <cstdio>
#include <algorithm>
using namespace std;

const int LOG = 19, MAXN = 1 << LOG | 1, P = 167772161, G = 3;

int W[LOG][MAXN];
int inv[MAXN], ifac[MAXN], fac[MAXN];

int fpow(int base, int b) {
int ret = 1;
while (b > 0) {
if (b & 1) ret = 1LL * ret * base % P;
base = 1LL * base * base % P, b >>= 1;
}
return ret;
}

namespace Poly {
int r[MAXN];
inline void init(const int& Lim, const int& L) {
for (int i = 1; i < Lim; ++i) r[i] = (r[i>>1] >> 1) | ((i & 1) << (L-1));
}

void NTT(int* f, const int& Lim, const int& type) {
for (int i = 1; i < Lim; ++i) if (i < r[i]) swap(f[i], f[r[i]]);
for (int k = 0, Mid = 1; Mid < Lim; ++k, Mid <<= 1) {
const int* w = W[k];
for (int i = 0; i < Lim; i += Mid << 1)
for (int j = 0; j < Mid; ++j) {
int f0 = f[i+j], f1 = 1LL * w[j] * f[i+j+Mid] % P;
f[i+j] = (f0 + f1) % P, f[i+j+Mid] = (f0 - f1 + P) % P;
}
}
if (type < 0) {
int iv = fpow(Lim, P - 2);
for (int i = 0; i < Lim; ++i) f[i] = 1LL * f[i] * iv % P;
reverse(f + 1, f + Lim);
}
}

void Mul(int* f, const int& n, int* g, const int& m, int* h) {
static int A[MAXN], B[MAXN];
int Lim = 1, L = 0;
while (Lim < n + m - 1) Lim <<= 1, ++L;
for (int i = 0; i < Lim; ++i)
A[i] = (i < n)? f[i]: 0, B[i] = (i < m)? g[i]: 0;
init(Lim, L), NTT(A, Lim, 1), NTT(B, Lim, 1);
for (int i = 0; i < Lim; ++i) h[i] = 1LL * A[i] * B[i] % P;
NTT(h, Lim, -1);
}
}

void PolyPre(int N) {
inv[1] = 1;
for (int i = 2; i <= N; ++i)
inv[i] = 1LL * inv[P % i] * (P - P / i) % P;
ifac[0] = fac[0] = 1;
for (int i = 1; i <= N; ++i) {
fac[i] = 1LL * fac[i - 1] * i % P;
ifac[i] = 1LL * ifac[i - 1] * inv[i] % P;
}
for (int w, k = 0, Mid = 1; k < LOG; ++k, Mid <<= 1) {
W[k][0] = 1, w = fpow(G, (P - 1) / (Mid << 1));
for (int i = 1; i < Mid; ++i)
W[k][i] = 1LL * w * W[k][i - 1] % P;
}
}

namespace Stirling {
void offset(int* f, const int& Mid, const int& k, int* g) {
static int A[MAXN], B[MAXN];
for (int pwk = 1, i = 0; i < Mid; ++i) {
A[Mid - i - 1] = 1LL * f[i] * fac[i] % P;
B[i] = 1LL * pwk * ifac[i] % P, pwk = 1LL * pwk * k % P;
}
Poly::Mul(A, Mid, B, Mid, A);
for (int i = 0; i < Mid; ++i)
g[i] = 1LL * A[Mid - i - 1] * ifac[i] % P;
}

void solve(int* f, const int& n) {
static int f0[MAXN];
if (!n)
return void( f[0] = 1 );
int Mid = n / 2;
// 得到 f(x + Mid)
solve(f, Mid), offset(f, Mid + 1, Mid, f0);
Poly::Mul(f, Mid + 1, f0, Mid + 1, f0);
if (n & 1) {
// n 为奇数时, 有乘 (x + (n-1)) 的情况, 此时直接乘就好了.
for (int i = 0; i <= n; ++i)
f[i] = (((i > 0)? f0[i - 1]: 0) + 1LL * (n - 1) * f0[i] % P) % P;
} else
for (int i = 0; i <= n; ++i) f[i] = f0[i];
}
}

int n;
int f[MAXN];

int main() {
#ifndef ONLINE_JUDGE
freopen("input.in", "r", stdin);
#endif
scanf("%d", &n);
PolyPre(n), Stirling::solve(f, n);
for (int i = 0; i <= n; ++i)
printf("%d%c", f[i], " \n"[i == n]);
return 0;
}

Part 2 列

首先有第一类 Stirling 数的一个性质, 即

同样也是归纳法易证.

对于 $(1+x)^t$, 由牛顿二项式定理, 可知

其中 $\binom{t}{i} = \frac{t (t-1) \cdots (t-i+1)}{i!}$, 不妨写作

愉快套公式, 得

交换枚举顺序, 得

同时, 有

对应项系数相等, 得

可以看到, 其中 $i$ 为给定的值, 利用多项式幂函数算就好了.

值得注意的是, $[x^0] \ln (1+x)$ 有不等于 $1$ 的可能, 所以要用鲁棒性更强的板子 = =

时间复杂度是带常数的 $O(n \log n)$, 大概是跑不过 $O(n \log^2 n)$ 的带常数吧

代码实现

> 点击显示 / 隐藏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// Luogu P5409
// DeP
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 131075 << 1;
const int P = 167772161, G = 3, iG = 55924054;

int fac[MAXN], ifac[MAXN], inv[MAXN];

int fpow(int base, int b, int m = P) {
int ret = 1;
while (b > 0) {
if (b & 1) ret = 1LL * ret * base % m;
base = 1LL * base * base % m, b >>= 1;
}
return ret;
}

namespace Poly {
int r[MAXN];
inline void init(const int& Lim, const int& L) {
for (int i = 1; i < Lim; ++i) r[i] = (r[i>>1] >> 1) | ((i & 1) << (L-1));
}

void NTT(int* f, int Lim, int type) {
for (int i = 1; i < Lim; ++i) if (i < r[i]) swap(f[i], f[r[i]]);
for (int Mid = 1; Mid < Lim; Mid <<= 1) {
int unit = fpow(type > 0? G: iG, (P-1) / (Mid << 1));
for (int i = 0; i < Lim; i += Mid << 1) {
int w = 1;
for (int j = 0; j < Mid; ++j, w = 1LL * w * unit % P) {
int f0 = f[i+j], f1 = 1LL * w * f[i+j+Mid] % P;
f[i+j] = (f0 + f1) % P, f[i+j+Mid] = (f0 - f1 + P) % P;
}
}
}
if (type < 0) {
int inv = fpow(Lim, P-2);
for (int i = 0; i < Lim; ++i) f[i] = 1LL * inv * f[i] % P;
}
}

void Inv(int* f, int* g, int n) {
static int A[MAXN], B[MAXN];
g[0] = fpow(f[0], P-2);
for (int L = 0, Lim = 1, Mid = 2; Mid < 2*n; Mid <<= 1) {
while (Lim < 2*Mid) Lim <<= 1, ++L;
for (int i = 0; i < Mid; ++i) A[i] = f[i], B[i] = g[i];
for (int i = Mid; i < Lim; ++i) A[i] = B[i] = 0;
init(Lim, L), NTT(A, Lim, 1), NTT(B, Lim, 1);
for (int i = 0; i < Lim; ++i)
g[i] = ((B[i] + B[i]) % P - 1LL * A[i] * B[i] % P * B[i] % P + P) % P;
NTT(g, Lim, -1);
for (int i = min(Mid, n); i < Lim; ++i) g[i] = 0;
}
}

void Der(int* f, int* g, int n) {
for (int i = 1; i < n; ++i) g[i-1] = 1LL * i * f[i] % P;
g[n-1] = 0;
}
void Int(int* f, int* g, int n) {
g[0] = 0;
for (int i = n-1; i; --i) g[i] = 1LL * inv[i] * f[i-1] % P;
}

void Ln(int* f, int* g, int n) {
static int A[MAXN], B[MAXN];
Der(f, A, n), Inv(f, B, n);
int Lim = 1, L = 0;
while (Lim < 2*n) Lim <<= 1, ++L;
for (int i = n; i < Lim; ++i) A[i] = B[i] = 0;
init(Lim, L), NTT(A, Lim, 1), NTT(B, Lim, 1);
for (int i = 0; i < Lim; ++i) A[i] = 1LL * A[i] * B[i] % P;
NTT(A, Lim, -1), Int(A, g, n);
}

void Exp(int* f, int* g, int n) {
static int A[MAXN], B[MAXN], lng[MAXN];
g[0] = 1;
for (int L = 0, Lim = 1, Mid = 2; Mid < 2*n; Mid <<= 1) {
Ln(g, lng, Mid);
while (Lim < 2*Mid) Lim <<= 1, ++L;
for (int i = 0; i < Mid; ++i) A[i] = (f[i] - lng[i] + P) % P, B[i] = g[i];
A[0] = (A[0] + 1) % P;
for (int i = Mid; i < Lim; ++i) A[i] = B[i] = 0;
init(Lim, L), NTT(A, Lim, 1), NTT(B, Lim, 1);
for (int i = 0; i < Lim; ++i) g[i] = 1LL * A[i] * B[i] % P;
NTT(g, Lim, -1);
for (int i = min(Mid, n); i < Lim; ++i) g[i] = 0;
}
}

void Pow(int* f, int* g, int n, int K) {
static int lng[MAXN];
int pos = 0;
while (!f[pos]) ++pos;
int Mid = n - pos;
for (int i = 0; i < Mid; ++i) g[i] = f[i+pos];
int base = g[0], inv = fpow(base, P-2);
for (int i = 0; i < Mid; ++i) g[i] = 1LL * g[i] * inv % P;
Ln(g, lng, Mid);
for (int i = 0; i < Mid; ++i) lng[i] = 1LL * lng[i] * K % P;
Exp(lng, g, Mid);
base = fpow(base, K);
for (int i = 0; i < Mid; ++i) g[i] = 1LL * g[i] * base % P;
pos = min(pos * K, n);
for (int i = n-1; i >= pos; --i) g[i] = g[i-pos];
for (int i = 0; i < pos; ++i) g[i] = 0;
}
}

int n, K;
int f[MAXN];

int main() {
#ifndef ONLINE_JUDGE
freopen("input.in", "r", stdin);
#endif
// input
scanf("%d%d", &n, &K);
// init
int N = n;
ifac[0] = fac[0] = inv[1] = 1;
for (int i = 2; i <= N; ++i) inv[i] = 1LL * (P - P / i) * inv[P % i] % P;
for (int i = 1; i <= N; ++i) fac[i] = 1LL * i * fac[i-1] % P;
ifac[N] = fpow(fac[N], P-2);
for (int i = N; i; --i) ifac[i-1] = 1LL * i * ifac[i] % P;
// solve
f[0] = f[1] = 1;
Poly::Ln(f, f, n+1); Poly::Pow(f, f, n+1, K);
for (int i = 0; i <= n; ++i) f[i] = 1LL * f[i] * ifac[K] % P;
for (int i = 0; i <= n; ++i)
f[i] = 1LL * f[i] * fac[i] % P * (((i-K) & 1)? P-1: 1) % P;
// output
for (int i = 0; i <= n; ++i) printf("%d%c", f[i], " \n"[i==n]);
return 0;
}

第二类斯特林数

本文采用 $\begin{Bmatrix} n \ m \end{Bmatrix}$ 表示第二类 Stirling 数.

Part 1 行

感觉这个是最简单的了

考虑第二类 Stirling 数的组合意义, 也就是把 $n$ 个有区别的小球放入 $m$ 个无区别的盒子里的方案数.

其实这个东西是可以用容斥算的, 可以得到

右边和式的意义为, 在 $m$ 个有区别的盒子里挑 $k$ 个空盒子, 然后把 $n$ 个小球丢到盒子里. 因为容斥时假设盒子有区别, 最后还要除以 $m!$.

把这个式子展开就可以看到卷积的形式了.

通过 NTT 就可以在 $O(n \log n)$ 的时间复杂度内计算.

代码实现

> 点击显示 / 隐藏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// Luogu P5395
// DeP
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 1e6+5;
const int P = 167772161, G = 3, iG = 55924054;

int fpow(int base, int b, int m = P) {
int ret = 1;
while (b > 0) {
if (b & 1) ret = 1LL * ret * base % m;
base = 1LL * base * base % m, b >>= 1;
}
return ret;
}

namespace Poly {
int r[MAXN];
inline void init(const int& Lim, const int& L) {
for (int i = 1; i < Lim; ++i) r[i] = (r[i>>1] >> 1) | ((i & 1) << (L-1));
}

void NTT(int* f, int Lim, int type) {
for (int i = 1; i < Lim; ++i) if (i < r[i]) swap(f[i], f[r[i]]);
for (int Mid = 1; Mid < Lim; Mid <<= 1) {
int unit = fpow(type > 0? G: iG, (P-1) / (Mid << 1));
for (int i = 0; i < Lim; i += Mid << 1) {
int w = 1;
for (int j = 0; j < Mid; ++j, w = 1LL * w * unit % P) {
int f0 = f[i+j], f1 = 1LL * w * f[i+j+Mid] % P;
f[i+j] = (f0 + f1) % P, f[i+j+Mid] = (f0 - f1 + P) % P;
}
}
}
if (type < 0) {
int inv = fpow(Lim, P-2);
for (int i = 0; i < Lim; ++i) f[i] = 1LL * f[i] * inv % P;
}
}
}

int n;
int f[MAXN], g[MAXN];
int fac[MAXN], ifac[MAXN];

int main() {
#ifndef ONLINE_JUDGE
freopen("input.in", "r", stdin);
#endif
// input
scanf("%d", &n);
// init
fac[0] = ifac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = 1LL * fac[i-1] * i % P;
ifac[n] = fpow(fac[n], P-2);
for (int i = n; i; --i) ifac[i-1] = 1LL * ifac[i] * i % P;
// solve
for (int i = 0; i <= n; ++i) {
f[i] = (i & 1)? P - ifac[i]: ifac[i];
g[i] = 1LL * fpow(i, n) * ifac[i] % P;
}
int Lim = 1, L = 0;
while (Lim < 2*n) Lim <<= 1, ++L;
Poly::init(Lim, L), Poly::NTT(f, Lim, 1), Poly::NTT(g, Lim, 1);
for (int i = 0; i < Lim; ++i) f[i] = 1LL * f[i] * g[i] % P;
Poly::NTT(f, Lim, -1);
// output
for (int i = 0; i <= n; ++i) printf("%d%c", f[i], " \n"[i==n]);
return 0;
}

Part 2 列

设第二类 Stirling 数一列的生成函数为 $S_m$, 即

根据第二类 Stirling 数递推式, 可以得到

移项并整理, 得

分式的分母可以使用分治 FFT 得到, 然后求逆并乘一个单项式 (也就是把系数右移) 即可.

所以我们就得到了 $O(n \log^2 n)$ 的做法, 然而存在 $O(n\log n)$ 且小常数的倍增优秀做法, 只是我学不动了…

佐以 O2 食用更佳.

代码实现

> 点击显示 / 隐藏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Luogu P5396
// DeP
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 131075 << 1, LOG = 35;
const int P = 167772161, G = 3, iG = 55924054;

int fpow(int base, int b, int m = P) {
int ret = 1;
while (b > 0) {
if (b & 1) ret = 1LL * base * ret % m;
base = 1LL * base * base % m, b >>= 1;
}
return ret;
}

namespace Poly {
int r[MAXN];
int tmp[LOG][MAXN], ptr = -1;
inline void init(const int& Lim, const int& L) {
for (int i = 1; i < Lim; ++i) r[i] = (r[i>>1] >> 1) | ((i & 1) << (L-1));
}

void NTT(int* f, int Lim, int type) {
for (int i = 1; i < Lim; ++i) if (i < r[i]) swap(f[i], f[r[i]]);
for (int Mid = 1; Mid < Lim; Mid <<= 1) {
int unit = fpow(type > 0? G: iG, (P-1) / (Mid << 1));
for (int i = 0; i < Lim; i += Mid << 1) {
int w = 1;
for (int j = 0; j < Mid; ++j, w = 1LL * w * unit % P) {
int f0 = f[i+j], f1 = 1LL * w * f[i+j+Mid] % P;
f[i+j] = (f0 + f1) % P, f[i+j+Mid] = (f0 - f1 + P) % P;
}
}
}
if (type < 0) {
int inv = fpow(Lim, P-2);
for (int i = 0; i < Lim; ++i) f[i] = 1LL * f[i] * inv % P;
}
}

void Inv(int* f, int* g, int n) {
static int A[MAXN], B[MAXN];
g[0] = fpow(f[0], P-2);
for (int L = 0, Lim = 1, Mid = 2; Mid < 2*n; Mid <<= 1) {
while (Lim < 2*Mid) Lim <<= 1, ++L;
for (int i = 0; i < Mid; ++i) A[i] = f[i], B[i] = g[i];
for (int i = Mid; i < Lim; ++i) A[i] = B[i] = 0;
init(Lim, L), NTT(A, Lim, 1), NTT(B, Lim, 1);
for (int i = 0; i < Lim; ++i)
g[i] = ((B[i] + B[i]) % P - 1LL * A[i] * B[i] % P * B[i] % P + P) % P;
NTT(g, Lim, -1);
for (int i = min(Mid, n); i < Lim; ++i) g[i] = 0;
}
}

void solve(int* f, int L, int R) {
if (L == R) return f[0] = 1, void( f[1] = P - L );
int Mid = (L + R) / 2, *f0 = tmp[++ptr], *f1 = tmp[++ptr];
solve(f0, L, Mid), solve(f1, Mid+1, R);
int Lim = 1, l = 0;
while (Lim <= R-L+1) Lim <<= 1, ++l;
init(Lim, l), NTT(f0, Lim, 1), NTT(f1, Lim, 1);
for (int i = 0; i < Lim; ++i) f[i] = 1LL * f0[i] * f1[i] % P, f0[i] = f1[i] = 0;
NTT(f, Lim, -1), ptr -= 2;
}
}

int n, K;
int f[MAXN], g[MAXN];

int main() {
#ifndef ONLINE_JUDGE
freopen("input.in", "r", stdin);
#endif
// input
scanf("%d%d", &n, &K);
// solve
Poly::solve(f, 1, K);
Poly::Inv(f, g, n+1);
for (int i = 0; i+K <= n; ++i) f[i+K] = g[i];
for (int i = 0; i < K; ++i) f[i] = 0;
// output
for (int i = 0; i <= n; ++i) printf("%d%c", f[i], " \n"[i==n]);
return 0;
}

参考资料