我们想要求出的是所有性质都不满足的元素,所以满足某条性质就是件“坏事”。所有满足某条性质的元素的集合就是一个“坏集合”。相反,所有性质都不满足的元素就是“好的”。坏集合以及坏集合的交是容易计算的。我们想要通过一种方法把各种坏集合的交集加加减减来求出好集合的大小,这就是容斥原理。
容斥原理
在1到100的整数中,我们想数出“既不是2的倍数,也不是3的倍数,也不是5的倍数”的数的个数。我们发现,我们很容易数出2的倍数的个数、3的倍数的个数、5的倍数的个数,但是由于这些个数之间有重叠,所以如果把100减去这些个数的总和,就多减了一点。为了弥补多减的部分,我们依次把同时是2和3的倍数的、同时是2和5的倍数的、同时是3和5的倍数的给加回来。但接着又发现加回来的太多了。如果这时我们再把同时是2、3、5的倍数的数的个数给减掉,就恰好得到了正确的答案。
以上这个过程可以在韦恩图上比较清晰的表达出来。把这个问题描述的对象抽象出来,我们发现我们在解决这样一个问题:我们的元素有一些性质,我们想要统计“不满足所有这些性质”的元素个数,但是这不容易计算。然而,“满足”其中几条性质的元素个数却是容易计算的。为了叙述方便,由于我们想要求出的是所有性质都不满足的元素,所以满足某条性质就是件“坏事”。所有满足某条性质的元素的集合就是一个“坏集合”。相反,所有性质都不满足的元素就是“好的”。坏集合以及坏集合的交是容易计算的。我们想要通过一种方法把各种坏集合的交集加加减减来求出好集合的大小,这就是容斥原理。
在给定的一个大集合U中,设坏集合为A1,⋯,Am。已知|k⋂i=1Api|是容易计算的。目标就是计算出|m⋂i=1¯Ai|。
为了方便理解,我们引入记号。把坏集合的集合记为A={A1,⋯,Am}。设J是A的一个子集,即J是某几个坏集合组成的集合。定义
N=(J)=|{x∈U|∀Ai∈J,x∈Ai且∀Ai∈A∖J,x∉Ai}|
即N=(J)表示所有只满足J种坏性质而不满足其它任何坏性质的元素。比如在刚才的例子中,N=({2,5})就表示所有又是2的倍数又是5的倍数,但不是3的倍数的数。注意到,N=(∅)就表示不满足任何坏性质的元素,也就是“好元素”,就是我们要的答案。
N=(J)往往是难求的。而根据我们的设定,N≥(J)=|{x∈U|∀Ai∈J,x∈Ai}|,即所有满足了这些坏性质别的啥都不管的元素,是容易求出的。其中N≥(∅)=U。
容斥原理就是指出:
N=(∅)=∑J⊆A(−1)|J|N≥(J)
根据定义,等式左侧即为|m⋂i=1¯Ai|,而右侧可以改写为m∑j=0(−1)j∑J∈(Aj)N≥(J)。我们考虑每个元素的贡献。如果x∈N=(∅),将在左侧贡献1。这意味着x不属于任何坏集合,因此它在右侧只会在j=0时(即J=∅)时被统计到,刚好在N≥(∅)中贡献1次;如果x∉N=(∅),那么左侧贡献0。x出现在了某个或某几个坏集合中,不妨设出现在Ai1,⋯,Ait中,那么当且仅当J⊆{Ai1,⋯,Ait}时(包括空集)右侧才会贡献,总的贡献恰好为t∑j=0(−1)j(tj)=t∑j=0(tj)(−1)j(1)t−j=(−1+1)t=0。证毕。
对于N=中不为空集的情况,我们可以证明更一般的容斥原理:
N=(S)=∑J:S⊆J⊆A(−1)|J|−|S|N≥(J)
∀x∈N=(S),则左边贡献1。等式右边当且仅当x∈N≥(J)才产生贡献,这要求J⊆S。而S⊆J,因此必须有S=J。因此等式右边也恰好贡献1。
如果x∉N=(S),则左边贡献0。等式右边,如果S⊊A,那么对于所有等式中的J,N≥(J)都产生贡献。于是由∑S⊆J⊆A(−1)|J|−|S|N≥(J)=|A|∑j=|S|(−1)j−|S|∑S⊆J∈(Aj)N≥(J),右侧贡献|A|−|S|∑j=0(−1)j(|A|−|S|j)=(−1+1)|A|−|S|=0。而如果本身就有S=A,则右侧等式直接可以写作N≥(A)=N=(A),等式成立。
证毕。
应用
错位排列通项
一个排列是错位排列当且仅当ai≠i恒成立。我们把ai=i看作坏事件Ai。那么容易求出N≥({Ai1,⋯,Ait})=(n−t)!
代入容斥原理的公式,N=(∅)=n∑j=0(−1)j∑J∈(Aj)(n−j)!=n∑j=0(−1)j(nj)(n−j)!
第二类斯特林数通项
k个球n个箱子,球不同箱子不同至少一个,答案是{kn}n!。而不从分组的角度而从球的角度看,相当于求从[k]到[n]的满射的个数。那么利用容斥原理,把i没有被射到看作坏事件Ai,那么有N≥({Ai1,⋯,Ait})=(n−t)k。于是有N=(∅)=n∑j=0(−1)j∑J∈(Aj)(n−j)k=n∑j=0(−1)j(nj)(n−j)k。
综上有{kn}=1n!n∑j=0(−1)j(nj)(n−j)k
二分图完美匹配
通过邻接矩阵,完美匹配的方案数可以写成积和式permA=∑σn∏i=1Ai,σ(i)。复杂度O(n!⋅n)
利用容斥原理,把右侧的节点i没有被匹配上看作坏事件Ai,那么N≥(J)=n∏i=1(∑j∈[n]∖JAi,j)。于是有N=(∅)=∑J∈A(−1)|J|n∏i=1(∑j∈[n]∖JAi,j)。复杂度由枚举全排列转化为了枚举子集,复杂度O(2n⋅n)。可以证明,在渐进意义下这是最优复杂度了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?