斯特林数学习笔记

第一类斯特林数

定义

n 个互不相同的元素,划分为 k 个互不区分的非空轮换的方案数,记为 s(n,k),或 [nm]

一个轮换就是一个首尾相接的环形排列。如轮换 [A,B,C,D],我们认为 [A,B,C,D]=[B,C,D,A]=[C,D,A,B]=[D,A,B,C]。即,两个可以通过旋转而相互得到的轮换是等价的。需要注意,通过翻转得到的轮换不等价,如:[A,B,C,D][D,C,B,A]

第一类斯特林数也可以形象理解为:n 个人坐 m 张圆桌(没有空桌)的方案数。

递推式

[nk]=[n1k1]+(n1)[n1k]

其中边界为 [n0]=[n==0]

递推式的证明可以考虑组合意义,新加入一个元素时,有两种方案:

  • 将该元素单独至于一个轮换中,那么共有 [n1k1] 种方案。

  • 将该元素加入当任意一个现有的轮换中,共有 (n1)[n1k] 种方案。

第二类斯特林数

n 个互不相同的元素,划分为 m 个互不区分的非空子集的方案数。记为 {nm},也可记为 S(n,m)

递推式

{nk}={n1k1}+k{n1k}

其中边界为 {n0}=[n==0]

递推式的证明可以考虑组合意义,新加入一个元素时,有两种方案:

  • 将该元素单独至于一个新集合中,那么共有 {n1k1} 种方案。

  • 将该元素加入当任意一个现有的轮换中,共有 k{n1k} 种方案。

通项公式

利用二项式反演,可以得到第二类斯特林数的通项公式:

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

建筑师

对于一个 1,2,,n 的排列,设有 A 个数的左边都比它小,B 个数的右边都比它小,求满足的排列个数。

T 组询问,答案对 998244353 取模。

1n50000, 1A,B100, 1T200000

思路:

不难发现,n 一定满足题意,考虑以 n 为界限,在其左侧的属于 A,在其右侧的属于 B

设第 i 个位置上的数为 piprei=max1jipi

那么对于一个在 A 中的数字,一定满足 prei=pi,在 B 中的同理。

i,j 为前后两个在 A 中的数,那么对于 k(i,j),一定满足 pk<prek。考虑将 [i,j1] 划分为一个区间,那么最终可以得到 A+B1 个区间,如下图所示:

image

其中黑色的方块表示该点在 AB 中,其中 n 单独成一个区间。

不难发现,划分为 A+B1 个区间以后,区间内最大的数一定在区间的最左侧或最右侧。而对于其他的数,则可以全排列。

那么一个区间就等价于一个固定了开头的轮换,于是就可以套用第一类斯特林数,总的划分方案数为 [n1A+B2]

而一个区间既可以放在 A 中,也可以放在 B 中,将区间分组的方案数就为 (A+B2A1)

于是对于每一组询问,答案就是 (A+B2A1)[n1A+B2]

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=50010,M=210,mod=1e9+7;
int n,A,B,c[N][M],s[N][M];
int main()
{
	s[0][0]=1;for(int i=1;i<N;i++) for(int j=1;j<M;j++) s[i][j]=(s[i-1][j-1]+1ll*(i-1)*s[i-1][j]%mod)%mod;
	for(int i=0;i<M;i++) for(int j=0;j<=i;j++) if(!j) c[i][j]=1;else c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
	int T;scanf("%d",&T);while(T--) scanf("%d%d%d",&n,&A,&B),printf("%d\n",1ll*c[A+B-2][A-1]*s[n-1][A+B-2]%mod);
	return 0;
}

组合数问题

计算

(k=0nf(k)×xk×(nk))modp

的值。其中 n, x, p 为给定的整数,f(k) 为给定的一个 m 次多项式 f(k)=a0+a1k+a2k2++amkm(nk) 为组合数,其值为 (nk)=n!k!(nk)!

1n,x,p109,0ai109,0mmin(n,1000)

思路:

xk_=i=xk+1x=x(x1)(x2)(xk+1)。称为 xk下降幂

而对于单项式下降幂,与组合数存在以下联系:

(nk)km_=(nmkm)nm_

考虑将多项式 f(k)=i=0maiki 转换为 f(k)=i=0mbiki_ 的形式。

对于一个单项式 xn,与第二类斯特林数存在以下等式:

xn=i=0n{ni}(xi)i!=i=0n{ni}xi_

形象地理解一下,xn 可以看成是将 n 个小球分别放到 x 个盒子当中,允许有空盒子的方案数。那么就可以考虑枚举哪些盒子不为空,然后就可以套用第二类斯特林数。由于盒子是不同的,所以最后还要乘上一个阶层。

于是就可以将 f(k) 变形:

f(k)=i=0maiki=i=0maij=0i{ij}kj_

=i=0mki_j=im{ji}aj

得到 bi 的表达式:

bi=j=im{ji}aj

于是 bi 就可以 O(m2) 预处理出来。

接下来考虑对原式进行变形:

k=0nf(k)×xk×(nk)

=k=0ni=0mbiki_×xk×(nk)

利用上面的组合公式,可以得到:

k=0ni=0mbini_×xk×(niki)

=i=0mbini_k=0n×xk×(niki)

注意到当 k<i 时,后面的组合数没有意义,考虑内层枚举 ki ,得到:

i=0mbini_k=0ni×xk+i×(nik)

=i=0mbini_xik=0ni×xk×(nik)

此时不难发现,内层循环就是一个二项式的展开形式,利用二项式定理,可以得到:

i=0mbini_xi(x+1)ni

由于题目中满足 m2000,此时就可以直接枚举得到答案。

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
const int N=2023;
int n,x,ans,p,m,a[N],b[N],s[N][N];
void Add(int &a,int b){a+=b;if(a>=p) a-=p;}
void Mul(int &a,int b){a=1ll*a*b%p;}
int mul(int a,int b){int res=1;while(b) ((b&1)&&(res=1ll*res*a%p,0)),a=1ll*a*a%p,b>>=1;return res;}
int main()
{
	scanf("%d%d%d%d",&n,&x,&p,&m);for(int i=0;i<=m;i++) scanf("%d",&a[i]);s[0][0]=1;
	for(int i=1;i<=m;i++) for(int j=1;j<=i;j++) s[i][j]=(s[i-1][j-1]+1ll*j*s[i-1][j]%p)%p;
	for(int i=0;i<=m;i++) for(int j=i;j<=m;j++) Add(b[i],1ll*a[j]*s[j][i]%p);int n_i=1,x_i=1;
	for(int i=0;i<=m;i++) Add(ans,1ll*b[i]*n_i%p*x_i%p*mul(x+1,n-i)%p),Mul(n_i,n-i),Mul(x_i,x);printf("%d\n",ans);
	return 0;
}
posted @   曙诚  阅读(50)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
点击右上角即可分享
微信分享提示