Zbigniew J. CzechMarcin Stefaniak
Treść zadania, OpracowanieProgram wzorcowy


Podróż


Rozważmy graf opisujący sieć komunikacji publicznej, np. sieć autobusową, tramwajową, metra, itp. Wierzchołki grafu, o numerach 1, 2, ..., n, reprezentują przystanki, a krawędzie langle p_i, p_j rangle, (dla p_ineq p_j) oznaczają możliwe, bezpośrednie przejazdy od przystanku pi do pj (1 le p_i, p_j le n).

W sieci kursują pojazdy linii komunikacyjnych. Linie komunikacyjne oznaczone są numerami 1, 2, ..., k. Linia komunikacyjna l zdefiniowana jest przez ciąg przystanków p_(l,1), p_(l,2), ..., p_(l, s_l), przez które przejeżdżają pojazdy linii, oraz czasy przejazdów r_(l,1), r_(l,2), ..., r_(l,s_l-1) pomiędzy przystankami - r_(l,1) jest czasem przejazdu od przystanku p_(l,1) do p_(l,2), lub z powrotem, tj. od przystanku p_(l,2) do p_(l,1); r_(l,2) jest czasem przejazdu od przystanku p_(l,2) do p_(l,3), itd. Wszystkie przystanki linii są różne, tj. dla ineq j zachodzi p_(l,i) neq p_(l,j).

Na danej linii l pojazdy kursują z określoną częstotliwością cl, gdzie cl jest liczbą ze zbioru (6, 10, 12, 15, 20, .... Pojazdy linii wyruszają z przystanku p_(l,1) o każdej ``okrągłej'' godzinie doby, g:0, (0 le g le 23), a następnie zgodnie z częstotliwością linii, a więc o godzinach g:cl, g:2cl, ... itd. (g:cl oznacza "`cl minut po godzinie g''). Ruch pojazdów linii odbywa się jednocześnie w obu kierunkach: z przystanku p_(l,1) do p_(l,s_l), a także z przystanku p_(l,s_l) do p_(l,1). Godziny odjazdów pojazdów linii z przystanku p_(l,s_l) są takie same, jak z przystanku p_(l,1).

W tak zdefiniowanej sieci komunikacji publicznej chcemy odbyć podróż z przystanku początkowego x, do przystanku końcowego y. Zakładamy, że podróż jest możliwa i nie będzie trwała dłużej niż 24 godziny. W trakcie podróży możemy się przesiadać dowolną liczbę razy z jednej linii komunikacyjnej na inną. Przyjmujemy, że czas dokonania przesiadki jest równy 0, jednakowoż, zmieniając linię musimy liczyć się z koniecznością czekania na pojazd linii, do którego chcemy się przesiąść. Naszym celem jest odbycie podróży z przystanku początkowego x, do przystanku końcowego y, w jak najkrótszym czasie.

Przykład

Na poniższym rysunku przedstawiono schemat sieci komunikacyjnej o 6 przystankach i dwóch liniach:
1
i
2
. Pojazdy linii
1
kursują pomiędzy przystankami 1, 3, 4 i 6, a linii
2
pomiędzy przystankami 2, 4, 3 i 5. Częstotliwości kursowania pojazdów linii wynoszą, odpowiednio, c1=15 oraz c2=20. Czasy przejazdów pomiędzy przystankami umieszczono obok krawędzi sieci opatrując je indeksami 1 i 2 dla poszczególnych linii.


          defdrawth...

Załóżmy, że o godzinie 23:30 znajdujemy się na przystanku początkowym 5 i chcemy odbyć podróż do przystanku końcowego 6. Wówczas musimy odczekać 10 minut i o godzinie 23:40 wyjeżdżamy linią
2
. Nasza podróż może mieć dwa warianty. W pierwszym wariancie, po dojechaniu do przystanku 3 o godzinie 23:51 i odczekaniu 3 minut, przesiadamy się o godzinie 23:54 na linię
1
i dojeżdżamy do przystanku 6 o godzinie 0:16 (następnego dnia). W drugim wariancie, dojeżdżamy linią
2
do przystanku 4 o godzinie 0:8, czekamy 13 minut i o godzinie 0:21 wsiadamy do pojazdu linii
1
, osiągając przystanek końcowy 6 o godzinie 0:31. Tak więc najwcześniej możemy dotrzeć do przystanku 6 o godzinie 0:16.

Zadanie

Napisz program, który:

Wejście

W pierwszym wierszu pliku tekstowego pod.in zapisanych jest sześć liczb całkowitych, pooddzielanych pojedynczymi odstępami: Przystanki są ponumerowane od 1 do n, a linie komunikacyjne od 1 do k.

W kolejnych 3k wierszach opisane są kolejne linie komunikacyjne - opis każdej linii zajmuje trzy kolejne wiersze.

Suma liczb przystanków, na wszystkich liniach razem, nie przekracza 4000 (tzn. s_1 + s_2 + ... +s_k...).

Wyjście

Twój program powinien zapisać w pierwszym i jedynym wierszu pliku tekstowego pod.out dwie liczby całkowite oddzielone pojedynczym odstępem: godzinę dotarcia do przystanku końcowego gy (0 le g_y le 23) oraz minutę dotarcia do przystanku końcowego my (0 le m_y le 59).

Przykład

Dla pliku wejściowego pod.in
6 2 5 6 23 30
4 15
1 3 4 6
9 12 10
4 20
5 3 4 2
11 17 11
poprawną odpowiedzią jest plik wyjściowy pod.out
0 16

Rozwiązanie

W pierwszej kolejności zaprojektujemy strukturę danych, w której będziemy przechowywać rozkład jazdy pojazdów. Z zadania wynika, że sieć komunikacyjna działa całą dobę. Pojazdy linii komunikacyjnych wyruszają z przystanków krańcowych z określoną częstotliwością, c, gdzie c jest liczbą ze zbioru (6, 10, 12, 15, 20, 30, 60). Ponieważ każda z tych częstotliwości dzieli liczbę 60, to pojazdy danej linii będą odjeżdżać z przystanków zawsze w tych samych minutach dowolnej godziny. Przykładowo, niech dla pewnej linii c=15. Wówczas pojazdy tej linii wyruszają z przystanku p1 o godzinie g minut 0, 15, 30, 45, gdzie gin(0,1,ldots,23). Podobnie, czasy odjazdu z przystanku p2 są równe godzina g minut (0+r_1)bmod 60, (15+r_1)bmod 60, (30+r_1)bmod 60, (45+r_1)bmod 60, gdzie r1 jest czasem przejazdu od przystanku p1 do p2. Tak więc, zapamiętanie rozkładu jazdy na dowolnym przystanku wymaga przechowania jednej z chwil odjazdów wyrażonej jako liczba minut po (dowolnej) godzinie g (pozostałe chwile odjazdów można łatwo obliczyć znając częstotliwość kursowania linii). Dane określające rozkład jazdy dla dowolnego przystanku pi\/ można przechować w rekordach o postaci:
1    type dane_odj = { dane dotyczące odjazdów z przystanku pi }
2        record
3            pi: integer; { pi - przystanek aktualny }
4            pj: integer; { pj - przystanek następny (sąsiedni) }
5            r: byte; { ``czysty'' czas przejazdu od przystanku }
6                                  { pi do pj }
7            go: byte; { jedna z chwil odjazdów pamiętana jako liczba }
8                                  { minut po (dowolnej) godzinie g }
9            c: byte; { częstotliwość kursowania pojazdów linii, }
10                                  { której dotyczy niniejszy rekord }
11        end;

zaś cały rozkład jazdy w tablicy: xxx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=

\> rozkl_jazdy\/: array[1 .. q] of dane_odj\/; gdzie q\/ jest całkowitą liczbą rekordów. Zauważmy, że dla dowolnego przystanku pi\/, z wyjątkiem przystanków krańcowych, liczba rekordów w rozkładzie jazdy jest równa podwojonej liczbie linii komunikacyjnych (bo linie prowadzą w obie strony), których pojazdy dojeżdżają, a następnie odjeżdżają z tego przystanku. Dla przystanków krańcowych liczba rekordów jest równa liczbie linii komunikacyjnych wychodzących z tych przystanków. Dla przykładowej sieci z treści zadania, q=12. Liczby rekordów w rozkładzie jazdy dla wierzchołków od 1 do 6 są równe, odpowiednio, 1, 1, 4, 4, 1 oraz 1.

Zadanie można rozwiązać adaptując do tego celu algorytm Dijkstry znajdowania najkrótszych dróg w grafie (zobacz [14]). W naszym przypadku czas przejazdu między przystankami nie jest stały i zależy od chwili h\/, w której przybywamy na przystanek. Znając chwilę h\/, przystanki pi\/, pj\/ oraz linię komunikacyjną, którą podróżujemy (tj. odpowiedni rekord w tablicy rozkl_jazdy\/), czas przejazdu pomiędzy przystankami pi\/ oraz pj\/ można wyznaczyć za pomocą funkcji:
1function{\it czas_przejazdu\/}({\it pi\/}, {\it pj\/}: {\it integer\/}; h: {\it integer\/}): {\it integer\/};
2begin
3    {\it czas_przejazdu\/} := ((60+pi-h),(bf mod),c...;
4end

Wartość (60 + go - h) bmod c jest czasem oczekiwania na przystanku pi\/, zaś r\/ jest ``czystym'' czasem przejazdu wybraną linią od przystanku pi\/ do pj\/.

Stosując algorytm Dijkstry do rozwiązania naszego zadania będziemy wyróżniać wśród wszystkich przystanków sieci te z nich, dla których najkrótszy czas dojazdu z przystanku początkowego x został już ustalony. Do tego celu wprowadzimy tablicę: xxx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=

\> ustal\/: array[1 .. n] of boolean; w której wartość ustal\/[i] = true\/, i=1, 2, ..., n, oznacza, że znamy już najkrótszy czas dojazdu od przystanku x do i. Początkowo inicjujemy wszystkie elementy na false\/, za wyjątkiem elementu ustal\/[x], któremu przypisujemy wartość true\/. Będziemy także korzystać z tablicy: xxx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=xx\=

\> D\/: array[1 .. n] of integer\,; w której zapamiętamy najkrótsze czasy dojazdu wyrażone w minutach od przystanku x do przystanku i, i=1, 2, ..., n, ineq x. Elementy tablicy inicjujemy wartością infty, za wyjątkiem przystanku x, dla którego przyjmujemy D[x]=0. Oto rozwiązanie naszego zadania:
1D[x] := 0; { przyjęcie czasu dojazdu do przystanku x jako równego 0 }
2for k := 1 to n do
3    { W każdej iteracji ustalany jest najkrótszy czas przejazdu }
4    { z przystanku x do jednego z przystanków sieci. }
5    Znajdź indeks w taki, że D[w] jest minimalne wśród wszystkich
6        D[i], i = 1, 2, ..., n, dla których ustal[i] = false;
7    if D[w] = infty then
8        Nie istnieje ścieżka do przystanku końcowego; break;
9    end if;
10    ustal[w] := true;
11    if w = y then
12        break; { Zadanie rozwiązano; wynik: D[w]=D[y] }
13    end if;
14    for wszystkich połączeń (w, i) między sąsiednimi przystankami
15            w oraz i, dla których ustal[i] = false do
16        a :=czas_przejazdu(w, i, (mx+D[w]) mod 60);
17        if (aneq infty) and (D[w]+a<D[i]) then
18            D[i] := D[w]+a;
19        end if;
20    end for;
21end for;
22t := gx*60+mx+D[y];
23writeln((t div 60) mod 24, ' ', t mod 60);

W każdej iteracji instrukcji for w wierszach 2-21 zostaje dołączony jeden przystanek, dla którego zostaje ustalony minimalny czas dojazdu z przystanku początkowego x. Przystanek ten, o indeksie w, ma najkrótszy czas dojazdu D[w] przez wszystkie przystanki, dla których najkrótsze czasy zostały już ustalone. W wierszach 14-20 sprawdzamy czy po dołączeniu przystanku w, czasy dojazdu do pozostałych przystanków o nieustalonym czasie dojazdu przez przystanek w nie uległy skróceniu. Jeżeli tak, to czasy te modyfikujemy.

Złożoność czasowa poprawiania tablicy D (wiersze 14-20) jest O(q), gdzie q jest liczbą rekordów w tablicy rozkl_jazdy\/. Złożoność czasowa n-krotnego wyszukiwania minimalnej wartości D[w] jest O(n2)

W efektywnej implementacji algorytmu warto jest pogrupować rekordy tablicy rozkl_jazdy\/ według numerów przystanków odjazdu, ponieważ uaktualniając tablicę D (wiersze 14-20) przeglądamy połączenia wychodzące z przystanku w. Numery przystanków są liczbami całkowitymi z zakresu 1 .. n, więc grupowanie takie można zrealizować za pomocą sortowania pozycyjnego z użyciem liczników częstości w czasie O(n+q) (zobacz [11]). Podsumowując, złożoność czasowa przedstawionego rozwiązania jest O(n2+q).

Testy

Do generowania losowych testów użyto generatora gen_pod.pas, który losuje zestaw danych o zadanych parametrach (bez gwarancji, że rozwiązanie istnieje). Ręcznie zadano stacje końcowe tak, żeby rozwiązanie było znajdowane pod koniec przeszukiwania.