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

定义

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

第二类斯特林数的递推式:{nm}={n1m1}+m{n1m}

第一类斯特林数的递推式:[nm]=[n1m1]+(n1)[n1m]

第二类斯特林数

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

{nm}=i=0m(1)miini!(mi)!

可以用卷积计算,O(nlogn)

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");

第二类斯特林数

一个集合装 i 个元素且非空的方案的 EGF:i=1+xii!=ex1

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

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

lnexp 做多项式快速幂,O(nlogn)

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");

第一类斯特林数

类似的,列出一个轮换的 EGF:i=1+(i1)!xii!=i=1+xii

这里乘以 (i1)! 是因为有 (i1)! 种轮换的排法。

同样求 m 次幂,O(nlogn)

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");

第一类斯特林数

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

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

Fn(x)=(n1)Fn1(x)+xFn1(x)=i=0n1(x+i)=(x+n1)!(x1)!=xn

考虑倍增,x2n=xn(x+n)n。已经求出了 xn=f(x)=i=0naixi,要求出 f(x+k)

f(x+k)=i=0nai(x+k)i=i=0naij=0i(ij)xjkij=i=0nxij=in(ji)kjiaj=i=0nxii!j=inkji(ji)!j!aj

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

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 ;
}

下降幂多项式乘法

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

先考虑一项,即 xn 的点值表示法。设一个 EGF:F(x)=i=0\infinini!xi,然后推式子:

F(x)=i=0\infinini!xi=i=0\infini!i!(in)!xi=i=n\infin1(in)!xi=xni=0\infinxii!

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

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

把点值表示法的 EGF 变回下降幂多项式也是容易的,和 ex 做一次卷积即可。

时间复杂度是 O(nlogn)

下面代码每调用一次 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)

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

分治求的是只考虑区间内的项,转换后的普通多项式,以及 xrl+1 转换后的普通多项式。

注意分治的时候是把 xl+i 项看成 xi 项,就相当于把 [l,r] 项提出来。

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

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

得到 X(x)=XL(x)XR(xlenl)G(x)=L(x)+R(xlenl)XL(x)

时间复杂度是 O(nlog2n)

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 ;
}

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

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

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

G(x)=L(x)+xlenlR(x),其中 xlenl 为普通多项式。

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

时间复杂度为 O(nlog2n)

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 @   fydj  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示