Optimización de Knuth en Programación Dinámica

La optimización de Knuth es una herramienta muy poderosa en programación dinámica , que puede usarse para reducir la complejidad temporal de las soluciones principalmente de O(N 3 ) a O(N 2 ) . Normalmente, se usa para problemas que se pueden resolver usando el rango DP, suponiendo que se cumplan ciertas condiciones. En este artículo, primero exploraremos la optimización en sí y luego resolveremos un problema con ella.

Criterios de optimización:

La optimización de Knuth se aplica a problemas de DP con una transición de la siguiente forma:

dp[i][j] = coste[i][j] + min i≤k<j (dp[i][k] + dp[k+1][j])

Además, la función de costo debe satisfacer las siguientes condiciones (para a ≤ b ≤ c ≤ d) –

  1. Es un monótono en la red de intervalos (MLI) [costo (b, c) ≤ costo (a, d)]
  2. Satisface la desigualdad del cuadrilátero (QI) [coste(a, c) + coste(b, d) ≤ coste(b, c) + coste(a, d)]

Mejoramiento:

Para una solución que se ejecuta en el tiempo O(N 3 ) , iteraríamos a través de todos los valores de k desde i hasta j-1 . Sin embargo, podemos hacerlo mejor que esto usando la siguiente optimización:

  • Definamos otra array además de la array dp: opt[N][N] . Defina opt[i][j] como el valor máximo (o mínimo) de k para el cual dp[i][j] se minimiza en la transición dp.
    opt[i][j] = argmin i≤k<j (dp[i][k] + dp[k+1][j])
  • La clave para la optimización de Knuth y varias otras optimizaciones en DP, como la optimización Divide and Conquer (que no debe confundirse con el algoritmo divide y vencerás explicado en este artículo ), es la siguiente desigualdad:

optar[i][j-1] ≤ optar[i][j] ≤ optar[i+1][j]

  • Esto ayuda porque ahora, para cada transición dp[] para calcular dp[i][j] , solo tenemos que probar los valores de k entre opt[i][j-1] y opt[i+1][j] en su lugar entre i y j-1 . Esto reduce la complejidad del tiempo del algoritmo por un factor lineal, lo que es una mejora significativa.

Prueba de corrección:

Para probar la corrección de este algoritmo, solo necesitamos probar la desigualdad

opt[i][j-1] ≤ opt[i][j] ≤ opt[i+1][j] .

Siga la siguiente sección para la prueba de la corrección del algoritmo:

Supuestos: Si cost(i, j) es un MLI y satisface la Desigualdad del Cuadrángulo, entonces dp[i][j] también satisface la desigualdad.  
Ahora, considere la siguiente configuración: 

  • Para i < j , tenemos algunos índices i ≤ p ≤ q < j-1 . 
  • Sea dp k [i][j] = costo[i][j] + dp[i][k] + dp[k+1][j]

Si podemos demostrar que:

dp p [i][j-1] ≥ dp q [i][j-1] ⇒ dp p [i][j] ≥ dp q [i][j] 

luego establecer q = opt[i][j-1] , quedará claro que opt[i][j] será al menos tanto como opt[i][j-1] , debido a la implicación de lo anterior desigualdad para todos los índices p menores que opt[i][j-1]
Esto probará que opt[i][j-1] ≤ opt[i][j] .

Prueba: Usando la desigualdad del cuadrilátero en la array dp , obtenemos:

dp[p+1][j-1] + dp[q+1][j] ≤ dp[q+1][j-1] + dp[p+1][j]
⇒ (dp[i, p ] + dp[p+1][j-1] + costo(i, j-1)) + (dp[i, q] + dp[q+1][j] + costo(i, j))
     ≤ (dp[i, q] + dp[q+1][j-1] + costo(i, j-1)) + (dp[i, p] + dp[p+1][j] + costo( i, j))
⇒ dp p [i][j-1] + dp q [i][j] ≤ dp p [i][j] + dp q [i][j-1]
⇒ dp p [i ][j-1] – dp q [i][j-1] ≤ dp p [i][j] – dp q [i][j]

dp p [i][j-1] ≥ dp q [i][j-1] 
⇒ 0 ≤ dp p [i][j-1] – dp q [i][j-1] ≤ dp p [i ][j] – dp q [i][j] 
⇒ dp p [i][j] ≥ dp q [i][j]. 

Por tanto, se demuestra que: dp p [i][j-1] ≥ dp q [i][j-1] ⇒ dp p [i][j] ≥ dp q [i][j]

La segunda parte de la desigualdad (es decir , opt[i][j] ≤ opt[i+1][j] ) se puede mostrar con la misma idea, comenzando con la desigualdad

dp[i][p]+dp[i+1][q] ≤ dp[i][q]+dp[i+1][p] .

La prueba de estas dos desigualdades combinadas da: opt[i][j-1] ≤ opt[i][j] ≤ opt[i+1][j]

Ejemplo:

Dada una array arr[] de N elementos, la tarea es encontrar el costo mínimo para reducir la array a un solo elemento en N-1 operaciones donde en cada operación: 

  • Elimine los elementos en los índices i e i+1 para algún índice i válido , reemplazándolos con su suma. 
  • El costo de hacerlo es arr[i] + arr[i+1] , donde arr[] es el estado del arreglo justo antes de la operación.
  • Este coste se sumará al coste final.

Ejemplos: 

Entrada: arr[] = {3, 4, 2, 1, 7}
Salida: 37
Explicación: 
elimine los elementos en el índice 0 y 1. arr[] = {7, 2, 1, 7}, Costo = 3 + 4 = 7
Eliminar los elementos del índice 1 y 2. arr[] = {7, 3, 7}, Costo = 2 + 1 = 3
Elimina los elementos del índice 1 y 2, arr[] = {7, 10}, Costo = 3 + 7 = 10
Elimina los dos últimos elementos. arr[] = {17}, Costo = = 7 + 10 = 17
Costo total = 7 + 3 + 10 + 17 = 37
Este es el costo total mínimo posible para esta array.

Entrada: arr[] = {1, 2, 3, 4}
Salida: 19
Explicación: 
elimine los elementos de índice 0 y 1. array[] = {3, 3, 4}. Costo = 1 + 2 = 3
Eliminar los elementos de índice 0 y 1. array[] = {6, 4}. Costo = 3 + 3 = 6
Elimine los elementos de índice 0 y 1. array[] = {10}. Costo = 6 + 4 = 10
Costo total = 3 + 6 + 10 = 19.
Este es el mínimo costo posible.

 

Solución subóptima (usando Range DP): El problema se puede resolver usando la siguiente idea: 

  • Sea arr[] la array original antes de realizar cualquier modificación. 
  • Para un elemento de la array que se haya derivado de los índices i a j de a[], el costo de la operación final para formar este único elemento será la suma arr[i] + arr[i+1] + . . . + arr[j] . Sea este valor denotado por la función costo(i, j).
  • Para encontrar el costo mínimo para la sección arr[i, i+1, … j] , considere el costo de convertir los pares de subarreglos arr[i, i+1. . . k] & arreglo[k+1, k+2 . . . j]en elementos individuales , y elija el mínimo entre todos los valores posibles de k de iaj-1 (ambos inclusive).

Para implementar la idea anterior:

  • La función de costo se puede calcular en tiempo constante con preprocesamiento, utilizando una array de suma de prefijos :
    • Calcule la suma de prefijos (digamos almacenados en la array pref[]).
    • Entonces, el costo (i, j) se puede calcular como (pref [j] – pref [i-1]).
  • Travesía de i = 0 a N-1:
    • Traverse j = i+1 a N-1 para generar todo el subarreglo del arreglo principal:
      • Resuelva este problema para todos estos subarreglos posibles con la siguiente transición dp: 
        dp[i][j] = cost(i, j) + min i≤k≤j-1 (dp[i][k] + dp[k+ 1][j]) como se explica en la idea anterior.
  • Aquí dp[i][j] es el costo mínimo de aplicar (j – i) operaciones en el subarreglo arr[i, i+1, . . . j] para convertirlo en un solo elemento. 
    cost(i, j) indica el costo de la operación final, es decir, el costo de sumar los dos últimos valores para convertir arr[i, i+1, . . ., j] a un solo elemento.
  • La respuesta final se almacenará en dp[0][N-1] .

A continuación se muestra la implementación del enfoque anterior.

C++

// C++ code to implement the approach
 
#include <bits/stdc++.h>
using namespace std;
 
// Function to find the minimum cst
int minCost(int arr[], int N)
{
    // Creating the prefix sum array
    int pref[N+1], dp[N][N];
    pref[0] = 0;
    memset(dp, 0, sizeof(dp));
   
    // Loop to calculate the prefix sum
    for (int i = 0; i < N; i++) {
        pref[i + 1] = pref[i] + arr[i];
    }
 
    // Iterating through all subarrays
    // of length 2 or greater
    for (int i = N - 2; i >= 0; i--) {
        for (int j = i + 1; j < N; j++) {
             
            // Cost function = sum of
            // all elements in the subarray
            int cost = pref[j + 1] - pref[i];
            dp[i][j] = INT_MAX;
            for (int k = i; k < j; k++) {
                 
                // dp transition
                dp[i][j]
                = min(dp[i][j], dp[i][k]
                      + dp[k + 1][j] + cost);
            }
        }
    }
 
    // Return answer
    return dp[0][N - 1];
}
 
// Driver code
int main()
{
    int arr[] = { 3, 4, 2, 1, 7 };
    int N = sizeof(arr) / sizeof(arr[0]);
     
    // Function call
    cout << minCost(arr, N);
    return 0;
}

Java

// Java code for the above approach
import java.io.*;
import java.lang.*;
import java.util.*;
 
class GFG {
 
// Function to find the minimum cst
static int minCost(int[] arr, int N)
{
    // Creating the prefix sum array
    int pref[] = new int[N+1];
    int dp[][] = new int[N][N];
    pref[0] = 0;
   
    // Loop to calculate the prefix sum
    for (int i = 0; i < N; i++) {
        pref[i + 1] = pref[i] + arr[i];
    }
 
    // Iterating through all subarrays
    // of length 2 or greater
    for (int i = N - 2; i >= 0; i--) {
        for (int j = i + 1; j < N; j++) {
             
            // Cost function = sum of
            // all elements in the subarray
            int cost = pref[j + 1] - pref[i];
            dp[i][j] = Integer.MAX_VALUE;
            for (int k = i; k < j; k++) {
                 
                // dp transition
                dp[i][j]
                = Math.min(dp[i][j], dp[i][k]
                      + dp[k + 1][j] + cost);
            }
        }
    }
 
    // Return answer
    return dp[0][N - 1];
}
 
// Driver Code
public static void main (String[] args) {
    int[] arr = { 3, 4, 2, 1, 7 };
    int N = arr.length;
     
    // Function call
    System.out.println(minCost(arr, N));
}
}
 
// This code is contributed by sanjoy_62.

C#

// C# program for the above approach
using System;
using System.Collections.Generic;
 
class GFG
{
 
// Function to find the minimum cst
static int minCost(int[] arr, int N)
{
    // Creating the prefix sum array
    int[] pref = new int[N+1];
    int[,] dp = new int[N, N];
    pref[0] = 0;
   
    // Loop to calculate the prefix sum
    for (int i = 0; i < N; i++) {
        pref[i + 1] = pref[i] + arr[i];
    }
 
    // Iterating through all subarrays
    // of length 2 or greater
    for (int i = N - 2; i >= 0; i--) {
        for (int j = i + 1; j < N; j++) {
             
            // Cost function = sum of
            // all elements in the subarray
            int cost = pref[j + 1] - pref[i];
            dp[i, j] = Int32.MaxValue;
            for (int k = i; k < j; k++) {
                 
                // dp transition
                dp[i, j]
                = Math.Min(dp[i,j], dp[i,k]
                      + dp[k + 1, j] + cost);
            }
        }
    }
 
    // Return answer
    return dp[0, N - 1];
}
 
 
// Driver Code
public static void Main(String[] args)
{
        int[] arr = { 3, 4, 2, 1, 7 };
    int N = arr.Length;
     
    // Function call
    Console.WriteLine(minCost(arr, N));
}
}
 
// This code is contributed by avijitmondal1998.

Javascript

<script>
       // JavaScript program for the above approach
 
       // Function to find the minimum cst
       function minCost(arr, N)
       {
        
           // Creating the prefix sum array
           let pref = new Array(N + 1), dp = new Array(N);
 
           for (let i = 0; i < dp.length; i++) {
               dp[i] = new Array(N);
           }
           for (let i = 0; i < dp.length; i++) {
               for (let j = 0; j < dp[i].length; j++) {
                   dp[i][j] = 0;
               }
           }
           pref[0] = 0;
 
 
           // Loop to calculate the prefix sum
           for (let i = 0; i < N; i++) {
               pref[i + 1] = pref[i] + arr[i];
           }
 
           // Iterating through all subarrays
           // of length 2 or greater
           for (let i = N - 2; i >= 0; i--) {
               for (let j = i + 1; j < N; j++) {
 
                   // Cost function = sum of
                   // all elements in the subarray
                   let cost = pref[j + 1] - pref[i];
                   dp[i][j] = Number.MAX_VALUE;
                   for (let k = i; k < j; k++) {
 
                       // dp transition
                       dp[i][j]
                           = Math.min(dp[i][j], dp[i][k]
                               + dp[k + 1][j] + cost);
                   }
               }
           }
 
           // Return answer
           return dp[0][N - 1];
       }
 
       // Driver code
       let arr = [3, 4, 2, 1, 7];
       let N = arr.length;
 
       // Function call
       document.write(minCost(arr, N));
 
   // This code is contributed by Potta Lokesh
 
   </script>
Producción

37

Complejidad temporal: O(N 3 )
Espacio auxiliar: O(N 2

Solución óptima (usando la optimización de Knuth) :

Las condiciones para aplicar la optimización de Knuth son válidas para esta pregunta:

cost(i, j) es simplemente la suma de todos los elementos en el subarreglo delimitado por los índices iy j . Además, la función de transición de la programación dinámica coincide con el caso general para aplicar esta técnica.

Siga los pasos mencionados aquí para implementar la idea de la optimización de Knuth:

  • Defina la array opt[N][N] y la array dp[][]
  • Ahora, procese los subarreglos en orden creciente de longitud, con el estado inicial dp[i][i] = 0 y opt[i][i] = i ( porque el valor debe ser i para minimizar el valor dp[i][ yo] ).
  • Así, cuando llegamos al subarreglo arr[i, . . ., j] , ya se conocen los valores de opt[i][j-1] y opt[i+1][j] . Entonces, solo verifique la condición opt[i][j-1] ≤ k ≤ opt[i+1][j] , para encontrar opt[i][j] y dp[i][j]
  • Aquí también la función de costo se calcula de la misma manera que en el enfoque anterior utilizando la array de suma de prefijos .

Nota: En el código que se proporciona a continuación, se utiliza una forma diferente de recorrer todos los subconjuntos (en lugar de recorrer en orden creciente de longitud). Sin embargo, asegúrese de que opt[i+1][j] y opt[i][j- 1] se calculan antes de dp[i][j].

A continuación se muestra la implementación del enfoque anterior. 

C++

// C++ code to implement the approach
 
#include <bits/stdc++.h>
using namespace std;
 
// Function to find the minimum possible cost
int minCost(int arr[], int N)
{
    // Creating the prefix sum array
    int pref[N + 1], dp[N][N], opt[N][N];
    pref[0] = 0;
    memset(dp, 0, sizeof(dp));
    memset(dp, 0, sizeof(dp));
   
    // Loop to calculate the prefix sum
    for (int i = 0; i < N; i++) {
        pref[i + 1] = pref[i] + arr[i];
        opt[i][i] = i;
    }
 
    // Iterating through all sub-arrays
    // of length 2 or greater
    for (int i = N - 2; i >= 0; i--) {
        for (int j = i + 1; j < N; j++) {
 
            // Cost function = sum of
            // all elements in the sub-array
            int cost = pref[j + 1] - pref[i];
            int mn = INT_MAX;
            for (int k = opt[i][j - 1];
                 k <= min(j - 1, opt[i + 1][j]);
                 k++) {
                if (mn >= dp[i][k]
                    + dp[k + 1][j] + cost) {
 
                    // Updating opt table
                    opt[i][j] = k;
 
                    // Updating minimum cost
                    mn = dp[i][k]
                         + dp[k + 1][j] + cost;
                }
            }
 
            // dp transition
            dp[i][j] = mn;
        }
    }
    return dp[0][N - 1];
}
 
// Driver code
int main()
{
    int arr[] = { 3, 4, 2, 1, 7 };
    int N = sizeof(arr) / sizeof(arr[0]);
 
    // Function call
    cout << minCost(arr, N);
    return 0;
}

Java

// Java code to implement the approach
import java.io.*;
 
class GFG
{
 
// Function to find the minimum possible cost
static int minCost(int arr[], int N)
{
    // Creating the prefix sum array
    int pref[] = new int[N + 1];
    int dp[][] = new int[N][N];
    int opt[][] = new int[N][N];
    pref[0] = 0;
     
    // Loop to calculate the prefix sum
    for (int i = 0; i < N; i++) {
        pref[i + 1] = pref[i] + arr[i];
        opt[i][i] = i;
    }
 
    // Iterating through all sub-arrays
    // of length 2 or greater
    for (int i = N - 2; i >= 0; i--) {
        for (int j = i + 1; j < N; j++) {
 
            // Cost function = sum of
            // all elements in the sub-array
            int cost = pref[j + 1] - pref[i];
            int mn = Integer.MAX_VALUE;
            for (int k = opt[i][j - 1];
                 k <= Math.min(j - 1, opt[i + 1][j]);
                 k++) {
                if (mn >= dp[i][k]
                    + dp[k + 1][j] + cost) {
 
                    // Updating opt table
                    opt[i][j] = k;
 
                    // Updating minimum cost
                    mn = dp[i][k]
                         + dp[k + 1][j] + cost;
                }
            }
 
            // dp transition
            dp[i][j] = mn;
        }
    }
    return dp[0][N - 1];
}
 
  // Driver code
  public static void main(String[] args)
  {
    int arr[] = { 3, 4, 2, 1, 7 };
    int N = arr.length;
 
    // Function call
    System.out.println(minCost(arr, N));
  }
}
 
// This code is contributed by sanjoy_62.

C#

// C# program to implement
// the above approach
using System;
 
class GFG
{
 
// Function to find the minimum possible cost
static int minCost(int[] arr, int N)
{
    // Creating the prefix sum array
    int[] pref = new int[N + 1];
    int[,] dp = new int[N, N];
    int[,] opt = new int[N, N];
    pref[0] = 0;
      
    // Loop to calculate the prefix sum
    for (int i = 0; i < N; i++) {
        pref[i + 1] = pref[i] + arr[i];
        opt[i, i] = i;
    }
  
    // Iterating through all sub-arrays
    // of length 2 or greater
    for (int i = N - 2; i >= 0; i--) {
        for (int j = i + 1; j < N; j++) {
  
            // Cost function = sum of
            // all elements in the sub-array
            int cost = pref[j + 1] - pref[i];
            int mn = Int32.MaxValue;
            for (int k = opt[i, j - 1];
                 k <= Math.Min(j - 1, opt[i + 1, j]);
                 k++) {
                if (mn >= dp[i, k]
                    + dp[k + 1, j] + cost) {
  
                    // Updating opt table
                    opt[i, j] = k;
  
                    // Updating minimum cost
                    mn = dp[i, k]
                         + dp[k + 1, j] + cost;
                }
            }
  
            // dp transition
            dp[i, j] = mn;
        }
    }
    return dp[0, N - 1];
}
 
// Driver Code
public static void Main()
{
    int[] arr = { 3, 4, 2, 1, 7 };
    int N = arr.Length;
  
    // Function call
    Console.Write(minCost(arr, N));
}
}
 
// This code is contributed by spevel62.
Producción

37

Tiempo Complejidad: O(N 2 )
Espacio Auxiliar: O(N 2

Publicación traducida automáticamente

Artículo escrito por omkarbajaj000 y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *