【问题标题】:Number of ways of distributing n identical balls into groups such that each group has atleast k balls?有多少种方法将 n 个相同的球分成几组,每组至少有 k 个球?
【发布时间】:2016-02-27 12:49:43
【问题描述】:

我正在尝试使用带有记忆的递归来做到这一点,我已经确定了以下基本情况。

I) 当 n==k 时,只有一组拥有所有球。

II) 当 k>n 时,任何组都不能有至少 k 个球,因此为零。

我无法从这里继续前进。如何才能做到这一点?

当 n=6 ,k=2 时举例说明 (2,2,2) (4,2) (3,3) (6)

即可以形成4个不同的分组。

【问题讨论】:

  • 尝试提出一个分区可能性的声明,例如“每个有效的解决方案都包含一个___和一个___”。如果第一个 ___ 是“简单”对象,则最简单。这个想法是,我们可以通过计算第一个___的每个选择的解决方案数量,然后将它们加在一起来计算解决方案的总数。这也意味着不能将任何解决方案计算两次(即,在第一个___的两个不同选择下)。
  • 例如如果问题是计算排序 n 项列表的方式的数量,我们可以说,“n 项的每个排序都由其中一项组成,然后是剩余 n-1 项的排序。”这具有避免重复计算的重要特性:它将排序根据它们的第一项划分为 n 个桶,并且由于任何特定的排序都只有一个第一项,因此它将只计算一次。计算 n-1 项的顺序很容易——可以使用我们刚刚编写的计算 n 项的顺序的相同函数来完成! :)
  • 在这个特定问题中,如何划分解决方案不太明显,因为组的顺序无关紧要——也就是说,(4, 2) 与 (2, 4),所以它应该只计算一次。 (顺便说一句,你应该在你的问题中明确说明这一点。)你能以某种方式修改问题来解决这个问题吗?提示:对于大多数解决方案,组列表可以按许多不同的顺序编写;如果有一种简单的方法可以从该集合中选择一个特定的顺序,您可以简单地计算解决方案的 此类顺序 的数量...

标签: algorithm combinations dynamic-programming


【解决方案1】:

这可以用下面描述的二维递归公式来表示:

T(0, k) = 1
T(n, k) = 0   n < k, n != 0
T(n, k) = T(n-k, k)                 +           T(n, k + 1)
             ^                                       ^
    There is a box with k balls,        No box with k balls, advance to next k
            put them 

在上面,T(n,k)n 球的分布数量,使得每个盒子至少得到k
诀窍是将k 视为尽可能少的球数,并将问题分为两种情况:是否有一个正好有k 球的盒子(如果有,放置它们并用@987654327 递归) @balls),或者不(然后,以k+1 的最小值和相同数量的球进行递归)。

示例,计算您的示例:T(6,2)(6 个球,每盒至少 2 个):

T(6,2) = T(4,2) + T(6,3) 
T(4,2) = T(2,2) + T(4,3) = T(0,2) + T(2,3) + T(1,3) + T(4,4) =
       = T(0,2) + T(2,3) + T(1,3) + T(0,4) + T(4,5) = 
       =  1     +  0     +  0     +  1     +    0 
       = 2
T(6,3) = T(3,3) + T(6,4) = T(0,3) + T(3,4) + T(2,4) + T(6,5)
       = T(0,3) + T(3,4) + T(2,4) + T(1,5) + T(6,6) = 
       = T(0,3) + T(3,4) + T(2,4) + T(1,5) + T(0,6) + T(6,7) =
       =   1    +   0    +   0    +   0    + 1      + 0 
       = 2
T(6,2) = T(4,2) + T(6,3) = 2 + 2 = 4

使用动态规划,可以在O(n^2)时间计算。

【讨论】:

  • 请原谅我的无知,但我在理解这一点时遇到了一些麻烦,为什么 T(0,k)=1 ,我可以理解 T(0,0)=1 作为我们至少 k 个元素的条件在一个群体中很满意。但为什么 T(0,1),T(0,2) 等为 1。其次,您能否用实际的术语详细说明“将 k 视为尽可能少的球数”是什么意思?在第一部分中,这是否意味着有一个当前是空的盒子,我们将 k 个球放入其中?
  • 您能否更详细地或实际地解释一下 T(n-k, k) 和 T(n, k + 1) 这两种情况?我无法跟上。
  • @Sam T(0,k)=1,因为无论k 的值如何,如果您还剩 0 个球 - 您可以通过 1 种方式将它们放入盒子中。无处可放。
  • @Sam 关于这两个术语:您首先要问自己,“有没有恰好有k 球的盒子?”。如果是这样,您将k 球放在一个盒子中,然后继续前进 - 剩下n-k 球,然后将刚刚填充的盒子放在一边。否则,你将k 提前一个,因为没有比k 少的盒子(问题要求),而且我们现在知道没有盒子正好有k 球,所以你可以保证所有盒子有k+1 球或更多。
【解决方案2】:

这个案例可以很简单的解决:

桶数

最大桶数b可以如下确定:

b = roundDown(n / k)

每个有效分发最多可以使用b 个桶。

具有x 存储桶的分布数

对于给定数量的桶,分布的数量可以很简单地找到:
k 球分发到每个桶。找出将剩余球(r = n - k * x)分配给x桶的方法数:

total_distributions(x) = bincoefficient(x , n - k * x)

编辑:如果顺序很重要,这将起作用。既然它不是问题,我们可以在这里使用一些技巧:

每个分布都可以映射到一个数字序列。例如:d = {d1 , d2 , ... , dx}。我们可以轻松地生成所有这些序列,从“第一个”序列{r , 0 , ... , 0} 开始,然后从左向右移动 1。所以下一个序列看起来像这样:{r - 1 , 1 , ... , 0}。如果只生成匹配d1 &gt;= d2 &gt;= ... &gt;= dx 的序列,则不会生成重复。这个约束可以很容易地用来优化这个搜索:如果给定了da - 1 &gt;= db + 1,我们只能将一个1从da移动到db(使用a = b - 1),否则数组排序的约束被违反。要移动的 1 始终是可以移动的最右边。另一种思考方式是将r 视为一元数字,然后将该字符串简单地拆分成组,这样每个组至少只要它是继任者。

 countSequences(x)
     sequence[]
     sequence[0] = r

     sequenceCount = 1

     while true
         int i = findRightmostMoveable(sequence)

         if i == -1
             return sequenceCount

         sequence[i] -= 1
         sequence[i + 1] -= 1
         sequenceCount

findRightmostMoveable(sequence)
    for i in [length(sequence) - 1 , 0)
       if sequence[i - 1] > sequence[i] + 1
           return i - 1

return -1

实际上findRightmostMoveable 可以稍微优化一下,如果我们看一下序列的结构转换(更准确地说是序列的两个元素之间的差异)。但老实说,我懒得进一步优化。

拼凑起来

range(1 , roundDown(n / k)).map(b -> countSequences(b)).sum()

【讨论】:

  • 据我了解这个解决方案,它假设“组”是不同的。不是这种情况。 (2,4) == (4,2)。
  • @amit 没有考虑到这一点。我会编辑答案。
猜你喜欢
  • 2018-06-16
  • 1970-01-01
  • 1970-01-01
  • 2020-02-04
  • 1970-01-01
  • 2018-06-16
  • 1970-01-01
  • 1970-01-01
  • 2020-06-01
相关资源
最近更新 更多