【总结】斯特林数

前言

目前由于时间关系,只研究了第一类斯特林数。

有关第二类斯特林数的问题将尽快补上。

2019.3.26 第二类斯特林数已补充

第一类斯特林数

第一类斯特林数的定义是:
[ n m ] 表 示 将 n 个 数 , 放 入 m 个 圆 排 列 的 方 案 数 {n \brack m}表示将n个数,放入m个圆排列的方案数 [mn]nm
圆排列:只考虑元素之间的相对位置,不关心绝对位置。换言之可以像圆一样转动。类似置换群中的循环。

有一个很显然的递推式
[ n m ] = [ n − 1 m − 1 ] + ( n − 1 ) ∗ [ n − 1 m ] {n \brack m}={n-1 \brack m-1}+(n-1)*{n-1 \brack m} [mn]=[m1n1]+(n1)[mn1]
解释是:n可以单独成一个圆排列。也可以加入到之前任何一个数左边。

不过这是 O ( n 2 ) O(n^2) O(n2)的,效率非常低下。

首先,有一个结论:
多项式 ∏ i = 0 i &lt; n ( x + i ) 展 开 后 , x i 的 系 数 正 好 就 是 [ n i ] \prod_{i=0}^{i&lt;n}(x+i)展开后,x^i的系数正好就是{n \brack i} i=0i<n(x+i)xi[in]
证明比较简单。
考虑递推的过程,在最开始,将 [ 1 1 ] { 1\brack 1} [11]作为多项式的一次项系数。
然后,每次递推,系数的转移都可以看作: k i = k i − 1 + ( t − 1 ) k i k_i=k_{i-1}+(t-1)k_i ki=ki1+(t1)ki,其中 t t t表示当前多项式的最高次项。
那么相当于就是在原来的多项式的基础上,乘上了 ( x + t − 1 ) (x+t-1) (x+t1)这个多项式。
所以从第1层转移到第n层,就得依次乘上
∏ i = 1 i &lt; n ( x + i ) \prod_{i=1}^{i&lt;n}(x+i) i=1i<n(x+i)
加上最开始的一次项: ∏ i = 0 i &lt; n ( x + i ) \prod_{i=0}^{i&lt;n}(x+i) i=0i<n(x+i)

由此,可以有一个 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)的分治FFT/NTT的方法了。

现在,考虑更高效的算法。

假设当前要求的多项式是
∏ i = 0 i &lt; 2 n ( x + i ) \prod_{i=0}^{i&lt;2n}(x+i) i=0i<2n(x+i)
可以先递归地求出前n项的乘积f
f ( x ) = ∏ i = 0 i &lt; n ( x + i ) = ∑ i = 1 i ≤ n a i x i f(x)=\prod_{i=0}^{i&lt;n}(x+i)=\sum_{i=1}^{i\leq n} a_ix^i f(x)=i=0i<n(x+i)=i=1inaixi
现在其实可以很简单地求出后n项的乘积 g ( x ) = ∏ i = n i &lt; 2 n ( x + i ) g(x)=\prod_{i=n}^{i&lt;2n}(x+i) g(x)=i=ni<2n(x+i)

g ( x ) = ∑ i = 1 i ≤ n a i ( x + n ) i g(x)=\sum_{i=1}^{i\leq n}a_i(x+n)^i g(x)=i=1inai(x+n)i
由二项式定理得
= ∑ i = 1 i ≤ n a i ∑ j = 0 j ≤ i ( i j ) n i − j x j =\sum_{i=1}^{i\leq n}a_i\sum_{j=0}^{j\leq i}{i \choose j}n^{i-j}x^j =i=1inaij=0ji(ji)nijxj
= ∑ i = 1 i ≤ n ∑ j = 0 j ≤ i ( i j ) n i − j a i x j =\sum_{i=1}^{i\leq n}\sum_{j=0}^{j\leq i}{i \choose j}n^{i-j}a_ix^j =i=1inj=0ji(ji)nijaixj
= ∑ i = 1 i ≤ n ∑ j = 0 j ≤ i ( n i − j ( i − j ) ! ) ( a i ∗ i ! ) ( x j j ! ) =\sum_{i=1}^{i\leq n}\sum_{j=0}^{j\leq i}(\frac {n^{i-j}} {(i-j)!})(a_i*i!)(\frac {x^j} {j!}) =i=1inj=0ji((ij)!nij)(aii!)(j!xj)

这就是个裸的卷积了。

所以可以算出这部分,得到 g ( x ) g(x) g(x)再乘上之前递归得到的 f ( x ) f(x) f(x)就成功做到倍增了。

注意:当n为奇数时,要向下取整,最后再 O ( n ) O(n) O(n)转移一次。


例题

2019雅礼集训permutation

第二类斯特林数

第二类比第一类简单些。。。(在求值方面)

与第一类相对应的,第二类的组合意义是:
S 2 ( n , m ) S_2(n,m) S2(n,m)表示将n个数,分为m个非空集合的方案数。(集合即与顺序无关)

那么转移式就要变了:
S 2 ( n , m ) = S 2 ( n − 1 , m − 1 ) + m ∗ S 2 ( n − 1 , m ) S_2(n,m)=S_2(n-1,m-1)+m*S_2(n-1,m) S2(n,m)=S2(n1,m1)+mS2(n1,m)

不过和第一类不同,这里不需要转移到某个上升幂的形式

求解的方法可以利用容斥:
即考虑集合让集合之间有序(最后再除以m!使其仍然无序),然后就可以暴力枚举必须空的集合的数量进行容斥:
S 2 ( n , m ) = 1 m ! ∑ i = 0 i ≤ m ( − 1 ) i C ( m , i ) ( m − i ) n S_2(n,m)=\frac {1} {m!}\sum_{i=0}^{i\leq m}(-1)^iC(m,i)(m-i)^n S2(n,m)=m!1i=0im(1)iC(m,i)(mi)n
上式中 C ( m , i ) C(m,i) C(m,i)即枚举空的集合是哪些,然后n个点可以随意分配到任意一个不必须空的集合中。

这个式子可以拆开:
S 2 ( n , m ) = ∑ i = 0 i ≤ n ( − 1 ) i ( m − i ) n i ! ( m − i ) ! S_2(n,m)=\sum_{i=0}^{i\leq n}\frac {(-1)^i(m-i)^n} {i!(m-i)!} S2(n,m)=i=0ini!(mi)!(1)i(mi)n

中间分开
S 2 ( n , m ) = ∑ i = 0 i ≤ n ( − 1 ) i i ! × ( m − i ) n ( m − i ) ! S_2(n,m)=\sum_{i=0}^{i\leq n}\frac {(-1)^i} {i!} \times \frac {(m-i)^n} {(m-i)!} S2(n,m)=i=0ini!(1)i×(mi)!(mi)n
没错这就是个卷积

弄成两个多项式卷积,其中m次项的系数就是答案

模板题 洛谷P4091

代码:

#include<cstdio>
#include<algorithm>
#include<cmath>
#define SF scanf
#define PF printf
#define MAXN 300010
#define MOD 998244353
using namespace std;
typedef long long ll;
const int G=3;
ll fsp(ll x,int y){
	ll res=1;
	while(y){
		if(y&1)
			res=res*x%MOD;
		x=x*x%MOD;
		y>>=1;
	}
	return res;
}
void NTT(ll A[],int N,int flag){
	for(int i=1,j=0;i<N;i++){
		for(int d=N;j^=d>>=1,~j&d;);
		if(i<j)
			swap(A[i],A[j]);
	}
	for(int i=1;i<N;i<<=1){
		ll wn=fsp(G,(MOD-1)/(i<<1));
		if(flag)
			wn=fsp(wn,MOD-2);
		for(int j=0;j<N;j+=(i<<1)){
			ll w=1;
			for(int k=0;k<i;k++,w=w*wn%MOD){
				ll x=A[j+k],y=A[i+j+k]*w%MOD;
				A[j+k]=(x+y)%MOD;
				A[i+j+k]=(x-y+MOD)%MOD;
			}
		}
	}
	if(flag){
		ll invN=fsp(N,MOD-2);
		for(int i=0;i<N;i++)
			A[i]=A[i]*invN%MOD;	
	}
}
ll fac[MAXN],ifac[MAXN],res[MAXN],f[MAXN],g[MAXN];
void mul(ll A[],ll B[],int n,int m,ll res[]){
	static ll tmp1[MAXN],tmp2[MAXN];
	for(int i=0;i<n;i++)
		tmp1[i]=A[i];
	for(int i=0;i<m;i++)
		tmp2[i]=B[i];
	int p=1;
	while(p<=n+m)
		p<<=1;
	NTT(tmp1,p,0);
	NTT(tmp2,p,0);
	for(int i=0;i<p;i++)
		res[i]=tmp1[i]*tmp2[i]%MOD;
	NTT(res,p,1);
	for(int i=0;i<p;i++)
		tmp1[i]=tmp2[i]=0;
}
int main(){
	int n;
	SF("%d",&n);	
	fac[0]=1;
	for(int i=1;i<=n;i++)
		fac[i]=fac[i-1]*i%MOD;
	ifac[n]=fsp(fac[n],MOD-2);
	for(int i=n;i>=1;i--)
		ifac[i-1]=ifac[i]*i%MOD;
	for(int i=0;i<=n;i++){
		if(i%2==0)
			f[i]=ifac[i];
		else
			f[i]=MOD-ifac[i];	
	}
	for(int i=0;i<=n;i++){
		if(i!=1)
			g[i]=(fsp(i,n+1)-1)*fsp(i-1,MOD-2)%MOD*ifac[i]%MOD;
		else
			g[i]=n+1;
	}
//	for(int i=0;i<=n;i++)
//		PF("%lld ",f[i]);
//	PF("\n");
//	for(int i=0;i<=n;i++)
//		PF("%lld ",g[i]);
//	PF("\n");
	mul(f,g,n+1,n+1,res);
//	for(int i=0;i<=2*n;i++)
//		PF("%lld ",res[i]);
//	PF("\n");
	ll ans=0;
	for(int i=0;i<=n;i++)
		ans=(ans+res[i]*fsp(2,i)%MOD*fac[i]%MOD)%MOD;
	PF("%lld",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值