人们在生活中经常会遇到排列组合问题。比如说:在
5
5
5个礼物中选
2
2
2个,问有多少种选取方法?
组合数学就是研究一个集合内满足一定规则的排列问题。这类问题如下:
- 存在问题:即判断这些排列是否存在
- 计数问题:计算出有多少种排列,并构造出来
- 优化问题:如果有最优解,给出最优解
组合数学涉及的内容很多,包括: - 基本计数规则:乘法规则、加法规则、生成排列组合、多项式系数、鸽巢原理等。
- 计数问题:二项式定理、递推关系、容斥定理、Polya定理等。
- 存在问题:编码、组合设计、图论中的存在问题等。
- 组合优化:如匹配和覆盖、图和网络的优化问题。
这部分之讲解一些简单的类型。
鸽巢原理(抽屉原理)
内容非常简单:把
n
+
1
n+1
n+1个物体放进
n
n
n个盒子,至少有一个盒子包含两个或更多的物体。
例如:在
1500
1500
1500人中,至少有
5
5
5人生日相同;
n
n
n个人互相握手,一定有两个人握手的次数相同。
比如说:小A有
K
K
K种糖果,每种数量已知,小A不喜欢连续两次吃同样的糖果,问有没有可行的吃糖方案。
该题是非常典型的鸽巢原理问题,可以用“隔板法”求解。找出最多的一种糖果,把它的数量
N
N
N看成
N
N
N个隔板,隔成
N
N
N个空间(把每个隔板的右边看成一个空间);其他所有糖果的数量为
S
S
S。
- 如果 S < N − 1 S<N-1 S<N−1,把 S S S个糖果放到隔板之间,这 N N N个隔板不够放,必然至少有两个隔板之间没有糖果,由于这两个隔板是同一种糖果,所以无解。
- 当 S ≥ N − 1 S≥N-1 S≥N−1时,肯定有解。其中一个解是把 S S S个糖果排成一个长队,注意同种类的糖果是挨在一起的,然后每次取 N N N个糖果,按顺序一个一个地放进 N N N个空间。由于隔板的数量比每一种糖果的数量都多,所以不可能有两个同样的糖果被放进一个空间里。把 S S S个糖果放完,就是一个解,一些隔板里面可能放几种糖果。
杨辉三角和二项式定理
读者一定非常熟悉排列和组合公式。
排列:
A
n
k
=
n
!
(
n
−
k
)
!
A^k_n=\displaystyle \frac{n!}{(n-k)!}
Ank=(n−k)!n!
组合:
C
n
k
=
(
n
k
)
=
A
n
k
k
!
=
n
!
k
!
(
n
−
k
)
!
C^k_n=\begin{pmatrix}n\\k \end{pmatrix}=\displaystyle \frac{A^k_n}{k!}=\displaystyle \frac{n!}{k!(n-k)!}
Cnk=(nk)=k!Ank=k!(n−k)!n!
这里把组合数
C
n
k
C^k_n
Cnk用符号
(
n
k
)
\begin{pmatrix}n\\k \end{pmatrix}
(nk)表示,称为二项式系数。杨辉三角是二项式系数
(
n
r
)
\begin{pmatrix}n\\r\end{pmatrix}
(nr)的典型应用。杨辉三角是排列成如下三角形的数字:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
每一行从上一行推导而来。如果编程求杨辉三角第
n
n
n行的数字,可以模拟这个推导过程,逐级递推,复杂度是
O
(
n
2
)
O(n²)
O(n2)。不过,若改用数学公式计算,则可以直接得到结果,比用递推快多了,这个公式就是
(
1
+
x
)
n
(1+x)^n
(1+x)n。观察
(
1
+
x
)
n
(1+x)^n
(1+x)n的展开:
(
1
+
x
)
0
=
1
(1+x)^0=1
(1+x)0=1
(
1
+
x
)
1
=
1
+
x
(1+x)^1=1+x
(1+x)1=1+x
(
1
+
x
)
2
=
1
+
2
x
+
x
2
(1+x)^2=1+2x+x^2
(1+x)2=1+2x+x2
(
1
+
x
)
3
=
1
+
3
x
+
3
x
2
+
x
3
1+x)^3=1+3x+3x^2+x^3
1+x)3=1+3x+3x2+x3
每行展开的系数刚好对应杨辉三角每一行的数字。即:杨辉三角可以用
(
1
+
x
)
n
(1+x)^n
(1+x)n来定义和计算。
那么如何计算
(
1
+
x
)
n
(1+x)^n
(1+x)n?二项式系数
(
n
k
)
=
n
!
k
!
(
n
−
k
)
!
\begin{pmatrix}n\\k \end{pmatrix}=\displaystyle \frac{n!}{k!(n-k)!}
(nk)=k!(n−k)!n!就是
(
1
+
x
)
n
(1+x)^n
(1+x)n展开后的系数。它们的关系可以这样理解:
(
1
+
x
)
n
(1+x)^n
(1+x)n的第
k
k
k项,实际上就是从
n
n
n个
x
x
x中选出
k
k
k个,这就是组合数
(
n
k
)
\begin{pmatrix}n\\k \end{pmatrix}
(nk)的定义。所以:
(
1
+
x
)
n
=
∑
k
=
1
n
(
n
k
)
k
n
(1+x)^n=\sum_{k = 1}^{n}\begin{pmatrix}n\\k \end{pmatrix}k^n
(1+x)n=k=1∑n(nk)kn
这个公式称为二项式定理。
有了这个公式,在求杨辉三角第n行的数字时就可以用公式直接计算了,复杂度为
O
(
1
)
O(1)
O(1)。不过,该公式中有
n
!
n!
n!,如果直接计算
n
!
n!
n!,由于太大,有可能溢出。例如
n
=
30
n=30
n=30,
30
!
30!
30!超过了
l
o
n
g
l
o
n
g
long\ long
long long的范围。此时可以利用
(
n
k
−
1
)
\begin{pmatrix}n\\k-1 \end{pmatrix}
(nk−1)和
(
n
k
)
\begin{pmatrix}n\\{k} \end{pmatrix}
(nk)的递推关系
(
n
k
)
(
n
k
−
1
)
=
n
−
k
+
1
k
\displaystyle\frac{\begin{pmatrix}n\\{k} \end{pmatrix}}{\begin{pmatrix}n\\{k-1} \end{pmatrix}}=\displaystyle\frac{n-k+1}{k}
(nk−1)(nk)=kn−k+1逐个推导,避免计算阶乘。
容斥原理
在计数时,有时情况比较多,相互有重叠。为了使重叠部分不被重复计算,可以这样处理:先不考虑重叠的情况,把所有对象的数目计算出来,然后减去重复计算的数目。这种计数方法称为容斥原理。例如一根长为
60
m
60m
60m的绳子,每隔
3
m
3m
3m做一个记号,每隔
4
m
4m
4m也做一个记号,然后把有记号的地方剪断,问绳子共被剪成了多少段?
容斥原理的解题思路是:
- 3 3 3的倍数有 20 20 20个,不算绳子两头,有: 20 − 1 = 19 20-1=19 20−1=19个记号;
- 4 4 4的倍数有 15 15 15个;
- 既是 3 3 3的倍数又是 4 4 4的倍数的,有: 60 ÷ ( 3 × 4 ) = 5 60÷(3×4)=5 60÷(3×4)=5个。
- 所以记号的总数量是: ( 20 − 1 ) + ( 15 − 1 ) − ( 5 − 1 ) = 29 (20-1)+(15-1)-(5-1)=29 (20−1)+(15−1)−(5−1)=29,绳子被剪成 29 29 29段。
概率与期望
概率和数学期望是概率论和统计学中的数学概念。
设有随机变量
X
X
X,出现取值
x
i
x_i
xi的概率是
p
i
p_i
pi,把它们的乘积之和称为数学期望(均值),记为
E
(
X
)
:
E(X):
E(X):
E
(
X
)
=
∑
i
=
1
n
x
i
p
i
E(X)=\sum_{i = 1}^{n}x_ip_i
E(X)=i=1∑nxipi
E
(
X
)
E(X)
E(X)是基本的数学特征之一,它反映了随机变量平均值的大小。
以妇女的生育率为例,假设某国有
2000
2000
2000万个育龄妇女,不生育妇女有
277
277
277万,一孩
724
724
724万,二孩
883
883
883万,三孩
116
116
116万。记一个妇女的孩子数量是
X
X
X,取值
0
、
1
、
2
、
3
0、1、2、3
0、1、2、3,概率分别是:
277
/
2000
=
0.1385
、
724
/
2000
=
0.362
、
883
/
2000
=
0.4415
、
116
/
2000
=
0.058
277/2000=0.1385、724/2000=0.362、883/2000=0.4415、116/2000=0.058
277/2000=0.1385、724/2000=0.362、883/2000=0.4415、116/2000=0.058。那么平均每个妇女生育的孩子数量如下:
E
(
X
)
=
0
×
0.1385
+
1
×
0.362
+
2
×
0.4415
+
3
×
0.058
=
1.419
E(X)=0×0.1385+1×0.362+2×0.4415+3×0.058=1.419
E(X)=0×0.1385+1×0.362+2×0.4415+3×0.058=1.419
数学期望具有线性性质。有限个随机变量之和的数学期望等于每个变量的数学期望之和:
E
(
X
+
Y
)
=
E
(
X
)
+
E
(
Y
)
E(X+Y)=E(X)+E(Y)
E(X+Y)=E(X)+E(Y)
竞赛中求数学期望的题目一般都会用到它的线性性质。由于线性性质和DP的状态转移思想很相似,所以常常用DP来实现。
例如:一个软件有
S
S
S个子系统,会产生
n
n
n种
b
u
g
bug
bug。现在要找出所有种类的
b
u
g
bug
bug。假设某人一天发现一个
b
u
g
bug
bug。一个
b
u
g
bug
bug属于某个子系统的概率是
1
S
\displaystyle \frac{1}{S}
S1,属于某种分类的概率是
1
n
\displaystyle \frac{1}{n}
n1,问发现
n
n
n种
b
u
g
bug
bug,且每个子系统都发现
b
u
g
bug
bug的天数的期望。
0
<
n
,
s
≤
1000
0<n,s≤1000
0<n,s≤1000。给出
n
和
s
n和s
n和s,求出数学期望。
分析:
定义状态
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j],它表示已经找到
i
i
i种
b
u
g
bug
bug,并存在于
j
j
j个子系统中,要达到目标状态还需要的期望天数。其中,
d
p
[
n
]
[
s
]
dp[n][s]
dp[n][s]表示已经找到
n
n
n种
b
u
g
bug
bug,且存在于
s
s
s个子系统,说明已经达到了目标,还需要
0
0
0天,所以
d
p
[
n
]
[
s
]
=
0
dp[n][s]=0
dp[n][s]=0。从
d
p
[
n
]
[
s
]
dp[n][s]
dp[n][s]倒推回
d
p
[
0
]
[
0
]
dp[0][0]
dp[0][0],就是本题的答案,即还没有找到任何
b
u
g
bug
bug的情况下到达
d
p
[
n
]
[
s
]
dp[n][s]
dp[n][s]时需要的期望天数。
从
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]开始:后面
1
1
1天找到
1
1
1个
b
u
g
bug
bug,可能有以下
4
4
4种情况:
- d p [ i ] [ j ] dp[i][j] dp[i][j]:发现一个 b u g bug bug,属于已经有的 i i i个分类和 j j j个系统,概率为 p 1 = ( i / n ) ∗ ( j / s ) p1=(i/n)*(j/s) p1=(i/n)∗(j/s)。这一天相当于浪费了。
-
d
p
[
i
+
1
]
[
j
]
dp[i+1][j]
dp[i+1][j]:发现一个
b
u
g
bug
bug,不属于已有分类、属于已有系统,概率为:
p 2 = ( 1 − i / n ) ∗ ( j / s ) p2=(1-i/n)*(j/s) p2=(1−i/n)∗(j/s) -
d
p
[
i
]
[
j
+
1
]
dp[i][j+1]
dp[i][j+1]:发现一个
b
u
g
bug
bug,属于已有分类、不属于已有系统,概率为:
p 3 = ( i / n ) ∗ ( 1 − j / s ) p3=(i/n)*(1-j/s) p3=(i/n)∗(1−j/s) - d p [ i + 1 ] [ j + 1 ] dp[i+1][j+1] dp[i+1][j+1]:发现一个 b u g bug bug,不属于已有系统、不属于已有分类,概率为: p 4 = ( 1 — i / n ) ∗ ( 1 − j / s ) p4=(1—i/n)*(1-j/s) p4=(1—i/n)∗(1−j/s)
可以验证:
p
1
+
p
2
+
p
3
+
p
4
=
1
p1+p2+p3+p4=1
p1+p2+p3+p4=1。
状态转移方程如下:
d
p
[
i
]
[
j
]
=
p
1
∗
d
p
[
i
]
[
j
]
+
p
2
∗
d
p
[
i
+
1
]
[
j
]
+
p
3
∗
d
p
[
i
]
[
j
+
1
]
+
p
4
∗
d
p
[
i
+
1
]
[
j
+
1
]
+
1
dp[i][j]=p1*dp[i][j]+p2*dp[i+1][j]+p3*dp[i][j+1]+p4*dp[i+1][j+1]+1
dp[i][j]=p1∗dp[i][j]+p2∗dp[i+1][j]+p3∗dp[i][j+1]+p4∗dp[i+1][j+1]+1
+
1
+1
+1是因为末尾要加上
1
1
1天
整理得到:
d
p
[
i
]
[
i
]
=
(
p
2
∗
d
p
[
i
+
1
]
[
i
]
+
p
3
∗
d
p
[
i
]
[
j
+
1
]
+
p
4
∗
d
p
[
i
+
1
]
[
j
+
1
]
+
1
)
/
(
1
−
p
1
)
dp[i][i]=(p2*dp[i+1][i]+p3*dp[i][j+1]+p4*dp[i+1][j+1]+1)/(1-p1)
dp[i][i]=(p2∗dp[i+1][i]+p3∗dp[i][j+1]+p4∗dp[i+1][j+1]+1)/(1−p1)
=
(
n
∗
s
+
(
n
i
)
∗
j
∗
d
p
[
i
+
1
]
[
j
]
+
i
∗
(
s
−
j
)
∗
d
p
[
i
]
[
j
+
1
]
=(n*s+(ni)*j*dp[i+1][j]+i*(s-j)*dp[i][j+1]
=(n∗s+(ni)∗j∗dp[i+1][j]+i∗(s−j)∗dp[i][j+1]
+
(
n
−
i
)
∗
(
s
−
j
)
∗
[
i
+
1
]
[
j
+
1
]
)
/
(
n
∗
s
−
i
∗
j
)
+(n-i)*(s-j)*[i+1][j+1])/(n*s-i*j)
+(n−i)∗(s−j)∗[i+1][j+1])/(n∗s−i∗j)
在写程序时,从
d
p
[
n
]
[
s
]
dp[n][s]
dp[n][s]倒推到
d
p
[
0
]
[
0
]
dp[0][0]
dp[0][0],
d
p
[
0
]
[
0
]
dp[0][0]
dp[0][0]就是答案。
cin >> n >> s;
for (int i = n; i >= 0; i--) {
for (int j = s; j >= 0; j--) {
if (i == n && j == s) {
dp[n][s] = 0.0;
} else {
dp[i][j] = (n * s + (n - i) * j * dp[i + 1][j]
+ i * (s - j) * dp[i][j + 1] +(n - i) * (s - j)
* dp[i + 1][j + 1]) / (n * s - i * j);
}
}
}