组合数学可以说是所有数论技巧中最有用的一种之一,无论是在生活中还是在学习中,都是一种极为方便的技能。
基本运算
加法原理
若完成一件事的方法有 n n n 类,其中第 i i i 类方法有 a i a_i ai 种不同的实现方法,且这些方法互不重复,那么完成这些事共有 a 1 + a 2 + a 3 ⋯ + a n a_1 +a_2 +a_3\cdots+a_n a1+a2+a3⋯+an 种不同的方法。这就是加法原理。
乘法原理
若完成一件事需要 n n n 个步骤,其中第 i i i 个步骤有 a i a_i ai 种完成方法,且这些步骤互不干扰,则完成这件事共有 a 1 × a 2 × a 3 ⋯ × a n a_1\times a_2 \times a_3\cdots \times a_n a1×a2×a3⋯×an种不同的方法,这就是乘法原理。
排列数
从 n n n 个不同元素种依次取出 m m m 个元素排成一列,产生的不同排列的数量为: n × ( n − 1 ) × ⋯ × ( n − m + 1 ) = n ! ( n − m ) ! ( 乘 法 原 理 ) n \times(n - 1)\times\cdots \times(n - m +1) = \frac{n!}{(n - m)!}\ \ \ \ (乘法原理) n×(n−1)×⋯×(n−m+1)=(n−m)!n! (乘法原理) 写作 A n m A_n^m Anm 或 P n m P_n^m Pnm。
组合数
从
n
n
n 个不同元素中取出
m
m
m 个构成一个集合,产生的不同集合数量为:
n
×
(
n
−
1
)
×
(
n
−
2
)
×
⋯
(
n
−
m
+
1
)
m
×
(
m
−
1
)
×
(
m
−
2
)
×
⋯
×
2
×
1
(
乘
法
原
理
)
=
n
!
m
!
(
n
−
m
)
!
\frac{n\times (n - 1)\times (n - 2)\times\cdots (n - m +1)}{m\times (m - 1)\times(m - 2)\times\cdots\times2\times1}(乘法原理)= \frac{n!}{m!(n - m)!}
m×(m−1)×(m−2)×⋯×2×1n×(n−1)×(n−2)×⋯(n−m+1)(乘法原理)=m!(n−m)!n!
性质
1.
C
n
m
=
C
n
n
−
m
C_n^m = C_n^{n - m}
Cnm=Cnn−m
根据组合数的定义,从
n
n
n 个元素中选出
m
m
m 个构成的集合一定和没有选的一 一对应。所以性质1成立。
2.
C
n
m
=
C
n
−
1
m
+
C
n
−
1
m
−
1
C_n^m = C_{n - 1}^m +C_{n - 1}^{m - 1}
Cnm=Cn−1m+Cn−1m−1
从
n
n
n 个元素中选出
m
m
m 个元素有两类方法:选择第
n
n
n 个和不选第
n
n
n 个。若选择第
n
n
n 个元素,那么就相当于在
n
−
1
n - 1
n−1 个数中选出
m
−
1
m - 1
m−1 个元素,方案数为
C
n
−
1
m
−
1
C_{n - 1}^{m - 1}
Cn−1m−1。若不选第
n
n
n 个元素,那么就相当于在
n
−
1
n - 1
n−1 个数中选出
m
m
m 个元素,方案数为
C
n
−
1
m
C_{n - 1}^{m}
Cn−1m。所以方案数共有
C
n
−
1
m
−
1
+
C
n
−
1
m
C_{n - 1}^{m - 1} + C_{n - 1}^{m}
Cn−1m−1+Cn−1m(加法原理)种情况。
3.
C
n
0
+
C
n
1
+
C
n
2
+
⋯
+
C
n
n
=
2
n
C_n^0 +C_n^1+C_n^2+\cdots+C_n^n = 2^n
Cn0+Cn1+Cn2+⋯+Cnn=2n
求法
1.我们可以直接用性质2来递推求解。复杂度为 O ( n 2 ) O(n^2) O(n2)。具体实现方法与杨辉三角相同。
2.在实际运算中,组合数一般会很大,所以题目可能会要求对所求的组合数取模。在这种情况下,我们可以先求分子
n
!
m
o
d
p
n!\ mod\ p
n! mod p ,再求分母
m
!
(
n
−
m
)
!
m
o
d
p
m!(n - m)!\ mod\ p
m!(n−m)! mod p 的逆元,将其相乘之后就得到了所求的组合数。
可以先将每一个
k
(
k
∈
[
0
,
n
]
)
k(k\in[0,n])
k(k∈[0,n]) 的
k
!
m
o
d
p
k!\ mod\ p
k! mod p及其逆元算出并存在两个数组中,这样在
O
(
n
)
O(n)
O(n)的预处理之后就可以
O
(
1
)
O(1)
O(1)地求组合数了。
此外,当所求组合数为高精数时,我们可以先用阶乘分解1的方法。将分子分母快速分解质因数,在数组中保存各项质因数的次数,然后将分子和分母的质因子次数对应相减(消去分母),最后把剩余质因子乘起来,时间复杂度为
O
(
n
l
o
g
n
)
O(n\ log\ n)
O(n log n)。
还有一种情况,那就是
n
n
n、
m
m
m 中有至少一个数比
p
p
p 要小。此时预处理求解就不那么适合了。对于该情况,我们可以使用
L
u
c
a
s
Lucas
Lucas 定理将一个
n
n
n、
m
m
m 较大的组合数分为若干个
n
n
n、
m
m
m 较小的组合数相乘进行求解
显然, n ! n! n! 中的每一个数都不会超过 n n n,所以我们可以先将小于 n n n 的质数 p p p 先全筛出来,在考虑在 n ! n! n! 有多少个质因子 p p p 。
n ! n! n! 中 p p p 的个数实际上就是 1 ~ n n n 中每个数包含质因子 p p p 的数量之和。显然,在1 ~ n n n 中, p p p 的倍数,即包含1个以上个 p p p 的数共有 ⌊ n p ⌋ \lfloor\frac{n}{p}\rfloor ⌊pn⌋个, p 2 p^2 p2 的倍数,即包含2个以上 p p p 的数共有 ⌊ n p 2 ⌋ \lfloor\frac{n}{p^2}\rfloor ⌊p2n⌋, p 3 p^3 p3 的倍数,即包含3个以上 p p p 的数共有 ⌊ n p 3 ⌋ \lfloor\frac{n}{p^3}\rfloor ⌊p3n⌋…因为每一次提高 p p p 的个数时,之前的 p p p 已经计算过了,所以每次加上数的个数即可,而不是 p p p 的个数。
综上所述, n ! n! n! 中包含 p p p 的个数为:
⌊ n p ⌋ + ⌊ n p 2 ⌋ + ⌊ n p 3 ⌋ + ⋯ ⌊ n p l o g p n ⌋ = ∑ p k ≤ n ⌊ n p k ⌋ \lfloor\frac{n}{p}\rfloor+\lfloor\frac{n}{p^2}\rfloor+\lfloor\frac{n}{p^3}\rfloor+\cdots\lfloor\frac{n}{p^{log_pn}}\rfloor =\sum_{p^k\leq n}\lfloor\frac{n}{p^k}\rfloor ⌊pn⌋+⌊p2n⌋+⌊p3n⌋+⋯⌊plogpnn⌋=pk≤n∑⌊pkn⌋
由此,对于每个 p p p,只需要 O ( l o g n ) O(log\ n) O(log n)的时间就可以计算上述和式。因此对整个 n ! n! n! 进行质因子分解的时间复杂度为 O ( n l o g n ) O(n\ log\ n) O(n log n)。 ↩︎