Динамическое программирование с большим количеством подзадач


11

Динамическое программирование с большим количеством подзадач. Поэтому я пытаюсь решить эту проблему с улицы Интервью:

Ходьба по сетке (оценка 50 баллов)
Вы находитесь в мерной сетке в позиции . Размеры сетки: ). За один шаг вы можете идти на один шаг вперед или назад в любом из измерений. (Так что всегда есть возможных разных ходов). Сколько способов вы можете сделать шагов, чтобы не покинуть сетку ни в одной точке? Вы выходите из сетки, если для любого , либо либо .N(x1,x2,,xN)(D1,D2,,DNN2NMxixi0xi>Di

Моей первой попыткой было это запомненное рекурсивное решение:

def number_of_ways(steps, starting_point):
    global n, dimensions, mem
    #print steps, starting_point
    if (steps, tuple(starting_point)) in mem:
        return mem[(steps, tuple(starting_point))]
    val = 0
    if steps == 0:
        val = 1
    else:
        for i in range(0, n):
            tuple_copy = starting_point[:]
            tuple_copy[i] += 1
            if tuple_copy[i] <= dimensions[i]:
                val += number_of_ways(steps - 1, tuple_copy)
            tuple_copy = starting_point[:]
            tuple_copy[i] -= 1
            if tuple_copy[i] > 0:
                val += number_of_ways(steps - 1, tuple_copy)
    mem[(steps, tuple(starting_point))] = val
    return val

Большой сюрприз: он не подходит для большого количества шагов и / или измерений из-за недостатка памяти.

Так что следующий шаг , чтобы улучшить свое решение с помощью динамического программирования. Но прежде чем начать, я вижу большую проблему с подходом. Аргумент starting_pointявляется -кратным, где таким же, как . Таким образом , в самом деле, функция может быть с .n 10 1 x i100nn10number_of_ways(steps, x1, x2, x3, ... x10)1xi100

Проблемы динамического программирования, которые я видел в учебниках, почти все имеют переменные twp, так что нужна только двумерная матрица. В этом случае потребуется десятимерная матрица. Итак, всего клеток.10010

С 2-D матрицами в динамическом программировании, как правило , только предыдущий ряд вычислений требуются для следующего расчета, следовательно , уменьшение пространственной сложности от до . Я не уверен, как бы я поступил так же в этом случае. Визуализация таблицы неосуществима, поэтому ответ должен прийти непосредственно из приведенной выше рекурсии.mnmin(m,n)

ОБНОВИТЬ

Используя предложения Питера Шора и внося некоторые незначительные исправления, в частности, необходимость отслеживать положение в функции , а не только измерения на два набора A и B, выполняя разбиение рекурсивно, эффективно используя метод «разделяй и властвуй», пока не будет достигнут базовый случай, когда в наборе есть только одно измерение.W(i,ti)

Я придумал следующую реализацию, которая прошла все тесты ниже максимального времени выполнения:

def ways(di, offset, steps):
    global mem, dimensions
    if steps in mem[di] and offset in mem[di][steps]:
        return mem[di][steps][offset]
    val = 0
    if steps == 0:
        val = 1
    else:
        if offset - 1 >= 1:
            val += ways(di, offset - 1, steps - 1)
        if offset + 1 <= dimensions[di]:
            val += ways(di, offset + 1, steps - 1)
    mem[di][steps][offset] = val
    return val


def set_ways(left, right, steps):
    # must create t1, t2, t3 .. ti for steps
    global mem_set, mem, starting_point
    #print left, right
    #sleep(2)
    if (left, right) in mem_set and steps in mem_set[(left, right)]:
        return mem_set[(left, right)][steps]
    if right - left == 1:
        #print 'getting steps for', left, steps, starting_point[left]
        #print 'got ', mem[left][steps][starting_point[left]], 'steps'
        return mem[left][steps][starting_point[left]]
        #return ways(left, starting_point[left], steps)
    val = 0
    split_point =  left + (right - left) / 2 
    for i in xrange(steps + 1):
        t1 = i
        t2 = steps - i
        mix_factor = fact[steps] / (fact[t1] * fact[t2])
        #print "mix_factor = %d, dimension: %d - %d steps, dimension %d - %d steps" % (mix_factor, left, t1, split_point, t2)
        val += mix_factor * set_ways(left, split_point, t1) * set_ways(split_point, right, t2)
    mem_set[(left, right)][steps] = val
    return val

import sys
from time import sleep, time

fact = {}
fact[0] = 1
start = time()
accum = 1
for k in xrange(1, 300+1):
    accum *= k
    fact[k] = accum
#print 'fact_time', time() - start

data = sys.stdin.readlines()
num_tests = int(data.pop(0))
for ignore in xrange(0, num_tests):
    n_and_steps = data.pop(0)
    n, steps = map(lambda x: int(x), n_and_steps.split())
    starting_point = map(lambda x: int(x), data.pop(0).split())
    dimensions = map(lambda x: int(x), data.pop(0).split())
    mem = {}
    for di in xrange(n):
        mem[di] = {}
        for i in xrange(steps + 1):
            mem[di][i] = {}
            ways(di, starting_point[di], i)
    start = time()
    #print 'mem vector is done'
    mem_set = {}
    for i in xrange(n + 1):
        for j in xrange(n + 1):
            mem_set[(i, j)] = {}
    answer = set_ways(0, n, steps)
    #print answer
    print answer % 1000000007
    #print time() - start

2
«это терпит неудачу для большого количества шагов и / или измерений» - что означает здесь «неудача»?
Рафаэль

1
Добро пожаловать! Я отредактировал ваш вопрос следующим образом: а) используйте правильное форматирование Markdown и LaTeX (пожалуйста, сделайте это сами в будущем) и б) удалите лишний желоб. Мы не заботимся о недостатках C-кода; пожалуйста, ограничьте себя идеями , то есть псевдокодом центральных вещей.
Рафаэль

Сбой означает, что он исчерпывает всю доступную системную память, заполняя mem[]словарь. И спасибо, что убрали мой ответ. Не слишком знаком с LaTeX, но постараюсь в следующий раз.
Александр

Вы можете найти помощь по Markdown рядом с окном редактора; см. здесь для начинающих на LaTeX.
Рафаэль

Ответы:


14

Различные размеры независимы . Что вы можете сделать, так это вычислить, для каждого измерения j , сколько различных шагов есть только в этом измерении, которые предпринимают шагов. Назовем это число . Из вашего вопроса вы уже знаете, как вычислить эти числа с помощью динамического программирования.tW(j,t)

Теперь легко подсчитать количество прогулок, которые делают шагов в измерении . У вас есть способов вкрапления измерений, так что общее количество шагов, сделанных в измерении равно , и для каждого из этих способов у вас есть . Суммируйте их, чтобы получить Теперь память находится под контролем, так как вам нужно только запомнить значения . Время увеличивается сверхполиномиально для больших , но у большинства компьютеров гораздо больше времени, чем памяти.tii(Nt1,t2,,tM)itiΠ1NW(i,ti)

t1+t2++tN=M(Mt1,t2,,tN) Πi=1NW(i,ti).
W(j,t)N

Вы можете сделать еще лучше. Рекурсивный разделить размеры на два подмножества, и , и вычислить , сколько ходит там , используя только размеры в подмножество , и только те , в . Назовите эти номера и соответственно. Вы получаете общее количество прогулок поABABWA(t)WB(t)

t1+t2=M(Mt1)WA(t1)WB(t2).

Привет Питер. Хорошо, это было недостающее понимание. Теперь у меня осталось только одно сомнение. Внешняя сумма повторяется по всем возможным комбинациям t1, t2, ... tn, которые суммируются с M. К сожалению, количество таких комбинаций равно C (M + 1, N-1), которое может достигать C (300 +1, 10-9). Очень большое количество ... :(
Александр

1
@Alexandre: Мой второй алгоритм (начиная с «Вы можете сделать еще лучше») не имеет этой проблемы. Я оставил первый алгоритм в своем ответе, потому что это первый, который я придумал, и потому что я думаю, что гораздо проще объяснить второй алгоритм как вариант первого, чем просто дать его без какой-либо мотивации.
Питер Шор

Я реализовал второй алгоритм. Это быстрее, но все еще слишком мало для самых больших границ. Проблема с первым состояла в итерации по всем возможностям t1, t2, t3, ... tn, которые суммировались с M. Второй алгоритм только перебирает решения t1 + t2 = M. Но тогда то же самое должно быть сделано для Wa (t1), перебирая решения t1 '+ t2' = t1. И так далее, рекурсивно. Вот реализация на случай, если вы заинтересуетесь: pastebin.com/e1BLG7Gk . И во втором алгоритме, там должно быть многочлен M над t1, t2 нет?
Александр

Ничего! Решил это! Пришлось также использовать памятку в функции set_ways. Вот окончательное решение, которое очень быстро! pastebin.com/GnkjjpBN Спасибо за ваше понимание, Питер. Вы сделали оба ключевых наблюдения: независимость проблемы и разделяй и властвуй. Я рекомендую людям взглянуть на мое решение, потому что есть некоторые вещи, которых нет в ответе выше, например, функция W (i, ti), нуждающаяся в третьем аргументе, который является позицией. Это должно быть рассчитано для комбинаций значений i, ti и position. Если вы можете, также добавьте t2 к многочлену во втором алгоритме.
Александр

4

Давайте из вашего кода формулу для (для внутренней ячейки, которая игнорирует граничные случаи):now(s,x1,,xn)

now(s,x1,,xn)=+i=0nnow(s1,x1,,xi1,xi+1,xi+1,,xn)+i=0nnow(s1,x1,,xi1,xi1,xi+1,,xn)

Вот несколько идей.

  • Мы видим, что как только вы вычислили все значения для , вы можете отбросить все вычисленные значения для .s=ks<k
  • Для фиксированных значений вы должны вычислять записи таблицы в лексикографическом порядке (просто потому, что это просто). Затем обратите внимание, что каждая ячейка нуждается только в таких ячейках внутри «радиуса одного», то есть никакая координата не может быть дальше, чем единица. Поэтому, как только ваша итерация достигнет , вы можете отбросить все значения для . Если этого недостаточно, сделайте то же самое для - для фиксированного , отбросьте значения с и когда достигается - и так далее.sx1=ix1i2x2x1=ix1=ix2j2x2=j
  • Обратите внимание, что «так что всегда есть возможных различных ходов» выполняется только в середине сетки, то есть если и для всех . Но это также означает , что ответ прост в середине: это просто . Если бы у вас был рабочий рецидив динамического программирования, это само по себе позволило бы сбрить большую часть таблицы (если ).2NxiM>0xi+M<Dii(2N)MMN
  • Следует также отметить, что вам не нужно вычислять всю таблицу; большинство значений будут заполнены любом случае (если ). Вы можете ограничиться (гипер) кубом с длиной ребра вокруг (обратите внимание, что он будет помят из-за путей, выходящих из сетки).0MN2Mx

Этого должно быть достаточно, чтобы сохранить использование памяти достаточно низким.


Привет Рафаэль, скажем, наша цель сейчас (3, 3, 3, 3), на сетке 5x5x5. Используя динамическое программирование и используя лексический порядок, как вы предложили, мы вычислим сейчас (0, 0, 0, 0), затем (0, 0, 0, 1), ... сейчас (0, 5, 5, 5). В какой -то момент мы могли бы отбросить сейчас (0, 0, 0, 0) (что больше , чем радиус одного от (5, 5, 5), так как мы сейчас это нужно, чтобы вычислить сейчас (1, 0, 0 , 0), сейчас (1, 0, 0, 1) и т. Д. Вы упомянули M << N пару раз, но границы составляют 1 <= M <= 300 и 1 <= N <= 10. Итак, , в крайности, не кажется , что 1 << 300.
Александр

1) То , что неясно , в моей второй пуле? Как только вы вычислить , вы можете отказаться . Это не самый ранний момент , где вы можете отказаться , хотя; последняя ячейка вам это нужно есть . 2) Я не слишком обеспокоен ваших конкретных значений и , если честно. Я бы лучше посмотрел на общую проблему. Если вы не имеете , последние две пули не помогут вам много. и должно быть достаточно , чтобы заметить эффект, хотя и ни стратегия никогда не помешает. (2,0,0,0)(0,\*,\*,\*)(0,0,0,0)(1,0,0,0)MNMNM=1N=10
Рафаэль

1
1) пулю я понимаю. Это уменьшает пространственную сложность с M * D ^ N до D ^ N, но D ^ N все еще слишком много. Я не совсем понимаю, как работает 2) пуля. Вы можете использовать пример моего комментария , чтобы проиллюстрировать это?
Александр

@Alexandre Я сделал в своем предыдущем комментарии. Если я читаю как значение , то применение второго один раз уменьшает сложность пространства до , второй раз до и скоро. (Точнее, он идет от к и т. Д.)Dmaxi=1,,NDiDN1DN2i=1NDii=2NDi
Рафаэль

Не совсем понимаю, как это сделать ... Допустим, я понял, и что я уменьшил пространственную сложность до D. По сути, не нужно ли еще решать подзадачи M * D ^ N? Не нужно ли дополнительное свойство, чтобы сделать задачу полиномиальной?
Александр
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.