一、题目
二、解法
0x01 前置芝士
第二类斯特林数的模型是把 n n n个不同的小球放进 m m m个相同的盒子中,要求盒子非空的方案数,记作 S ( n , m ) S(n,m) S(n,m)。
递推式: S ( n , m ) = S ( n − 1 , m − 1 ) + m ⋅ S ( n − 1 , m ) S(n,m)=S(n-1,m-1)+m\cdot S(n-1,m) S(n,m)=S(n−1,m−1)+m⋅S(n−1,m),也就是考虑加入一个球或一个盒子。
容斥推导的通项公式: S ( n , m ) = ∑ i = 0 m ( − 1 ) i × C ( m , i ) × ( m − i ) n S(n,m)=\sum_{i=0}^{m} (-1)^i\times C(m,i)\times (m-i)^n S(n,m)=∑i=0m(−1)i×C(m,i)×(m−i)n,也就是容斥非空的这个限制, i i i表示空出来的盒子个数,那么方案数就是 选出空出的盒子 × \times ×在剩下的盒子中随便放的方案数。
0x02 式子推导
f
(
n
)
=
∑
i
=
0
n
∑
j
=
0
i
S
(
i
,
j
)
×
2
j
×
j
!
=
∑
i
=
0
n
∑
j
=
0
i
2
j
∑
k
=
0
j
(
−
1
)
k
×
C
(
j
,
k
)
×
(
j
−
k
)
i
=
∑
i
=
0
n
∑
j
=
0
i
2
j
×
j
!
∑
k
=
0
j
(
−
1
)
k
k
!
×
(
j
−
k
)
i
(
j
−
k
)
!
=
∑
i
=
0
n
∑
j
=
0
n
2
j
×
j
!
∑
k
=
0
j
(
−
1
)
k
k
!
×
(
j
−
k
)
i
(
j
−
k
)
!
=
∑
j
=
0
n
2
j
×
j
!
∑
k
=
0
j
(
−
1
)
k
k
!
×
∑
i
=
0
n
(
j
−
k
)
i
(
j
−
k
)
!
f(n)=\sum_{i=0}^{n}\sum_{j=0}^{i}S(i,j)\times 2^j\times j!\\=\sum_{i=0}^{n}\sum_{j=0}^i2^j\sum_{k=0}^{j}(-1)^k\times C(j,k)\times (j-k)^i\\=\sum_{i=0}^{n}\sum_{j=0}^i2^j\times j!\sum_{k=0}^{j}\frac{(-1)^k}{k!}\times\frac{(j-k)^i}{(j-k)!}\\=\sum_{i=0}^{n}\sum_{j=0}^n2^j\times j!\sum_{k=0}^{j}\frac{(-1)^k}{k!}\times\frac{(j-k)^i}{(j-k)!}\\=\sum_{j=0}^{n}2^j\times j!\sum_{k=0}^{j}\frac{(-1)^k}{k!}\times \frac{\sum_{i=0}^{n}(j-k)^i}{(j-k)!}
f(n)=i=0∑nj=0∑iS(i,j)×2j×j!=i=0∑nj=0∑i2jk=0∑j(−1)k×C(j,k)×(j−k)i=i=0∑nj=0∑i2j×j!k=0∑jk!(−1)k×(j−k)!(j−k)i=i=0∑nj=0∑n2j×j!k=0∑jk!(−1)k×(j−k)!(j−k)i=j=0∑n2j×j!k=0∑jk!(−1)k×(j−k)!∑i=0n(j−k)i记
f
(
i
)
=
(
−
1
)
k
k
!
,
g
(
i
)
=
∑
j
=
0
n
i
j
i
!
f(i)=\frac{(-1)^k}{k!},g(i)=\frac{\sum_{j=0}^n i^j}{i!}
f(i)=k!(−1)k,g(i)=i!∑j=0nij,则:
=
∑
j
=
0
n
2
j
×
j
!
∑
k
=
0
j
f
(
k
)
g
(
j
−
k
)
=\sum_{j=0}^{n}2^j\times j!\sum_{k=0}^jf(k)g(j-k)
=j=0∑n2j×j!k=0∑jf(k)g(j−k)最后一个式子就是卷积的形式,用
NTT
\text{NTT}
NTT优化即可。
解释一下第四个柿子,因为我们的
S
(
n
,
m
)
S(n,m)
S(n,m)(当
m
>
n
m>n
m>n时)用容斥公式算出来是
0
0
0,所以可以扩大
j
j
j的范围,方便改变枚举顺序,
g
g
g数组需要用等比数列求和,我一开始写了一个预处理阶乘法。
#include <cstdio>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 300005;
const int MOD = 998244353;
int read()
{
int num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
return num*flag;
}
int n,len,lg,ans,f[MAXN],g[MAXN];
int Rev[MAXN],fac[MAXN],inv[MAXN];
void init(int n)
{
inv[0]=inv[1]=fac[0]=fac[1]=1;
for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=2;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[i]*inv[i-1]%MOD;
}
int qkpow(int a,int b)
{
int res=1;
while(b>0)
{
if(b&1) res=res*a%MOD;
a=a*a%MOD;
b>>=1;
}
return res;
}
void NTT(int *a,const int len,int tmp)
{
for(int i=0;i<len;i++)
{
Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
if(i<Rev[i])
swap(a[i],a[Rev[i]]);
}
for(int s=2;s<=len;s<<=1)
{
int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
for(int i=0;i<len;i+=s)
{
int x=1;
for(int j=0;j<t;j++,x=x*w%MOD)
{
int fe=a[i+j],fo=a[i+j+t];
a[i+j]=(fe+x*fo)%MOD;
a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
}
}
}
if(tmp==1) return ;
int inv=qkpow(len,MOD-2);
for(int i=0;i<len;i++)
a[i]=a[i]*inv%MOD;
}
signed main()
{
n=read();
init(n);
for(int i=0;i<=n;i++)
if(i%2) f[i]=MOD-inv[i];
else f[i]=inv[i];
g[0]=1;
for(int i=1;i<=n;i++)
{
if(i==1) g[i]=n+1;
else g[i]=(qkpow(i,n+1)-1)*qkpow(i-1,MOD-2)%MOD*inv[i]%MOD;
}
len=1;while(len<=2*n+2)len<<=1,lg++;
NTT(f,len,1);NTT(g,len,1);
for(int i=0;i<len;i++) f[i]=f[i]*g[i];
NTT(f,len,-1);
for(int i=0;i<=n;i++)
ans+=qkpow(2,i)*fac[i]%MOD*f[i]%MOD,ans%=MOD;
printf("%lld\n",(ans%MOD+MOD)%MOD);
}