斯特林数和下降幂学习笔记

定义

第二类斯特林数 \(n\brace m\) 表示 \(n\)两两不同的元素划分为 \(m\)互不区分非空子集的方案数;第一类斯特林数 \(n \brack m\) 表示 \(n\)两两不同的元素划分为 \(m\)互不区分非空轮换(可以理解为环)的方案数。

第二类斯特林数的递推式:\({n\brace m}={n-1\brace m-1}+m{n-1\brace m}\)

第一类斯特林数的递推式:\({n\brack m}={n-1\brack m-1}+(n-1){n-1\brack m}\)

第二类斯特林数 \(\bullet\)

用容斥原理和二项式反演可以证明:

\[{n \brace m}=\sum_{i=0}^m \frac{(-1)^{m-i}i^n}{i!(m-i)!} \]

可以用卷积计算,\(O(n\log n)\)

for(i=0;i<=n;++i) f[i]=kuai(i,n)*finv[i]%moder;
for(i=0;i<=n;++i) g[i]=i&1?sub(0,finv[i]):finv[i];
getlim(n<<1),ntt(f,1),ntt(g,1);
for(i=0;i<lim;++i) h[i]=f[i]*g[i]%moder;
ntt(h,-1);
for(i=0;i<=n;++i) printf("%lld ",h[i]); printf("\n");

第二类斯特林数 \(\bullet\)

一个集合装 \(i\) 个元素且非空的方案的 EGF:\(\sum _{i=1}^{+\infty} \frac{x^i}{i!}=e^x-1\)

求它的 \(m\) 次方就得到 \(m\) 个集合的 EGF,也就得到一列的第二类斯特林数。

因为是 EGF,所以要乘上 \(i!\);因为集合互不区分,所以要除以 \(m!\)

\(\ln\)\(\exp\) 做多项式快速幂,\(O(n\log n)\)

for(i=0;i<n;++i) f[i]=finv[i+1];
Ln(f,lnf,n);
for(i=0;i<n;++i) lnf[i]=lnf[i]*K%moder;
Exp(lnf,explnf,n);
for(i=0;i<K;++i) f[i]=0;
for(i=K;i<=n;++i) f[i]=explnf[i-K];
for(i=0;i<=n;++i) f[i]=f[i]*finv[K]%moder*fact[i]%moder;
for(i=0;i<=n;++i) printf("%lld ",f[i]); printf("\n");

第一类斯特林数 \(\bullet\)

类似的,列出一个轮换的 EGF:\(\sum_{i=1}^{+\infty} \frac{(i-1)!x^i}{i!}=\sum_{i=1}^{+\infty}\frac{x^i}{i}\)

这里乘以 \((i-1)!\) 是因为有 \((i-1)!\) 种轮换的排法。

同样求 \(m\) 次幂,\(O(n\log n)\)

for(i=0;i<n;++i) f[i]=finv[i+1]*fact[i]%moder;
Ln(f,lnf,n);
for(i=0;i<n;++i) lnf[i]=lnf[i]*K%moder;
Exp(lnf,explnf,n);
for(i=0;i<K;++i) f[i]=0;
for(i=K;i<=n;++i) f[i]=explnf[i-K];
for(i=0;i<=n;++i) f[i]=f[i]*finv[K]%moder*fact[i]%moder;
for(i=0;i<=n;++i) printf("%lld ",f[i]); printf("\n");

第一类斯特林数 \(\bullet\)

感觉是最难写的,也是 OI-Wiki 这四种中唯一一个没给代码的。

根据公式,构造同一行第一类斯特林数的生成函数:

\[\begin{aligned} F_n(x)&=(n-1)F_{n-1}(x)+xF_{n-1}(x)\\ &=\prod_{i=0}^{n-1}(x+i)\\ &=\frac{(x+n-1)!}{(x-1)!}\\ &=x^{\overline n} \end{aligned} \]

考虑倍增,\(x^{\overline {2n}}=x^{\overline n}(x+n)^{\overline n}\)。已经求出了 \(x^{\overline n}=f(x)=\sum_{i=0}^n a_ix^i\),要求出 \(f(x+k)\)

\[\begin{aligned} f(x+k)&=\sum_{i=0}^na_i(x+k)^i\\ &=\sum_{i=0}^na_i\sum_{j=0}^i {i\choose j}x^jk^{i-j}\\ &=\sum_{i=0}^nx_i\sum_{j=i}^n{j\choose i}k^{j-i}a_j\\ &=\sum_{i=0}^n\frac{x^i}{i!}\sum_{j=i}^n \frac{k^{j-i}}{(j-i)!}j!a_{j} \end{aligned} \]

是一个差卷积的形式,时间复杂度是 \(O(n\log n)\)

void solve(int a[],int n) {
	if(n==0) return a[0]=1,void();
	static int f[N]={},g[N]={};
	solve(f,n>>1); int i,nn=n>>1;
	for(i=0;i<=nn;++i) f[i]=(ll)a[i]*fact[i]%moder;
	g[0]=1; for(i=1;i<=nn;++i) g[i]=(ll)g[i-1]*nn%moder;
	MulT(f,g,nn+1,nn+1);
	for(i=0;i<=nn;++i) f[i]=(ll)f[i]*finv[i]%moder;
	Mul(a,f,nn+1,nn+1);
	if(n&1) {
		for(i=n;i;--i) f[i]=(f[i-1]+(ll)f[i]*n)%moder;
		f[0]=(ll)f[0]*n%moder;
	}
	return ;
}

下降幂多项式乘法

和普通多项式乘法一样,考虑求出下降幂多项式的点值表示法。

先考虑一项,即 \(x^{ \underline n }\) 的点值表示法。设一个 EGF:\(F(x)=\sum_{i=0}^{\infin} \frac{i^{ \underline n }}{i!}x^i\),然后推式子:

\[\begin{aligned} F(x)&=\sum_{i=0}^{\infin} \frac{i^{ \underline n }}{i!}x^i\\ &=\sum_{i=0}^{\infin} \frac{i!}{i!(i-n)!}x^i\\ &=\sum_{i=n}^{\infin} \frac{1}{(i-n)!}x^i\\ &=x^n\sum_{i=0}^{\infin} \frac{x^i}{i!} \end{aligned} \]

从这个式子可以看出,把下降幂多项式转换为点值表示法的 EGF,相当于和 \(e^x\) 做一次卷积。

把两个乘数的点值表示法的 EGF 按位相乘,再乘上 \(i!\)(因为两个 EGF 相乘 \(\frac{1}{i!}\) 项会被算两次),就得到了乘积的点值表示法的 EGF。

把点值表示法的 EGF 变回下降幂多项式也是容易的,和 \(e^{-x}\) 做一次卷积即可。

时间复杂度是 \(O(n\log n)\)

下面代码每调用一次 DFT 都可能会让 \(lim\) 改变,记得 getlim。

void DFT(int f[],int n,int type) {
	static int e[N]={}; int i;
	getlim(n<<1);
	for(i=n;i<lim;++i) f[i]=e[i]=0;
	for(i=0;i<n;++i) e[i]=type==-1&&(i&1)?sub(0,finv[i]):finv[i];
	ntt(f,1),ntt(e,1);
	for(i=0;i<lim;++i) f[i]=(ll)f[i]*e[i]%moder;
	ntt(f,-1); for(i=n;i<lim;++i) f[i]=0;
	return ;
}

下降幂多项式转普通多项式

受第一类斯特林数行的影响,想到了分治,推了一天总算把式子推出来了。

现在要把下降幂多项式 \(F(x)\) 转换为普通多项式 \(G(x)\)

考虑分治,假设现在只考虑 \(\left[l,r\right]\) 项,把它分成 \(\left[l,mid\right]\)\(\left(mid,r\right]\)

分治求的是只考虑区间内的项,转换后的普通多项式,以及 \(x^{ \underline {r-l+1}}\) 转换后的普通多项式。

注意分治的时候是把 \(x^{ \underline {l+i}}\) 项看成 \(x^{ \underline i}\) 项,就相当于把 \(\left[l,r\right]\) 项提出来。

设左区间的答案为 \(L(x)\)\(XL(x)\),右区间的答案为 \(R(x)\)\(XR(x)\),区间的答案为 \(G(x)\)\(X(x)\),左区间的长度为 \(lenl=mid-l+1\)

在第一类斯特林数行中,已经知道了如何根据 \(f(x)\)\(O(n\log n)\) 的时间内求出 \(f(x+k)\)

得到 \(X(x)=XL(x)*XR(x-lenl)\)\(G(x)=L(x)+R(x-lenl)*XL(x)\)

时间复杂度是 \(O(n\log^2 n)\)

poly xAdd(poly f,int k) {
	static poly a,b;
	int n=f.size(),i,temp;
	a.resize(n),b.resize(n);
	for(i=0,temp=1;i<n;++i,temp=(ll)temp*k%moder)
		a[i]=(ll)fact[i]*f[i]%moder,
		b[i]=(ll)temp*finv[i]%moder;
	f=Decmul(a,b);
	for(i=0;i<n;++i) f[i]=(ll)f[i]*finv[i]%moder;
	return f;
}
void solve(int l,int r,const poly &f,poly &g,poly &x) {
	if(l==r) return g.resize(1),g[0]=f[l],x.resize(2),x[0]=0,x[1]=1,void();
	poly L,xL,R,xR; int mid=l+r>>1;
	solve(l,mid,f,L,xL),solve(mid+1,r,f,R,xR);
	x=Mul(xL,xAdd(xR,sub(0,mid-l+1)));
	g=Add(L,Mul(xAdd(R,sub(0,mid-l+1)),xL));
	return ;
}

普通多项式转下降幂多项式

还是考虑分治,每次处理区间 \(\left[l,r\right]\) 转换为下降幂多项式的答案。

设左区间 \(\left[l,mid\right]\) 的答案为 \(L(x)\),右区间 \(\left(mid,r\right]\) 的答案为 \(R(x)\),区间 \(\left[l,r\right]\) 的答案为 \(G(x)\)\(lenl=mid-l+1\)。三个多项式都是下降幂多项式。

\(G(x)=L(x)+x^{lenl}*R(x)\),其中 \(x^{lenl}\) 为普通多项式。

普通多项式和下降幂多项式相乘还是一样,求出点值表示法,\(x^{lenl}\) 的点值表示法可以暴力算,而 \(R(x)\) 的点值表示法的 EGF 可以卷一个 \(e^x\) 得到。

时间复杂度为 \(O(n\log^2 n)\)

void dft(poly &f,int mod) {
	int n=f.size(),i; poly g;
	getlim(n<<1),f.resize(lim),g.resize(lim);
	for(i=0;i<n;++i)
		g[i]=mod&&(i&1)?sub(0,finv[i]):finv[i];
	ntt(f,1),ntt(g,1);
	for(i=0;i<lim;++i) f[i]=(ll)f[i]*g[i]%moder;
	ntt(f,-1),f.resize(n);
	return ;
}
void solve(int l,int r,const poly &f,poly &g) {
	if(l==r) return g.resize(1),g[0]=f[l],void();
	int mid=l+r>>1,i; poly L,R;
	solve(l,mid,f,L),solve(mid+1,r,f,R);
	poly x;
	getlim(r-l+1),x.resize(lim),R.resize(lim);
	dft(R,0),getlim(r-l+1);
	for(i=0;i<lim;++i)
		x[i]=kuai(i,mid-l+1);
	for(i=0;i<lim;++i)
		R[i]=(ll)R[i]*x[i]%moder;
	dft(R,1),R.resize(r-l+1);
	g=Add(L,R);
	return ;
}
posted @ 2024-08-22 22:28  fydj  阅读(13)  评论(0编辑  收藏  举报