2012-03-13 18 views
12

Estoy revisando un problema de programación de un concurso de programación local.Solución de retroceso para ejercicio de programación (montaje de tuberías)

Puede descargar el problema here (pdf). Está en holandés pero las imágenes ayudarán a entenderlo.

Recibe una cuadrícula m * m como entrada con algunos tubos y algunos puntos faltantes (los interrogantes). Las tuberías restantes deben colocarse en la red para que se conecten con las demás.

Cada tubería se representa como una letra (ver imagen en la página 2). La letra 'A' tiene el valor 1, 'B' tiene el valor 2, ..

Intenté resolverlo con retroceso (todavía no funciona). Pero dado que la cuadrícula puede ser de 10x10, esto será demasiado lento. ¿Alguien puede sugerir una solución/enfoque mejor (más rápido)?

#include <iostream> 
#include <stdio.h> 
#include <vector> 
using namespace std; 

#define sz(a) int((a).size()) 
#define pb push_back 

int m, found; 
string letters; 
vector<int> done; 
vector<string> a; 

int ok(char letter, int c, int r) 
{ 
    int val = letter - 'A' + 1; 

    //checking if no side goes outside 
    if (r == 0 && (val & 1)) 
     return 0; 
    if (r == m - 1 && (val & 4)) 
     return 0; 
    if (c == 0 && (val & 8)) 
     return 0; 
    if (c == m - 1 && (val & 2)) 
     return 0; 

    //check if the side is connected the other pipe on the grid 
    if (r > 0 && a[r - 1][c] != '?' && (a[r - 1][c] & 4) && !(val & 1)) 
     return 0; 
    if (c > 0 && a[r][c - 1] != '?' && (a[r][c - 1] & 2) && !(val & 8)) 
     return 0; 
    if (r < m - 1 && a[r + 1][c] != '?' && (a[r + 1][c] & 1) && !(val & 4)) 
     return 0; 
    if (c < m - 1 && a[r][c + 1] != '?' && (a[r][c + 1] & 8) && !(val & 2)) 
     return 0; 

    return 1; 
} 

void solve(int num_placed, int pos) 
{ 
    if (found) return; 

    //done 
    if (num_placed == sz(letters)) { 
     for (int i = 0; i < m; ++i) 
      cout << a[i] << endl; 
     found = 1; 
     return; 
    } 

    int c = pos % m; 
    int r = pos/m; 
    if (a[r][c] != '?') 
     solve(num_placed, pos + 1); 

    //try all the pipes on this position 
    for (int i = 0; i < sz(letters); ++i) { 
     if (!done[i] && ok(letters[i], c, r)) { 
      a[r][c] = letters[i]; 
      done[i] = 1; 
      solve(num_placed + 1, pos + 1); 
      done[i] = 0; 
      a[r][c] = '?'; 
     } 
    } 
} 

int main() 
{ 
    freopen("input.txt", "r", stdin); 

    int n; 
    cin >> n; 

    while (n--) { 
     cin >> m; 
     cin >> letters; 

     cout << m << endl; 
     a.clear(); 
     for (int i = 0; i < m; ++i) { 
      string line; 
      cin >> line; 
      a.pb(line); 
     } 

     done = vector<int>(sz(letters), 0); 

     found = 0; 
     solve(0, 0); 
    } 

    return 0; 
} 
+1

Se ha vinculado a la entrada, no a la descripción del problema. – Bart

+2

Parece que la descripción del problema es: http://www.vlaamseprogrammeerwedstrijd.be/2011/opgaves/cat2-2011/loodgieter.pdf – fgb

+0

Sí, tiene razón. Cambié la url. – Jasper

Respuesta

7

respuesta original

tienes que escribir todo el código sí mismo o está interesado en explorar otras herramientas? porque sugeriría mirar la propagación de restricciones/programación lineal. usted ya tiene muchas restricciones de límites, que los bordes exteriores no pueden tener tuberías, más los bordes internos, así que imagino que esto funcionará de manera bastante eficiente. Además, las restricciones parecen ser equivalentes simples, por lo que debería ser bastante fácil de configurar.

No tengo suficiente experiencia con esto para dar más detalles aquí (aunque si tengo tiempo en la próxima semana puedo intentarlo en algún momento), pero si este tipo de cosas es interesante hay un poco más antecedentes en another answer i wrote some time ago.

ps pregunta interesante; Gracias por publicar esto.

[Editar: si no puede usar otras bibliotecas, puede hacer la propagación de restricciones usted mismo. hay un maravilloso article by norvig que muestra cómo hacer esto para sudoku. Sugeriría leer eso, creo que verás cómo llevar las técnicas, aunque es sudoku y python.]

respuesta actualizada (2012-04-06 - actualizado con referencias de blog; los comentarios antiguos eran cochecillo)

una búsqueda en profundidad, en donde la siguiente celda vacía se llena con cada uno, consistente baldosas disponibles, y la comprobación de coherencia incluye tanto las restricciones de borde (no hay tuberías fuera del borde) y los vecinos más cercanos, es bastante eficiente . tengo una implementación no optimizada en clojure que resolverá el ejemplo más pequeño en alrededor de 0.4ms (1000 en 360ms después del calentamiento de JVM) y el más grande en 3ms (cedric van goethem informa 1ms para una implementación Java optimizada pero todavía OO, lo cual parece razonable) puede resolver un rompecabezas de 10x10 (círculos concéntricos de tubos sin pistas iniciales) en 12s.

También dediqué un enfoque "inteligente", que rastrea las restricciones en cada celda, al igual que en el documento de Norvig anterior. y luego intenté usar choco. todo esto se describe con mucho más detalle en blog posts here (tuve más detalles en una actualización de esta respuesta, pero se basaron en un código defectuoso: el blog tiene más y mejor información). la fuente también está disponible para descargar.

La conclusión general de todo esto fue que la búsqueda directa está bien hasta 10x10. después de eso, más inteligencia puede ayudar, pero es difícil estar seguro porque generar casos de prueba es difícil (tienden a ser ambiguos, incluso cuando se administran muchas células).

+0

Sí, cabe en esa categoría de problema, pero no puedo usar bibliotecas no estándar – Jasper

+2

norvig escribió un artículo muy bueno sobre cómo resolver sudoku que muestra cómo combinar la búsqueda de retroceso con otras propagaciones de restricciones sin usar un paquete dedicado. en este momento tiene la búsqueda pero no está haciendo uso de las otras restricciones (como las tuberías que no cruzan el borde). entonces sugeriría leer http://norvig.com/sudoku.html donde describe con detalles muy legibles cómo resolver este tipo de problema. –

+0

Gracias Andrew, muy bien escrito. ¡Podría ayudar – Jasper

2

Buen problema. Encontré una solución en O (n · m · 8^m), que parece ser suficiente.

  1. Enfoque en el borde entre la primera y la segunda fila. Hay 2^m posibilidades (línea de salida o no, para cada lado). Esta será la línea del borde superior, la línea del límite inferior no tendrá conexión en cada lado.

  2. Para cada par de línea de borde inferior y borde superior (que serán 2^m · 2^m = 4^m pares), calcule cada fila que encaje. Si vienes de la izquierda, eres dado el lado izquierdo, el lado superior y el inferior, por lo que solo tienes 2 posibilidades. Si el mosaico que miras está fijo en el mapa, verifica que encaje y cancela lo contrario. Llame esto recursivamente y obtendrá 2^m filas cada una o 4^m * 2^m = 8^m en total.

  3. Mientras que el último paso fue pura fuerza bruta, usamos DP esta vez. Salva la tupla (borde, ladrillos usados, fila) en una matriz de matrices. array [0] contendrá una sola tupla (empty-border, no-brick-used, nothing). array [n] contiene las 8^m filas generadas en la fila n (comenzando en 1) combinadas con cada elemento en la matriz [n-1] que se ajusta (es decir, el borde del elemento es igual que el borde inferior de la fila) Tenga en cuenta que si combina inteligentemente esta condición con el ciclo en el paso 2, esto no le cuesta nada.

  4. Elimina todas las tuplas que necesitan más ladrillos de los que están disponibles y ordena la matriz. Luego continúe en el paso 2 hasta que se manejen todas las filas.

  5. Si ha terminado, marque la matriz [n] para la tupla (borde vacío, todos los ladrillos usados, fila). Dado que la descripción de su tarea implica que existe, imprima su fila. Luego mire el borde inferior de la fila y busque eso en el conjunto [n-1] e imprímalo también, y así sucesivamente.

Espero que entiendas mi inglés.

+0

Thx para responder, pero me temo que no entiendo su solución. – Jasper

+0

por DP quiere decir programación dinámica. –