将k个球分配到不同容量的n个箱子中,排列和非排列组合

5
我希望将 k 个球分配到不同容量的 n 个容器中。如何在给定 n、k 和容器容量的情况下对分配进行等级排序和取消排序?
示例:
n := 3
k := 4
容器容量 := 3,2,1
容器中的球:
1,2,1,2,1,1,2,2,0,3,0,1,3,1,0 := 5
是否有一个公式?

什么是排列和组合?看起来你想计算k个球分配到n个不同的、有限数量的箱子中的方法数? - doggie_breath
1
@doggie_breath 当人们说“排名和未排名”时,通常是指在球分配到箱子的分布上进行排序,然后能够查看一个分布并说出它是哪个,以及查看一个数字m并说出哪个是第m个分布。 - btilly
似乎缺少分布2,1,1。我假设列表是按字典顺序排列的,这个应该是列表中的第二个? - btilly
@btilly 哦,明白了。谢谢你的澄清。 - doggie_breath
是的,“2,1,1”缺失了。 - TTho Einthausend
3个回答

3

我不确定这种技术是否有一个标准名字,但这是一种问题,我使用动态规划的技巧成功解决了许多次。

我使用动态规划构建数据结构,从中可以进行排列/组合操作,然后构建逻辑来执行排列/组合操作。

动态规划部分最难。

import collections
BallSolutions = collections.namedtuple('BallSolutions', 'bin count balls next_bin_solutions next_balls_solutions');

def find_ball_solutions (balls, bin_capacities):
    # How many balls can fit in the remaining bins?
    capacity_sum = [0 for _ in bin_capacities]
    capacity_sum[-1] = bin_capacities[-1]

    for i in range(len(bin_capacities) - 2, -1, -1):
        capacity_sum[i] = capacity_sum[i+1] + bin_capacities[i]

    cache = {}
    def _search (bin_index, remaining_balls):
        if len(bin_capacities) <= bin_index:
            return None
        elif capacity_sum[bin_index] < remaining_balls:
            return None
        elif (bin_index, remaining_balls) not in cache:
            if bin_index + 1 == len(bin_capacities):
                cache[(bin_index, remaining_balls)] = BallSolutions(
                    bin=bin_index, count=1, balls=remaining_balls, next_bin_solutions=None, next_balls_solutions=None)
            else:
                this_solution = None
                for this_balls in range(min([remaining_balls, bin_capacities[bin_index]]), -1, -1):
                    next_bin_solutions = _search(bin_index+1, remaining_balls - this_balls)
                    if next_bin_solutions is None:
                        break # We already found the fewest balls that can go in this bin.
                    else:
                        this_count = next_bin_solutions.count
                        if this_solution is not None:
                            this_count = this_count + this_solution.count
                        next_solution = BallSolutions(
                            bin=bin_index, count=this_count,
                            balls=this_balls, next_bin_solutions=next_bin_solutions,
                            next_balls_solutions=this_solution)
                        this_solution = next_solution
                cache[(bin_index, remaining_balls)] = this_solution
        return cache[(bin_index, remaining_balls)]

    return _search(0, balls)

这里是生成排名解决方案的代码:
def find_ranked_solution (solutions, n):
    if solutions is None:
        return None
    elif n < 0:
        return None
    elif solutions.next_bin_solutions is None:
        if n == 0:
            return [solutions.balls]
        else:
            return None
    elif n < solutions.next_bin_solutions.count:
        return [solutions.balls] + find_ranked_solution(solutions.next_bin_solutions, n)
    else:
        return find_ranked_solution(solutions.next_balls_solutions, n - solutions.next_bin_solutions.count)

这里是用于生成解决方案排名的代码。请注意,如果提供无效的答案,它将会出错。
def find_solution_rank (solutions, solution):
    n = 0
    while solutions.balls < solution[0]:
        n = n + solutions.next_bin_solutions.count
        solutions = solutions.next_balls_solutions
    if 1 < len(solution):
        n = n + find_solution_rank(solutions.next_bin_solutions, solution[1:])
    return n

以下是一些测试代码:

s = find_ball_solutions(4, [3, 2, 1])
for i in range(6):
    r = find_ranked_solution(s, i)
    print((i, r, find_solution_rank(s, r)))

1
这里有一种方法(伪代码实现),虽然看起来不太高效。在剩余总容量无法装下所有球的情况下,可能需要在某些地方添加短路。如果给定的容量列表将被多次使用,则可以使用一些聪明的缓存技巧来帮助。
所有数字均为非负整数。函数ArrayTail(array a)是输入数组中第一个元素之后的所有元素组成的子数组。函数ArrayCon(number head, array a)是由heada的元素组成的数组。
function Count(array capacities, number balls) -> number
    If balls == 0:
       return 1
    Else if capacities is empty:
       return 0
    Else:
       Let sum: number
       sum <- 0
       For b from 0 to max(balls, capacities[0]):
           sum <- sum + Count(ArrayTail(capacities), b)
       End For
       return sum
    End If/Else
End function

function Rank(array capacities, array counts) -> number
    Precondition: length(capacities) == length(counts)
    Precondition: counts[i] <= capacities[i] for all i < length(counts)
    If counts is empty:
        return 0
    Else:
        Let total: number
        total <- 0
        For c in counts:
            total <- total + c
        End For
        Let r: number
        r <- Rank(ArrayTail(capacities), ArrayTail(counts))
        For b from 0 to (counts[0]-1):
            r <- r + Count(ArrayTail(capacities), total - b)
        End For
        return r
    End If/Else
End function

function Unrank(array capacities, number balls, number rank) -> array
    Precondition: rank < Count(capacities, balls)
    If capacities is empty:
        return empty array
    Else
        Let c0: number
        c0 <- 0
        Loop until "return":
            Let subcount: number
            subcount <- Count(ArrayTail(capacities), balls-c0)
            If subcount <= rank:
                c0 <- c0 + 1
                rank <- rank - subcount
            Else
                return ArrayCon(c0, Unrank(ArrayTail(capacities), balls-c0, rank))
            End If/Else
        End Loop
    End If/Else
End function

1
你可以递归地定义这些组合的数量。给定k个球和箱子容量q_1, ..., q_n,对于每个j0q_1之间,将j个球放在q_1中,并将其余的k-j个球分配到其他箱子中。
以下是一个快速的Python实现:
from functools import lru_cache

@lru_cache(None)
def f(n, *qs):
  if not qs:
    return 1 if n == 0 else 0
  q = qs[0]
  return sum(f(n-j, *qs[1:]) for j in range(q+1))

f(4, 3, 2, 1)
# 5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接