斯特林数和下降幂学习笔记
定义
第二类斯特林数 \(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\) 行
用容斥原理和二项式反演可以证明:
可以用卷积计算,\(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 这四种中唯一一个没给代码的。
根据公式,构造同一行第一类斯特林数的生成函数:
考虑倍增,\(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)\)。
是一个差卷积的形式,时间复杂度是 \(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\),然后推式子:
从这个式子可以看出,把下降幂多项式转换为点值表示法的 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 ;
}