C++基础组合计数入门指南

在算法竞赛和计算机科学中,组合计数是解决排列组合问题的基石。无论是计算排列数、组合数,还是处理多重集合的排列组合,掌握组合计数的原理和实现方法都至关重要。本文将详细介绍C++中组合计数的基础知识,包括排列与组合的定义、计算公式、性质,以及如何通过代码高效实现。


一、组合计数的核心概念

1. 加法原理与乘法原理

  • 加法原理:完成一件事有 n n n类独立的方法,每类方法分别有 m 1 , m 2 , … , m n m_1, m_2, \dots, m_n m1,m2,,mn种,则总方法数为:
    N = m 1 + m 2 + ⋯ + m n N = m_1 + m_2 + \dots + m_n N=m1+m2++mn
    即:
    N = ∑ i = 1 n m i N = \sum\limits_{i=1}^n m_i N=i=1nmi

  • 乘法原理:完成一件事需要分 n n n步,每步分别有 m 1 , m 2 , … , m n m_1, m_2, \dots, m_n m1,m2,,mn种方法,则总方法数为:
    N = m 1 × m 2 × ⋯ × m n N = m_1 \times m_2 \times \dots \times m_n N=m1×m2××mn
    即:
    N = ∏ i = 1 n m i N = \prod\limits_{i=1}^n m_i N=i=1nmi

示例
由数字 0 , 1 , 2 , 3 , 4 , 5 0,1,2,3,4,5 0,1,2,3,4,5组成无重复数字的五位奇数。

  • 步骤

    1. 末位必须是奇数( 1 , 3 , 5 1,3,5 1,3,5),有 C 3 1 C^1_3 C31种选择。
    2. 首位不能为 0 0 0,且已选一个奇数,剩下 4 4 4个数字可选,有 C 4 1 C^1_4 C41种选择。
    3. 中间三位从剩余 4 4 4个数字中排列,有 A 4 3 A^3_4 A43种方法。
  • 总方法数
    N = C 3 1 × C 4 1 × A 4 3 = 3 × 4 × 24 = 288 N = C^1_3 \times C^1_4 \times A^3_4 = 3 \times 4 \times 24 = 288 N=C31×C41×A43=3×4×24=288


二、排列与组合

1. 排列(Permutation)

n n n个不同元素中取出 m m m个元素并按顺序排列,记作 A n m A^m_n Anm P ( n , m ) P(n, m) P(n,m)
公式
A n m = n ! ( n − m ) ! = n × ( n − 1 ) × ⋯ × ( n − m + 1 ) A^m_n = \frac{n!}{(n-m)!} = n \times (n-1) \times \dots \times (n-m+1) Anm=(nm)!n!=n×(n1)××(nm+1)
即:
A n m = ∏ i = n − m + 1 n i A^m_n =\prod\limits_{i=n-m+1}^n i Anm=i=nm+1ni

2. 组合(Combination)

n n n个不同元素中取出 m m m个元素但不考虑顺序,记作 C n m C^m_n Cnm
公式
C n m = A n m m ! = n ! m ! ( n − m ) ! = ∏ i = n − m + 1 n i m ! ( n − m ) ! C^m_n = \frac{A^m_n}{m!} = \frac{n!}{m!(n-m)!}=\frac{\prod\limits_{i=n-m+1}^n i}{m!(n-m)!} Cnm=m!Anm=m!(nm)!n!=m!(nm)!i=nm+1ni

性质

  1. C n m = C n n − m C^m_n = C^{n-m}_n Cnm=Cnnm(对称性)。
  2. C n m = C n − 1 m − 1 + C n − 1 m C^m_n = C^{m-1}_{n-1} + C^m_{n-1} Cnm=Cn1m1+Cn1m(递推关系)。
  3. ∑ i = 0 n C n i = 2 n \sum\limits_{i=0}^n C^i_n = 2^n i=0nCni=2n(子集总数)。

示例
7 7 7种不同的花中选出两种特殊的葵花,要求它们不在中间或两边的位置。

  • 步骤

    1. 特殊葵花的位置有 A 4 2 A^2_4 A42种选择(中间和两边除外)。
    2. 剩余 5 5 5种花任意排列,有 A 5 5 A^5_5 A55种方法。
  • 总方法数
    N = A 4 2 × A 5 5 = 12 × 120 = 1440 N = A^2_4 \times A^5_5 = 12 \times 120 = 1440 N=A42×A55=12×120=1440


三、组合数的计算方法

方法1:递推法(杨辉三角)

利用组合数的递推关系 C n m = C n − 1 m − 1 + C n − 1 m C^m_n = C^{m-1}_{n-1} + C^m_{n-1} Cnm=Cn1m1+Cn1m,预处理所有可能的组合数。
时间复杂度 O ( n × m ) O(n\times m) O(n×m)
代码示例

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n, m;
    cout << "Enter n and m: ";
    cin >> n >> m;
    vector<vector<int>> c(n + 1, vector<int>(m + 1, 0));
    c[0][0] = 1;
    for (int i = 1; i <= n; i++) {
        c[i][0] = 1;
        for (int j = 1; j <= min(i, m); j++) {
            c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
        }
    }
    cout << "C(" << n << "," << m << ") = " << c[n][m] << endl;
    return 0;
}

方法2:阶乘与逆元(快速计算)

通过预处理阶乘和逆元,直接计算组合数。
公式
C n m = n ! m ! ( n − m ) ! = n ! × inv ( m ! ) × inv ( ( n − m ) ! ) C^m_n = \frac{n!}{m!(n-m)!} = n! \times \text{inv}(m!) \times \text{inv}((n-m)!) Cnm=m!(nm)!n!=n!×inv(m!)×inv((nm)!)
其中, inv ( ) \text{inv}() inv()函数用于计算矩阵的逆矩阵。如果矩阵 A A A是一个方阵且可逆(非奇异),那么 inv ( A ) \text{inv}(A) inv(A)将返回其逆矩阵 A − 1 A^{-1} A1,满足 A × A − 1 = A − 1 × A = I A \times A^{-1}=A^{-1} \times A = I A×A1=A1×A=I,其中 I I I是单位矩阵,即 inv ( A ) = A − 1 \text{inv}(A)=A^{-1} inv(A)=A1
时间复杂度 O ( n ) O(n) O(n)
代码示例

#include <iostream>
#include <vector>
using namespace std;

const int MOD = 1e9 + 7;

// 快速幂求逆元
long long power(long long a, long long p) {
    long long res = 1;
    while (p) {
        if (p % 2) res = res * a % MOD;
        a = a * a % MOD;
        p /= 2;
    }
    return res;
}

int main() {
    int n, m;
    cout << "Enter n and m: ";
    cin >> n >> m;
    m = min(m, n - m); // 利用对称性优化
    vector<long long> fact(n + 1, 1);
    for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i % MOD;
    long long numerator = fact[n];
    long long denominator = fact[m] * fact[n - m] % MOD;
    long long inv_denominator = power(denominator, MOD - 2);
    long long result = numerator * inv_denominator % MOD;
    cout << "C(" << n << "," << m << ") = " << result << endl;
    return 0;
}

四、多重集合的排列与组合

1. 多重集合的排列

若集合中有重复元素(如 n 1 n_1 n1 a 1 a_1 a1 n 2 n_2 n2 a 2 a_2 a2,…),则本质不同的排列数为:
( n 1 + n 2 + ⋯ + n k ) ! n 1 ! × n 2 ! × ⋯ × n k ! \frac{(n_1 + n_2 + \dots + n_k)!}{n_1! \times n_2! \times \dots \times n_k!} n1!×n2!××nk!(n1+n2++nk)!

示例
字母序列AAABBBC的排列数为:
7 ! 3 ! × 3 ! × 1 ! = 140 \frac{7!}{3! \times 3! \times 1!} = 140 3!×3!×1!7!=140

2. 多重集合的组合

从多重集合中选取 R R R个元素,允许重复选取,则组合数为:
C k + R − 1 R C_{k + R - 1}^R Ck+R1R
其中 k k k为不同元素的种类数。


五、总结

组合计数是算法竞赛和软件开发中的基础工具。通过掌握排列与组合的定义、递推关系、阶乘预处理等方法,可以高效解决大多数组合问题。对于更复杂的场景(如多重集合、容斥原理),则需要结合数学推导和代码优化。建议通过练习经典题目(如杨辉三角、二项式定理)加深理解。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐