多重集合与多重集合上的排列组合
一、课上练习
编程练习
(暂无)
二、知识总结
✨ 核心思想
多重集合(Multiset)是允许元素重复出现的集合。与普通集合不同,多重集合中每个元素都有一个重复度(multiplicity),表示该元素出现的次数。多重集合上的排列组合问题是组合数学中的重要分支,在竞赛中频繁出现。
例如,多重集合 ${2 \cdot a, 3 \cdot b, 1 \cdot c}$ 表示元素 $a$ 出现 2 次,$b$ 出现 3 次,$c$ 出现 1 次。
✨ 多重集合的排列
全排列公式
设多重集合 $S = {n1 \cdot a1, n2 \cdot a2, \ldots, nk \cdot ak}$,其中元素总数 $n = n1 + n2 + \cdots + n_k$。
$S$ 的全排列数为多项式系数:
$$ \frac{n!}{n1! \cdot n2! \cdots n_k!} $$
直觉理解:先把 $n$ 个元素当作互不相同来排列,有 $n!$ 种排法。但同种元素之间的交换不产生新排列,所以要除去每种元素内部排列的数量。
经典示例
"MISSISSIPPI" 有多少种不同的排列?
- M: 1个, I: 4个, S: 4个, P: 2个,总共 11 个字母
- 排列数 = $\frac{11!}{1! \cdot 4! \cdot 4! \cdot 2!} = \frac{39916800}{1 \times 24 \times 24 \times 2} = 34650$
1#include <iostream>
2using namespace std;
3
4// 计算阶乘
5long long factorial(int n) {
6 long long res = 1;
7 for (int i = 2; i <= n; i++) {
8 res *= i;
9 }
10 return res;
11}
12
13// 计算多重集合排列数
14// cnt[i] 表示第 i 种元素的个数
15long long multisetPermutation(int cnt[], int k) {
16 int total = 0;
17 for (int i = 0; i < k; i++) {
18 total += cnt[i];
19 }
20 long long res = factorial(total);
21 for (int i = 0; i < k; i++) {
22 res /= factorial(cnt[i]);
23 }
24 return res;
25}
26
27int main() {
28 // MISSISSIPPI: M=1, I=4, S=4, P=2
29 int cnt[] = {1, 4, 4, 2};
30 cout << "MISSISSIPPI 的排列数: "
31 << multisetPermutation(cnt, 4) << endl;
32 return 0;
33}✨ 多重集合的组合
无上限的可重复组合(隔板法)
设有 $k$ 种元素,每种数量无限,从中选取 $r$ 个元素的组合数为:
$$ \binom{r + k - 1}{r} = \binom{r + k - 1}{k - 1} $$
这就是著名的隔板法(Stars and Bars)。其本质是将 $r$ 个相同的球放入 $k$ 个不同的盒子中的方案数——等价于在 $r$ 个球和 $k-1$ 个隔板组成的序列中选择隔板位置。
1#include <iostream>
2using namespace std;
3
4// 计算组合数 C(n, r)
5long long C(int n, int r) {
6 if (r > n - r) r = n - r; // 优化
7 long long res = 1;
8 for (int i = 0; i < r; i++) {
9 res = res * (n - i) / (i + 1);
10 }
11 return res;
12}
13
14int main() {
15 int k, r;
16 cout << "输入元素种类数 k 和选取个数 r: ";
17 cin >> k >> r;
18 // 从 k 种元素中可重复选 r 个的方案数
19 cout << "组合数: " << C(r + k - 1, r) << endl;
20 return 0;
21}有上限的组合:容斥原理
当第 $i$ 种元素最多选 $ni$ 个时,需要用容斥原理。设 $Ai$ 为第 $i$ 种元素选取超过 $n_i$ 个的方案集合:
$$ \text{答案} = \sum{T \subseteq {1,\ldots,k}} (-1)^{|T|} \binom{r - \sum{i \in T}(n_i+1) + k - 1}{k - 1} $$
1#include <iostream>
2#include <vector>
3using namespace std;
4
5long long C(int n, int r) {
6 if (r < 0 || n < r) return 0;
7 if (r > n - r) r = n - r;
8 long long res = 1;
9 for (int i = 0; i < r; i++) {
10 res = res * (n - i) / (i + 1);
11 }
12 return res;
13}
14
15// upper[i] 为第 i 种元素的上界
16long long multisetCombination(vector<int>& upper, int r) {
17 int k = upper.size();
18 long long ans = 0;
19 // 枚举所有子集进行容斥
20 for (int mask = 0; mask < (1 << k); mask++) {
21 int bits = __builtin_popcount(mask);
22 int subtract = 0;
23 for (int i = 0; i < k; i++) {
24 if (mask & (1 << i)) {
25 subtract += upper[i] + 1;
26 }
27 }
28 long long val = C(r - subtract + k - 1, k - 1);
29 if (bits % 2 == 0) ans += val;
30 else ans -= val;
31 }
32 return ans;
33}
34
35int main() {
36 // 示例:从 {3·a, 2·b, 1·c} 中选 4 个
37 vector<int> upper = {3, 2, 1};
38 int r = 4;
39 cout << "组合数: " << multisetCombination(upper, r) << endl;
40 // 输出: 6
41 return 0;
42}✨ 执行示例
示例 1:多重集合排列
多重集合 ${2 \cdot a, 2 \cdot b}$ 的全排列:
- 总数 = 4, 排列数 = $\frac{4!}{2! \cdot 2!} = 6$
- 具体排列:aabb, abab, abba, baab, baba, bbaa
示例 2:可重复组合
从 3 种水果(苹果、橘子、香蕉)中选 5 个,每种不限:
- $k = 3, r = 5$
- 方案数 = $\binom{7}{2} = 21$
示例 3:有上界的组合
从 ${3 \cdot a, 2 \cdot b, 1 \cdot c}$ 中选 4 个:
- 无限制:$\binom{6}{2} = 15$
- 减去 $a$ 超限($\geq 4$ 个):$\binom{2}{2} = 1$
- 减去 $b$ 超限($\geq 3$ 个):$\binom{3}{2} = 3$
- 减去 $c$ 超限($\geq 2$ 个):$\binom{4}{2} = 6$
- 加回同时 $b,c$ 超限:$\binom{0}{2} = 0$;其余交集项也为 0
- 最终答案 = $15 - 1 - 3 - 6 + 0 + 1 + 0 - 0 = 6$
✨ 解题步骤详解
- 识别问题类型:判断是排列还是组合,是否涉及重复元素
- 建立多重集合模型:确定每种元素及其重复度
- 选择公式:
- 全排列 → 多项式系数公式
- 无上限组合 → 隔板法
- 有上限组合 → 容斥原理
- 注意数据范围:大数需要取模(通常对 $10^9+7$)
- 验证:用小规模数据枚举验证公式正确性
✨ 常见错误
- 混淆集合与多重集合:忘记考虑元素重复,直接用普通排列公式
- 隔板法条件不满足:元素上限不够大时直接使用隔板法,未用容斥修正
- 容斥原理符号错误:奇数个集合的交集应该减去,偶数个应该加上
- 阶乘溢出:$20!$ 就已经超过
long long范围,需要取模或使用高精度 - 组合数计算中间溢出:先乘后除可能中间溢出,应交替乘除或用逆元
✨ 算法评价
| 问题类型 | 时间复杂度 | 说明 |
|---|---|---|
| 多重集合全排列 | $O(n)$ | 直接公式计算 |
| 可重复组合(隔板法) | $O(k)$ | 一次组合数计算 |
| 有上限的组合(容斥) | $O(2^k \cdot k)$ | 枚举所有子集 |
多重集合上的排列组合是组合数学的基础工具,掌握这些公式和技巧对于解决更复杂的计数问题(如生成函数、Burnside 引理等)至关重要。