# 斯特林数,上升幂,下降幂,普通幂的定义
# 第二类斯特林数
n | {0n} | {1n} | {2n} | {3n} | {4n} | {5n} | {6n} | {7n} | {8n} | {9n} |
---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 1 | 3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 1 | 7 | 6 | 1 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 1 | 15 | 25 | 10 | 1 | 0 | 0 | 0 | 0 |
6 | 0 | 1 | 31 | 90 | 65 | 15 | 1 | 0 | 0 | 0 |
7 | 0 | 1 | 63 | 301 | 350 | 140 | 21 | 1 | 0 | 0 |
8 | 0 | 1 | 127 | 966 | 1701 | 1050 | 266 | 28 | 1 | 0 |
9 | 0 | 1 | 255 | 3025 | 7770 | 6951 | 2446 | 462 | 36 | 1 |
定义 {mn} 表示将 n 件物品分成 m 个子集(非空)的方案数。
有
{mn}={m−1n−1}+m{mn−1}
# 第一类斯特林数
n | [0n] | [1n] | [2n] | [3n] | [4n] | [5n] | [6n] | [7n] | [8n] | {9n} |
---|
0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
3 | 0 | 2 | 3 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
4 | 0 | 6 | 11 | 6 | 1 | 0 | 0 | 0 | 0 | 0 |
5 | 0 | 24 | 50 | 35 | 10 | 1 | 0 | 0 | 0 | 0 |
6 | 0 | 120 | 274 | 225 | 85 | 15 | 1 | 0 | 0 | 0 |
7 | 0 | 720 | 1764 | 1624 | 735 | 175 | 21 | 1 | 0 | 0 |
8 | 0 | 5040 | 13068 | 13132 | 6769 | 1960 | 322 | 28 | 1 | 0 |
9 | 0 | 40320 | 109584 | 118124 | 67284 | 22449 | 4536 | 546 | 36 | 1 |
定义 [mn] 表示将 n 件物品分成 m 个轮换(非空,圆排列)的方案数。
有
[mn]=(n−1)[mn−1]+[m−1n−1]
# 上升幂,下降幂,普通幂
xn=(x−n)!x!xn=(x−1)!(x+n−1)!xn=xnxn=(x+n−1)nxn=(x−n+1)n
# 斯特林数的性质
i=0∑n{in}xi=i=0∑n(i{in−1}+{i−1n−1})xi=i=0∑ni{in−1}xi+i=0∑n{i−1n−1}xi=i=0∑ni{in−1}xi+i=1∑n{i−1n−1}xi=i=0∑ni{in−1}xi+i=1∑n{i−1n−1}x(i−1)+1=i=0∑ni{in−1}xi+i=0∑n−1{in−1}xi+1=i=0∑n−1i{in−1}xi+i=0∑n−1{in−1}xi(x−i)=i=0∑n−1i{in−1}xi+{in−1}xi(x−i)=xi=0∑n−1{in−1}xi=x×xn−1=xn
i=0∑n[in]xi=i=0∑n((n−1)[in−1]+[i−1n−1])xi=i=0∑n(n−1)[in−1]xi+i=0∑n[i−1n−1]xi=i=0∑n(n−1)[in−1]xi+i=1∑n[i−1n−1]x(i−1)+1=i=0∑n(n−1)[in−1]xi+i=0∑n−1[in−1]xi+1=(n−1+x)i=0∑n[in−1]xi=(x+n−1)xn−1=xn
xn=(−1)n(−x)n=(−1)ni=0∑n[in](−x)i=i=0∑n[in](−1)n−ixi
xn=(−1)n(−x)n=(−1)ni=0∑n{in}(−x)i=(−1)ni=0∑n{in}(−1)ixi=i=0∑n{in}(−1)n−ixi
整理一下
xn=i=0∑n{in}xixn=i=0∑n{in}(−1)n−ixixn=i=0∑n[in]xixn=i=0∑n[in](−1)n−ixi
表示普通幂时用第二类斯特林数,表示上升幂、下降幂时用第一类斯特林数,用大数表示小数时用 (−1)n−i。
考虑公式的嵌套,让斯特林数更毒瘤:
xn=i=0∑n{in}(−1)n−ixi=i=0∑n{in}(−1)n−im=0∑i[mi]xm=m=0∑nxmi=m∑n{in}[mi](−1)n−i
i=m∑n{in}[mi](−1)n−i=[n=m]
xn=i=0∑n[in](−1)n−ixi=i=0∑n[in](−1)n−ij=0∑i{ji}xj=j=0∑ixji=j∑n[in]{ji}(−1)n−i
i=m∑n[in]{mi}(−1)n−i=[m=n]
既然有二项式反演,也有斯特林反演!
g(n)=i=0∑n{in}f(i)g(n)=i=0∑n[in]f(i)⟺f(n)=i=0∑n[in](−1)n−ig(i)⟺f(n)=i=0∑n{in}(−1)n−ig(i)
证明略。
在来个柿子:
xn=i=0∑x{in}(ix)i!
考虑组合意义来解释, xn 表示将 n 个小球放进 x 个盒子的方案数, ∑i=0x 表示有几个盒子非空,(ix) 表示选哪几个盒子非空, i! 表示非空盒子的顺序, {in} 表示 n 个小球划分成 i 个集合的方案数。
二项式反演可得:
xn=i=0∑x{in}(ix)i!⟺{mn}m!=i=0∑m(im)(−1)m−iin
{mn}=i=0∑mm!(im)(−1)m−iin=i=0∑mi!(m−i)!m!m!(−1)m−iin=i=0∑mi!(m−i)!in(−1)m−i=i=0∑mi!in×(m−i)!(−1)m−i
可以发现这是个卷积, NTT 即可计算同一行的第二类斯特林数。
# 例题
P6620 [省选联考 2020 A 卷] 组合数问题
遇到不可以直接计算的多项式、组合数计数题时可以考虑普通幂转下降幂或上升幂。
(k=0∑nf(k)xk(kn))modp=i=0∑mk=0∑nbikixk(kn)=i=0∑mk=0∑nbinixk(k−in−i)=i=0∑mbinik=0∑n(k−in−i)xk=i=0∑mbinik=0∑n−i(k−in−i)xk1n−i−k=i=0∑mbinixik=0∑n−i(kn−i)xk1n−i−k=i=0∑mbinixi(x+1)n−i
其中
f(x)=i=0∑mbixi
有
f(x)=i=0∑maixi=i=0∑maij=0∑i{ji}xj=j=0∑mxji=j∑m{ji}ai
第二类斯特林数直接暴力 m2 计算即可,总时间复杂度为 O(m2)。
| #include <bits/stdc++.h> |
| using namespace std; |
| using ll = long long; |
| const ll N = 3001; |
| ll stirling[N][N], MOD; |
| void get_stirling(ll tt){ |
| stirling[0][0]=1; |
| for(ll i = 1; i <= tt; i++) |
| for(ll j = 1; j <= i; j++) |
| stirling[i][j] = 1ll*(1ll*stirling[i-1][j]*j%MOD+1ll*stirling[i-1][j-1]) % MOD; |
| } |
| ll my_pow(ll x, ll y){ |
| ll res = 1; |
| for(;y;y>>=1,x=x*x%MOD) |
| if(y&1)res=res*x%MOD; |
| return res; |
| } |
| ll n, X, m; |
| ll a[N], b[N]; |
| int main(){ |
| cin>>n>>X>>MOD>>m; |
| get_stirling(m); |
| for(ll i = 0; i <= m; i++) |
| cin>>a[i]; |
| for(ll i = 0; i <= m; i++) |
| for(ll j = i; j <= m; j++) |
| b[i] = (b[i]+stirling[j][i]*a[j]%MOD)%MOD; |
| ll ans = 0; |
| for(ll i = 0, now = 1; i <= m; i++){ |
| ll res = b[i] * now % MOD * my_pow(X, i) % MOD * my_pow(X+1, n-i) % MOD; |
| ans = (ans + res) % MOD; |
| now = now * (n-i) % MOD; |
| } |
| cout<<(ans+MOD)%MOD<<endl; |
| return 0; |
| } |
P5395 第二类斯特林数・行
xn=i=0∑x{in}(ix)i!
考虑组合意义来解释, xn 表示将 n 个小球放进 x 个盒子的方案数, ∑i=0x 表示有几个盒子非空,(ix) 表示选哪几个盒子非空, i! 表示非空盒子的顺序, {in} 表示 n 个小球划分成 i 个集合的方案数。
二项式反演可得:
xn=i=0∑x{in}(ix)i!⟺{mn}m!=i=0∑m(im)(−1)m−iin
{mn}=i=0∑mm!(im)(−1)m−iin=i=0∑mi!(m−i)!m!m!(−1)m−iin=i=0∑mi!(m−i)!in(−1)m−i=i=0∑mi!in×(m−i)!(−1)m−i
可以发现这是个卷积, NTT 即可计算同一行的第二类斯特林数。
| #include <bits/stdc++.h> |
| using namespace std; |
| using ll = long long; |
| const ll MOD = 167772161; |
| const ll N = 1e6 + 11; |
| ll rev[N], w[N], gen = 3; |
| ll my_pow(ll x, ll y){ |
| ll res = 1; |
| for(;y;y>>=1,x=x*x%MOD) |
| if(y&1)res=res*x%MOD; |
| return res; |
| } |
| void NTT(ll *a, ll Len, bool type){ |
| for(int i = 0; i < Len; i++){ |
| rev[i] = (rev[i>>1]>>1) + (i&1?Len>>1:0); |
| if(rev[i]>i)swap(a[rev[i]], a[i]); |
| } |
| for(ll d = 1; d < Len; d <<= 1){ |
| ll W = my_pow(gen, (MOD-1)/(d*2)); |
| if(type) W = my_pow(W, MOD-2); |
| w[0] = 1; |
| for(ll i = 1; i < d; i++) |
| w[i] = w[i-1] * W % MOD; |
| for(ll fir = 0; fir < Len; fir += (d*2)){ |
| ll sec = fir + d; |
| for(ll i = 0 ; i < d; i++){ |
| ll a0 = a[fir+i], a1 = a[sec+i]*w[i]%MOD; |
| a[fir+i] = (a0+a1)%MOD; |
| a[sec+i] = (a0-a1+MOD)%MOD; |
| } |
| } |
| } |
| if(type){ |
| ll invlen=my_pow(Len, MOD-2); |
| for(int i=0; i<Len; i++) |
| a[i] = a[i]*invlen%MOD; |
| } |
| } |
| ll f[N], g[N], fac[N], ifac[N], n; |
| ll fu(int k){return k&1?-1:1;} |
| int main(){ |
| cin>>n; |
| ifac[0]=fac[0]=1; |
| for(ll i = 1; i <= n; i++) fac[i] = fac[i-1] * i % MOD; |
| ifac[n] = my_pow(fac[n], MOD-2); |
| for(ll i = n-1; i; i--) ifac[i] = ifac[i+1] * (i+1) % MOD; |
| for(ll i = 0; i <= n; i++){ |
| f[i] = fu(i)*ifac[i]%MOD, f[i] = (f[i]+MOD)%MOD; |
| g[i] = my_pow(i, n) * ifac[i] % MOD; |
| |
| } |
| ll len = 1; |
| while(len<=(n*2))len<<=1; |
| NTT(f, len, 0), NTT(g, len, 0); |
| for(int i = 0; i < len; i++) |
| f[i] = f[i] * g[i] % MOD, f[i] = (f[i] + MOD) % MOD; |
| NTT(f, len, 1); |
| for(int i = 0; i <= n; i++) |
| cout << f[i] << " "; |
| cout << endl; |
| return 0; |
| } |
P5408 第一类斯特林数・行
有
xn=i=0∑n[in]xi
考虑倍增:
x2n=xn(x+n)n
假设我们求出了 xn 的多项式表达 f1 。
现在要求 (x+n)n 的多项式表达 f2 。
有:
f1(x)=f2(x−n)
设 ai 为 f1 的系数,有:
f2=i=0∑nai(x+n)i=i=0∑naij=0∑ixjni−j(ji)=j=0∑nxji=j∑nni−jaij!(i−j)!i!=j=0∑nj!xji=j∑naii!(i−j)!ni−j
可以发现里面是个卷积, NTT 计算即可。
注意卷积时 Len 的长度不能大也不能小(代码第 48 行)。
| #include <bits/stdc++.h> |
| using namespace std; |
| using ll = long long; |
| const ll MOD = 167772161; |
| const ll N = 7e5, gen=3; |
| |
| ll my_pow(ll x, ll y){ |
| ll res = 1; |
| for(;y;y>>=1,x=x*x%MOD) |
| if(y&1)res=res*x%MOD; |
| return res; |
| } |
| ll inv(ll x){ |
| return my_pow(x, MOD-2); |
| } |
| |
| ll rev[N], w[N]; |
| void ntt(ll *a, ll Len, bool type){ |
| for(ll i = 0 ; i < Len; i++){ |
| rev[i] = (rev[i>>1]>>1) + (i&1?Len>>1:0); |
| if(rev[i]>i)swap(a[rev[i]], a[i]); |
| } |
| for(ll d=1; d<Len; d<<=1){ |
| ll W = my_pow(gen, (MOD-1)/(d*2)); |
| if(type) W = inv(W); |
| w[0]=1; |
| for(ll i = 1; i < d; i++) |
| w[i] = w[i-1] * W % MOD; |
| for(ll fir=0; fir<Len; fir+=d*2){ |
| ll sec=fir+d; |
| for(ll i=0; i<d; i++){ |
| ll a0=a[fir+i], a1=a[sec+i]*w[i]%MOD; |
| a[fir+i]=(a0+a1)%MOD; |
| a[sec+i]=(a0-a1+MOD)%MOD; |
| } |
| } |
| } |
| if(type){ |
| ll invlen=inv(Len); |
| for(ll i=0; i<Len; i++) |
| a[i]=a[i]*invlen%MOD; |
| } |
| } |
| |
| ll aa[N], bb[N]; |
| void mul(ll *a, ll *b, ll alen, ll blen){ |
| ll len=1; |
| while(len<=(alen+blen))len<<=1; |
| ntt(a, len, 0); |
| ntt(b, len, 0); |
| for(int i=0; i<len; i++) |
| a[i]=a[i]*b[i]%MOD; |
| ntt(a, len, 1); |
| } |
| |
| ll ans[N], s[N], A[N], B[N], C[N]; |
| ll fac[N], ifac[N]; |
| |
| void sol_striling_one_line(ll tt){ |
| if(tt==1){s[1]=1;return;} |
| if(tt&1){ |
| sol_striling_one_line(tt-1); |
| for(ll i=tt; i; i--) |
| s[i] = (s[i-1] + s[i]*(tt-1)%MOD)%MOD; |
| s[0]=s[0]*(tt-1)%MOD; |
| return; |
| } |
| ll slen = tt/2, res=1; |
| sol_striling_one_line(slen); |
| for(int i=0; i<=slen; i++) |
| A[i]=s[i]*fac[i]%MOD, |
| B[i]=res*ifac[i]%MOD, |
| res=res*slen%MOD; |
| reverse(A, A+slen+1); |
| mul(A, B, slen+1, slen+1); |
| for(int i=0; i<=slen; i++) |
| C[i]=A[slen-i]*ifac[i]%MOD; |
| mul(s,C,slen+1,slen+1); |
| int len=1; |
| while(len<=(tt+2))len<<=1; |
| for(int i=slen+1; i<len; i++)A[i]=B[i]=C[i]=0; |
| for(int i=tt+1; i<len; i++)s[i]=0; |
| } |
| |
| void init_fac(int tt){ |
| fac[0]=ifac[0]=1; |
| for(ll i=1; i<=tt; i++) |
| fac[i]=fac[i-1]*i%MOD; |
| ifac[tt]=my_pow(fac[tt], MOD-2); |
| for(ll i=tt-1; i; i--) |
| ifac[i] = ifac[i+1]*(i+1)%MOD; |
| } |
| |
| ll* striling_first_line(ll tt){ |
| init_fac(tt<<1); |
| memset(A, 0, sizeof A); |
| memset(B, 0, sizeof B); |
| memset(s, 0, sizeof s); |
| memset(C, 0, sizeof C); |
| sol_striling_one_line(tt); |
| return s; |
| } |
| |
| |
| int main(){ |
| ll n; |
| cin>>n; |
| ll* striling=striling_first_line(n); |
| for(int i=0; i<=n; i++) cout<<striling[i]<<' '; |
| cout<<'\n'; |
| return 0; |
| } |