考虑到起始位置k
以及两个跳跃大小d1
和d2
,我们的任务是找到尽可能达到x
的最小跳跃次数
Input : k = 10, d1 = 4, d2 = 6 and x = 8
Output : 2
1st step 10 + d1 = 14
2nd step 14 - d2 = 8
Input : k = 10, d1 = 4, d2 = 6 and x = 9
Output : -1
-1 indicates it is not possible to reach x.
我通过递归解决上述问题的代码是:
from itertools import product
def position(k, d1, d2, p, count):
count += 1
arr = [[k], ['+', '-'], [d1, d2]]
x = list(product(*arr))
for i in range(len(x)):
if x[i][1] == '+':
x[i] = x[i][0] + x[i][2]
else:
x[i] = x[i][0] - x[i][2]
if x[i] == p:
return count
for i in range(2):
position(x[i], d1, d2, p, count)
if __name__ == "__main__":
y = [int(i) for i in input().split()]
k = y[0]
d1 = y[1]
d2 = y[2]
p = y[3]
print(position(k, d1, d2, p, 0))
对于输入:10 4 6 8
x = [(10,'+',4), (10,'+',6), (10,'-',4), (10,'-',6)]
之后,x
= [14,16,6,4]
。并且count
= 1
。检查x
的每个元素是否等于8。现在将x
的第一个参数的position()
调用为14和16,如:
对于14:
x = [(14,'+',4), (14,'+',6), (14,'-',4), (14,'-',6)]
然后,x
= [18,20,10,8]
现在计数变为2,并且在第4个索引处找到8。
但是问题是我的代码错过了14 4 6 8
的递归,并且None
被打印在控制台上。另外,如果无法从我的position()到达x,我将无法弄清楚如何返回-1。
答案 0 :(得分:1)
您可以将其公式化为整数线性程序,然后使用PuLP或Pyomo之类的工具进行求解。
这个想法是,您需要选择非负整数up1
,down1
,up2
和down2
,以使up1*d1 - down1*d1 + up2*d2 - down2*d2 == p
正确。此外,您要为这些变量选择值,以使步骤up1 + down1 + up2 + down2
的总数最少。
以下是使用PuLP的示例(宽松地基于this tutorial):
from pulp import *
def position(k, d1, d2, p):
prob = LpProblem("Minimum Steps", LpMinimize)
# define decision variables (values to be chosen by solver)
up1 = LpVariable('up1', lowBound=0, cat='Integer')
down1 = LpVariable('down1', lowBound=0, cat='Integer')
up2 = LpVariable('up2', lowBound=0, cat='Integer')
down2 = LpVariable('down2', lowBound=0, cat='Integer')
# define objective function (expression to minimize)
num_steps = up1 + down1 + up2 + down2
prob += num_steps
# define main constraint (rule to enforce)
prob += (k + up1*d1 - down1*d1 + up2*d2 - down2*d2 == p)
# solve with a time limit, because otherwise CBC may search forever
prob.solve(PULP_CBC_CMD(maxSeconds=2))
# if you have CPLEX installed, it can detect infeasibility immediately
# prob.solve(CPLEX_CMD())
status = LpStatus[prob.status]
solution = [str(k)]
if status == 'Optimal':
# report steps
steps = [
(1, up1, 'd1'), (-1, down1, 'd1'),
(1, up2, 'd2'), (-1, down2, 'd2')
]
for (sign, v, step) in steps:
if v.varValue > 0:
solution.append('-' if sign < 0 else '+')
solution.append('{} * {}'.format(int(v.varValue), step))
solution.extend(['=', str(p)])
print(' '.join(solution))
return int(num_steps.value())
elif status in {'Infeasible', 'Not Solved', 'Undefined'}:
# infeasible or timed out (probably infeasible)
return -1
else:
raise RuntimeError("Problem status was {}".format(status))
print(position(10, 4, 6, 8))
# 10 + 1 * d1 - 1 * d2 = 8
# 2
print(position(10, 4, 6, 9))
# -1
print(position(10, 171, 53, 8))
# 10 - 9 * d1 + 29 * d2 = 8
# 38
print(position(10, 3, 4, 1000000001))
# 10 + 1 * d1 + 250000000 * d2 = 1000000001
# 250000001
整数线性程序求解器使用分支定界技术。他们首先为变量(步数)求分数分数的最佳解,然后将允许区域依次划分为具有整数边的较小部分,最后在边找到最佳解时停下来。
对于较小的k
,d1
,d2
和p
,这应该很快找到一个解决方案(蛮力递归解决方案也应如此)。但是对于不可行的问题,天真的方法可以永远持续下去(就像暴力递归解决方案一样)。商业求解器(例如CPLEX或Gurobi)可以识别这种不可行并迅速返回,但是开源求解器(CBC或GLPK)可能会运行很长时间甚至永远(我等了几分钟就放弃了)。 / p>
我在这里通过使用求解器上的时间限制解决了这个问题,并假设如果在限制之前没有找到解决方案,则该问题是不可行的。您可以在递归方法中执行类似的操作,例如,限制要采取的步骤数。即使您需要数千步或数百万步,线性编程方法也趋向于行得通(请参见最后一个示例),因为它可以快速找到正确的解决方案,然后进行精炼以获得精确匹配。我猜想在这种情况下,递归方法会花费太长时间。但是,即使存在解决方案,您也可以设计出线性规划方法无法在时限内找到解决方案的情况。
...经过进一步检查,我发现当问题在数字上定义不明确或需要大量分支时,CBC求解器的性能不佳。例如,即使有可能回答,此超时也仍然是position(10, 100000, 100001, 10000000)
。这将报告无效答案:position(10, 10000000, 10000001, 10000000)
(很高兴使用几乎真实的答案10 + 1 * d2 = 10000000
)。另一方面,通过强力方法很难解决这些情况。因此,也许更深入地思考问题的本质会有所帮助。 (我会稍后再尝试,但是今天我已经呆呆了很久!)