📌  相关文章
📜  计算用N个不同的项目填充K个盒子的方式数量(1)

📅  最后修改于: 2023-12-03 15:41:40.825000             🧑  作者: Mango

计算用N个不同的项目填充K个盒子的方式数量

当我们需要将N个不同的项目分配到K个盒子里时,可能会想要知道有多少种分配的可能性。这个问题被称为盒子问题(box problem)。

对于盒子问题,有两种情况:每个盒子里至少有一个项目(强限制情况),或是盒子里可以有空项目(弱限制情况)。我们将逐一介绍这两种情况的解决方案。

强限制情况(每个盒子里至少有一个项目)
排列组合公式

由于每个盒子里至少要有一个项目,所以我们可以从N个不同的项目中选择K个项目,先将这K个项目放到K个盒子里,再将剩下的N-K个项目放到这K个盒子的任意一个盒子中。我们先计算前面K个项目的全部排列,再乘以后面N-K个项目的全部组合,即为盒子问题的解。

$$ \text{Solution} = P^{N}_{K} \times \binom{N-K+K-1}{K-1} $$

其中,P为排列数,$\binom{N-K+K-1}{K-1}$为组合数,即从N-K个项目中选择K-1个项目,将它们放在K-1个隔板中间。

示例代码
def box_problem_strong(N: int, K: int) -> int:
    P = 1
    for i in range(K):
        P *= N - i

    C = 1
    for i in range(K):
        C *= i + 1

    result = P // C

    C = 1
    for i in range(K):
        C *= i + N - K + 1

    return result * C // ((K - 1) * C // N)
弱限制情况(盒子里可以有空项目)
球和盒子的经典问题

我们可以将盒子问题转化为球和盒子的问题。具体来说,我们可以看作将N个不同的球丢到K个盒子里,其中允许有空盒子。

这个问题也被称为球和盒子的经典问题(classical ball and box problem)。对于球和盒子的经典问题,有两种解决方案——隔板法和指数型生成函数。我们将分别介绍这两个方案。

隔板法

我们可以将题目中的K个盒子抽象成K-1个隔板,比如说,如果K为3,那就有两个隔板,第一个隔板和第二个隔板之间代表第一个盒子和第二个盒子,第二个隔板和第三个隔板之间代表第二个盒子和第三个盒子,这样就整个将K个盒子分成了K-1个区间。

接下来,我们将N个球放到这K-1个隔板中间,于是就分成了K个盒子。为了表示每个盒子里有多少个球,我们可以用每个隔板上的球的数量来表示。比如,如果第一个隔板上放了3个球,第二个隔板上放了4个球,那么第一个盒子里有3个球,第二个盒子里有1个球,第三个盒子里有0个球。注意,最后一个隔板上是不放球的。

由于每个盒子里可以有任意数量的球,因此我们相当于需要将N个球随意地丢到这K-1个隔板中间,相邻隔板之间可以不放球。换言之,这就是一个计算组合数的问题。将隔板看做一类物品,将球看做另一类物品,那么我们需要从N个球和K-1个隔板中,选择K-1个物品作为隔板,再将剩下的球(数量为N-K+1)随便地填充进去,这样就可以得到一个方案。

因此,盒子问题的解就等于:

$$ \text{Solution} = \binom{N-K+K-1}{K-1} = \binom{N-1}{K-1} $$

示例代码
def box_problem_weak(N: int, K: int) -> int:
    C = 1
    for i in range(K - 1):
        C *= N + K - 2 - i
        C //= i + 1

    return C
指数型生成函数

另一个计算组合数的方法是使用指数型生成函数(exponential generating function)。对于每个盒子,我们可以表示为$(1+x+x^2+...) = \frac{1}{1-x}$。将K个盒子相乘,可以得到用球填充盒子的方案数的生成函数:

$$ \begin{aligned} F(x) &= (1+x+x^2+...)^K \ &= (\frac{1}{1-x})^K \ \end{aligned} $$

将$F(x)$展开成泰勒级数,可以得到:

$$ F(x) = \sum_{n=0}^{+\infty} \binom{K+n-1}{n} x^n $$

因此,盒子问题的解就是

$$ \text{Solution} = \binom{K+N-1}{N} $$

示例代码
def box_problem_weak(N: int, K: int) -> int:
    C = 1
    for i in range(N):
        C *= K + i
        C //= i + 1

    return C
结论

如果盒子数K远小于项目数N,那么我们建议使用弱限制情况的计算方法。在这种情况下,隔板法是一种特别简单的方法。如果盒子数K和项目数N都很大,我们可以使用指数型生成函数。无论哪种方法,都可以得到正确的答案。