组合数学


排列组合

排列组合是组合数学中的基础板块。然后我也不知道这里应该塞什么了。


加法原理

一件事情有 n 类解决方法,第 i 类方法有 ai 个解决方案,那么这件事情的总解决方案数就是 i=1nai

因为这 n 类解决方法,我们一次只能选择一类,再选择它的方案,它们的存在是互斥的。


乘法原理

一件事情有 n 步解决步骤,第 i 个步骤有 ai 个解决方案,那么这件事情的总解决方案数就是 i=1nai

因为这 n 步解决步骤,我们每次都依次确定它们每一步的方案,它们的存在是分步且共存的。


排列数

n 个不同的元素( 即集合 )中选取 m 个元素 (mn) ,将这 m 个元素排成一个序列,这个序列被称为从 n 个不同元素中取出 m 个元素的一个排列。

比如:我们要从 {1,2,3,4,5} 中取出 3 个元素。

{1,3,5} 是一个满足条件的排列;

{3,1,5} 同样也是一个满足条件的排列。

所以相同的元素以不同的顺序排成的序列是不同的排列。

n 个不同元素中取出 m 个元素能够取出的互异排列的总个数,就叫做从 n 个不同元素中取出 m 个元素的排列数,记作 AnmPnm

我们把定义转化成实际的问题:

求从 n 个不同元素中取出 m 个元素的排列数,相当于求从装有 n 只不同种类的玩偶的抓娃娃机中抓 m 只玩偶,按抓到的顺序把它们排成一列,拍一张照,能得到的不同的照片的张数。

( 感觉这里可以插张图,但是没有图,所以先插个眼。 )

这不是很像我们乘法原理中举的例子吗?

总共有 m 步,现在要确定的就是每次操作的方案数。

在第一步时我们可以从 n 个玩偶中任选一只抓走(没错,您是抓娃娃带师),所以这一步的方案数是 n

我们第一步从 n 个玩偶里抓走了一只,那么第二步我们就只能从 n1 只玩偶里选一只抓走,所以这一步的方案数是 n1

以此类推,直到取第 m 只玩偶时,我们的机里只剩下 nm+1 只玩偶了,所以最后一步的方案数是 nm+1

所以我们可以得到下面的式子:

Anm=n(n1)(n2)(nm+1)=i=nm+1ni

特别地,当 m>n 时,Anm=0

它好长啊,我们要把它压一压。

有一个好东西叫阶乘,它的定义十分简单:

n!=i=1ni

特别地, (0!)=1

所以我们可以把 Anm 变一下。

Anm=i=nm+1ni=i=1nii=1nmi=n!(nm)!

这就是著名而常用的排列数公式了。

因为 (0!)=1 ,所以 Ann=n! ,这是排列数的特殊情况。这种有多少个元素取多少个的排列,我们称其为全排列。( 就是您把整台抓娃娃机给清了的情况,也相当于您把整台机里的娃娃拎出来排队的总方案数。 )


组合数

组合数与排列数的定义不同点在于:排列数注重顺序,但组合数对它不感冒。

像上面的例子, {3,1,5}{1,3,5} 是不同的排列,但它们是相同的组合。

n 个不同元素中选 m 个元素的组合数相当于从这个 n 元素的集合中 能选出的大小为 m 个元素的互异集合个数,记为 Cnm(nm) ,读作 “ nm ” 。( 尽管我知道您是不会在屏幕前读一遍的 )

我们向上面一样把它转化成一个实际问题:

求从 n 个不同元素中选 m 个元素的组合数相当于求从装有 n 只不同种类的玩偶的抓娃娃机中抓 m 只玩偶,收获不同的情况总数。

正如这节开头所说,与排列数的例子相比,这里的区别就是不考虑您中途是怎么抓娃娃的。

那么这和计算组合数有什么关系呢?

关系可大了。

因为组合数与排列数相差在是否考虑这 m 只玩偶的顺序,所以排列数应该等于组合数乘上这 m 只玩偶排队的方案数。

那这 m 只玩偶排队方案数是多少呢?

回头看看:

这种有多少个元素取多少个的排列,我们称其为全排列。( 就是您把整台抓娃娃机给清了的情况,也相当于您把整台机里的娃娃拎出来排队的总方案数。 )

我们得到的 m 只娃娃就相当于是一台装有 m 只娃娃的机,那排队方案数不就是 m 的全排列了嘛。

我们把式子写出来:

(nm)Amm=Anm

(nm)=AnmAmm=n!m!(nm)!

这就是著名而常用的组合数公式了。

特别地,当 m>nm<0 时, (nm)=0


求排列组合数

我们先摆出我们的排列数和组合数公式。

Anm=n!(nm)!

(nm)=n!m!(nm)!

关注到,求解上面两个式子,都需要计算阶乘。

通常组合计数题的答案很大,所以会给出一个模数。这个模数通常是一个大质数,否则我们很可能会遇到阶乘被模为 0 的情况,这时候我们就要用下面讲的 Lucas 定理来解决。

这时候我们可以考虑线性递推预处理出需要用到的阶乘,但由于本身每步运算结果一定为整数的组合数可能会因为取模导致中途某一步运算不能取整,所以我们还需要进行乘法逆元,预处理出逆元数组。

逆元同样有线性预处理的式子,具体预处理代码如下:

void init()
{
	fac[0]=fac[1]=inv[1]=finv[0]=finv[1]=1;
	for (int i=2;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%mod;
		inv[i]=mod-mod/i*inv[mod%i]%mod;
		finv[i]=finv[i-1]*inv[i]%mod;
	}
}

fac[] 为阶乘数组,inv[] 为逆元数组,finv[] 为阶乘逆元数组。

那么求排列数和组合数也就易如反掌了:

inline int A(int m,int n)
{
	if (n<m||n<0||m<0) return 0;
	return fac[n]*finv[n-m]%mod;
}

inline int C(int m,int n)
{
	if (n<m||n<0||m<0) return 0;
	return fac[n]*finv[m]%mod*finv[n-m]%mod;
}

预处理阶乘与逆元的时间复杂度为 O(n) ,每次应答的时间复杂度为 O(1)


二项式定理

小朋友,你知道 (a+b)2 展开是什么吗?

Too young.

当然是 a2+2ab+b2 了。

那你知道 (a+b)3 展开是什么吗?

Too simple.

当然是 a3+3a2b+3ab2+b3 啦。

那你知道 (a+b)4 展开是什么吗?

Sometimes naive.

这我也知道,是 a4+4a3b+6a2b2+4ab3+b4

(a+b)n 呢?

哈?

从上面的几个式子中,我们不难看出,(a+b)n 展开后得到了 n+1n 次项。

我们对其按 a 降幂排序,每个项中 a 的次数与 b 的次数和为 n ,故 b 按升幂排序。所以我们可以看成第 i 项的非常数项是在 n(a+b) 中选取了 i1bni+1a 相乘,而常数项就是这样选择的方案数。

这个取 ab 的过程像不像我们的组合数呢?( 其实组合数还有一个名字,就叫二项式系数 )

按照上面的推导,可以得到下面的式子:

(a+b)n=i=0n(ni)anibi

这就是著名而常用的二项式定理了。

二项式定理不止对 (a+b) 适用,它还可以再推广,得到:

(i=1nai)k=i=1nbi=k(ki=1nbi)i=1naibi

但这个式子其实远没有上面那个式子常用。


Lucas 定理

Lucas 定理可以用来处理模数较小导致阶乘预处理会出问题的情况。

先直接给出 Lucas 定理:

(nm)(npmp)(nmodpmmodp)(mod p)

好,背下它就够用了。

下面给出证明:(之所以先讲二项式定理,一个是因为它的应用范围很广,另一个是 Lucas 定理的证明中会频繁应用到二项式定理)

Lucas 定理中的 (nm) 是组合数形式,不方便运算,所以我们先用二项式定理转成多项式来考虑。

(nm)i=0n(ni)xixm 项的系数。

通过二项式定理,我们可以把它转成完全幂的格式:

i=0n(ni)xi=(1+x)n

所以我们接下来考虑对 (1+x)n 进行一些变换,想方设法让它与除,模 p 扯上关系。

(1+x)n(1+x)pnp+nmodp(1+x)pnp(1+x)nmodp(mod p)

然后呢?

这时候我们要先证一步引理,以进行下一步的变换。

引理:

(1+x)p1+xp(mod p)

证明:

根据二项式定理得:

(1+x)p=i=0p(pi)xi

又因为:

(nm)=n!m!(nm)!

所以对于每个 i0  and  ip ,都含有质因子 p

所以:

(1+x)pi=0p(pi)xi1+xp(mod p)

我们回到 Lucas 定理的证明。

(1+x)n(1+x)pnp(1+x)nmodp(1+xp)np(1+x)nmodp(mod p)

我们再用一次二项式定理:

(1+xp)np(1+x)nmodp=(i=0np(npi)xpi)(i=0nmodp(nmodpi)xi)

所以(更改了一下不同式子里的变量名字,避免重名在描述上带来的不便):

(1+x)ni=0n(ni)xi(j=0np(npj)xpj)(k=0nmodp(nmodpk)xk)(mod p)

我们要求的 (nm) 就是 xm 项的系数,所以我们要考虑的是 i=m,pj+k=m 的情况。

此时:

(nm)(npmp)(nmodpmmodp)(mod p)

即 Lucas 定理。

证毕。


组合恒等式

每次进行组合数的运算时都把它们转为阶乘形式未免太过麻烦,所以我们需要推出一些组合数之间的恒等式,作为它们间的运算法则。

在解释恒等式时,我们同样使用抓娃娃机的例子。


递推式

有些时候我们需要对一个式子进行变形递推,会应用到一些组合数的递推式。


(nk)=(nnk)

解释:

这条恒等式的意思就是,你选择抓 k 只玩偶的结果相当于你在抓娃娃机里留下选择 nk 只玩偶的结果。

证明:

(nk)=n!k!(nk)!=n!(nk)!(n(nk))!=(nnk)


(nk)=nk(n1k1)

解释:

这条恒等式怎么理解呢?

我们可以认为,娃娃机里有一只玩偶你最喜欢,你会保证抓出那只玩偶,再在剩下的 n1 只里抓 k1 只。

你最喜欢的那只玩偶有多少种可能呢,总共有 n 只玩偶,每只玩偶都可以成为你最喜欢的那只,所以有 n 种可能。

但是每一种方案都抓出了 k 只玩偶,你最喜欢的可能是这里面的任意一只,相当于每种方案被重复计算了 k 次,所以我们要再把它除去 k

所以 n 只抓 k 只的组合数相当于 n1 只抓 k1 只的组合数乘上 n 再除以 k ,即:

(nk)=nk(n1k1)

证明:

(nk)=n!k!(nk)!=nk(n1)!(k1)!(nk)!=nk(n1k1)


(nk)=(n1k)+(n1k1)

这条恒等式与杨辉三角和二项式定理有着不小的关系,不过是个大坑,有时间再来补。

解释:

我们可以认为,娃娃机里有一只玩偶, crq 很喜欢,你可以选择是否抓这只玩偶。

如果抓了这只玩偶,剩下的 n1 只玩偶中,我们再抓 k1 只即可;如果没抓这只玩偶(crq 哭哭),剩下的 n1 只玩偶中,我们还是需要抓 k 只。

因为我们并没有像上面一样直接确定选择这只玩偶,而是考虑了这只玩偶的选与不选。这代表着我们在决策时,这只玩偶与其它 n1 只并没有什么区别,所以不会带来计数上的重复,我们直接把两种可能相加即可。

即:

(nk)=(n1k)+(n1k1)

证明:

(nk)=n!k!(nk)!=n(n1)!k!(nk)!=((nk)+k)(n1)!k!(nk)!

=(nk)(n1)!k!(nk)!+k(n1)!k!(nk)!

=(n1)!k!(nk1)!+(n1)!(k1)!(nk)!

=(n1k)+(n1k1)

不过我们可以利用刚才的第二个恒等式,得到更简单的方法。

(nk)=nk(n1k1)=nkk(n1k1)+(n1k1)=(n1k)+(n1k1)


(ni)(ij)=(nj)(niij)

解释:

左式我们可以看作你有 n 只娃娃,先抓出 i 只染成色,再抓出 j 只染成色。

所以方案数就相当于先从 n 只里把那 j 只蓝的抓出来染了,再把从剩下 ni 只里把那 j 只抓出来染。

证明:

(ni)(ij)=n!i!(ni)!i!j!(ij)!=n!j!(nj)!(nj)!(ij)!(ni)!=(nj)(niij)


求和式

为了降低复杂度,我们需要尽可能把求和的式子转化成一个可以快速预处理出的数,这个过程就会应用到下面的求和式。


i=0n(ni)=2n

解释:

i=0n(ni) ,相当于求抓娃娃的所有结果数。即对于 n 个玩偶,每个玩偶都决定它是取或不取,所以结果是 2n

证明:

2n=(1+1)n=i=0n(ni)


i=0n(1)i(ni)=0

解释:

i=0n(1)i(ni) ,相当于让一个人只抓奇数个娃娃,另一个人只抓偶数个娃娃,求他们各自所有结果数的差值。

那怎么确定是奇是偶呢?我们同样假设,这 n 只玩偶里有一只 crq 很喜欢,需要决定它抓不抓。

我们发现,剩下 n1 只,无论我们抓几只,都可以通过改变 crq 喜欢的一只抓或不抓,使得最后抓的只数一定是奇数或一定是偶数。

所以从 n 只中抓奇数只或偶数只的结果数都等于从 n1 只中抓任意只的结果数。

根据上面第一条求和式,我们就可以知道,在 n 只玩偶种,只抓奇数只或只抓偶数只,结果数都是 2n1 种。

所以它们的差值就是 0 了。

证明:

如果不这么看中式子的现实意义的话,还有另一种更方便的方法证明。

n 选奇或偶,根据 (nk)=(n1k)+(n1k1) ,都等于在杨辉三角中的第 n1 行的总和,即 2n1

i=0n(1)i(ni)=i=0n(1)i((n1i)+(n1i1))=(n11)+(n1n)

因为 (n11)=(n1n)=0 ,所以原式等于 0

posted @   jzcrq  阅读(170)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示