Przeskocz do treści

Delta mi!

Informatyczny kącik olimpijski

Bitoniczny komiwojażer

Jakub Radoszewski

o artykule ...

  • Publikacja w Delcie: październik 2012
  • Publikacja elektroniczna: 30-09-2012

Tym razem w kąciku lekko zmodyfikowana wersja zadania Listonosz z konkursu Wielka Przesmycka 2004.

obrazek

W klasycznym problemie komiwojażera mamy dany graf nieskierowany math  w którym każda krawędź math ma przypisaną pewną nieujemną, całkowitą wagę math  i poszukujemy najlżejszego cyklu przechodzącego przez każdy wierzchołek grafu dokładnie raz. Ten problem jest NP-trudny, co znaczy, że nie należy spodziewać się, że uda się go rozwiązać w czasie wielomianowym (przynajmniej nie w czasie zawodów). W bitonicznej wersji problemu komiwojażera wprowadzamy jedno dodatkowe wymaganie dotyczące cyklu. Otóż przyjmujemy, że wierzchołki grafu są ponumerowane od 1 do math, i poszukujemy najlżejszego cyklu przechodzącego raz przez każdy wierzchołek grafu, w którym ciąg numerów wierzchołków jest bitoniczny, czyli najpierw rosnący, a potem malejący (przy pewnym wyborze wierzchołka startowego cyklu). Jak nietrudno się domyślić, wymaganie, by cykl był bitoniczny, istotnie upraszcza podany problem.

Nazwijmy interesujące nas w tym zadaniu cykle cyklami bitonicznymi. Zacznijmy od spostrzeżenia, że wierzchołek numer 1 na pewno znajduje się na początku bądź na końcu każdego cyklu bitonicznego. W drugim z tych przypadków możemy przenieść ten wierzchołek na początek cyklu. To oznacza, że dowolny cykl bitoniczny zaczyna się w wierzchołku 1, prowadzi pewną ścieżką o rosnących numerach wierzchołków aż do wierzchołka math po czym wraca do wierzchołka 1, idąc po malejących numerach wierzchołków. Na cykl takiej postaci możemy też spojrzeć nieco inaczej: z wierzchołka 1 prowadzimy dwie rozłączne ścieżki o rosnących numerach wierzchołków, które na końcu spotykają się w wierzchołku math Ścieżki o rosnących numerach wierzchołków będziemy dalej nazywali po prostu ścieżkami rosnącymi.

Takie spojrzenie na nasz problem pozwala uzyskać pierwsze rozwiązanie wielomianowe. Zastosujemy metodę programowania dynamicznego. Dla każdej pary wierzchołków math takich że math przez math oznaczmy sumę wag w najlżejszej parze ścieżek rosnących zaczynających się w wierzchołku 1, kończących się odpowiednio w wierzchołkach math oraz math oraz przechodzących przez każdy wierzchołek – poza wierzchołkiem 1 i, ewentualnie, wierzchołkiem math – co najwyżej raz. Po chwili namysłu zauważamy, że aby dało się te wartości obliczać, na math musimy nałożyć jeszcze jeden dodatkowy warunek: nasze dwie ścieżki rosnące muszą przechodzić przez wszystkie wierzchołki ze zbioru math Teraz jest już całkiem prosto opisać przejścia ze stanu math Naszą parę ścieżek rosnących możemy rozszerzyć za pomocą krawędzi wychodzącej z wierzchołka math albo z wierzchołka math Aby był spełniony nasz dodatkowy warunek, dodana krawędź musi prowadzić do wierzchołka math W pierwszym przypadku aktualizujemy wartość math (za pomocą math ), a w drugim – wartość math (tym razem za pomocą math ). Jeśli jednak math to mamy tylko jedno przejście, do stanu math (za pomocą math ). Zaczynamy, oczywiście, od stanu math a kończymy w stanie math Jeśli graf będziemy reprezentować za pomocą tablicy sąsiedztwa (czyli funkcję wagi math  zapiszemy w tablicy dwuwymiarowej), otrzymamy algorytm o złożoności czasowej math

Może wydawać się nieco zaskakujące, że istnieje bardziej efektywne rozwiązanie naszego problemu. Aby je dostrzec, należy przyjrzeć się dokładniej strukturze cyklu bitonicznego.

Załóżmy, że gdzieś na tym cyklu, na jednej ze ścieżek rosnących, leży jakaś „długa” krawędź math czyli taka, że math Załóżmy na razie, że math i  math Wówczas możemy od razu powiedzieć coś o drugiej ścieżce rosnącej: mamy na niej jakąś krawędź math dla math dalej znajduje się sekwencja krawędzi „jednostkowych” math wreszcie z wierzchołka math wychodzi krawędź prowadząca do jakiegoś wierzchołka math patrz rysunek poniżej.

obrazek

Powyższe spostrzeżenie pozwala uprościć postać stanu w naszym programowaniu dynamicznym. Otóż wystarczy nam tylko jeden parametr: stan math będzie reprezentować najlżejszą parę ścieżek rosnących, z których jedna kończy się w wierzchołku math i wchodzi tam długą krawędzią, natomiast druga kończy się w wierzchołku math Oczywiście, ścieżki muszą być rozłączne, pomijając wierzchołek 1, oraz muszą razem zawierać wszystkie wierzchołki z zakresu math Przejścia pomiędzy stanami są wyznaczone przez długie krawędzie. Dokładniej, długa krawędź z wierzchołka math do wierzchołka math generuje przejście ze stanu math do stanu math jeśli tylko istnieje sekwencja krawędzi jednostkowych prowadzących z wierzchołka math do wierzchołka math

W tym algorytmie będziemy mieli co najwyżej tyle przejść między stanami, ile jest długich krawędzi w grafie, a jedyne wyzwanie polega na tym, żeby efektywnie symulować te przejścia. To już nie będzie trudne, będziemy to wykonywać w czasie stałym. Musimy jedynie umieć sprawdzać, czy dwa zadane wierzchołki są połączone sekwencją krawędzi jednostkowych, a jeśli tak, to jaka jest suma wag tych krawędzi. Do tego celu wystarczą nam ciągi sum częściowych dwóch ciągów: ciągu math oznaczającego istnienie krawędzi math (jedynka, jeśli krawędź istnieje, zero, jeśli nie istnieje) oraz ciągu math  Oznaczmy przez math  i  math tablice reprezentujące odpowiednie ciągi sum częściowych (czyli math i analogicznie dla math ). Tablice te łatwo obliczamy w czasie math  Wówczas przejście między stanami math i  math za pomocą długiej krawędzi math jest możliwe, gdy math  a waga tego przejścia to math

Ustalenie stanów początkowych i końcowych w usprawnionym programowaniu dynamicznym pozostawiamy Czytelnikowi. Całe rozwiązanie działa w czasie math  a zatem liniowo od rozmiaru wejścia.