Programação dinâmica com grande número de subproblemas


11

Programação dinâmica com grande número de subproblemas. Então, eu estou tentando resolver esse problema da Interview Street:

Grid Walking (Score 50 points)
Você está situado em uma grade N dimensional na posição (x1,x2,...,xN) . As dimensões da grade são (D1,D2,...,DN ). Em uma etapa, você pode dar um passo à frente ou atrás em qualquer uma das N dimensões. (Portanto, sempre existem 2N movimentos diferentes possíveis). De quantas maneiras você pode tomar Metapas de modo que você não saia da grade em nenhum momento? Você sai da grade se, para qualquer xEu , xEu0 0 ou xEu>DEu .

Minha primeira tentativa foi esta solução recursiva memorizada:

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

Grande surpresa: falha em um grande número de etapas e / ou dimensões devido à falta de memória.

Portanto, o próximo passo é melhorar minha solução usando programação dinâmica. Mas antes de começar, estou vendo um grande problema com a abordagem. O argumento starting_pointé um n duplo, onde n é tão grande quanto 10 . Portanto, de fato, a função poderia estar number_of_ways(steps, x1, x2, x3, ... x10)com 1xEu100 .

Os problemas de programação dinâmica que eu já vi nos livros didáticos quase todos têm variáveis ​​twp, de modo que apenas uma matriz bidimensional é necessária. Nesse caso, seria necessária uma matriz de dez dimensões. Então, células no total.10010

mnmin(m,n)

ATUALIZAR

Usando as sugestões de Peter Shor e fazendo algumas correções menores, notavelmente a necessidade de acompanhar a posição na função e, em vez de apenas dividir as dimensões em dois conjuntos A e B, fazendo a divisão recursivamente, efetivamente usando um método de dividir e conquistar, até que um caso base seja alcançado, onde apenas uma dimensão está no conjunto.W(Eu,tEu)

Eu vim com a seguinte implementação, que passou em todos os testes abaixo do tempo máximo de execução:

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
"falha em um grande número de etapas e / ou dimensões" - o que significa "falha" aqui?
Raphael

1
Bem-vinda! Editei sua pergunta para: a) use a formatação apropriada de Markdown e LaTeX (por favor, faça você mesmo no futuro) eb) remova a calha supérflua. Não nos importamos com anúncios em código C; por favor, restrinja-se a idéias , ou seja, pseudo-código das coisas centrais.
Raphael

Falhar significa esgotar toda a memória disponível do sistema, preenchendo o mem[]dicionário. E obrigado por limpar minha resposta. Não está muito familiarizado com o LaTeX, mas fará um esforço na próxima vez.
1040 Alexandre

Você pode encontrar ajuda no Markdown ao lado da caixa do editor; veja aqui uma cartilha sobre o LaTeX.
Raphael

Respostas:


14

As diferentes dimensões são independentes . O que você pode fazer é calcular, para cada dimensão j , quantas caminhadas diferentes existem naquela dimensão que executa etapas. Vamos chamar esse número de W ( j , t ) . Com a sua pergunta, você já sabe como calcular esses números com programação dinâmica.tW(j,t)

Agora, é fácil de contar o número de passeios que levam passos na dimensão i . Você tem maneiras de intercalar dimensões, de modo que o número total de etapas executadas na dimensão seja e, para cada uma dessas maneiras, você ande. Soma-os para obter Agora, a memória está sob controle, pois você só precisa se lembrar dos valores . O tempo cresce superpolinomialmente para grande , mas a maioria dos computadores tem muito mais tempo que memória.tEuEuitiΠN1W(i,Ti)Σt1+t2+...+tN=M(H(Nt1,t2,...,tM)EutEuΠ1NW(Eu,tEu)W(j,t)N

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

Você pode fazer ainda melhor. Recursivamente dividir as dimensões em dois subgrupos, e , e calcular o número de passeios não são usando apenas as dimensões em subconjunto , e apenas aqueles em . Ligue para esses números e , respectivamente. Você obtém o número total de caminhadas porB A B W A ( t ) W B ( t )UMABUMABWUMA(t)WB(t)

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

Olá Peter. Tudo bem, esse foi o insight que faltava. Agora só tenho uma dúvida. A soma externa itera sobre todas as combinações possíveis de t1, t2, ... tn que somam M. Infelizmente, o número de tais combinações é C (M + 1, N-1), que pode ser tão alto quanto C (300 +1, 10-9). Número muito grande ... :(
Alexandre

1
@ Alexandre: Meu segundo algoritmo (começando com "Você pode fazer ainda melhor") não tem esse problema. Eu deixei o primeiro algoritmo na minha resposta porque é o primeiro que eu criei e porque acho muito mais fácil explicar o segundo algoritmo como uma variante do primeiro do que apenas fornecê-lo sem qualquer motivação.
Peter Shor

Eu implementei o segundo algoritmo. É mais rápido, mas baixo demais para os limites maiores. O problema com o primeiro foi iterando sobre todas as possibilidades de t1, t2, t3, ... tn que somaram M. O segundo algoritmo apenas itera sobre soluções para t1 + t2 = M. Mas o mesmo deve ser feito para Wa (t1), iterando sobre soluções para t1 '+ t2' = t1. E assim por diante recursivamente. Aqui está a implementação, caso você esteja interessado: pastebin.com/e1BLG7Gk . E no segundo algoritmo, multinomial deve haver M sobre t1, t2 não?
Alexandre

Deixa pra lá! Resolvi-o! Teve que usar a memorização na função set_ways também. Aqui está a solução final, que é incrivelmente rápida! pastebin.com/GnkjjpBN Obrigado pela sua compreensão, Peter. Você fez as duas observações principais: independência do problema e divisão e conquista. Eu recomendo que as pessoas olhem para minha solução, porque há algumas coisas que não estão na resposta acima, como a função W (i, ti) que precisa de um terceiro argumento, que é a posição. Isso deve ser calculado para combinações de valores de i, ti e posição. Se você puder, adicione t2 o multinomial no seu segundo algoritmo.
Alexandre

4

Vamos extrair uma fórmula para do seu código (para uma célula interna, que está ignorando casos de borda):agora(s,x1,...,xn)

agora(s,x1,...,xn)=+Eu=0 0nagora(s-1,x1,...,xEu-1,xEu+1,xEu+1,...,xn)+Eu=0 0nagora(s-1,x1,...,xEu-1,xEu-1,xEu+1,...,xn)

Aqui estão algumas idéias.

  • Vemos que, depois de calcular todos os valores para , você pode eliminar todos os valores calculados para .s=ks<k
  • Para um fixo , você deve calcular as entradas da tabela em ordem lexicográfica (apenas porque é simples). Observe que todas as células precisam apenas dessas células dentro de um "raio de uma", ou seja, nenhuma coordenada pode estar mais distante que uma. Portanto, quando sua iteração atingir , você poderá eliminar todos os valores de . Se isso não for suficiente, faz o mesmo para - para fixa , valores de queda com e quando é atingido - e assim por diante.sx1=Eux1Eu-2x2x1=Eux1=Eux2j-2x2=j
  • Observe que "para que sempre haja possíveis movimentos diferentes" é válido apenas no meio da grade, ou seja, se e para todos os . Mas isso também significa que a resposta é fácil no meio: é apenas . Se você tivesse uma recorrência de programação dinâmica em funcionamento, isso por si só permitiria raspar a maior parte da tabela (se ).2NxEu-M>0 0xEu+M<DEuEu(2N)MMN
  • Outra coisa a se notar é que você não precisa calcular a tabela inteira; a maioria dos valores será preenchida com qualquer maneira (se ). Você pode restringir-se ao (hiper) cubo de comprimento da borda em torno de (observe que ele será amassado devido a caminhos que saem da grade).0 0MN2Mx

Isso deve ser suficiente para manter o uso de memória bastante baixo.


Oi Raphael, digamos que nosso objetivo seja agora (3, 3, 3, 3), em uma grade 5x5x5. Usando programação dinâmica e usando ordem lex como sugerido, calcularíamos agora (0, 0, 0, 0), depois (0, 0, 0, 1), ... agora (0, 5, 5, 5). Em que ponto podemos descartar agora (0, 0, 0, 0) (que está a mais de um raio de distância de (5, 5, 5), já que agora precisamos dele para calcular agora (1, 0, 0 , 0), agora (1, 0, 0, 1), etc? Você mencionou M << N algumas vezes, mas os limites são 1 <= M <= 300 e 1 <= N <= 10. Então , nos extremos, não parece que 1 << 300.
Alexandre

(2,0 0,0 0,0 0)(0 0,\ *,\ *,\ *)(0 0,0 0,0 0,0 0)(1,0 0,0 0,0 0)MNMNM=1N=10

1
A 1) bala que eu entendo. Isso reduz a complexidade espacial de M * D ^ N para D ^ N, mas D ^ N ainda é muito. Não estou vendo bem como a bala 2) funciona. Você pode usar o exemplo no meu comentário para ilustrá-lo?
1140 Alexandre

DmaxEu=1,...,NDEuDN-1DN-2Eu=1NDEuEu=2NDEu

Não entendo muito bem como fazê-lo ... Digamos que eu entendi e reduzi a complexidade espacial para D. Fundamentalmente, os subproblemas M * D ^ N ainda não precisam ser resolvidos? Não é necessária uma propriedade adicional para tornar o problema polinomial?
Alexandre
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.