我需要一种算法,将n
数字的分区生成k
部分,并增加限制,即分区的每个元素必须介于a
和b
之间。理想情况下,满足限制的所有可能分区应该同样可能。如果分区具有不同顺序的相同元素,则认为分区相同。
例如,n=10
,k=3
,a=2
,b=4
只有{4,4,2}
和{4,3,3}
作为可能的结果。
是否存在针对此类问题的标准算法?可以假设至少有一个满足限制的分区总是存在。
答案 0 :(得分:2)
您可以将其实现为递归算法。基本上,重现是这样的:
k == 1
和a <= n <= b
,那么唯一的分区是[n]
,否则没有x
与a
中的所有元素b
与n-x
,k-1
a
替换为x
这里有一些Python(又名可执行的伪代码):
def partitions(n, k, a, b):
if k == 1 and a <= n <= b:
yield [n]
elif n > 0 and k > 0:
for x in range(a, b+1):
for p in partitions(n-x, k-1, x, b):
yield [x] + p
print(list(partitions(10, 3, 2, 4)))
# [[2, 4, 4], [3, 3, 4]]
这可以通过分别检查(k-1)*a
和(k-1)*b
的剩余元素的下限和上限,并相应地限制x
的范围来进一步改进:
min_x = max(a, n - (k-1) * b)
max_x = min(b, n - (k-1) * a)
for x in range(min_x, max_x+1):
对于具有3,157个解决方案的partitions(110, 12, 3, 12)
,这会将递归调用的数量从638,679减少到24,135。
答案 1 :(得分:2)
这是一个使用条件概率的采样算法。
import collections
import random
countmemo = {}
def count(n, k, a, b):
assert n >= 0
assert k >= 0
assert a >= 0
assert b >= 0
if k == 0:
return 1 if n == 0 else 0
key = (n, k, a, b)
if key not in countmemo:
countmemo[key] = sum(
count(n - c, k - 1, a, c) for c in range(a, min(n, b) + 1))
return countmemo[key]
def sample(n, k, a, b):
partition = []
x = random.randrange(count(n, k, a, b))
while k > 0:
for c in range(a, min(n, b) + 1):
y = count(n - c, k - 1, a, c)
if x < y:
partition.append(c)
n -= c
k -= 1
b = c
break
x -= y
else:
assert False
return partition
def test():
print(collections.Counter(
tuple(sample(20, 6, 2, 5)) for i in range(10000)))
if __name__ == '__main__':
test()
答案 2 :(得分:1)
如果k
和b - a
不是太大,您可以尝试随机深度优先搜索:
import random
def restricted_partition_rec(n, k, min, max):
if k <= 0 or n < min:
return []
ps = list(range(min, max + 1))
random.shuffle(ps)
for p in ps:
if p > n:
continue
elif p < n:
subp = restricted_partition(n - p, k - 1, min, max)
if subp:
return [p] + subp
elif k == 1:
return [p]
return []
def restricted_partition(n, k, min, max):
return sorted(restricted_partition_rec(n, k, min, max), reverse=True)
print(restricted_partition(10, 3, 2, 4))
>>>
[4, 4, 2]
虽然我不确定在这种情况下所有分区是否具有完全相同的概率。