【笔记】组合数学

组合数学

排列组合

排列组合是组合数学中的基础。排列就是指从给定个数的元素中取出指定个数的元素进行排序;组合则是指从给定个数的元素中仅仅取出指定个数的元素,不考虑排序。

排列组合的中心问题是研究给定要求的排列和组合可能出现的情况总数。排列组合与古典概率论关系密切。

在高中初等数学中,排列组合多是利用列表、枚举等方法解题。 - \(\text{oi-wiki}\)

加法 & 乘法原理

加法原理

完成一个工程可以有 \(n\) 类办法,设 \(a_i(1 \leq i \leq n)\) 代表第 \(i\) 类方法的数目。那么完成这件事共有 \(\sum_{i = 1} ^ n a_i\) 种不同的方法。

乘法原理

完成一个工程要分 \(n\) 个步骤,设 \(a_i(1 \leq i \leq n)\) 代表第 \(i\) 个步骤的不同方法的数目。那么完成这件事共有 \(\prod_{i = 1} ^ n a_i\) 种不同的方法。

排列组合

这玩意不想多说了,自己看数学选修 2 就可以了。

\(A(n,m)\) 排列数 : 从 \(n\) 个不同的数中选出 \(m\) 个数字,组成序列的方案数,又写作 \(A_n ^ m\)

\[A_n ^ m = \frac{n!}{(n - m)!} \]

\(C(n,m)\) 组合数 : 从 \(n\) 个不同的数中选出一大小为 \(m\) 的无序集合的方案数,又写作 \(\dbinom{n}{m}\)

\[\dbinom{n}{m} = \frac{n!}{m! \times (n - m)!} \]

\(n!\) 意思是 \(n\) 的阶乘 = \(\prod_{i = 1} ^ n i\)

特别的,当 \(m > n\) 时,\(A_n ^ m = C_n ^ m = 0\)

至于证明 ,详见 \(Link\)

组合数的几个性质

  • 性质一

\[\dbinom{n}{m} = \dbinom{n}{n - m} \]

证明 : 由于组合数的定义,对于从 \(n\) 个不同元素中取出 \(m\) 个组成的每个集合,剩余未取出的元素也构成一个集合,两个集合一一对应,所以性质一成立。

  • 性质二

\[\dbinom{n}{m} = \dbinom{n - 1}{m} + \dbinom{n - 1}{m - 1} \]

证明 : 从 \(n\) 个不同元素中取出 \(m\) 个组成一个集合有两类方法 ,取 \(n\) 号元素,不取 \(n\) 号元素。若取 \(n\) 号元素,则应在剩余的 \(n - 1\) 个元素中选出 \(m - 1\) 个,有 \(\dbinom{n - 1}{m - 1}\) 种选法。根据加法原理,性质二成立。

  • 性质三

\[\dbinom{n}{0} + \dbinom{n}{1} + \dbinom{n}{2} + \cdots + \dbinom{n}{n} = 2 ^ n \]

证明 : 从 \(n\) 个不同的元素中取出若干的元素组成一个集合,有 \(n + 1\) 类方法,分别是取出 \(1,2,3, \cdots , n\) 个,根据加法原理,共有 \(\dbinom{n}{0} + \dbinom{n}{1} + \dbinom{n}{2} + \cdots + \dbinom{n}{n}\) 种方法,从另一个方面去想,\(n\) 个元素每个元素都可以取或不取,总方案数是 \(2 ^ n\),二者相等,故性质三成立。

  • 性质四
    • \[\dbinom{n + m}{n} = \dbinom{n + m}{m} \]

    • \[\dbinom{n + r + 1}{r} = \dbinom{n + r}{r} + \dbinom{n + r - 1}{r - 1} + \cdots + \dbinom{n}{0} \]

    • \[\dbinom{n}{l} \dbinom{l}{r} = \dbinom{n}{r} \dbinom{n - r}{l - r} \]

    • \[\dbinom{n}{0} - \dbinom{n}{1} + \dbinom{n}{2} - \cdots = 0 \]

    • \[\dbinom{r}{r} + \dbinom{r + 1}{r} + \cdots + \dbinom{n}{r} = \dbinom{n + 1}{r + 1} \]

    • \[(1 + x) ^ n = \sum_{k = 0} ^ n \dbinom{n}{k} x ^ {n - k} = \sum_{k = 0} ^ n \dbinom{n}{k} x ^ k \]

二项式定理

\[(a + b) ^ n = \sum_{k = 0} ^ n \binom{n}{k} a ^ {k} b ^ {n - k} \]

证明 :

采用数学归纳法 \(\dbinom{n}{k} + \dbinom{n}{k - 1} = \dbinom{n + 1}{k}\),做归纳。

\(n = 1\) 时,\((a + b) ^ 1 = \dbinom{n}{0}a ^ 0b ^ 1 + \dbinom{n}{1} a ^ 1b ^ 0 = a + b\) 显然成立。

假设当 \(n = m\) 时命题成立,当 \(n = m + 1\) 时 :

\[\begin{aligned}(a + b) ^ {m + 1} &= (a + b)(a + b) ^ m \\&= (a + b) \sum_{k = 0} ^ m \dbinom{m}{k} a ^ k b ^ {m - k} \\&= \sum_{k = 0} ^ m \dbinom{m}{k}a ^ {k + 1} b ^ {m - k} + \sum_{k = 0} ^ m \dbinom{m}{k} a ^ k b ^ {m - k + 1} \\&= \sum_{k = 1} ^ {m + 1} \dbinom{m}{k - 1} a ^ k b ^ {m - k + 1} + \sum_{k = 0} ^ m \dbinom{m}{k} a ^ k b ^ {m - k + 1} \\&= \sum_{k = 0} ^ {m + 1}(\dbinom{m}{k - 1} + \dbinom{m}{k} ) a ^ k b ^{m - k + 1} \\&= \sum_{k = 0} ^ {m + 1} \dbinom{m + 1}{k} a ^ kb ^ {m + 1 - k} \end{aligned} \]

组合数的求法

  • 递推

根据 \(\dbinom{n}{m} = \dbinom{n - 1}{m} + \dbinom{n - 1}{m - 1}\),我们可以直接递推 \((0 \leq x \leq y \leq n)\) 的所有组合数 \(\dbinom{y}{x}\),时间复杂度 \(O(n ^ 2)\)

#include <cstdio>
#include <cmath>
#include <iostream>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;
const int Maxk = 1e3 + 10;
LL f[Maxk][Maxk];
inline int read()
{
	int s = 0, f = 0;char ch = getchar();
	while (!isdigit(ch)) f |= ch == '-', ch = getchar();
	while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
	return f ? -s : s;
}
signed main()
{
  int n = read(),m = read();
  f[0][0] = 1;
  for(int i = 1;i <= n;i ++) {
    for(int j = 0;j <= i;j ++) {
      f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
    }
  }
  return 0;
}
  • 阶乘

如果题目要求求出 \(\dbinom{n}{m}\) 对一个数 \(p\) 取模后的值,并且 1 到 \(n\) 都存在模 \(p\) 的乘法逆元,可以先计算分子 \(n! \bmod p\),再计算分母 \(m!(n - m)! \bmod p\) 的逆元,乘起来得到 \(\dbinom{n}{m} \bmod p\),时间复杂度为 \(O(n)\)

#include <cstdio>
#include <cmath>
#include <iostream>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;
const int Maxk = 1e5 + 10;
int p;
LL a[Maxk];
inline int read()
{
	int s = 0, f = 0;char ch = getchar();
	while (!isdigit(ch)) f |= ch == '-', ch = getchar();
	while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
	return f ? -s : s;
}
LL KSM(LL x,LL y,LL mod)
{
  x %= mod;
  LL ans = 1;
  while(y) {
    if(y & 1) ans = ans * x % mod;
    x = x * x % mod;
    y >>= 1;
  }
  return ans;
}
LL C(LL n,LL m) 
{
  if(m > n) return 0;
  return ((a[n] * KSM(a[m],p - 2,p)) % p * KSM(a[n - m],p - 2,p) % p) % p; 
}
signed main()
{
  int n = read(),m = read();p = read();
  a[0] = 1;
  for(int i = 1;i <= p;i ++) a[i] = (a[i - 1] * i) % p;
  printf("%lld\n",C(n + m,n));
  return 0;
}

组合数取模

求解 : $$\dbinom{n}{m} \bmod p$$

  • 杨辉三角递推

本方案适用于 \(n,m\) 均较小或者 \(n\) 较大但 \(m\),较小的时候。

可以得到所有的组合数,时间复杂度都是 \(O(nm)\)

  • 阶乘 + 逆元。

考虑组合数公式 \(\dfrac{n!}{m!(n - m)!}\)

预处理阶乘,并求逆元即可,复杂度 \(O(n)\) 级别。

以上这两种做法时间复杂度与 \(p\) 无关。

  • \(\text{lucas}\) 定理

\(\text{lucas}\) 定理用于求解大组合数取模的问题,其中模数必须为素数。

这里只写一个结论,因为博主太菜了不会证明不值得写。

\[\dbinom{n}{m} \bmod p = \dbinom{\left \lfloor n / p \right \rfloor}{\left \lfloor m / p \right \rfloor} \cdot \dbinom{n \bmod p}{m \bmod p} \bmod p \]

因为 \(n \bmod p\)\(m \bmod p\) 肯定比 \(p\) 要小,可以直接求解。

\(\dbinom{\left \lfloor n / p \right \rfloor}{\left \lfloor m / p \right \rfloor}\) 可以继续用 \(\text{lucas}\) 来求解,要求 \(p\) 的范围不能太大,一般在 \(10 ^ 5\) 左右。

返回 : 当 \(m = 0\) 时,返回 \(1\)

时间复杂度 \(O(f(p) + g(n) \log n)\),其中 \(f(n)\) 为预处理组合数的复杂度,\(g(n)\) 为单次求组合数的复杂度。

证明请看 \(\text{Link}\)

  • \(\text{exLucas}\) 定理

咕咕咕

多重集的排列数和组合数

多重集是指包含重复元素的广义集合。设 \(S = \{n_1 \cdot a_1,n_2 \cdot a_2,n_3 \cdot a_3,\cdots,n_k \cdot a_k\}\),是由 \(n_1\)\(a_1\)\(n_2\)\(a_2\) \(\cdots\) \(n_k\)\(a_k\) 组成的多重集,记 \(n = n_1 + n_2 + \cdots + n_k\)\(S\) 的全排列的个数为 :$$\frac{n!}{n_1!n_2!\cdots n_3!}$$

\(S = \{n_1 \cdot a_1,n_2 \cdot a_2,n_3 \cdot a_3,\cdots,n_k \cdot a_k\}\),是由 \(n_1\)\(a_1\)\(n_2\)\(a_2\) \(\cdots\) \(n_k\)\(a_k\) 组成的多重集,设整数 \(r \leq n_i (\forall i \in [1,k])\)。从 \(S\) 中取出 \(r\) 个元素组成一个多重集(不考虑元素的顺序),求产生的不同多重集的数量为 : $$\dbinom{k + r - 1}{k - 1}$$

几道例题

【计算系数】

知识 : 排列组合,杨辉三角递推,二项式定理。

\(Solution\)

根据二项式定理 :

\[(ax + by) ^ k = \sum_{i = 0} ^ k \dbinom{k}{i} a ^ i x ^ i b ^ {k - i} y ^ {k - i} \]

所以就可以得到 \(x ^ ny ^ m\) 的系数为 \(\dbinom{k}{n} a ^ nb ^ m\),又因为 \(m + n = k\),直接化为 \(\dbinom{k}{n} a ^ nb ^ {k - n}\)

套上个递推求组合数 + 快速幂就 \(OK\) 了。

\(Code\)

#include <cstdio>
#include <cmath>
#include <iostream>
#include <cstring>
#include <algorithm>
#define LL long long
using namespace std;
const int Maxk = 1e3 + 10;
const int mod = 10007;
LL f[Maxk][Maxk];
LL a,b,k,n,m,Ans;
inline LL read()
{
	LL s = 0, f = 0;char ch = getchar();
	while (!isdigit(ch)) f |= ch == '-', ch = getchar();
	while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
	return f ? -s : s;
}
LL KSM(LL a,LL b)
{
  LL ans = 1;
  while(b) {
    if(b & 1) ans = a * ans % mod;
    a = a * a % mod;
    b >>= 1;
  }
  return ans;
}
signed main()
{
  a = read(),b = read(),k = read(),n = read(),m = read();
  f[1][1] = 1;
  for(int i = 2;i <= k + 3;i ++) 
    for(int j = 1;j <= i;j ++) 
      f[i][j] = (f[i - 1][j - 1] % mod + f[i - 1][j] % mod) % mod;
  Ans = f[k + 1][n + 1];//第 k 层的数
  Ans *= ((KSM(a,n) % mod) * (KSM(b,m) % mod)) % mod;
  printf("%lld\n",Ans);
  return 0;
}

\(\text{Question 1}\)

Description

\(n\) 个不同的元素,从中选 \(r\) 个,但是每个可以选多次 (可重),求证 : 其方案数总共有 \(\dbinom{n + r - 1}{r}\) 种。

Solution

这是一道数学题,不存在代码。

我们假设选择 \(r\) 个数为 :

\[a_1 \leq a_2 \leq a_3 \leq a_4 \leq \cdots \leq a_r \]

\(b_i = a_i + i - 1\)

所以,原序列就变成了 :

\[1 \leq b_1 < b_2 < b_3 < b_4 < \cdots < b_r \leq n + r - 1 \]

所以,就转换成了原来的组合数的形式,即从 \(n + r - 1\) 个不同的数中选出 \(r\) 个数。

\(\text{Question 2}\)

Description

\(n\) 个不同的元素,从中选择 \(r\) 个,但是这 \(r\) 个数不能相邻,证明 : 方案数一共有 \(\dbinom{n - r + 1}{r}\) 种方案数。

Solution

首先,还是有这么 \(r\) 个数字 :

\[a_1,a_2,a_3,a_4,\cdots,a_r \]

所以,想要使得他们不相邻,选择的数必须满足 :

\[a_1 + 1 < a_2,a_2 + 1 < a_3,a_3 + 1 < a_4,\cdots \]

且 $$1 \leq a_1,a_r \leq n$$

\(b_i = a_i - i + 1\),可得 :

\[1 \leq b_1 < b_2 < b_3 < \cdots < b_r \leq n \]

所以,转化为从 \(n - r + 1\) 个不同的数中选择 \(r\) 个数的方案数。

posted @ 2021-04-11 20:13  Ti_Despairy  阅读(306)  评论(2)    收藏  举报