# Programación Dinámica ###### tags: `temas 2do Parcial` ## Resumen ### Glosario/definiciones ### Trucos * Pensar las N posibilidades que tenemos en cada paso de la recursión * Si hacemos mín sobre la recursión, podemos usar +infinito para filtrar resultados o para los valores no-definidos (lo mismo con máx y -infinito) * "minimtoria" (simil sumatoria/productoria) (ver ejercicio corte de vara / rod cutting) ### Errores comunes * Olvidarse el +1 en la recursión * Olvidarse de devolver el resultado memoizado si ya está seteado * Olvidarse del return en el algoritmo ## Ejercicios de la guia 1, sección "Programación dinámica (y su relación con backtracking" ### 5. Subset sum Salteado porque el enunciado spoilea la función, los algoritmos y la complejidad ### 6. Coin change #### Función recursiva Suponemos que el problema siempre tiene solución (alcanzan los billetes) $B = \{b_1, ..., b_n\}$ $cc(i, c) =$ \begin{cases} ∅ & \text{si $i = 0 ∧ c ≤ 0$} \\ B & \text{si $i = 0 ∧ 0 < c$} \\ \arg\min_{\#}( \\ \ cc(i-1, c - B_i) ∪ B_i, \\ \ cc(i-1, c) \\ ) & \text{sino} \end{cases} #### Algoritmo ```python # B = {b1, ..., bn} B = {2, 3, 5, 10, 20, 20} UNDEFINED = {-1} M[B.size()][UNDEFINED for i in range(0, c)] def cc(i ,c): # coin change if (M[i][c] != UNDEFINED): return M[i][c] if (i == 0 and c ≤ 0): M[i][c] = {} elif (i == 0 and 0 < c): M[i][c] = B else: M[i][c] = argmin(size(), cc(i-1, c - B[i]).union(B[i]), cc(i-1, c) ) return M[i][c] cc(B.size() - 1, 14) ``` #### Complejidad $O(n²c)$ Porque la estructura de memoización tiene tamaño $O(nc)$ pero copiar los conjuntos a cada posición es $O(n)$. ### 7. Astro Void #### Función $p = {p1, p2, ..., pn}$ i: índice en p a: cantidad de asteroides n: tamaño de p av: saldo $av(i, a) =$ \begin{cases} 0, & \text{ si } i < 0 \\ \max( \\ \ -p[i] + av(i-1, a+1), &&\text{// compra} \\ \ av(i-1, a) &&\text{// ninguna operación} \\ ), & \text{ si } a = 0 \\ \max( \\ \ p[i] + av(i-1, a-1), &&\text{// venta} \\ \ -p[i] + av(i-1, a+1), &&\text{// compra} \\ \ av(i-1, a) &&\text{// ninguna operación} \\ ), & sino \end{cases} $av(n-1, 0)$ // para resolver #### Algoritmo ```python P = (3, 2, 5, 6) def av(i, a): if M[i][a] != -infinity: return M[i][a] elif (i < 0): M[i][a] = 0 elif (a == 0): # si no tengo asteroides, solo puedo comprar o seguir de largo M[i][a] = max(-p[i] + av(i-1, a+1), # compra av(i-1, a) # no compro ni vendo ) else: M[i][a] = max(p[i] + av(i-1, a-1), # vende -p[i] + av(i-1, a+1), # compra av(i-1, a) # no compro ni vendo ) return M[i][a] ``` #### Complejidad $O(n²)$ por el tamaño de la matriz (tamaño del vector al cuadrado) y porque completar cada posición es $O(1)$. ### Corte de vara / Rod cutting #### Fórmula Cortes: $L = (2, 4, 7)$ Longitud: $l = 10$ s: start c: cut e: end rc: total rod cutting cost Definimos $L' = (0, 2, 4, 7, l) = (0, 2, 4, 7, 10)$ $rc(s, e) = e - s + \min_{c \in L' \\ s < c < e} ( rc(s, c) + rc(c, e) )$ Llamado que resuelve el problema: $rc(0, |L'| - 1)$ #### Algoritmo ```python L_prima = [0, 2, 4, 7, 10] min_cut = INFINITY M[L_prima.size()][INFINITY for i in range(0, L_prima.size())] def rc(s, e): if M[s][e] != INFINITY: return M[s][e] for c in L_prima: min_cut = min(min_cut, rc(s, c) + rc(c, e)) return M[s][e] = e-s + min_cut return M[s][e] ``` #### Complejidad $O(n³)$ ($O(n²)$ por el tamaño de la matriz de memoización y $O(n)$ por el costo computacional de cada nodo) ### 9: terreno pociones trampas vida mínima #### Fórmula ![](https://i.imgur.com/8MYBVU4.png) #### Algoritmo ![](https://i.imgur.com/UqzC5Fx.png) #### Complejidad la matriz ### 10: apilar cajas #### Fórmula w: pesos $w = [19, 7, 5, 6, 1]$ s: soporte $s = [15, 13, 7, 8, 2]$ $N$: cantidad de cajas $s'$: soporte de la caja de menor soporte (eslabón más débil/reactivo limitante) $ac(i, s') =$ \begin{cases} 0, && \text{si } i = N \\ ac(i+1, s'), && \text{si la caja } i \text{ no es apilable}\\ \max( &ac(i+1, \min(s' - w[i], s[i]) ) + 1,\\ &ac(i+1, s' ) ),& \text{si la caja } i \text{ es apilable} \end{cases} $ac(0, +inf)$ $apilable(i, s') = 0 < s' - w[i]$ #### Algoritmo ```python UNDEFINED = -infinity M[w.size()][UNDEFINED for i in range(0, w.size())] def ac(i, sp): if M[i][sp] != UNDEFINED return M[i][sp] if i == N: return 0 if apilable(i, sp): M[i][sp] = ac(i+1, sp) else: M[i][sp] = max( ac(i+1, min(sp - w[i], s[i]) + 1 ), ac(i+1, sp )) return M[i][sp] def apilable(i, sp): return 0 < sp - w[i] ac(0, infinity) ``` #### Complejidad tamaño de la matriz ### 11: Intercalar operaciones al vector **TODO: CONSULTAR** #### Fórmula Hacemos versión de decisión $f(v, w, i, w_a) =$ \begin{cases} True, && \text{si $i = |v|-1 ∧ w = w_a$} \\ False, && \text{si $i = |v|-1 ∧ w ≠ w_a$} \\ f(v, w, i, w_a + v_{i+1}) ∨ f(v, w, i, w_a * v_{i+1}) ∨ f(v, w, i, w_a ↑ v_{i+1}), && \text{caso contrario} \\ \end{cases} #### Algoritmo No anda :( ```python v = [3, 1, 5, 2, 1] w = 400 UNDEFINED = -999 M = [[UNDEFINED for _ in range(len(v))] for _ in range(w)] def f(i, w_a): print(i, w_a) if (i == len(v)-1 and w_a == w): return True if (i == len(v)-1 and w_a != w): return False if (type(M[i][w_a]) == type(True)): return M[i][w_a] else: if (w < w_a + v[i+1]): M[i][w] = False else: M[i][w_a + v[i+1]] = f(i+1, w_a + v[i]) if (w < w_a * v[i+1]): M[i][w] = False else: M[i][w_a * v[i+1]] = f(i+1, w_a * v[i]) if (w < w_a ** v[i+1]): M[i][w] = False else: M[i][w_a ** v[i+1]] = f(i+1, w_a ** v[i]) return operation_in_range(w_a, i, '+') or operation_in_range(w_a, i, '*') or operation_in_range(w_a, i, '**') print(f(0, v[0])) def operation_in_range(w_a, i, op): if (op == '+'): if (w < w_a): M[i][w] = False return M[i][w] else: M[i][w_a] = f(i+1, w_a + v[i]) return M[i][w_a] if (op == '*'): if (w < w_a): M[i][w] = False return M[i][w] else: M[i][w_a] = f(i+1, w_a * v[i]) return M[i][w_a] if (op == '**'): if (w < w_a): M[i][w] = False return M[i][w] else: M[i][w_a] = f(i+1, w_a ** v[i]) return M[i][w_a] ``` #### Complejidad