好的,我有一个问题。我有一套各种尺寸的瓶装“A”,里面装满了水。 然后我又买了一套各种尺寸的“B”瓶,都是空的。
我想将水从A转移到B,知道每组的总容量是相同的。 (即:A组含有与B组相同的水量)。
这当然是微不足道的,只需拿B中的第一个瓶子,倒入A中的第一个瓶子直到它满了。然后,如果B中的瓶子中还有水,请继续使用A中的第二个瓶子等
但是,我希望尽量减少灌注总量(从一个瓶子倒入另一个瓶子的动作,每个动作计数1,与其涉及的水量无关)
我想找到一个贪婪的算法来做到这一点,或者如果不可能,至少是一个有效的算法。然而,效率是算法正确性的次要因素(我不想要一个次优的解决方案)。
当然,这个问题只是计算机程序中管理个人开支的真实问题的隐喻。
答案 0 :(得分:8)
坏消息:通过减少子集和来解决这个问题。给定数字x 1 ,...,x n ,S,子集和的对象是确定x i 的某个子集总和为S.我们生产容量为x 1 ,...,x n 的A瓶和容量为S且(x 1 + ... + x n - S)并确定n次浇注是否足够。
好消息:任何贪婪的策略(即选择任何非空的A,选择任何未填充的B,倾倒直到我们必须停止)是2近似(即,最多使用两倍于最佳的倾倒)。最优解决方案至少使用max(| A |,| B |)pours,而greedy最多使用| A | + | B |,因为每当贪婪倾倒时,无论是A排水还是B充满,都不需要倒出或倒入。
可能有一个近似方案(任何ε> 0的a(1 +ε) - 近似。)我认为现在更有可能出现不可接近性结果 - 通常的获取技巧近似方案似乎不适用于此。
以下是一些可能导致实用精确算法的想法。
给定一个解决方案,绘制一个左顶点A
和右顶点B
以及a
到b
的(无向)边的二分图,当且仅当{ {1}}被填入a
。如果解决方案是最佳的,我声称没有循环 - 否则我们可以消除循环中最小的灌注并替换循环周围的丢失体积。例如,如果我已经倾倒
b
然后我可以像a1 -> b1: 1
a1 -> b2: 2
a2 -> b1: 3
a2 -> b3: 4
a3 -> b2: 5
a3 -> b3: 6
一样消灭:
a1 -> b1
现在,由于图表没有周期,我们可以将边数(pours)计算为a2 -> b1: 4 (+1)
a2 -> b3: 3 (-1)
a3 -> b3: 7 (+1)
a3 -> b2: 4 (-1)
a1 -> b2: 3 (+1)
。这里唯一的变量是连接组件的数量,我们希望最大化。
我声称贪婪算法形成没有循环的图形。如果我们知道最佳解决方案的连接组件是什么,我们可以在每个组件上使用贪婪算法并获得最佳解决方案。
解决这个子问题的一种方法是使用动态编程来枚举B的A和Y的所有子集对X,使得sum(X)== sum(Y),然后将它们馈送到精确的覆盖算法中。这两个步骤当然都是指数级的,但它们可能在真实数据上运行良好。
答案 1 :(得分:3)
这是我的看法:
更新:在步骤2中每次浇筑后,重复步骤1.(Steve Jessop建议的优化步骤)。冲洗并重复,直到所有的水都被转移。
答案 2 :(得分:0)
我认为这给出了最小的注意次数:
import bisect
def pours(A, B):
assert sum(A) == sum(B)
count = 0
A.sort()
B.sort()
while A and B:
i = A.pop()
j = B.pop()
if i == j:
count += 1
elif i > j:
bisect.insort(A, i-j)
count += 1
elif i < j:
bisect.insort(B, j-i)
count += 1
return count
A=[5,4]
B=[4,4,1]
print pours(A,B)
# gives 3
A=[5,3,2,1]
B=[4,3,2,1,1]
print pours(A,B)
# gives 5
用英文写着:
sum(A) > sum(B)
或sum(A) < sum(B)
为真,算法仍然有效)当A不为空且B不为空时: