我们有N套三元组,比如
1. { (4; 0,1), (5 ; 0.3), (7; 0,6) }
2. { (7; 0.2), (8 ; 0.4), (1 ; 0.4) }
...
N. { (6; 0.3), (1; 0.2), (9 ; 0.5) }
并且需要从每个三元组中仅选择一对,以便成对的第一个成员的总和将是最小的,但是我们还有一个条件,即成对中的第二个成员的总和必须不小于给定的P数。
我们可以通过将所有可能的对组合与它们的第一个成员(3 ^ N个组合)的总和进行排序来解决这个问题,并且在该排序列表中选择也满足第二个条件的第一个。你能帮忙建议一个更好的,非平凡的解决方案来解决这个问题吗?
如果对三元组内的值没有约束,那么我们将面对一个非常通用的integer programming problem版本,更具体地说是0-1线性编程问题,因为它可以表示为每个系数为0或1的方程组您可以在维基页面上找到可能的方法,但一般来说,这个问题没有快速简便的解决方案。
或者,如果每对的第二个数字(需要总结到>= P
的数量)来自足够小的范围,我们可以将其视为类似于Knapsack问题的动态规划问题。 “足够小”有一点难以定义,因为原始数据具有非整数。如果它们是整数,那么我将描述的解决方案的算法复杂性是O(P * N)
。对于非整数,它们需要首先通过将它们全部乘以P
以及足够大的数字来转换为整数。在您的示例中,每个数字的精度在零之后是1位数,因此乘以10就足够了。因此,实际的复杂性是O(M * P * N)
,其中M是所有因子乘以实现整数的因子。
在此之后,我们基本上解决了修改后的背包问题:我们不是从上面限制重量,而是从下面限制它,并且在每一步我们从三元组中选择一对,而不是决定是否将项目放入背包与否。
让我们定义一个函数minimum_sum[i][s]
,其值i, s
表示最小可能总和(我们采取的每对中的第一个数字),如果到目前为止所采用的第二个数字的总和等于s
,我们可以实现,我们已经考虑了第一个i
三胞胎。这个定义的一个例外是minimum_sum[i][P]
对所有超过P
的总和都是最小的。如果我们可以计算这个函数的所有值,那么minimum_sum[N][P]
就是答案。函数值可以通过以下方式计算:
minimum_sum[0][0]=0, all other values are set to infinity
for i=0..N-1:
for s=0..P:
for j=0..2:
minimum_sum[i+1][min(P, s+B[i][j])] = min(minimum_sum[i+1][min(P, s+B[i][j])], minimum_sum[i][s] + A[i][j]
A[i][j]
在这里表示第i个三联体的第j个对中的第一个数字,B[i][j]
表示同一个三联体中的第二个数字。
如果N
很大,这个解决方案是可行的,但P
很小,并且B
s的精度不是太高。例如,如果N=50
,没有希望计算3^N
的可能性,但使用M*P=1000000
这种方法可以非常快速地工作。
Python实现上面的想法:
def compute(A, B, P):
n = len(A)
# note that I use 1,000,000 as “infinity” here, which might need to be increased depending on input data
best = [[1000000 for i in range(P + 1)] for j in range(n + 1)]
best[0][0] = 0
for i in range(n):
for s in range(P+1):
for j in range(3):
best[i+1][min(P, s+B[i][j])] = min(best[i+1][min(P, s+B[i][j])], best[i][s]+A[i][j])
return best[n][P]
测试:
A=[[4, 5, 7], [7, 8, 1], [6, 1, 9]]
# second numbers in each pair after scaling them up to be integers
B=[[1, 3, 6], [2, 4, 4], [3, 2, 5]]
In [7]: compute(A, B, 0)
Out[7]: 6
In [14]: compute(A, B, 7)
Out[14]: 6
In [15]: compute(A, B, 8)
Out[15]: 7
In [20]: compute(A, B, 13)
Out[20]: 14