【学习笔记】【数学】斯特林数基础
斯特林数基础
高等的我不会
前置知识:
圆排列相关
循环排列,又称圆排列,环排列,轮换。
指 个数中选 个不同的元素排列成一个无头无尾的环形,两个圆排列相同当且仅当索取元素个数相同,取法一致,环上排列顺序相同。
如: 是五个元素的一种圆排列。
而 则又是另一种圆排列,这是圆排列与直线排列的主要区别。
而根据刚刚我们知道,一种圆排列可以拆成五种不同的直线排列,而五个元素的直线排列有 种,设圆排列个数为 ,易得:
即,对于 个不同的元素,其圆排列个数为 。
而我们知道, 个相异元素里,选出 个数的方案数是 。
因此,从 个相异元素选出 个可以组成圆排列个数:
- 上升幂与下降幂(通过斯特林数转换普通幂)
上升幂、下降幂
上升幂与下降幂怎么来的我就不知道了,好像是微积分?
定义下降幂,上升幂 ,有:
他们之间有关联:
我认为这是显然的,读者自证不难(
小小的证明一下:
相应的,排列组合数也和二项式有关:
甚至有非常奇妙的有关同余的性质:
但是没什么用好像,也不是很想证明了,留给佬的闲话(什
证了的话可以@我谢谢喵
- 数学归纳法(通过斯特林数转换普通幂)
有关数学归纳法
数学归纳法是证明某个命题对于所有满足 的整数 的都成立的一种方法,具体步骤如下:
-
首先在 取得最小值 时证明命题,即基础。
-
其次对 ,假设 与 之间(包括它们本身)所有值都已经被证明,证明该命题对 成立,即归纳。
用有限步可以得到无限的结果,这就是数学归纳!
往往递推式可以用数学归纳法完美地建立。
定义:
斯特林数,多出现在组合枚举问题中。
-
第一类斯特林数 ,也被记为 ,表示将 个不同元素构成 个圆排列的方案数。
-
第二类斯特林数 ,也被记为 ,表示将 个不同元素分成 个集合的方案数。
由于第一类斯特林数与第二类斯特林数的 大小写难以区分,所以本文将采用另一种写法。
通常第二类斯特林数更加常用,因此首先描述第二类斯特林数。
第二类斯特林数
第二类斯特林数(斯特林子集数),表示将 个相异元素划分为 个互不区分的非空子集的方案数。
(互不区分:不考虑非空子集之间的排列)
递推式:
边界为 。
( 返回值是一个 bool
值)
证明:
-
这是一个递推式,我们每新加入一个新元素,将新元素单独开一个子集,有 种方案。
-
将新元素放入一个现有的非空子集,有 种方案。
加法原理相加。
最后边界是 ,毫无疑问如果 是无意义的,否则方案数为 。
于是有递推代码:
递推求第二类斯特林数
#define rg register int #define il inline il void pre(){ //递推求至S[n][m] S[0][0]=1; for(rg i=1;i<=n;++i){ for(rg j=1;j<=min(i,m);++j){ S[i][j]=S[i-1][j-1]+j*S[i-1][j]%mod; } } }
通项公式
这个公式可以用容斥原理或二项式反演证明,这里使用二项式反演:
设 个互异元素,划分到 个互异集合(包括空集)的方案数是 ,而 个互异元素,划分至 个两两不同的非空集合(不包括空集)的方案数是 。
根据二项式反演形式一:,易得:
而 与 的唯一不同点在于: 不计算非空集合之间的排列,因此 ,得证:
至于同一行第二类斯特林数之类的计算我不会,长大再学(
第一类斯特林数
第一类斯特林数(斯特林轮换数),,表示将 个相异元素划分为 个互不区分的非空的圆排列方案数。
(互不区分:不考虑非空子集之间的排列)
(有关圆排列已经在前置知识解释)
递推式
边界是 。
( 返回值是一个 bool
值)
相似的证明:
当我们插入一个新元素的时候,有两种方案:
-
新元素单独放入一个圆排列: 。
-
新元素插入到任何一个现有的圆排列中:。
加法原理易证。
对于 边界, 无意义,否则方案为 。
递推求第一类斯特林数
#define rg register int #define il inline il void pre(){ //递推求至s[n][m] s[0][0]=1; for(rg i=1;i<=n;++i){ for(rg j=1;j<=min(i,m);++j){ s[i][j]=s[i-1][j-1]+(i-1)*s[i-1][j]%mod; } } }
同一行第一类斯特林数我不会,长大再学(
上升幂、下降幂与普通幂的转化
有恒等式:
如何证明?数学归纳法(前置知识有)。
首先假设我们认为:
显然本式在 时成立,故假设本式在 区间成立,证明 是否成立。
(我只会这么证明,不要问我先辈是如何找到这个式子的,我真不会)
因为 ,所以 。(本式在下面第 行使用)
(ps:在第 行中:设 在 的情况下等于 , 在 的情况下等于 。)
因此得到:
证毕。
其余同理。
例题:Team Work
Team Work
题面翻译
给定 ,求:
题目描述
You have a team of people. For a particular task, you can pick any non-empty subset of people. The cost of having people for the task is .
Output the sum of costs over all non-empty subsets of people.
输入格式
Only line of input contains two integers representing total number of people and .
输出格式
Output the sum of costs for all non empty subsets modulo .
样例 #1
样例输入 #1
1 1
样例输出 #1
1
样例 #2
样例输入 #2
3 2
样例输出 #2
24
提示
In the first example, there is only one non-empty subset with cost .
In the second example, there are seven non-empty subsets.
- with cost
- with cost
- with cost
- with cost
- with cost
- with cost
- with cost
The total cost is .
解题:
题意就是给你 个元素,你可以选任意几个元素组成的非空子集,选 个元素的代价是 。
要求输出所有非空子集的元素代价总和。
也就是要求输出答案 。
而 。
对于 可以用第二类斯特林数展开:
推柿子:
的 要取 。
然后某些 OJ 有一些离谱数据,什么 之类的,因为 较小 过大,所以直接暴力过就行。
Miku's Code
#include<bits/stdc++.h> using namespace std; #define rg register int #define il inline #define int long long typedef long double llf; namespace mystd{ il int Max(int a,int b){ if(a<b) return b; else return a; } il int Min(int a,int b){ if(a>b) return b; else return a; } il int Abs(int a){ if(a<0) return a*(-1); else return a; } } const int maxn=2e5+50,mod=1e9+7; int n,k,S[5000][5000]; int fac[maxn],inv[maxn],facinv[maxn]; int ans; int qpow(int x,int y){ int ans=1; while(y>0){ if(y&1) ans=(ans*x)%mod; x=(x*x)%mod; y=y>>1; } return ans%mod; } int getc(int y,int x,int m){ if(x<y) return 0; return fac[x]%m*facinv[y]%m*facinv[x-y]%m; } int lucas(int y,int x,int m){ if(y==0) return 1; return getc(y%m,x%m,m)%m*lucas(y/m,x/m,m)%m; } il void input(){ scanf("%lld %lld",&n,&k); } il void pre(int w){ S[0][0]=1,fac[0]=fac[1]=1; for(int i=1;i<=w;++i){ fac[i]=i*fac[i-1]%mod; for(int j=1;j<=i;++j){ S[i][j]=(S[i-1][j-1]+(int)j*S[i-1][j]%mod)%mod; } } } il void init(int maxp){ fac[0]=fac[1]=1; inv[0]=inv[1]=1; facinv[0]=facinv[1]=1; for(int i=2;i<=maxp-1;++i){ fac[i]=i*fac[i-1]%mod; } for(int i=2;i<=maxp-1;++i){ inv[i]=(mod-mod/i*inv[mod%i]%mod+mod)%mod; } for(int i=2;i<=maxp-1;++i){ facinv[i]=facinv[i-1]*inv[i]%mod; } } signed main(){ input(); if(k<=5000){ pre(k); for(int j=0;j<=min(n,k);++j){ int res=0; res=(int)S[k][j]*qpow(2,n-j)%mod; for(int q=n-j+1;q<=n;++q){ res=res*q%mod; } ans=(ans+res)%mod; } printf("%lld",ans); } else{ init(2e5+50); for(int i=0;i<=n;++i){ int res=0; res=lucas(i,n,mod)*qpow(i,k)%mod; ans=(ans+res)%mod; } printf("%lld",ans); return 0; } return 0; }
结束了,所有 latex
手打可能出错,若有错误请@我。
一次卷积以及反演我不会,长大再学(
upd on 10.7:集中修复部分 问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步