# Ejercicio 5 practica 1
### Ejercicio 5: Problema de la Suma de Subconjuntos usando Programación Dinámica
## Consigna
En este ejercicio, vamos a resolver el problema de la suma de subconjuntos utilizando la técnica de programación dinámica.
a) Sea $n = |C|$ la cantidad de elementos de $C$. Considerar la siguiente función recursiva $ss'_{C} : \{0, \ldots , n\} \times \{0, \ldots , k\} \rightarrow \{V, F\}$ (donde $V$ indica verdadero y $F$ falso) tal que:
$$
ss'_{C}(i, j) =
\begin{cases}
j = 0 & \text{si } i = 0\\
ss'_{C}(i- 1, j) & \text{si } i \neq 0 \land C[i] > j\\
ss'_{C}(i- 1, j) \lor ss'_{C}(i- 1, j - C[i]) & \text{si no}
\end{cases}
$$
Convencerse de que esta es una definición equivalente de la función $ss$ del inciso e) del Ejercicio 1, observando que $ss(C, k) = ss'_{C}(n, k)$. En otras palabras, convencerse de que el algoritmo del inciso f) es una implementación por backtracking de la función $ss'_{C}$. Concluir, pues, que $O(2^n)$ llamadas recursivas de $ss'_{C}$ son suficientes para resolver el problema.
b) Observar que, como $C$ no cambia entre llamadas recursivas, existen $O(nk)$ posibles entradas para $ss'_{C}$. Concluir que, si $k \ll 2^n/n$, entonces necesariamente algunas instancias de $ss'_{C}$ son calculadas muchas veces por el algoritmo del inciso f). Mostrar un ejemplo donde se calcule varias veces la misma instancia.
c) Considerar la estructura de memoización (i.e., el diccionario) $M$ implementada como una matriz de $(n+ 1) \times (k+ 1)$ tal que $M[i, j]$ o bien tiene un valor indefinido $\bot$ o bien tiene el valor $ss'_{C}(i, j)$, para todo $0 \leq i \leq n$ y $0 \leq j \leq k$. Convencerse de que el siguiente algoritmo top-down mantiene un estado válido para $M$ y computa $M[i, j] = ss'_{C}(i, j)$ cuando se invoca $ss'_{C}(i, j)$.
1. Inicializar $M[i, j] = \bot$ para todo $0 \leq i \leq n$ y $0 \leq j \leq k$.
1. subset_sum($C, i, j$): // implementa $ss({c_1, \ldots , c_i}, j) = ss'_{C}(i, j)$ usando memoización
1. Si $j < 0$, retornar falso
1. Si $i = 0$, retornar $(j = 0)$
1. Si $M[i, j] = \bot$:
1. Poner $M[i, j] = \text{subset_sum}(C, i- 1, j) \lor \text{subset_sum}(C, i- 1, j - C[i])$
1. Retornar $M[i, j]$
d) Concluir que $\text{subset_sum}(C, n, k)$ resuelve el problema. Calcular la complejidad y compararla con el algoritmo $\text{subset_sum}$ del inciso f) del Ejercicio 1. ¿Cuál algoritmo es mejor cuando $k \ll 2^n$? ¿Y cuándo $k \gg 2^n$?
e) Supongamos que queremos computar todos los valores de $M$. Una vez computados, por definición, obtenemos que
$$
M[i, j] = ss'_{C}(i, j)
$$
$$
ss' = ss'_{C}(i- 1, j) \lor ss'_{C}(i- 1, j - C[i])
$$
$$
= M[i- 1, j] \lor M[i- 1, j - C[i]]
$$
cuando $i > 0$, asumiendo que $M[i- 1, j-C[i]]$ es falso cuando $j-C[i] < 0$. Por otra parte, $M[0, 0]$ es verdadero, mientras que $M[0, j]$ es falso para $j > 0$. A partir de esta observación, concluir que el siguiente algoritmo bottom-up computa $M$ correctamente y, por lo tanto, $M[i, j]$ contiene la respuesta al problema de la suma para todo $\{c_1, \ldots , c_i\}$ y $j$.
1) subset_sum(C, k): // computa M [i, j] para todo 0 ≤ i ≤ n y 0 ≤ j ≤ k.
2) Inicializar M [0, j] := (j = 0) para todo 0 ≤ j ≤ k.
3) Para i = 1, . . . , n y para j = 0, . . . , k:
4) Poner M [i, j] := M [i − 1, j] ∨ (j − C[i] ≥ 0 ∧ M [i − 1, j − C[i]])
## Resolución
- Recordar como es la suma de subconjuntos
- Que querías sumar hasta llegar a un $k$, etc, etc.
## a)
- Tiene sentido, pues eseta función recursiva es igual a la implementación del inciso 1_F.
## b)
- Tomemos el ejemplo de $C= ${$6,12,6$} y al número que queremos llegar es 12. Si vemos el arbol de recursión se repiten varios calculos.
![](https://apuntes.grunt.ar/uploads/95ede9b5-3459-46d5-8ba8-c9134a8d4d9d.png)
Recomiendo tocar click derecho y abrir en una nueva ventana par auna mejor vista
![](https://apuntes.grunt.ar/uploads/28f3516c-8dd6-4540-a0c2-5ec473b611ad.png)
- Al hacer una busqueda exhaustiva, explora las mismas opciones y las vuelve a chequear.
- Otro ejemplo puede ser $C$ = {6,12,6,7,7,7} con $k=19$
- el algoritmo de backtracking va a hacer varias sumas parciales mas de una vez
## c)
- El paso 6 del algoritmo es crucial ya que evita recalculaciones innecesarias del algoritmo $(n+1) \times (k+1)$
## d)
- El algoritmo es capaz de identificar si existe un subconjunto $C$ que sume exactamente $k$
![](https://apuntes.grunt.ar/uploads/c5a7bebd-f982-45e5-92b8-cadaaf90a4f3.png)
![](https://apuntes.grunt.ar/uploads/ea8761e3-ec76-4cc5-9c7c-1ef348615e63.jpeg)
- Hay $n \times k$ subproblemas unicos, y por su tabla de memoizacoin es de $(n+1) \times (k+1)$.
- la complejidad temporal es el producto total de subproblemas por el trabajo realizado por subproblema unico.
- por cada $n$ se podría sumar desde 0 hasta $k$ $O(n \times k)$.
- El algoritmo es definitivamente mejor con programación dinamica.
- O sea cuando $k << 2^n$ porque el número de subproblemas unicos a resolver es menor.
- Pero si $k$ es mas grande que $2^n$ $\Rightarrow$ $O(n \times k)$ va a ser mucho mas mayor.
## e)
- Lo que se nos está diciendo es que este algoritmo iterativo es equivalente al algoritmo top-down recursivo.
## f-g)
- [resolución](https://www.youtube.com/watch?v=dQw4w9WgXcQ&pp=ygUXbmV2ZXIgZ29ubmEgZ2l2ZSB5b3UgdXA%3D)