¿Cuál es la técnica para crear una solución DP iterativa a partir de su solución recursiva?

Yo mismo he luchado con esto, pero con el tiempo he mejorado. Hay muchos otros que pueden dar una mejor respuesta a esta pregunta, pero lo intentaré de todos modos.

Creo que algunas soluciones de arriba hacia abajo se pueden convertir directamente de abajo hacia arriba, pero otras son muy difíciles de convertir y es mejor usar la memorización en tales casos.


Hagamos esto con un ejemplo.

Calcularemos el coeficiente binomial. La función recursiva para el coeficiente binomial es:

[matemáticas] {n \ elegir r} = {n-1 \ elegir r-1} + {n-1 \ elegir r} [/ matemáticas]

Supongo que eres capaz de encontrar ecuaciones recursivas pero luchas con la base. Entonces, veamos cómo podemos convertir la definición recursiva anterior en un enfoque ascendente.

Definamos la ecuación anterior como una función f.

[matemáticas] f (n, r) = f (n-1, r-1) + f (n-1, r) [/ matemáticas]

Necesitamos una mesa:

La forma más sencilla de convertir un enfoque de arriba abajo a abajo es creando una tabla y recordando todos los estados. Esto es similar a la memorización pero llenamos la tabla iterativamente en lugar de recursivamente en la memorización.

¿Cuál es el tamaño de la mesa?

Echemos un vistazo a la función nuevamente.

[matemáticas] f (n, r) = f (n-1, r-1) + f (n-1, r) [/ matemáticas]

Nuestra respuesta es el valor f (n, r). Por lo tanto, necesitamos dos dimensiones. El tamaño de las dos dimensiones depende de dónde se encuentre su respuesta. Nuestra respuesta es el valor f (n, r), por lo tanto, necesitamos que la tabla sea de tamaño [matemática] n \ veces r [/ matemática]. Podemos optimizar el espacio y lo haremos más tarde.

Por lo tanto, una matriz bidimensional de tamaño [math] n \ times r [/ math] será suficiente. Ahora, ¿cómo debemos llenar esta matriz? orden mayor de fila, orden mayor de columna? Algo más ?

Cuando miramos nuestra función; para calcular la función [matemáticas] f (i, j) [/ matemáticas], necesitamos valores [matemáticas] f (i-1, j-1) [/ matemáticas] y [matemáticas] f (i-1, j) [/mates]. Por lo tanto, debemos llenarlo de una manera que (i-1, j-1) y (i-1, j) se llene antes de que se llene (i, j). Esto se puede lograr llenando la matriz en orden mayor de fila u orden mayor de columna.
Ahora solo necesitamos dos bucles anidados para llenar la matriz en orden de fila mayor o columna mayor. También necesitamos completar los valores iniciales, es decir, la fila 0 y la columna 0.

int dp[n][r];
for(int i=1;i<n;i++)
{
for(int j=1;j<r;j++)
{
dp[i][j] = dp[i-1][j-1] + dp[i-1][j];
}
}

Ya casi hemos terminado. Prometimos que optimizaremos el espacio. Tenga en cuenta que cuando llenamos una fila, solo necesitamos una fila anterior y no todas las filas anteriores. Por lo tanto, necesitamos un espacio auxiliar O (r) y podemos barajar entre las filas actuales y anteriores.

Solo cambio en el código:

int dp[2][r];
for(int i=1;i<n;i++)
{
for(int j=1;j<r;j++)
{
dp[i%2][j] = dp[(i-1)%2][j-1] + dp[(i-1)%2][j];
}
}

Espero que ayude.

Entonces, ¿qué hace tu recursión de arriba hacia abajo?

Habría alguna función de algún estado que desea calcular. Llámalo F (S). La recursión entonces intentaría ramificarse en algunos subproblemas e intentar calcular la función para otros estados F (S ‘). Eventualmente, si su recursión es correcta, terminará llamando a la función para algunos estados base para los cuales conoce la respuesta directamente sin ninguna ramificación adicional, y la recursión terminaría allí.

Una propiedad importante que debería tener esa función recursiva es que no debería haber dependencias cíclicas . Es decir, si está tratando de calcular F (S), no debe terminar ramificándose a F (S ‘), F (S’ ‘) … y eventualmente terminar de nuevo en S. Obviamente, si eso sucede, lo hará caer en un bucle infinito.

Con esta observación clave, intentemos hacer un gráfico de todos los estados posibles y la dependencia entre ellos. Los estados en los que se llamará a la función son los vértices del gráfico. Si la evaluación de F (S) se repite directamente en F (S ‘), habrá un borde dirigido de S a S’ en el gráfico. Como el gráfico está dirigido y no tiene ciclos, es un gráfico acíclico dirigido (DAG).

Esto es lo principal que hay que entender al convertir un problema general de recursión de arriba hacia abajo en una evaluación de abajo hacia arriba. Tiene un DAG, y la manera de asegurarse de evaluar los vértices solo después de que se hayan satisfecho sus dependencias es hacer la evaluación de acuerdo con algún tipo de topología del DAG.

No existe una receta general para generar el tipo topológico del gráfico de estado sin generar el gráfico en sí. De hecho, esto último podría ser más costoso que la recursión memorizada. Sin embargo, lo que puede hacer es mirar la recurrencia cuidadosamente y pensar mucho. Es posible que pueda encontrar algunos ordenamientos de los estados que garantizarán que no se requiera un estado anterior para llamar a un estado posterior. Anon ha dado un buen ejemplo aquí (¿Por qué anon con esto? La gente debería saber quién eres). La idea aproximada es tomar los parámetros del estado y generar alguna función a partir de ellos, lo que garantiza que el valor de la función de cada estado en el que recurre directamente sea estrictamente más bajo que el valor de la función de su propio estado.

Permítanme usar el ejemplo de anon para explicar. (n, r) es el estado para el que desea evaluar la función. Recurre directamente a los estados (n-1, r-1) y (n-1, r) a partir de ahí. Solo necesitamos alguna función f (n, r) que garantice que f (n, r) sea mayor que f (n-1, r) yf (n-1, r-1). Un candidato simple es f (n, r) = n + r. Es decir, si evalúa los estados en orden creciente de n + r (separe los vínculos de cualquier forma que desee), podrá realizar la evaluación de abajo hacia arriba correctamente. Esto sugeriría el llenado de la tabla DP en el orden (0,0) → (0,1) → (1,0) → (0,2) → (1,1) → (2,0) … Es Es fácil ver que esto hace el trabajo. Por supuesto, hay otras funciones que también hacen el trabajo. f (n, r) = n * NMAX + r proporciona el orden de evaluación principal de la fila habitual, mientras que f (n, r) = n + r * NMAX proporciona el orden de evaluación principal de la columna.

Encontrar la función en sí podría ser difícil en problemas complicados. Pero en tales casos, la recursividad memorable es probablemente la mejor manera de proceder.

Antes de responder a su pregunta, ¿por qué desearía crear una solución iterativa a partir de una solución recursiva? ¿Complejidad del tiempo? No. Es exactamente lo mismo.
¿Legibilidad? Digo que la solución recursiva tiene más sentido mientras lee. ¿Actuación? No mucha diferencia. La solución iterativa podría funcionar mejor debido a menos errores de caché. ¿Pero realmente hace una gran diferencia? No lo creo. ¿O te estás quedando sin espacio de pila debido a la recursividad? Por lo general, eso no sucede a menos que alguien haya establecido que el límite de pila sea realmente menor.

De todos modos, para responder a tu pregunta. Todo depende de la recursividad de cómo crear una solución iterativa.

En el caso de 1D DP es realmente fácil. Por ejemplo, considere las series [matemáticas] F (N) = F (N-1) + F (N-2) +… + F (NK) [/ matemáticas]. La solución iterativa se puede escribir como:

dp [N]
para i = 1 a N:
para j = N-1 a NK:
dp [i] + = dp [j]

Las cosas pueden ponerse un poco locas en el caso de DP 2D dependiendo de qué recursividad sea. Si es algo así como [matemáticas] F (i, j) = F (i-1, j_1) + F (i-1, j_2) +… F (i-1, j_k) [/ matemáticas], entonces notamos que la respuesta para una fila depende de las respuestas de la fila anterior, por lo que llenamos nuestra tabla de DP en fila.
Ahora, considere algo como [matemáticas] F (i, j) = \ sum_ {k = i} ^ {j} F (i, k) + F (k, j) [/ matemáticas]. En este caso, es un poco difícil darse cuenta de la solución iterativa. Necesitamos llenar la tabla DP en forma diagonal aquí (piense un poco y debería poder formalizarla).

Con dimensiones crecientes aumenta la dificultad de escribir soluciones iterativas. A veces, puede que no sea posible escribir una solución iterativa. Y por qué incluso perder un tiempo precioso en un concurso de programación que formaliza soluciones iterativas, cuando la recursión con la memorización funciona igual.

A2A

Solo mantén lo que estás haciendo por un tiempo (de arriba hacia abajo, conviértelo)

Luego, regrese a estos problemas en los que hizo los 2 pasos … y resuélvalos nuevamente, pero esta vez de abajo hacia arriba directamente

Luego, en la práctica, siempre hazlos de abajo hacia arriba

para cuando tus habilidades suban

Resuelva los problemas del proyector para comprender los problemas de DP de abajo hacia arriba. No puedes resolver esos problemas usando la fuerza bruta. Pero eventualmente comprenderá que utilizando solo el enfoque DP de abajo hacia arriba puede resolver esos problemas. Si desea un enfoque a nivel del suelo de DP ascendente, resuélvalos.