Teoria Złożoności Zadania Łukasz Czajka 14 czerwca 2008 1
Spis treści 1 Zadanie 1 3 2 Zadanie 5 8 2
1 Zadanie 1 Niech A NP 1. Istnieje zatem jedno-taśmowa niedeterministyczna maszyna Turinga M o alfabecie 0, 1 działająca w czasie wielomianowym p(n), która rozpoznaje A. Skonstruujemy deterministyczną maszynę Turinga M działającą w pamięci logarytmicznej, która dla wejścia 1 n generuje obwód logiczny C n o n wejściach, wielomianowej liczbie wyjść, zerowej głębokości i rozmiarze wielomianowym, obliczający funkcję f : {0, 1} {0, 1} taką, że f(x) jest kodowaniem obwodu logicznego F x o p(n) wejściach, który jest spełnialny wtedy i tylko wtedy, gdy x A. Dla obwodów C n przyjmujemy następujące kodowanie. 2 Pojedynczą bramkę kodujemy tak: #<typ bramki>@<opcjonalny numer zmiennej wejściowej> Przyjmujemy tutaj alfabet {0, 1, #, @}. Pole <typ bramki> koduje binarnie typ bramki: stała 1, stała 0, zmienna, negacja zmiennej. Drugie pole koduje numer zmiennej, lub jest zerem jeśli typ bramki to stała. Poszczególne bramki są ustawione na taśmie według uporządkowania bramek wyjściowych. Wszystkie bramki obecne w kodowaniu są wyjściowe. Jeśli jakiejś bramki wejściowej (odpowiadającej zmiennej) nie ma w kodowaniu, to oznacza, że wejście tej bramki jest ignorowane. Dla obwodów F x przyjmujemy następujące kodowanie. Pojedynczą bramkę kodujemy tak: <etykieta bramki><typ bramki><ilość wejść> \ <etykieta wejścia 1><etykieta wejścia 2>... Alfabetem jest tutaj 0, 1. Typ bramki określa, czy bramka jest typu AND, OR, wejściem, wyjściem itp. Zaznaczmy jeszcze, że kodowanie dla typu bramki wybieramy tak, aby to, czy stała bramka ma wartość 1 czy 0 zależało tylko od tego, czy jeden ustalony bit ma wartość 1 czy 0. Etykieta to jednoznaczny identyfikator bramki co najwyżej logarytmicznej długości. Na taśmie poszczególne bramki zapisane są jedna za drugą. Wszystkie pola mają ustaloną długość K, której wartość podana będzie niżej (jest ona O(log n)). Takie kodowanie ma rozmiar wielomianowy względem ilości bramek w obwodzie. Każde obliczenie maszyny M możemy przedstawić jako wektor binarny długości p(n), w którym i-ty bit przedstawia wybór w i-tym kroku obliczeń. Można założyć, że w każdym kroku M dokonuje niedeterministycznego wyboru spośród dokładnie dwóch możliwości lub się zatrzymuje, tzn. dla każdego stanu (niekońcowego) są dokładnie dwa przejścia na każdym symbolu. Jeśli jest k > 2 możliwości wyboru dla jakiegoś stanu q i symbolu 1 Przyjmujemy bez straty ogólności, że A {0, 1} 2 To kodowanie nie jest akurat bardzo istotne. Bardziej istotne jest kodowanie dla F x. 3
σ, to zawsze dodając k 2 stanów możemy przekształcić stan q na drzewo binarne stanów, w którym przejście do każdego z k 2 wewnętrznych węzłów (stanów) będzie tylko dawało dodatkowy niedeterministyczny wybór bez zmieniania taśmy wejściowej ani ruszania głowicy, a liście w tym drzewie będą pierwotnymi następnikami q. Jeśli jest tylko jedna możliwość, to rozważamy ją jako dwie możliwości. Jeśli obliczenia kończą się po mniej niż dokładnie p(n) krokach, to uzupełniamy resztę wektora zerami. Zakładamy także, iż M nie ma przejść ze stanu akceptującego. 3 Wejściem (tzn. zmiennymi) dla obwodu F x będzie właśnie wektor binarny przedstawiający obliczenie maszyny M na słowie x, tzn. wybory dokonane w kolejnych krokach obliczeń. Nazwijmy go c = [c 1,..., c p(n) ]. W obwodzie F x będziemy wyróżniali kilka rodzajów bramek. Pierwszy z nich to bramki z etykietami t, i, q, σ, gdzie t oraz i są liczbami, q stanem lub ɛ (brak stanu), σ symbolem (0, 1 lub symbol pusty). W zamyśle bramka t, i, q, σ ma mieć wartość 1 przy danym obliczeniu c (wartościowaniu zmiennych na wejściach obwodu F x ) wtedy i tylko wtedy, gdy w kroku t w komórce i występuje σ. Dodatkowo jeśli q ɛ, to maszyna jest w kroku t w stanie q z głowicą nad i-tą komórką taśmy. Jeśli q = ɛ, to w kroku t głowica nie jest nad i-tą komórką taśmy. Komórki taśmy oraz kroki wykonania numerujemy od 1. Bramki tego rodzaju są bramkami typu OR. Drugi rodzaj bramek, to bramki wejściowe o etykietach c 1,..., c p(n) oraz ich negacje c 1,..., c p(n). Trzeci rodzaj bramek to bramki o etykietach t, i 1, q 1, ρ, τ, i 2, q 2, σ. Są to bramki typu AND. Zakładamy, że i 2 {i 1, i 1 + 1, i 1 1}, oraz q 1, q 2 ɛ. W zamyśle taka bramka, przy danym obliczeniu c, ma mieć wartość 1 wtedy i tylko wtedy, gdy maszyna w kroku t 1 wykonała przejście ze stanu q 1 do stanu q 2 na symbolu ρ, przesuwając głowicę o pozycji i 1 na i 2 i wypisując symbol τ, przy czym po wykonaniu przejścia w obserwowanej komórce taśmy jest symbol σ. Czwarty rodzaj bramek, to bramki NOT o etykietach NOT t, i 1, q 1, ρ, τ, i 2, q 2, σ, które mają być negacją odpowiednich bramek trzeciego rodzaju. Kolejny rodzaj, to bramki AND o etykietach CONST t, i, σ 1, σ 2, σ 3, q 1, q 2. W zamyśle mają one mieć przy danym obliczeniu wartość 1 wtedy i tylko wtedy, gdy: na pozycjach taśmy i 1,i,i+1 w kroku t 1 były odpowiednio symbole σ 1, σ 2, σ 3 ; jeśli q 1 ɛ, to głowica była na pozycji i 1, a maszyna w stanie q 1 ; jeśli q 2 ɛ, to głowica była na pozycji i + 1, a maszyna w stanie q 2 ; 3 Por. [Pap07], s. 187, dowód twierdzenia 8.2 (Cooka). Jak można poniżej łatwo zauważyć obwód F x jest tutaj (prawie) dokładnie obwodem z dowodu twierdzenia 8.2. 4
w wyniku przejścia do kroku t nie zmieniła się zawartość komórki i oraz głowica nie przesunęła się na pozycję i. Dodajemy jeszcze jedną bramkę wyjściową Out typu OR, której wejściami będą wszystkie bramki t, i, q f, σ gdzie q f jest stanem akceptującym. Dla każdego symbolu x dodajemy także dwie bramki In xi oraz In xi. Bramka In xi jest bramką stałą o wartości 1 jeśli x i = 1 lub o wartości 0 jeśli x i = 0. Dualnie dla bramki In xi. Zaznaczmy, że same etykiety In xi i In xi nie zależą od x. Tylko jeden bit w kodowaniu typu takiej bramki zależy od x. Na koniec dodajemy jeszcze jedną bramkę B 0. Jest to stała bramka o wartości 0. Etykiety kodujemy w następujący sposób. <pole rodzaju etykiety><kolejne pola w zależnośći od rodzaju> Pola mają ustaloną długość N = α log 2 p(n), gdzie α jest pewną stałą zależącą od M. Jeśli w etykiecie jest mniej pól niż ich maksymalna ilość (9), to resztę uzupełnia się zerami (po wartości pierwszego pola typu można poznać, czy te zera coś oznaczają). Chodzi tutaj o to, żeby wszystkie etykiety miały taką samą długość K = 9α log 2 p(n) i w każdym polu zmieściła się na pewno wartość jaka może tam być przechowywana. Możemy przyjąć, że przejścia maszyny M są w jakiś sposób liniowo uporządkowane. Ponieważ ilość tych przejść jest stała, więc możemy zawsze na początku w stałym czasie je np. ponumerować. Przyjęliśmy też, że w każdym stanie na każdym symbolu są dokładnie dwa przejścia lub nie ma żadnych, więc możemy każdemu przejściu jednoznacznie przypisać 0 lub 1 w zależności od tego, czy bliźniacze przejście z tego samego stanu na tym samym symbolu ma mniejszy czy większy numer. Maszyna M działa w następujący sposób. Na samym początku zlicza ilość jedynek na taśmie wejściowej i zapamiętuje wynik (w zapisie binarnym). Jeśli na taśmie jest coś innego niż n jedynek, to odrzuca. Następnie oblicza wartość p(n). Może to zrobić w pamięci logarytmicznej, bo sam wielomian dany jest a priori, tzn. zależy tylko od M. Maszyna oblicza także N i K. Potem M przechodzi do głównej fazy swojego działania, w której będzie po kolei generować bramki C n, których wyjściami będą poszczególne warstwy obwodu F x. Warstwę numer t obwodu F x tworzą bramki t, i, q, σ dla pewnego i, q oraz σ, tzn. bramki, które odpowiadają za t-ty krok obliczeń. Dokładniej wygląda to tak, jak stoi napisane w algorytmie 1. Wypisanie bramek obwodu C n generujących bramkę B oznacza, że wypisujemy po kolei (zakodowane według pierwszego kodowania) bramki wyjściowe odpowiadające poszczególnym bitom kodowania B (wraz z kodowaniem etykiet wejść bramki B). Bitowi o wartości 1 (0) odpowiada kod bramki stałej o wartości 1 (0), natomiast bitowi, którego wartość zależy pozytywnie 5
Algorytm 1 Konstrukcja obwodu C n 1: for i = 1 to p(n) do 2: Wypisz bramki obwodu C n generujące bramki c i oraz c i. 3: end for 4: for i = 1 to n do 5: Wypisz bramki obwodu C n generujące bramki In xi oraz In xi. 6: end for 7: for t = 2 to p(n), i = 1 to p(n), all q Q M do 8: for all σ {0, 1, } do 9: Wypisz bramki obwodu C n generujące bramkę t, i, q, σ. 10: for all ρ {0, 1, }, q Q M do 11: for all τ {0, 1} do 12: if i p(n) oraz istnieje w M przejście ze stanu q do q na symbolu ρ w którym głowicę przesuwamy w prawo z wypisaniem symbolu τ then 13: Wypisz bramki obwodu C n generujące bramkę t, i, q, ρ, τ, i + 1, q, σ. 14: Wypisz bramki obwodu C n generujące bramkę NOT t, i, q, ρ, τ, i + 1, q, σ. 15: end if 16: if i > 0 oraz istnieje w M przejście ze stanu q do q na symbolu ρ w którym głowicę przesuwamy w lewo z wypisaniem symbolu τ then 17: Wypisz bramki obwodu C n generujące bramkę t, i, q, ρ, τ, i 1, q, σ. 18: Wypisz bramki obwodu C n generujące bramkę NOT t, i, q, ρ, τ, i 1, q, σ. 19: end if 20: end for 21: if istnieje w M przejście ze stanu q do q na symbolu ρ w którym nie przesuwamy głowicy, a na taśmie zapisujemy symbol σ then 22: Wypisz bramki obwodu C n generujące bramkę t, i, q, ρ, σ, i, q, σ. 23: Wypisz bramki obwodu C n generujące bramkę NOT t, i, q, ρ, σ, i, q, σ. 24: end if 25: end for 26: end for 27: for all σ 1, σ 2, σ 3 {0, 1, } do 28: Wypisz bramki obwodu C n generujące bramkę CONST t, i, σ 1, σ 2, σ 3, q, q. 29: end for 30: end for 31: Wypisz bramki obwodu C n generujące bramki B 0 oraz Out. 6
(negatywnie) od i-tego symbolu słowa x odpowiada kod bramki wejściowowyjściowej x i ( x i ). Zauważmy, że tak ustaliliśmy kodowanie F x, że bity kodowania bramki B, które zależą od x są dokładnie którymś z bitów x lub jego negacją. Ponadto powyższy algorytm wypisze każdą bramkę wejściową obwodu C n dokładnie raz (podczas wypisywania In xi lub In xi ). Musimy jeszcze ustalić jakie wejścia generujemy dla każdej bramki obwodu F x, gdyż w powyższym algorytmie to pominęliśmy. Dla bramki 1, 1, q 0, 1 jedynym wejściem jest bramka In x1. Dla bramki 1, 1, q 0, 0 bramka In x1. Dla bramki 1, i, ɛ, 1, gdzie i > 1, jedynym wejściem jest bramka In xi. Dla bramki 1, i, ɛ, 0 (i > 1) bramka In xi. Dla pozostałych bramek typu 1, i, q, σ jedynym wejściem jest B 0. Dla bramki t, i, q, σ (1 < t p(n), 1 i p(n), q ɛ) wejściami są wszystkie bramki t, i 1, q, ρ, τ, i, q, σ takie, że istnieje w M przejście ze stanu q do q na symbolu ρ z przesunięciem głowicy w prawo i wypisaniem τ; wszystkie bramki t, i + 1, q, ρ, τ, i, q, σ takie, że istnieje w M przejście ze stanu q do q na symbolu ρ z przesunięciem głowicy w lewo i wypisaniem τ; oraz wszystkie bramki t, i, q, ρ, σ, i, q, σ takie, że istnieje w M przejście ze stanu q do q na symbolu ρ bez przesuwania głowicy, z wypisaniem symbolu σ. Dodatkowym wejściem jest także bramka B 0. 4 Dla bramki t, i, ɛ, σ, gdzie 1 < t p(n) oraz 1 i p(n), wejściami są wszystkie bramki t, i, q, ρ, σ, i 2, q, τ (i 2 {i + 1, i 1}) takie, że istnieje w M przejście ze stanu q do q na symbolu ρ z przesunięciem głowicy o i 2 i pozycji w prawo i wypisaniem symbolu σ (τ to dowolny symbol); oraz wszystkie bramki CONST t, i, σ 1, σ, σ 3, q 1, q 2, gdzie oba q 1 i q 2 nie mogą być równocześnie różne od ɛ. Dla bramki t, i, q, ρ, τ, i + 1, q, σ odpowiadającej przejściu ze stanu q do q na symbolu ρ z ruchem głowicy w prawo i wypisaniem symbolu τ wejściami są bramki t 1, i, q, ρ, t 1, i + 1, ɛ, σ. Jeśli przejściu odpowiadającemu naszej bramce przypisaliśmy 0 to dodatkowym wejściem jest dla niej c t, w przeciwnym przypadku c t. Analogicznie dla bramek typu t, i, q, ρ, τ, i + 1, σ, t, i, q, ρ, σ, i, σ. Dla bramki CONST t, i, σ 1, σ 2, σ 3, ɛ, ɛ (t > 1) wejściami są bramki t 1, i 1, ɛ, σ 1, t 1, i, ɛ, σ 2, t 1, i + 1, ɛ, σ 3. 4 Jest to potrzebne tylko po to, aby zapewnić, że każda bramka OR ma przynajmniej jedno wejście. Alternatywnie można by przyjąć, że bramka OR bez wejść to stała bramka o wartości 0. Nie ma to większego znaczenia. 7
Dla bramki CONST t, i, σ 1, σ 2, σ 3, q 1, ɛ wejściami są t 1, i 1, q 1, σ 1, t 1, i, ɛ, σ 2, t 1, i + 1, ɛ, σ 3, oraz wszystkie bramki NOT t, i 1, q 1, σ 1, τ, i, q, σ dla każdego q Q M i σ, τ {0, 1, }, o ile istnieje w M przejście ze stanu q 1 do q na symbolu σ 1 z przesunięciem głowicy w prawo i wypisaniem τ. Analogicznie dla bramek typu CONST t, i, σ 1, σ 2, σ 3, ɛ, q 2. Jedynym wejściem bramki NOT t, i 1, q 1, ρ, τ, i 2, q 2, σ jest bramka t, i 1, q 1, ρ, τ, i 2, q 2, σ. Dla Out wejściami są wszystkie bramki t, i, q f, σ, gdzie q f jest stanem akceptującym. W powyższym jeśli jakaś wspomniana bramka nie istnieje ponieważ i przekracza zakres dopuszczalnych wartości (i < 1 lub i > p(n)), to przyjmujemy, że odpowiednim wejściem jest B 0. Zauważmy, że etykiety wejść dla danej bramki B zależą tylko od M oraz etykiety B oraz liczba wejść każdej bramki jest stała. Wyjątkiem jest tu bramka Out, ale jak ustalimy t oraz i dla jej bramek wejściowych jest to już prawdą. Etykiety bramek wejściowych można zatem generować w pamięci logarytmicznej na podstawie tylko etykiety B. Wykorzystujemy tutaj wszędzie zapamiętane wcześniej liczby K i N do zliczania symboli, żeby wiedzieć o ile należy się przesuwać po taśmie generując odpowiednie bramki. Potrzebujemy także stałej ilości liczników logarytmicznej wielkości względem ilości bramek do liczenia t, i, itp. Zauważmy także, iż wszystkich bramek w F x jest O(p(n) 2 ). Kodowanie każdej bramki F x ma wielomianową długość, zatem wszystkich bramek obwodu C n też jest wielomianowo wiele. Czyli C n ma wielomianową wielkość i może być wygenerowany w pamięci logarytmicznej. Fakt, iż F x jest spełnialny wtedy i tylko wtedy, gdy istnieje obliczenie akceptujące maszyny M na słowie x wynika bezpośrednio z przytoczonej konstrukcji. 2 Zadanie 5 Zauważmy na początku, że jeśli jakaś zmienna występuje tylko w literałach pozytywnych lub tylko w negatywnych, to możemy usunąć wszystkie klauzule w których występuje i otrzymamy równoważną formułę. 5 Przyjmujemy przy tym konwencję, że pusta koniunkcja to 1, a pusta alternatywa to 0. Takie przekształcenie można wykonać w pamięci logarytmicznej. Po prostu przesuwamy się po formule z lewej do prawej i dla każdej zmiennej 5 Równoważną w tym sensie, że jest ona spełnialna wtedy i tylko wtedy, gdy oryginalna formuła jest spełnialna. 8
x występującej w literale pozytywnym (negatywnym) w bieżącej klauzuli α przechodzimy całą formułę sprawdzając, czy x występuje także w literale negatywnym (pozytywnym). Jeśli tak, to przesuwamy się dalej w prawo do następnego literału w klauzuli α, a jeśli był to ostatni literał to wypisujemy α. Jeśli nie występuje, to przechodzimy do następnej klauzuli nie wypisując klauzuli bieżącej. 6 Możemy zatem założyć, że każda zmienna występuje w formule dokładnie dwa razy raz w literale pozytywnym i raz w negatywnym. Fakt 1 Istnieje wartościowanie v spełniające formułę α = φ (x ψ 1 ) ( x ψ 2 ) w koniunkcyjnej postaci normalnej, gdzie x nie występuje w φ, ψ 1 ani ψ 2, wtedy i tylko wtedy, gdy istnieje wartościowanie w spełniające formułę β = φ (ψ 1 ψ 2 ). 7 Dowód: ( ) Musi być ψ 1 [w] = 1 lub ψ 2 [w] = 1. W pierwszym przypadku bierzemy v = w{0/x}, w drugim v = w{1/x}. ( ) Bierzemy w = v V ar(β). Mamy oczywiście φ[w] = 1. Przypuśćmy, że v(x) = 1. Wtedy musi być ψ 2 [v] = 1, więc β[w] = 1. Analogicznie dla v(x) = 0. Następujący algorytm wykazuje, że rozważany problem spełnialności formuły φ jest w P. 1. Usuń wszystkie klauzule zawierające dwa sprzeczne literały. 2. Usuń wszystkie zmienne występujące w formule tylko pozytywnie lub tylko negatywnie, wraz z klauzulami w których te zmienne występują. Powtarzaj ten krok tak długo, aż nie będzie już tego typu zmiennych. 3. Jeśli formuła jest pusta (usunęliśmy wszystkie klauzule), to zakończ φ jest spełnialna. 4. Wybierz dowolną zmienną x oraz dwie klauzule α 1 = (x ψ 1 ) oraz α 2 = ( x ψ 2 ). Zamień α 1 α 2 w formule na β, gdzie β = (ψ 1 ψ 2 ). Jeśli β jest pustą klauzulą, to zakończ φ nie jest spełnialna. 5. Przejdź do kroku 1. 6 Oczywiście zakładamy jakieś kodowanie formuł i jako model obliczeń przyjmujemy maszynę Turinga, ale już nie będę wchodzić w takie oczywiste szczegóły. Np. możemy założyć, że w alfabecie mamy wszystkie potrzebne symbole do normalnego tekstowego zapisania formuły, a zmiennym przypisujemy numerki, żeby zapis zmiennej miał logarytmiczną długość. 7 Literkami greckimi będę oznaczać (pod-)formuły, łacińskimi zmienne. 9
Krok 2 zapewnia, że w kroku 4 zawsze będziemy mogli wybrać zmienną występującą zarówno pozytywnie jak i negatywnie. Inaczej formuła byłaby pusta i zakończylibyśmy w kroku 3. Każdy krok 8 wymaga co najwyżej wielomianowej liczby operacji, przy czym powtórzeń kroków 2 i 4 nie może być więcej niż jest zmiennych, bo za każdym razem usuwamy co najmniej jedną zmienną. Natomiast kroków 1, 3 i 5 nie może być więcej niż kroków 2 i 4. Zatem algorytm działa w czasie wielomianowym. Zużycie pamięci też jest wielomianowe, bo pamiętamy cały czas przekształcaną formułę. 9 Poprawność wynika z faktu 1 oraz oczywistej obserwacji, że kroki 1 i 2 przekształcają formułę na równoważną 10. Jeśli formuła jest spełnialna, to po każdym z kroków 1, 2 i 4 także musi pozostać spełnialna. Jeśli nie jest spełnialna, to przekształcenia te nie mogą dać formuły spełnialnej. Pokażemy teraz, że rozważany problem jest w L. Dla każdej formuły φ w koniunkcyjnej postaci normalnej, w której każda zmienna występuje dokładnie dwa razy, przy czym raz w literale negatywnym i raz w pozytywnym, definiujemy graf nieskierowany G φ z wielokrotnymi krawędziami. Wierzchołkami grafu będą klauzule, a krawędzie odpowiadać będą zmiennym. Krawędź e x odpowiadająca zmiennej x łączy obie klauzule zawierające x (może być to ten sam wierzchołek). Ze względu na założenia o formule φ graf ten jest dobrze zdefiniowany. Dla danej formuły φ graf G φ może być wygenerowany w pamięci logarytmicznej w następujący sposób. Zakładamy, że graf reprezentowany będzie przez listy sąsiedztwa, zakodowane np. jakoś tak: #<numer wierzchołka>@<numer sąsiada>@<numer sąsiada>@... #<numer wierzchołka>@... Tego rodzaju kodowanie ma rozmiar wielomianowy względem długości φ. 11 Deterministyczna maszyna Turinga przekształcająca φ w G φ przegląda wejście od lewej do prawej, utrzymując cały czas licznik informujący o numerze bieżącej klauzuli. Przy dodawaniu klauzuli α do grafu najpierw jest wypisywany jej numer, po czym maszyna przegląda po kolei literały klauzuli. Dla każdego literału l przeszukuje od początku całą formułę licząc w osobnym drugim liczniku numery przeglądanych klauzul. Gdy napotka na literał przeciwny do l, wypisuje numer klauzuli z drugiego licznika jako numer sąsiada dla α (oddzielając to wszystko odpowiednimi specjalnymi symbolami gdzie potrzeba). Potem przechodzi do rozpatrywania następnego literału w α. Dwa liczniki wymagają oczywiście tylko logarytmicznej pamięci. 8 N powtórzeń kroku 2 liczymy jako N kroków 2, a nie jeden wielki krok 9 Zakładamy, że maszyna ma dwie taśmy. Przy przekształcaniu formuły pisze sobie wynik na drugą taśmę, potem zamienia taśmy rolami, itp., itd., ale to oczywiste szczegóły. 10 W sensie spełnialności. 11 Tutaj kodowanie nie jest już tak całkiem oczywiste, bo nie można wziąć np. całych klauzul jako nazw wierzchołków, gdyż wynik mógłby wyjść zbyt duży. 10
Rozważmy teraz następujący algorytm działający na grafie G φ. 1. Usuń wszystkie wierzchołki znajdujące się na jakiejś jedno-krawędziowej pętli (tzn. wszystkie, z których prowadzi krawędź do nich samych). 2. Usuń wszystkie wierzchołki, które straciły krawędź w wyniku poprzedniego kroku. Powtarzaj ten krok tak długo, jak tylko jakieś wierzchołki obecne w grafie tracą w jego wyniku krawędzie. 3. Jeśli graf jest pusty (usunęliśmy wszystkie wierzchołki), to zakończ φ jest spełnialna. 4. Wybierz dowolną krawędź e x oraz dwa wierzchołki α 1 i α 2 połączone tą krawędzią. Ściągnij krawędź e x łącząc wierzchołki α 1 i α 2 w jeden wierzchołek β, nie usuwając jednak żadnych krawędzi poza e x, a krawędzie różne od e x łączące α 1 i α 2 zamień na jedno-krawędziowe pętle łączące β z β. 12 Jeśli β jest wierzchołkiem izolowanym, tzn. nie jest incydentny z żadnymi krawędziami, w tym z żadną jedno-krawędziową pętlą, to zakończ φ nie jest spełnialna. 5. Przejdź do kroku 1. Inaczej mówiąc, w wyniku kroków 1 i 2 usuwamy wszystkie spójne składowe zawierające jedno-krawędziowe pętle. Nie jest to jeszcze algorytm, którego szukamy, ale chwila zastanowienia wykazuje, że jest to właściwie ten sam algorytm, co pierwszy algorytm działający na formułach. Przekształcenia grafu G φ odpowiadają dokładnie przekształceniom formuł w pierwszym algorytmie. Poprawność jest zatem oczywista. Zastanówmy się teraz jaki warunek musi być spełniony, aby algorytm zatrzymał się w kroku 3, tzn. φ była spełnialna. Oczywiście potrzeba i wystarcza, aby każda spójna składowa zawierała jakiś cykl. Potrzeba, bo jeśli jakaś spójna składowa jest drzewem, to zostanie w końcu ściągnięta do pojedynczego wierzchołka przez operacje w kroku 4. Wystarcza, ponieważ jeśli spójna składowa zawiera cykl, to zostanie on w końcu ściągnięty w kroku 4 do jedno-krawędziowej pętli, po czym zaraz w krokach 1 i 2 cała składowa zostanie usunięta. Zatem problem z zadania sprowadza się (poprzez dwie redukcje w pamięci logarytmicznej) do problemu sprawdzania, czy każda spójna składowa w grafie nieskierowanym z wielokrotnymi krawędziami zawiera cykl, który to problem można rozwiązać w deterministycznej pamięci logarytmicznej. Rozwiązanie to jednak korzysta z mocno nietrywialnego algorytmu dla problemu USTCON, rozstrzygającego w deterministycznej pamięci logarytmicznej, czy dwa wierzchołki grafu nieskierowanego są połączone ścieżką. Algorytm ten przedstawiony jest w [Rei05]. 12 Pamiętamy, że pozwalamy na wielokrotne krawędzie. 11
Nie wiem czy algorytm z [Rei05] działa dla grafów z wielokrotnymi krawędziami, ale to nie problem, bo graf G można przekształcić w pamięci logarytmicznej do grafu G bez krawędzi wielokrotnych i jedno-krawędziowych pętli takiego, że każda składowa G zawiera cykl wtedy i tylko wtedy, gdy każda składowa G zawiera cykl. Wystarczy na każdej krawędzi dostawić jeden dodatkowy wierzchołek, tzn. krawędź (u, v) zamieniana jest na nowy wierzchołek w (u,v) oraz dwie krawędzie (u, w (u,v) ) i (w (u,v), v). Przekształcenie to można wykonać w pamięci logarytmicznej. Wybieramy zawsze numer dla w (u,v) w jakiś jednoznaczny sposób, np. jako konkatenację numerów dla u i v w ustalonej kolejności (np. najpierw mniejszy z nich), powiększoną o ilość wszystkich wierzchołków w G. Oczywiście numery dla u i v nie mogą mieć przy takim kodowaniu różnych długości musimy ustalić maksymalną długość binarnego zapisu numerów wierzchołków w G i uzupełniać potem wszystkie numery zerami tak, aby miały taką samą długość. Inaczej kodowanie nie byłoby jednoznaczne. Po policzeniu na samym początku ilości wszystkich wierzchołków i sufitu z logarytmu dwójkowego tej wielkości, co można zrobić w pamięci logarytmicznej, możemy już w czasie logarytmicznym wyliczać numer dla w (u,v). Przechodzimy zatem wejście od lewej do prawej i dla każdego wierzchołka u przechodzimy wszystkich jego sąsiadów v. Dla każdego v wypisujemy obliczony numer w (u,v) jako sąsiada u w G. Jak już wypiszemy wszystkich sąsiadów u, to znów dla każdego v sąsiada u sprawdzamy czy wierzchołek w (u,v) nie został jeszcze wypisany. Można to zrobić przechodząc wszystkie wierzchołki w wejściu znajdujące się przed u i sprawdzając, czy mają one u jako sąsiada. Jeśli w (u,v) nie został jeszcze wypisany, to wypisujemy go teraz razem z jego listą sąsiedztwa składającą się z wierzchołków u i v. Po zakończeniu tej fazy przechodzimy do przetwarzania następnego wierzchołka po u w wejściu. Mając algorytm dla USTCON można już w prosty sposób sprawdzić, czy wszystkie spójne składowe zawierają cykl. Innymi słowy, chcemy sprawdzić, czy z każdego wierzchołka jest osiągalny jakiś wierzchołek leżący na jakimś cyklu. Sprawdzić, czy wierzchołek v leży na jakimś cyklu można w następujący sposób. Dla każdego sąsiada w wierzchołka v sprawdzamy, czy istnieje cykl przechodzący przez v i w. W tym celu usuwamy tymczasowo krawędź między v i w 13, po czym sprawdzamy algorytmem z [Rei05], czy v i w są połączone ścieżką. Jeśli są, to występuje cykl zawierający krawędź (v, w). Natomiast jeśli v leży na jakimś cyklu, to oczywiście któraś z incydentnych z nim krawędzi musi leżeć na tym cyklu, więc ta procedura wykryje cykl. Główny algorytm przechodzi wejście od lewej do prawej i dla każdego wierzchołka v robi co następuje. Zaczyna przechodzić od początku całe 13 Stałą liczbę krawędzi możemy zawsze usunąć bez straty dla złożoności, zapamiętując np. gdzieś na boku parę (numer(v), numer(w)) i w dalszym ciągu algorytmu sprawdzając zawsze czy rozważana krawędź nie jest tą usuniętą. 12
wejście i dla każdego wierzchołka u sprawdza algorytmem z [Rei05], czy istnieje ścieżka między v i u (lub czy u = v). Jeśli tak, to sprawdza, czy u leży na jakimś cyklu. Jeśli tak, to z v osiągalny jest wierzchołek leżący na cyklu i możemy przejść do rozpatrywania kolejnego wierzchołka po v. Jeśli przejrzymy wszystkie wierzchołki u i okaże się, że żaden wierzchołek osiągalny z v nie leży na cyklu, to oznacza, że spójna składowa zawierająca v nie zawiera cyklu. Możemy zatem zakończyć algorytm z odpowiedzią negatywną. Jeśli uda nam się szczęśliwie przetworzyć wszystkie wierzchołki v, to znaczy, że każda spójna składowa zawiera jakiś cykl. Kończymy wtedy z odpowiedzią pozytywną. W powyższym korzystaliśmy milcząco z ogólnie znanego faktu, że złożenie stałej liczby redukcji w pamięci logarytmicznej jest redukcją w pamięci logarytmicznej, oraz że L jest zamknięta na redukcje w pamięci logarytmicznej. Można zatem wywnioskować, że problem spełnialności formuł w koniunkcyjnej postaci normalnej, w których każda zmienna występuje co najwyżej dwa razy, jest w L. Rozwiązanie bonusowe 14 Zakładamy, że w formule φ każda zmienna występuje dokładnie dwa razy raz pozytywnie i raz negatywnie. Niech k będzie liczbą klauzul, a m liczbą zmiennych. Zakładamy, że m k, inaczej φ jest niespełnialna, bo w każdej klauzuli musiał by być przynamniej jeden spełniony literał, a każda zmienna spełnia dokładnie jeden literał. Dla każdej klauzuli α tworzymy wierzchołek v α. Tworzymy także wierzchołek w x dla każdej zmiennej x. Zmienną łączymy dwoma krawędziami z wierzchołkami odpowiadającymi obu klauzulom, w których występuje ta zmienna. Następnie wszystkie wierzchołki odpowiadające zmiennym łączymy w jedną wielką klikę. Jeśli m k jest nieparzyste, to do tej kliki dodajemy jeszcze jeden dodatkowy wierzchołek. W takim grafie istnieje idealne skojarzenie wtedy i tylko wtedy, gdy φ jest spełnialna. Istotnie, jeśli istnieje idealne skojarzenie, to każdy wierzchołek odpowiadający klauzuli jest skojarzony z dokładnie jednym wierzchołkiem odpowiadającym zmiennej. Możemy wtedy wartość tej zmiennej ustawić tak, aby klauzula była spełniona. Jeśli istnieje wartościowanie spełniające, to dla każdej klauzuli wybieramy zmienną, która występuje w którymś z prawdziwych literałów tej klauzuli. Wierzchołek odpowiadający tej zmiennej kojarzymy z wierzchołkiem odpowiadającym klauzuli. Ze względu na postać formuły zmienna ta nie występuje już w innych prawdziwych literałach. Można zatem dla każdej klauzuli dobrać taką zmienną i będzie to funkcja różnowartościowa. Po 14 No wiem, że jedno rozwiązanie to już niby wystarczy, ale tak mi się spodobała ta redukcja, którą wymyśliłem, że aż muszę ją napisać. Można to rozwiązanie zignorować. 13
skojarzeniu wszystkich klauzul zostaje nam parzysta liczba wierzchołków połączonych w klikę. W trywialny sposób można tam znaleźć idealne skojarzenie. Problem znajdowania idealnego skojarzenia, jak powszechnie wiadomo, jest w P. Literatura [Pap07] Christos H. Papadimitriou. Złożoność obliczeniowa. Wydanie 2. WNT 2007. [Rei05] Omer Reingold. Undirected st-connectivity in log-space. W Proceedings of the 37th ACM Symposium on Theory of Computing, s. 376-385, 2005. Wersja elektroniczna: http://www.wisdom.weizmann.ac. il/~reingold/publications/sl.ps. 14