CSP-J 初赛(以满分为目标):第三十九课《数学与逻辑②——阶乘、排列数与组合数:公式到底从哪里来?》


第三十九课:数学与逻辑②------阶乘、排列数与组合数:公式到底从哪里来?

上一课我们解决了一个最重要的问题:

换一下顺序,算不算一种新的结果?

  • 不算 → 组合

  • 算 → 排列

今天我们继续向前走一步,把三个非常重要的数学工具真正搞明白:

重点不是让大家死记公式,而是让同学们知道:

这些公式不是天上掉下来的,而是"一步一步数出来的"。


一、今天的故事:小明要组织一场颁奖典礼

班里有5位同学:

复制代码
A   B   C   D   E

老师准备颁发三个奖项:

复制代码
🥇 冠军
🥈 亚军
🥉 季军

问题来了:

从5个人中选出3个人,并分别获得冠军、亚军、季军,一共有多少种可能?

我们先不用公式。


二、第一步:冠军有几种选择?

冠军可以是谁?

复制代码
A
B
C
D
E

一共有:

5

种。


三、第二步:亚军有几种选择?

假设冠军已经是A。

那么剩下:

复制代码
B C D E

所以亚军有:

4

种选择。


四、第三步:季军有几种选择?

假设:

复制代码
冠军:A
亚军:B

那么只剩:

复制代码
C D E

所以季军有:

3

种选择。

因此:

5×4×3=60

答案:

60

这就是排列。


五、于是我们发现了排列数公式

从5个人中选3个人排列:

如果是:

从n个人中选m个人排列

那么:

复制代码
第1个位置:n种
第2个位置:n-1种
第3个位置:n-2种
......
第m个位置:n-m+1种

所以:

这就是排列数。


六、但是还有一个神奇的东西:阶乘!

刚才我们看到:

这个东西在数学中有一个专门的名字:

读作:

5的阶乘

定义:

例如:

3!=3×2×1=6

4!=4×3×2×1=24

5!=5×4×3×2×1=120

6!=6×5×4×3×2×1=720


七、为什么阶乘这么重要?

因为:

阶乘其实就是"把所有东西排一遍"的方法数。

例如:

复制代码
A B C

把3个人排成一列。

所有方法:

复制代码
ABC
ACB
BAC
BCA
CAB
CBA

一共有:

6

而:

3!=6

所以:

3个人全部排列 = 3!

同理:

n个人全部排列=n!\boxed{n个人全部排列=n!}


八、一个特别重要的特殊情况:0!

孩子第一次看到:

0!

通常会问:

老师,0个东西怎么排列?

数学中规定了:

0! = 1

这个结论在组合公式中非常重要。

例如:

意思就是:

从n个东西里面一个都不选,只有一种方法。

那就是:

复制代码
什么都不选

所以:

0! = 1

非常合理。


九、排列数如何改写成阶乘?

例如:

我们知道:

5!=5×4×3×2×1

所以:

因此:

所以一般公式:


十、为什么是 (n−m)!?

这个地方不要死记。

例如:

就是:

7×6×5

但是:

7!=7×6×5×4×3×2×1

后面多出来的是:

4×3×2×1 = 4!

而:

4 = 7−3

所以:

也就是:


十一、现在轮到组合登场

还是5个人:

复制代码
A B C D E

如果我们只想:

选出3个人参加比赛。

那么:

复制代码
ABC

和:

复制代码
BAC

是同一种选择。

但是排列会把它们当成不同情况。


十二、排列比组合多了多少?

假设我们选中了:

复制代码
A B C

这三个人可以怎么排列?

3!

种。

也就是:

6

种。

因此:

每一个"3人组合",在排列中会被计算3!次。

所以:

于是:

所以:


十三、组合公式终于来了!


十四、千万不要把两个公式混淆


十五、一个非常好用的"反推法"

看到:

可以这样想:

先把m个人选出来,再把这m个人全部排列。

例如:

如果先组合:

然后这2个人可以交换位置:

2!

所以:

也就是:

而:

完全一致。


十六、组合数的一个超级重要性质

观察:

和:

它们实际上相等。

为什么?

从5个人里面选2个人:

复制代码
选中的2个人

等价于:

复制代码
没有被选中的3个人

所以:

例如:


十七、这个性质为什么非常有用?

比如:

直接算:

看起来很吓人。

但利用:

可以变成:

于是只需要计算:

简单很多。

所以考试看到:

先看看:

m是不是很大?

如果很大,就把它变成:

n−m


十八、组合数还有一个神奇规律

看看:

分别是:

1, 5, 10, 10, 5, 1

于是:

复制代码
              1
            1   1
          1   2   1
        1   3   3   1
      1   4   6   4   1
    1   5   10  10  5   1

这就是我们曾经学习的:

杨辉三角

它和组合数有直接关系:

第n行的数字就是组合数

例如:

复制代码
1  5  10  10  5  1

对应:

这也是为什么前面学习过的杨辉三角,和今天的组合数学是连在一起的。


十九、初赛中经常不让你真的计算

例如:

一个10个元素的集合有多少个子集?

这时候甚至不需要排列组合公式。

对于每一个元素:

复制代码
选
不选

都有两种可能。

10个元素:

所以:

我们可以给出这一结论:n个元素的集合,每个元素都有"选/不选"两种可能,因此子集共有 2^n 个。

例如:

10个元素的集合中,恰好选择7个元素组成子集,有多少个?

就是:

所以如果问:

"10个元素集合中,7元素子集占所有子集的比例?"

就是:

这也是典型题型。


二十、再看一道竞赛中的经典题

甲、乙、丙三位同学选修课程。

有4门课程,甲选2门,乙、丙各选3门。

一共有多少种不同的选课方案?

这个题特别适合训练:

先分别计算,再使用乘法原理。


4门中选2门:


4门中选3门:


4门中选3门:


三个人同时完成

甲有6种。

每一种甲的选择,乙有4种。

每一种甲乙组合,丙又有4种。

所以:

6×4×4=96

答案:

96

这就是采用了"组合数 和 乘法原理"思路。


二十一、C++中怎么计算组合数?

如果数据比较小,可以直接写一个阶乘函数:

复制代码
#include <iostream>
using namespace std;

long long fact(int n)
{
    long long ans = 1;

    for(int i = 1; i <= n; i++)
    {
        ans *= i;
    }

    return ans;
}

long long C(int n, int m)
{
    return fact(n) / fact(m) / fact(n - m);
}

int main()
{
    cout << C(5, 3) << endl;

    return 0;
}

输出:

复制代码
10

二十二、但是这个程序有一个大问题

如果:

复制代码
fact(100)

会发生什么?

100!

是一个极其巨大的数字。

long long 根本装不下。

所以在真正的算法题中,不能看到组合数就傻乎乎地计算:

复制代码
n!

这就是信息学竞赛非常重要的一个意识:

数学公式正确,不代表直接写程序计算公式就是正确的算法。

以后我们会学习:

  • 杨辉三角求组合数

  • 递推

  • 取模

  • 防止整数溢出

  • 快速计算组合数

今天先基本建立数学基础。


二十三、初赛"看到题目"的快速判断模板

以后大家遇到类似题目,可以按照下面流程:

复制代码
第一步:到底是在"选"还是"排"?
              ↓
       需要考虑顺序吗?
        ↙            ↘
      不需要          需要
        ↓              ↓
      组合             排列
        ↓              ↓
   C(n,m)          A(n,m)

如果是全部排列:

如果是从n个中选m个排列:

如果只是从n个中选m个:


二十四、今天容易错的5个地方

错误1:把"选3个人"自动当组合

不一定。

选3个人当冠军、亚军、季军

是排列。


错误2:把"排队"自动当排列

也不一定。

选出的同学按照年龄从小到大排列。

顺序已经被规定了,本质上只是选择。


错误3:忘记 m!

排列和组合最核心的关系:


错误4:不会使用对称性

看到:

C_{100}^{97}

应该想到:


错误5:看到阶乘就直接写程序算

数学上可以写:

但程序中可能溢出。

所以:

公式是数学工具,算法是程序工具。

两者不能混为一谈。


二十五、课堂练习

练习1

4个人全部排队,有多少种排法?

答案:

4! = 24


练习2

7个人中选3个人参加比赛,有多少种选法?


练习3

7个人中选3个人,分别担任队长、副队长、秘书。


练习4

可以改写成什么?

答案:


练习5

可以写成阶乘形式吗?

可以:


练习6

等于什么?

根据:

所以:


二十六、今天的"知识地图"

把今天的知识串起来:

复制代码
                 阶乘
                  │
             n! = 全排列
                  │
          ┌───────┴────────┐
          ↓                ↓
       排列 A              组合 C
          │                │
      考虑顺序          不考虑顺序
          │                │
          └──────┬─────────┘
                 ↓
        A(n,m)=C(n,m)×m!

再记住:


二十七、给同学们的最终记忆口诀

全部排,阶乘 n!;

选了还要排,排列 A(n,m);

只选不排,组合 C(n,m);

排列里面藏组合,差一个 m!。

更重要的是这一句:

先别急着套公式,先问:换个顺序,还是不是同一种结果?

这句话真正掌握以后,排列组合题就从"背公式"变成了"分析问题"。


相关推荐
水饺编程1 小时前
第5章,[Win32 章节] :在平面直角坐标系中描点
c语言·c++·windows·visual studio
linx2951 小时前
单元九 · 零基础路线图-第 7–9 周·类与 RAII
c语言·开发语言·数据结构·c++·嵌入式硬件·算法
布莱克6051 小时前
C++ 七大排序算法详解:选择、冒泡、插入、计数、堆、快速与归并排序
数据结构·c++·算法·排序算法
一只旭宝1 小时前
Python 与 C/C++ 内存模型对比总结
c语言·c++·python
心态还需努力呀2 小时前
把 Flameshot 编译进鸿蒙 PC:一次 Qt C++ 截图工具的源码级移植实战
c++·qt·harmonyos
科学实验家3 小时前
最小生成树:Prim,kruskal
数据结构·c++·算法
学习智者3 小时前
《玄》IDE v3.6.3重磅发布:全功能修复与性能飞跃
开发语言·c++·ide·算法·中文语言 玄
2402_882893863 小时前
C++异常:从概念到实践的一篇博客
c++·异常
m0_734571763 小时前
深入理解C++ 析构函数<二>析构顺序
开发语言·c++