想象你是一位社恐的派对主办者,为了让派对显得热闹又不超预算,你决定统计所有可能来的人。第一版名单是从“毽球群”拿到的一份200人名单,第二版名单是从“桌游群”拿到的一份150人名单。这两个群有相当多的人重叠——有些人是既踢毽球又玩桌游的“双重身份者”。如果你直接把两个名单的人数相加,就会发现那些同时出现在两份名单里的人被数了两次。于是你学到了一条生活经验:当两个圈子的名单叠加时,一定要“减掉”那些被重复点名的人。但如果遇到三个圈子,事情就开始变得有趣——三个圈子两两交叠的部分在减法中又被减多了,必须把它们“加回来”。 这个看似简单的“加加减减”的智慧,正是组合数学中赫赫有名的容斥原理。它像一个精明的记账员,通过交替进行“先加、再减、再加、再减……”的操作,精确地统计出一堆彼此重叠的集合中到底有多少个“独特的人”。本篇文章将带你从最直观的两个集合说起,一直走到错排问题、欧拉函数、满射函数计数等高级应用,你会发现,这个“派对记账法”竟然藏在计算机科学的各个角落。

岗位关联

容斥原理是组合计数的三大基本工具之一,掌握它意味着你能优雅处理各种“重叠”问题——从数据库查询优化、概率论推导到密码学中的欧拉定理,它是AI从业者数学直觉的重要一环。

目录
  1. 可视化演示
  2. 容斥原理基础
  3. 双集合特例
  4. 三集合推广
  5. 容斥通用形式
  6. 错排问题
  7. 欧拉函数
  8. 满射函数计数
  9. 应用与编程
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示容斥原理解决重叠集合计数问题,通过韦恩图、错排问题与欧拉函数三种模式理解交替加减的核心思想。

本节配有交互动效(进入视口后自动加载,约 3KB)。

容斥原理韦恩图 三集合交替加减计数 先加后减再补回交集 |A∪B∪C|=|A|+|B|+|C|-|A∩B|-|A∩C|-|B∩C|+|A∩B∩C|

容斥原理基础

派对主办者把几个圈子的名单叠在一起时,最头疼的事就是“重复”。每个人似乎总有那么几个身份重叠:既是毽球群元老,又是桌游群常客,还可能是烘焙比赛冠军。想要知道“不重不漏”的总人数,主办者必须先理解一个更底层的概念——集合运算:我们把每个圈子想象成一个“箱子”,箱子之间可以有重叠的部分,这份重叠就是交集(取两家名单里共同出现的名字),而所有箱子合在一起就是并集(只要出现在任何一个箱子里就算)。

在数学里,容斥原理关心的是并集的大小。两个集合的并集大小显然不是简单地把各自大小相加,因为交集部分被数了两次。用集合论的记号来表达,对于两个集合 A 和 B,“不重不漏”的并集计数方式为:

回到派对主办者的处境:把毽球群和桌游群合并时,他只需要先相加再减去重叠者,就得到了第一批稳重的来宾预测。这第一步,就是容斥原理这座大厦的第一块砖——两个箱子叠加时,去掉那些“两面派”。

∣A∪B∣=∣A∣+∣B∣−∣A∩B∣\displaystyle |A\cup B| = |A| + |B| - |A\cap B|
这个公式说的是:把 A 和 B 的人数相加,再减去两边名单里重叠的那些人,就得到“总共可能来的不同人数”。
  1. 符号转译|A∪B| 表示“A 和 B 并集的大小”,也就是两份名单各自的人数相加后去掉重复的最终结果。|A| 与 |B| 分别表示两份独立名单的人数。
    ∣A∪B∣=∣A∣+∣B∣−∣A∩B∣\displaystyle |A\cup B| = |A| + |B| - |A\cap B|
  2. 减去交集|A∩B| 表示两个名单里共同出现的那些名字。由于在 |A|+|B| 中交集被算到了两次,因此要减去一次。
    ∣A∣+∣B∣−∣A∩B∣\displaystyle |A| + |B| - |A\cap B|
  3. 对称美感容斥原理天然具有对称性——你从哪份名单开始“加”,最终结果都一致。正是这种对称性,让它成为组合数学的基石之一。
    ∣A∪B∣=∣A∪B∣\displaystyle |A\cup B| = |A\cup B|

双集合特例

主办者遇到的第一个简化情况,是只有毽球群(A)和桌游群(B)两个“箱子”。如果两个群完全没有交集(没有一个人同时加两个群),那么公式退化为简单的加法。但如果交集不为空,就必须执行“交叠即减”的操作。

这是最直观的特例:两个集合的并集计数,就是“各自的独立人数相加,再减去重叠的那一群人”。用图形来想,就是两个圆叠在一起,中间那一小块月牙形的重叠区被数了两次,所以我们把它挖掉一次。这个过程可以用一个简单的步骤表达:

更一般地,若存在三个或更多的集合,这个公式可以依次推广。比如三个集合 A、B、C 的并集大小是:

主办者手里有两份名单时,这个特例就像一把直尺,量出重叠,立即减去。双集合特例虽简单,却是理解整个容斥原理“加减交替”节奏的起点。

∣A∪B∣=∣A∣+∣B∣−∣A∩B∣\displaystyle |A\cup B| = |A| + |B| - |A\cap B|
∣A∪B∪C∣=∣A∣+∣B∣+∣C∣−∣A∩B∣−∣A∩C∣−∣B∩C∣+∣A∩B∩C∣\displaystyle |A\cup B\cup C| = |A|+|B|+|C| - |A\cap B| - |A\cap C| - |B\cap C| + |A\cap B\cap C|
三个集合相加后,两个集合的重叠被减掉了,但三个集合共同重叠的那部分被减了三次,需要“加回”一次,才能不多不少。
  1. 拆分理解先加三个集合各自的大小:|A|+|B|+|C|,此时某些元素被重复计数。
    ∣A∣+∣B∣+∣C∣\displaystyle |A| + |B| + |C|
  2. 减去成对交集任意两个集合的交集被数了两次,都要减去一次。因此减去 |A∩B|+|A∩C|+|B∩C|。
    −∣A∩B∣−∣A∩C∣−∣B∩C∣\displaystyle - |A\cap B| - |A\cap C| - |B\cap C|
  3. 加回三重交集一个元素若同时属于 A、B、C,在第一步被加了 3 次,第二步被减了 3 次,等于没被计入。因此要加上 |A∩B∩C| 进行修正。
    +∣A∩B∩C∣\displaystyle + |A\cap B\cap C|

三集合推广

现在派对主办者收到了第三份名单——烘焙群。三份名单两两重叠,甚至还有三个人同时出现在三份名单里的“超级活跃分子”。如果继续用“先加后减”的思路,主办者会遇到一个微妙的问题:当三个集合 A、B、C 都存在时,|A∩B∩C| 在第二轮减法中被减去了整整三次,而这部分人原本应当在最终并集中被保留。

这正是容斥原理的深层智慧:每一项修正都会引入新的“修正偏差”,于是需要交替地加减,直到最内层的所有交集都被处理干净。那三个集合都喜欢的人,在 |A|+|B|+|C| 中被算了 3 次,又在 |A∩B|+|A∩C|+|B∩C| 中被减了 3 次,于是净计数归零。为了把它救回来,我们必须加回一次 |A∩B∩C|。

三份名单的派对预测,让主办者体会到了“交替加减”的节奏——加一次,减一堆,再加回那个被误伤的“核心团体”。这种交替的意识,很快就会延伸到任意数量的集合。

∣A∪B∪C∣=∑i∣Ai∣−∑i<j∣Ai∩Aj∣+∣A1∩A2∩A3∣\displaystyle |A\cup B\cup C| = \sum_{i}|A_i| - \sum_{i<j}|A_i\cap A_j| + |A_1\cap A_2\cap A_3|
每个“所有集合的交集”在交替加减中的符号由它被交集的集合个数决定:奇数个集合时加号收尾,偶数个集合时减号收尾。
  1. 识别全部交集列出 A、B、C 三个集合的所有非空交集:三个单集合、三个双集合、一个三集合。
    A,  B,  C;A∩B,  A∩C,  B∩C;A∩B∩C\displaystyle A,\; B,\; C;\quad A\cap B,\; A\cap C,\; B\cap C;\quad A\cap B\cap C
  2. 单项符号规律交集中每个元素如果被包含在 k 个集合中,则在并集计数过程中会经历 k 次“加成”与若干次“扣除”。最终符号为 (-1)^(k+1)。
    (−1)k+1  ∑∣S∣=k∣⋂i∈SAi∣\displaystyle (-1)^{k+1}\; \sum_{|S|=k} \left|\bigcap_{i\in S} A_i\right|
  3. 从内向外累加对 k=1 到 k=3 逐层相加,即先加单个集合,再减两两交集,最后加三交集。
    ∑k=13(−1)k+1∑∣S∣=k∣⋂i∈SAi∣\displaystyle \sum_{k=1}^{3} (-1)^{k+1} \sum_{|S|=k} \left|\bigcap_{i\in S}A_i\right|

容斥通用形式

当派对主办者意识到自己可能拥有几十个不同的部门、社群或客户群时,双集合和三集合的手工计算就不再可行。他需要的是一条“统一公式”,不管有多少个箱子,都能按同样的节奏算到底。

容斥原理的通用形式正是为这种场景而生:设我们有 n 个集合 A₁, A₂, …, Aₙ,它们的并集大小可以写成对所有非空子集 S 的交替求和。每一项的符号由子集的大小 |S| 决定:奇数个集合取正,偶数个集合取负。最后的收尾项往往是所有集合的交集(|S| = n 时),因此整个序列以一个清晰的符号结束。

这就像主办者终于请到了一位专业的“派对统计师”,他不再需要蹲在地上一项一项地核对,只需把每个子集对应的重叠人数按“奇正偶负”规则排成一列,一加一减,答案自然浮现。

∣⋃i=1nAi∣=∑S⊆{1,…,n}S≠∅(−1)∣S∣+1∣⋂i∈SAi∣\displaystyle \left|\bigcup_{i=1}^{n} A_i\right| = \sum_{\substack{S\subseteq \{1,\dots,n\}\\ S\neq \varnothing}} (-1)^{|S|+1} \left|\bigcap_{i\in S} A_i\right|
这是容斥原理的“完整形态”:不看漏任何一个子集,符号的正负由该子集的大小奇偶决定——奇正偶负,依次交替。
  1. 子集遍历公式要求遍历所有非空下标集合 S(共有 2^n - 1 个),比如 {1} {2} {1,2} …… 一直到 {1,2,...,n}。
    S⊆{1,2,…,n},  S≠∅\displaystyle S\subseteq \{1,2,\dots,n\},\; S\neq \varnothing
  2. 交集大小对每个 S,求这些集合的交集大小 |∩A_i|——也就是同时满足 S 中所有条件的人数。
    ∣⋂i∈SAi∣\displaystyle \left|\bigcap_{i\in S} A_i\right|
  3. 奇偶定符号(-1)^{|S|+1} 意味着当 S 含 1、3、5…个集合时,该子集项取正号;当 S 含 2、4、6…个集合时取负号。
    (−1)∣S∣+1{+,∣S∣ 为奇数−,∣S∣ 为偶数\displaystyle (-1)^{|S|+1} \begin{cases} +, & |S|\text{ 为奇数} \\ -, & |S|\text{ 为偶数}\end{cases}

错排问题

派对主办者想把礼物随机分给来宾们,他的要求很特别:每个人都不能拿到写有自己名字的礼物。如果 n 个朋友的礼物被随机打乱,究竟有多少种分配方式能保证“人人拿到的都是别人的礼物”?这种“完全错位”的排列叫做错排。主办者凭直觉意识到,这个问题的答案藏在容斥原理的“排除法”里——先把所有排列都算上,再逐步排除那些有人拿到自己礼物的坏情况。

设 n 个元素的全部排列数为 n!。我们用集合 Aᵢ 表示“第 i 个人拿到了自己的礼物”这一坏事件。那么“没有人拿到自己礼物”的全错排数 Dₙ,就是从总数中排除掉所有 Aᵢ 的并集。这正是容斥原理的经典战场:算出 Aᵢ 的并集大小,再从 n! 中减去,公式推导得到:

派对主办者惊喜地发现,无论来宾有多少,只要随机打乱礼物,保证所有人都拿到别人礼物的概率总是稳定在 36.8% 左右。这份“乱中有序”的奇妙常数,就是错排问题赠送给容斥原理的勋章。

Dn=n!∑k=0n(−1)kk!\displaystyle D_n = n!\sum_{k=0}^{n} \frac{(-1)^k}{k!}
错排公式:全部排列数量 × 一个交替加减的修正系数。当 n 足够大时,这个系数趋近于 1/e,大约 0.368。
  1. 总量起点n 个对象的所有排列数是 n!。
    n!=n×(n−1)×⋯×1\displaystyle n! = n \times (n-1) \times \cdots \times 1
  2. 容斥加权在 n! 基础上,用 (-1)^k/(k!) 作为权重对 k 求和。k 代表“至少 k 个固定点”的修正。
    ∑k=0n(−1)kk!=1−1+12!−13!+⋯\displaystyle \sum_{k=0}^{n} \frac{(-1)^k}{k!} = 1 - 1 + \frac{1}{2!} - \frac{1}{3!} + \cdots
  3. 极限常数当 n 趋向无穷大时,这个交错级数收敛到指数函数 e⁻¹,即 1/e ≈ 0.3679。
    lim⁡n→∞Dnn!=1e≈0.3679\displaystyle \lim_{n\to\infty} \frac{D_n}{n!} = \frac{1}{e} \approx 0.3679

欧拉函数

当派对名单上的所有人都换上了从 1 到 n 的数字编号,主办者想统计其中与 n 互素的编号个数——这些编号与 n 没有大于 1 的公因数。这个统计量在数论中叫做欧拉函数 φ(n),它也是密码学中 RSA 算法的重要构件。容斥原理在这里扮演的角色,是从 1 到 n 的完整列表里,逐一剔除那些与 n 共享质因子的“坏编号”。

例如 n = 12,质因子是 2 和 3。我们想统计 1 到 12 中既不能被 2 整除、也不能被 3 整除的数。先用容斥原理:12 中所有数减去 2 的倍数,再减去 3 的倍数,最后加回“既是 2 的倍数又是 3 的倍数”(即 6 的倍数):

派对主办者看着这份编号表,突然觉得容斥原理不只是记人数的工具——它像一台筛子,把“不想要的分子”一层层筛走,只保留那些与 n 互素的忠实宾客。这也正是欧拉函数被称为“筛出来的计数”的原因。

ϕ(n)=n∏p∣n(1−1p)\displaystyle \phi(n) = n \prod_{p\mid n}\left(1 - \frac{1}{p}\right)
欧拉函数 φ(n) 等于 n 乘以所有质因子 (1 − 1/p) 的连乘积。它通过容斥原理,优雅地筛掉了所有与 n 共享质因子的数字。
  1. 质因子提取列出 n 的所有不同质因子 p₁, p₂, …, pₖ(如 12 的质因子为 2、3)。
    n=p1a1p2a2⋯pkak\displaystyle n = p_1^{a_1} p_2^{a_2} \cdots p_k^{a_k}
  2. 逐项筛选从 n 中减去所有被 p₁ 整除的数,再减所有被 p₂ 整除的数……相当于对每个质因子做一次“去除处理”。
    n−np1−np2+np1p2+⋯\displaystyle n - \frac{n}{p_1} - \frac{n}{p_2} + \frac{n}{p_1 p_2} + \cdots
  3. 连乘积压缩把上面的加加减减用连乘积表达。每个质因子贡献一个因子 (1 − 1/p)。
    n∏p∣n(1−1p)\displaystyle n \prod_{p\mid n}\left(1 - \frac{1}{p}\right)

满射函数计数

如果派对主办者想把 m 件不同的礼物分给 n 个朋友,规则是每位朋友至少得到一件礼物,那么他要统计的就是“满射函数”的数量——每个收到礼物的人都不能空手而归。这是一个更复杂的计数问题,因为礼物是互不相同的,而每位朋友的“名额”没有上限。

满射函数计数的本质,是从所有 m 个礼物到 n 个朋友的函数总数 nᵐ 中,剔除掉那些“至少有一位朋友没有收到礼物”的情况。令 Eᵢ 表示“第 i 位朋友没收到任何礼物”这一坏事件。用容斥原理,逐个排除坏事件,得到:

主办者看着这个公式,回想起自己从“两份名单”一路走到“分礼物”的历程。容斥原理的本质始终如一:从一个“粗略的全体”出发,用交替加减的方式,精确剥离掉所有不需要的情况,直到每一个细节都恰到好处。

Surj(m,n)=∑k=0n(−1)k(nk)(n−k)m\displaystyle \mathrm{Surj}(m,n) = \sum_{k=0}^{n} (-1)^k \binom{n}{k} (n-k)^m
满射计数的思想:全部映射总数,减去有人空手的映射数,加回有两人空手的情况……直到处理完所有“空手组合”。
  1. 总映射数m 个礼物各可送给 n 个朋友中的任意一个,因此总数为 n^m。
    nm=n×n×⋯×n  (m 次)\displaystyle n^m = n \times n \times \cdots \times n \;(m\text{ 次})
  2. 选空手者从 n 位朋友中选 k 位让他们“空手”(收不到任何礼物),方式数为 C(n,k)。
    (nk)=n!k!(n−k)!\displaystyle \binom{n}{k} = \frac{n!}{k!(n-k)!}
  3. 剩余分配剩下 n−k 位朋友接收全部 m 个礼物,共有 (n-k)^m 种方法。
    (n−k)m\displaystyle (n-k)^m
  4. 符号修正k 从 0 到 n 交替加减。k=0 是总映射数,k=1 减去至少一人空手,k=2 加回两人空手,依此类推。
    ∑k=0n(−1)k(nk)(n−k)m\displaystyle \sum_{k=0}^{n} (-1)^k \binom{n}{k} (n-k)^m

应用与编程

容斥原理从派对名单中诞生,却绝不止于派对。在计算机科学里,它到处“兼职”:数据库查询中统计满足多个条件之一的行数时,可以用容斥原理去重;概率论中计算多个事件至少一个发生的概率时,同样可以套用容斥的框架;而欧拉函数更是 RSA 加密算法中密钥生成的核心步骤。主办者把记账经验写成代码,就是希望让计算机也学会这种“先加后减”的智慧。

编程实现容斥原理最直接的方式,是用二进制枚举所有非空子集。比如对于 n 个集合,用一个整数的二进制位表示“选择了哪些集合”,然后按奇偶符号累加交集大小。这是一个广泛应用于算法竞赛、数据去重和概率模拟的可复用套路:

派对主办者终于释然:无论是手动记账还是编写程序,容斥原理的思想始终如一——先把所有可能都纳入,再一重重减去重复,最后把减过头的救回来。这就是“计数不漏”的终极心法。

result=∑mask=12n−1(−1)popcount(mask)+1∣⋂i∈maskAi∣\displaystyle \text{result} = \sum_{mask=1}^{2^n-1} (-1)^{\text{popcount}(mask)+1} \left| \bigcap_{i \in mask} A_i \right|
程序里的 mask 就像一枚枚派对邀请函上的邮戳——哪些圈子被盖了章,就参与本次交集计算。奇偶邮戳决定加减方向。
  1. 枚举子集mask 从 1 到 2^n - 1,覆盖所有非空子集。比如 n=3 时 mask=1,2,3,…,7。
    1≤mask≤2n−1\displaystyle 1 \le mask \le 2^n - 1
  2. 计算交集对于每个 mask,找出被选中的集合(对应二进制位为 1 的那些),求它们的交集大小。
    ∣⋂i:  biti(mask)=1Ai∣\displaystyle \left| \bigcap_{i:\; bit_i(mask)=1} A_i \right|
  3. 奇偶定号popcount(mask) 统计 mask 中 1 的个数;奇数为正,偶数为负。
    (−1)popcount(mask)+1\displaystyle (-1)^{\text{popcount}(mask)+1}

AI 应用场景

数据库多条件统计

阶段:数据预处理与特征工程

解决的问题:在用户行为分析中,常需统计满足至少一个兴趣标签的用户数。传统做法是多次查询再合并,但标签互相重叠导致结果虚高。AI 系统需要精确的“唯一用户数”来训练推荐模型,容斥原理可以保证每个用户只被计数一次。

具体案例:某推荐系统有两个兴趣标签组“科技”和“游戏”,分别命中 5 万和 3.8 万用户,两标签交集 1.2 万。通过容斥公式 5万 + 3.8万 − 1.2万 = 7.6万,得到精确的“至少对一个标签感兴趣”的用户数,训练集大小因此更可靠。

概率计算与事件模拟

阶段:模型评估与不确定性估计

解决的问题:在评估 AI 模型时,常需计算多个失败条件同时出现的概率。如果简单相加各失败率,会高估风险;使用容斥原理的交替加减,可以精确估计“至少一个失败条件发生”的总体概率。

具体案例:自动驾驶感知模块有三个可能导致误判的场景:夜间、雨天、强光,单场景失败率分别为 0.05、0.03、0.02,两两重合 0.005,三重合 0.001。容斥计算得到总失败率 ≈ 0.097−0.011+0.001=0.087,比直接相加的 0.10 更准确地反映了真实风险。

密码学与欧拉函数

阶段:安全通信与加密模块

解决的问题:RSA 加密中需要计算 φ(n) 来决定公私钥。传统试除 1 到 n 逐一检查互素,效率极低。使用容斥原理推导出的欧拉公式,可以在秒级内完成大数的 φ(n) 计算,为加密算法高效生成密钥对。

具体案例:系统生成 RSA 密钥时,选择两个大素数 p、q,令 n = p×q,则 φ(n) = (p−1)(q−1)。该公式正是容斥原理在 n 只有两个质因子时的特例。它让密钥生成从分钟级提速到毫秒级。

Python 演示

python87 行
# -*- coding: utf-8 -*-"""容斥原理演示:1. 通用并集大小计算(二进制枚举子集)2. 错排数 D_n 计算3. 欧拉函数 phi(n) 计算"""from math import gcd, prod  def union_size_intersection(func_intersection, n):    """通用容斥原理:并集大小 = 交替加减所有子集交集大小    func_intersection(mask) 返回下标集合 mask 的交集大小    """    total = 0    for mask in range(1, 1 << n):        bits = bin(mask).count("1")        inter = func_intersection(mask)        if bits % 2 == 1:            total += inter        else:            total -= inter    return total  def derangement(n):    """错排数:D_n = n! * sum_{k=0}^{n} (-1)^k / k!"""    fact = 1    for i in range(2, n + 1):        fact *= i    d = 0    for k in range(n + 1):        term = fact        for i in range(2, k + 1):            term //= i        d += term if k % 2 == 0 else -term    return d  def euler_phi(n):    """欧拉函数 phi(n):与 n 互素的正整数个数    使用容斥原理: phi(n) = n * prod(1 - 1/p)    """    result = n    temp = n    p = 2    while p * p <= temp:        if temp % p == 0:            while temp % p == 0:                temp //= p            result -= result // p        p += 1    if temp > 1:        result -= result // temp    return result  def disjoint_union_example():    """用具体集合验证容斥原理(数字集合)"""    A = {1, 2, 3, 4, 5}    B = {4, 5, 6, 7}    C = {5, 7, 8, 9}    union = A | B | C    # 手工套用容斥公式    calc = len(A) + len(B) + len(C) \        - len(A & B) - len(A & C) - len(B & C) \        + len(A & B & C)    print("实际并集大小:", len(union))    print("容斥计算并集:", calc)    assert len(union) == calc  if __name__ == "__main__":    # 直观验证    disjoint_union_example()     # 错排数    for n in range(1, 8):        print(f"D_{n} = {derangement(n)}")     # 欧拉函数    for n in [6, 12, 15, 30]:        print(f"phi({n}) = {euler_phi(n)}")        # 暴力验证互素个数        brute = sum(1 for x in range(1, n + 1) if gcd(x, n) == 1)        assert brute == euler_phi(n)    print("所有断言通过,容斥原理实现正确!")
目的:用 Python 实现容斥原理的三个经典应用——通用并集计数、错排数与欧拉函数,并通过断言验证结果正确。
  1. for mask in range(1, 1 << n):用二进制枚举所有非空子集,mask 的每一位代表是否选择对应集合。
  2. if bits % 2 == 1: total += inter奇数个集合取正号,偶数个取负号,这就是容斥的“交替加减”。
  3. fact预计算 n!,后续每一项都从它出发除以 k!,避免浮点误差。
  4. result -= result // p等价于 result *= (1 - 1/p),用整数运算消除分数,是容斥在欧拉函数中的实现。
  5. while p * p <= temp:只需遍历到 sqrt(n) 即可提取所有质因子,体现算法效率。
  6. assert len(union) == calc用实际集合的并集验证容斥公式计算正确。

常见误区

误区我算三个集合并集时,直接用 `|A|+|B|+|C|−|A∩B|−|A∩C|−|B∩C|`,不加回三重交集,感觉减得越干净越好。

正解当存在同时属于三个集合的元素时,它们在三个单集合中总计被加 3 次,又在三个两两交集中被减 3 次,净计数为 0。必须加回 |A∩B∩C| 才算完整。可以想象三张名单里那个三个群都加入的“全勤王”,如果不把他加回来,他就被彻底遗忘了。

为何常见:人脑天然倾向“加法计算、减法修正”,却忽略了修正本身也可能带来新的偏差。这与最初“减少重复”的直觉相冲突,导致初学者忽视三重叠加项。

误区我觉得容斥原理就是 `|A∪B|=|A|+|B|−|A∩B|`,超过两个集合就没法用,或者只能两两套用。

正解容斥原理的真正威力在于通用形式——任意 n 个集合都可以通过“枚举所有非空子集,按奇偶定符号”来计算并集。只要把每个集合看作一个“坏事件”,就能用同样的交替加减法处理错排、欧拉函数等复杂计数问题。认为只能用于双集合,就像是学会了一道菜就以为厨师只会做这道菜。

为何常见:教材通常先讲双集例子再推广,但学生常把最简形式当作全部;另外日常直觉中很少遇到三个以上圈子重叠的场景,所以很难自发推广到 n 个集合。

误区求 n 个人的错排数时,我觉得只要用 `n! − 1` 就行了,因为“至少一个拿到自己礼物”的情况似乎只有一种。

正解“至少一个人拿到自己礼物”的事件远不止一种,因为哪个人拿到自己的礼物有多种选择,而且还有可能两个人同时拿到自己的礼物。错排数必须用容斥原理把所有这些情况都剔除干净,公式 D_n = n!∑(−1)^k/k! 就是“先排除单个固定点、加回双固定点、再减三固定点…”的完整记账。

为何常见:小规模 n=2 或 3 时,"n!−1"恰巧碰对答案,让人误以为规律简单;实际 n=4 时 D₄=9,而 4!−1=23,相差悬殊,直觉被小样本误导。

误区我理解 φ(n) = n∏(1−1/p) 就是把 n 一直除以质因子,不太明白这和容斥原理有什么关系,感觉像独立的数论公式。

正解欧拉函数本质是“从 1 到 n 中排除所有被任意质因子整除的数”。比如 φ(12) 要排除被 2 整除的和被 3 整除的,但 6 的倍数(如 6)被排除了两次,所以要加回一次——这正是双集合容斥。连乘积形式只是把“交替加减”压缩成简洁的乘法外壳。

为何常见:最终公式过于简洁,隐藏了推导过程中层层筛除的细节;学生记住结论却丢失了思想来源,导致遇到变体问题(如求 n 以内且不被任何质因子整除的数的个数)时不会迁移。

面试题

Q1. 容斥原理中,两个集合 A 和 B 的并集大小为 |A|=10、|B|=8、|A∩B|=3,则 |A∪B| 等于多少?

单选题 · easy

  • A.21
  • B.18
  • C.15← 正确答案
  • D.5

答案:C。根据双集合容斥公式 |A∪B| = 10+8−3 = 15。选项 C 正确。选项 B 是把 3 当成了不相交的第三集合;选项 A 是直接相加忘记减交集;选项 D 是误把交集当并集。可以把 10 和 8 想象成两份派对名单,3 人是两边都出现的“双重身份者”,减去一次后才是真正会来的不同人数。

Q2. 三个集合 A、B、C 的并集计算中,为什么最后要加回 |A∩B∩C|?

单选题 · easy

  • A.因为三个集合的交集在第一步没有被加上
  • B.因为三重交集在单集合加总中被加了 3 次,又在两两交集中被减了 3 次,净计数为 0← 正确答案
  • C.因为交集永远是正数
  • D.因为容斥原理规定必须凑成偶数项

答案:B。一个元素同时属于 A、B、C 时,首先在 |A|+|B|+|C| 中被加了 3 次,然后在减去三个两两交集时被减了 3 次,最终等于没被统计。为了不漏掉它,必须加回一次三重交集。这就像三个群都加入的“全勤王”,先被数了三遍,又被删了三遍,最后得靠“加回”救回来。

Q3. 关于错排问题 Dₙ,下列说法正确的有?

多选题 · mid

  • A.Dₙ 表示 n 个人都拿到别人礼物的分配方式数← 正确答案
  • B.Dₙ = n!∑(−1)ᵏ/k!← 正确答案
  • C.当 n 足够大时,Dₙ/n! 逼近 1/e← 正确答案
  • D.Dₙ 等于 n!−1

答案:ABC。A 是错排的定义——人人都拿别人的礼物;B 是容斥原理推导出的标准公式;C 源于级数展开,n→∞ 时 Dₙ/n!→e⁻¹≈0.368;D 错误,n=4 时 D₄=9 而 4!−1=23,说明“至少一人拿自己礼物”的情况远不止一种,必须用容斥细算。

Q4. 用容斥原理计算 1 到 30 中与 30 互素的整数个数,φ(30) 等于多少?

单选题 · mid

  • A.8← 正确答案
  • B.10
  • C.12
  • D.16

答案:A。30 = 2×3×5,容斥计算为 30 − 30/2 − 30/3 − 30/5 + 30/(2×3) + 30/(2×5) + 30/(3×5) − 30/(2×3×5) = 30−15−10−6+5+3+2−1=8。也可以直接用公式 30×(1−1/2)×(1−1/3)×(1−1/5)=8。选项 B 是忘了减 30/5 的结果;选项 C 少算了个质因子;选项 D 是单纯减去除 2、3、5 的倍数但没加回重叠。

Q5. 关于容斥原理的通用形式,以下说法正确的是?

多选题 · mid

  • A.需要对所有非空子集求和← 正确答案
  • B.子集大小为奇数时取正号,偶数时取负号← 正确答案
  • C.最内层项一定是所有集合的交集← 正确答案
  • D.通用形式只适用于两个集合

答案:ABC。通用形式的求和范围是所有非空子集(A 对);符号由子集大小奇偶决定,奇正偶负(B 对);当子集大小为 n 时即所有集合的交集,作为收尾项(C 对);D 错在把通用形式限制为双集合,实际上它处理任意 n 个集合,就像记账员管着几十本名单依然套用同一套加减法则。

Q6. 满射函数计数问题中,将 m 件不同礼物分给 n 个朋友且每位朋友至少一件,如果用容斥原理,第一步应计算什么?

单选题 · mid

  • A.所有分配方式总数 nᵐ← 正确答案
  • B.恰好一位朋友空手的方式数
  • C.每人恰好一件的方式数
  • D.所有朋友都不空手的方式数

答案:A。容斥原理的起点是“全集合”——所有可能的分配方式 nᵐ,然后在此基础上排除“至少一人空手”的坏事件。选项 B、C、D 都是后续修正或最终目标,不是第一步。就像统计派对人数时,先把所有圈子名单相加,再从里面剔除重复。

Q7. 已知 |A|=20, |B|=15, |C|=12, 两两交集分别为 5、6、4,三重交集为 2,则 |A∪B∪C| 为多少?

单选题 · hard

  • A.44
  • B.40
  • C.34
  • D.36← 正确答案

答案:D。代入三集合容斥公式:20+15+12 − 5 − 6 − 4 + 2 = 34。选项 C 正确。选项 A 是 20+15+12−5−6−4 忘记加回三重交集的结果;选项 B 误把三重交集减掉;选项 D 是计算错误。这里可以想象三个社交群,两两重叠的人被各减一次,但三个群都加入的 2 人被减了三次,最后加回一次才准确。

Q8. 关于欧拉函数 φ(n),以下哪些说法是正确的?

多选题 · hard

  • A.φ(n) 计算的是 1 到 n 中与 n 互素的整数个数← 正确答案
  • B.若 n = p×q 且 p、q 为不同素数,则 φ(n) = (p−1)(q−1)← 正确答案
  • C.φ(n) 的容斥推导过程涉及排除被各质因子整除的数← 正确答案
  • D.φ(12) = 4← 正确答案

答案:ABCD。A 是欧拉函数的标准定义。B 是质因数分解后 φ(n)=(p−1)(q−1) 的直接结论。C 描述其容斥本质——先减能被 p₁ 整除的数、再减 p₂ 的倍数、加回 p₁p₂ 的倍数,正是交替加减的筛选过程。D 中 12=2²×3,1 到 12 中与 12 互素的是 1、5、7、11,共 4 个,成立。

Q9. 错排问题中 D₅ 的值是多少?

单选题 · hard

  • A.24
  • B.44← 正确答案
  • C.48
  • D.76

答案:B。D₅ = 5!(1 − 1 + 1/2! − 1/3! + 1/4! − 1/5!) = 120 − 120 + 60 − 20 + 5 − 1 = 44。选项 B 正确。选项 A 是 4!;选项 C 是 5!−5!+60−20 的错误中间结果;选项 D 是计算时忘减最后一项。错排问题就像五个人交换礼物但谁都不能拿自己的,容斥原理逐层修正得到 44 种。

Q10. 容斥原理的典型应用场景包括?

多选题 · easy

  • A.统计多个标签用户去重后的总数← 正确答案
  • B.计算错排问题← 正确答案
  • C.计算欧拉函数← 正确答案
  • D.计算两个集合的笛卡尔积大小

答案:ABC。A 是数据库/推荐系统中的常见应用,多个标签重叠需去重;B 错排数的推导正是容斥原理的经典案例;C 欧拉函数通过排除质因子的倍数来计算互素个数,本质是容斥;D 的笛卡尔积大小是 |A|×|B|,与并集无关,不需要容斥原理。