组合数学 Part3 | 斯特林数

第二类斯特林数

第二类斯特林数,记作 \(\genfrac\{\}{0pt}{}{n}{k}\),表示把 \(n\) 个不同元素放进 \(k\) 个非空集合的方案数。

递推式

\[\genfrac\{\}{0pt}{}{n}{k}=\genfrac\{\}{0pt}{}{n-1}{k-1}+k\times\genfrac\{\}{0pt}{}{n-1}{k} \]

感性理解一下,就是新加一个元素的时候可以选择在已有 \(k-1\) 个集合时放进一个新的集合,也可以选择在已有 \(k\) 个集合时选择其中一个放进去。
边界:\(\genfrac\{\}{0pt}{}{n}{0}=[n=0]\)

通项公式

\[\genfrac\{\}{0pt}{}{n}{k}=\sum_{i=0}^{k}\frac{(-1)^{k-i}j^n}{j!(k-i)!} \]

\(F(i)\) 为把 \(i\) 个元素放进 \(n\)彼此不同 非空集合中的方案数,\(G(i)\) 为把 \(i\) 个元素放进 \(n\)彼此不同集合(可以为空)中的方案数,显然有

\[G(i)=i^n \]

又有

\[G(i)=\sum_{j=0}^i\binom{i}{j}F(j) \]

二项式反演可以求得 \(F(i)\)。由于第二类斯特林数表示的方案数不对集合作区分,得到的式子要除掉所有集合的排列数。

例题

Partitions

\(n\) 个物品,每个物品有价值 \(w_i\),一个子集 \(S\) 的价值为 \(W(S)=|S|\sum_{x\in S} w_x\),全部物品的一种划分 \(R\) 的价值为 \(W'(R)=\sum_{s\in R}\),求所有含有 \(k\) 个集合的划分价值之和。(\(1\le k\le n\le 2·10^5\))

显然我们需要用每个物品的价值乘上一个由每个物品能被选进多大的集合多少次决定的系数然后求和。
显然答案是

\[ans=\sum_{x}w_x\sum_{i=1}^{n}i\binom{n-1}{i-1}\genfrac\{\}{0pt}{}{n-i}{k-1} \]

只考虑系数部分,先把第二类斯特林数拆开,得到

\[\sum_{i=1}^{n}i\binom{n-1}{i-1}\sum_{j=0}^{k-1}\frac{(-1)^{k-j-1}j^{n-i}}{j!(k-j-1)!} \]

常见操作,把枚举 \(j\) 换成枚举 \(j\)\(k-1\) 的差值,得到

\[\sum_{i=1}^{n}i\binom{n-1}{i-1}\sum_{j=0}^{k-1}\frac{(-1)^{j}(k-j-1)^{n-i}}{j!(k-j-1)!} \]

稍微整理一下,把枚举 \(i\) 弄到里边来,把 \(j\) 相关的一部分弄出去,得到

\[\sum_{j=0}^{k-1}(-1)^{j}\frac{1}{j!(k-j-1)!}\sum_{i=1}^{n}i\binom{n-1}{i-1}(k-j-1)^{n-i} \]

注意到组合数下边有 \(i-1\),而且有 \(k\binom{n}{k}=n\binom{n-1}{k-1}\) 的结论,我们把前边的 \(i\) 拆开(现在开始只看枚举 \(j\) 的部分拉)

\[\sum_{i=1}^{n}(i-1)\binom{n-1}{i-1}(k-j-1)^{n-i}+\binom{n-1}{i-1}(k-j-1)^{n-i} \]

转化为

\[(n-1)\sum_{i=1}^{n}\binom{n-2}{i-2}(k-j-1)^{n-i} + \sum_{i=1}^{n}\binom{n-1}{i-1}(k-j-1)^{n-i} \]

发现这个东西非常的像二项式定理,那么得到

\[(n-1)(k-j)^{n-2} + (k-j)^{n-1} \]

再随便的合并一下,加上前面扔掉的部分,得到答案

\[ans=\sum_{x}w_x\sum_{j=0}^{k-1}(-1)^{j}\frac{1}{j!(k-j-1)!}(k-j+n-1)(k-j)^{n-2} \]

太好拉!然后我们就可以 \(O(k\log n)\) 算出答案了!

点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int t=0;char h=getchar();
	while(!isdigit(h))h=getchar();
	while(isdigit(h))t=(t<<1)+(t<<3)+(h^48),h=getchar();
	return t;
}
void write(int x)
{
	if(x>9)write(x/10);putchar(x%10+'0');
}
const int mod=1e9+7;
const int N=2e5+10;
int n,k;
long long inv[N],z[2];
long long ans=0,sum=0;
inline long long quikp(long long a,int b)
{
	if(b<0)return 1;
	long long res=1;
	while(b)(b&1)&&((res*=a)%=mod),(a*=a)%=mod,b>>=1;
	return res;
}
int main()
{
	n=read();k=read();inv[0]=1;z[0]=1;z[1]=mod-1;
	for(int i=1;i<=k;i++)inv[i]=inv[i-1]*i%mod;
	inv[k]=quikp(inv[k],mod-2);
	for(int i=k-1;i;i--)inv[i]=inv[i+1]*(i+1)%mod;
	for(int i=1;i<=n;i++)(sum+=read())%=mod;
	for(int j=0;j<=k-1;j++)
		(ans+=z[j&1]*inv[j]%mod*inv[k-j-1]%mod*quikp(k-j,n-2)%mod*(n+k-j-1)%mod)%=mod;
	write(ans*sum%mod);
	return 0;
}

常用推论

普通幂转下降幂:\(x^n=\sum_{k=0}^n\genfrac\{\}{0pt}{}{n}{k}x^{\underline{k}}\)

例题

Team Work

给定 \(n,k\),求 \(\sum_{i=1}^n \binom{n}{i}i^k\)\(1\le n\le10^9,1\le k\le 5000\))。

考虑 \(i\)\(0\) 开始枚举(注意 \(k=0\) 时会给答案多算 \(0^k=1\),记得减掉)用第二类斯特林数将普通幂转化为下降幂,得到

\[\sum_{i=0}^n \binom{n}{i}\sum_{j=0}^{k}\genfrac\{\}{0pt}{}{k}{j}i^{\underline{j}} \]

把枚举 \(j\) 的循环丢到前面去,再把下降幂和组合数拆开,得到

\[\sum_{j=0}^{k}\genfrac\{\}{0pt}{}{k}{j}\sum_{i=0}^n \frac{n!}{(i-j)!(n-i)!} \]

经过一些奇妙的变化

\[\sum_{j=0}^{k}\genfrac\{\}{0pt}{}{k}{j}\sum_{i=0}^n \frac{(n-j)!}{(i-j)!(n-i)!}·\frac{n!}{(n-j)!} \]

变成

\[\sum_{j=0}^{k}\genfrac\{\}{0pt}{}{k}{j}\frac{n!}{(n-j)!}\sum_{i=0}^n /binom{n-j}{i-j} \]

最后边那一块根据组合数的某个结论可以换掉(\(i<j\) 的时候就变成 \(0\) 了不用管),得到

\[\sum_{j=0}^{k}\genfrac\{\}{0pt}{}{k}{j}\frac{n!}{(n-j)!}2^{n-j} \]

然后就可以 \(O(k^2+n)\) 求出答案拉。注意提前处理 \(2\) 的若干次幂,会 T。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int t=0;char h=getchar();
	while(!isdigit(h))h=getchar();
	while(isdigit(h))t=(t<<1)+(t<<3)+(h^48),h=getchar();
	return t;
}
void write(int x)
{
	if(x>9)write(x/10);putchar(x%10+'0');
}
const int mod=1e9+7;
const int N=5010;
inline long long quikp(long long a,int b)
{
	long long res=1;
	while(b)
	{
		if(b&1)(res*=a)%=mod;(a*=a)%=mod;b>>=1;
	}
	return res;
}
inline long long f(long long a,int b)
{
	long long res=1;
	for(int i=0;i<b;i++)(res*=a-i)%=mod;
	return res;
}
int n,k;
long long s[N][N];
long long ans=0;
int main()
{
	// freopen("b.in","r",stdin);

	n=read();k=read();s[0][0]=1;
	for(int i=1;i<=k;i++)
		for(int j=1;j<=k;j++)s[i][j]=(s[i-1][j-1]+j*s[i-1][j]%mod)%mod;
	long long p=quikp(2,n),inv2=quikp(2,mod-2),ff=1;
	for(int j=0;j<=k;j++)(ans+=s[k][j]*ff%mod*p%mod)%=mod,(p*=inv2)%=mod,(ff*=n-j)%=mod;
	if(!k)(ans+=mod-1)%=mod;
	write(ans);
}

第一类斯特林数

第一类斯特林数,记作 \(\genfrac[]{0pt}{}{n}{k}\),表示把 \(n\) 个不同元素放进 \(k\) 个非空环的方案数。第一类斯特林数没有实用的通项公式。

递推式

\[\genfrac[]{0pt}{}{n}{k}=\genfrac[]{0pt}{}{n-1}{k-1}+(n-1)\times\genfrac[]{0pt}{}{n-1}{k} \]

感性理解一下,就是每次加入一个新的元素时可以选择在已有 \(k-1\) 个环时选择一个新环,也可以选择在已有 \(k\) 个环时选择 \(n-1\) 个空隙中的一个放进去。
边界:\(\genfrac[]{0pt}{}{n}{0}=[n=0]\)

例题

[FJOI2016] 建筑师

定义一个元素能从左边/右边被看到,当且仅当它以前/以后没有比它大的元素。给定 \(n\),问你有多少长度为 \(n\),有 \(A\) 个元素能从左边被看到,\(B\) 个元素能从右边被看到的排列。

显然值为 \(n\) 的元素是一定会被看到的,考虑把剩下的元素分成 \(A+B-2\) 组,然后每一组最大的元素放到边上(被看到),其余在内部排列,然后选出来 \(A-1\) 组放到左边就好了。

但是发现在每一组内部的排列数是难算的,于是考虑用轮换表示。每组元素形成的轮换数可以用第一类斯特林数求出,又因为最大的元素要放到边上,环上的断点是固定的。最终答案就是 \(\genfrac[]{0pt}{}{n-1}{A+B-2}\times \binom{A+B-2}{A-1}\)。只需 \(O(n(A+B))\) 递推第一类斯特林数并且 \(O(n)\) 求出阶乘及逆元。

posted @ 2025-04-11 17:06  baiguifan  阅读(108)  评论(0)    收藏  举报