🎁 当前为 免费试看 内容 · 开通VIP 解锁 GESP 通关手册全部 1–8 级知识点 + 题库 + 详细解析
🌐 主站
📖 已学 0

📌 组合数学

所属等级:GESP 8级 | 难度:★★★ | 关键词:排列组合、容斥原理、递推计数

📝 考纲要求

  • 掌握基本计数:加法原理与乘法原理;
  • 理解排列数 A(n,m) 与组合数 C(n,m) 的含义与计算;
  • 掌握容斥原理:|A∪B| = |A| + |B| − |A∩B|;
  • 能用递推/递推式计数(如斐波那契、走楼梯、卡特兰数入门)。

📖 通俗讲解

💡 一句话理解 排列讲究“谁在前谁在后”(顺序有关),组合只看“选了哪些人”(顺序无关)。容斥就是“先全加,再把重复算的减掉”。递推是用小情况推算大情况。

① 组合数 C(n,m) 与杨辉三角

组合数有递推:C(n,m) = C(n-1,m-1) + C(n-1,m),正是杨辉三角。下面用递推表计算。

🖼️ 图解

杨辉三角:每个数 = 正上方(左上) + 左上方(右上),即组合数递推
            1
           1   1
         1   2   1
       1   3   3   1
     1   4   6   4   1
           ↑   ↑
       C(4,2)=6 由 3+3 得到:C(n,m)=C(n-1,m-1)+C(n-1,m)
C++
#include <iostream>
using namespace std;

const int N = 25;
long long C[N][N];   // C[n][m] 存组合数

// 用杨辉三角递推打表
void init() {
    for (int n = 0; n < N; n++) {
        C[n][0] = 1;             // 选 0 个只有 1 种
        C[n][n] = 1;             // 全选也只有 1 种
        for (int m = 1; m < n; m++) {
            // 第 n 行 = 上一行左边 + 上一行自己
            C[n][m] = C[n-1][m-1] + C[n-1][m];
        }
    }
}

int main() {
    init();
    int n, m; cin >> n >> m;
    cout << C[n][m] << endl;   // 输出 C(n,m)
    return 0;
}
输入 / 输出5 2 10

② 递推计数:上楼梯(斐波那契)

问题:一次可走 1 或 2 级台阶,n 级台阶共有多少种走法?

C++
#include <iostream>
using namespace std;

const int N = 55;
long long f[N];     // f[i] 表示 i 级台阶的走法数

int main() {
    int n; cin >> n;
    f[0] = 1;        // 0 级:原地不动算 1 种
    f[1] = 1;        // 1 级:只有 1 种
    for (int i = 2; i <= n; i++) {
        // 最后一步要么走1级(来自 i-1),要么走2级(来自 i-2)
        f[i] = f[i-1] + f[i-2];
    }
    cout << f[n] << endl;
    return 0;
}
输入 / 输出5 8

🔵 核心必背重点

✅ 必背
  • 加法原理:分类相加;乘法原理:分步相乘;
  • 排列 A(n,m)=n!/(n-m)!;组合 C(n,m)=n!/(m!(n-m)!),且 C(n,m)=C(n,n-m);
  • 容斥:两数并集 = 各数之和 − 交集;三集合再 ± 三交集;
  • 组合数递推:C(n,m)=C(n-1,m-1)+C(n-1,m);
  • 斐波那契递推:f[i]=f[i-1]+f[i-2],注意用 long long 防溢出。
🔑
记忆口诀:“排列要顺序,组合只看人;容斥先加后减交,递推小推大。”

🔴 考试拉分难点

⚠️ 难点
  • 容斥的多集合:三集合容斥为 A+B+C − 两两交 + 三交,符号交替,容易漏;
  • 计数与顺序:区分“排列(有序)”还是“组合(无序)”,选错公式整题错;
  • 大数溢出:组合数、斐波那契增长极快,n 稍大就要 long long 甚至取模;
  • 递推边界:f[0] 取 0 还是 1 要根据题意定,影响后续所有值。

🟡 高频易错陷阱

🚫 扣分坑点
  • 把排列当组合(或反过来),忘记除以顺序数;
  • 容斥只加不减,或三集合符号搞反;
  • 用 int 存组合数导致溢出,应 long long;
  • 递推初值写错(如 f[0]=0 导致 f[2] 也跟着错);
  • 用 bits/stdc++.h(禁用),只用 iostream。

🧪 例题演练

例题 1(基础题):容斥原理计数

题目:1~n 中,能被 a 或 b 整除的数有多少个?(用容斥:被 a 整除 + 被 b 整除 − 被 lcm(a,b) 整除)

C++
#include <iostream>
using namespace std;

// 求最大公约数(辗转相除法)
long long gcd(long long x, long long y) {
    while (y) { long long t = x % y; x = y; y = t; }
    return x;
}
// 求最小公倍数
long long lcm(long long a, long long b) {
    return a / gcd(a, b) * b;   // 先除后乘,避免溢出
}

int main() {
    long long n, a, b;
    cin >> n >> a >> b;
    long long cntA = n / a;          // 被 a 整除的个数
    long long cntB = n / b;          // 被 b 整除的个数
    long long L = lcm(a, b);
    long long cntAB = n / L;         // 同时被 a、b 整除(被公倍数整除)
    // 容斥:并集 = A + B - 交集
    cout << cntA + cntB - cntAB << endl;
    return 0;
}
输入 / 输出20 2 3 13

例题 2(模拟真题):网格路径计数(组合应用)

题目:从 (0,0) 走到 (n,m),每次只能向右或向上走一格,共有多少条路径?(答案 = C(n+m, n))

C++
#include <iostream>
using namespace std;

const int N = 25;
long long C[N][N];

// 杨辉三角打表求组合数
void init() {
    for (int i = 0; i < N; i++) {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; j++)
            C[i][j] = C[i-1][j-1] + C[i-1][j];
    }
}

int main() {
    init();
    int n, m; cin >> n >> m;
    // 共走 n+m 步,选 n 步向右(其余向上)
    cout << C[n + m][n] << endl;
    return 0;
}
输入 / 输出2 3 10
🚫 错误示范 cout << C[n][m]; —— 把“向右步数 n、向上步数 m”错当成 C(n,m)。实际总步数是 n+m,应在 n+m 中选 n 步向右,应为 C(n+m, n)。

🛡️ 避坑总结

  1. 排列看顺序、组合不看顺序,先判断是否有序;
  2. 容斥:并集 = 各集合和 − 两两交 + 三交(符号交替);
  3. 组合数用杨辉三角递推,C(n,m)=C(n-1,m-1)+C(n-1,m);
  4. 递推计数注意初值与 long long 防溢出;
  5. 只用 iostream,禁用 bits/stdc++.h。

💡 提示:按 ← → 方向键可在上一 / 下一知识点间快速翻页

GESP 8级 · 学习进度 0/0

📑 本页目录