前言
目前由于时间关系,只研究了第一类斯特林数。
有关第二类斯特林数的问题将尽快补上。
2019.3.26 第二类斯特林数已补充
第一类斯特林数
第一类斯特林数的定义是:
[
n
m
]
表
示
将
n
个
数
,
放
入
m
个
圆
排
列
的
方
案
数
{n \brack m}表示将n个数,放入m个圆排列的方案数
[mn]表示将n个数,放入m个圆排列的方案数
圆排列:只考虑元素之间的相对位置,不关心绝对位置。换言之可以像圆一样转动。类似置换群中的循环。
有一个很显然的递推式
[
n
m
]
=
[
n
−
1
m
−
1
]
+
(
n
−
1
)
∗
[
n
−
1
m
]
{n \brack m}={n-1 \brack m-1}+(n-1)*{n-1 \brack m}
[mn]=[m−1n−1]+(n−1)∗[mn−1]
解释是:n可以单独成一个圆排列。也可以加入到之前任何一个数左边。
不过这是 O ( n 2 ) O(n^2) O(n2)的,效率非常低下。
首先,有一个结论:
多项式
∏
i
=
0
i
<
n
(
x
+
i
)
展
开
后
,
x
i
的
系
数
正
好
就
是
[
n
i
]
\prod_{i=0}^{i<n}(x+i)展开后,x^i的系数正好就是{n \brack i}
i=0∏i<n(x+i)展开后,xi的系数正好就是[in]
证明比较简单。
考虑递推的过程,在最开始,将
[
1
1
]
{ 1\brack 1}
[11]作为多项式的一次项系数。
然后,每次递推,系数的转移都可以看作:
k
i
=
k
i
−
1
+
(
t
−
1
)
k
i
k_i=k_{i-1}+(t-1)k_i
ki=ki−1+(t−1)ki,其中
t
t
t表示当前多项式的最高次项。
那么相当于就是在原来的多项式的基础上,乘上了
(
x
+
t
−
1
)
(x+t-1)
(x+t−1)这个多项式。
所以从第1层转移到第n层,就得依次乘上
∏
i
=
1
i
<
n
(
x
+
i
)
\prod_{i=1}^{i<n}(x+i)
i=1∏i<n(x+i)
加上最开始的一次项:
∏
i
=
0
i
<
n
(
x
+
i
)
\prod_{i=0}^{i<n}(x+i)
∏i=0i<n(x+i)
由此,可以有一个 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的分治FFT/NTT的方法了。
现在,考虑更高效的算法。
假设当前要求的多项式是
∏
i
=
0
i
<
2
n
(
x
+
i
)
\prod_{i=0}^{i<2n}(x+i)
∏i=0i<2n(x+i)
可以先递归地求出前n项的乘积f
即
f
(
x
)
=
∏
i
=
0
i
<
n
(
x
+
i
)
=
∑
i
=
1
i
≤
n
a
i
x
i
f(x)=\prod_{i=0}^{i<n}(x+i)=\sum_{i=1}^{i\leq n} a_ix^i
f(x)=∏i=0i<n(x+i)=∑i=1i≤naixi
现在其实可以很简单地求出后n项的乘积
g
(
x
)
=
∏
i
=
n
i
<
2
n
(
x
+
i
)
g(x)=\prod_{i=n}^{i<2n}(x+i)
g(x)=∏i=ni<2n(x+i)
g
(
x
)
=
∑
i
=
1
i
≤
n
a
i
(
x
+
n
)
i
g(x)=\sum_{i=1}^{i\leq n}a_i(x+n)^i
g(x)=i=1∑i≤nai(x+n)i
由二项式定理得
=
∑
i
=
1
i
≤
n
a
i
∑
j
=
0
j
≤
i
(
i
j
)
n
i
−
j
x
j
=\sum_{i=1}^{i\leq n}a_i\sum_{j=0}^{j\leq i}{i \choose j}n^{i-j}x^j
=i=1∑i≤naij=0∑j≤i(ji)ni−jxj
=
∑
i
=
1
i
≤
n
∑
j
=
0
j
≤
i
(
i
j
)
n
i
−
j
a
i
x
j
=\sum_{i=1}^{i\leq n}\sum_{j=0}^{j\leq i}{i \choose j}n^{i-j}a_ix^j
=i=1∑i≤nj=0∑j≤i(ji)ni−jaixj
=
∑
i
=
1
i
≤
n
∑
j
=
0
j
≤
i
(
n
i
−
j
(
i
−
j
)
!
)
(
a
i
∗
i
!
)
(
x
j
j
!
)
=\sum_{i=1}^{i\leq n}\sum_{j=0}^{j\leq i}(\frac {n^{i-j}} {(i-j)!})(a_i*i!)(\frac {x^j} {j!})
=i=1∑i≤nj=0∑j≤i((i−j)!ni−j)(ai∗i!)(j!xj)
这就是个裸的卷积了。
所以可以算出这部分,得到 g ( x ) g(x) g(x)再乘上之前递归得到的 f ( x ) f(x) f(x)就成功做到倍增了。
注意:当n为奇数时,要向下取整,最后再 O ( n ) O(n) O(n)转移一次。
例题
第二类斯特林数
第二类比第一类简单些。。。(在求值方面)
与第一类相对应的,第二类的组合意义是:
S
2
(
n
,
m
)
S_2(n,m)
S2(n,m)表示将n个数,分为m个非空集合的方案数。(集合即与顺序无关)
那么转移式就要变了:
S
2
(
n
,
m
)
=
S
2
(
n
−
1
,
m
−
1
)
+
m
∗
S
2
(
n
−
1
,
m
)
S_2(n,m)=S_2(n-1,m-1)+m*S_2(n-1,m)
S2(n,m)=S2(n−1,m−1)+m∗S2(n−1,m)
不过和第一类不同,这里不需要转移到某个上升幂的形式
求解的方法可以利用容斥:
即考虑集合让集合之间有序(最后再除以m!使其仍然无序),然后就可以暴力枚举必须空的集合的数量进行容斥:
S
2
(
n
,
m
)
=
1
m
!
∑
i
=
0
i
≤
m
(
−
1
)
i
C
(
m
,
i
)
(
m
−
i
)
n
S_2(n,m)=\frac {1} {m!}\sum_{i=0}^{i\leq m}(-1)^iC(m,i)(m-i)^n
S2(n,m)=m!1i=0∑i≤m(−1)iC(m,i)(m−i)n
上式中
C
(
m
,
i
)
C(m,i)
C(m,i)即枚举空的集合是哪些,然后n个点可以随意分配到任意一个不必须空的集合中。
这个式子可以拆开:
S
2
(
n
,
m
)
=
∑
i
=
0
i
≤
n
(
−
1
)
i
(
m
−
i
)
n
i
!
(
m
−
i
)
!
S_2(n,m)=\sum_{i=0}^{i\leq n}\frac {(-1)^i(m-i)^n} {i!(m-i)!}
S2(n,m)=i=0∑i≤ni!(m−i)!(−1)i(m−i)n
中间分开
S
2
(
n
,
m
)
=
∑
i
=
0
i
≤
n
(
−
1
)
i
i
!
×
(
m
−
i
)
n
(
m
−
i
)
!
S_2(n,m)=\sum_{i=0}^{i\leq n}\frac {(-1)^i} {i!} \times \frac {(m-i)^n} {(m-i)!}
S2(n,m)=i=0∑i≤ni!(−1)i×(m−i)!(m−i)n
没错这就是个卷积
弄成两个多项式卷积,其中m次项的系数就是答案
模板题 洛谷P4091
代码:
#include<cstdio>
#include<algorithm>
#include<cmath>
#define SF scanf
#define PF printf
#define MAXN 300010
#define MOD 998244353
using namespace std;
typedef long long ll;
const int G=3;
ll fsp(ll x,int y){
ll res=1;
while(y){
if(y&1)
res=res*x%MOD;
x=x*x%MOD;
y>>=1;
}
return res;
}
void NTT(ll A[],int N,int flag){
for(int i=1,j=0;i<N;i++){
for(int d=N;j^=d>>=1,~j&d;);
if(i<j)
swap(A[i],A[j]);
}
for(int i=1;i<N;i<<=1){
ll wn=fsp(G,(MOD-1)/(i<<1));
if(flag)
wn=fsp(wn,MOD-2);
for(int j=0;j<N;j+=(i<<1)){
ll w=1;
for(int k=0;k<i;k++,w=w*wn%MOD){
ll x=A[j+k],y=A[i+j+k]*w%MOD;
A[j+k]=(x+y)%MOD;
A[i+j+k]=(x-y+MOD)%MOD;
}
}
}
if(flag){
ll invN=fsp(N,MOD-2);
for(int i=0;i<N;i++)
A[i]=A[i]*invN%MOD;
}
}
ll fac[MAXN],ifac[MAXN],res[MAXN],f[MAXN],g[MAXN];
void mul(ll A[],ll B[],int n,int m,ll res[]){
static ll tmp1[MAXN],tmp2[MAXN];
for(int i=0;i<n;i++)
tmp1[i]=A[i];
for(int i=0;i<m;i++)
tmp2[i]=B[i];
int p=1;
while(p<=n+m)
p<<=1;
NTT(tmp1,p,0);
NTT(tmp2,p,0);
for(int i=0;i<p;i++)
res[i]=tmp1[i]*tmp2[i]%MOD;
NTT(res,p,1);
for(int i=0;i<p;i++)
tmp1[i]=tmp2[i]=0;
}
int main(){
int n;
SF("%d",&n);
fac[0]=1;
for(int i=1;i<=n;i++)
fac[i]=fac[i-1]*i%MOD;
ifac[n]=fsp(fac[n],MOD-2);
for(int i=n;i>=1;i--)
ifac[i-1]=ifac[i]*i%MOD;
for(int i=0;i<=n;i++){
if(i%2==0)
f[i]=ifac[i];
else
f[i]=MOD-ifac[i];
}
for(int i=0;i<=n;i++){
if(i!=1)
g[i]=(fsp(i,n+1)-1)*fsp(i-1,MOD-2)%MOD*ifac[i]%MOD;
else
g[i]=n+1;
}
// for(int i=0;i<=n;i++)
// PF("%lld ",f[i]);
// PF("\n");
// for(int i=0;i<=n;i++)
// PF("%lld ",g[i]);
// PF("\n");
mul(f,g,n+1,n+1,res);
// for(int i=0;i<=2*n;i++)
// PF("%lld ",res[i]);
// PF("\n");
ll ans=0;
for(int i=0;i<=n;i++)
ans=(ans+res[i]*fsp(2,i)%MOD*fac[i]%MOD)%MOD;
PF("%lld",ans);
}