第一类斯特林数
暑假里,小L到海边去玩,捡了
n
n
n个不同的贝壳。现在她想把这些贝壳串成
k
k
k个项链(项链是环形的)。她忽然很疑惑,这有多少种方案呢?
聪明的小L很快想到,假设
S
1
(
n
,
k
)
S_1(n,k)
S1(n,k)为
n
n
n个贝壳串成
k
k
k条项链的方案数,那么显然有
S
1
(
n
,
k
)
=
S
1
(
n
−
1
,
k
−
1
)
+
(
n
−
1
)
S
1
(
n
−
1
,
k
)
S_1(n,k)=S_1(n-1,k-1)+(n-1)S_1(n-1,k)
S1(n,k)=S1(n−1,k−1)+(n−1)S1(n−1,k),即要么将第
n
n
n个贝壳单独串成一条项链,要么让前
n
−
1
n-1
n−1个贝壳已经串成
k
k
k条项链,然后考虑第
n
n
n个贝壳放到哪个贝壳的后面。
这个递推式还可以看作进行
n
n
n次操作,其中第
i
i
i次操作有
i
−
1
i-1
i−1种方案不取物品,有
1
1
1种方案取一个新物品,最后取得
k
k
k个物品的方案数。根据这个意义,小L写出了一个生成函数:
∏
i
=
0
n
−
1
(
x
+
i
)
\prod_{i=0}^{n-1}(x+i)
i=0∏n−1(x+i)
这个生成函数的
k
k
k次项系数就是
S
1
(
n
,
k
)
S_1(n,k)
S1(n,k)。可以用分治FFT。
通过查阅资料,小L还得知,
∏
i
=
0
n
−
1
(
x
−
i
)
\prod_{i=0}^{n-1}(x-i)
∏i=0n−1(x−i)这个多项式的每一项系数就是有符号第一类斯特林数,它们的递推式为
S
1
(
n
,
k
)
=
S
1
(
n
−
1
,
k
−
1
)
−
(
n
−
1
)
S
1
(
n
−
1
,
k
)
S_1(n,k)=S_1(n-1,k-1)-(n-1)S_1(n-1,k)
S1(n,k)=S1(n−1,k−1)−(n−1)S1(n−1,k)
例题:codeforces960G
设
f
(
i
,
j
)
f(i,j)
f(i,j)表示
i
i
i个数的排列,存在
j
j
j个数,在它们前面没有比它们大的数。
考虑最小的数放在哪,可以得到递推式:
f
(
i
,
j
)
=
f
(
i
−
1
,
j
−
1
)
+
(
i
−
1
)
f
(
i
−
1
,
j
)
f(i,j)=f(i-1,j-1)+(i-1)f(i-1,j)
f(i,j)=f(i−1,j−1)+(i−1)f(i−1,j),就是第一类斯特林数。
因为
n
n
n的前面和后面都没有比它更大的数,所以题目要求的
a
a
a个数一定在
n
n
n前面,
b
b
b个数一定在
n
n
n后面,枚举
n
n
n所在的位置,答案就是:
∑
i
=
1
n
S
1
(
i
−
1
,
a
−
1
)
S
1
(
n
−
i
,
b
−
1
)
C
n
−
1
i
−
1
\sum_{i=1}^n S_1(i-1,a-1)S_1(n-i,b-1)C_{n-1}^{i-1}
i=1∑nS1(i−1,a−1)S1(n−i,b−1)Cn−1i−1
考虑一些前面无比其大数的数,假设它们所在的位置为
p
1
,
p
2
.
.
.
p
k
p_1,p_2...p_k
p1,p2...pk,把
[
p
i
,
p
i
+
1
−
1
]
[p_i,p_{i+1}-1]
[pi,pi+1−1]看作是一整块,那么我们可以先产生
a
+
b
−
2
a+b-2
a+b−2块,然后从其中选择
b
−
1
b-1
b−1块,将它们块内翻转,然后丢到
n
n
n的后面。所以答案就是:
S
1
(
n
−
1
,
a
+
b
−
2
)
C
a
+
b
−
2
b
−
1
S_1(n-1,a+b-2)C_{a+b-2}^{b-1}
S1(n−1,a+b−2)Ca+b−2b−1
#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int N=200005,mod=998244353,G=3;
int n,A,B,ans,a[18][N],rev[N];
int ksm(int x,int y) {
int re=1;
for(RI i=y;i;i>>=1,x=1LL*x*x%mod) if(i&1) re=1LL*re*x%mod;
return re;
}
void NTT(int *a,int n,int x) {
for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
for(RI i=1;i<n;i<<=1) {
int gn=ksm(G,(mod-1)/(i<<1));
for(RI j=0;j<n;j+=(i<<1)) {
int g=1,t1,t2;
for(RI k=0;k<i;++k,g=1LL*g*gn%mod) {
t1=a[j+k],t2=1LL*g*a[j+i+k]%mod;
a[j+k]=(t1+t2)%mod,a[j+i+k]=(t1-t2+mod)%mod;
}
}
}
if(x==1) return;
int inv=ksm(n,mod-2);reverse(a+1,a+n);//a+1!!!
for(RI i=0;i<n;++i) a[i]=1LL*a[i]*inv%mod;
}
void work(int s,int t,int d) {
if(s==t) {a[d][0]=s,a[d][1]=1;return;}
int mid=(s+t)>>1,len=0,kn=1;
work(s,mid,d+1);
for(RI i=0;i<=mid-s+1;++i) a[d][i]=a[d+1][i];
work(mid+1,t,d+1);
while(kn<=t-s+1) kn<<=1,++len;
for(RI i=0;i<kn;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
for(RI i=mid-s+2;i<kn;++i) a[d][i]=0;
for(RI i=t-mid+1;i<kn;++i) a[d+1][i]=0;
NTT(a[d],kn,1),NTT(a[d+1],kn,1);
for(RI i=0;i<kn;++i) a[d][i]=1LL*a[d][i]*a[d+1][i]%mod;
NTT(a[d],kn,-1);
}
int C(int d,int u) {
int k1=1,k2=1;
for(RI i=d-u+1;i<=d;++i) k1=1LL*k1*i%mod;
for(RI i=1;i<=u;++i) k2=1LL*k2*i%mod;
return 1LL*k1*ksm(k2,mod-2)%mod;
}
int main()
{
scanf("%d%d%d",&n,&A,&B);
if(!A||!B||A+B-2>n-1) {puts("0");return 0;}
if(n==1) {puts("1");return 0;}
work(0,n-2,0);
ans=1LL*a[0][A+B-2]*C(A+B-2,B-1)%mod;
printf("%d\n",ans);
return 0;
}
第二类斯特林数
现在,小L准备把这些项链送给她的朋友们。假设有
n
n
n条项链,项链各不相同。她准备了
k
k
k个一模一样的礼盒,她准备将项链装进礼盒里且不让任何礼盒是空的。现在她又想知道有多少种方案了。
通过考虑第
n
n
n条项链是单独放一个礼盒还是跟其他项链挤同一个礼盒,小L很快写出了递推式:
S
2
(
n
,
k
)
=
S
2
(
n
−
1
,
k
−
1
)
+
k
S
2
(
n
−
1
,
k
)
S_2(n,k)=S_2(n-1,k-1)+kS_2(n-1,k)
S2(n,k)=S2(n−1,k−1)+kS2(n−1,k)
虽然不能生成函数做了,但是可以利用容斥原理,考虑空几个盒和盒子无序,得到一个新式子:
S
2
(
n
,
k
)
=
1
k
!
∑
i
=
0
k
(
−
1
)
i
C
k
i
(
k
−
i
)
n
S_2(n,k)=\frac{1}{k!}\sum_{i=0}^k (-1)^i C_k^i (k-i)^n
S2(n,k)=k!1i=0∑k(−1)iCki(k−i)n
这个可以写成卷积形式,用FFT
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)求出。
例题:log2058/洛谷P4091
原式=
∑
j
=
0
n
2
j
j
!
∑
i
=
0
n
S
2
(
i
,
j
)
\sum_{j=0}^n2^j j! \sum_{i=0}^n S_2(i,j)
∑j=0n2jj!∑i=0nS2(i,j)
将第二类斯特林数的通项公式代入得:
∑
j
=
0
n
2
j
j
!
∑
i
=
0
n
∑
k
=
0
j
(
−
1
)
k
k
!
(
j
−
k
)
i
(
j
−
k
)
!
\sum_{j=0}^n 2^j j! \sum_{i=0}^n \sum_{k=0}^j \frac{(-1)^k}{k!} \frac{(j-k)^i}{(j-k)!}
j=0∑n2jj!i=0∑nk=0∑jk!(−1)k(j−k)!(j−k)i
发现后面的那个东西很像卷积,于是我们就让它更像卷积一点:
∑
j
=
0
n
2
j
j
!
∑
k
=
0
j
(
−
1
)
k
k
!
∑
i
=
0
n
(
j
−
k
)
i
(
j
−
k
)
!
\sum_{j=0}^n 2^j j! \sum_{k=0}^j \frac{(-1)^k}{k!} \frac{\sum_{i=0}^n(j-k)^i}{(j-k)!}
j=0∑n2jj!k=0∑jk!(−1)k(j−k)!∑i=0n(j−k)i
对了,
∑
i
=
0
n
t
i
=
t
n
+
1
−
1
t
−
1
\sum_{i=0}^nt^i=\frac{t^{n+1}-1}{t-1}
∑i=0nti=t−1tn+1−1,所以函数
f
(
t
)
=
(
−
1
)
t
t
!
f(t)= \frac{(-1)^t}{t!}
f(t)=t!(−1)t和
g
(
t
)
=
∑
i
=
0
n
t
i
t
!
g(t)=\frac{\sum_{i=0}^nt^i}{t!}
g(t)=t!∑i=0nti都还挺好求的,那么答案就是:
∑
j
=
0
n
2
j
j
!
(
f
∗
g
)
(
j
)
\sum_{j=0}^n 2^j j! (f*g)(j)
j=0∑n2jj!(f∗g)(j)
#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int mod=998244353,G=3,N=262150;
int n,kn,len,ans;
int a[N],b[N],fac[N],ni[N],rev[N];
int ksm(int x,int y) {
int re=1;
for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
return re;
}
void NTT(int *a,int n,int x) {
for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
for(RI i=1;i<n;i<<=1) {
int gn=ksm(G,(mod-1)/(i<<1));
for(RI j=0;j<n;j+=(i<<1)) {
int g=1,t1,t2;
for(RI k=0;k<i;++k,g=1LL*g*gn%mod) {
t1=a[j+k],t2=1LL*g*a[j+i+k]%mod;
a[j+k]=(t1+t2)%mod,a[j+i+k]=(t1-t2+mod)%mod;
}
}
}
if(x==1) return;
int inv=ksm(n,mod-2);reverse(a+1,a+n);
for(RI i=0;i<n;++i) a[i]=1LL*a[i]*inv%mod;
}
int main()
{
scanf("%d",&n);
fac[0]=1;for(RI i=1;i<=n;++i) fac[i]=1LL*fac[i-1]*i%mod;
ni[n]=ksm(fac[n],mod-2);
for(RI i=n-1;i>=0;--i) ni[i]=1LL*ni[i+1]*(i+1)%mod;
for(RI i=0;i<=n;++i) {
a[i]=1LL*(1-2*(i&1)+mod)%mod*ni[i]%mod;
if(i!=1) b[i]=1LL*(ksm(i,n+1)-1+mod)%mod*ni[i]%mod*ksm(i-1+mod,mod-2)%mod;
else b[i]=n+1;
}
kn=1;while(kn<=n+n) kn<<=1,++len;
for(RI i=0;i<kn;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
NTT(a,kn,1),NTT(b,kn,1);
for(RI i=0;i<kn;++i) a[i]=1LL*a[i]*b[i]%mod;
NTT(a,kn,-1);
for(RI i=0,j=1;i<=n;++i,j=(j+j)%mod)
ans=(ans+1LL*j*fac[i]%mod*a[i]%mod)%mod;
printf("%d\n",ans);
return 0;
}