Проверить, можно ли набрать номер, совершая прыжки заданной длины? - PullRequest
3 голосов
/ 29 июня 2019

Учитывая начальную позицию 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 вызывается для 1-го аргумента position() как 14 и 16, например:

Для 14:

x = [(14,'+',4), (14,'+',6), (14,'-',4), (14,'-',6)]

затем x = [18,20,10,8] теперь счет становится 2 и 8 находится в 4-м индексе.

Но проблема в том, что мой код пропускает рекурсию 14 4 6 8 и None печатается на консоли,Кроме того, я не могу понять, как вернуть -1, если невозможно достичь х из моей позиции ().

1 Ответ

1 голос
/ 04 июля 2019

Вы можете сформулировать это как целочисленную линейную программу, а затем решить ее с помощью такого инструмента, как PuLP или Pyomo.

Идея состоит в том, что вам нужно выбрать неотрицательные целые числа up1, down1, up2 и down2 такие, что up1*d1 - down1*d1 + up2*d2 - down2*d2 == p.Кроме того, вы хотите выбрать значения для этих переменных, которые минимизируют общее количество шагов up1 + down1 + up2 + down2.

Вот пример использования PuLP (в общих чертах этого урока ):

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) могут работать очень долго или навсегда (я ждал несколько минут и сдался).

Я решил эту проблему здесь, используя ограничение времени для решателя и предположив, что проблема невозможна, если до этого предела не было найдено решение.Вы можете сделать что-то подобное в своем рекурсивном подходе, например, установить ограничение на количество шагов, которые нужно предпринять.Даже если вам нужны тысячи или миллионы шагов, подход линейного программирования имеет тенденцию работать хорошо (см. Последний пример), потому что он быстро приближается к правильному решению, а затем уточняется, чтобы получить точное соответствие.Я предполагаю, что рекурсивный подход займет слишком много времени в этом случае.Но, возможно, вам удастся разработать случаи, когда подход линейного программирования не может найти решение в установленные сроки, даже если решение существует.

... После дальнейшей проверки я обнаружил, что решатель CBCне работает хорошо, когда проблема численно плохо определена или требует много ответвлений.Например, это время ожидания, хотя ответ возможен: position(10, 100000, 100001, 10000000).И это сообщает о неверном ответе: position(10, 10000000, 10000001, 10000000) (счастлив использовать почти правдивый ответ 10 + 1 * d2 = 10000000).С другой стороны, эти случаи также могут быть очень трудно решить с помощью метода грубой силы.Так что, возможно, более глубокое понимание природы проблемы поможет.(Я постараюсь сделать это позже, но я уже слишком долго болтал!)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...