2012-03-13 10 views
12

Przejmuję problem programowania z lokalnego konkursu programistycznego.Rozwiązanie cofania do ćwiczenia programistycznego (dopasowywanie rur)

Możesz pobrać ten problem here (pdf). Jest w języku holenderskim, ale zdjęcia pomogą to zrozumieć.

Otrzymujesz kratkę m * m jako wejście z niektórymi rurkami i brakującymi miejscami (questionmarks). Pozostałe rury muszą być umieszczone w siatce tak, aby łączyły się z pozostałymi.

Każda rura jest reprezentowana jako litera (patrz rysunek na stronie 2). Litera "A" ma wartość 1, "B" ma wartość 2, ..

Próbowałem rozwiązać go z powrotem (nie działa jeszcze). Ale ponieważ siatka może wynosić 10x10, będzie to zbyt wolne. Czy ktoś może zaproponować lepsze (szybsze) rozwiązanie/podejście?

#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

Połączyłeś dane wejściowe, a nie opis problemu. – Bart

+2

Wygląda na to, że opis problemu: http://www.vlaamseprogrammeerwedstrijd.be/2011/opgaves/cat2-2011/loodgieter.pdf – fgb

+0

Tak, masz rację. Zmieniłem adres URL. – Jasper

Odpowiedz

7

oryginalna odpowiedź

masz napisać cały kod samodzielnie lub jesteś zainteresowany odkrywaniem innych narzędzi? ponieważ sugerowałbym spojrzenie na propagację ograniczeń/programowanie liniowe. masz już wiele ograniczeń brzegowych - że zewnętrzne krawędzie nie mogą mieć rur, plus wewnętrzne krawędzie - więc wyobrażam sobie, że to zadziała całkiem sprawnie. również, ograniczenia wyglądają tak, jakby były prostymi równościami, więc powinno być całkiem łatwe do skonfigurowania.

Nie mam wystarczającego doświadczenia z tym, aby podać więcej szczegółów tutaj (chociaż jeśli mam czas w przyszłym tygodniu, mogę dać mu szansę w pewnym momencie), ale jeśli takie rzeczy są interesujące, jest trochę więcej tła w another answer i wrote some time ago.

ps ciekawe pytanie; dzięki za zamieszczenie tego.

[edytuj: jeśli nie możesz korzystać z innych bibliotek, możesz sam przeprowadzić propagację ograniczeń. jest wspaniały article by norvig, który pokazuje, jak to zrobić dla sudoku. Chciałbym zdecydowanie sugerują, że czytanie - myślę, że będzie można zobaczyć, jak przeprowadzić technik drugiej, mimo że sudoku i python]

zaktualizowaną odpowiedź (2012-04-06 - Aktualizacja z odniesieniami bloga;. stare komentarze były buggy)

przeszukiwanie w głąb, gdzie obok pusta komórka jest wypełniona każdego dostępnego, spójny płytki i sprawdzenie spójności obejmuje zarówno ograniczenia krawędź (brak rur poza krawędź) i najbliższych sąsiadów, jest dość skuteczny . Mam niezoptymalizowaną implementację w clojure, która rozwiąże mniejszy przykład w około 0,4 ms (1000 w 360ms po rozgrzewce JVM), a większa w 3ms (cedrowe van goethem zgłasza 1ms dla zoptymalizowanej - ale wciąż OO - implementacji java, która wydaje się rozsądna). może rozwiązać układankę 10x10 (koncentryczne koła rurek bez początkowych wskazówek) w 12s.

Spędziłem również czas na "inteligentnym" podejściu, które śledzi ograniczenia na każdej komórce, podobnie jak w dokumencie norviga powyżej. a następnie próbowałem używać choco. wszystko to jest opisane znacznie bardziej szczegółowo w blog posts here (miałem więcej szczegółów w aktualizacji tej odpowiedzi, ale były one oparte na błędnym kodzie - na blogu jest więcej, lepsza informacja). źródło jest również dostępne do pobrania.

Ogólnym wnioskiem z tego wszystkiego było to, że wyszukiwanie bezpośrednie jest w porządku do 10x10. po tym więcej inteligentnych może pomóc, ale trudno jest być pewnym, ponieważ generowanie przypadków testowych jest trudne (wydają się być niejednoznaczne, nawet gdy podano dużo komórek).

+0

Tak, pasuje do tej kategorii problemów, ale nie mogę używać niestandardowych bibliotek. – Jasper

+2

norvig napisał bardzo fajny artykuł na temat rozwiązywania sudoku, który pokazuje, jak połączyć wyszukiwanie z powrotem z innymi propagacjami ograniczeń bez użycia dedykowanego pakietu. w tej chwili masz wyszukiwanie, ale nie korzystasz z innych ograniczeń (np. rury nie przekraczające granicy). więc proponuję przeczytać http://norvig.com/sudoku.html, gdzie opisuje w bardzo czytelny sposób, jak rozwiązać ten problem. –

+0

Dziękuję Andrzej, bardzo dobrze napisany. To może pomóc – Jasper

2

Przyjemny problem. Znalazłem rozwiązanie w O (n · m · 8^m), które wydaje się wystarczające.

  1. Skupia się na granicy między pierwszym a drugim rzędem. Istnieją 2^m możliwości (wychodząca linia lub nie, dla każdej strony). Będzie to górna linia graniczna, dolna linia graniczna nie będzie połączona z każdej strony.

  2. Dla każdej pary niższej linii granicznej i górnej linii granicznej (która będzie 2^m · 2^m = 4^m par), obliczyć każdy rząd, który pasuje. Jeśli przyjdziesz od lewej, jesteś biorąc pod uwagę lewą stronę, górną i dolną stronę, więc masz tylko 2 możliwości. Jeśli kafelek, na który patrzysz, jest ustalony na mapie, sprawdź, czy pasuje do niego i przerwij go w inny sposób. Nazwij to rekurencyjnie, a otrzymasz 2^m wierszy, każdy lub razem 4^m * 2^m = 8^m.

  3. Podczas gdy ostatnim krokiem była czysta brutalna siła, tym razem używamy DP. Zabezpiecz krotkę (obramowanie, cegły, rząd) w tablicy tablic. array [0] będzie zawierał pojedynczą krotkę (puste obramowanie, bez cegły), nic). tablica [n] zawiera wszystkie 8^m wygenerowanych wierszy w wierszu n (zaczynając od 1) w połączeniu z każdym elementem w tablicy [n-1], w którym pasuje (tzn. obramowanie elementu jest takie samo jak dolna krawędź wiersza) Zauważ, że jeśli mądrze połączysz ten warunek z pętlą w kroku 2, nic Cię to nie kosztuje.

  4. Usuń wszystkie krotki, które wymagają więcej klocków jednego rodzaju niż dostępne i posortuj tablicę. Następnie przejdź do kroku 2, aż wszystkie wiersze zostaną obsłużone.

  5. Jeśli skończyłeś, sprawdź tablicę [n] dla krotki (puste obramowanie, wszystkie cegły używane, wiersz). Ponieważ opis zadania sugeruje, że istnieje, wydrukuj jego wiersz. Następnie spójrz na dolną krawędź wiersza i wyszukaj ją w tablicy [n-1] i wydrukuj również, i tak dalej.

Mam nadzieję, że rozumiesz mój angielski.

+0

Thx za odpowiedź, ale obawiam się, że nie rozumiem twojego rozwiązania. – Jasper

+0

DP to dynamiczne programowanie. –