检查是否可以通过进行给定长度的跳跃来达到数字?

时间:2019-06-29 17:00:00

标签: python python-3.x list

考虑到起始位置k以及两个跳跃大小d1d2,我们的任务是找到尽可能达到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。

1 个答案:

答案 0 :(得分:1)

您可以将其公式化为整数线性程序,然后使用PuLP或Pyomo之类的工具进行求解。

这个想法是,您需要选择非负整数up1down1up2down2,以使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

整数线性程序求解器使用分支定界技术。他们首先为变量(步数)求分数分数的最佳解,然后将允许区域依次划分为具有整数边的较小部分,最后在边找到最佳解时停下来。

对于较小的kd1d2p,这应该很快找到一个解决方案(蛮力递归解决方案也应如此)。但是对于不可行的问题,天真的方法可以永远持续下去(就像暴力递归解决方案一样)。商业求解器(例如CPLEX或Gurobi)可以识别这种不可行并迅速返回,但是开源求解器(CBC或GLPK)可能会运行很长时间甚至永远(我等了几分钟就放弃了)。 / p>

我在这里通过使用求解器上的时间限制解决了这个问题,并假设如果在限制之前没有找到解决方案,则该问题是不可行的。您可以在递归方法中执行类似的操作,例如,限制要采取的步骤数。即使您需要数千步或数百万步,线性编程方法也趋向于行得通(请参见最后一个示例),因为它可以快速找到正确的解决方案,然后进行精炼以获得精确匹配。我猜想在这种情况下,递归方法会花费太长时间。但是,即使存在解决方案,您也可以设计出线性规划方法无法在时限内找到解决方案的情况。

...经过进一步检查,我发现当问题在数字上定义不明确或需要大量分支时,CBC求解器的性能不佳。例如,即使有可能回答,此超时也仍然是position(10, 100000, 100001, 10000000)。这将报告无效答案:position(10, 10000000, 10000001, 10000000)(很高兴使用几乎真实的答案10 + 1 * d2 = 10000000)。另一方面,通过强力方法很难解决这些情况。因此,也许更深入地思考问题的本质会有所帮助。 (我会稍后再尝试,但是今天我已经呆呆了很久!)