前言
n球放入m盒是一个经典的组合数学问题的理解范式。很多问题可以看作是不同条件的n球入m盒问题。
关于这个问题的答案,网络上也已有很多前辈整理。但我个人没有在一篇文章里完全看懂看明白的,于是决定自己整理一份结合多文章内容的理解。
基本问题描述
有n个不同的球,m个不同的盒子。将这些球放入盒子中,允许有空盒子。共有多少种放法?
根据条件的不同,这个问题至少有8种基础变体:球是否相同,盒子是否相同,是否允许有空盒子。
还有更复杂的,例如规定每个盒子最多放s个球,等等,在此不做讨论。
以下是对8个基础变体的由简单到复杂的解答:
1、球不同,盒不同,允许空盒
对于第1个球,有m个盒子可以选择;第2个球,有m个盒子可以选择……以此类推,共有n个球。
因此:
方案数量 = m * m * m * …… = mn
2、球同,盒不同,不允许空盒
组合问题的经典范式之一。一个经典的解决方法是隔板法:
对于n个球,排成一行。每个球之间都有空隙,总共有n-1个空隙。
我们只需要在这些空隙里,选m-1个空隙,插入隔板,就会将n个球分成m份。因为不存在“一个空隙里插两个隔板”这种事情,所以每份都至少有1个球,将球装盒即可。
于是问题转变成:从n-1个不同元素(元素变成了“空隙”,而不是球了)中选取m-1个元素并成一组(即一个解决方案),总共有多少种选法。这符合“组合数”这一数学概念的定义。
因此:
方案数量 = C(n - 1, m - 1)
C是组合数符号,组合数C(n, m) = n! / (m! * (n-m)!)
3、球同,盒不同,允许空盒
我们回头看看2。在2的条件下,我们可以给出另一个处理方案:
如果我们给每个盒子都放上1个球,那么剩下的n-m个球放入m个盒子里,就不需要管是否有空盒了(因为已经事先给每个盒子都放了一个球)。
如果用T(n-m, m)表示加粗部分的方案数量,那么2的答案 = 1(先每个盒子一个球,只有1种放法) * T(n-m, m)。
那么显然,T(n-m, m)就是情况3,只不过是n-m个球入m个盒,而不是我们要求的n个球入m个盒。
那么我们只需要把2情况里,球的总数量变成n+m个,在上述方案里,就会变成“如果我们给每个盒子都放上1个球,那么剩下的n + m - m = n个球放入m个盒子里,就不需要管是否有空盒了”
因此,方案数量在数值上是等于2里,把n替换成n+m的:
方案数量 = C(n + m - 1, m - 1)
4、球不同,盒同,不允许空盒
在这之后的所有情况都是包含一种动态规划的思想,即:要求nm球盒问题,就去思考答案可以由什么情况递推过来。
在情况4,乃至所有的“球不同”情况,都是以“第二类斯特林数”为基础,在其上做变形。
情况4即第二类斯特林数的定义:将n个不同的元素划分为m个集合(不考虑顺序,即“盒同”),不允许空集,划分的方案数量。记为S(n, m)。
那么思考递推式:
当前面n - 1个球都放完盒子了,手上还剩最后一个球,它要放哪里?
1、前面n - 1个球用了m - 1个盒子。那么不允许空盒,最后一个球必须放进剩下的那1个空盒子里,对于这一个球,只有这1种放法。方案数量则取决于前面n-1个球放m-1个盒子里且不空盒的数量,这又回到了问题本身,它的:
方案数量 = S(n - 1, m - 1)
2、前面的n-1个球已经用了m个盒子了。那么已经没有空盒了。那么最后一个球,可以在m个盒子里任选一个放入,对于这一个球,因此有m种放法。方案数量则还要取决于前面n-1个球放m个盒子里且不空盒的数量,这又回到了问题本身,它的:
方案数量 = m * S(n - 1, m)
如果前面的n-1个球用了m-2个盒子呢?那么会剩下2个空盒子,无论手上这个球放哪个,必定有空盒。这种情况不是一个方案,所以不需要考虑。
因此,我们可以得到:
S(n, m) = S(n - 1, m - 1) + m * S(n - 1, m)
那么我们只需要知道初值、n和m的数量关系,就可以通过递推式,将目标值分解到初始值,就可以求出答案。
显然,n >= m(即:S(第一参数,第二参数)中,第一个参数必须≥第二个参数),时,可以递推。因为当n < m时,必有空盒,S = 0
在式子里,我们可以发现,两个参数要么同时-1,要么只有n-1。在n>=m的情况下,n必定会下降到==m;以及m会下降到1。因此只需要考量n = m,以及m = 1的情形。递推式的参数在下降过程中必定会经过这两点,即终止。
把递推式放到这里,方便看:
S(n, m) = S(n - 1, m - 1) + m * S(n - 1, m)
以下是初值,对于S(n, m):
当m = 1时,所有球必须放入盒内,只有1种放法,S = 1
当n = m时,每个盒子必须放一个球,也只有1种放法,S = 1
特别的,当m = 0时,连盒子都没了,S = 0
我们来试试求一个S(3, 2),3球入2盒,显然有3种放法:【1】【2、3】,【2】【1、3】,【3】【1、2】
S(3, 2) = S(2, 1) + 2 * S(2, 2)
=1 + 2 * 1
=3
再求一个S(5, 3),5球入3盒。放法有。。。算不过来。、
S(5, 3) = S(4, 2) + 3 * S(4, 3)
= S(3, 1) + 2 * S(3, 2) + 3 * (S(3, 2) + 3 * S(3, 3))
= 1 + 2 * (S(2, 1) + 2 * S(2, 2)) + 3 * (S(2, 1) + 2 * S(2, 2) + 3)
= 1 + 2 * (1 + 2) + 3 * (1 + 2 + 3)
= 25
如果有兴趣的话可以试试把25个都枚举出来。。。
5、球不同,盒同,允许空盒
与4情况相似,只是允许有空盒。那么我们可以这样解决:当我不空盒子时,有S0个方案,当我空1个盒子时,有S1个方案,……,当我空m-1个盒子时,有Sm-1个方案。那么,方案数量S = S0+S1+……+Sm-1
Si 很显然,就是4情况里,盒子数量 = m-i 的情况。Si= S(n, m - i)。
即:
方案数量 = Sigma( S(n, m - i) ),i∈[0, m - 1]。区间右端点取到m也是可以的,因为值是0。
这跟其他的博文似乎看起来不太一样。那是因为他们是正过来想的:当我只用0个盒子时,有S0个方案(这个情况当然是0个),当我用1个盒子时,有S1个方案,……,最后方案数量等于上述Si的和。
此时很显然,Si = S(n, i)。即:
方案数量 = Sigma( S(n, i) ),i∈[0, m]。
两个式子实质上只是相加顺序颠倒了一下。
6、球不同,盒不同,不允许空盒
在4的基础上,盒子也有区别了。也就是排列的顺序也不同了。即:【1】【2、3】和【2、3】【1】算作了2个不同的方案。
其实过程和4一样,只是给球分好组后,需要再对m个盒子进行排列,因此,最终需要再乘以“m个盒子取m个排列”的排列数A(m, m) = m * (m - 1) * (m - 2) * …… * (m - m + 1) = m!,这也是排列数的公式。
因此:
方案总数 = S(n, m) * m!
7、球不同,盒不同,允许空盒
不要被斯特林数迷了双眼,看看情况1。你也可以通过对过程的分析来得出一个斯特林数格式的公式,然后再去验证一下是否等于开头的阶乘。
即:只用1个盒子,只用2个盒子,……,以此类推得到累加。当只用i个盒子时,还需要乘以“从m个盒子里取i个盒子并排列”的排列数,也就是A(m, i)。
7、球同,盒同,允许空盒
这是我个人觉得最复杂,最费解的一个情况。值得好好理一下思路。
因为球已经相同了,就不符合第二类斯特林数的条件“n个不同元素”了,所以后面都不能用斯特林数直接求解。
但我们可以借用它的思想,我们设置一个新的符号 T(n, m) 来代表7情况的解,思考它的递推式。
当我们说,把n个球放入m个盒子(分为m份),允许盒子为空(允许出现空集)时,一个分法(组合数学上称为一个“划分”)只有两种:有空盒,和没有空盒。那么方案总数就等于有空盒的分法+没有空盒的分法。
当一个划分里没有空集时,每个盒子都有球,那么回顾情况3里对2的解释,可以认为是先把每个盒子里放上了一个球(只有1种放法),再对剩下的n - m个球做放入m个盒子允许空盒,回到了问题本身。那么方案数量就是:1 * T(n - m, m)
当一个划分里存在空集时,那么它至少有1个空盒子。既然盒子和球都是一样的,那么当我们把空出来的那个盒子扔掉时,方案的数量不会受到影响。扔掉那个空盒子之后,我们在做的其实就是把n个球放进剩下的m - 1个盒子里去,并且允许空盒,回到了问题本身。那么方案数量就是:T(n, m - 1)。
你可能会疑惑:
“怎么能扔盒子呢?”——因为我们扔盒子的前提,是我们已经知道最后划分完毕后一定有空盒子。没有空盒子的情况已经在上一段计算过了。把9个球放进5个盒子,必须至少要有一个空的;和把9个球放进4个盒子里,随便空不空。问题的答案是一样的。
“既然你要扔空盒子,那为什么不扔两个,三个,更多个?它们都符合题意”——当然可以扔两个三个更多个。只是,当你扔了两个盒子的时候,你做的其实是“把n个球放进m - 2个盒子",或者说,“至少空了2个盒子”。这就把一个情况忽视掉了:只空了1个盒子。因此,你需要再加上“把n个球放进m - 1个盒子并且不允许空盒”的情况,才不会遗漏。这样会让问题无法回到自身,并且递推式子会变得冗长。
因此,递推式子:
T(n, m) = T(n - m, m) + T(n, m - 1)
显然,上述推论是建立在n >= m的基础上的。
接下来要确定初值。
在这里我们可以发现,第一参数出现了“n-m”。它下降的速度我们无法知晓,很有可能出现第一参数比第二参数小的情况即:n - m < m。因此我们需要知道,当T(n, m)在n < m的时候,它等于多少。
为0吗?显然不为0,因为“2个球放入5个盒子并且允许空盒”,很显然我们就可以找到一个解“【1】【1】【】【】【】”。事实上它有2种方案,另一个放法就是两个球放进一个盒子。
我们可以知道,当 n < m 时,意味着盒子比球多,它必定有空盒。
关键词触发!必定有空盒,那么我们就可以扔掉1个空盒!甚至你可以做一下优化!当 n < m 且 n 至少比 m 小 i 的时候,我们可以把多余的盒子扔掉!因为最差最差的情况,每个盒子都至多1个球,我们也会有 m - n 个盒子用不到。
这意味着:
T(n, m) = T(n, m - 1),此时 n < m
让我把两段递推式放在一起:
T(n, m) = T(n - m, m) + T(n, m - 1),此时 n >= m
T(n, m) = T(n, m - 1),此时 n < m
那么我们只需要知道当第一参数和第二参数下降到1和0的时候的值,就可以递推得到目标值了。
对于T(n, m):
当m = 1时,所有球必须放入盒内,只有1种放法,T = 1
当n = 1时,只有一个球,随便放哪个盒子,也只有1种放法,T = 1
当n = 0时,手上一个球都没有,只留下一地空集,符合要求,因此,T = 1
特别的,当m = 0时,连盒子都没了,T = 0
8、球同,盒同,不允许空盒
把7看懂后,就很显然地会想到,这就是7情况里的“当一个划分里没有空集”的小分支。
我们就先在每个盒子里放一个球,还剩下 n - m 个球,我们就可以随便放入m个盒子里,允许空盒,就回到了情况7。
因此答案就是:T(n - m, m)
应用
主要是最近笔试做到了一道题:给两个数n,k,把n划分成k个数的和。求划分的方式数量。
其实就对应于上面的n球m盒的8情况,球同,盒同,不允许空盒。我当时已经摸到了要递推这一层,也知道要先放1个球。但是7情况真的是不看教材不看解释,光凭空想,想不出来的。于是没做出来Orz、。。。
参考博客
本文依据以下三位大佬的博客,经过个人整理、个人理解写出。
https://blog.csdn.net/qwb492859377/article/details/50654627
https://blog.csdn.net/qq_40181728/article/details/110947769
https://blog.csdn.net/qq_39565901/article/details/86683315
再次感谢上面的大佬。
更多
正如开篇所说,还有更多变体。例如“n个球放入m个盒子,每个盒子最多放s个球”,等等。那就靠大家自己找资料了~
哎,上课还专门选了组合数学这门选修课的,什么普通母函数之类的工具啊概念啊之类的,全都忘光光!~