组合数学

组合数学

基本概念

1. 加法原理和乘法原理

  1. 加法原理:一件事情可以用 \(m\) 类不同的方法完成,其中第 \(i\) 类方法有 \(a_i\) 中不同的方法。则总方法数为 \(\sum\limits_{i=1}^{m}a_i\)
  2. 乘法原理:假设两个集合 \(S_A\)\(S_B\)\(S_A\) 中每一个选择都对应了 \(S_B\) 中的 \(|S_B|\) 个数,则总方案数为 \(|S_A|·|S_B|\)。推广得到:有 \(m\) 个集合 \(S_i\),每个集合选一个数,则总方案数为 \(\prod\limits_{i=1}^{m}|S_i|\)

2. 排列

​ 排列是有序的,理解为把 \(n\) 个元素的集合 \(S\) 的一个 \(m\) 排列理解为在这 \(n\) 个元素中选 \(m\) 个进行有序摆放的总方案数

  1. 不可重复排列数:从 \(n\) 个不同的物体中不重复地取出 \(m\) 个,记作 \(P_n^m = \cfrac{n!}{(n-r)!} = n(n-1)(n-2)\cdots(n-m+1)\)

  2. 可重复排列数:从 \(n\) 个不同的物体中可重复地取出 \(m\) 个,排列数为 \(n^r\)

  3. 循环排列:从 \(n\) 个构成环的不同的物体中可重复地取出 \(m\) 个,排列数为 \(\cfrac{P_n^m}{m}\)

3. 组合

​ 排列是有序的,组合是无序的,理解为把 \(n\) 个元素的集合 \(S\) 的一个 \(m\) 排列理解为在这 \(n\) 个元素中选 \(m\) 个进行无序摆放(相同元素构 成的不同方案算作一个)的总方案数,记作\(C_n^m\)

​ 当所有元素互不相同时,组合数 \(C_n^m=\cfrac{P_n^m}{r!}=\cfrac{n!}{r!(n-r)!}=\cfrac{n(n-1)\cdots(n-m+1)}{r(r-1)\cdots 2\times1}\)

​ 组合数有三个重要性质

  • \(C_n^m=C_n^{n-m}\):从 \(n\) 个元素中选 \(m\) 个等价于在 \(n\) 个元素中选择丢弃 \(n-m\)

  • \(C_n^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}\)帕斯卡公式):可以如下证明:

    1. 选第 \(n\) 个元素 \(\rightarrow\) 在剩下的 \(n-1\) 个元素中还要选 \(m-1\)个(\(C_{n-1}^{m-1}\)
    2. 不选第 \(n\) 个元素 \(\rightarrow\) 在剩下的 \(n-1\) 个元素中还要选 \(m\) 个(\(C_{n-1}^{m}\)

    通过加法原理可以得出 \(C_n^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}\)

  • \(\sum\limits_{i=0}^{n}C_n^i=2^n\)\(C_n ^m\)在二进制中的表示为在所有长度为 \(n\) 的二进制 \(01\) 串,其中有 \(m\)\(1\)的串有几个。而长度取 \([0,n]\) 时总方案为 \([0,2^n]\) 的二进制数的数量

二项式定理与杨辉三角

观察 \((x+y)^n\) 的展开项:

\[\begin{equation} (x+y)^0=1 \\ (x+y)^1=1x+1y \\ (x+y)^2=1x^2+2xy+1y^2 \\ (x+y)^3=1x^3+3x^2y+3xy^2+1y^3 \\ (x+y)^4=1x^4+4x^3y+6x^2y^2+4xy^3+1y^4 \end{equation} \]

其左侧的展开式 \((x+y)^n\) 中系数构成杨辉三角第 \(n+1\)

我们需要逐个通过杨辉三角计算 \((x+y)^n\) 的各项系数,这样复杂度会较高。我们用二项式定理快速求解。

我们将 \((x+y)^n\) 展开为 \((x+y)(x+y)\cdots(x+y)\),我们将这些 \((x+y)\) 看作装着两个元素 \(x\)\(y\) 的集合。而在展开式中第 \(m\) 项目(第 \(1\) 个算作第 \(0\) 项)为 \(k·x^{n-m}y^{m}\),可以理解为在这 \(n\) 个集合中选 \(n-m\)\(x\)(选 \(m\)\(y\))相乘,有 \(C_n^{m}\) 种方案,也就是系数为\(C_n^{m}\)

推导得:

\[(a+b)^n=\sum_{m=0}^{n}C_n^ma^mb^{n-m}=\sum_{m=0}^{n}C_n^mb^ma^{n-m} \]

而计算二项式系数有两种方法:

  1. 递推法\(C_n^{m}=C_{n-1}^{m}+C_{n-1}^{m-1}\),这个递推式就是杨辉三角的定义,数值等于“两肩”和(时间复杂度为 \(O(n^2)\)

    for (int i = 1;i <= n;i ++) 
      c[i][1] = c[i][i] = 1;
      for (int j = 2;j < i;j ++)
        c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
    
  2. 用公式直接算: \(C_n^m=\cfrac{n!}{r!(n-r)!}\)

    由于二项式系数增长极快,大部分情况都需要取模,需要用到

    \(C_n^m~mod~p=\cfrac{n!}{r!(n-r)!}~mod~p=(n! ~ mod ~ p)(r!)^{-1}((n-r)!)^{-1}~~(mod~p)\)

    时间复杂度为 \(O(n)\)

    void init(int n) {
      fac[0] = 1ll;
      for (int i = 1;i <= n;i ++) {
        fac[i] = (fac[i - 1] * i) % mod; // 预处理 i!
        inv[i] = fastPow(fac[i],mod - 2ll); // 费马小定理求i!的逆
      }
    }
    ll C(int n,int m) {
      return (fac[n] * inv[m] % mod * inv[n - m] % mod) % mod;
    }
    

卢卡斯定理

卢卡斯定理用于计算组合数取模,即 \(C_n ^m~mod~p\)\(m\) 为质数

组合数的计算可以用,但这种计算方法也有局限性。通过的定义( \(ax\equiv 1~(mod~p)\) 的一个解)我们可以得出 \(p\) 最好是大于 \(a\) 的质数,才能保证 \(\gcd(a,p)=1\)。在组合数的计算 \(C_n^m=(n! ~ mod ~ p)(r!)^{-1}((n-r)!)^{-1}~~(mod~p)\) 中,如果 \(p<n\),就无法保证 \((n!)^{-1}\)\(((n-r)!)^{-1}\) 的逆的存在。而卢卡斯定理则可以解决这类问题

卢卡斯定理 对于非负整数 \(n\)\(m\) 和素数 \(p\),有:

\[C_n^m\equiv\prod_{i=0}^{k}C_{n_i}^{m_i}~(mod~p) \\ C_n^m~mod~p=\prod_{i=0}^{k}C_{n_i}^{m_i}~mod~p \]

其中 \(n=n_kp^k+\cdots+n_1p+n_0\)\(m=m_kp^k+\cdots+m_1p+m_0\)\(n\)\(m\)\(p\) 进制展开式

意义是对 \(p\) 进制下的 \(n\)\(m\) 的每一位计算组合数,最后乘起来

但在编程中我们通常使用另外一个表达:

\[C_n^m \equiv C_{n~mod~p}^{m~mod~p}~\cdot~C_{n/p}^{m/p} \]

在计算时我们将两边拆开

  • 左侧 \(C_{n~mod~p}^{m~mod~p}\)直接算(\((n~mod~p)<p\)
  • 右侧 \(C_{n/p}^{m/p}\) 则递归继续计算(\(m=0\) 时结束递归并返回 \(C_n^0=1\)
ll Lucas(ll n,ll m,ll p) {
  if (p == 0) return 1ll; 
  return C(n % p,m % p,p) * Lucas(n / p,m / p,p) % p;
}
posted @ 2025-04-01 22:17  nightmare_lhh  阅读(21)  评论(0)    收藏  举报