斯特林數 & 貝爾數學習筆記

背景:

好久沒有更blog\text{blog},最近都在準備模擬( )賽。



第一類斯特林數:

第一類斯特林數第nnmm列,記做[ nm]\LARGE[_{\,n}^m]

que\text{que}

題目傳送門:https://www.luogu.org/problem/P5408
現在你有nn個珠子,每一個珠子的顏色各不相同,求這些珠子組成mm個項鍊的方案數。
現在求1,2,3,...m1,2,3,...m時方案數。

sol\text{sol}

Sn,mS_{n,m}表示上面的方案數。
那麼有:
Sn,m=Sn1,m1+(n1)Sn1,mS_{n,m}=S_{n-1,m-1}+(n-1)S_{n-1,m}

解釋一下:對於當前的珠子來說,有兩種選擇:
[1][1]:新開一個項鍊,那麼方案數就要繼承Sn1,m1S_{n-1,m-1}
[2][2]:在原來的項鍊里加入,考慮它會加在某一個珠子的後面,由於之前已經有(n1)(n-1)個珠子,可以加在任意一個後面,因此方案數爲(n1)Sn1,m(n-1)S_{n-1,m}

可是這是Θ(nm)\Theta(nm)的大暴力啊。
第一類斯特林數有一個性質就是它可以表示下面的形式:
Sn,m=[xm]x(x+1)(x+2)+...+(x+n1)S_{n,m}=[x^m]x(x+1)(x+2)+...+(x+n-1)

現在這個式子就可以分治NTT\text{NTT}解決了。
時間複雜度:Θ(nlog2n)\Theta(n\log^2n)
而模板題卡這種做法,因此不能通過。

Θ(nlogn)\Theta(n\log n)的做法,可我太菜了,不想學。

code\text{code}
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
const LL mod=167772161,G=3,inv_G=55924054;
using namespace std;
	int r[800010],f[800010],a[20][800010],b[20][800010];
	int n,A,B,l,limit;
int dg(int x,int k)
{
	if(!k) return 1;
	int op=dg(x,k>>1);
	if(k&1) return (LL)op*op%mod*x%mod; else return (LL)op*op%mod;
}
int get_inv(int x)
{
	return dg(x,mod-2);
}
void init(int n)
{
	limit=1,l=0;
	while(limit<(n<<1))
		limit<<=1,l++;
	for(int i=1;i<limit;i++)
		r[i]=((r[i>>1]>>1)|((i&1)<<(l-1)));
}
void NTT(int *now,int limit,int op)
{
	for(int i=0;i<limit;i++)
		if(i<r[i]) swap(now[i],now[r[i]]);
	for(int mid=1;mid<limit;mid<<=1)
	{
		int wn=dg(op==1?G:inv_G,(mod-1)/(mid<<1));
		for(int j=0;j<limit;j+=(mid<<1))
		{
			int w=1;
			for(int k=0;k<mid;k++,w=((LL)w*wn)%mod)
			{
				int x=now[j+k],y=(LL)w*now[j+k+mid]%mod;
				now[j+k]=(x+y)%mod;
				now[j+k+mid]=(x-y+mod)%mod;
			}
		}
	}
	if(op==1) return;
	int INV=get_inv(limit);
	for(int i=0;i<limit;i++)
		now[i]=(LL)now[i]*INV%mod;
}
void poly_CDQ(int *f,int dep,int l,int r)
{
	if(l==r)
	{
		f[0]=l;f[1]=1;
		return;
	}
	int mid=(l+r)>>1;
	init(r-l+1+1);
	for(int i=0;i<limit;i++)
		a[dep][i]=b[dep][i]=0;
	poly_CDQ(a[dep],dep+1,l,mid),poly_CDQ(b[dep],dep+1,mid+1,r);
	init(r-l+1+1);
	NTT(a[dep],limit,1),NTT(b[dep],limit,1);
	for(int i=0;i<limit;i++)
		f[i]=(LL)a[dep][i]*b[dep][i]%mod;
	NTT(f,limit,-1);
}
int main()
{
	scanf("%d",&n);
	poly_CDQ(f,0,0,n-1);
	for(int i=0;i<=n;i++)
		printf("%d ",f[i]);
}


第二類斯特林數:

第二類斯特林數第nnmm列,記做{&ThinSpace;nm}\left\{{\LARGE_{\,n}^m}\right\}
這一個更加廣泛應用。

que\text{que}

題目傳送門:https://www.luogu.org/problem/P5395
現在你有nn個珠子,每一個珠子的顏色各不相同,求這些珠子放進mm個盒子的方案數。
現在求1,2,3,...m1,2,3,...m時方案數。

sol\text{sol}

Sn,mS_{n,m}表示上面的方案數。
那麼有:
Sn,m=Sn1,m1+mSn1,mS_{n,m}=S_{n-1,m-1}+mS_{n-1,m}

解釋一下:對於當前的珠子來說,有兩種選擇:
[1][1]:新開一個盒子,那麼方案數就要繼承Sn1,m1S_{n-1,m-1}
[2][2]:在原來的盒子裏加入,考慮它會加在某一個盒子裏,由於之前已經有mm個盒子,可以加在任意一個裏面,因此方案數爲(n1)Sn1,m(n-1)S_{n-1,m}

可是這是nmnm的大暴力啊。
第二類斯特林數有一個性質就是它可以表示下面的形式:
Sn,m=1m!k=0m(1)kCmk(mk)nS_{n,m}=\frac{1}{m!}\sum_{k=0}^{m}(-1)^kC_{m}^{k}(m-k)^n

Sn,m=1m!k=0m(1)km!k!(mk)!(mk)nS_{n,m}=\frac{1}{m!}\sum_{k=0}^{m}(-1)^k\frac{m!}{k!(m-k)!}(m-k)^n

Sn,m=k=0m(1)k1k!(mk)!(mk)nS_{n,m}=\sum_{k=0}^{m}(-1)^k\frac{1}{k!(m-k)!}(m-k)^n

Sn,m=k=0m(1)kk!(mk)n(mk)!S_{n,m}=\sum_{k=0}^{m}\frac{(-1)^k}{k!}\frac{(m-k)^n}{(m-k)!}

爲什麼呢?
假設每一個盒子都不同,並且我們允許空盒的存在。
但是我們當然就是不允許空盒存在啊。
所以考慮容斥。
枚舉我當前有kk個空盒子。
那麼先把這kk個盒子選出來,也就是CmkC_{m}^{k}
然後剩下mkm−k個盒子,nn個球可以隨便放,也就是(mk)n(m−k)^n
考慮到我們盒子是沒有區別的,所以除以m!m!

現在這個式子是一個卷積的形式,就可以NTT\text{NTT}解決了。
時間複雜度:Θ(nlogn)\Theta(n\log n)

code\text{code}
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
const int mod=167772161,G=3,inv_G=55924054;
using namespace std;
	int a[1000010],b[1000010],f[1000010],g[1000010],inv[1000010],Inv[1000010];
	int limit,n,l,r[1000010];
int dg(int x,int k)
{
	if(!k) return 1;
	int op=dg(x,k>>1);
	if(k&1) return (LL)op*op%mod*x%mod; else return (LL)op*op%mod;
}
int get_inv(int x)
{
	return dg(x,mod-2);
}
void init(int n)
{
	limit=1,l=0;
	while(limit<(n<<1))
		limit<<=1,l++;
	for(int i=1;i<limit;i++)
		r[i]=((r[i>>1]>>1)|((i&1)<<(l-1)));
}
void NTT(int *now,int limit,int op)
{
	for(int i=0;i<limit;i++)
		if(i<r[i]) swap(now[i],now[r[i]]);
	for(int mid=1;mid<limit;mid<<=1)
	{
		int wn=dg(op==1?G:inv_G,(mod-1)/(mid<<1));
		for(int j=0;j<limit;j+=(mid<<1))
		{
			int w=1;
			for(int k=0;k<mid;k++,w=((LL)w*wn)%mod)
			{
				int x=now[j+k],y=(LL)w*now[j+k+mid]%mod;
				now[j+k]=(x+y)%mod;
				now[j+k+mid]=(x-y+mod)%mod;
			}
		}
	}
}
void dft(int *f,int n,int limit)
{
	NTT(f,limit,-1);
	int INV=get_inv(limit);
	for(int i=0;i<n;i++)
		f[i]=(LL)f[i]*INV%mod;
}
void Init()
{
	inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++)
		inv[i]=((LL)mod-mod/i)*inv[mod%i]%mod;

	Inv[0]=Inv[1]=1;
	for(int i=2;i<=n;i++)
		Inv[i]=(LL)Inv[i-1]*inv[i]%mod;
}
int main()
{
	scanf("%d",&n);
	Init();
	for(int i=0;i<=n;i++)
	{
		f[i]=(((i&1)?-1ll:1ll)*Inv[i]+mod)%mod;
		g[i]=(LL)dg(i,n)*Inv[i]%mod;
	}
	init(n+1);
	NTT(f,limit,1),NTT(g,limit,1);
	for(int i=0;i<limit;i++)
		f[i]=(LL)f[i]*g[i]%mod;
	dft(f,n+1,limit);
	for(int i=0;i<=n;i++)
		printf("%d ",f[i]);
}


貝爾數:

que\text{que}

現在你有nn個珠子,每一個珠子的顏色各不相同,求這些珠子放進若干個盒子的方案數。
現在求1,2,3,...m1,2,3,...m時方案數。

sol\text{sol}

枚舉盒子數ii,顯然我們有:
Bn,m=i=1m{n&ThinSpace;i}B_{n,m}=\sum_{i=1}^{m}\left\{{\LARGE_{n}^{\,i}}\right\}

可是這樣的時間複雜度是Θ(nm)\Theta(nm)的,而且還要預處理第二類斯特林數,極其複雜。
考慮它的遞推式:
Bn=k=1n1Cn1kBkB_{n}=\sum_{k=1}^{n-1}C_{n-1}^{k}B_k

理解起來也不難,枚舉珠子數kk,考慮從n1n-1個珠子裏選出kk個珠子放在一個盒子裏,那麼
未完待續。



後記:

有什麼好的套路或性質以後再補充吧,畢竟還沒有做題。
歡迎大佬指出錯誤。
一些性質:
nk=i=0k{k&ThinSpace;i}Cnii!n^k=\sum_{i=0}^{k}\left\{{\LARGE_{k}^{\,i}}\right\}C_{n}^{i}i!

持續更哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章