将 n 个相同的球分配到组中以使每组至少有 k 个球的方法有多少种?
Number of ways of distributing n identical balls into groups such that each group has atleast k balls?
我正在尝试使用带记忆的递归来做到这一点,我已经确定了以下基本情况。
I) 当n==k时只有一组有所有的球
II) 当 k>n 时,没有组至少有 k 个球,因此为零。
我无法从 here.How 前进,可以这样做吗?
以n=6,k=2为例
(2,2,2)
(4,2)
(3,3)
(6)
即可以组成4个不同的分组。
这个案例可以很简单地解决:
桶数
桶的最大数量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}
。我们可以很容易地生成所有这些序列,从 "first" 序列 {r , 0 , ... , 0}
开始,然后从左向右移动 1。所以下一个序列看起来像这样:{r - 1 , 1 , ... , 0}
。如果只生成匹配 d1 >= d2 >= ... >= dx
的序列,则不会生成重复项。这个约束可以很容易地用来稍微优化这个搜索:如果给出 da - 1 >= 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()
这可以用下面描述的二维递归公式表示:
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
视为尽可能少的球数,并将问题分为两种情况:它们并用 n-k
个球递归)或不递归(然后用 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)
时间内计算出来。
我正在尝试使用带记忆的递归来做到这一点,我已经确定了以下基本情况。
I) 当n==k时只有一组有所有的球
II) 当 k>n 时,没有组至少有 k 个球,因此为零。
我无法从 here.How 前进,可以这样做吗?
以n=6,k=2为例 (2,2,2) (4,2) (3,3) (6)
即可以组成4个不同的分组。
这个案例可以很简单地解决:
桶数
桶的最大数量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}
。我们可以很容易地生成所有这些序列,从 "first" 序列 {r , 0 , ... , 0}
开始,然后从左向右移动 1。所以下一个序列看起来像这样:{r - 1 , 1 , ... , 0}
。如果只生成匹配 d1 >= d2 >= ... >= dx
的序列,则不会生成重复项。这个约束可以很容易地用来稍微优化这个搜索:如果给出 da - 1 >= 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()
这可以用下面描述的二维递归公式表示:
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
视为尽可能少的球数,并将问题分为两种情况:它们并用 n-k
个球递归)或不递归(然后用 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)
时间内计算出来。