第二类斯特林数学习笔记

定义

第二类斯特林数 \(S(n,m)\)

表示的是把 \(n\) 个不同的小球放在 \(m\) 个相同的盒子里方案数

求法

递推式

$S(n,m)=S(n-1,m-1)+mS(n-1,m) $

含义分别是新开了一个集合和在已有的集合中选择一个放

容斥

\(S(n,m)=\frac{1}{m!}\sum_{i=0}^m(-1)^iC_m^i(m-i)^n\)

考虑用二项式反演

假设一共有 \(n\) 个小球

\(f[i]\) 为恰好有 \(i\) 个盒子放了小球的方案数

\(g[i]\) 为至多有 \(i\) 个盒子放了小球的方案数

那么 \(g[k]=\sum_{i=0}^k C_k^if[i]\)

\(f[k]=\sum_{i=0}^k(-1)^{k-i}C_k^ig[i]=\sum_{i=0}^k(-1)^{k-i}C_k^i i^n\)

化得好看一点就成了

\(f[k]=\sum_{i=0}^k(-1)^iC_k^i (k-i)^n\)

那么 \(f[m]=\sum_{i=0}^m(-1)^iC_m^i(m-i)^n\)

因为我们算组合数的时候是按照盒子不同算的,所以还要除以一个 \(m!\)

这个式子可以化成卷积的形式

\(S(n,m)=\frac{1}{m!}\sum_{i=0}^m(-1)^iC_m^i(m-i)^n\)

\(S(n,m)=\sum_{i=0}^m\frac{(-1)^i}{i!}\frac{(m-i)^n}{(m-i)!}\)

就可以用 \(ntt\)\(nlogn\) 的时间复杂度内求出一行的第二类斯特林数

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=524289,mod=167772161,G=3;
inline int addmod(rg int now1,rg int now2){
	return now1+=now2,now1>=mod?now1-mod:now1;
}
inline int delmod(rg int now1,rg int now2){
	return now1-=now2,now1<0?now1+mod:now1;
}
inline int mulmod(rg long long now1,rg int now2){
	return now1*=now2,now1>=mod?now1%mod:now1;
}
int ksm(rg int ds,rg int zs){
	rg int nans=1;
	while(zs){
		if(zs&1) nans=mulmod(nans,ds);
		ds=mulmod(ds,ds);
		zs>>=1;
	}
	return nans;
}
int w[25][maxn],wz[maxn];
void ntt(rg int A[],rg int lim,rg int typ){
	for(rg int i=0;i<lim;i++) if(i<wz[i]) std::swap(A[i],A[wz[i]]);
	for(rg int len=1,t0=0;len<lim;len<<=1,t0++){
		for(rg int j=0,now=len<<1;j<lim;j+=now){
			for(rg int k=0;k<len;k++){
				rg int x=A[j+k],y=mulmod(A[j+k+len],w[t0][k]);
				A[j+k]=addmod(x,y),A[j+k+len]=delmod(x,y);
			}
		}
	}
	if(typ==-1){
		std::reverse(A+1,A+lim);
		rg int ny=ksm(lim,mod-2);
		for(rg int i=0;i<lim;i++) A[i]=mulmod(A[i],ny);
	}
}
int a[maxn],b[maxn],jc[maxn],jcc[maxn],ny[maxn],n;
void pre(){
	ny[1]=1;
	for(rg int i=2;i<=n;i++) ny[i]=mulmod(mod-mod/i,ny[mod%i]);
	jc[0]=jcc[0]=1;
	for(rg int i=1;i<=n;i++) jc[i]=mulmod(jc[i-1],i),jcc[i]=mulmod(jcc[i-1],ny[i]);
}
int main(){
	n=read();
	pre();
	for(rg int i=0;i<=n;i++){
		if(i&1) a[i]=mod-jcc[i];
		else a[i]=jcc[i];
		b[i]=mulmod(ksm(i,n),jcc[i]);
	}
	rg int lim=1,bit=0;
	for(;lim<=n+n;lim<<=1) bit++;
	for(rg int i=0;i<lim;i++) wz[i]=(wz[i>>1]>>1)|((i&1)<<(bit-1));
	for(rg int len=1,t0=0;len<lim;len<<=1,t0++){
		w[t0][0]=1,w[t0][1]=ksm(G,(mod-1)/(len<<1));
		for(rg int i=2;i<len;i++) w[t0][i]=mulmod(w[t0][1],w[t0][i-1]);
	}
	ntt(a,lim,1),ntt(b,lim,1);
	for(rg int i=0;i<lim;i++) a[i]=mulmod(a[i],b[i]);
	ntt(a,lim,-1);
	for(rg int i=0;i<=n;i++) printf("%d ",a[i]);
	printf("\n");
	return 0;
}

应用

\(n^k=\sum_ { i=0}^k S(k,i)×i!×C(n,i)\)

左边的含义就是 \(k\) 个球放在 \(n\) 个盒子里

右边的含义是枚举有多少个盒子放

也可以写成下降幂的形式

\(n^k=\sum_{i=0}^k S(k,i) n^{\underline i}\)

Tags: