You are on page 1of 303

Uniwersytet Warszawski Wydzia Matematyki, Informatyki i Mechaniki

Piotr Staczyk
Nr albumu: 209291

Algorytmika praktyczna w konkursach Informatycznych


Praca magisterska na kierunku INFORMATYKA w zakresie ALGORYTMIKA

Praca wykonana pod kierunkiem dra hab. Krzysztofa Diksa Instytut Informatyki

Czerwiec 2006

Owiadczenie kierujacego praca


Owiadczam, e niniejsza praca zostaa przygotowana pod moim kierunkiem i stwierdzam, e spenia ona warunki do przedstawienia jej w postepowaniu o nadanie tytuu zawodowego.

Data

Podpis kierujacego praca

Owiadczenie autora (autorw) pracy


wiadom odpowiedzialnoci prawnej owiadczam, e niniejsza praca dyplomowa zostaa napisana przeze mnie samodzielnie i nie zawiera treci uzyskanych w sposb niezgodny z obowiazujacymi przepisami. Owiadczam rwnie, e przedstawiona praca nie bya wczeniej przedmiotem procedur zwiazanych z uzyskaniem tytuu zawodowego w wyszej uczelni. Owiadczam ponadto, e niniejsza wersja pracy jest identyczna z zaaczona wersja elektroniczna.

Data

Podpis autora (autorw) pracy

Streszczenie
Brak

Sowa kluczowe
Brak

Dziedzina pracy (kody wg programu Socrates-Erasmus)


11.3 Informatyka

Klasykacja tematyczna
Brak

Spis treci
1. Przedmowa . . . . . . . 1.1. Struktura ksiki . . a 1.2. Wymagania wstpne 1.3. Podzikowania . . . 1.4. Nagwki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 8 9 9 10 13 14 18 24 29 35 39 42 48 54 57 61 64 64 68 70 73 74 76 78 82 87 91 92 94 100 108 110 114 117

2. Algorytmy Grafowe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.1. Struktura grafu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2. Przeszukiwanie grafu wszerz . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.3. Przeszukiwanie grafu w gb . . . . . . . . . . . . . . . . . . . . . . . . . . . . a 2.4. Silnie spjne skadowe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.5. Sortowanie topologiczne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.6. Acykliczno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.7. Dwuspjne skadowe, mosty i punkty artykulacji . . . . . . . . . . . . . . . . 2.8. cieka i cykl Eulera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.9. Minimalne drzewo rozpinajce . . . . . . . . . . . . . . . . . . . . . . . . . . a 2.10. Algorytm Dijkstry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.11. Algorytm Bellmana - Forda . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.12. Maksymalny przepyw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.12.1. Maksymalny przepyw metod Dinica . . . . . . . . . . . . . . . . . . a 2.12.2. Maksymalny przepyw dla krawdzi jednostkowych . . . . . . . . . . . 2.12.3. Najtaszy, maksymalny przepyw dla krawdzi jednostkowych . . . . . 2.13. Maksymalne skojarzenie w grae dwudzielnym . . . . . . . . . . . . . . . . . 2.13.1. Dwudzielno grafu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.13.2. Maksymalne skojarzenie w grae dwudzielnym w czasie O(n (n + m)) 2.13.3. Maksymalne skojarzenie w grae dwudzielnym w czasie O((n + m) n) 2.13.4. Najdrosze skojarzenie w grae dwudzielnym . . . . . . . . . . . . . . 3. Geometria obliczeniowa na paszczynie 3.1. Odlego punktu od prostej . . . . . . . 3.2. Powierzchnia wielokta . . . . . . . . . . a 3.3. Przynaleno punktu . . . . . . . . . . 3.4. Punkty przecicia . . . . . . . . . . . . . 3.5. Trzy punkty okrg . . . . . . . . . . a 3.6. Wypuka otoczka . . . . . . . . . . . . . 3.7. Sortowanie ktowe . . . . . . . . . . . . a 3.8. Para najbliszych punktw . . . . . . . 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

4. Kombinatoryka . . . . . . . . . . . . . . . . . . . . . . . . 4.1. Permutacje w kolejnoci antyleksykogracznej . . . . . 4.2. Permutacje minimalne transpozycje . . . . . . . . . 4.3. Permutacje minimalne transpozycje ssiednie . . . a 4.4. Wszystkie podzbiory zbioru . . . . . . . . . . . . . . . 4.5. Podzbiory k-elementowe w kolejnoci leksykogracznej 4.6. Podziay zbioru minimaln liczb zmian . . . . . . . . a a 4.7. Podziay liczby w kolejnoci antyleksykogracznej . . . 5. Teoria liczb . . . . . . . . . . . . . 5.1. Dwumian Newtona . . . . . . . 5.2. Najwikszy wsplny dzielnik . . 5.3. Odwrotno modularna . . . . 5.4. Kongruencje . . . . . . . . . . . 5.5. Szybkie potgowanie modularne 5.6. Sito liczb pierwszych . . . . . . 5.7. Lista liczb pierwszych . . . . . 5.8. Test pierwszoci . . . . . . . . . 5.9. Arytmetyka wielkich liczb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

123 123 125 127 129 131 132 134 137 137 139 142 144 146 148 149 151 154 171 172 175 177 180 182 185 188 192 203 203 206 207 213 217 220 221 222 224 225 226 231 231 232 235 238

6. Struktury danych . . . . . . . . . . . . . . . . . . . . . . 6.1. Struktura danych do reprezentacji zbiorw rozcznych a 6.2. Drzewa poszukiwa binarnych . . . . . . . . . . . . . . 6.2.1. Drzewa maksimw . . . . . . . . . . . . . . . . 6.2.2. Drzewa licznikowe . . . . . . . . . . . . . . . . 6.2.3. Drzewa pozycyjne . . . . . . . . . . . . . . . . 6.2.4. Drzewa pokryciowe . . . . . . . . . . . . . . . . 6.3. Binarne drzewa statyczne dynamicznie alokowane . . . 6.4. Wzbogacane drzewa binarne . . . . . . . . . . . . . . . 7. Algorytmy tekstowe . . . . . . . . . . . . . . . . . . 7.1. Algorytm KMP . . . . . . . . . . . . . . . . . . . 7.2. Minimalny okres sowa . . . . . . . . . . . . . . . 7.3. KMP dla wielu wzorcw (algorytm Bakera) . . . 7.4. Promienie palindromw w sowie . . . . . . . . . 7.5. Drzewa suksowe . . . . . . . . . . . . . . . . . . 7.5.1. Liczba wystpie wzorca w tekcie . . . . a 7.5.2. Liczba rnych podsw sowa . . . . . . . 7.5.3. Najdusze podsowo wystpujce n razy . a 7.6. Maksymalny leksykogracznie suks . . . . . . . 7.7. Rwnowano cykliczna . . . . . . . . . . . . . . 7.8. Minimalna leksykograczna cykliczno sowa . . 8. Algebra liniowa . . . . . . . . 8.1. Eliminacja Gaussa . . . . . 8.1.1. Eliminacja Gaussa w 8.1.2. Eliminacja Gaussa w 8.2. Programowanie liniowe . . . . . . . Z2 Zp . . . . . . . . . . . . . . . . . 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9. Elementy strategii podczas zawodw . . . . . . . . . . . . . . . . . . . . 9.1. Szacowanie oczekiwanej zoonoci czasowej . . . . . . . . . . . . . . . . 9.2. Strategia pracy w druynie . . . . . . . . . . . . . . . . . . . . . . . . . 9.3. Szablon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.4. Makele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5. Parametry kompilacji programw . . . . . . . . . . . . . . . . . . . . . . 9.5.1. -Wec++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.2. -Wformat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.3. -Wshadow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.4. -Wsequence-point . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.5. -Wunused . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.6. -Wuninitialized . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.5.7. -Woat-equal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.6. Nieustanny Time - Limit . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.6.1. Wczytywanie wejcia . . . . . . . . . . . . . . . . . . . . . . . . . 9.6.2. Kompilacja z optymalizacjami i generowanie kodu maszynowego 9.6.3. Lepsze wykorzystanie pamici podrcznej . . . . . . . . . . . . . 9.6.4. Preprocessing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.Rozwizania zada . . . . . . . . . . . . . a 10.1. Algorytmy Grafowe . . . . . . . . . . . . 10.1.1. Mrwki i biedronka . . . . . . . . 10.1.2. Komiwojaer Bajtazar . . . . . . 10.1.3. Drogi . . . . . . . . . . . . . . . 10.1.4. Spokojna komisja . . . . . . . . . 10.1.5. Wirusy . . . . . . . . . . . . . . 10.1.6. Linie autobusowe . . . . . . . . . 10.1.7. Przemytnicy . . . . . . . . . . . 10.1.8. Skoczki . . . . . . . . . . . . . . 10.2. Geometria obliczeniowa na paszczynie 10.2.1. Akcja komandosw . . . . . . . . 10.2.2. Pomniki . . . . . . . . . . . . . . 10.2.3. Otarze . . . . . . . . . . . . . . 10.3. Kombinatoryka . . . . . . . . . . . . . . 10.3.1. Liczby permutacyjnie - pierwsze 10.4. Teoria liczb . . . . . . . . . . . . . . . . 10.4.1. Bilard . . . . . . . . . . . . . . . 10.4.2. Wyliczanka . . . . . . . . . . . . 10.4.3. acuch . . . . . . . . . . . . . . 10.5. Struktury danych . . . . . . . . . . . . . 10.5.1. Mapki . . . . . . . . . . . . . . 10.5.2. Kodowanie permutacji . . . . . . 10.5.3. Marsjaskie mapy . . . . . . . . 10.5.4. Puste prostopadociany . . . . . 10.6. Algorytmy tekstowe . . . . . . . . . . . 10.6.1. Szablon . . . . . . . . . . . . . . 10.6.2. MegaCube . . . . . . . . . . . . 10.6.3. Palindromy . . . . . . . . . . . . 10.6.4. Punkty . . . . . . . . . . . . . . 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

245 245 247 250 250 251 251 253 254 255 258 259 260 260 261 263 265 266 269 269 269 270 270 271 271 272 272 273 273 273 274 274 274 274 275 275 275 276 276 276 277 277 277 278 278 279 279 279

10.7. Algebra liniowa . . . . 10.7.1. Taniec . . . . . 10.7.2. Sejf . . . . . . 10.7.3. Szalony malarz

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

280 280 281 281

A. Nagwki Eryka Kopczyskiego . . . . . . . . . . . . . . . . . . . . . . . . . . 283 B. Sposoby na sukces w zawodach . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 C. Zbir zada na programowanie dynamiczne . . . . . . . . . . . . . . . . . . 293 D. Zbir zada na programowanie zachanne . . . . . . . . . . . . . . . . . . . . 295 Bibliograa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297

Rozdzia 1

Przedmowa
Najstarszym akademickim konkursem programistycznym jest International Collegiate Programming Contest organizowany przez Association for Computing Machinery (w skrcie ACM, ocjalna strona konkursu http://icpc.baylor.edu/icpc/ ). Pierwsze zawody naowe odbyy si 2 lutego 1977 roku w Atlancie, w Stanach Zjednoczonych (stan Georgia). Na pocztku lat dziewidziesitych w eliminacjach do tego konkursu brao udzia okoo 400 a a druyn, z ktrych 25 kwalikowao si co roku do nau. Dzi o 70 - 80 miejsc w naach walczy ponad 4000 druyn z 1600 uczelni caego wiata. Wzrost liczby uczestnikw wiadczy o ogromnej popularnoci konkursu, ktrej towarzyszy zwikszajcy si stopie przygotowania a druyn oraz trudno zada. wiatowej rangi konkursem, organizowanym dla uczniw szk rednich, jest Olimpiada Informatyczna (strona konkursu http://olympiads.win.tue.nl/ioi/ ), ktra zostaa po raz pierwszy zorganizowana w 1989 roku. W konkursie tym bierze udzia po czterech reprezentantw z kadego kraju, wybieranych w ramach olimpiad narodowych. Oprcz tych, cieszcych a si wieloletni tradycj konkursw, powstaje wiele nowych, takich jak TopCoder, Google Code a a Jam czy Imagine Cup. Udzia w konkursach dla uczniw oraz studentw staje si nieodzownym elementem nauki algorytmiki. Nieustannie podwyszajcy si poziom konkursw przynosi ze sob rne a a metodologie przygotowywania si do zawodw oraz specjalne techniki pisania programw. Ich celem jest minimalizowanie czasu potrzebnego na rozwizanie zadania, przy jednoczea snym eliminowaniu jak najwikszej liczby powstajcych bdw. Ze wzgldu na ograniczony a czas trwania konkursw, jego uczestnicy nie mog sobie pozwoli na wymylanie rozwiza a a wszystkich zada od samego pocztku. W wielu przypadkach musz wykorzystywa pomysy a a zastosowane w podobnych zadaniach, rozwizywanych ju wczeniej, co pozwala im zaoszcza dzi troch czasu. Ksika ta jest swego rodzaju podrcznikiem do algorytmiki. Nie zawiera ona jednak a wnikliwego opisu algorytmw wraz z ich analiz zoonoci oraz dowodem poprawnoci. Jest a to kolekcja implementacji rnych, bardzo przydatnych podczas zawodw algorytmw oraz zbir zada pozwalajcy na przewiczenie ich wykorzystania w praktyce. a Znaczc cz grona czytelnikw stanowi bd zapewne osoby zainteresowane zwika a a szeniem swoich umiejtnoci w zakresie szybkiego implementowania zada algorytmicznych w jzyku C++. Lektura ta na pewno wpynie pozytywnie na szybko rozwizywania zada a podczas takich konkursw jak Olimpiada Informatyczna. Uyteczno tej ksiki bdzie tym a wiksza podczas konkursw typu ACM ICPC, podczas ktrych dozwolone jest korzystanie z literatury. Wiele algorytmw z tej ksiki moe by po prostu przepisanych do implementoa wanych podczas zawodw programw. Prezentowane tutaj algorytmy zostay tak napisane, 7

aby ich adaptacja, w celu wykorzystania w rnych zadaniach, bya jak najprostsza, a jednoczenie ich kod rdowy jak najkrtszy. Pozycja ta moe suy nie tylko zawodnikom w poczeniu z ksik dotyczc poda a a a a staw algorytmiki, tak jak Wprowadzenie do algorytmiki [WDA], moe take stanowi doskoa nay sposb nauki od podstaw, czcy w sobie teoretyczn analiz algorytmw, z podeja a a ciem bardziej praktycznym, opisanym w tej ksice. Nauk tak w istotny sposb uatwi a a a liczne odwoania do literatury. Pomys na napisanie ksiki narodzi si podczas treningw mojego zespou Warsaw a Predators, do zawodw ACM ICPC. W czasie wielu sesji treningowych tworzylimy i rozbudowywalimy nasz biblioteczk algorytmiczn, w ktrej umieszczalimy implementacje a a najczciej wykorzystywanych w trakcie zawodw algorytmw i struktur danych. Ksik t a mona okreli mianem szczegowej dokumentacji do biblioteczki algorytmicznej druyny Warsaw Predators. Mam nadziej e materiay w niej zawarte pomog wielu osobom w przya gotowaniach do zawodw.

1.1. Struktura ksiki a


Ksika zostaa podzielona na siedem rozdziaw, w ktrych omwione s algorytmy z ra a nych dziedzin algorytmiki: teoria grafw, geometria obliczeniowa, kombinatoryka, teoria liczb, struktury danych, algorytmy tekstowe oraz algebra liniowa. W kadym z tych rozdziaw, przedstawione s rne algorytmy wraz z ich implementacjami, pochodzcymi z biblioteczki a a algorytmicznej. Ich omwienie ma na celu przyblienie problematyki poruszanego zagadnienia oraz zaprezentowanie sposobu wykorzystania algorytmw w zadaniach. W celu zapoznania si z dowodami poprawnoci, czy dokadn analiz zoonoci, naley siga do literatury, do a a ktrej odwoania umieszczone s w rnych miejscach ksiki. a a W dziale dodatkw znajduje si rozdzia dotyczcy programowania dynamicznego oraz a zachannego. Z uwagi na fakt, i z tymi dwoma technikami nie wi si specyczne struka a tury danych ani algorytmy (rozwizania zada bazujcych na programowaniu dynamicznym, a a czy zachannym - wykorzystuj algorytmy z rnych dziedzin), zatem rozdzia ten nie zaa wiera adnych przydatnych implementacji, lecz odwoania do wielu zada, na ktrych mona wiczy umiejtno stosowania tych metod w praktyce. W dodatkach umieszczone s rwnie obserwacje oraz rady pochodzce od wielu zawoda a nikw wiatowej klasy. Lektura tego rozdziau moe okaza si niezwykle cenna, gdy zawarte w nim informacje pomagaj unikn wielu problemw podczas przygotowa do zawodw. a a W poszczeglnych rozdziaach ksiki umieszczone s zadania, ktrych rozwizanie wie a a a a si z omawianym algorytmem. Zadania te s wybrane w taki sposb, aby algorytmy wymaa gane do ich rozwizania byy omwione, w miar moliwoci, w rozdziaach poprzedzajcych a a zamieszczone zadania. W ten sposb, ledzc w kolejnoci poszczeglne rozdziay ksiki, czya a telnik bdzie posiada wiedz potrzebn do rozwizania wszystkich zada. Wikszo z tych a a zada pochodzi z polskich konkursw informatycznych, takich jak Olimpiada Informatyczna, Potyczki Algorytmiczne czy Pogromcy Algorytmw. Programy stanowice ich rozwizania a a znajduj si na doczonej pycie. Dodatkowo, na kocu ksiki umieszczony jest rozdzia a a a zawierajcy wskazwki do wszystkich tych zada. Do kadego z nich znajduje si po kilka a podpowiedzi, ktre mog by pomocne podczas rozwizywania zadania kada kolejna a a wskazwka konkretyzuje pomys naszkicowany w poprzedniej. Dziki takiemu podejciu, jeli pocztkowe wskazwki okazuj si niewystarczajce do rozwizania zadania, mona pokusi a a a a si o przeczytanie kolejnych. Oprcz penych zada, w ksice znajduj si rwnie odwoania do innych zada, poa a 8

chodzcych z internetowych serwisw, umoliwiajcych automatyczn werykacj poprawnoa a a ci rozwiza. Czytelnik moe najpierw rozwiza samodzielnie zadanie, a nastpnie wysa a a je do systemu sprawdzajcego, w celu zwerykowania poprawnoci. Poniej znajduje si lista a serwisw internetowych, z ktrych wybrane zostay zadania do tej ksiki: a http://acm.sgu.ru/ Saratov State University :: Online Contester http://acm.uva.es/ Valladolid Programming Contest Site http://spoj.sphere.pl/ Sphere Online Judge

Wszystkie zadania zostay podzielone na trzy kategorie: zadania proste, redniej trudnoci oraz trudne. Trudno zada jest rzecz subiektywn. Podzia trudnoci uyty w ksice baa a a zuje na statystykach rozwiza, dostpnych w wymienionych wyej serwisach. Zadania trudne a charakteryzuj si stosunkowo ma liczb prb ich rozwizania, a wrd nich niewielka cz a a a a rozwiza okazuje si by poprawna. Zadania proste z kolei cechuj si wysokim odsetkiem a a zaakceptowanych rozwiza w stosunku do wszystkich nadesanych programw. a

1.2. Wymagania wstpne


Wszystkie przedstawione w tej ksice algorytmy s zaimplementowane w jzyku C++ a a jego znajomo jest nieodzowna do zrozumiaej analizy przedstawianych tu algorytmw. Konieczna jest rwnie znajomo biblioteki Standard Template Library (w skrcie STL), ktrej dokumentacj mona znale na stronie http://www.sgi.com/tech/stl/. Implementacja algorytmw wykorzystuje rne struktury danych oraz funkcje z tej biblioteki najwaniejsze z nich, to funkcje sort, swap i binary search oraz struktury danych vector, map i priority queue. Oprcz powyszych elementw, biblioteka STL zawiera wiele bardzo uytecznych narzdzi. Polecam zapoznanie si z ni, gdy moe ona w istotny sposb wpyn a a na efektywno rozwizywania zada algorytmicznych. a

1.3. Podzikowania
Opisywana w tej ksice biblioteczka algorytmiczna zostaa stworzona wsplnie z moimi koa legami z zespou Warsaw Predators Markiem Cyganem i Marcinem Pilipczukiem. Chciabym rwnie podzikowa Tomaszowi Idziaszkowi, ktrego kilka chwytw programistycznych znalazo si w naszej biblioteczce oraz Erykowi Kopczyskiemu, ktry zgodzi si na opublikowanie zbioru swoich makr uywanych w konkursie TopCoder. Chciabym podzikowa Tomaszowi Czajce, Andrzejowi Gsienicy - Samkowi, Tomaszowi a Malesiskiemu, Krzysztofowi Onakowi oraz Marcinowi Stefaniakowi za wyraenie zgody na publikacj ich dobrych rad, ktre udzielili na prob prof. Krzysztofa Diksa swoim modszym kolegom, biorcym udzia w konkursie ACM ICPC na Uniwesytecie Warszawskim. a Wskazwki te zostay umieszczone w dziale Dodatki. Jednym z wyzwa, podczas gromadzenia materiaw do ksiki, by proces wybierania oda powiednich zada z rnych serwisw internetowych. Liczba dostpnych zada jest ogromna (w momencie pisania tego tekstu, serwis Valladolid Programming Contest Site zawiera ponad dwa tysice zada). Chciabym podzikowa Jakubowi Radoszewskiemu za udzielenie a cennych wskazwek dotyczcych wyboru zada. a Wielkie podzikowania nale si prof. Krzysztofowi Diksowi naszemu wykadowcy i a wielkiemu przyjacielowi, ktry wytrwale opiekowa si nami i stwarza warunki pozwalajce a 9

na efektywne przygotowania naszych druyn do zawodw. Ksika ta powstaa pod jego a opiek i nadzorem. a

1.4. Nagwki
Wszystkie programy w tej ksice s zaimplementowane w jzyku C++, z wykorzystaniem a a biblioteki STL. Poniewa podczas zawodw bardzo istotne jest, aby kody rdowe implementowanych programw byy jak najkrtsze, dlatego wielu zawodnikw stosuje pewne skrty dla najczciej wystpujcych w programach konstrukcji jzykowych. Podczas zawodw ACM a ICPC, w cigu piciogodzinnych sesji, do rozwizania jest 7 do 10 zada, zatem opaca si na a a pocztku zawodw przepisa zbir najwaniejszych instrukcji, a nastpnie wykorzystywa je a we wszystkich pisanych programach. Instrukcje te nazywamy mianem nagwkw zawieraj one zarwno list najczciej doczanych do programw bibliotek, jak i pewne operatory. a a Listing 1.1 przedstawia zbir podstawowych nagwkw pochodzcych z biblioteczki algoryta micznej mojego zespou wraz z komentarzami opisujcymi ich wykorzystanie. Na listingu 1.2 a przedstawione zostay te same nagwki po usuniciu komentarzy.
Listing 1.1: Nagwki z komentarzami 01 #include <cstdio> 02 #include <iostream> 03 #include <algorithm> 04 #include <string> 05 #include <vector> 06 using namespace std; // Dwa z najczciej uywanych typw o dugich nazwach - ich skrcenie jest bardzo // istotne 07 typedef vector<int> VI; 08 typedef long long LL; // W programach bardzo rzadko mona znale w peni zapisan instrukcj ptli. // Zamiast niej, wykorzystywane s trzy nastpujce makra: // FOR - ptla zwikszajca zmienn x od b do e wcznie 09 #dene FOR(x, b, e) for(int x=b; x<=(e); ++x) // FORD - ptla zmniejszajca zmienn x od b do e wcznie 10 #dene FORD(x, b, e) for(int x=b; x>=(e); x) // REP - ptla zwikszajca zmienn x od 0 do n. Jest ona bardzo // czsto wykorzystywana do konstruowania i przegldania struktur danych 11 #dene REP(x, n) for(int x=0; x<(n); ++x) // Makro VAR(v,n) deklaruje now zmienn o nazwie v oraz typie i wartoci // zmiennej n. Jest ono czsto wykorzystywane podczas operowania na iteratorach // struktur danych z biblioteki STL, ktrych nazwy typw s bardzo dugie 12 #dene VAR(v,n) typeof(n) v=(n) // ALL(c) reprezentuje par iteratorw wskazujcych odpowiednio na pierwszy i // za ostatni element w strukturach danych STL. Makro to jest bardzo przydatne // chociaby w przypadku korzystania z funkcji sort, ktra jako parametry // przyjmuje par iteratorw reprezentujcych przedzia elementw do posortowania. 13 #dene ALL(c) c.begin(),c.end() // Ponisze makro suy do wyznaczania rozmiaru struktur danych STL. Uywa si go // w programach, zamiast pisa po prostu x.size() z uwagi na fakt, i wyraenie // x.size() jest typu unsigned int i podczas porwnywania z typem int,

10

Listing 1.1: (c.d. listingu z poprzedniej strony) // proces kompilacji generuje ostrzeenie. 14 #dene SIZE(x) (int)x.size() // Bardzo poyteczne makro, suce do iterowania po wszystkich elementach w // strukturach danych STL. 15 #dene FOREACH(i,c) for(VAR(i,(c).begin());i!=(c).end();++i) // Skrt - zamiast pisa push back podczas wstawiania elementw na koniec // struktury danych, takich jak vector, wystarczy napisa PB 16 #dene PB push back // Podobnie - zamiast first bdziemy pisali po prostu ST 17 #dene ST rst // a zamiast second - ND. 18 #dene ND second

Listing 1.2: Nagwki bez komentarzy 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 #include <cstdio> #include <iostream> #include <algorithm> #include <string> #include <vector> using namespace std; typedef vector<int> VI; typedef long long LL; #dene FOR(x, b, e) for(int x=b; x<=(e); ++x) #dene FORD(x, b, e) for(int x=b; x>=(e); x) #dene REP(x, n) for(int x=0; x<(n); ++x) #dene VAR(v,n) typeof(n) v=(n) #dene ALL(c) c.begin(),c.end() #dene SIZE(x) (int)x.size() #dene FOREACH(i,c) for(VAR(i,(c).begin());i!=(c).end();++i) #dene PB push back #dene ST rst #dene ND second

Oprcz nagwkw podstawowych, istnieje rwnie wiele innych skrtw, ktre okazuj si a pomocne podczas rozwizywania zada. Omawiana w tej ksice biblioteczka, oprcz nagwa a kw podstawowych, zawiera rwnie dodatkowe makra, umieszczane tylko w tych programach, ktre z nich korzystaj. Ich lista przedstawiona jest na listingu 1.3. W prezentowanych w a ksice programach bdziemy odwoywali si zarwno do tych podstawowych, jak i dodata kowych nagwkw, bez informowania o koniecznoci dopisywania ich denicji do implementowanych programw. Dziki takiemu podejciu, kody rdowe, przedstawianych w ksice a algorytmw, s krtsze, a czytelnik nie musi analizowa za kadym razem powtarzajcych si a a fragmentw kodu.

11

Listing 1.3: Dodatkowe nagwki 01 #include <complex> 02 #include <iterator> 03 #include <set> 04 #include <bitset> 05 #include <map> 06 #include <stack> 07 #include <list> 08 #include <queue> 09 #include <deque> // Staa INF jest wykorzystywana jako reprezentacja nieskoczonoci. Ma ona // warto 1000000001, a nie 2147483647 (najwiksza warto typu int) ze // wzgldu na dwa fakty - prosty zapis, oraz brak przepenienia wartoci zmiennej // w przypadku dodawania dwch nieskoczonoci do siebie // ((int) 2147483647 + (int) 2147483647 = -2). 10 const int INF = 1000000001; // Staa EPS jest uywana w wielu algorytmach geometrycznych do porwnywania // wartoci bliskich zera (w zadaniach tego typu pojawia si wiele problemw // zwizanych z bdami zaokrgle) 11 const double EPS = 10e-9; // Skrcone nazwy rnych typw i operatorw o dugich nazwach 12 typedef vector<VI> VVI; 13 typedef vector<LL> VLL; 14 typedef vector<double> VD; 15 typedef vector<string> VS; 16 typedef pair<int, int> PII; 17 typedef vector<PII> VPII; 18 #dene PF push front 19 #dene MP make pair

Znane s przypadki, e niektrzy zawodnicy tworzyli przy uyciu makr wasne, specyczne a jzyki programowania, pozwalajce na szybkie pisanie programw. Takie podejcie wymaga a niestety przepisania znacznej iloci kodu, zanim przystpi si do waciwego rozwizywania a a problemu. W przypadku konkursw organizowanych przez internet podejcie takie okazuje si jednak bardzo wygodne. W dodatku A znajduje si przykadowe rodowisko programistyczne, wykorzystywane w konkursie TopCoder przez Eryka Kopczyskiego. Jest ono do rozbudowane, dlatego te korzystanie z niego w innych konkursach (takich jak ACM ICPC ) okazuje si nieprzydatne.

12

Rozdzia 2

Algorytmy Grafowe
Graf G deniowany jest jako para (V, E), gdzie V to zbir Literatura wierzchokw, natomiast E to multizbir krawdzi czcych wierza a [WDA] - 23 choki z V . Dla grafu G = (V, E) z czterema wierzchokami oraz [ASD] - 7 picioma krawdziami, przedstawionego na rysunku 2.1.a, zbir [MD] - 3.2, 6 V = {1, 2, 3, 4}, natomiast E = {(1, 2), (1, 2), (1, 3), (2, 4), (3, 3)}. Krawdzie grafu mog by skierowane (jak na rysunku 2.1.b), bd a a nieskierowane (rysunek 2.1.a). Zarwno z wierzchokami, jak i krawdziami mona wiza a dodatkowe informacje, takie jak waga, kolor czy dugo. Grafy znajduj wiele odpowiednia kw w wiecie rzeczywistym, co sprawia, e s one bardzo przydatnym narzdziem podczas a rozwizywania praktycznych problemw algorytmicznych. a Rozpatrzmy dla przykadu zbir skrzyowa w miecie poczonych drogami. Naturala nym jest utosamienie wierzchokw grafu ze skrzyowaniami, a krawdzi grafu z drogami. Drogi mog by jednokierunkowe (rwnowane krawdziom skierowanym) lub dwukierunkowe a (rwnowane krawdziom nieskierowanym), maj okrelon dugo (ktr moemy rwnie a a a przypisa krawdziom). Skrzyowania natomiast mog mie okrelone pooenie geograczne, a ktre moemy przechowywa w reprezentujcych je wierzchokach. Na potrzeby tego rozdziau a wprowadzimy kilka czsto uywanych poj: Denicja 2.0.1 Ptla, to dowolna krawd (skierowana lub nieskierowana), prowadzca do a wierzchoka, z ktrego wychodzi. Innymi sowy, jest to krawd postaci (v, v), v V . Denicja 2.0.2 Multikrawdzie to co najmniej dwie krawdzie prowadzce midzy tymi saa mymi wierzchokami. W przypadku grafw nieskierowanych, s to krawdzie czce te same a a a wierzchoki, natomiast w przypadku grafw skierowanych wychodzce z tego samego wierza choka oraz wchodzce do tego samego wierzchoka. a

1 3

2 4

1 3

2 4

1 3

(a)

(b)

(c)

Rysunek 2.1: (a) graf nieskierowany krawdzie czce wierzchoki 1 i 2 s multikrawdziami, naa a a
tomiast (3, 3) to ptla. (b) Graf skierowany zawierajcy ptl (1, 1) oraz multikrawdzie a (1, 2). (c) Graf indukowany przez graf z rysunku (b) przez zbir wierzchokw {1, 2, 3}

13

Denicja 2.0.3 Podgraf G = (V , E ) grafu G = (V, E) jest to taki graf, dla ktrego V V , oraz E E. Denicja 2.0.4 Grafem indukowanym G = G[V ] przez zbir V z grafu G = (V, E), V V nazywamy podgraf grafu G, ktrego zbiorem wierzchokw jest V , natomiast do zbioru krawdzi nale wszystkie krawdzie (u, v) E, dla ktrych u, v V . a Krawd, czc wierzchoki u i v, bdziemy oznacza przez (u, v), natomiast ciek a a a prowadzc z wierzchoka u do wierzchoka v przez u ; v. a a W wielu miejscach w szczeglnoci podczas omawiania zoonoci algorytmw, bdziemy odwoywa si do liczby wierzchokw oraz krawdzi w grae. W sytuacjach, w ktrych jednoznacznie z kontekstu bdzie wynikao o jaki graf chodzi, liczb wierzchokw oznacza bdziemy przez n, natomiast liczb krawdzi przez m. W aktualnym rozdziale omwionych jest wiele algorytmw zwizanych z teori grafw. a a Przedstawione zostan midzy innymi metody przeszukiwania grafw, topologiczne sortoa wanie wierzchokw, sprawdzanie czy graf jest acykliczny, wyznaczanie maksymalnego przepywu oraz wiele innych zagadnie. Zanim jednak przejdziemy do analizy tych algorytmw, musimy najpierw omwi sposb reprezentowania grafu w programach.

2.1. Struktura grafu


Graf bdziemy reprezentowa przy uyciu sparametryzowanej Literatura struktury (ang. template) Graph<V,E>, gdzie V jest typem okre[WDA] - 23.1 lajcym dodatkowe informacje przechowywane w wierzchokach, a [ASD] - 1.5.3, 7 natomiast E jest typem okrelajcym dodatkowe informacje przea [KDP] - 2.1 chowywane w krawdziach grafu. W ten sposb, jeli chcemy stwo[MD] - 3.3 rzy graf modelujcy sie drg w miecie, to wystarczy wzbogaci a struktur V o informacje zwizane ze skrzyowaniami, natomiast a struktur E z ulicami. Implementacja grafu udostpnia dwa operatory Graph<V,E>::EdgeD(int, int, E), oraz Graph<V,E>::EdgeU(int, int, E), suce odpowiednio do dodawania do grafu krawa dzi skierowanych oraz nieskierowanych. Pierwszy oraz drugi parametr tych funkcji okrelaj a numery wierzchokw poczonych dodawan krawdzi, natomiast ostatni parametr deniuje a a a dodatkowe informacje zwizane z t krawdzi. Dla wikszoci algorytmw, korzystajcych a a a a ze struktury Graph<V,E>, te dwa operatory s w zupenoci wystarczajce, ale w razie poa a trzeby mona w prosty sposb wzbogaci implementacj o nowe operacje chociaby funkcj usuwajc krawdzie z grafu. a a Listing 2.1 prezentuje implementacj struktury Graph<V,E> wraz z komentarzami wyjaniajcymi przyjte rozwizania. Implementacja ta wykorzystuje bibliotek STL (dokadniej, a a struktur vector do reprezentowania listy wierzchokw oraz krawdzi).
Listing 2.1: Implementacja struktury Graph<V,E> 01 template <class V, class E> struct Graph { // Typ krawdzi (Ed) dziedziczy po typie zawierajcym dodatkowe informacje // zwizane z krawdzi (E). Zawiera on rwnie pole v, okrelajce numer // wierzchoka, do ktrego prowadzi krawd. Zaimplementowany konstruktor // pozwala na skrcenie zapisu wielu funkcji korzystajcych ze struktury grafu. 02 struct Ed : E { 03 int v;

14

Listing 2.1: (c.d. listingu z poprzedniej strony) 04 Ed(E p, int w) : E(p), v(w) { } 05 }; // Typ wierzchoka (Ve) dziedziczy po typie zawierajcym dodatkowe informacje // z nim zwizane (V) oraz po wektorze krawdzi. To drugie dziedziczenie moe // wydawa si na pierwszy rzut oka stosunkowo dziwne, lecz jest ono przydatne // umoliwia atwe iterowanie po wszystkich krawdziach wychodzcych z // wierzchoka v: FOREACH(it, g[v]) 06 struct Ve : V, vector<Ed> { }; // Wektor wierzchokw w grafie 07 vector<Ve> g; // Konstruktor grafu - przyjmuje jako parametr liczb wierzchokw 08 Graph(int n = 0) : g(n) { } // Funkcja dodajca do grafu now krawd skierowan z wierzchoka b do e, // zawierajc dodatkowe informacje okrelone przez zmienn d. 09 void EdgeD(int b, int e, E d = E()) { 10 g[b].PB(Ed(d, e)); 11 } // Funkcja dodajca do grafu now krawd nieskierowan, czc wierzchoki // b i e oraz zawierajc dodatkowe informacje okrelone przez zmienn // d. Krawd nieskierowana jest reprezentowana przez dwie krawdzie // skierowane - jedn prowadzc z wierzchoka b do wierzchoka e, oraz // drug z wierzchoka e do b. Struktura E w grafach nieskierowanych // musi dodatkowo zawiera element int rev. Dla danej krawdzi skierowanej // (b,e), pole to przechowuje pozycj krawdzi (e,b) na licie incydencji // wierzchoka e. Dziki temu, dla dowolnej krawdzi w grafie w czasie staym // mona znale krawd o przeciwnym zwrocie. 12 void EdgeU(int b, int e, E d = E()) { 13 Ed eg(d, e); 14 eg.rev = SIZE(g[e]) + (b == e); 15 g[b].PB(eg); 16 eg.rev = SIZE(g[eg.v = b]) - 1; 17 g[e].PB(eg); 18 } 19 };

W kolejnych rozdziaach dotyczcych teorii grafw, przedstawione zostan implementacje a a rnych algorytmw wykorzystujcych struktur Graph<V,E>. Wszystkie funkcje realizujce a a te algorytmy s operatorami grafu, a co za tym idzie, ich tre musi zosta umieszczona a w obrbie struktury Graph<V,E>. Faktu tego w dalszej czci ksiki nie bdziemy wicej a przywoywa zdajemy si na dobr pami czytelnika. a W ramach zadania ozgrzewkowego, napiszemy prost funkcj wypisujc struktur a a a grafu na standardowe wyjcie. Na pocztku kadego wiersza opisu, powinien znale si nua mer kolejnego wierzchoka (zaczynajc numeracj od 0), a nastpnie lista wierzchokw, z a ktrymi ten wierzchoek jest poczony krawdzi. Implementacja tej funkcji przedstawiona a a jest na listingu 2.2.

15

Listing 2.2: Implementacja funkcji void Graph<V,E>::Write() 1 void Write() { // Dla wszystkich wierzchokw w grafie zaczynajc od 0... 2 REP(x, SIZE(g)) { // Wypisz numer wierzchoka 3 cout << x << ":"; // Dla kadej krawdzi wychodzcej z przetwarzanego wierzchoka o numerze // x, wypisz numer wierzchoka, do ktrego ona prowadzi 4 FOREACH(it, g[x]) cout << " " << it->v; 5 cout << endl; 6 } 7}

Zamy, e w pliku znajduje si opis grafu skierowanego w nastpujcym formacie: w pierwa szym wierszu zapisane s dwie liczby naturalne n i m, oznaczajce odpowiednio liczb a a wierzchokw, oraz krawdzi w grae (wierzchoki numerowane s od 0 do n 1). W koleja nych m wierszach umieszczone s po dwie liczby naturalne b i e, oznaczajce, e z wierza a choka o numerze b prowadzi krawd do wierzchoka o numerze e. Chcemy napisa program, ktry przekonwertuje opis tego grafu na format, jaki jest generowany przez funkcj void Graph<V,E>::Write(). Dla nastpujcego opisu wejciowego: a
5 0 3 4 4 1 2 3 4 8 1 4 1 0 2 4 2 3

Program wygeneruje nastpujcy wynik: a


0: 1: 2: 3: 4: 1 2 4 4 2 1 0 3

Rozwizanie jest proste wystarczy wykorzysta struktur Graph<V,E>, na podstawie a opisu z pliku skonstruowa odpowiedni graf, a nastpnie wypisa jego struktur przy uyciu funkcji void Graph::Write(). Gwna cz programu, realizujcego to zadanie, przedstaa wiona jest na listingu 2.3. Cz kodu rdowego programu pochodzcego z biblioteczki a zostaa pominita. Wanie w ten sposb, w dalszej czci ksiki, bd prezentowane przya a kadowe programy. Z uwagi na fakt, i omwiony tu przykad jest pierwszym kompletnym programem, jaki do tej pory udao nam si stworzy, zatem na listingu 2.4 zamieszczony jest peny kod rdowy tego programu. W dalszych rozdziaach ksiki po peny kod rdowy a programw naley siga do zaczonej pyty. a
Listing 2.3: Gwna cz programu su cego do konwertowania reprezentacji grafu a // Zarwno dla wierzchokw, jak i dla krawdzi nie potrzebne s adne dodatkowe // informacje 01 struct Empty { }; 02 int main() { 03 int n, m, b, e; // Wczytaj liczb wierzchokw i krawdzi w grafie

16

Listing 2.3: (c.d. listingu z poprzedniej strony) 04 cin >> n >> m; // Skonstruuj graf o odpowiednim rozmiarze, nie zawierajcy dodatkowych // informacji dla wierzchokw ani krawdzi 05 Graph<Empty, Empty> gr(n); 06 REP(x, m) { // Wczytaj pocztek i koniec kolejnej krawdzi 07 cin >> b >> e; // Dodaj do grafu krawd skierowan z wierzchoka b do e 08 gr.EdgeD(b, e); 09 } // Wypisz graf 10 gr.Write(); 11 return 0; 12 }

Listing 2.4: Peny kod rdowy programu konwertuj cego reprezentacj grafu a 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <cstdio> #include <iostream> #include <algorithm> #include <string> #include <vector> using namespace std; typedef vector<int> VI; typedef long long LL; #dene FOR(x, b, e) for(int x=b; x<=(e); ++x) #dene FORD(x, b, e) for(int x=b; x>=(e); x) #dene REP(x, n) for(int x=0; x<(n); ++x) #dene VAR(v,n) typeof(n) v=(n) #dene ALL(c) c.begin(),c.end() #dene SIZE(x) (int)x.size() #dene FOREACH(i,c) for(VAR(i,(c).begin());i!=(c).end();++i) #dene PB push back #dene ST rst #dene ND second template <class V, class E> struct Graph { struct Ed : E { int v; Ed(E p, int w) : E(p), v(w) { } }; struct Ve : V, vector<Ed> { }; vector<Ve> g; Graph(int n = 0) : g(n) { } void EdgeD(int b, int e, E d = E()) { g[b].PB(Ed(d, e)); } void Write() { REP(x, SIZE(g)) {

17

Listing 2.4: (c.d. listingu z poprzedniej strony) 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 cout << x << ":"; FOREACH(it, g[x]) cout << " " << it->v; cout << endl; } } }; struct Empty { }; int main() { int n, m, b, e; cin >> n >> m; Graph<Empty, Empty> gr(n); REP(x, m) { cin >> b >> e; gr.EdgeD(b, e); } gr.Write(); return 0; }

2.2. Przeszukiwanie grafu wszerz


BFS czyli przeszukiwanie grafu wszerz (ang. breadth - rst Literatura search), jest metod systematycznego badania struktury grafu. Mea [WDA] - 23.2 toda ta zakada, e dany jest graf G = (V, E) (skierowany bd niea [ASD] - 1.5.3 skierowany), oraz wyrniony wierzchoek s, zwany rdem prze[KDP] - 2.3 szukiwania. Dla kadego wierzchoka v V , algorytm BFS oblicza odlego t(v) od wierzchoka s (rozumian jako najmniejsz a a liczb krawdzi na ciece od wierzchoka s do v). Wyznaczany jest rwnie wierzchoek s(v) poczony krawdzi z v, z ktrego prowadzi najkrtsza cieka ze rda wyszukiwaa a nia. Zasada dziaania algorytmu BFS jest prosta na pocztku, za odwiedzony wierzchoa ek uwaa si tylko s wyznaczona dla niego odlego to 0. Nastpnie przetwarzane s a wierzchoki v, dla ktrych odlego zostaa ju wyznaczona, w kolejnoci niemalejcych oda legoci dla kadego nie odwiedzonego ssiada u wierzchoka v, przypisywane s wartoci a a t(u) = t(v) + 1, s(u) = v. Na skutek wykonania algorytmu BFS, konstruowane jest drzewo przeszukiwa wszerz, w skad ktrego wchodz wszystkie wierzchoki osigalne ze rda wya a szukiwania, oraz krawdzie konstruujce najkrtsze cieki (reprezentowane przez zmienne a s(v)). Ze wzgldu na fakt, i nie wszystkie krawdzie grafu G musz znajdowa si w drzewie a przeszukiwa BFS, istnieje moliwo klasykacji krawdzi grafu G. Rozrniane s nasta pujce typy krawdzi, ktrych denicja ma sens w przypadku rozpatrywania konkretnego a drzewa przeszukiwa (denicje te s uywane nie tylko w ramach przeszukiwania wszerz): a Denicja 2.2.1 Krawd drzewowa, jest to krawd grafu G naleca do wyznaczonego lasu a przeszukiwa. Denicja 2.2.2 Krawd niedrzewowa, jest to krawd grafu G, ktra nie naley do wyznaczonego lasu przeszukiwa. 18

Denicja 2.2.3 Krawd powrotna, jest to krawd niedrzewowa, ktra prowadzi z wierzchoka v do jego przodka w lesie przeszukiwania. Denicja 2.2.4 Krawd w przd, jest to krawd niedrzewowa, ktra prowadzi z wierzchoka v do jego potomka w lesie przeszukiwania. Denicja 2.2.5 Krawd poprzeczna, jest to krawd niedrzewowa, ktra prowadzi z wierzchoka v do wierzchoka nie bdcego, ani jego poprzednikiem, ani nastpnikiem w wyznaczoa nym lesie przeszukiwa. W przypadku przeszukiwania wszerz grafw nieskierowanych nie zawierajcych multikraa wdzi, krawdzie w przd oraz krawdzie powrotne nie wystpuj, natomiast dla grafw skiea rowanych nie wystpuj krawdzie w przd. W zalenoci od rodzaju stosowanego przeszukia wania, krawdzie poszczeglnych klas mog mie ciekawe wasnoci, ktre s wykorzystywane a a w rnego rodzaju algorytmach. Najciekawsz wasnoci, w przypadku przeszukiwania grafu a a wszerz, jest fakt, i dla kadej krawdzi (u, v) E zachodzi d(u) = d(v) lub d(u) = d(v) 1. Dziki tej wasnoci, algorytm BFS moe by wykorzystywany do wyznaczania najkrtszych cieek midzy zadan par wierzchokw. a a Algorytm BFS zrealizowany zosta jako funkcja void Graph<V,E>::Bfs(int), ktra przyjmuje jeden parametr numer wierzchoka bdcego rdem przeszukiwania. Implea mentacja wymaga wzbogacenia struktury wierzchokw o dwie dodatkowe zmienne int t oraz int s. Po wykonaniu algorytmu na grae, zmienna int t jest rwna odlegoci wierzchoka od rda wyszukiwania, natomiast zmienna int s zawiera numer wierzchoka, z ktrego proa wadzi znaleziona, najkrtsza cieka. Zmienne int s wyznaczaj tzw. las przeszukiwania grafu wszerz przykad takiego lasu przedstawiony jest na rysunku 2.2. Implementacja funkcji void Graph<V,E>::Bfs(int) znajduje si na listingu 2.5.
Listing 2.5: Implementacja funkcji void Graph<V,E>::Bfs(int) // Po wykonaniu algorytmu BFS, pole int t wierzchoka zawiera odlego od // rda (-1 w przypadku, gdy wierzchoek jest nieosigalny ze rda), pole // int s zawiera numer ojca w drzewie BFS (dla wierzchoka bdcego rdem // wyszukiwania oraz wierzchokw nieosigalnych jest to -1) 01 void Bfs(int s) { // Dla kadego wierzchoka w grafie ustawiana jest pocztkowa warto zmiennych // t oraz s na -1. rdo wyszukiwania ma czas rwny 0 02 FOREACH(it, g) it->t = it->s = -1; 03 g[s].t = 0; // Algorytm BFS jest realizowany przy uyciu kolejki FIFO, do ktrej wstawiane // s kolejne wierzchoki oczekujce na przetworzenie 04 int qu[SIZE(g)], b, e; // Do kolejki wstawione zostaje rdo 05 qu[b = e = 0] = s; // Dopki w kolejce s jakie wierzchoki... 06 while (b <= e) { 07 s = qu[b++]; // Dla kadej krawdzi wychodzcej z aktualnie przetwarzanego wierzchoka, // jeli wierzchoek do ktrego ona prowadzi nie by jeszcze wstawiony do // kolejki, wstaw go i wyznacz wartoci zmiennych int t i int s 08 FOREACH(it, g[s]) if (g[it->v].t == -1) { 09 g[qu[++e] = it->v].t = g[s].t + 1;

19

0 1 3 5 2 1 2 1

-1 0

(a)

(b)

Rysunek 2.2: (a) nieskierowany graf o szeciu wierzchokach [0 . . . 5], (b) wyznaczone drzewo BFS dla
grafu z rysunku (a) i wierzchoka rdowego 2 (wewntrz wierzchokw znajduj si wya a znaczone czasy). Krawd (4, 5) jest jedyn krawdzi niedrzewow jest ona jednoczenie a a a krawdzi poprzeczn. a a

Listing 2.5: (c.d. listingu z poprzedniej strony) 10 g[it->v].s = s; 11 } 12 } 13 }

Dla grafu przedstawionego na rysunku 2.2.a, po wykonaniu funkcji Graph<V,E>::Bfs(2), wartoci zmiennych wyliczone przez algorytm, przedstawione s na listingu 2.6, natomiast na a listingu 2.7 umieszczony jest kod rdowy programu uytego do wygenerowania tego wyniku. Algorytm przeszukiwania grafw metod BFS ma zoono czasow O(n + m). Wynika a a to z faktu, i kady wierzchoek w grae odwiedzany jest co najwyej raz, a podczas jego przetwarzania, wszystkie krawdzie z niego wychodzce s analizowane jednokrotnie. a a
Listing 2.6: Przykad uycia algorytmu BFS dla grafu z rysunku 2.2 i wierzchoka rdowego 2.

Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek

0: 1: 2: 3: 4: 5:

czas czas czas czas czas czas

= = = = = =

-1, ojciec w drzewie BFS = -1 1, ojciec w drzewie BFS = 2 0, ojciec w drzewie BFS = -1 2, ojciec w drzewie BFS = 1 1, ojciec w drzewie BFS = 2 1, ojciec w drzewie BFS = 2

Listing 2.7: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.6. Peny kod rdowy programu znajduje si w pliku bfs str.cpp // Krawdzie grafu nieskierowanego wymagaj dodatkowego pola int rev 01 struct Ve { 02 int rev; 03 }; // Wzbogacenie wierzchokw musi zawiera pola wymagane przez algorytm BFS 04 struct Vs { 05 int t, s; 06 };

20

Listing 2.7: (c.d. listingu z poprzedniej strony) 07 int main() { 08 int n, m, s, b, e; // Wczytaj liczb wierzchokw, krawdzi oraz numer wierzchoka startowego 09 cin >> n >> m >> s; 10 Graph<Vs, Ve> g(n); // Dodaj do grafu wszystkie krawdzie 11 REP(x,m) { 12 cin >> b >> e; 13 g.EdgeU(b, e); 14 } // Wykonaj algorytm BFS 15 g.Bfs(s); // Wypisz wynik 16 REP(x, n) cout << "Wierzcholek " << x << ": czas = " << g.g[x].t << 17 ", ojciec w drzewie BFS = " << g.g[x].s << endl; 18 return 0; 19 }

21

Zadanie: Mrwki i biedronka


Pochodzenie: VIII Olimpiada Informatyczna Rozwizanie: a ants.cpp

Jak wiadomo, mrwki potra hodowa mszyce. Mszyce wydzielaj sodk ros miodow, a a a a ktr spijaj mrwki. Mrwki za broni mszyc przed ich najwikszymi wrogami biedrona a a kami. Na drzewie obok mrowiska znajduje si wanie taka hodowla mszyc. Mszyce eruj na a liciach oraz w rozgazieniach drzewa. W niektrych z tych miejsc znajduj si rwnie a mrwki patrolujce drzewo. Dla ustalenia uwagi, mrwki s ponumerowane od jeden w gr. a a Hodowli zagraa biedronka, ktra zawsze siada na drzewie tam, gdzie s mszyce, czyli na a liciach lub w rozgazieniach. W chwili, gdy gdzie na drzewie usidzie biedronka, mrwki a patrolujce drzewo ruszaj w jej stron, aby j przegoni. Kieruj si przy tym nastpujcymi a a a a a zasadami: z kadego miejsca na drzewie (licia lub rozgazienia) mona doj do kadego innego miejsca (bez zawracania) tylko na jeden sposb; kada mrwka wybiera wanie tak a drog do miejsca ldowania biedronki, a jeeli w miejscu ldowania biedronki znajduje si mrwka, biedronka natychmiast oda latuje, jeeli na drodze, od aktualnego pooenia mrwki do miejsca ldowania biedronki, znaja dzie si inna mrwka, to ta pooona dalej od biedronki koczy wdrwk i zostaje w miejscu swojego aktualnego pooenia, jeeli dwie lub wicej mrwek prbuje wej na to samo rozgazienie drzewa, to robi to tylko jedna mrwka ta z najmniejszym numerem, a reszta mrwek pozostaje na swoich miejscach (liciach lub rozgazieniach), mrwka, ktra dociera do miejsca ldowania biedronki, przegania j i pozostaje w tym a a miejscu.

Biedronka jest uparta i znowu lduje na drzewie. Wwczas mrwki ponownie ruszaj, aby a a przegoni intruza. Dla uproszczenia przyjmujemy, e przejcie gazki czcej li z rozgazieniem lub a a a czcej dwa rozgazienia, zajmuje wszystkim mrwkom jednostk czasu. a a

Zadanie
Napisz program, ktry: wczyta opis drzewa, pocztkowe pooenia mrwek oraz miejsca, w ktrych kolejno a siada biedronka, dla kadej mrwki znajdzie jej kocowe pooenie i wyznaczy liczb mwic, ile razy a a przegonia ona biedronk.

Wejcie
W pierwszym wierszu wejcia znajduje si jedna liczba cakowita n, rwna cznej liczbie lici i a rozgazie w drzewie, 1 n 5 000. Przyjmujemy, e licie i rozgazienia s ponumerowane a od 1 do n. W kolejnych n 1 wierszach s opisane gazki w kadym z tych wierszy s a a a 22

zapisane dwie liczby cakowite a i b oznaczajce, e dana gazka czy miejsca a i b. Gazki a a a a pozwalaj na przejcie z kadego miejsca na drzewie, do kadego innego miejsca. W n+1-szym a wierszu jest zapisana jedna liczba cakowita k, 1 k 1 000, k n, rwna liczbie mrwek patrolujcych drzewo. W kadym z kolejnych k wierszy zapisana jest jedna liczba cakowita a z przedziau od 1 do n. Liczba zapisana w wierszu n + 1 + i oznacza pocztkowe pooenie a mrwki nr i. W kadym miejscu (liciu lub rozgazieniu) moe znajdowa si co najwyej jedna mrwka. W wierszu n + k + 2 zapisana jest jedna liczba cakowita l, 1 l 500, mwica ile razy biedronka siada na drzewie. W kadym z kolejnych l wierszy zapisana jest a jedna liczba cakowita z zakresu od 1 do n. Liczby te opisuj kolejne miejsca, w ktrych siada a biedronka.

Twj program powinien wypisa k wierszy. W i-tym wierszu powinny zosta zapisane dwie liczby cakowite oddzielone pojedynczym odstpem kocowa pozycja i-tej mrwki (numer rozgazienia lub licia) i - liczba mwica, ile razy przegonia ona biedronk. a

Wyjcie

Przykad
4 1 2 1 3 2 4 2 1 2 2 2 4

Dla nastpujcego wejcia: a

Poprawnym rozwizaniem jest: a


1 0 4 2

Proste acm.uva.es - zadanie 10187 acm.uva.es - zadanie 10959 spoj.sphere.pl - zadanie 206 acm.sgu.ru - zadanie 226

rednie acm.uva.es - zadanie 321 acm.uva.es - zadanie 10067 spoj.sphere.pl - zadanie 135 acm.sgu.ru - zadanie 213

wiczenia

Trudne acm.uva.es - zadanie 589 acm.uva.es - zadanie 10097 spoj.sphere.pl - zadanie 212

23

2.3. Przeszukiwanie grafu w gb a


Inn metod systematycznego przeszukiwania grafw jest ala a Literatura gorytm DFS przeszukiwanie w gb (ang. depth - rst search). a [WDA] - 23.3 Metoda ta podobnie jak BFS, dziaa prawidowo dla grafw [KDP] - 2.2 skierowanych, jak i nieskierowanych. Rozpoczyna ona przeszukiwa[MD] - 7.3 nie grafu od pewnego startowego wierzchoka s, a nastpnie rekurencyjnie odwiedza wszystkich jeszcze nie odwiedzonych ssiadw a przetwarzanego wierzchoka. Podczas wykonywania algorytmu, wyznaczane s dla kadego a wierzchoka trzy wartoci d, f oraz s. Zmienne d i f reprezentuj odpowiednio czasy weja cia oraz wyjcia z wierzchoka, natomiast s to numer wierzchoka - ojca w wyznaczonym lesie DFS. Czas wejcia do wierzchoka v jest rozumiany jako moment, w ktrym rozpoczto przetwarzanie tego wierzchoka (przeszukiwanie DFS po raz pierwszy odwiedzio ten wierzchoek), natomiast czas wyjcia z wierzchoka v, to czas, w ktrym przetwarzanie wierzchoka v zostao zakoczone. Ze wzgldu na rekurencyjne odwiedzanie wszystkich ssiadw wierza choka v przed zakoczeniem jego przetwarzania, czasy wejcia oraz wyjcia reprezentuj a poprawn struktur nawiasow, w ktrej wejcie do wierzchoka reprezentowane jest przez a a nawias otwierajcy, natomiast wyjcie nawias zamykajcy. Na rysunkach 2.3.d oraz 2.3.e a a przedstawione s przykadowe kolejnoci przetwarzania wierzchokw grafu. a Czasy wyznaczane przez algorytm DFS maj wiele zastosowa su midzy innymi a a do znajdowania mostw, punktw artykulacji, czy silnie spjnych skadowych w grafach. Zmienna s dla wierzchoka v (podobnie jak w algorytmie BFS), reprezentuje numer wierzchoka, z ktrego wierzchoek v zosta odwiedzony wszystkie wartoci s wyznaczaj las a przeszukiwa w gb (przykady takich lasw s przedstawione na rysunkach 2.3.b oraz 2.3.c). a a Implementacja algorytmu DFS przedstawiona na listingu 2.8, jest zrealizowana przez funka a cj void Graph<V,E>::DfsR(int), przyjmujc jako parametr numer wierzchoka, z ktrego naley rozpocz przeszukiwanie. W przypadku, gdy parametr ten nie zosta podany, wykoa nywane jest przeszukiwanie ze wszystkich, jeszcze nie odwiedzonych wierzchokw grafu, w kolejnoci rosncych numerw (przykad takiej sytuacji przedstawiono na rysunku 2.3.c). a
Listing 2.8: Implementacja rekurencyjna przeszukiwania DFS jako funkcja void Graph <V,E> ::DfsR(int) // W polu int d kadego wierzchoka algorytm umieszcza czas wejcia, w polu // int f - czas wyjcia, natomiast w polu int s - numer ojca w lesie DFS // (wartoci te dla wierzchokw nieodwiedzonych s rwne -1) // Zmienna t jest uywana do pamitania aktualnego czasu przetwarzania 01 int t; // Funkcja rekurencyjna, przeszukujca poddrzewo wierzchoka v metod DFS 02 void DfsV(int v) { // Ustaw czas wejcia do wierzchoka 03 g[v].d = ++t; // Dla kadej krawdzi, wychodzcej z wierzchoka v, jeli wierzchoek, do // ktrego prowadzi krawd nie by jeszcze odwiedzony, to go odwied 04 FOREACH(it, g[v]) if (g[it->v].s == -1) { 05 g[it->v].s = v; 06 DfsV(it->v); 07 } // Ustaw czas wyjcia z wierzchoka 08 g[v].f = ++t;

24

Listing 2.8: (c.d. listingu z poprzedniej strony) 09 } 10 void DfsR(int e = -1) { 11 t = -1; 12 int b = 0; // Dla wierzchokw z przedziau [b..e] zostanie wywoana funkcja // void DfsV(int). Jeli do funkcji void DfsR(int) nie przekazano // parametru, to przedziaem tym jest [0..SIZE(g)-1] (wszystkie wierzchoki w // grafie), w przeciwnym przypadku [e..e] (tylko jeden wierzchoek) 13 e == -1 ? e = SIZE(g) - 1 : b = e; 14 REP(x, SIZE(g)) g[x].d = g[x].f = g[x].s = -1; // Wykonaj algorytm DFS dla wszystkich wierzchokw z przedziau [b..e] 15 FOR(x, b, e) if (g[x].s == -1) DfsV(x); 16 }

Implementacja przeszukiwania grafu metod DFS, realizowana przez funkcj void Graph a <V,E>::DfsR(int), jest rekurencyjna. Podejcie takie jest stosunkowo proste w realizacji (nie jest wymagane implementowanie wasnego stosu aktualnie przetwarzanych wierzchokw), jednak podczas operowania na duych grafach, pojawia si ryzyko przepenienie stosu programu, a co za tym idzie doprowadzenie do bdu wykonania. W przypadku przeszukiwania duych grafw, naley upewni si, e w systemie operacyjnym, w ktrym bdzie wykonywany program, nie ma dodatkowego ograniczenia na stos lub jest on na tyle duy, e nie zagraa dziaaniu naszego programu. W przypadku, gdy nie istnieje moliwo skorzystania z rekurencyjnej implementacji algorytmu, mona uy implementacji iteracyjnej, realizowanej przez funkcj void Graph<V,E>::Dfs(int). Kod rdowy tej funkcji przedstawiony zosta na listingu 2.9.
Listing 2.9: Implementacja iteracyjna przeszukiwania grafu w g b jako funkcja void a Graph<V,E>::Dfs(int) 01 void Dfs(int e = -1) { 02 VI st(SIZE(g)); 03 int t = -1, i = 0, b = 0; 04 e == -1 ? e = SIZE(g) - 1 : b = e; 05 REP(x, SIZE(g)) g[x].d = g[x].f = g[x].s = -1; // Dla wszystkich wierzchokw z przedziau [b..e], jeli wierzchoek nie by // jeszcze odwiedzony... 06 FOR(s, b, e) if (g[s].d == -1) { // Wstaw wierzchoek na stos i ustaw dla niego odpowiedni czas wejcia. // Zmienna f wierzchoka suy tymczasowo jako licznik nieprzetworzonych // krawdzi wychodzcych z wierzchoka. 07 g[st[i++] = s].d = ++t; 08 g[s].f = SIZE(g[s]); // Dopki stos jest niepusty... 09 while (i) { 10 int s = st[i - 1]; // Przetwrz kolejn krawd wychodzc z aktualnego wierzchoka, lub usu // go ze stosu (gdy nie ma ju wicej krawdzi) 11 if (!g[s].f) {

25

0 2 4 6

1 3

-1/-1 5/8

-1/-1 0/9

0/3 4/13

1/2 7/12

6/7

2/3 1/4

5/6

9/10 8/11

(a)
6 3 2 4 5

(b)
0 1 4 2 3 6

(c)

(d)

(e)

Rysunek 2.3: (a) Przykadowy nieskierowany graf o siedmiu wierzchokach. (b) Wyznaczone drzewo
DFS dla rda wyszukiwania w wierzchoku 3 (wewntrz wierzchokw wpisane s odpoa a wiednio czasy wejcia oraz wyjcia). (c) Wyznaczone drzewo DFS dla przeszukiwania caego grafu w kolejnoci rosncych numerw wierzchokw (odpowiednik wywoania funkcji a void Graph<V,E>::Dfs()). (d) Kolejno przeszukiwania wierzchokw dla drzewa DFS z rysunku (b). (e) Kolejno przeszukiwania wierzchokw dla drzewa DFS z rysunku (c).

Listing 2.9: (c.d. listingu z poprzedniej strony) 12 g[s].f = ++t; 13 --i; 14 } else { // Jeli wierzchoek, do ktrego prowadzi krawd nie by jeszcze // odwiedzony, to... 15 if (g[s = g[s][--g[s].f].v].d == -1) { // Ustaw numer wierzchoka-ojca w drzewie DFS oraz ustaw liczb // nieprzetworzonych krawdzi wychodzcych z wierzchoka 16 g[s].s = st[i - 1]; 17 g[s].f = SIZE(g[s]); // Wstaw wierzchoek na stos i ustaw czas wejcia 18 g[st[i++] = s].d = ++t; 19 } 20 } 21 } 22 } 23 }

26

Algorytm przeszukiwania grafw metod DFS ma zoono O(n + m). Kady wierzchoa ek w grae odwiedzany jest co najwyej raz, a w ramach jego przetwarzania, sprawdzane s a wszystkie krawdzie z niego wychodzce operacja ta rwnie wykonywana jest jednokrota nie. Dla nieskierowanego grafu przedstawionego na rysunku 2.3.a, po wykonaniu procedury Graph<V,E>::Dfs(3), wartoci wyliczonych przez algorytm zmiennych s przedstawione na a listingu 2.10. Jeli dla tego samego przykadu wykona przeszukiwanie caego grafu metod a DFS odpowiadajce wywoaniu funkcji Graph<V,E>::Dfs(), to wyznaczone wartoci a zmiennych bd takie, jak na listingu 2.11. a
Listing 2.10: Wynik wykonania funkcji void Graph<V,E>::Dfs(3) dla grafu z rysunku 2.3.a (rezultat jest zaleny od kolejnoci wstawiania krawdzi do grafu)

Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek

0: 1: 2: 3: 4: 5: 6:

czas czas czas czas czas czas czas

wejscia wejscia wejscia wejscia wejscia wejscia wejscia

= = = = = = =

-1, czas wyjscia = -1, ojciec w drzewie DFS = -1 -1, czas wyjscia = -1, ojciec w drzewie DFS = -1 5, czas wyjscia = 8, ojciec w drzewie DFS = 3 0, czas wyjscia = 9, ojciec w drzewie DFS = -1 6, czas wyjscia = 7, ojciec w drzewie DFS = 2 2, czas wyjscia = 3, ojciec w drzewie DFS = 6 1, czas wyjscia = 4, ojciec w drzewie DFS = 3

Listing 2.11: Wynik wykonania void Graph<V,E>::Dfs() (rezultat jest zaleny od kolejnoci wstawiania krawdzi do grafu)

Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek

0: 1: 2: 3: 4: 5: 6:

czas czas czas czas czas czas czas

wejscia wejscia wejscia wejscia wejscia wejscia wejscia

= = = = = = =

0, 1, 4, 7, 5, 9, 8,

czas czas czas czas czas czas czas

wyjscia wyjscia wyjscia wyjscia wyjscia wyjscia wyjscia

= = = = = = =

3, ojciec w drzewie DFS = -1 2, ojciec w drzewie DFS = 0 13, ojciec w drzewie DFS = -1 12, ojciec w drzewie DFS = 2 6, ojciec w drzewie DFS = 2 10, ojciec w drzewie DFS = 6 11, ojciec w drzewie DFS = 3

Listing 2.12: Program uyty do wyznaczenia wyniku z listingw 2.10 i 2.11. Peny kod rdowy tego programu znajduje si w pliku dfs str.cpp // Krawdzie grafu nieskierowanego wymagaj dodatkowego pola int rev 01 struct Ve { 02 int rev; 03 }; // Wzbogacenie wierzchokw przechowujce wynik generowany przez algorytm DFS 04 struct Vs { 05 int d, f, s; 06 }; 07 int main() { 08 int n, m, s, b, e; // Wczytaj liczb wierzchokw, krawdzi oraz numer wierzchoka startowego 09 cin >> n >> m >> s; // Skonstruuj odpowiedni graf 10 Graph<Vs, Ve> g(n); // Dodaj do grafu wszystkie krawdzie

27

Listing 2.12: (c.d. listingu z poprzedniej strony) 11 REP(x,m) { 12 cin >> b >> e; 13 g.EdgeU(b, e); 14 } // Wykonaj algorytm DFS i wypisz wynik 15 g.Dfs(s); 16 REP(x, SIZE(g.g)) cout << "Wierzcholek " << x << ": czas wejscia = " << 17 g.g[x].d << ", czas wyjscia = " << g.g[x].f << 18 ", ojciec w drzewie DFS = " << g.g[x].s << endl; 19 return 0; 20 }

Zadanie: Komiwojaer Bajtazar


Pochodzenie: IX Olimpiada Informatyczna Rozwizanie: a kom.cpp

Komiwojaer Bajtazar ciko pracuje podrujc po Bajtocji. W dawnych czasach komiwoa jaerowie sami mogli wybiera miasta, ktre chcieli odwiedzi i kolejno w jakiej to czynili, jednak te czasy miny ju bezpowrotnie. Z chwil utworzenia Centralnego Urzdu d/s Kona troli Komiwojaerw, kady komiwojaer otrzymuje z Urzdu list miast, ktre moe odwiedzi i kolejno w jakiej powinien to uczyni. Jak to zazwyczaj bywa z centralnymi urzdami, narzucona kolejno odwiedzania miast nie ma zbyt duo wsplnego z kolejnoci optymaln. a a Przed wyruszeniem w tras Bajtazar chciaby przynajmniej dowiedzie si, ile czasu zajmie mu odwiedzenie wszystkich miast obliczenie tego jest Twoim zadaniem. Miasta w Bajtocji s ponumerowane od 1 do n. Numer 1 ma stolica Bajtocji, z niej waa nie rozpoczyna podr Bajtazar. Miasta poczone s sieci drg dwukierunkowych. Podr a a a midzy dwoma miastami bezporednio poczonymi drog zawsze zajmuje 1 jednostk czasu. a a Ze stolicy mona dotrze do wszystkich pozostaych miast Bajtocji. Jednak sie drg zostaa zaprojektowana bardzo oszczdnie, std drogi nigdy nie tworz cykli. a a

Zadanie

Napisz program, ktry: wczyta ze standardowego wejcia opis sieci drg Bajtocji oraz list miast, ktre musi odwiedzi Bajtazar obliczy sumaryczny czas podry Bajtazara, wypisze wynik na standardowe wyjcie

Wejcie
W pierwszym wierszu wejcia zapisana jest jedna liczba cakowita n rwna liczbie miast w Bajtocji (1 n 30 000). W kolejnych n 1 wierszach opisana jest sie drg w kadym z tych wierszy s zapisane dwie liczby cakowite a i b (1 a a, b n, a = b), oznaczajce, a e miasta a i b poczone s drog. W wierszu o numerze n + 1 zapisana jest jedna liczba a a a cakowita m rwna liczbie miast, ktre powinien odwiedzi Bajtazar, (1 m 50 000). W nastpnych m wierszach zapisano numery kolejnych miast na trasie podry Bajtazara po 28

jednej liczbie w wierszu.

Wyjcie
W pierwszym i jedynym wierszu wyjcia powinna zosta zapisana jedna liczba cakowita rwna cznemu czasowi podry Bajtazara. a

Przykad
5 1 1 3 4 4 1 3 2 5 2 5 5 5

Dla nastpujcego wejcia: a

Poprawnym rozwizaniem jest: a


7

Proste acm.uva.es - zadanie acm.uva.es - zadanie acm.uva.es - zadanie acm.uva.es - zadanie

260 280 459 776

rednie acm.uva.es - zadanie 871 acm.uva.es - zadanie 10802 spoj.sphere.pl - zadanie 38 spoj.sphere.pl - zadanie 372

wiczenia

Trudne acm.uva.es - zadanie 10410 spoj.sphere.pl - zadanie 287

2.4. Silnie spjne skadowe


Graf skierowany G = (V, E) nazywany jest silnie spjnym, jeLiteratura eli dla kadej pary wierzchokw u, v V z tego grafu, istnieje [WDA] - 23.5 cieka prowadzca z wierzchoka u do wierzchoka v oraz z wierza [ASD] - 7.3 choka v do wierzchoka u (u ; v oraz v ; u). Dla kadego skierowanego grafu G mona dokona takiego podziau jego wierzchokw na maksymalne (w sensie zawierania) rozczne zbiory, e grafy a indukowane przez te zbiory stanowi silnie spjne skadowe. Wyznaczanie silnie spjnych a skadowych jest czsto istotne przy rozwizywaniu rnych problemw grafowych, nie tylko a ze wzgldu na moliwo zredukowania wielkoci problemu (poprzez niezalene analizowanie poszczeglnych, silnie spjnych skadowych), ale rwnie ze wzgldu na moliwo sprowadzenia grafu do postaci acyklicznej poprzez wyznaczenie grafu silnie spjnych skadowych (przykad takiego grafu przedstawiony jest na rysunku 2.4.b). Do wyznaczania silnie spjnych skadowych wykorzystuje si informacje wygenerowane przez algorytm DFS. W pierwszym kroku, dla wszystkich wierzchokw wyznacza si ich czasy przetworzenia (f ) przez algorytm DFS, a nastpnie odwraca si skierowanie wszystkich krawdzi w grae i wykonuje si algorytm DFS jeszcze raz, tym razem rozpoczynajc a przeszukiwanie grafu z wierzchokw w kolejnoci malejcych wartoci f . Kade pojedyncze a drzewo przeszukiwania w gb z fazy drugiej stanowi jedn, silnie spjn skadow. a a a a Poprawno takiego algorytmu wynika z nastpujcych trzech faktw: a 29

jeli w grae istniej cieki u ; v oraz v ; u, to wierzchoki u i v nale do tej samej a a silnie spjnej skadowej, a przeszukiwanie w gb umieci oba wierzchoki w tym samym a drzewie DFS, gdy bez wzgldu na to ktry wierzchoek zostanie odwiedzony wczeniej podczas drugiej fazy algorytmu, drugi bdzie jego potomkiem. jeli w grae nie istnieje cieka u ; v, ani v ; u, to wierzchoki nie mog znale si w a tym samym drzewie przeszukiwa DFS oraz nie s w tej samej silnie spjnej skadowej. a jeli istnieje tylko jedna cieka (dla ustalenia uwagi niech to bdzie u ; v), to pierwsza faza algorytmu DFS przypisze mniejsz warto f wierzchokowi v, co spowoduje, e a w drugiej fazie, jako pierwszy, zostanie odwiedzony wierzchoek v. Ze wzgldu na brak cieki v ; u, wierzchoki u i v tra do rnych drzew przeszukiwa DFS. a

Funkcja void Graph<V,E>::SccS() dla kadego wierzchoka wyznacza numer silnie spjnej skadowej do ktrej on naley informacja ta jest umieszczana w dodatkowej zmiennej wierzchoka int t. Implementacja tego algorytmu przedstawiona zostaa na listingu 2.13.
Listing 2.13: Implementacja funkcji void Graph<V,E>::SccS() // Zmienna nr w pierwszej fazie algorytmu uywana jest do pamitania czasu // odwiedzania wierzchokw, natomiast w drugiej fazie algorytmu do numerowania // silnie spjnych skadowych 01 int nr; // Funkcja rekurencyjna, przeszukujca poddrzewo wierzchoka v. Jest ona // uywana do realizacji obu faz przeszukiwania DFS 02 void SccSDfs(int v) { // Jeli wierzchoek nie by odwiedzony, to go odwied 03 if (g[v].t == -1) { 04 g[v].t = nr; // Odwied wszystkie wierzchoki, do ktrych prowadzi krawd z v 05 FOREACH(it, g[v]) SccSDfs(it->v); // Jeli wykonywana jest pierwsza faza algorytmu, to ustaw zmienn t // wierzchoka na czas przetworzenia 06 if (nr < 0) g[v].t = -(--nr) - 3; 07 } 08 } // Waciwa funkcja, wykonujca wyznaczanie silnie spjnych skadowych 09 void SccS() { // Zbuduj graf transponowany gt oraz ustaw wartoci zmiennych t // wierzchokw na -1 (nieodwiedzone) 10 Graph<V, E> gt(SIZE(g)); 11 REP(x, SIZE(g)) { 12 g[x].t = gt.g[x].t = -1; 13 FOREACH(it, g[x]) gt.EdgeD(it->v, x); 14 } 15 gt.nr = -2; 16 nr = 0; 17 VI v(SIZE(g)); // Wykonaj pierwsz faz przeszukiwania w gb na grafie transponowanym oraz // wypenij wektor v numerami wierzchokw w kolejnoci rosncych czasw // przetworzenia DFS

30

(a)

(b)

Rysunek 2.4: (a) przykadowy graf skierowany o omiu wierzchokach. (b) graf silnie spjnych skadowych wyznaczony dla grafu z rysunku (a). Wierzchoek 0 reprezentuje zbir wierzchokw {0, 1}, 1 {5}, 2 {2, 3, 4}, 3 {6}, 4 {7}

Listing 2.13: (c.d. listingu z poprzedniej strony) 18 REP(x, SIZE(g)) { 19 gt.SccSDfs(x); 20 v[gt.g[x].t] = x; 21 } // Wykonaj drug faz przeszukiwania w gb na oryginalnym grafie. 22 FORD(x, SIZE(g) - 1, 0) { 23 SccSDfs(v[x]); 24 nr++; 25 } 26 }

W wielu przypadkach, wyliczenie dla kadego wierzchoka v z grafu G numeru silnie spjnej skadowej, do ktrej on naley, nie jest wystarczajce. Czsto przydaje si rwnie skona struowanie grafu silnie spjnych skadowych G , w ktrym kady wierzchoek reprezentuje jedn, silnie spjn skadow. Niech u oraz v bd wierzchokami w grae G silnie spjnych a a a a skadowych grafu G. Wierzchoki te s poczone skierowan krawdzi (u , v ) wtedy, gdy w a a a a grae G istniej wierzchoki u oraz v poczone krawdzi (u, v), nalece odpowiednio do sila a a a nie spjnych skadowych, reprezentowanych przez wierzchoki u oraz v . Graf silnie spjnych skadowych dla przykadowego grafu z rysunku 2.4.a przedstawiony jest na rysunku 2.4.b. Kady graf silnie spjnych skadowych jest acykliczny gdyby bowiem istnia w nim cykl, to wszystkie wierzchoki na nim lece reprezentowayby t sam, silnie spjn skadow. a a a a Funkcja Graph<V,E> Graph<V,E>::Scc(), ktrej implementacja zostaa przedstawiona na listingu 2.14, oprcz wyznaczania wartoci zmiennych int t, jako wynik swojego dziaania zwraca dodatkowo graf silnie spjnych skadowych. Wierzchoki tego grafu s numerowane a zgodnie z porzdkiem topologicznym (od wierzchoka o wikszym numerze nie prowadzi kraa wd do wierzchoka o numerze mniejszym), co jest bardzo poyteczn wasnoci podczas a a rozwizywania wielu zada. a

31

Listing 2.14: Implementacja funkcji Graph<V,E> Graph<V,E>::Scc() // Wektor sucy do odznaczania odwiedzonych wierzchokw 01 VI vis; // Wskanik do konstruowanego grafu silnie spjnych skadowych 02 Graph<V, E> *sccRes; // Funkcja przechodzca graf algorytmem DFS. Jest ona wykorzystywana dwukrotnie // w pierwszej fazie podczas wyznaczania kolejnoci wierzchokw dla drugiej fazy, // oraz podczas drugiej fazy - do wyznaczania silnie spjnych skadowych oraz // konstrukcji grafu silnie spjnych skadowych 03 void SccDfs(int v, int nr, bool phase) { // Zaznacz wierzchoek jako odwiedzony 04 g[v].t = 1; // Jeli wykonywana jest druga faza przeszukiwania, to ustaw dla wierzchoka // numer silnie spjnej skadowej 05 if (!phase) vis[v] = nr; // Odwied kolejne wierzchoki oraz (jeli wykonywana jest druga faza) dodaj // krawd do konstruowanego grafu silnie spjnych skadowych 06 FOREACH(it, g[v]) if (g[it->v].t == -1) SccDfs(it->v, nr, phase); 07 else if (!phase && nr > vis[it->v]) 08 sccRes->EdgeD(g[it->v].t, vis[it->v] = nr); // Jeli wykonywana jest pierwsza faza, to wstaw wierzchoek do listy, jeli // natomiast druga, to zaktualizuj jego czas 09 if (phase) vis.PB(v); 10 else g[v].t = nr; 11 } // Funkcja wyznaczajca silnie spjne skadowe w grafie 12 Graph<V, E> Scc() { // Graf gt to graf transponowany, natomiast res to konstruowany graf // silnie spjnych skadowych 13 Graph<V, E> gt(SIZE(g)), res(SIZE(g)), *tab[] = { 14 this, &gt}; 15 gt.sccRes = &res; 16 gt.vis.resize(SIZE(g), -1); 17 vis.clear(); // Budowa grafu transpozycyjnego 18 REP(i, SIZE(g)) FOREACH(it, g[i]) gt.EdgeD(it->v, i); // Przeprowad dwie fazy algorytmu DFS... 19 REP(i, 2) { // Zaznacz wierzchoki jako nieodwiedzone 20 FOREACH(it, tab[i]->g) it->t = -1; 21 int comp = 0, v; // Dla kolejnych, nieodwiedzonych wierzchokw, wykonaj przeszukiwanie 22 FORD(j, SIZE(g) - 1, 0) 23 if (tab[i]->g[v = (i ? vis[j] : j)].t == -1) 24 tab[i]->SccDfs(v, comp++, 1 - i); 25 if (i) res.g.resize(comp); 26 } 27 REP(i, SIZE(g)) g[i].t = gt.g[i].t;

32

Listing 2.14: (c.d. listingu z poprzedniej strony) 28 return res; 29 }

Zoono czasowa obu przedstawionych funkcji void Graph<V,E>::SccS() oraz Graph <V,E> Graph<V,E>::Scc() jest liniowa, ze wzgldu na wielko wejciowego grafu (O(n + m)). Wynika to z faktu, e obie funkcje wykonuj dwukrotnie zmodykowany algorytm DFS. a Funkcja void Graph<V,E>::SccS() nie tylko jest krtsza w implementacji, ale w praktyce okazuje si by szybsza, ze wzgldu na brak koniecznoci konstruowania grafu wynikowego, dlatego te, jeli rezultat przez ni wyznaczany jest wystarczajcy, to nie naley korzysta z a a Graph<V,E> Graph<V,E>::Scc().
Listing 2.15: Wynik wyznaczony przez funkcj Graph<V,E>::Scc() dla grafu z rysunku 2.4.a

Wierzcholek 0 nalezy do silnie spojnej Wierzcholek 1 nalezy do silnie spojnej Wierzcholek 2 nalezy do silnie spojnej Wierzcholek 3 nalezy do silnie spojnej Wierzcholek 4 nalezy do silnie spojnej Wierzcholek 5 nalezy do silnie spojnej Wierzcholek 6 nalezy do silnie spojnej Wierzcholek 7 nalezy do silnie spojnej Graf silnie spojnych skladowych: 0: 2 3 4 1: 2 2: 3: 4 4:

skladowej skladowej skladowej skladowej skladowej skladowej skladowej skladowej

numer numer numer numer numer numer numer numer

0 0 2 2 2 1 3 4

Listing 2.16: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.15. Peny kod rdowy programu znajduje si w pliku scc str.cpp 01 struct Ve {}; // Wzbogacenie dla wierzchokw zawiera pole, w ktrym umieszczany jest numer // silnie spjnej skadowej 02 struct Vs { 03 int t; 04 }; 05 int main() { 06 int n, m, s, b, e; 07 Ve ed; // Wczytaj liczb wierzchokw oraz krawdzi 08 cin >> n >> m; 09 Graph<Vs, Ve> g(n); // Dodaj do grafu wszystkie krawdzie 10 REP(x, m) { 11 cin >> b >> e; 12 g.EdgeD(b, e);

33

Listing 2.16: (c.d. listingu z poprzedniej strony) 13 } // Wykonaj algorytm Scc oraz wypisz wyznaczony wynik 14 Graph<Vs, Ve> scc = g.Scc(); 15 REP(x,n) cout << "Wierzcholek " << x << 16 " nalezy do silnie spojnej skladowej numer " << g.g[x].t << endl; 17 cout << "Graf silnie spojnych skladowych: " << endl; 18 scc.Write(); 19 return 0; 20 }

Zadanie: Drogi
Pochodzenie: Potyczki Algorytmiczne 2005 Rozwizanie: a road.cpp

W Bajtocji jest n miast. Miasta s poczone jednokierunkowymi drogami. Kada droga a a czy tylko dwa miasta i nie przechodzi przez adne inne. Niestety, nie zawsze z kadego a miasta da si dojecha do kadego innego. Krl Bajtazar postanowi rozwiza ten problem. a Krl ma wiadomo, e budowanie nowych drg jest bardzo kosztowne, a budet Bajtocji nie jest zbyt zasobny. Dlatego te poprosi Ci o pomoc. Naley obliczy minimaln liczb a jednokierunkowych drg, ktre trzeba zbudowa, eby z kadego miasta dao si dojecha do kadego innego miasta.

Zadanie
Napisz program, ktry: wczyta opis istniejcej sieci drg, a obliczy minimaln liczb drg, ktre trzeba dobudowa tak, aby z kadego miasta w a Bajtocji dao si dojecha do kadego innego, wypisze wynik.

Wejcie
Pierwszy wiersz zawiera dwie liczby cakowite n i m (2 n 10 000, 0 m 100 000) oddzielone pojedynczym odstpem i oznaczajce, odpowiednio, liczb miast i liczb drg w a Bajtocji. Miasta s ponumerowane od 1 do n. W kadym z kolejnych m wierszy znajduj si a a dwie liczby cakowite, oddzielone pojedynczym odstpem. W i + 1 wierszu znajduj si liczby a ai i bi (1 ai , bi n dla 1 i m), reprezentuj one jednokierunkow drog prowadzc z a a a a miasta ai do bi .

Pierwszy i jedyny wiersz wyjcia powinien zawiera dokadnie jedn nieujemn liczb cakoa a wit minimaln liczb drg, ktre trzeba zbudowa w Bajtocji tak, aby z kadego miasta a a dao si dojecha do kadego innego miasta.

Wyjcie

34

Przykad
7 1 3 2 2 3 4 5 3 1 6 7 11 3 2 1 2 4 5 4 4 6 7 6

Dla nastpujcego wejcia: a

6 7

Poprawnym rozwizaniem jest: a


2

Proste acm.uva.es - zadanie 10731 spoj.sphere.pl - zadanie 51

rednie acm.uva.es - zadanie 247

wiczenia

Trudne acm.uva.es - zadanie 125 acm.uva.es - zadanie 10510

2.5. Sortowanie topologiczne


Skierowany graf acykliczny (ang. dag), jest to graf, ktry nie Literatura posiada cykli. Innymi sowy, w grae takim nie istnieje para wierz[WDA] - 23.4 chokw u i v poczonych ciekami u ; v i v ; u. a [ASP] - 4.3.3 Sortowanie topologiczne skierowanego grafu G = (V, E), polega na takim uporzdkowaniu wierzchokw ze zbioru V , aby dla a kadej krawdzi (u, v) E, wierzchoek u znajdowa si przed wierzchokiem v. Sortowanie topologiczne daje si wyznaczy dla wszystkich grafw skierowanych nie zawierajcych cykli. a Do wykonania tego zadania mona wykorzysta algorytm DFS jedyne, co naley zrobi, to posortowa wierzchoki w kolejnoci malejcych czasw przetworzenia f . a Zamy, e mamy dany skierowany acykliczny graf G oraz dwa wierzchoki u oraz v. Niech wartoci f wyznaczone dla tych wierzchokw przez algorytm DFS bd rwne odpoa wiednio uf i vf , oraz zamy bez straty oglnoci, e uf < vf . Zgodnie z naszym sposobem wyznaczania porzdku topologicznego, wierzchoek v zostanie umieszczony przed wierzchoa kiem u. Postpowanie takie jest zgodne z denicj porzdku topologicznego, gdy z warunku a a uf < vf oraz z acyklicznoci grafu G wynika, e w grae tym nie ma krawdzi (u, v). Algorytm DFS, oprcz zmiennych int f, wyznacza rwnie inne, niepotrzebne z punktu widzenia sortowania topologicznego wartoci, dlatego te implementacja funkcji void Graph <V,E>::TopoSort() nie korzysta z przedstawionej wczeniej implementacji algorytmu DFS. Wyznaczony porzdek topologiczny umieszczany jest w dodatkowych polach wierzchokw a int t pierwszy wierzchoek ma t warto rwn 0, drugi 1... Listing 2.17 przedstawia a a implementacj funkcji void Graph<V,E>::TopoSort().

35

2 0 1 4 3 2 5

(a)

(b)

Rysunek 2.5: (a) Przykadowy skierowany graf acykliczny (b) jeden z moliwych porzdkw topologicza
nych okrelonych na zbiorze wierzchokw grafu z rysunku (a)

Listing 2.17: Implementacja funkcji void Graph<V,E>::TopoSort() 01 int topo; // Funkcja wykonujca algorytm DFS z wierzchoka v i aktualizujca wartoci // zmiennych t 02 void TopoDfs(int v) { // Jeli wierzchoek nie by odwiedzony, to naley go odwiedzi 03 if (!g[v].t) { // Zaznacz wierzchoek jako odwiedzony 04 g[v].t = 1; // Odwied wszystkie wierzchoki, do ktrych prowadzi krawd z v 05 FOREACH(it, g[v]) TopoDfs(it->v); // Zaktualizuj warto t przetwarzanego wierzchoka 06 g[v].t = --topo; 07 } 08 } // Waciwa funkcja implementujca sortowanie topologiczne 09 void TopoSort() { 10 FOREACH(it, g) it->t = 0; 11 topo = SIZE(g); // Odwied wszystkie wierzchoki w grafie 12 FORD(x, topo - 1, 0) TopoDfs(x); 13 }

W wielu przypadkach, forma wyniku obliczana przez funkcj void Graph<V,E>::TopoSort() jest nieporczna w uyciu czsto oczekiwanym rezultatem jest posortowana lista wierzchokw grafu. Funkcja VI Graph<V,E>::TopoSortV(), ktrej implementacja przedstawiona jest na listingu 2.18, jako wynik dziaania zwraca wektor liczb reprezentujcych numery kolejnych a wierzchokw w porzdku topologicznym. a
Listing 2.18: Implementacja funkcji VI Graph<V,E>::TopoSortV() 1 VI TopoSortV() { 2 VI res(SIZE(g)); // Wyznacz sortowanie topologiczne 3 TopoSort(); // Na podstawie wartoci zmiennych t wierzchokw, wyznacz wektor z wynikiem 4 REP(x, SIZE(g)) res[g[x].t] = x;

36

Listing 2.18: (c.d. listingu z poprzedniej strony) 5 return res; 6}

Czas dziaania algorytmu sortowania topologicznego to O(n + m), co wynika bezporednio z faktu, i jest on modykacj przeszukiwania grafu w gb. a a
Listing 2.19: Wynik wyznaczony przez funkcj VI Graph<V,E>::TopoSortV() dla grafu z rysunku 2.5.a

Kolejnosc topologiczna wierzcholkow: 0 1 Wierzcholek 0 ma pozycje 0 w porzadku Wierzcholek 1 ma pozycje 1 w porzadku Wierzcholek 2 ma pozycje 4 w porzadku Wierzcholek 3 ma pozycje 2 w porzadku Wierzcholek 4 ma pozycje 3 w porzadku Wierzcholek 5 ma pozycje 5 w porzadku

3 4 2 5 topologicznym topologicznym topologicznym topologicznym topologicznym topologicznym

Listing 2.20: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.19. Peny kod rdowy programu znajduje si w pliku toposort str.cpp 01 struct Ve {}; // Wzbogacenie wierzchoka o pole t, w ktrym umieszczany jest wynik 02 struct Vs { 03 int t; 04 }; 05 int main() { 06 int n, m, b, e; // Wczytaj liczb wierzchokw oraz krawdzi, stwrz graf o odpowiedniej wielkoci 07 cin >> n >> m; 08 Graph<Vs, Ve> g(n); // Dodaj do grafu wszystkie krawdzie skierowane 09 REP(x,m) { 10 cin >> b >> e; 11 g.EdgeD(b, e); 12 } // Wyznacz porzdek topologiczny oraz wypisz wynik 13 VI res = g.TopoSortV(); 14 cout << "Kolejnosc topologiczna wierzcholkow: "; 15 FOREACH(it, res) cout << *it << " "; 16 cout << endl; 17 REP(x, SIZE(g.g)) cout << "Wierzcholek " << x << " ma pozycje " << 18 g.g[x].t << " w porzadku topologicznym" << endl; 19 return 0; 20 }

37

Zadanie: Spokojna komisja


Pochodzenie: VIII Olimpiada Informatyczna Rozwizanie: a comm.cpp

W parlamencie Demokratycznej Republiki Bajtocji, zgodnie z Bardzo Wan Ustaw, naley a a ukonstytuowa Komisj Poselsk do Spraw Spokoju Publicznego. Niestety spraw utrudnia a fakt, i niektrzy posowie wzajemnie si nie lubi. a Komisja musi spenia nastpujce warunki: a kada partia ma dokadnie jednego reprezentanta w Komisji, jeli dwaj posowie si nie lubi, to nie mog jednoczenie by w Komisji. a a

3 2 1 3 2 4

Kada partia ma w parlamencie dokadnie dwch posw. Wszyscy posowie s ponumerowani a liczbami od 1 do 2n. Posowie o numerach 2i 1 i 2i nale do partii o numerze i. a

Zadanie
Napisz program, ktry: wczyta liczb partii oraz pary posw, ktrzy si wzajemnie nie lubi, a wyznaczy skad Komisji, lub stwierdzi, e nie da si jej ukonstytuowa, wypisze wynik

Wejcie
W pierwszym wierszu wejcia znajduj si dwie nieujemne liczby cakowite n i m. Liczba a n, speniajca warunki 1 n 8 000, oznacza liczb partii. Liczba m, speniajca warunki a a 0 m 20 000, oznacza liczb par nielubicych si posw. W kadym z kolejnych m wiera szy zapisana jest para liczb naturalnych a i b, 1 a=b 2n, oddzielonych pojedynczym odstpem. Oznacza ona, e posowie o numerach a i b wzajemnie si nie lubi. a

Wyjcie
W pierwszym i jedynym wierszu wyjcia powinno znale si pojedyncze sowo N IE, jeli utworzenie Komisji nie jest moliwe. W przypadku, gdy utworzenie Komisji jest moliwe, program powinien wypisa n liczb cakowitych z przedziau od 1 do 2n, zapisanych w kolejnoci rosncej i oznaczajcych numery posw zasiadajcych w Komisji. Kada z tych liczb poa a a winna zosta zapisana w osobnym wierszu. Jeli Komisj mona utworzy na wiele sposobw, Twj program moe wypisa dowolny z nich.

Przykad

Dla nastpujcego wejcia: a


1 4 6

Poprawnym rozwizaniem jest: a

38

Proste acm.uva.es - zadanie 124 acm.uva.es - zadanie 10305 spoj.sphere.pl - zadanie 70

rednie acm.uva.es - zadanie 200 acm.sgu.ru - zadanie 230

wiczenia

Trudne acm.uva.es - zadanie 10319 spoj.sphere.pl - zadanie 44

2.6. Acykliczno
Majc dany graf G = (V, E) (skierowany bd nieskierowany), mona zada pytanie, a a czy jest on acykliczny. Acykliczno grafu jest podan wasnoci w wielu algorytmach. a a a Przykadowo, wyznaczanie topologicznego porzdku wierzchokw grafu skierowanego jest a moliwe pod warunkiem, e graf, dla ktrego wyznaczany jest ten porzdek, jest acykliczny. a Opisany w poprzednim rozdziale algorytm zakada prawdziwo tego faktu i nie sprawdza acyklicznoci grafu. W przypadku grafw skierowanych, stosunkowo atwo mona Literatura zmodykowa algorytm sucy do sortowania topologicznego grafu, a [WDA] - 5.4 wzbogacajc go o dodatkowy test na acykliczno. Wystarczy pod a koniec algorytmu sprawdzi, czy wszystkie krawdzie prowadz a od wierzchokw znajdujcych si wczeniej, do wierzchokw znajdujcych si pniej w a a porzdku topologicznym. Implementacja tej metodologii jest zrealizowana w funkcji bool a Graph<V,E> ::AcyclicD() do dziaania wymaga ona zaimplementowania funkcji void Graph<V,E>::TopoSort(), a zatem wykorzystuje rwnie dodatkowe pole int t wierzchokw. Implementacja tej funkcji znajduje si na listingu 2.21.
Listing 2.21: Implementacja funkcji bool Graph<V,E>::AcyclicD() // Funkcja sprawdzajca, czy dany graf skierowany jest acykliczny 1 bool AcyclicD() { // Wyznacz sortowanie topologiczne 2 TopoSort(); // Dla kadej krawdzi w grafie sprawd, czy prowadzi ona od wierzchoka // wczeniejszego, do wierzchoka pniejszego w porzdku topologicznym 3 FOREACH(it, g) FOREACH(it2, *it) if (it->t >= g[it2->v].t) return false; 4 return true; 5}

Algorytm realizowany przez funkcj bool Graph<V,E>::AcyclicD() nie dziaa prawidowo w przypadku grafw nieskierowanych reprezentacja krawdzi (u, v) w takich grafach skada si z dwch krawdzi skierowanych: (u, v), oraz (v, u). W podejciu realizowanym przez funkcj bool Graph<V,E>::AcyclicD(), kady graf nieskierowany z co najmniej jedn kraa wdzi uwaany jest za zawierajcy cykl. a a Dla grafw acyklicznych potrzebny jest inny algorytm. Funkcja bool Graph<V,E>:: AcyclicU(), ktrej kod rdowy zosta przedstawiony na listingu 2.22, jest implementacj a pomysu dziaajcego wanie dla takich grafw. Wykonuje ona algorytm DFS dla wszysta kich wierzchokw w grae i w momencie powtrnego wejcia do tego samego wierzchoka, stwierdza, e zawiera on cykl. Problem z reprezentacj krawdzi nieskierowanych w postaci a pary krawdzi skierowanych zosta rozwizany poprzez nieprzetwarzanie krawdzi, po kta a rej algorytm wszed do wierzchoka. Funkcja bool Graph<V,E>::AcyclicU() traktuje kad ptl oraz multikrawdzie jako cykl. 39

0 1 3 5 2 1 3

0 2

4 5

(a)

(b)

Rysunek 2.6: (a) Nieskierowany graf acykliczny (b) Skierowany graf zawierajcy cykl 5 ; 4 ; 0 ; 5 a

Listing 2.22: Implementacja funkcji bool Graph<V,E>::AcyclicU() // Wskanik na tablic suc do odznaczania wierzchokw odwiedzonych 01 bool *vis; // Zmienna, w ktrej umieszczany jest wynik 02 bool acyc; // Funkcja przeszukujca poddrzewo wierzchoka v w celu znalezienia cyklu 03 void AcDfs(int v, Ed * p) { // Jeli wierzchoek nie by jeszcze odwiedzony... 04 if (!vis[v]) { 05 vis[v] = 1; // Przetwrz jego wszystkie krawdzie za wyjtkiem tej, po ktrej przyszede // od ojca 06 FOREACH(it, g[v]) if (&(*it) != p) AcDfs(it->v, &g[it->v][it->rev]); 07 } else acyc = 0; 08 } // Funkcja sprawdzajca, czy graf jest acykliczny - dziaa prawidowo dla grafw // nieskierowanych 09 bool AcyclicU() { // Inicjalizacja zmiennych 10 acyc = 1; 11 vis = new bool[SIZE(g)]; 12 REP(x, SIZE(g)) vis[x] = 0; // Dla kadego wierzchoka w grafie dokonaj przeszukiwania 13 REP(x, SIZE(g)) if (!vis[x]) AcDfs(x, 0); 14 delete[]vis; 15 return acyc; 16 }

Zoono czasowa obu przedstawionych w tym rozdziale funkcji to O(n + m). Tak samo jak w przypadku funkcji przedstawionych w poprzednich rozdziaach, implementacja jest pewn modykacj algorytmu DFS, ktra przetwarza kady wierzchoek, oraz kad krawd a a a grafu dokadnie raz. Dla grafu, przedstawionego na rysunku 2.6.a, funkcja bool Graph<V,E> ::AcyclicU() zwrci fasz, natomiast funkcja bool Graph<V,E>::AcyclicD() dla grafu z rysunku 2.6.b prawd. 40

Zadanie: Wirusy
Pochodzenie: VII Olimpiada Informatyczna Rozwizanie: a viruses.cpp

Komisja Badania Wirusw Binarnych wykrya, e pewne cigi zer i jedynek s kodami wia a rusw. Komisja wyodrbnia zbir wszystkich kodw wirusw. Cig zer i jedynek nazywamy a bezpiecznym, gdy aden jego segment (tj. cig kolejnych znakw) nie jest kodem wirusa. a Komisja dy do ustalenia, czy istnieje nieskoczony, bezpieczny cig zer i jedynek. a a Dla zbioru kodw {011, 11, 00000} nieskoczonym, bezpiecznym cigiem jest 010101 . . .. a Dla zbioru kodw {01, 11, 00000} nie istnieje nieskoczony, bezpieczny cig zer i jedynek. a

Zadanie
Napisz program, ktry: wczyta kody wirusw, stwierdzi, czy istnieje nieskoczony, bezpieczny cig zer i jedynek, a wypisze wynik.

3 01 11 00000

Wejcie
W pierwszym wierszu znajduje si jedna liczba cakowita n, bdca liczb wszystkich kodw a a wirusw. W kadym z kolejnych n wierszy znajduje si jedno niepuste sowo zoone ze znakw 0 i 1 kod wirusa. Sumaryczna dugo wszystkich sw nie przekracza 30 000.

Wyjcie

W pierwszym i jedynym wierszu powinno znajdowa si sowo: TAK jeeli istnieje nieskoczony, bezpieczny cig zer i jedynek, a NIE w przeciwnym przypadku.

Przykad

Dla nastpujcego wejcia: a Poprawnym rozwizaniem jest: a


NIE

41

1 0 2 6 3 4

10

(a)

(b)

Rysunek 2.7: Dwa przykadowe grafy. Przerywane krawdzie reprezentuj mosty, wierzchoki o przerya
wanym obwodzie to punkty artykulacji. Pogrubione krawdzie przedstawiaj wyznaczone a drzewa przeszukiwania algorytmu DFS. (a) Graf o jedenastu wierzchokach i 12 krawdziach. Krawdzie (8, 9), (9, 10), (2, 6) oraz (3, 4) to mosty, wierzchoki 2, 3, 6 , 8 i 9 stanowi punkty artykulacji. (b) Graf zawiera dwa mosty (2, 3) i (2, 6), oraz dwa punkty a artykulacji 2 i 3.

2.7. Dwuspjne skadowe, mosty i punkty artykulacji


Literatura [KDP] - 2.6 [ASD] - 7.2 Denicja 2.7.1 Dwuspjn skadow w grae G jest maksymalny (ze wzgldu na zawieraa a nie) zbir krawdzi, takich, e dowolne dwie z nich le na wsplnym cyklu prostym. a Denicja 2.7.2 Punkt artykulacji jest to taki wierzchoek grafu G, ktrego usunicie zwiksza liczb spjnych skadowych grafu G. Denicja 2.7.3 Most jest to taka krawd (u, v) E, ktrej usunicie z grafu powoduje zwikszenie liczby spjnych skadowych grafu G. Dwuspjne skadowe wyznaczaj jednoznaczny podzia zbioru krawdzi grafu E. Zaa uwamy, e jeeli krawd (u, v) naley do jednoelementowej dwuspjnej skadowej, to jest ona mostem jej usunicie powoduje, e wierzchoki u i v nie nale ju do tej samej spjnej a skadowej grafu. W aktualnym rozdziale przedstawione zostanie kilka implementacji funkcji, sucych do a wyznaczania dwuspjnych skadowych, mostw oraz punktw artykulacji. Wyznaczanie dwuspjnych skadowych oraz elementw z nimi zwizanych (mostw oraz a punktw artykulacji) w grae jest moliwe dziki zastosowaniu przeszukiwania grafu w gb. a Zamy zatem, e dla caego grafu G = (V, E) wykonalimy ju algorytm DFS, na skutek czego zostay wyznaczone czasy wej do wierzchokw d oraz skonstruowany zosta las przeszukiwa w gb. Do dalszej analizy zagadnienia bdziemy potrzebowali denicji funkcji low, a okrelonej na zbiorze wierzchokw grafu G: low(v) = min(d(v), d(w), low(q) : w W, q Q), v V 42 Niech G = (V, E) bdzie nieskierowanym, spjnym grafem. Na wstpie zdeniujmy obiekty, ktrymi bdziemy si interesowali w obrbie aktualnego rozdziau:

gdzie W jest zbiorem wierzchokw, dla ktrych istnieje krawd (v, w) E nie bdca krawa dzi drzewow w lesie DFS, natomiast zbir Q skada si z nastpnikw v w lesie przeszukiwa a a DFS. Bardziej intuicyjnie low(v) jest rwne wartoci najmniejszej zmiennej d wierzchoka osigalnego z wierzchoka v przy uyciu krawdzi z poddrzewa DFS tego wierzchoka oraz co a najwyej jednej krawdzi niedrzewowej. Charakterystyka mostw oraz punktw artykulacji, wykorzystujca funkcj low, jest naa stpujca: a Wierzchoek v jest punktem artykulacji wtedy i tylko wtedy, gdy: jest korzeniem drzewa DFS oraz posiada co najmniej dwa nastpniki, lub nie jest korzeniem drzewa DFS oraz dla kadego nastpnika u wierzchoka v w drzewie DFS, low(u) d(v)

Mostami w grae s krawdzie drzewowe (u, v), dla ktrych d(u) a low(v). Wszystkie inne krawdzie w grae nale do pewnych dwuspjnych skadowych, ktre s rozdzielone a a midzy sob przez mosty oraz punkty artykulacji. a Charakterystyka punktw artykulacji, mostw oraz dwuspjnych skadowych jest silnie powizana ze sob, co pozwala na prosty sposb wyznaczania wszystkich tych obiektw na a a raz przy uyciu jednej funkcji. Ze wzgldu jednak na fakt, e w wielu zadaniach wymagane jest wyznaczenie tylko jednego z tych elementw, w dalszej czci rozdziau przedstawionych zostanie kilka funkcji, sucych do wyznaczania poszczeglnych, interesujcych nas obiektw. a a Funkcj, suc do wyznaczania mostw w grae, jest void Graph<V,E>::Bridges(VPII a a a &res), ktra wypenia, przekazany przez referencj, wektor parami numerw wierzchokw, ktre poczone s mostem. Funkcja ta wymaga wzbogacenia wierzchokw grafu o dwa doa a datkowe pola int d, oraz int low. Jej implementacja przedstawiona jest na listingu 2.23.
Listing 2.23: Implementacja funkcji void Graph<V,E>::Bridges(VII&) 01 int s; 02 VPII *X; // Funkcja dla kadego mostu w grafie, wstawia do wektora res par liczb // wierzchokw, ktre s poczone tym mostem 03 void Bridges(VPII & res) { // Odpowiednia inicjalizacja poszczeglnych zmiennych 04 res.clear(); 05 X = &res; 06 s = 0; 07 FOREACH(it, g) it->d = -1; // Przetworzenie wszystkich, jeszcze nieodwiedzonych, wierzchokw w grafie 08 REP(i, SIZE(g)) if (g[i].d == -1) BriSearch(i, -1); 09 } // Funkcja realizuje przeszukiwanie grafu metod DFS - odwiedza wierzchoek v, // gdzie u jest ojcem v w drzewie DFS 10 void BriSearch(int v, int u) { 11 g[v].d = g[v].low = s++; // Dla kadej krawdzi wychodzcej z wierzchoka v... 12 FOREACH(it, g[v]) { 13 int w = it->v; // Jeli wierzchoek w nie by jeszcze odwiedzony, to go odwied 14 if (g[w].d == -1) {

43

Listing 2.23: (c.d. listingu z poprzedniej strony) 15 BriSearch(w, v); // Jeli znaleziono w grafie most, to dodaj go do wyniku 16 if (g[w].low > g[v].d) { 17 X->PB(MP(v <? w, v >? w)); 18 } else g[v].low <?= g[w].low; 19 } else if (w != u) g[v].low <?= g[w].d; 20 } 21 }

Funkcj, suc do wyznaczania dwuspjnych skadowych, jest void Graph<V,E>::Dcc(). a a a Podobnie jak poprzednio, omawiana funkcja, wymaga wzbogacenia struktury wierzchoka o pola int d oraz int low. Dodatkowo, do struktury krawdzi musi zosta dodane pole int dcc oraz bool bridge. Dla kadej krawdzi w grae, algorytm okrela, czy jest ona mostem (pole bool bridge), oraz w przypadku, gdy krawd nie jest mostem, wylicza numer dwuspjnej skadowej, do ktrej ta krawd naley (pole int dcc).
Listing 2.24: Implementacja funkcji void Graph<V,E>::Dcc() // Zmienna id uywana jest do numerowania dwuspjnych skadowych, natomiast // l do pamitania aktualnego czasu odwiedzenia wierzchokw metod DFS 01 int id, l; 02 typedef typename vector<Ed>::iterator EIT; 03 vector<EIT> qu; // Makro ustawia odpowiednie wartoci zmiennych dcc i bri dla krawdzi // nieskierowanej, wskazywanej przez iterator e 04 #dene DccMark(bri) e->dcc=g[e->v][e->rev].dcc=id,\ 05 e->bridge=g[e->v][e->rev].bridge=bri 06 void Dcc() { // Odpowiednia inicjalizacja zmiennych 07 id = l = 0; 08 qu.clear(); 09 FOREACH(it, g) it->d = -1; // Przetwrz wszystkie, jeszcze nieodwiedzone, wierzchoki w grafie 10 REP(i, SIZE(g)) if (g[i].d == -1) DccSearch(i, -1); 11 } 12 void DccSearch(int v, int u) { 13 EIT e; // Ustawienie na odpowiednie wartoci zmiennych d i low wierzchoka v 14 g[v].d = g[v].low = l++; // Dla wszystkich krawdzi, wychodzcych z wierzchoka v 15 FOREACH(it, g[v]) { 16 int w = it->v; // Jeli wierzchoek docelowy nie zosta jeszcze odwiedzony... 17 if (g[w].d == -1) { // Wstaw iterator wskazujcy na aktualnie przetwarzan krawd na stos 18 qu.PB(it); // Odwied wierzchoek, do ktrego prowadzi krawd 19 DccSearch(w, v);

44

Listing 2.24: (c.d. listingu z poprzedniej strony) 20 if (g[w].low >= g[v].d) { // Znaleziono dwuspjn skadow - dla kadej krawdzi z tej skadowej // ustaw odpowiednie wartoci zmiennych bri oraz dcc 21 int cnt = 0; 22 do { 23 e = qu.back(); 24 DccMark(0); 25 qu.pop back(); 26 cnt++; 27 } while (e != it); // Znaleziony zosta most 28 if (cnt == 1) DccMark(1); 29 id++; 30 } else g[v].low <?= g[w].low; 31 } else if (g[w].d < g[v].d && w != u) qu.PB(it), g[v].low <?= g[w].d; 32 } 33 }

Ostatni z tej serii funkcj jest void Graph<V,E>::BriArt(VPII&), ktra wymaga wzboa a gacenia struktury wierzchokw grafu o pola int d oraz bool art, a wyznacza dla kadego z nich, czy jest on punktem artykulacji informacja ta umieszczana jest w polu bool art. Dodatkowo, funkcja wypenia wektor przekazany przez referencj parami numerw wierzchokw poczonych mostami (podobnie do funkcji void Graph<V,E>::Bridges(VII&)). a
Listing 2.25: Implementacja funkcji void Graph<V,E>::BriArt(VPII&) // Zmienna wykorzystywana do przechowywania czasu odwiedzenia wierzchokw przez // algorytm DFS 01 int t; // Wskanik na konstruowan list znalezionych mostw 02 VPII *br; // Funkcja przeszukuje poddrzewo wierzchoka v metod DFS (p jest // wierzchokiem-ojcem v w drzewie DFS) 03 int BriArtR(int v, int p) { 04 int l = g[v].d = ++t; // Dla kadej krawdzi wychodzcej z wierzchoka v i nie prowadzcej do // wierzchoka-ojca w drzewie DFS... 05 FOREACH(it, g[v]) if (it->v != p) // Aktualizacja wartoci funkcji low dla wierzchoka v. 06 l <?= !g[it->v].d ? BriArtR(it->v, v) : g[it->v].d; // Zaktualizowanie informacji o znalezionym punkcie artykulacji 07 if (g[p].d <= l) g[p].art = 1; // Jeli zosta znaleziony most, to jest on dodawany do wyniku 08 if (g[p].d < l) br->PB(MP(v <? p, v >? p)); 09 return l; 10 } 11 void BriArt(VPII & res) { // Odpowiednia inicjalizacja zmiennych

45

Listing 2.25: (c.d. listingu z poprzedniej strony) 12 res.clear(); 13 br = &res; 14 t = 0; 15 REP(x, SIZE(g)) g[x].art = g[x].d = 0; // Od kadego jeszcze nieodwiedzonego wierzchoka rozpocznij przeszukiwanie // wgb 16 REP(x, SIZE(g)) if (!g[x].d) { 17 g[x].d = ++t; 18 int c = 0; 19 FOREACH(it, g[x]) if (!g[it->v].d) { 20 c++; 21 BriArtR(it->v, x); 22 } // Jeli z korzenia drzewa DFS wychodzi wicej ni 1 krawd drzewowa, to // jest on punktem artykulacji 23 g[x].art = (c > 1); 24 } 25 }

Tablica 2.26: Wyniki wyznaczone dla dwch przykadowych grafw z rysunkw 2.7.a oraz 2.7.b przez poszczeglne omwione funkcje

Funkcja void Graph<V,E>::Bridges(VPII&)

Graf z rysunku 2.7.a


(3 ,4) (9 ,10) (8 ,9) (2 ,6) E( 1 , E( 1 , E( 2 , E( 2 , E( 3 , E( 5 , E( 5 , E( 6 , E( 6 , E( 7 , E( 8 , E( 9 , 2) 5 3) 5 3) 5 6 ) 4 ( most ) 4 ) 0 ( most ) 6) 3 7) 3 7) 3 8) 3 8) 3 9 ) 2 ( most ) 1 0 ) 1 ( most ) 4 ) most 1 0 ) most 9 ) most 6 ) most punkt a r t punkt a r t punkt a r t punkt a r t punkt a r t

Graf z rysunku 2.7.b


(2 ,3) (2 ,6)

void Graph<V,E>::Dcc()

E( 0 , E( 0 , E( 1 , E( 2 , E( 2 , E( 3 , E( 3 , E( 4 ,

1) 2) 2) 3) 6) 4) 5) 5)

3 3 3 1 ( most ) 2 ( most ) 0 0 0

void Graph<V,E>::BriArt(VPII&)

(3 , (9 , (8 , (2 , 2 3 6 8 9

(2 , (2 , 2 3

3 ) most 6 ) most punkt a r t punkt a r t

46

Listing 2.27: Program prezentuj cy sposb uycia funkcji void Graph<V,E>::Bridges(VII&), a void Graph<V,E>::Dcc() oraz void Graph<V,E>::BriArt(VPII&). Peny kod rdowy tego programu znajduje si w pliku bri art dcc.cpp // Wzbogacenie struktur wierzchokw oraz krawdzi wymagane // przez funkcj Bridges 01 struct VsBridges { 02 int d, low; 03 }; 04 struct VeBridges { 05 int rev; 06 }; // Wzbogacenie struktury wierzchokw i krawdzi o elementy wymagane // przez funkcj Dcc 07 struct VsDcc { 08 int d, low; 09 }; 10 struct VeDcc { 11 int rev, dcc; 12 bool bridge; 13 }; // Wzbogacenie struktury wierzchokw oraz krawdzi o elementy wymagane // przez funkcj BriArt 14 struct VsBriArt { 15 int d; 16 bool art; 17 }; 18 struct VeBriArt { 19 int rev; 20 }; 21 int main() { 22 int n, m, b, e; // Wczytaj liczb wierzchokw oraz krawdzi 23 cin >> n >> m; 24 Graph<VsBridges, VeBridges> g1(n); 25 Graph<VsDcc, VeDcc> g2(n); 26 Graph<VsBriArt, VeBriArt> g3(n); // Dodaj do grafw wszystkie krawdzie 27 REP(x, m) { 28 cin >> b >> e; 29 g1.EdgeU(b, e); 30 g2.EdgeU(b, e); 31 g3.EdgeU(b, e); 32 } // Stosowanie funkcji Bridges 33 VPII res1; 34 g1.Bridges(res1); 35 FOREACH(it, res1) 36 cout << "(" << it->ST << "," << it->ND << ")" << endl;

47

Listing 2.27: (c.d. listingu z poprzedniej strony) // Stosowanie funkcji Dcc 37 g2.Dcc(); 38 REP(x, SIZE(g2.g)) FOREACH(it, g2.g[x]) if (x < it->v) { 39 if (it->bridge) 40 cout << "(" << x << "," << it->v << ") - most" << endl; 41 cout << "E(" << x << "," << it->v << ") - " << it->dcc << endl; 42 } // Stosowanie funkcji BriArt 43 VPII res3; 44 g3.BriArt(res3); 45 FOREACH(it, res3) 46 cout << "(" << it->ST << ", " << it->ND << ") - most" << endl; 47 REP(x, SIZE(g3.g)) 48 if (g3.g[x].art) cout << x << " - punkt art." << endl; 49 return 0; 50 }

Proste acm.uva.es - zadanie 315

rednie acm.uva.es - zadanie 10199

wiczenia

Trudne spoj.sphere.pl - zadanie 185 spoj.sphere.pl - zadanie 208

2.8. cieka i cykl Eulera


cieka Eulera w grae G = (V, E), jest to dowolna cieka Literatura przechodzca przez wszystkie krawdzie tego grafu dokadnie raz. a [KDP] - 2.7 W przypadku, gdy pierwszy wierzchoek na ciece jest rwny [ASD] - 7.4 wierzchokowi ostatniemu, to ciek Eulera nazywamy cyklem Eu[MD] - 6.2 lera. W zalenoci od przyjtych zaoe, cieka Eulera musi przechodzi przez wszystkie wierzchoki w grae, lub te nie przechodzi przez wierzchoki izolowane (czyli takie, z ktrych nie wychodz adne krawdzie). W tym a rozdziale zakadamy, e wierzchoki izolowane nie musz by odwiedzone dziki takiemu a podejciu, przed rozpoczciem wyszukiwania cieki Eulera, nie musimy si martwi o eliminowanie z grafu wierzchokw izolowanych, ktre czsto pojawiaj si przypadkowo przy a szybkim implementowaniu zada podczas zawodw. cieki oraz cykle Eulera mona wyznacza zarwno dla grafw skierowanych i nieskierowanych. Warunki na ich istnienie w grae (oprcz spjnoci grafu z pominiciem ewentualnych wierzchokw izolowanych), s nastpujce: a a W grae nieskierowanym, cykl Eulera istnieje wtedy i tylko wtedy, gdy stopie kadego wierzchoka jest parzysty. W grae skierowanym, cykl Eulera istnieje wtedy i tylko wtedy, gdy stopie wejciowy kadego wierzchoka jest rwny stopniowi wyjciowemu. 48

0 2 1 6

3 4 5

0 2 1 6

3 4 5

(a)

(b)

Rysunek 2.8: (a) Graf skierowany zawierajcy cykl Eulera 0 ; 2 ; 3 ; 4 ; 5 ; 3 ; 6 ; 2 ; 1 ; 0. a


(b) Graf skierowany nie posiadajcy cyklu Eulera (stopie wejciowy wierzchoka 6 nie a jest rwny stopniu wyjciowemu). Graf ten zawiera ciek Eulera 6 ; 2 ; 1 ; 0 ; 2 ; 3 ; 4 ; 5 ; 3.

W grae nieskierowanym, cieka Eulera, nie bdca cyklem Eulera, istnieje wtedy i a tylko wtedy, gdy dwa wierzchoki maj stopie nieparzysty, natomiast wszystkie inne a wierzchoki parzysty. W grae skierowanym, cieka Eulera nie bdca cyklem Eulera istnieje wtedy i tylko a wtedy, gdy jeden wierzchoek w grae ma wikszy o jeden stopie wejciowy od wyjciowego, jeden wierzchoek ma o jeden mniejszy stopie wejciowy od wyjciowego, natomiast reszta wierzchokw maj rwne stopnie wejciowe i wyjciowe. a

Prezentowane algorytmy wyszukiwania cieki Eulera bazuj na przeszukiwaniu grafu w a gb. Gwna rnica w podejciu, w stosunku do oryginalnego przeszukiwania DFS jest a taka, e podczas wyznaczania cieki Eulera, wierzchoki mog by odwiedzane wielokrota nie, natomiast krawdzie tylko raz. Dla kolejno odwiedzanych wierzchokw, wybierane s a krawdzie jeszcze nieodwiedzone. W momencie znalezienia si w wierzchoku, z ktrego nie da si wyj (gdy wszystkie krawdzie z niego wychodzce zostay ju przetworzone), algoa rytm wycofuje si z tego wierzchoka, jednoczenie dodajc krawdzie, po ktrych si cofa do a wyznaczanej cieki Eulera. Jedyna rnica w sposobie podejcia do grafw nieskierowanych polega na tym, e przechodzc krawdzi (v, u), naley rwnie zaznaczy krawd (u, v) jako a a odwiedzon. a Wyznaczanie cieek Eulera w prosty sposb mona zrealizowa przy uyciu funkcji rekurencyjnej. Takie podejcie jednak nie jest bezpieczne ze wzgldu na moliwo przepenienia stosu. Prezentowane w tym rozdziale funkcje void Graph<V,E>EulerD::(VI&) oraz void Graph<V,E>::EulerU(VI&) s iteracyjn realizacj przedstawionej wyej koncepcji. Obie a a a funkcje przyjmuj jako parametr referencj na wektor zmiennych typu int. W przypadku, a gdy w grae nie istnieje cieka Eulera, zwracaj fasz. Gdy cieka istnieje, zwracana jest a prawda, natomiast przekazany przez referencj wektor wypeniony zostaje numerami, kolejno odwiedzanych na wyznaczonej ciece, wierzchokw. W przypadku, gdy cieka Eulera jest cyklem, pierwszy i ostatni element wektora s rwne (cieka koczy si w tym samym wierza choku, co zaczyna). Odpowiednie implementacje przedstawione s na listingach 2.28 oraz a 2.29.
Listing 2.28: Implementacja funkcji void Graph<V,E>::EulerD(VI&) 01 bool EulerD(VI &r) { // Inicjalizacja wymaganych zmiennych 02 int v = -1, kr = 1, k1 = 0, k2 = 0, h; 03 r.clear();

49

Listing 2.28: (c.d. listingu z poprzedniej strony) 04 VI st, za(SIZE(g), 0); // Dla wszystkich wierzchokw wyliczany jest stopie wejciowy 05 FOREACH(it, g) FOREACH(it2, *it) ++za[it2->v]; // Naley wyznaczy wierzchoek v, od ktrego rozpoczte // zostanie wyszukiwanie cieki Eulera. 06 REP(x, SIZE(g)) { // Jeli wierzchoek ma wikszy stopie wyjciowy od wejciowego, // to jest wierzchokiem startowym 07 if ((h = SIZE(g[x])) > za[x]) v = x; else // Jeli wierzchoek ma jakie wychodzce krawdzie oraz nie znaleziono // jeszcze wierzchoka pocztkowego, to uyj go jako wierzchoek startowy 08 if (h && v == -1) v = x; 09 kr += za[x] = h; 10 } 11 if (v != -1) st.PB(v); // Konstrukcja cieki Eulera jest rozpoczynana w wierzchoku v 12 st.PB(v); // Dopki istniej wierzchoki na stosie, przeszukuj graf metod DFS 13 while(SIZE(st)) if (!za[v = st.back()]) { 14 st.pop back(); 15 r.PB(v); 16 } else st.PB(v = g[v][--za[v]].v); // Wyznaczona cieka Eulera zostaa skonstruowana w odwrotnym kierunku, // wic trzeba j odwrci 17 reverse(ALL(r)); // Algorytm zwraca prawd, jeli wykorzystane zostay wszystkie // w grafie 18 return SIZE(r) == kr; 19 }

Listing 2.29: Implementacja funkcji void Graph<V,E>::EulerU(VI&) 01 bool EulerU(VI & ce) { // Inicjalizacja wymaganych zmiennych 02 int v = -1; 03 ce.clear(); 04 VI st, za(SIZE(g), 0), of(SIZE(g) + 1, 0); // Naley wyznaczy wierzchoek v, od ktrego rozpoczte zostanie // wyszukiwanie cieki Eulera 05 REP(x, SIZE(g)) { 06 of[x + 1] = of[x] + (za[x] = SIZE(g[x])); 07 if ((za[x] & 1) || (v == -1 && za[x])) v = x; 08 } // Wektor sucy do odznaczania wykorzystanych krawdzi. 09 vector<bool> us(of[SIZE(g)], 0); // Konstrukcja cieki Eulera jest rozpoczynana w wierzchoku v 10 if (v != -1) st.PB(v); // Dopki istniej wierzchoki na stosie, przeszukuj graf metod DFS

50

Listing 2.29: (c.d. listingu z poprzedniej strony) 11 while (SIZE(st)) { 12 v = st.back(); // Dopki kolejne krawdzie zostay ju przetworzone, pomi je 13 while (za[v] && us[of[v] + za[v] - 1]) --za[v]; // Jeli nie ma ju wicej krawdzi, to wyjd z wierzchoka i dodaj krawd, // po ktrej si cofasz do wyniku 14 if (!za[v]) { 15 st.pop back(); 16 ce.PB(v); 17 } else { // Przejd po jeszcze niewykorzystanej krawdzi 18 int u = g[v][--za[v]].v; 19 us[of[u] + g[v][za[v]].rev] = 1; 20 st.PB(v = u); 21 } 22 } // Algorytm zwraca prawd, jeli wykorzystane zostay wszystkie krawdzie w // grafie 23 return 2 * (SIZE(ce) - 1) == of[SIZE(g)]; 24 }

Zarwno w przypadku grafw skierowanych, jak i nieskierowanych, zoono czasowa procesu wyznaczania cieki Eulera to O(n + m) kada krawd w grae przetwarzana jest jednokrotnie. Dla grafu przedstawionego na rysunku 2.8.a, wywoanie funkcji Graph<V,E>::EulerD(VI &l) spowoduje wypenienie wektora l nastpujcymi liczbami, reprezentujcymi numery a a kolejno odwiedzanych wierzchokw na wyznaczonym cyklu Eulera: {0, 2, 3, 4, 5, 3, 6, 2, 1, 0} Gdyby ten sam graf potraktowa jako nieskierowany, to zawarto wektora l po wywoaniu funkcji Graph<V,E>::EulerU(VI &l) byaby nastpujca: a {0, 1, 2, 3, 5, 4, 3, 6, 2, 0} Po usuniciu z grafu krawdzi czcej wierzchoki 3 i 6 (graf ten przedstawiony jest na a a rysunku 2.8.b), wywoanie Graph<V,E>::EulerD(VI &l) spowoduje wypenienie wektora l nastpujcymi liczbami: a {6, 2, 1, 0, 2, 3, 4, 5, 3} Gdyby ten sam graf potraktowa jako nieskierowany, to zawarto wektora l po wywoaniu funkcji Graph<V,E>::EulerU(VI &l) byaby nastpujca: a {6, 2, 0, 1, 2, 3, 5, 4, 3} Podane wyniki dziaania funkcji Graph<V,E>::EulerD(VI &l) i Graph<V,E>::EulerU(VI &l) s przykadowe i zale od kolejnoci wstawiania do grafu krawdzi midzy wierzchoa a kami.

51

Listing 2.30: Program prezentuj cy uycie funkcji Graph<V,E>::EulerD(VI &l). Peny kod ra dowy tego programu znajduje si w pliku eulerpath.cpp. // Zarwno krawdzie, jak i wierzchoki nie wymagaj dodatkowych wzbogace 01 struct Ve { }; 02 struct Vs { }; 03 int main() { 04 VI res; 05 int n, m, b, e; // Wczytaj liczb wierzchokw oraz krawdzi w grafie 06 cin >> n >> m; 07 Graph<Vs, Ve> g(n); // Dodaj do grafu odpowiednie krawdzie 08 REP(x, m) { 09 cin >> b >> e; 10 g.EdgeD(b, e); 11 } // Jeli graf zawiera ciek Eulera - wypisz j, jeli nie - poinformuj o tym 12 if (g.EulerD(res)) { 13 FOREACH(it, res) cout << *it << " "; 14 cout << endl; 15 } else { 16 cout << "No Euler Path" << endl; 17 } 18 return 0; 19 }

Listing 2.31: Program prezentuj cy uycie funkcji Graph<V,E>::EulerU(VI &l). Peny kod ra dowy tego programu znajduje si w pliku eulerupath.cpp // Wymagane wzbogacenie krawdzi w grafie nieskierowanym 01 struct Ve { 02 int rev; 03 }; // Wierzchoki nie wymagaj dodatkowych wzbogace 04 struct Vs { }; 05 int main() { 06 VI res; 07 int n, m, b, e; // Wczytaj liczb wierzchokw oraz krawdzi w grafie 08 cin >> n >> m; 09 Graph<Vs, Ve> g(n); // Dodaj do grafu odpowiednie krawdzie 10 REP(x, m) { 11 cin >> b >> e; 12 g.EdgeU(b, e); 13 } // Jeli graf zawiera ciek Eulera - wypisz j, jeli nie - poinformuj o tym 14 if (g.EulerU(res)) { 15 FOREACH(it, res) cout << *it << " ";

52

Listing 2.31: (c.d. listingu z poprzedniej strony) 16 cout << endl; 17 } else { 18 cout << "No Euler Path" << endl; 19 } 20 return 0; 21 }

Zadanie: Linie autobusowe


Pochodzenie: Potyczki Algorytmiczne 2005 Rozwizanie: a buses.cpp

W Bajtocji jest n miast, poczonych dwukierunkowymi drogami, przy ktrych le liczne a a wioski. Krl Bajtazar zdecydowa si utworzy sie linii autobusowych obsugujcych miasta a i wioski. Kada linia moe si zaczyna i koczy w dowolnym miecie oraz przebiega przez dowolne miasta. Miasta na trasie linii mog si powtarza, jednak adna linia nie moe a przebiega wielokrotnie t sam drog. a a a Aby wszystkim mieszkacom zapewni transport, a jednoczenie zminimalizowa koszty inwestycji, krl Bajtazar postanowi, e kad drog bdzie przebiegaa dokadnie jedna linia a autobusowa, a take, e liczba linii autobusowych bdzie minimalna.

Zadanie
Napisz program, ktry: wczyta opis sieci drg, zaprojektuje sie linii autobusowych speniajc podane wymagania, a a wypisze wynik.

Wejcie
Pierwszy wiersz zawiera dwie liczby cakowite n i m, oddzielone pojedynczym odstpem, 2 n 10 000, n 1 m 200 000; n jest liczb miast, a m liczb drg. Miasta s a a a ponumerowane od 1 do n. Kolejnych m wierszy zawiera opis sieci drg. Kady z tych wierszy zawiera dwie liczby cakowite a i b, oddzielone pojedynczym odstpem, 1 a b n numery miast poczonych drog. Kada droga jest podana na wejciu dokadnie raz. a a Moesz zaoy, e dowolne dwa miasta s poczone co najwyej jedn drog (chocia moe a a a a by wiele tras czcych dwa miasta) i e istnieje moliwo przejazdu pomidzy dowolnymi a a dwoma miastami.

Wyjcie
Pierwszy wiersz powinien zawiera liczb c, rwn minimalnej liczbie linii autobusowych. a Kolejnych c wierszy powinno zawiera opisy kolejnych linii: i+1-szy wiersz powinien zawiera liczb li rwn liczbie miast na trasie i-tej linii, a nastpnie li numerw tych miast, podanych a w kolejnoci przebiegu linii. Liczby w wierszach powinny by pooddzielane pojedynczymi odstpami. Jeeli linia ma swj pocztek i koniec w tym samym miecie, jego numer powinien a si znale na pocztku i na kocu opisu trasy. a

53

Przykad
4 1 2 2 1 3 1 6 2 4 3 3 4 4

Dla nastpujcego wejcia: a

Poprawnym rozwizaniem jest: a


2 3 3 1 4 5 1 2 4 3 2

Proste acm.uva.es - zadanie 117 acm.uva.es - zadanie 10129 acm.sgu.ru - zadanie 101

rednie acm.uva.es - zadanie 10054 acm.sgu.ru - zadanie 121

wiczenia

Trudne acm.uva.es - zadanie 10441 spoj.sphere.pl - zadanie 41 acm.sgu.ru - zadanie 286

2.9. Minimalne drzewo rozpinajce a


Rozpatrzmy spjny graf nieskierowany G = (V, E), w ktrym Literatura kadej krawdzi przypisana jest pewna nieujemna waga. Drzewo [WDA] - 24 rozpinajce dla grafu G, jest to spjny podgraf G = (V, E ), E a [KDP] - 2.4 E grafu G, zawierajcy dokadnie |V | 1 krawdzi. Minimalne a [ASD] - 7.6 drzewo rozpinajce, to takie drzewo rozpinajce, ktrego suma wag a a [MD] - 6.6 krawdzi jest minimalna. Na rysunku 2.9 przedstawiony jest przykadowy graf oraz dwa jego drzewa rozpinajce. Na listingu 2.32 znajduje si implementacja funkcji int Graph<V,E> a ::MinSpanTree(), ktra dla danego spjnego grafu wyznacza minimalne drzewo rozpinajce a i zwraca jako wynik sum wag krawdzi nalecych do tego drzewa. Funkcja ta wymaga, a aby dla kadej krawdzi w grae, jej waga znajdowaa si w dodatkowej zmiennej int l. Po zakoczeniu dziaania, pole bool span kadej krawdzi zawiera warto prawda, gdy odpowiadajca jej krawd naley do wyznaczonego, minimalnego drzewa rozpinajcego. a a Dziaanie funkcji int Graph<V,E>::MinSpanTree() realizuje algorytm Prima. Na pocza tku konstruowane jest jednowierzchokowe drzewo rozpinajce (wybierany jest w tym celu a dowolny wierzchoek grafu wejciowego), a nastpnie dokadane s kolejne krawdzie o naja mniejszej wadze, czce wierzchoek nalecy do konstruowanego drzewa, z wierzchokiem a a a jeszcze do niego nie nalecym. Poprawno algorytmu wynika z faktu, i w kadym kroku a algorytm konstruuje minimalne drzewo rozpinajce (wybierane s najlejsze moliwe krawa a dzie) dla coraz wikszego zbioru wierzchokw, a w kocu uzyskiwane jest drzewo rozpinajce a dla caego grafu G. Zoono czasowa takiego algorytmu to O(m log(n)). Innym, rwnie popularnym jak algorytm Prima, jest algorytm Kruskala, ktry na pocztku a konstruuje graf skadajcy si tylko z wierzchokw oryginalnego grafu G, a nastpnie przea twarza wszystkie krawdzie grafu G w kolejnoci niemalejcych wag. Dodaje do drzewa roza pinajcego te krawdzie, ktre cz rne spjne skadowe konstruowanego grafu. a a a

54

0
10 10

6 15

1
20

30

0
15 10

1
20

30

0
10

30

15

10

10

10

(a)

(b)

(c)

Rysunek 2.9: (a) Graf o szeciu wierzchokach [0 . . . 5] oraz dziewiciu krawdziach. Wzdu krawdzi
zaznaczone s ich wagi. (b) Drzewo rozpinajce dla grafu z rysunku (a) o sumarycznej a a wadze krawdzi 85. (c) Minimalne drzewo rozpinajce dla grafu z rysunku (a) o sumarycza nej wadze krawdzi 61. Nie jest to jedyne minimalne drzewo rozpinajce drugie mona a uzyska poprzez wymian krawdzi (0, 3) na (1, 3).

Listing 2.32: Implementacja funkcji int Graph<V,E>::MinSpanTree() // W polu bool span krawdzi algorytm wstawia warto prawda gdy krawd naley // do wyznaczonego minimalnego drzewa rozpinajcego. Funkcja zwraca wag // znalezionego drzewa. 01 int MinSpanTree() { // Tablica d dla kadego wierzchoka, nie nalecego jeszcze do drzewa // rozpinajcego, zawiera dugo najkrtszej krawdzi czcej go z dowolnym // wierzchokiem drzewa 02 int r = 0, d[SIZE(g)]; // Tablica suca do odznaczanie wierzchokw dodanych do drzewa 03 bool o[SIZE(g)]; 04 REP(x, SIZE(g)) { 05 d[x] = INF; 06 o[x] = 0; 07 } // Kolejka priorytetowa wierzchokw, osigalnych z budowanego drzewa, w // kolejnoci niemalejcych kosztw krawdzi 08 set<PII> s; 09 s.insert(MP(d[0] = 0, 0)); // Dopki istniej wierzchoki nie nalece do drzewa 10 while (!s.empty()) { // Wybierz wierzchoek, ktrego dodanie jest najtasze 11 int v = (s.begin())->ND; 12 s.erase(s.begin()); 13 bool t = 0; // Zaznacz wierzchoek jako dodany do drzewa oraz zwiksz sumaryczn wag // drzewa 14 o[v] = 1; 15 r += d[v]; // Dla wszystkich krawdzi wychodzcych z dodawanego wierzchoka... 16 FOREACH(it, g[v]) { 17 it->span = 0; // Jeli jest to krawd, ktr dodano do drzewa, to zaznacz ten fakt 18 if (!t && o[it->v] && it->l == d[v])

55

Listing 2.32: (c.d. listingu z poprzedniej strony) 19 t = it->span = g[it->v][it->rev].span = 1; 20 else // Prba zaktualizowania odlegoci od drzewa dla wierzchokw jeszcze nie // dodanych... 21 if (!o[it->v] && d[it->v] > it->l) { 22 s.erase(MP(d[it->v], it->v)); 23 s.insert(MP(d[it->v] = it->l, it->v)); 24 } 25 } 26 } // Zwr wag skonstruowanego drzewa rozpinajcego 27 return r; 28 }

Listing 2.33: Wynik wygenerowany przez funkcj int Graph<V,E>::MinSpanTree() dla grafu z rysunku 2.9.a

Waga minimalnego drzewa rozpinajacego: 61 Krawedzie nalezace do drzewa: (0,1) (1,2) (1,3) (1,5) (4,5)

Listing 2.34: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.33. Peny kod rdowy programu znajduje si w pliku minspantree str.cpp // Wzbogacenie krawdzi wymagane przez funkcj wyznaczajc minimalne drzewo // rozpinajce 01 struct Ve { 02 int rev, l; 03 bool span; 04 }; 05 struct Vs {}; 06 int main() { 07 int n, m, b, e; // Wczytaj liczb wierzchokw oraz krawdzi w grafie 08 cin >> n >> m; 09 Ve l; // Skonstruuj graf o odpowiedniej wielkoci i dodaj do niego krawdzie 10 Graph<Vs, Ve> g(n); 11 REP(x,m) { 12 cin >> b >> e >> l.l; 13 g.EdgeU(b, e, l); 14 } // Wyznacz minimalne drzewo rozpinajce 15 cout << "Waga minimalnego drzewa rozpinajacego: " << 16 g.MinSpanTree() << endl << "Krawedzie nalezace do drzewa:"; // Wypisz wszystkie krawdzie nalece do drzewa rozpinajcego 17 REP(x, SIZE(g.g)) FOREACH(it, g.g[x]) if (it->span && it->v < x) 18 cout << " (" << it->v << "," << x << ")"; 19 cout << endl;

56

Listing 2.34: (c.d. listingu z poprzedniej strony) 20 return 0; 21 }

Proste acm.uva.es - zadanie 534 acm.uva.es - zadanie 10034 spoj.sphere.pl - zadanie 368

rednie acm.uva.es - zadanie 10462 acm.uva.es - zadanie 10600 spoj.sphere.pl - zadanie 30

wiczenia

Trudne acm.uva.es - zadanie 10147 spoj.sphere.pl - zadanie 148 acm.sgu.ru - zadanie 206

2.10. Algorytm Dijkstry


Kolejnym problemem, jakim si zajmiemy, bdzie znajdowanie Literatura dla kadego wierzchoka w waonym grae G = (V, E) (skiero[WDA] - 25.2 wanym lub nieskierowanym), odlegoci od zadanego rda s [KDP] - 3.3 V . Zamy, e kadej krawdzi grafu przypisana jest pewna nie[MD] - 8.3 ujemna waga, reprezentujca dugo tej krawdzi. Odlego mia [CON] - 3.5 dzy wierzchokami v i w w takim grae rozumiana jest jako dugo najkrtszej cieki v ; . . . ; w, czyli suma wag krawdzi na niej wystpujcych. Przy takiej denicji dugoci cieki, najkrtsza cieka midzy dwoma a wierzchokami nie musi by t, ktra skada si z najmniejszej liczby krawdzi. Wystarczy a rozpatrze graf o trzech wierzchokach v1 , v2 oraz v3 , ktry zawiera krawdzie (v1 , v2 ) o dugoci 1, (v1 , v3 ) o dugoci 3, oraz (v2 , v3 ) o dugoci 1. cieka, jaka zostanie wyznaczona przez algorytm BFS dla pary wierzchokw v1 oraz v3 , bdzie skadaa si z jednej krawdzi (v1 , v3 ) o dugoci 3. Tym czasem istnieje krtsza droga v1 ; v2 ; v3 o dugoci 2. Rozwizanie tego problemu moliwe jest przy uyciu algorytmu Dijkstry. Na pocztku a a dziaania, ustala on odlegoci dla wszystkich wierzchokw w grae na nieskoczono. Wyjtkiem jest wierzchoek startowy, dla ktrego odlego ustalana jest na 0. W kolejnych a krokach wybierany jest wierzchoek v, o ktrym wiadomo, e wyznaczona dla niego odlego ju si nie zmieni (pierwszym takim wierzchokiem jest v), oraz aktualizowane s odlegoci a do wszystkich wierzchokw, do ktrych istnieje krawd z v. Powstaje pytanie dla jakiego wierzchoka od pewnego momentu dziaania algorytmu, poszukiwana odlego nie ulegnie ju zmianie? Algorytm Dijkstry zakada, e jest to wierzchoek o najmniejszej aktualnie wyznaczonej odlegoci spord wierzchokw jeszcze nie przetworzonych. Zaoenie takie sprawia, e algorytm Dijkstry dziaa prawidowo tylko w przypadku grafw z nieujemnymi wagami krawdzi. Algorytm Dijkstry, zrealizowany jest przez funkcj void Graph<V,E>::Dijksta(int), ktrej kod rdowy przedstawiony jest na listingu 2.35. Funkcja ta przyjmuje jako parametr numer wierzchoka startowego s i dla kadego wierzchoka wyznacza odlego od s umieszczan w dodatkowej zmiennej wierzchoka int t. Funkcja wyznacza rwnie numer a wierzchoka - ojca w drzewie najkrtszych cieek umieszcza go w zmiennej int s. Dugoci krawdzi grafu naley umieci w zmiennych int l krawdzi. Zoono czasowa to O(m log(n)) w przypadku szczeglnych grafw istnieje moliwo poprawienia tej zoonoci. Przykadowo, dla grafw gstych, w ktrych liczba krawdzi jest rzdu O(n2 ), istnieje 57

10

1
15 5

35 10

4
2

10 0 15 (b) 20

30

0
15

3
15 20

32

(a)

Rysunek 2.10: (a) Przykadowy graf z zaznaczonymi wagami krawdzi. (b) Wyznaczone przez algorytm Dijkstry drzewo najkrtszych cieek dla rda znajdujcego si w wierzchoku 0. a Wewntrz wierzchokw umieszczone s odlegoci. a a

moliwo uzyskania zoonoci O(m) poprzez zmian sposobu reprezentowania kolejki priorytetowej nieprzetworzonych wierzchokw.
Listing 2.35: Implementacja funkcji void Graph<V,E>::Dijkstra(int) // Operator okrelajcy porzdek liniowy (w kolejnoci rosncych odlegoci od // rda) na wierzchokach grafu 01 struct djcmp { 02 bool operator() (const Ve * a, const Ve * b) const { 03 return (a->t == b->t) ? a < b : a->t < b->t; 04 } 05 }; // Funkcja realizujca algorytm Dijkstry. Dla kadego wierzchoka wyznaczana jest // odlego od rda wyszukiwania s i umieszczana w zmiennej t oraz numer // wierzchoka-ojca w drzewie najkrtszych cieek - umieszczany w zmiennej s. // Dla wierzchokw nieosigalnych ze rda t = INF, s = -1 06 void Dijkstra(int s) { // Kolejka priorytetowa suca do wyznaczania najbliszych wierzchokw 07 set<Ve *, djcmp> k; 08 FOREACH(it, g) it->t = INF, it->s = -1; // Na pocztku wstawiany jest do kolejki wierzchoek rdo 09 g[s].t = 0; 10 g[s].s = -1; 11 k.insert(&g[s]); // Dopki s jeszcze nieprzetworzone wierzchoki... 12 while (!k.empty()) { // Wybierz wierzchoek o najmniejszej odlegoci i usu go z kolejki 13 Ve *y = *(k.begin()); 14 k.erase(k.begin()); // Dla kadej krawdzi, wychodzcej z aktualnie przetwarzanego wierzchoka, // sprbuj zmniejszy odlego do wierzchoka, do ktrego ta krawd prowadzi 15 FOREACH(it, *y) if (g[it->v].t > y->t + it->l) { 16 k.erase(&g[it->v]); 17 g[it->v].t = y->t + it->l; 18 g[it->v].s = y - &g[0]; 19 k.insert(&g[it->v]); 20 }

58

Listing 2.35: (c.d. listingu z poprzedniej strony) 21 } 22 }

Listing 2.36: Wynik wywoania funkcji void Graph<V,E>::Dijkstra(0) dla grafu przedstawionego na rysunku 2.10

Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek

0: 1: 2: 3: 4: 5:

odleglosc odleglosc odleglosc odleglosc odleglosc odleglosc

od od od od od od

zrodla zrodla zrodla zrodla zrodla zrodla

= = = = = =

0, ojciec w drzewie najkrotszych sciezek = -1 10, ojciec w drzewie najkrotszych sciezek = 0 15, ojciec w drzewie najkrotszych sciezek = 0 20, ojciec w drzewie najkrotszych sciezek = 2 30, ojciec w drzewie najkrotszych sciezek = 3 32, ojciec w drzewie najkrotszych sciezek = 4

Listing 2.37: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.36. Peny kod rdowy programu znajduje si w pliku dijkstra str.cpp // Wzbogacenie wierzchokw oraz krawdzi wymagane przez algorytm Dijkstry 01 struct Vs { 02 int t, s; 03 }; 04 struct Ve { 05 int l, rev; 06 }; 07 int main() { 08 int n, m, s, b, e; 09 Ve ed; // Wczytaj liczb wierzchokw, krawdzi oraz wierzchoek startowy 10 cin >> n >> m >> s; // Skonstruuj graf o odpowiednim rozmiarze oraz dodaj do niego krawdzie 11 Graph<Vs, Ve> g(n); 12 REP(x,m) { 13 cin >> b >> e >> ed.l; 14 g.EdgeU(b, e, ed); 15 } // Wykonaj algorytm Dijkstry 16 g.Dijkstra(s); // Wypisz dla wszystkich wierzchokw znalezion odlego od rda // oraz numer wierzchoka, z ktrego prowadzi wyznaczona, najkrtsza cieka 17 REP(x, SIZE(g.g)) cout << "Wierzcholek " << x << 18 ": odleglosc od zrodla = " << g.g[x].t << 19 ", ojciec w drzewie najkrotszych sciezek = " << g.g[x].s << endl; 20 return 0; 21 }

59

Zadanie: Przemytnicy
Pochodzenie: X Olimpiada Informatyczna Rozwizanie: a alchemy.cpp

Bajtocja synie z bogatych z zota, dlatego przez dugie lata kwita sprzeda tego kruszcu do ssiedniego krlestwa, Bitlandii. Niestety, powikszajca si ostatnio dziura budetowa, a a zmusia krla Bitlandii do wprowadzenia zaporowych ce na metale i mineray. Handlarze przekraczajcy granic musz zapaci co w wysokoci 50% wartoci przewoonego adunku. a a Bajtockim kupcom grozi bankructwo. Na szczcie bajtoccy alchemicy opracowali sposoby pozwalajce zamienia pewne mea tale w inne. Pomys kupcw polega na tym, aby z pomoc alchemikw zamienia zoto w a pewien tani metal, a nastpnie, po przewiezieniu go przez granic i zapaceniu niewielkiego ca, znowu otrzymywa z niego zoto. Niestety alchemicy nie znaleli sposobu na zamian dowolnego metalu w dowolny inny. Moe si wic zdarzy, e proces otrzymania danego metalu ze zota musi przebiega wielostopniowo i e na kadym etapie uzyskiwany bdzie inny metal. Alchemicy ka sobie sono paci za swoje usugi i dla kadego znanego sobie proa cesu zamiany metalu A w metal B wyznaczyli cen za przemian 1 kg surowca. Handlarze zastanawiaj si, w jakiej postaci naley przewozi zoto przez granic oraz jaki cig procea a sw alchemicznych naley zastosowa, aby zyski byy moliwie najwiksze. Pom uzdrowi bajtock gospodark! a

Zadanie
Napisz program, ktry: Wczyta tabel cen wszystkich metali, a take ceny przemian oferowanych przez alchemikw, Wyznaczy taki cig metali m0 , m1 , . . . , mk , e: a m0 = mk to zoto, dla kadego i = 1, 2, . . . , k alchemicy potra otrzyma metal mi z metalu mi 1 a oraz, koszt wykonania caego cigu procesw alchemicznych dla 1 kg zota, powikszony a o pacone na granicy co (50% ceny 1 kg najtaszego z metali mi , dla i = 0, 1, . . . , k) jest najmniejszy z moliwych. Zakadamy, e podczas procesw alchemicznych waga metali nie zmienia si. Wypisze koszt wykonania wyznaczonego cigu procesw alchemicznych powikszony o a pacone na granicy co.

Wejcie
W pierwszym wierszu standardowego wejcia znajduje si jedna dodatnia liczba cakowita n oznaczajca liczb rodzajw metali, 1 n 5 000. W wierszu o numerze k+1, dla 1 k n, a znajduje si nieujemna parzysta liczba cakowita pk cena 1 kg metalu oznaczonego numerem k, 0 pk 109 . Przyjmujemy, e zoto ma numer 1. W wierszu o numerze n + 2 znajduje si jedna nieujemna liczba cakowita m rwna liczbie procesw przemiany znanych alchemikom, 0 m 100 000. W kadym z kolejnych m wierszy znajduj si po trzy liczby naa turalne, pooddzielane pojedynczymi odstpami, opisujce kolejne procesy przemiany. Trjka a 60

liczb a, b, c oznacza, e alchemicy potra z metalu o numerze a otrzymywa metal o numerze a b i za zamian 1 kg surowca ka sobie paci c bajtalarw, 1 a a, b n, 0 c 10 000. Uporzdkowana para liczb a i b moe si pojawi w danych co najwyej jeden raz. a

Wyjcie
Twj program powinien pisa na standardowe wyjcie. W pierwszym wierszu powinna zosta wypisana jedna liczba cakowita koszt wykonania wyznaczonego cigu procesw alchea micznych powikszony o pacone na granicy co.

Przykad
4 200 100 40 2 6 1 2 1 3 2 1 3 2 3 4 4 1

Dla nastpujcego wejcia: a

Poprawnym rozwizaniem jest: a


10 5 25 10 5 50 60

Proste acm.uva.es - zadanie 336 acm.uva.es - zadanie 10048 acm.uva.es - zadanie 10099 spoj.sphere.pl - zadanie 50

rednie acm.uva.es - zadanie 429 acm.uva.es - zadanie 10917 spoj.sphere.pl - zadanie 15 spoj.sphere.pl - zadanie 119

wiczenia

Trudne acm.uva.es - zadanie 10171 spoj.sphere.pl - zadanie 25 spoj.sphere.pl - zadanie 145 spoj.sphere.pl - zadanie 391

2.11. Algorytm Bellmana - Forda


Opisany w poprzednim rozdziale algorytm Dijkstry pozwala na Literatura wyznaczanie najkrtszych cieek z zadanego wierzchoka w gra[WDA] - 25.3 e. Niestety, nie dziaa on poprawnie w przypadku pojawienia si [CON] - 3.6 krawdzi o ujemnych wagach, z ktrymi wi si dodatkowe proa a blemy. Na rysunku 2.11.c przedstawiony jest graf zawierajcy cykl a o ujemnej sumie wag krawdzi na nim lecych. W takim przypadku, dla pewnych wierza chokw w grae nie istnieje najkrtsza cieka, gdy dla dowolnej cieki mona wyznaczy krtsz (przechodzc odpowiedni liczb razy wzdu ujemnego cyklu). a a a a W obliczu koniecznoci zmagania si z grafami, zawierajcymi krawdzie o ujemnych a wagach, naley zrezygnowa z algorytmu Dijkstry i sign po inny niestety wolniejszy, a algorytm Bellmana - Forda. Algorytm ten, podobnie jak algorytm Dijkstry, na pocztku a ustala wyznaczon odlego od rda dla wszystkich wierzchokw na nieskoczono (za a wyjtkiem wierzchoka startowego, dla ktrego odlego ta jest ustalana na 0). Nastpnie a algorytm przetwarza wszystkie krawdzie (u, v) grafu, aktualizujc odlegoci wierzchokw a 61

1
10 -5

3
10 2

3 0 -1 (b)

5
2

10 5

4 2
3 -10 8

0
4

10

(a)

(c)

Rysunek 2.11: (a) Przykadowy graf skierowany z zaznaczonymi wagami krawdzi. (b) Wyznaczone
przez algorytm Bellmana - Forda najkrtsze cieki dla rda znajdujcego si w wierza choku 0. Wewntrz wierzchokw umieszczone s odlegoci. (c) Graf zawierajcy cykl a a a 1 ; 3 ; 2 o ujemnej wadze. Funkcja bool Graph<V,E>::BellmanFord(int) zwrci dla tego grafu prawd.

zgodnie z regu: t(v) = min(t(v), t(u) + l(u, v)), gdzie l(u, v) jest dugoci krawdzi, naa a tomiast t(v) aktualn odlegoci wierzchoka v od rda. Dopki proces aktualizacji a a wprowadza jakie zmiany, jest on powtarzany, ale nie wicej ni n razy. Jeli za n razem wartoci t(v) nadal ulegaj zmianom, oznacza to e graf zawiera cykl o ujemnej wadze (w a grafach nie zawierajcych takich cykli kada najkrtsza cieka skada si z co najwyej n 1 a krawdzi). Zoono czasowa tego algorytmu to O(n (n + m)) cay graf jest przetwarzany co najwyej n-krotnie. Funkcja bool Graph<V,E>::BellmanFord(int), ktrej implementacj mona znale na listingu 2.38, jest realizacj wyej opisanej metody. Funkcja ta, w przypadku gdy graf, a dla ktrego zostaa ona wykonana zawiera cykl o ujemnej wadze, zwraca warto prawda. W przypadku, gdy takiego cyklu nie ma, funkcja zwraca fasz, oraz wyznacza dla kadego wierzchoka w grae dwie wartoci int t oraz int s reprezentujce odpowiednio obliczon a a odlego od rda, oraz numer wierzchoka, z ktrego prowadzi najkrtsza cieka. Algorytm wymaga, aby dugoci krawdzi zostay umieszczone w dodatkowych polach int l krawdzi.
Listing 2.38: Implementacja funkcji bool Graph<V,E>::BellmanFord(int) 01 bool BellmanFord(int v) { // Inicjalizacja zmiennych 02 FOREACH(it, g) it->t = INF; 03 g[v].t = 0; 04 g[v].s = -1; 05 int change, cnt = 0; // Dopki przebieg ptli poprawia wyznaczane odlegoci, ale ptla nie zostaa // wykonana wicej ni SIZE(g) razy... 06 do { 07 change = 0; // Dla kadej krawdzi (v,u) zaktualizuj odlego do wierzchoka u 08 REP(i, SIZE(g)) FOREACH(it, g[i]) 09 if (g[i].t + it->l < g[it->v].t) { 10 g[it->v].t = g[i].t + it->l; 11 g[it->v].s = i; 12 change = 1; 13 } 14 } while (change && ++cnt < SIZE(g)); // Jeli ptla wykonaa si SIZE(g) razy, to oznacza, e w grafie istnieje

62

Listing 2.38: (c.d. listingu z poprzedniej strony) // cykl o ujemnej wadze 15 return cnt == SIZE(g); 16 }

Listing 2.39: Dla grafu przedstawionego na rysunku 2.11.a, wartoci wyliczonych przez wywoanie funkcji bool Graph<V,E>::BellmanFord(0) zmiennych bd nastpuj ce a a

Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek

0: 1: 2: 3: 4:

odleglosc odleglosc odleglosc odleglosc odleglosc

od od od od od

zrodla zrodla zrodla zrodla zrodla

= = = = =

0, ojciec w drzewie najkrotszych sciezek = -1 3, ojciec w drzewie najkrotszych sciezek = 0 -2, ojciec w drzewie najkrotszych sciezek = 1 5, ojciec w drzewie najkrotszych sciezek = 1 7, ojciec w drzewie najkrotszych sciezek = 3

Listing 2.40: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.39. Peny kod rdowy programu znajduje si w pliku bellman ford str.cpp // Wzbogacenie struktury wierzchokw oraz krawdzi wymagane przez algorytm // Bellmana-Forda 01 struct Ve { 02 int l; 03 }; 04 struct Vs { 05 int t, s; 06 }; 07 int main() { 08 int n, m, s, b, e; 09 Ve ed; // Wczytaj liczb wierzchokw, krawdzi oraz wierzchoek rdowy 10 cin >> n >> m >> s; // Skonstruuj graf o odpowiedniej wielkoci oraz dodaj do niego wszystkie // krawdzie 11 Graph<Vs, Ve> g(n); 12 REP(x, m) { 13 cin >> b >> e >> ed.l; 14 g.EdgeD(b, e, ed); 15 } // Wykonaj algorytm Bellmana-Forda 16 g.BellmanFord(s); // Wypisz wyznaczony wynik 17 REP(x, SIZE(g.g)) 18 cout << "Wierzcholek " << x << ": odleglosc od zrodla = " << g.g[x]. 19 t << ", ojciec w drzewie najkrotszych sciezek = " << g.g[x].s << endl; 20 return 0; 21 }

63

Proste acm.uva.es - zadanie 104 acm.uva.es - zadanie 558

rednie acm.uva.es - zadanie 10746 acm.uva.es - zadanie 10806

wiczenia

Trudne acm.uva.es - zadanie 10449

2.12. Maksymalny przepyw


Rozpatrywalimy ju przykad modelowania sieci miejskich drg Literatura przy uyciu grafu. Wyobramy sobie teraz, e kada droga czy a [WDA] - 27 ze sob pewne dwa skrzyowania, oraz ma okrelon maksymaln a a a [KDP] - 4.1, 4.2 przepustowo (liczb samochodw, ktre mog ni przejeda w a a [CON] - 4 jednostce czasu). Nasuwa si oczywiste pytanie, jaka jest maksymalna liczba samochodw, ktre mog przejeda z pewnego stara towego skrzyowania do skrzyowania docelowego w jednostce czasu. Innymi sowy, jaka jest maksymalna przepustowo sieci drg pomidzy tymi dwoma skrzyowaniami. Naley na wstpnie zauway, e samochody nie musz porusza si po tej samej ciece niektre a mog podrowa po najkrtszej trasie, inne mog jecha drog okrn. Naszkicowane tu a a a a zagadnienie nazywane jest problemem wyznaczania maksymalnego przepywu w grae. Istniej rne modykacje problemu maksymalnego przepywu oraz rne metody pozwaa lajce na jego wyznaczanie. Mona rozwaa przepywy, ktrych kada krawd ma jednosta kow przepustowo, analizowa maksymalny przepyw, ktrego koszt utrzymania jest mia nimalny pod jakim wzgldem... W obrbie kilku kolejnych rozdziaw przedstawimy rne algorytmy rozwizujce te problemy. a a Dla kadej krawdzi k = (u, v), poprzez ck bdziemy oznaczali jej przepustowo, natomiast przez fk wielko aktualnie przedostajcego si przez ni przepywu. W analizie a a algorytmw przepywowych, wan rol odgrywaj dwa pojcia krawd (nie)nasycona a a oraz cieka poszerzajca: a Denicja 2.12.1 Krawd nasycona k, to taka krawd, ktrej przepustowo ck jest rwna aktualnie przedostajcego si przez ni przepywowi fk (ck = fk ). a a Denicja 2.12.2 Krawd nienasycona k, to taka krawd, ktrej przepustowo ck jest wiksza od aktualnie przedostajcego si przez ni przepywu fk (ck > fk ). a a Denicja 2.12.3 cieka poszerzajca u ; v, to taka cieka, ktrej wszystkie krawdzie a s nienasycone istnieje moliwo przesania wzdu tej cieki dodatkowego przepywu z a wierzchoka u do wierzchoka v. Podstaw, na jakiej bazuje wiele algorytmw wyznaczania maksymalnego przepywu jest a to, e jeli w grae nie istnieje cieka poszerzajca midzy rdem a ujciem, to aktualny a przepyw jest zarazem maksymalnym. Dopki aktualnie wyznaczony przepyw nie jest maksymalny, programy staraj si wyznacza kolejne cieki poszerzajce, wzdu ktrych mona a a przesa dodatkowy przepyw. W zalenoci od sposobu wyszukiwania kolejnych cieek poszerzajcych, otrzymywane s algorytmy o rnych zoonociach czasowych. a a

2.12.1. Maksymalny przepyw metod Dinica a


Algorytm Dinica suy do wyznaczania maksymalnego przepywu w czasie O(n3 ). Pomys jest nastpujcy dopki w grae istnieje cieka poszerzajca midzy rdem a ujciem: a a 64

dokonuje si podziau zbioru wierzchokw grafu na warstwy. W kolejnych warstwach umieszczane s wierzchoki o coraz wikszych odlegociach od wierzchoka rdowego a (odlego midzy wierzchokami u i v rozumiana jest jako liczba krawdzi znajdujcych a si na najkrtszej ciece u ; v). Do warstwy zerowej naley wierzchoek rdowy, do warstwy pierwszej wierzchoki poczone krawdzi ze rdem, . . . , do warstwy k a a ujcie. Podzia na warstwy moe by zrealizowany poprzez przeszukiwanie grafu wszerz w czasie O(n + m). wyznacza si wszystkie cieki poszerzajce midzy rdem a ujciem, ktrych wierza choki nale do kolejnych warstw (0, 1, . . . k). W ten sposb wszystkie wyznaczone w a jednej fazie algorytmu cieki poszerzajce maj tak sam dugo k. Dla kadej kraa a a a wdzi (u, v), przez ktr zosta zwikszony przepyw, zmniejsza si o tyle samo przepyw a w krawdzi (v, u) (w kolejnych fazach moe si okaza, e wyznaczenie maksymalnego przepywu wymaga cofnicia czci aktualnego przepywu, co realizowane jest w algorytmie przez wprowadzenie ujemnego przepywu o przeciwnym zwrocie). Faza wyznaczania cieek poszerzajcych w przedstawionej tu implementacji realizowana jest a poprzez przeszukiwanie grafu w gb w czasie O(n + m). a usuwa si z grafu skierowane krawdzie nasycone, aby nie przetwarza niepotrzebnie krawdzi, ktre nie mog nalee do kolejnych cieek poszerzajcych. a a

Ze wzgldu na konieczno minimalizowania dugoci kodu implementowanych programw podczas zawodw, przedstawiona funkcja int Graph<V,E>::MaxFlow(int, int), ktrej kod rdowy znajduje si na listingu 2.41, nie usuwa z grafu krawdzi nasyconych, co w konsekwencji powoduje, e algorytm ten w przypadkach pesymistycznych dziaa moe asymptotycznie wolniej od oryginalnego algorytmu Dinica w zoonoci O(n4 ). Jednak w przypadku losowych grafw, metoda ta sprawdza si bardzo dobrze. Funkcja int Graph<V,E>::MaxFlow(int, int) przyjmuje jako parametry odpowiednio numer wierzchoka stanowicego rdo oraz ujcie wyznaczanego przepywu, a zwraca wielko tego przepywu. a Dodatkowo, dla kadej krawdzi w grae wyznaczana jest warto int f, ktra rwna jest wielkoci przepywu w tej krawdzi (w przypadku ujemnej wartoci przepywu, jest to przepyw skierowany w przeciwn stron). Do dziaania, funkcja wymaga wzbogacenia struktury wierza chokw o pole int t (jest ono wykorzystywane przez faz przeszukiwania wszerz oraz w gb) a oraz krawdzi o pole int c przepustowoci krawdzi, oraz int f wyznaczona warto przepywu. Grafy, na jakich operuje omawiana funkcja s nieskierowane, jednak istnieje moa liwo ustawiania rnych przepustowoci krawdzi w rne strony przed przystpieniem a do wyznaczania przepywu naley jedynie zmodykowa odpowiednio wartoci pl int c krawdzi.
Listing 2.41: Implementacja funkcji int Graph<V,E>::MaxFlow(int, int) // Zmienna out reprezentuje numer wierzchoka-rda 01 int out; 02 #dene ITER typename vector<Ed>::iterator // Wektor itL zawiera dla kadego wierzchoka wskanik na aktualnie // przetwarzan krawd 03 vector<ITER> itL; 04 VI vis; // Funkcja wykorzystuje czasy odwiedzenia wierzchokw z tablicy vis do // wyznaczania cieek poszerzajcych 05 int FlowDfs(int x, int fl) {

65

Listing 2.41: (c.d. listingu z poprzedniej strony) 06 int r = 0, f; // Jeli aktualny wierzchoek jest ujciem, lub nie mona powikszy przepywu, // to zwr aktualny przepyw 07 if (x == out || !fl) return fl; // Przetwrz kolejne krawdzie wierzchoka w celu znalezienia cieki // poszerzajcej 08 for (ITER & it = itL[x]; it != g[x].end(); ++it) { // Jeli krawd nie jest nasycona i prowadzi midzy kolejnymi warstawmi... 09 if (vis[x] + 1 == vis[it->v] && it->c - it->f) { // Wyznacz warto przepywu, ktry mona przeprowadzi przez przetwarzan // krawd oraz zaktualizuj odpowiednie zmienne 10 it->f += f = FlowDfs(it->v, fl <? it->c - it->f); 11 g[it->v][it->rev].f -= f; 12 r += f; 13 fl -= f; // Jeli nie mona powikszy ju przepywu to przerwij 14 if (!fl) break; 15 } 16 } 17 return r; 18 } // Funkcja wyznacza maksymalny przepyw midzy wierzchokami s oraz f 19 int MaxFlow(int s, int f) { // Inicjalizacja zmiennych 20 int res = 0, n = SIZE(g); 21 vis.resize(n); 22 itL.resize(n); 23 out = f; 24 REP(x, n) FOREACH(it, g[x]) it->f = 0; 25 int q[n], b, e; 26 while (1) { // Ustaw wszystkie wierzchoki jako nieodwiedzone 27 REP(x, n) vis[x] = -1, itL[x] = g[x].begin(); // Wykonaj algorytm BFS zaczynajc ze rda s i analizujc tylko // nienasycone krawdzie 28 for (q[vis[s] = b = e = 0] = s; b <= e; ++b) 29 FOREACH(it, g[q[b]]) if (vis[it->v] == -1 && it->c - it->f > 0) 30 vis[q[++e] = it->v] = vis[q[b]] + 1; // Jeli nie istnieje cieka do ujcia f, to przerwij dziaanie 31 if (vis[f] == -1) break; // Zwiksz aktualny przepyw 32 res += FlowDfs(s, INF); 33 } 34 return res; 35 }

66

10

8 3

2
10

10

8 2

2
3

0
20

5
10

0
5

5
10

(a)

(b)

Rysunek 2.12: (a) Graf nieskierowany o szeciu wierzchokach. Wzdu krawdzi zaznaczone s ich przea
pustowoci. (b) Maksymalny przepyw wyznaczony dla sieci przedstawionej na rysunku (a). Skierowanie krawdzi reprezentuje kierunek przepywu, natomiast warto obok krawdzi reprezentuje wielko przepywu. Jak wida z rysunku, krawdzie (0, 3), (1, 4) oraz (2, 4) s nienasycone ich przepustowo jest wiksza od wartoci przepywu. a

Listing 2.42: Dla grafu przedstawionego na rysunku 2.12, wywoanie funkcji Graph<V,E>::MaxFlow(0, 5) wyznaczy nastpuj c warto maksymalnego przepywu a a

int

Wielkosc calkowitego przeplywu: 15 Wielkosc przeplywu dla kolejnych krawedzi: f(0, 1) = 10 f(0, 3) = 5 f(1, 2) = 7 f(1, 4) = 3 f(2, 4) = 2 f(2, 5) = 5 f(3, 4) = 5 f(4, 5) = 10

Listing 2.43: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.42. Peny kod rdowy programu znajduje si w pliku maxflow.cpp // Wzbogacenie struktury wierzchokw oraz krawdzi o elementy wymagane przez // algorytm Dinica 01 struct Ve { 02 int rev, c, f; 03 }; 04 struct Vs { 05 int t; 06 }; 07 int main() { 08 int n, m, s, f, b, e; // Wczytaj liczb wierzchokw oraz krawdzi w grafie i rdo oraz ujcie // wyznaczanego przepywu 09 cin >> n >> m >> s >> f; // Skonstruuj graf o odpowiedniej wielkoci, a nastpnie dodaj do niego // wszystkie krawdzie 10 Graph<Vs, Ve> g(n); 11 Ve l;

67

Listing 2.43: (c.d. listingu z poprzedniej strony) 12 REP(x, m) { 13 cin >> b >> e >> l.c; 14 g.EdgeU(b, e, l); 15 } // Wypisz wielko przepywu midzy rdem a ujciem 16 cout << "Wielkosc calkowitego przeplywu: " << g.MaxFlow(s, f) << endl; 17 cout << "Wielkosc przeplywu dla kolejnych krawedzi:" << endl; 18 REP(x, SIZE(g.g)) FOREACH(it, g.g[x]) if (it->f > 0) 19 cout << "f(" << x << ", " << it->v << ") = " << it->f << endl; 20 return 0; 21 }

2.12.2. Maksymalny przepyw dla krawdzi jednostkowych


W wielu zadaniach, zwizanych z wyznaczaniem maksymalnego przepywu, pojawiaj si a a rne dodatkowe ograniczenia, ktre niekiedy uatwiaj bd utrudniaj rozwizywany proa a a a blem. Jednym z takich ogranicze moe by zaoenie, e przez kad krawd moe przea pywa co najwyej jednostkowa wielko przepywu. Problem tego typu nazywa bdziemy maksymalnym jednostkowym przepywem (z krawdziami o jednostkowej przepustowoci). Algorytm z poprzedniego rozdziau jak najbardziej nadaje si do rozwizywania tego proa blemu, jednak istnieje nie tylko prostszy, ale rwnie i szybszy algorytm. Przedstawiony w tym rozdziale algorytm, suy do wyznaczania maksymalnego jednostkowego przepywu w czasie O(n8/3 ) dla grafw skierowanych. Podobnie jak w algorytmie Dinica, pomys polega na wyszukiwaniu kolejnych cieek poszerzajcych. Fakt, i kada a krawd na wyznaczanych ciekach poszerzajcych ma tak sam niewykorzystan przea a a a pustowo, pozwala na prosty sposb usuwania krawdzi nasyconych, oraz dodawania ich odpowiednikw o przeciwnym zwrocie. Wyznaczanie cieek poszerzajcych realizowane jest a przez dwie fazy przeszukiwanie BFS, ktre dokonuje podziau wierzchokw na warstwy, a nastpnie przeszukiwanie w gb, na skutek ktrego odwracane s skierowania krawdzi a a nalecych do znalezionych cieki poszerzajcych. W przypadku, gdy w grae nie istnieje a a ju wicej cieek poszerzajcych prowadzcych ze rda do ujcia, algorytm koczy dziaaa a nie, zwracajc jako wynik liczb wyznaczonych dotychczas cieek. Przykad dziaania zosta a przedstawiony na rysunku 2.13. Funkcja int UnitFlow(int, int) z listingu 2.44 realizuje opisany algorytm, przyjmujc jako parametry numery wierzchokw stanowicych odpowiednio a a rdo oraz ujcie wyznaczanego przepywu. Jako wynik zwracana jest wielko maksymalnego, jednostkowego przepywu. Funkcja wymaga wzbogacenia wierzchokw o elementy int t oraz int s. W przypadku korzystania z tej funkcji naley pamita, e struktura grafu ulega modykacji zostaje zmienione skierowanie krawdzi wchodzcych w skad cieek poszerzajcych. a a Funkcja moe by wykorzystywana w przypadku grafw skierowanych, jak i nieskierowanych (w tym drugim przypadku jednak, modykacje wykonane na grae zaburzaj reprezentacj a krawdzi nieskierowanych przy uyciu pl int rev).
Listing 2.44: Implementacja funkcji int Graph<V,E>::UnitFlow(int, int) // Funkcja odwraca skierowanie krawdzi e wychodzcej z wierzchoka v 01 void mvFlow(int v, Ed & e) { 02 int u = e.v;

68

Listing 2.44: (c.d. listingu z poprzedniej strony) 03 g[u].PB(e); 04 g[u].back().v = v; 05 swap(g[v].back(), e); 06 g[v].pop back(); 07 } 08 int UFend; // Funkcja szuka cieki prowadzcej do wierzchoka UFend przy uyciu // przeszukiwania w gb 09 bool UFDfs(int v) { // Jeli wierzchoek jest ujciem, to zostaa znaleziona cieka poszerzajca 10 if (v == UFend) return true; 11 g[v].s = 1; // Dla kadej krawdzi wychodzcej z wierzchoka... 12 FOREACH(it, g[v]) // Jeli prowadzi ona pomidzy kolejnymi warstwami oraz wierzchoek docelowy // nie by jeszcze odwiedzony, to odwied go... 13 if (g[it->v].t == 1 + g[v].t && !g[it->v].s && UFDfs(it->v)) { // W przypadku znalezienia cieki poszerzajcej, zamie skierowanie krawdzi 14 mvFlow(v, *it); 15 return true; 16 } 17 return false; 18 } // Waciwa funkcja wyznaczajca maksymalny przepyw jednostkowy midzy // wierzchokami v1 i v2 19 int UnitFlow(int v1, int v2) { 20 int res = 0; 21 UFend = v2; 22 while (1) { // Wyznacz drzewo przeszukiwania BFS 23 Bfs(v1); 24 if (g[v2].t == -1) break; // Jeli ujcie nie zostao odwiedzone, to nie da si powikszy przepywu 25 FOREACH(it, g) it->s = 0; // Dla kadej krawdzi wychodzcej z wierzchoka rdowego, jeli istnieje // cieka poszerzajca zawierajca t krawd, to powiksz przepyw 26 FOREACH(it, g[v1]) if (UFDfs(it->v)) { 27 res++; 28 mvFlow(v1, *it--); 29 } 30 } 31 return res; 32 }

69

1 0 3

2 5 4 0

2 5 0

2 5

(a)

(b)

(c)

Rysunek 2.13: Przykad wyznaczania maksymalnego jednostkowego przepywu dla grafu z rysunku (a)
pomidzy wierzchokiem numer 0, a wierzchokiem numer 5. (b) Stan skierowania krawdzi grafu po wyznaczeniu pierwszej cieki rozszerzajcej 0 ; 1 ; 2 ; 5. (c) Stan a skierowania krawdzi po wyznaczeniu drugiej cieki poszerzajcej 0 ; 3 ; 4 ; 5. W a grae nie ma ju wicej cieek 0 ; . . . ; 5, zatem wyznaczony przepyw jest maksymalny

Listing 2.45: Warto wyznaczonego maksymalnego przepywu dla grafu z rysunku 2.13.a midzy wierzchokami 0 i 5 przez funkcj Graph<V,E>::UnitFlow(int, int)

Przeplyw miedzy wierzcholkami 4 i 5 wynosi 2

Listing 2.46: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.45. Peny kod rdowy programu znajduje si w pliku unitflow.cpp 01 struct Ve {}; // Wzbogacenie struktury wierzchokw o pole wymagane przez funkcj UnitFlow 02 struct Vs { 03 int t,s; 04 }; 05 int main() { 06 int n, m, s, f, b, e; // Wczytaj liczb wierzchokw i krawdzi w grafie oraz numer wierzchoka // pocztkowego i kocowego 07 cin >> n >> m >> s >> f; // Skonstruuj graf o odpowiednim rozmiarze oraz dodaj do niego wymagane krawdzie 08 Graph<Vs, Ve> g(n); 09 REP(x,m) { 10 cin >> b >> e; 11 g.EdgeD(b, e); 12 } // Wypisz wielko wyznaczonego maksymalnego skojarzenia 13 cout << "Przeplyw miedzy wierzcholkami " << b << " i " << e << 14 " wynosi " << g.UnitFlow(s, f) << endl; 15 return 0; 16 }

2.12.3. Najtaszy, maksymalny przepyw dla krawdzi jednostkowych


Na problem wyznaczania maksymalnego przepywu z poprzedLiteratura niego rozdziau mona narzuci dodatkowe wymaganie. Zamy, [CON] - 4.7 e dla wszystkich krawdzi analizowanej sieci dodane zostay nieujemne koszty. Koszt utrzymania krawdzi jest rwny iloczynowi jej kosztu oraz wielkoci przepywu. Kosztem 70 utrzymania caej sieci jest suma kosztw utrzymania wszystkich krawdzi. Nowo postawiony problem polega na wyznaczeniu w sieci maksymalnego przepywu jednostkowego, ktrego koszt jest minimalny. Algorytm, realizowany przez prezentowan funkcj PII Graph<V,E>::MinCostFlow(int, a int) z listingu 2.47 dziaa w czasie O(n m u), gdzie u jest wielkoci wyznaczanego maka

1
17 3 15 7

30 8

2
7 20

20 5

10

10

(a)

(b)

(c)

Rysunek 2.14: (a) Skierowany graf z kosztami przypisanymi krawdziom. (b) Wyznaczony maksymalny
przepyw o wielkoci 2 i koszcie 109 dla krawdzi o wagach jednostkowych. (c) Maksymalny przepyw o wielkoci 2 i minimalnym koszcie 75.

nia cieek poszerzajcych wykorzystywany jest algorytm Bellmana - Forda. Zauwamy, e a jeli poszukujemy przepywu jednostkowego o minimalnym koszcie, ktrego wielko wynosi 1 (przepyw taki skada si z dokadnie jednej cieki poszerzajcej), to ciek poszerzajc a a a mona wyznaczy przy uyciu algorytmu Dijkstry sumaryczny koszt utrzymania krawdzi na ciece mona potraktowa jako jej dugo. Zatem najkrtsza znaleziona cieka midzy rdem a ujciem jest zarazem najtasza. Po wyznaczeniu pierwszej cieki poszerzajcej, naley zamieni zwroty oraz koszty kraa wdzi do niej nalecych, a nastpnie przystpi do wyszukiwania kolejnych cieek poszea a rzajcych. Ze wzgldu jednak na zamian kosztw krawdzi, w grae pojawi si krawdzie z a a ujemnymi wagami, co powoduje, e algorytm Dijkstry nie moe zosta ju uyty zamiast niego mona jednak skorzysta z algorytmu Bellmana - Forda. Korzystajc z funkcji PII Graph<V,E>::MinCostFlow(int, int) naley pamita o tym, a e dokonuje ona modykacji struktury grafu, poprzez zamiany zwrotu krawdzi oraz zmiany ich kosztw.
Listing 2.47: Implementacja funkcji PII Graph<V,E>::MinCostFlow(int, int) // Funkcja wyznacza wielko oraz koszt maksymalnego, jednostkowego przepywu o // minimalnym koszcie w sieci midzy wierzchokami v1 oraz v2. Na skutek jej // dziaania, graf podlega modyfikacjom. 01 PII MinCostFlow(int v1, int v2) { 02 int n = SIZE(g); 03 PII res = MP(0, 0); 04 vector < typename vector<Ed>::iterator > vit(SIZE(g)); 05 while (1) { // Ustaw aktualne odlegoci dla wszystkich wierzchokw na INF oraz // zaznacz wierzchoek v1 jako rdo wyszukiwania 06 FOREACH(it, g) it->t = INF; 07 g[v1].t = 0; 08 g[v1].s = -1; // Wykonaj co najwyej SIZE(g) faz wyznaczania najkrtszych cieek metod // Bellmana-Forda 09 for (int chg = 1, cnt = 0; chg && ++cnt < SIZE(g);) { 10 chg = 0; 11 REP(i, SIZE(g)) FOREACH(it, g[i]) 12 if (g[i].t + it->l < g[it->v].t) { 13 g[it->v].t = g[i].t + it->l;

71

Listing 2.47: (c.d. listingu z poprzedniej strony) 14 g[it->v].s = i; 15 vit[it->v] = it; 16 chg = 1; 17 } 18 } // Jeli nie wyznaczono cieki midzy v1 a v2, to przerwij 19 if (g[v2].t == INF) break; // Zwiksz wynik 20 res.ST++; 21 res.ND += g[v2].t; // Odwr skierowanie krawdzi na wyznaczonej ciece oraz zmie znaki wag // krawdzi na przeciwne 22 int x = v2; 23 while (x != v1) { 24 int v = g[x].s; 25 swap(*vit[x], g[v].back()); 26 Ed e = g[v].back(); 27 e.l *= -1; 28 e.v = v; 29 g[x].PB(e); 30 g[x = v].pop back(); 31 } 32 } // Zwr wynik 33 return res; 34 }

Listing 2.48: Wielko oraz koszt maksymalnego przepywu o minimalnym koszcie wyznaczonego przez funkcj PII Graph<V,E>::MinCostFlow(int, int) wykonanej dla grafu z rysunku 2.14, rda 0 i ujcia 7.

Wyznaczanie przeplywu z 0 do 7 Wielkosc przeplywu: 2, koszt przeplywu: 70

Listing 2.49: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.49. Peny kod rdowy programu znajduje si w pliku mincostflow.cpp // Wzbogacenie dla wierzchokw oraz krawdzi wymagane przez funkcj // MinCostFlow 01 struct Vs { 02 int t, s; 03 }; 04 struct Ve { 05 int l; 06 }; 07 int main() { 08 int n, m, s, f, b, e; // Wczytaj liczb wierzchokw i krawdzi oraz rdo i ujcie wyznaczanego

72

Listing 2.49: (c.d. listingu z poprzedniej strony) // przepywu 09 cin >> n >> m >> s >> f; // Skonstruuj graf o odpowiedniej liczbie wierzchokw oraz dodaj do niego // wymagane krawdzie 10 Graph<Vs, Ve> g(n); 11 Ve l; 12 REP(x, m) { 13 cin >> b >> e >> l.l; 14 g.EdgeD(b, e, l); 15 } // Wyznacz maksymalny, najtaszy przepyw oraz wypisz wyznaczony wynik 16 cout << "Wyznaczanie przeplywu z " << s << " do " << f << endl; 17 PII res = g.MinCostFlow(s, f); 18 cout << "Wielkosc przeplywu: " << res.ST << 19 ", koszt przeplywu: " << res.ND << endl; 20 return 0; 21 }

Proste acm.uva.es - zadanie 10092 acm.uva.es - zadanie 10330 acm.sgu.ru - zadanie 194

rednie acm.uva.es - zadanie 10480 acm.sgu.ru - zadanie 176

wiczenia

Trudne acm.uva.es - zadanie 10546 acm.sgu.ru - zadanie 212

2.13. Maksymalne skojarzenie w grae dwudzielnym


Problem znajdowania maksymalnego skojarzenia w grae nieLiteratura skierowanym G = (V, E) polega na wyznaczeniu najwikszego ze [WDA] - 27.3 wzgldu na liczno zbioru krawdzi V V , takiego, aby adne [KDP] - 4.3 dwie krawdzie nie miay wsplnego koca. W przypadku, gdy wszystkie wierzchoki grafu s kocem pewnej krawdzi ze skoa jarzenia (wielko skojarzenia jest wtedy rwna n ), skojarzenie takie nazywamy doskonaym. 2 Na rysunku 2.15 przedstawione s przykadowe grafy wraz z wyznaczonymi maksymalnymi a skojarzeniami. Znalezienie takiego zbioru krawdzi w dowolnym grae nie jest rzecz atw (wprawdzie a a istnieje algorytm dziaajcy w czasie O(m n), jednak jego implementacja jest nie trya wialna). Na szczcie istniej dosy ciekawe klasy grafw, dla ktrych rozwizanie okazuje a a si istotnie prostsze. Jedn z takich klas s grafy dwudzielne w ich przypadku, problem a a maksymalnego skojarzenia mona sprowadzi do wyznaczania maksymalnego przepywu jednostkowego. W kolejnych podrozdziaach przedstawimy prosty algorytm sucy do wyznaczania maka symalnego skojarzenia w grafach dwudzielnych w czasie O(n(n+m)), a nastpnie pokaemy implementacj algorytmu Hopcrofta - Karpa, dziaajcego w czasie O((n + m) n). Zanim a jednak to nastpi, przybliymy pojcie grafw dwudzielnych. a 73

0 1 2 6

5 4 3 1

0 6 2 7

5 4 3

(a)

(b)

Rysunek 2.15: (a) Przykadowy graf o maksymalnym skojarzeniu wielkoci 3. Krawdzie nalece do a
przykadowego maksymalnego skojarzenia zostay pogrubione. (b) Graf o doskonaym skojarzeniu wielkoci 4.

0 3 6

1 4 7

2 5 8

(a)

(b)

Rysunek 2.16: (a) Graf dwudzielny o dziewiciu wierzchokach i dwunastu krawdziach. (b) Podzia
zbioru wierzchokw grafu z rysunku (a) na dwa rozczne zbiory {0, 1, 2, 3, 8} oraz a {4, 5, 6, 7}, taki, e midzy dowolnymi dwoma wierzchokami w tym samym zbiorze nie istnieje adna krawd.

2.13.1. Dwudzielno grafu


Graf G = (V, E) nazywamy dwudzielnym, gdy zbir jego wierzchokw V mona podzieli na dwa rozczne zbiory V1 oraz V2 , V1 V2 = V , w taki sposb, aby wszystkie krawdzie a grafu (u, v) E prowadziy midzy wierzchokami z rnych zbiorw (u V1 , v V2 lub v V1 , u V2 ). Podzia taki jest moliwy dla kadego grafu nie zawierajcego cyklu o niepaa rzystej dugoci. Wiele algorytmw skonstruowanych z myl o grafach dwudzielnych, przed a przystpieniem do rozwizania waciwego zagadnienia, musz wyznaczy podzia (V1 , V2 ). a a a Na listingu 2.50 przedstawiona jest implementacja funkcji bool Graph<V,E>::BiPart(vector <bool>&), suca do wyznaczania tego podziau. Funkcja przyjmuje jako parametr weka tor zmiennych logicznych, a zwraca prawd, jeli graf, dla ktrego zostaa wykonana jest dwudzielny. Wtedy rwnie przekazany przez referencj wektor wypeniany jest wartociami logicznymi k-ta z tych wartoci reprezentuje przynaleno k-tego wierzchoka grafu do zbioru V1 . Dziaanie funkcji opiera si na algorytmie sortowania topologicznego grafu posortowane wierzchoki s przetwarzane w kolejnoci wyznaczonego porzdku i zachannie a a umieszczane w jednym ze zbiorw V1 lub V2 . Zoono czasowa algorytmu wynosi zatem O(n + m).
Listing 2.50: Implementacja funkcji bool Graph<V,E>::BiPart(vector<bool>&) 01 bool BiPart(vector<char> &v) { // Inicjalizacja zmiennych 02 v.resize(SIZE(g), 2);

74

Listing 2.50: (c.d. listingu z poprzedniej strony) // Wykonaj sortowanie topologiczne grafu. W grafie mog wystpowa cykle, ale // nie stanowi to problemu 03 VI r = TopoSortV(); // Dla kadego wierzchoka w grafie 04 FOREACH(x, r) { // Jeli wierzchoek nie by jeszcze odwiedzony, to przydzielany jest on do // pierwszego zbioru wierzchokw 05 if (v[*x] == 2) v[*x] = 0; // Przetwrz kadego ssiada aktualnego wierzchoka - jeli nie by on jeszcze // odwiedzony, to przydziel go do innego zbioru ni wierzchoek x, a jeli // by odwiedzony i jest w tym samym zbiorze co x, to graf nie jest // dwudzielny 06 FOREACH(it, g[*x]) if (v[it->v] == 2) v[it->v] = 1 - v[*x]; 07 else if (v[it->v] == v[*x]) return 0; 08 } 09 return 1; 10 }

Przedstawiona funkcja bool Graph<V,E>::BiPart(vector<bool>&) bdzie wykorzystywana przez algorytmy wyznaczania maksymalnego skojarzenia w grafach dwudzielnych. Oryginalne algorytmy, pochodzce z biblioteczki algorytmicznej, nie wymagaj implementacji a a tej funkcji, gdy wychodz one z zaoenia, e wierzchoki w grae o numerach 0 . . . n 1 a 2 nale do zbioru V1 , podczas gdy wierzchoki n . . . n 1 do zbioru V2 . Takie zaoenie jest a 2 wygodne w przypadku wikszoci zada, gdy podczas konstrukcji grafu zazwyczaj z gry wiadomo, ktre wierzchoki nale do ktrego zbioru. Odpowiednia numeracja wierzchokw a pozwala na skrcenie kodu implementowanego zadania.
Listing 2.51: Podzia wierzchokw grafu z rysunku 2.16.a wyznaczony przez funkcj bool Graph<V,E>::BiPart(vector<bool>&l)

Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek Wierzcholek

0 1 2 3 4 5 6 7 8

nalezy nalezy nalezy nalezy nalezy nalezy nalezy nalezy nalezy

do do do do do do do do do

V0 V0 V0 V0 V1 V1 V1 V1 V0

Listing 2.52: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.52. Peny kod rdowy programu znajduje si w pliku bipart str.cpp 01 struct Ve { 02 int rev; 03 }; // Wzbogacenie struktury wierzchokw wymagane przez funkcj Bipart 04 struct Vs {

75

Listing 2.52: (c.d. listingu z poprzedniej strony) 05 int t, s; 06 }; 07 int main() { 08 int n, m, b, e; 09 cin >> n >> m; // Skonstruuj graf o n wierzchokach i m krawdziach 10 Graph<Vs, Ve> g(n); 11 REP(x, m) { 12 cin >> b >> e; 13 g.EdgeU(b, e); 14 } 15 vector<char> l; // Wypisz wynik wyznaczony przez funkjc Bipart 16 if (g.BiPart(l)) { 17 REP(x, 18 SIZE(l)) cout << "Wierzcholek " << x << " nalezy do V" << ((int) l[x]) 19 << endl; 20 } else cout << "Graf nie jest dwudzielny" << endl; 21 return 0; 22 }

2.13.2. Maksymalne skojarzenie w grae dwudzielnym w czasie O(n(n+m))


Bardzo wanym pojciem, w kontekcie maksymalnego skojarzenia, jest cieka naprzemienna, ktra peni podobn rol co cieka poszerzajca w przypadku maksymalnego przepywu. a a Niech dany bdzie graf G = (V, E) oraz zbir E E, stanowicy skojarzenie grafu G (niekoa niecznie maksymalne). ciek naprzemienn S nazywamy ciek postaci s1 ; s2 ; . . . ; a a sk , tak e s1 oraz sk nie s wierzchokami skojarzonymi, oraz krawdzie (s2l , s2l+1 ) a a V , l {1, 2, . . . , k 1}. Przykad cieki naprzemiennej o dugoci 5 przedstawiony jest na 2 rysunku 2.17.d. Pierwsz, prezentowan funkcj suc do wyznaczania maksymalnego skojarzenia w a a a a a grae dwudzielnym, jest bool Graph<V,E>::BipMatching(), ktrej kod rdowy przedstawiony jest na listingu 2.53. W przypadku, gdy graf nie jest dwudzielny, funkcja ta zwraca fasz. W przeciwnym razie, dla kadego wierzchoka v w grae wyznaczana jest warto zmiennej int m numer wierzchoka skojarzonego z v. W przypadku wierzchokw nieskojarzonych, wyznaczona warto to 1. Funkcja wymaga wzbogacenia struktury wierzchokw o dodatkowe pola int m oraz int t. Metoda dziaania algorytmu polega na znajdowaniu kolejnych cieek naprzemiennych. W kadym pojedynczym przebiegu algorytmu, przy uyciu przeszukiwania grafu w gb, wyznaa czana jest cieka naprzemienna, a nastpnie zamieniana jest przynaleno do skojarzenia wszystkich krawdzi lecych na tej ciece. Pojedyncze przeszukiwanie zwiksza wielko a wyznaczonego skojarzenia o 1. Poniewa wielko maksymalnego skojarzenia w grae jest ograniczona z gry przez n , zatem zoono algorytmu to O(n (n + m)). 2
Listing 2.53: Implementacja funkcji bool Graph<V,E>::BipMatching() // Funkcja realizujca wyszukiwanie cieki naprzemiennej w grafie przy uyciu // przeszukiwania w gb

76

Listing 2.53: (c.d. listingu z poprzedniej strony) 01 bool MDfs(int x) { // Jeli wierzchoek nie zosta jeszcze odwiedzony... 02 if (!g[x].t) { 03 g[x].t = 1; // Dla kadej krawdzi wychodzcej z wierzchoka, jeli koniec krawdzi nie // jest skojarzony lub istnieje moliwo wyznaczenia rekurencyjnie cieki // naprzemiennej... 04 FOREACH(it, g[x]) if (g[it->v].m == -1 || MDfs(g[it->v].m)) { // Skojarz wierzchoki wzdu aktualnie przetwarzanej krawdzi 05 g[g[it->v].m = x].m = it->v; 06 return true; 07 } 08 } 09 return false; 10 } // Funkcja wyznacza maksymalne skojarzenie w grafie dwudzielnym. Umieszcza ona w // polu m kadego wierzchoka numer wierzchoka z nim skojarzonego (lub -1 // dla wierzchokw nieskojarzonych). 11 bool BipMatching() { 12 vector<char> l; // Jeli graf nie jest dwudzielny, zwr fasz 13 if (!BiPart(l)) return 0; // Inicjalizacja zmiennych 14 int n = SIZE(g), p = 1; 15 FOREACH(it, g) it->m = -1; // Dopki istnieje cieka naprzemienna... 16 while (p) { 17 p = 0; 18 FOREACH(it, g) it->t = 0; // Wykonaj przeszukiwanie w gb w celu znalezienia cieki naprzemiennej 19 REP(i, n) if (l[i] && g[i].m == -1) p |= MDfs(i); 20 } 21 return 1; 22 }

Listing 2.54: Maksymalne skojarzenie wyznaczone Graph<V,E>::BipMatching() dla grafu z rysunku 2.17.a

przez

funkcj

bool

Wierzcholek 0 skojarzono z 3 Wierzcholek 1 skojarzono z 2 Wierzcholek 4 skojarzono z 5

Listing 2.55: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.54. Peny kod rdowy programu znajduje si w pliku bipmatch.cpp 01 struct Ve { 02 int rev; 03 };

77

Listing 2.55: (c.d. listingu z poprzedniej strony) // Wzbogacenie wierzchokw grafu wymagane przez funkcj BipMatching 04 struct Vs { 05 int m, t; 06 }; 07 int main() { 08 int n, m, s, b, e; // Skonstruuj odpowiedni graf na podstawie danych wejciowych 09 cin >> n >> m; 10 Graph<Vs, Ve> g(n); 11 REP(x, m) { 12 cin >> b >> e; 13 g.EdgeU(b, e); 14 } // Wyznacz maksymalne skojarzenie oraz wypisz wynik 15 if (!g.BipMatching()) cout << "Graf nie jest dwudzielny" << endl; 16 else REP(x, SIZE(g.g)) if (g.g[x].m > x) 17 cout << "Wierzcholek " << x << " skojarzono z " << g.g[x].m << endl; 18 return 0; 19 }

2.13.3. Maksymalne skojarzenie w grae dwudzielnym w czasie O((n + m) n)


Przedstawiona w tym rozdziale implementacja algorytmu Hopcrofta - Karpa wyznacza maksymalne skojarzenie w grae dwudzielnym, sprowadzajc problem do wyznaczania maka symalnego, jednostkowego przepywu w grae. Niech zbiory V1 oraz V2 stanowi podzia a zbioru wierzchokw grafu G = (V, E), dla ktrego wyznaczane jest maksymalne skojarzenie. Nasz algorytm modykuje graf G, dodajc do niego dwa specjalne wierzchoki rdo a oraz ujcie. Wierzchoek-rdo czony jest ze wszystkimi wierzchokami ze zbioru V1 , naa tomiast wszystkie wierzchoki ze zbioru V2 czone s z ujciem. Przykadowa konstrukcja a a tego typu zostaa przedstawiona na rysunku 2.18. Po wyznaczeniu maksymalnego przepywu midzy rdem a ujciem, krawdzie oryginalnego grafu G, przez ktre realizowany jest jednostkowy przepyw, nale do wyznaczonego maksymalnego skojarzenia. Do wyznaczea nia maksymalnego przepywu mona wykorzysta dowolny algorytm, jednak stosujc algoa rytm do wyznaczania jednostkowego maksymalnego przepywu realizowanego przez funkcj int Graph<V,E>::UnitFlow(int,int), czas wykonania caego algorytmu sprowadza si do O((n + m) n). Opisany algorytm realizowany jest przez funkcj VI Graph<V,E>::Hopcroft() z listingu 2.56. Funkcja ta zwraca jako wynik wektor liczb cakowitych o dugoci n. Dla kadego wierzchoka v odpowiadajca mu liczba reprezentuje numer wierzchoka, z ktrym v zosta skoa jarzony. W przypadku, gdy wierzchoek v nie zosta skojarzony, odpowiadajc liczb jest a a a 1.
Listing 2.56: Implementacja funkcji VI Graph<V,E>::Hopcroft() // UWAGA: Na skutek dziaania algorytmu graf ulega modyfikacji 01 VI Hopcroft() { // Inicjalizacja zmiennych

78

Listing 2.56: (c.d. listingu z poprzedniej strony) 02 int n = SIZE(g); 03 VI res(n, -1); 04 vector<char> l; // Jeli graf nie jest dwudzielny, to algorytm zwraca puste skojarzenie 05 if (!BiPart(l)) return res; // Do grafu dodawane s dwa wierzchoki, jeden z nich jest czony ze wszystkimi // wierzchokami z pierwszego zbioru wyznaczonego przez funkcj BiPart, // natomiast drugi z wierzchokami z drugiego zbioru. 06 g.resize(n + 2); 07 REP(i, n) if (!l[i]) EdgeD(n, i); 08 else EdgeD(i, n + 1); // Wyznaczany jest przepyw jednostkowy w zmodyfikowanym grafie 09 UnitFlow(n, n + 1); // Skojarzenie jest rekonstruowane na podstawie wyniku wyliczonego przez // algorytm wyznaczajcy przepyw jednostkowy 10 REP(i, n) if (l[i] && g[i][0].v != n + 1) 11 res[res[g[i][0].v] = i] = g[i][0].v; 12 return res; 13 }

Listing 2.57: Wynik wygenerowany przez funkcj VI Graph<V,E>::Hopcroft() dla grafu z rysunku 2.18.a

Wierzcholek 0 skojarzono z 3 Wierzcholek 1 skojarzono z 2 Wierzcholek 4 skojarzono z 5

Listing 2.58: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.58. Peny kod rdowy programu znajduje si w pliku hopcroft str.cpp 01 struct Ve { }; // Wzbogacenie wierzchokw grafu wymagane przez funkjc Hopcroft 02 struct Vs { 03 int t, s; 04 }; 05 int main() { 06 int n, m, b, e; // Skonstruj graf o odpowiednim rozmiarze oraz dodaj do niego wymagane krawdzie 07 cin >> n >> m; 08 Graph<Vs, Ve> g(n); 09 REP(x, m) { 10 cin >> b >> e; 11 g.EdgeD(b, e); 12 } // Wykonaj algorytm Hopcrofta oraz wypisz wyznaczony wynik 13 VI res = g.Hopcroft(); 14 REP(x, SIZE(res)) if (res[x] > x) 15 cout << "Wierzcholek " << x << " skojarzono z " << res[x] << endl;

79

Listing 2.58: (c.d. listingu z poprzedniej strony) 16 return 0; 17 }

Zadanie: Skoczki
Pochodzenie: Olimpiada Informatyczna krajw Batyckich 2001 Rozwizanie: a knights.cpp

Dana jest szachownica o rozmiarach n n, z ktrej usunito pewn liczb pl. Zadanie polega a na wyznaczeniu maksymalnej liczby skoczkw, ktre mona ustawi na planszy w ten sposb, aby adne dwa z nich nie biy si.

Zadanie
Napisz program, ktry: wczyta ze standardowego wejcia opis szachownicy z usunitymi polami, wyznaczy maksymaln liczb skoczkw, ktre mona ustawi na szachownicy w ten a sposb, eby adne dwa nie biy si, wypisze wynik na standardowe wyjcie

3 2 1 1 3 3 5

Wejcie
Pierwszy wiersz wejcia zawiera dwie liczby cakowite n oraz m (1 n 200, 0 m n2 ), n jest rozmiarem szachownicy, a m to liczba usunitych pl. Kady z kolejnych m wierszy zawiera dwie liczby x oraz y (1 x, y n) wsprzdne usunitego pola. Wsprzdne grnego - lewego pola szachownicy to (1, 1), natomiast dolnego - prawego to (n, n). Usunite pola nie powtarzaj si na licie. a

Wyjcie
W pierwszym i jedynym wierszu wyjcia naley wypisa liczb maksymalna liczba skoczkw, ktrych da si umieci na planszy, tak, aby adne dwa si nie biy.

Przykad
Dla nastpujcego wejcia: a

x x S
Poprawnym rozwizaniem jest: a

x x x

x x x

80

0 2

1 3

0 2

1 3

0 2

1 3

0 2

1 3 5

5 4 6 (a) (b) 4

5 4 6 (c)

5 4 6 (d)

Rysunek 2.17: Kolejne fazy wyznaczania maksymalnego skojarzenia w grae dwudzielnym. (a) graf
wejciowy. (b) wyznaczono ciek naprzemienn 4 ; 3. (c) wyznaczono ciek naa przemienn 2 ; 6. (d) Wyznaczono ciek naprzemienn 0 ; 3 ; 4 ; 6 ; 2 ; 1. a a Krawdzie (2, 6) i (3, 4) usunite zostay ze skojarzenia

0 2

1 3 7 5

0 2

1 3 8 5

4 6 (a)

4 6 (b)

Rysunek 2.18: (a) Graf, dla ktrego wyznaczane jest maksymalne skojarzenie. (b) sprowadzenie problemu maksymalnego skojarzenia w grae dwudzielnym do wyznaczania maksymalnego, jednostkowego przepywu.

81

0
10

23 15 8 12 10 20

1 1 3 3 5

0 23 10

0
10 15 8

15 -INF 2 8 10
12

3
10

5 -INF 12

20

(a)

(b)

(c)

Rysunek 2.19: (a) Graf dwudzielny z wagami na krawdziach. (b) Zmodykowana macierz ssiedztwa, a
wyznaczona dla grafu z rysunku (a). (c) Graf nie zawierajcy doskonaego skojarzenia a

2.13.4. Najdrosze skojarzenie w grae dwudzielnym


W rozdziale tym przedstawiona jest funkcja VI Hungarian(int Literatura **w, int n) realizujca metod wgiersk wyznaczania najdroa a [CON] - 5.8 szego maksymalnego skojarzenia w grafach dwudzielnych o rwnolicznych zbiorach wierzchokw V1 oraz V2 . Dziaanie algorytmu zasadniczo polega na sprowadzeniu problemu wyznaczania maksymalnego najdroszego skojarzenia w grae do zagadnienia programowania liniowego, ktre zostao omwione w rozdziale dotyczcym algebry liniowej. Ze wzgldu na zoono algorytmu, nie przedstawimy a analizy dziaania tego algorytmu, a jedynie jego implementacj. Zainteresowanych zachcamy do zapoznania si z literatur. a Funkcja ta rni si istotnie od dotychczasowo omawianych. Jako parametr, przyjmuje ona bowiem macierz ssiedztwa grafu dwudzielnego oraz jej rozmiar. Kolumny takiej maciea rzy reprezentuj wierzchoki grafu nalece do zbioru V1 , a wiersze wierzchoki ze zbioru a a V2 . Warto pola w i-tej kolumnie i j-tym wierszu reprezentuje wag krawdzi midzy i-tym wierzchokiem ze zbioru V1 i j-tym wierzchokiem ze zbioru V2 Algorytm zakada e graf, dla ktrego wyznaczane jest skojarzenie, jest peny. Takie zaoenie atwo jest speni wystarczy dla kadej nieistniejcej krawdzi w grae wstawi do macierzy odpowiadajc jej wag a a a a a rwn -INF. Na rysunku 2.19 przedstawiony jest przykadowy graf wraz z odpowiadajc mu a zmodykowan macierz ssiedztwa. a a a Przy uyciu metody wgierskiej mona wyznacza nie tylko najdrosze skojarzenie w grae, ale rwnie i najtasze jedyne, co naley zrobi, to zmieni na przeciwne znaki wag wszystkich krawdzi w grae. a Funkcja VI Hungarian(int **w, int n) zwraca wektor liczb reprezentujcy skojarzenie wierzchokw ze zbioru V1 i V2 (k-ty element tego wektora jest numerem wierzchoka ze zbioru V2 , skojarzonego z k-tym wierzchokiem ze zbioru V1 ). Implementacja tej funkcji przedstawiona jest na listingu 2.59. Jej zoono czasowa to O(n3 ).
Listing 2.59: Implementacja funkcji VI Hungarian() 01 VI Hungarian(int **w, int n) { 02 int lx[n], ly[n], skojx[n], skojy[n]; 03 int markx[n], marky[n], sl[n], par[n], q[n]; 04 REP(i, n) { 05 skojx[i] = skojy[i] = -1; 06 ly[i] = 0; 07 lx[i] = *max element(w[i], w[i] + n); 08 }

82

Listing 2.59: (c.d. listingu z poprzedniej strony) 09 REP(k, n) { 10 int v = -1, qb = 0, qe = 0; 11 REP(i, n) { 12 marky[i] = markx[i] = 0; 13 sl[i] = -1; 14 if (skojx[i] == -1) q[qe++] = i; 15 } 16 while (v == -1) { 17 while (qb < qe) { 18 int i = q[qb++]; 19 markx[i] = 1; 20 REP(j, n) 21 if (!marky[j] && (sl[j] == -1 || sl[j] > lx[i] + ly[j] - w[i][j])) { 22 if ((sl[j] = lx[par[j] = i] + ly[j] - w[i][j]) == 0) { 23 marky[j] = 1; 24 if (skojy[j] != -1) q[qe++] = skojy[j]; 25 else { 26 v = j; 27 goto end; 28 } 29 } 30 } 31 } 32 int x = -1; 33 REP(i, n) if (!marky[i] && (x == -1 || sl[i] < x)) x = sl[i]; 34 REP(i, n) { 35 if (markx[i]) lx[i] -= x; 36 if (marky[i]) ly[i] += x; 37 else if ((sl[i] -= x) == 0) { 38 marky[i] = 1; 39 if (skojy[i] != -1) q[qe++] = skojy[i]; 40 else v = i; 41 } 42 } 43 } 44 end: 45 while (v != -1) { 46 int y = skojx[par[v]]; 47 skojx[par[v]] = v; 48 skojy[v] = par[v]; 49 v = y; 50 } 51 } 52 return VI(skojx, skojx + n); 53 }

W wielu zadaniach dane wejciowe s tak przedstawione, e w bardzo prosty sposb mona a 83

skonstruowa macierz przekazywan do funkcji VI Graph<V,E>::Hungarian(int **w, int a n) jako parametr. Nie zawsze jednak okazuje si to prostym zadaniem. Sytuacja taka wystpuje przede wszystkim w zadaniach, w ktrych najpierw naley dokona wstpnego przetworzenia danych, a dopiero potem mona przystpi do wyznaczania najdroszego, maksymala nego skojarzenia. W takich przypadkach pomocna okaza si moe funkcja VI Graph<V,E> ::HungarianG(), ktra wywouje VI Graph<V,E>::Hungarian(int **w, int n) dla pocza tkowego grafu. W kolejnym kroku nastpuje konwersja rezultatu do postaci zgodnej z formatem wyniku wyznaczanym przez algorytm Hopcrofta. Dziaanie funkcji VI Graph<V,E>:: HungarianG() skada si z wyznaczenia podziau zbioru wierzchokw grafu na zbiory V1 oraz V2 , konstrukcji zmodykowanej macierzy ssiedztwa, wywoania waciwej funkcji wya znaczajcej maksymalne skojarzenie, oraz konwersji wyznaczonego wyniku. Funkcja ta doa datkowo usuwa sabo metody wgierskiej wymuszenia rwnolicznoci zbiorw V1 oraz V2 . Jeli w grae zbiory te nie s rwne, to tworzone s specjalne wierzchoki - atrapy. a a
Listing 2.60: Implementacja funkcji VI Graph<V,E>::HungarianG() 01 VI HungarianG() { 02 vector<char> l; 03 VI re(SIZE(g), -1); // Jeli graf nie jest dwudzielny, to zwr puste skojarzenie 04 if (!BiPart(l)) return re; 05 int gr[SIZE(g)], rel[2][SIZE(g)], n = 0, m = 0; // Przypisz wierzchokom z oryginalnego grafu odpowiedniki w macierzy ssiedztwa 06 REP(x, SIZE(g)) rel[l[x]][gr[x] = (l[x] ? n++ : m++)] = x; // Skonstruuj macierz ssiedztwa 07 int *w[n >? = m]; 08 REP(i, n) { 09 w[i] = new int[n]; 10 REP(j, n) w[i][j] = -INF; 11 } // Dodaj do macierzy wagi krawdzi z grafu 12 REP(x, SIZE(g)) FOREACH(it, g[x]) 13 w[gr[x] <? gr[it->v]][gr[x] >? gr[it->v]] >? = it->c; // Wykonaj algorytm wgierski 14 VI res = Hungarian(w, n); // Zrekonstruuj wynik, uywajc 15 REP(x, SIZE(res)) if (w[x][res[x]] != -INF) { 16 re[rel[0][x]] = rel[1][res[x]]; 17 re[rel[1][res[x]]] = rel[0][x]; 18 } 19 REP(i, n) delete[]w[i]; 20 return re; 21 }

Listing 2.61: Wynik wygenerowany przez funkcj VI Graph<V,E>::HungarianG() dla grafu z rysunku 2.19.c

Wierzcholek 1 skojarzono z 2 Wierzcholek 3 skojarzono z 4

84

Listing 2.62: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 2.62. Peny kod rdowy programu znajduje si w pliku hungarian.cpp // Wzbogacenie wierzchokw oraz krawdzi wymagane przez algorytm wyznaczania // najlejszego maksymalnego skojarzenia 01 struct Vs { 02 int t, s; 03 }; 04 struct Ve { 05 int c, rev; 06 }; 07 int main() { 08 int n, m, s, b, e; // Konstruowanie grafu zgodnie z zadanym opisem 09 cin >> n >> m; 10 Graph<Vs, Ve> g(n); 11 Ve l; 12 REP(x, m) { 13 cin >> b >> e >> l.c; 14 g.EdgeU(b, e, l); 15 } // Wyznaczenie maksymalnego skojarzenia przy uyciu metody wgierskiej, oraz // wypisanie wyznaczonego wyniku 16 VI res = g.HungarianG(); 17 REP(x, SIZE(res)) if (res[x] > x) 18 cout << "Wierzcholek " << x << " skojarzono z " << res[x] << endl; 19 return 0; 20 }

Proste acm.uva.es - zadanie 259 acm.uva.es - zadanie 10080 acm.uva.es - zadanie 10349 acm.uva.es - zadanie 10888 spoj.sphere.pl - zadanie 286 spoj.sphere.pl - zadanie 373 acm.sgu.ru - zadanie 190

rednie acm.uva.es - zadanie 10746 acm.uva.es - zadanie 10804 spoj.sphere.pl - zadanie 203 spoj.sphere.pl - zadanie 660 acm.sgu.ru - zadanie 210 acm.sgu.ru - zadanie 242

wiczenia

Trudne acm.uva.es - zadanie 10615 spoj.sphere.pl - zadanie 377 spoj.sphere.pl - zadanie 412 acm.sgu.ru - zadanie 218 acm.sgu.ru - zadanie 234

85

Rozdzia 3

Geometria obliczeniowa na paszczynie


W tym rozdziale przedstawione s najczciej wykorzystywane a Literatura w zadaniach algorytmy zwizane z geometri obliczeniow. Porua a a [WDA] - 35 szone zostan midzy innymi takie zagadnienia, jak wyznaczanie a [ASD] - 8 punktw przecicia, liczenie powierzchni wielokta, wyznaczanie a wypukej otoczki, czy sortowanie ktowe zbioru punktw. a Podobnie jak w przypadku grafw, algorytmy geometryczne rwnie wykorzystuj wspln a a struktur do reprezentacji obiektw geometrycznych jako elementarny, bdzie nim oczywicie punkt. Wszystkie inne obiekty geometryczne bd reprezentowane przy jego uyciu: a odcinek para punktw stanowicych odpowiednio pocztek i koniec odcinka, a a prosta para rnych punktw nalecych do prostej, a okrg i koo punkt stanowicy rodek okrgu/koa oraz liczba okrelajca jego proa a a mie, wielokt wektor punktw bdcych wierzchokami wielokta w kolejnoci wystpoa a a wania na obwodzie.

Na wstpie musimy wprowadzi pewn konwencj oznaczeniow dla poszczeglnych obieka a tw. Punkty bdziemy oznacza maymi literami alfabetu angielskiego: a, b, . . .. Wsprzdne punktu bd przedstawiane w postaci (x, y), gdzie x jest odcit, natomiast y rzdn a a a punktu. Inne obiekty geometryczne oznacza bdziemy wielkimi literami alfabetu z kontekstu bdzie zawsze wynikao, o jakiego rodzaju obiekt chodzi. Deniujc odcinek wyznaa czony przez dwa punkty a i b bdziemy pisali a b. Prost przechodzc przez dwa rne a a a punkty a i b reprezentujemy jako a b. Zarwno w przypadku prostej, jak i odcinka istotna jest kolejno podawania punktw je wyznaczajcych. W wielu miejscach powstaje potrzeba a okrelania pozycji punktu wzgldem prostej moe on lee na prostej lub po jednej z jej stron. Przez stron lew bdziemy rozumieli t ppaszczyzn, ktra zawiera punkty lece a a po lewej stronie wektora a b. Wielokty reprezentowane bd jako lista wierzchokw podawanych w kolejnoci wysta a powania na obwodzie, natomiast okrgi oraz koa jako para (rodek, promie), przykadowo: (a, 10). Zadania geometryczne, oprcz problemw algorytmicznych zwizanych z ich a rozwizaniem, kryj w sobie dodatkow trudno bdy zaokrgle. Choby podczas wya a a a znaczania punktw przecicia dwch prostych, nie jestemy w stanie w oglnym przypadku 87

poda dokadnych wsprzdnych wyznaczanego punktu przecicia przy uyciu reprezentacji liczb uywanych przez komputery. Podczas rozwizywania zada, kolejno pojawiajce si a a bdy zaokrgle, w przypadku le zaimplementowanego programu, mog kumulowa si i w a a konsekwencji silnie zaburza wynik. W przypadku wielu konkursowych zada, na szczcie istnieje moliwo takiego rozwizaa nia, e wszystkie wykonywane obliczenia s cakowitoliczbowe (jedn ze stosowanych metod a a jest odpowiednie przeskalowywanie wsprzdnych). W zalenoci od rodzaju wykonywanych oblicze, struktura punktu, do reprezentacji swoich wsprzdnych, powinna uywa typu int lub long long - w przypadku oblicze cakowitoliczbowych, lub oat, double, a niekiedy nawet (w zadaniach wymagajcych wysokiej dokadnoci) long double - przy obliczeniach a zmiennoprzecinkowych. W oryginalnej biblioteczce algorytmicznej znajduj si dwa typy POINTT oraz POINTR, a ktre s wykorzystywane odpowiednio do reprezentacji wsprzdnych punkw oraz do przea chowywania wynikw dziaania niektrych algorytmw (takich jak pole wielokta). W przya padku zada, operujcych na zmiennych cakowitoliczbowych, denicje POINTT oraz POINTR a dobierane s nastpujco: a a typedef POINTT int; typedef POINTR LL;

w przypadku zada wymagajcych oblicze zmiennoprzecinkowych, denicje te zostaj oda a powiednio zmodykowane, przykadowo moe to by: typedef POINTT oat; typedef POINTR long double;

Ze wzgldu na edukacyjny ksztat ksiki, konwencja ta zostaa zmieniona (co niestety wpya no negatywnie na dugo implementacji algorytmw). Wprowadzone zostay dwa niezalene typy POINT oraz POINTD. Pierwszy z nich suy do reprezentowania punktw o wsprzdnych cakowitoliczbowych, drugi natomiast zmiennoprzecinkowych. Implementacja struktury POINT przedstawiona jest na listingu 3.1, natomiast listing 3.2 prezentuje implementacj struktury POINTD.
Listing 3.1: Implementacja struktury POINT // Struktura reprezentujca punkt o wsprzdnych cakowitoliczbowych 01 struct POINT { 02 int x, y; // Konstruktor punktu pozwalajcy na skrcenie zapisu wielu funkcji // wykorzystujcych punkty - w szczeglnoci operacje wstawiania punktw do // struktur danych 03 POINT(int x = 0, int y = 0) : x(x), y(y) { } // Operator sprawdzajcy, czy dwa punkty s sobie rwne. 04 bool operator ==(POINT & a) { 05 return a.x == x && a.y == y; 06 } 07 };

88

Listing 3.1: (c.d. listingu z poprzedniej strony) // Operator uywany przez przykadowe programy do wypisywania struktury punktu 08 ostream & operator<<(ostream & a, POINT & p) { 09 a << "(" << p.x << ", " << p.y << ")"; 10 return a; 11 }

Listing 3.2: Implementacja struktury POINTD // Struktura reprezentujca punkt o wsprzdnych rzeczywistych 01 struct POINTD { 02 double x, y; // Konstruktor punktu 03 POINTD(double wx = 0, double wy = 0) : x(wx), y(wy) { } // Konstruktor POINTD z typu POINT - jest on potrzebny w celu wykonywania // automatycznej konwersji midzy POINT a POINTD. 04 POINTD(const POINT & p) : x(p.x), y(p.y) { } // Operator sprawdzajcy, czy dwa punkty s sobie rwne. Ze wzgldu na // reprezentowanie wsprzdnych punktw przy uyciu zmiennych double, // operator porwnuje wsprzdne punktw z pewn tolerancj 05 bool operator ==(POINTD & a) { 06 return IsZero(a.x - x) && IsZero(a.y - y); 07 } 08 }; // Operator uywany przez przykadowe programy do wypisywania struktury punktu 09 ostream & operator<<(ostream & a, POINTD & p) { 10 a << "(" << p.x << ", " << p.y << ")"; 11 return a; 12 }

W zadaniach geometrycznych, pojawiaj si czsto problemy zwizane z zaokrglaniem liczb. a a a W przypadku porwnywania wartoci dwch liczb typu double, ktre teoretycznie powinny by sobie rwne, moe okaza si, e ze wzgldu na powstae bdy zaokrgle nie jest to a prawd. Tego typu problemy powoduj, e podczas porwnywania wartoci liczb zmiennoa a przecinkowych, naley dokonywa tego z dodatkow tolerancj. W tym celu, wprowadzone a a zostay denicje staej EPS, oraz funkcji bool IsZero(double) przedstawione na listingach 3.3 i 3.4.
Listing 3.3: Staa const double EPS // Staa EPS jest uywana w wielu algorytmach geometrycznych do porwnywania // wartoci bliskich zera 1 const double EPS = 10e-9;

Listing 3.4: Implementacja funkcji inline bool IsZero(double) // Funkcja sprawdza, czy podana liczba jest dostatecznie bliska 0 1 inline bool IsZero(double x) { 2 return x >= -EPS && x <= EPS;

89

Listing 3.4: (c.d. listingu z poprzedniej strony) 3}

Algorytmy geometryczne operujce na zbiorach punktw, bardzo czsto musz sprawdza a a pooenie poszczeglnych punktw wzgldem siebie. Oglne pytanie, przy uyciu ktrego mona odpowiedzie na wiele innych, brzmi: po ktrej stronie prostej b c ley punkt a? Potrac odpowiada na to pytanie, moemy rwnie odpowiedzie na pytanie typu: czy a dwa odcinki a b i c d przecinaj si. Aby to stwierdzi, wystarczy sprawdzi, czy a punkty a i b le po przeciwnych stronach prostej c d, oraz czy punkty c i d le po a a przeciwnych stronach prostej a b. W celu udzielenia odpowiedzi na pierwotne pytanie po ktrej stronie prostej b c, ley punkt a, mona skorzysta z wasnoci iloczynu wektorowego. Jak wiadomo, iloczyn wektorowy A B ma warto 0, jeli wektory A i B s a do siebie rwnolege, jego warto jest wiksza od 0, jeli kt skierowany midzy wektorem a A i B znajduje si w przedziale (0, 180) stopni, a mniejszy od zera, jeli jest wikszy od 180 stopni. W algorytmach geometrycznych iloczyn wektorowy bdzie odgrywa du rol, zatem a makro Det przedstawione na listingu 3.5 suy bdzie do wyznaczania wartoci iloczynu wektorowego pomidzy wektorami a b i a c. Wsprzdne punktw wyznaczajcych a wektory a b i a c musz by cakowitoliczbowe. a
Listing 3.5: Implementacja makra Det // Makro wyznacza warto iloczynu wektorowego (a -> b)*(a -> c) 1 #dene Det(a,b,c) (LL(b.x-a.x)*LL(c.y-a.y)-LL(b.y-a.y)*(c.x-a.x))

Oprcz wyznaczania wartoci iloczynu wektorowego, czst operacj podczas analizowania a a zbioru punktw jest ich sortowanie. Moliwe s rne sposoby okrelania porzdku liniowego a a na zbiorze punktw jednym z nich moe by choby omawiany w jednym z podrozdziaw porzdek ktowy. Najczciej stosowanymi s jednak porzdki po wsprzdnych (x, y) a a a a oraz (y, x). Operatorami wykorzystywanymi przez funkcj sort z biblioteki STL do okrelenia tych dwch porzdkw s bool OrdXY(POINT*, POINT*) oraz bool OrdYX(POINT*, a a POINT*). Ich implementacja przedstawiona jest na listingach 3.6 i 3.7.
Listing 3.6: Implementacja operatora bool OrdXY(POINT*, POINT*) // Operator okrelajcy liniowy porzdek na zbiorze punktw po wsprzdnych // (x, y) 1 bool OrdXY(POINT * a, POINT * b) { 2 return a->x == b->x ? a->y < b->y : a->x < b->x; 3}

Listing 3.7: Implementacja operatora bool OrdYX(POINT*, POINT*) // Operator okrelajcy liniowy porzdek na zbiorze punktw po wsprzdnych // (y, x) 1 bool OrdYX(POINT * a, POINT * b) { 2 return a->y == b->y ? a->x < b->x : a->y < b->y; 3}

90

Y (7, 6) (-3, 4) (3, 2) (-5,-1) X (5, -2)

(-3, -4) (-5, -8)

Rysunek 3.1: Zbir siedmiu punktw na paszczynie oraz prosta a b o rwnaniu y = x1 wyznaczona
przez punkty a = (3, 4) i b = (7, 6). Punkty (3, 4) oraz (5, 1) le po lewej stronie a prostej a b, natomiast punkty (5, 2) oraz (5, 8) po prawej

3.1. Odlego punktu od prostej


Czsto wykonywan operacj w geometrii obliczeniowej, jest liczenie odlegoci punktu od a a prostej. Rozpatrujc prost A o rwnaniu ax + by + c = 0, oraz punkt b o wsprzdnych a a (x0 , y0 ), odlego d punktu b od prostej A mona wyznaczy na podstawie dobrze znanego wzoru: |ax0 + by0 + c| d= a2 + b2 Stosujc ten wzr, funkcja double PointLineDist(POINTD, POINTD, POINTD), ktrej ima plementacja przedstawiona jest na listingu 3.8, wyznacza odlego punktu okrelonego przez trzeci parametr funkcji, od prostej wyznaczonej przez punkty podane jako dwa pierwsze parametry.
Listing 3.8: Implementacja funkcji double PointLineDist(POINTD, POINTD, POINTD) // Funkcja wyznacza odlego punktu p od prostej (p1 -> p2) 1 double PointLineDist(POINTD p1, POINTD p2, POINTD p) { 2 double A = p2.y - p1.y, B = p2.x - p1.x; 3 return abs(A * (p1.x - p.x) + B * (p.y - p1.y)) / sqrt(A * A + B * B); 4}

Listing 3.9: Odlegoci poszczeglnych punktw z rysunku 3.1 od prostej y = x 1, wyznaczone przy uyciu funkcji double PointLineDist(POINT&, POINT&, POINT&)

Odleglosc Odleglosc Odleglosc Odleglosc Odleglosc

punktu punktu punktu punktu punktu

(-5, -8) od prostej ((-3, -4),(7, 6)) wynosi 1.41421 (5, -2) od prostej ((-3, -4),(7, 6)) wynosi 4.24264 (3, 2) od prostej ((-3, -4),(7, 6)) wynosi 0 (-3, 4) od prostej ((-3, -4),(7, 6)) wynosi 5.65685 (-5, -1) od prostej ((-3, -4),(7, 6)) wynosi 3.53553

91

Listing 3.10: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.9. Peny kod rdowy programu znajduje si w pliku pointdist.cpp 1 int main() { 2 POINT l1, l2, p; // Wczytaj pozycje punkw wyznaczajcych prost 3 cin >> l1.x >> l1.y >> l2.x >> l2.y; // Dla wszystkich punktw wyznacz odlego od prostej 4 while(cin >> p.x >> p.y) 5 cout << "Odleglosc punktu " << p << " od prostej (" << 6 l1 << "," << l2 << ") wynosi " << PointLineDist(l1, l2, p) << endl; 7 return 0; 8}

Proste acm.uva.es - zadanie 10263 acm.uva.es - zadanie 10310

rednie acm.uva.es - zadanie 10709 acm.uva.es - zadanie 10011

wiczenia

Trudne acm.uva.es - zadanie 10762

3.2. Powierzchnia wielokta a


Wyznaczanie powierzchni wieloktw wypukych w porwnaniu z dowolnymi wieloktami a a wydaje si istotnie prostsze. Jak przedstawiono na rysunku 3.2.a, wielokt wypuky mona a podzieli na trjkty, ktrych sumaryczna powierzchnia jest rwna powierzchni caego wiea lokta. W celu wyznaczenia powierzchni trjkta mona skorzysta z iloczynu wektoroa a wego pole powierzchni trjkta wyznaczonego przez trzy punkty a, b i c jest rwne a 1 a a a 2 |(a b) (a c)|. Rozpatrujc wielokty nie bdce wypukymi, podzia taki nie zachowuje podanej wasnoci. Okazuje si jednak, e istnieje moliwo zaadoptowania tego a samego pomysu. Wprawdzie (jak pokazuje rysunek 3.2.b), niektre trjkty nie musz naa a lee w caoci do wielokta, jak rwnie niektre trjkty mog si czciowo pokrywa, to a a a 1 warto sumy oznaczonych powierzchni wszystkich trjktw ( 2 (A B)) jest rwna oznaczoa nemu polu wielokta. Wasno ta wynika z faktu, e kady punkt nie nalecy do wntrza a a wielokta, naley do takiej samej liczby trjktw zorientowanych zgodnie, co i przeciwnie z a a paszczyzn dziki temu powierzchnie wszystkich takich liczonych wielokrotnie pl znosz a a si. Posta wzoru na pole dowolnego wielokta wyznaczonego przez punkty (x1 , y1 ), (x2 , y2 ), a . . ., (xn , yn ) jest nastpujca: a 1 |x1 y2 x2 y1 + x2 y3 x3 y2 + . . . + xn1 yn xn yn1 + xn y1 x1 yn | 2 Przedstawiona na listingu 3.11 funkcja double PolygonArea(vector<POINT>&) przyjmuje jako parametr list punktw wyznaczajcych wielokt, a zwraca pole jego powierzchni. a a
Listing 3.11: Implementacja funkcji double PolygonArea(vector<POINT>&) // Funkcja liczy pole powierzchni wielokta, ktrego wierzchoki wyznaczone s // przez wektor p

92

Y (2, 6) (-3, 4)

Y (8, 7) (-7, 5) (-3, 6)

(2, 6)

(8, 7)

(3, 2) X (-6, -2) (5, -2) (-6, -2) (5, -2) X

Rysunek 3.2: (a) Wielokt wypuky wyznaczony przez zbir piciu punktw. Przerywane odcinki rea
prezentuj podzia tego wielokta na rozczne trjkty. (b) Wielokt wyznaczony przez a a a a a siedem punktw.

Listing 3.11: (c.d. listingu z poprzedniej strony) 1 double PolygonArea(vector<POINT> &p) { 2 double area = 0; 3 int s = SIZE(p); 4 REP(x, s) area += (p[x].x + p[(x + 1) % s].x) * (p[x].y - p[(x + 1) % s].y); 5 return abs(area) / 2; 6}

Listing 3.12: Pole powierzchni wielok ta z rysunku 3.2.b wyznaczony przez funkcj double a PolygonArea(vector<POINT>&)

Pole powierzchni wielokata: 94

Listing 3.13: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.13. Peny kod rdowy programu znajduje si w pliku polygonarea.cpp 01 int main() { 02 int n; 03 vector<POINT> l; 04 POINT p; // Wczytaj liczb wierzchokw wielokta 05 cin >> n; // Dodaj wszystkie wierzchoki wielokta do wektora punktw 06 REP(x, n) { 07 cin >> p.x >> p.y; 08 l.PB(p); 09 } // Wyznacz pole powierzchni wielokta 10 cout << "Pole powierzchni wielokata: " << PolygonArea(l) << endl; 11 return 0; 12 }

93

Proste acm.uva.es - zadanie 10088 spoj.sphere.pl - zadanie 55

rednie acm.uva.es - zadanie 10002 acm.sgu.ru - zadanie 209

wiczenia

Trudne acm.uva.es - zadanie 10456 acm.uva.es - zadanie 10907

3.3. Przynaleno punktu


Rozwizanie wielu zada wymaga sprawdzania, czy pewne punkty a Literatura nale do rnych obiektw geometrycznych, takich jak odcinki, a [WDA] - 35.1 proste, wielokty czy koa. W aktualnym rozdziale przedstawione a [ASD] - 8.2 zostan metody umoliwiajce sprawdzanie tych przynalenoci. a a Analiz problemu rozpoczniemy od bardzo prostego przykadu sprawdzania przynalenoci punktu do prostokta o bokach rwnolegych do osi ukadu wsprzda nych. W celu sprawdzenia, czy punkt a = (x, y) naley do wntrza prostokta wyznaczonego a przez dwa jego przeciwlege wierzchoki b = (x1 , y1 ) oraz c = (x2 , y2 ), wystarczy sprawdzi, czy wsprzdna x punktu ley pomidzy wsprzdnymi x wierzchokw prostokta, oraz czy a podobna zaleno jest zachowana przez wsprzdne y. cile nierwnoci, ktre musz a zosta spenione s nastpujce: a a min(x1 , x2 ) < x < max(x1 , x2 ), min(y1 , y2 ) < y < max(y1 , y2 ) W przypadku sprawdzania, czy punkt naley do wntrza prostokta, lub ley na jego obwoa dzie, naley zamieni ostre nierwnoci z poprzednich warunkw na nieostre: min(x1 , x2 ) x max(x1 , x2 ), min(y1 , y2 ) y max(y1 , y2 )

Do werykowania tych dwch rodzajw przynalenoci su makra PointInsideRect oraz a PointInsideRect. Oba makra przyjmuj jako parametry dwa punkty wyznaczajce proa a stokt oraz punkt, dla ktrego badana jest przynaleno. W przypadku, gdy punkt naley a do prostokta, makra przyjmuj warto prawda. Makra te zostay przedstawione na listina a gach 3.14 oraz 3.15.
Listing 3.14: Implementacja makra PointInsideRect 1 #dene PointInsideRect(p1,p2,p3) ((p1.x <? p2.x) < p3.x && \ 2 (p1.y <? p2.y) < p3.y && (p1.x >? p2.x) > p3.x && (p1.y >? p2.y) > p3.y)

Listing 3.15: Implementacja makra PointInRect 1 #dene PointInRect(p1,p2,p3) ((p1.x <? p2.x) <= p3.x && \ 2 (p1.y <? p2.y) <= p3.y && (p1.x >? p2.x) >= p3.x && (p1.y >? p2.y) >= p3.y)

Analogicznymi makrami s PointInsideSegment oraz PointInSegment. Ich zadaniem jest a odpowiednio sprawdzanie, czy punkt podany jako trzeci parametr ley wewntrz / na odcinku a wyznaczonym przez punkty stanowice pierwszy oraz drugi parametr makr. a Implementacja tych dwch makr, wykorzystuje zarwno poprzednie makra do sprawdzania przynalenoci punktu do prostokta, jak rwnie iloczyn wektorowy. Najpierw jest a 94

Y (-8, 6) (-7, 4) (-8, 2) (-3, 0)

(4, 8) (7, 7)

(3, -4) (-2, -6) (-4, -8)

Rysunek 3.3: Przykadowy zbir punktw prezentujcy rne przypadki przynalenoci punktw do a
obiektw geometrycznych odcinka, prostokta i koa a

sprawdzane, czy testowany punkt ley na prostej wyznaczonej przez odcinek. Jeli tak, to musi on jeszcze nalee do prostokta wyznaczonego przez koce odcinka. Implementacja a tych makr przedstawiona jest na listingach 3.16 i 3.17.
Listing 3.16: Implementacja makra PointInsideSegment 1 #dene PointInsideSegment(p1,p2,l) (Det(p1,p2,l)==0 && PointInsideRect(p1,p2,l))

Listing 3.17: Implementacja makra PointInSegment 1 #dene PointInSegment(p1,p2,l) (Det(p1,p2,l)==0 && PointInRect(p1,p2,l))

Ostatnimi z serii prostych i krtkich makr, s PointInsideCircle oraz PointInCircle. a Su one odpowiednio do sprawdzania, czy punkt okrelony przez trzeci parametr makra a naley do wntrza / wntrza lub obwodu koa o rodku w punkcie okrelonym przez pierwszy parametr makra, oraz promieniu zdeniowanym przez drugi parametr. Implementacje tych makr zostay umieszczone odpowiednio na listingach 3.18 i 3.19.
Listing 3.18: Implementacja makra PointInsideCircle 1 #dene PointInsideCircle(c,r,p) (sqr(c.x-p.x)+sqr(c.y-p.y) < sqr(r))

Listing 3.19: Implementacja makra PointInCircle 1 #dene PointInCircle(c,r,p) (sqr(c.x-p.x)+sqr(c.y-p.y) <= sqr(r))

Listing 3.20: Wynik dziaania makr su cych do sprawdzania przynalenoci punktw do rnych a obiektw geometrycznych na przykadzie odcinka o kocach w punktach (2, 6) i (8, 6), okrgu o rodku w punkcie (3, 2) i promieniu 5, oraz prostok ta wyznaczonego przez punkty (4, 8) i a (7, 7) (patrz rysunek 3.3).

(-8, 2) (-7, 4)

Rectangle Inside In 0 0 0 0

Segment Inside In 0 0 1 1 95

Circle Inside In 0 1 1 1

Listing 3.20: (c.d. listingu z poprzedniej strony)

(-8, 6) (-3, 0) (3, -4) (4, 8)

0 1 1 0

0 1 1 0

0 0 0 0

1 0 0 0

0 1 0 0

0 1 0 0

Listing 3.21: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.20. Peny kod rdowy programu znajduje si w pliku pointin inside.cpp 01 int main() { 02 POINT r1,r2,c,s1,s2,p; 03 int r; // Wczytaj wsprzdne wierzchokw wyznaczajcych prostokt 04 cin >> r1.x >> r1.y >> r2.x >> r2.y; // Wczytaj wsprzdne punktw wyznaczajcych odcinek 05 cin >> s1.x >> s1.y >> s2.x >> s2.y; // Wczytaj rodek oraz promie okrgu 06 cin >> c.x >> c.y >> r; 07 cout << "\t\t\t Rectangle\t\t Segment\t\t Circle" << endl; 08 cout << "\t\t\tInside\t In\t Inside\t In\t Inside\t In" << endl; // Dla wszystkich punktw wyznacz wynik dziaania poszczeglnych makr 09 while(cin >> p.x >> p.y) { 10 cout << p << "\t\t" << PointInsideRect(r1, r2, p) << "\t\t" << 11 PointInRect(r1, r2, p) << "\t\t" << 12 PointInsideSegment(s1, s2, p) << "\t\t" << 13 PointInSegment(s1, s2, p) << "\t\t" << 14 PointInsideCircle(c, r, p) << "\t\t" << 15 PointInCircle(c, r, p) << endl; 16 } 17 return 0; 18 }

Bardziej interesujcym zagadnieniem przynalenoci punktu, jest sprawdzanie przynalenoci a do wielokta. Zajmiemy si tym problemem, przy zaoeniu, e wszystkie punkty stanowice a a wierzchoki wielokta maj cakowitoliczbowe wsprzdne. W celu rozwizania tego proa a a blemu, istotn obserwacj jest to, e dowolna pprosta o pocztku w punkcie nalecym do a a a a wntrza wielokta przecina jego obwd nieparzyst liczb razy. a a Sytuacja odwrotna wystpuje w przypadku punktw nie nalecych do wielokta wtedy a a liczba punktw przecicia jest parzysta. Zatem w celu sprawdzenia przynalenoci punktu do wielokta, mona sprawdzi, z iloma bokami wielokta przecina si dowolna pprosta o a a pocztku w tym punkcie. Wybr jakiejkolwiek prostej niesie ze sob jednak wiele problemw. a a Jak przedstawiono na rysunku 3.4, prosta taka moe przecina obwd wielokta na rne a sposoby, co wie si z dodatkowymi przypadkami do rozpatrzenia, znacznie komplikujcymi a a implementacj. W przypadku przedstawionym na rysunku 3.4.a, punkt przecicia pprostej z obwodem wielokta powinien by liczony podwjnie, podczas gdy sytuacja z rysunku 3.4.b a pojedynczo. Wida, e odpowiedni wybr pprostej jest bardzo wany najlepiej byoby wybra 96

(a)

(b)

(c)

Rysunek 3.4: Rne przypadki przecinania obwodu wielokta przez pprost. a a

j w ten sposb, aby nie przechodzia przez aden wierzchoek. Okazuje si, e wyboru taa kiego mona dokona w bardzo prosty sposb dla testowanego punktu (x, y), pprosta przechodzca przez punkt (, y + 1), nie przecina adnego wierzchoka wielokta, co wynika a a bezporednio z faktu, i wszystkie punkty maj cakowitoliczbowe wsprzdne. a Implementacja algorytmu wykorzystujcego ten fakt zostaa zrealizowana w funkcjach a bool PointInsidePol(vector<POINT>, POINT) oraz bool PointInPol(vector<POINT>, POINT) przedstawionych na listingach 3.22 i 3.23 przyjmuj one jako parametry odpoa wiednio wektor wierzchokw wielokta, oraz punkt, dla ktrego przeprowadzany jest test. a Pierwsza z tych funkcji zwraca prawd, jeli punkt naley do wntrza wielokta, natomiast a druga zwraca prawd, jeli punkt naley do wntrza lub obwodu wielokta. Czas ich dziaania a jest liniowy dla kadego boku wielokta funkcja sprawdzaj, czy przecina on pprost o a a a pocztku w testowanym punkcie. a
Listing 3.22: Implementacja funkcji bool PointInsidePol(vector<POINT>, POINT) 1 bool PointInsidePol(vector<POINT> &l, POINT p) { 2 int v = 0, s = SIZE(l); 3 POINT d(INF, p.y + 1); // Jeli punkt ley na jednym z bokw wielokta, to nie naley do wntrza // wielokta 4 REP(x, s) if (PointInSegment(l[x], l[(x + 1) % s], p)) return false; // Wyznacz liczb przeci obwodu wielokta z pprost (p -> d) 5 REP(x, s) v += SegmentCross(p, d, l[x], l[(x + 1) % s]); // Jeli pprosta przecina obwd nieparzyst liczb razy, to punkt naley do // wielokta 6 return v & 1; 7}

Listing 3.23: Implementacja funkcji bool PointInPol(vector<POINT>, POINT) 1 bool PointInPol(vector<POINT> &l, POINT p) { 2 int v = 0, s = SIZE(l); 3 POINT d(INF, p.y + 1); // Jeli punkt ley na jednym z bokw wielokta, to zwr prawd 4 REP(x, s) if (PointInSegment(l[x], l[(x + 1) % s], p)) return true; // Wyznacz liczb przeci obwodu wielokta z pprost (p -> d) 5 REP(x, s) v += SegmentCross(p, d, l[x], l[(x + 1) % s]); // Jeli pprosta przecina obwd nieparzyst liczb razy, to punkt naley do // wielokta 6 return v & 1; 7}

97

W przypadku rozpatrywania przynalenoci punktu do wielokta wypukego, wczeniej a omwione funkcje jak najbardziej dziaaj poprawnie, jednak nie s optymalne. W takich a a przypadkach, istnieje moliwo sprawdzenia przynalenoci punktu w czasie logarytmicznym ze wzgldu na liczb wierzchokw wielokta. a Zasada dziaania takiego algorytmu opiera si na metodzie wyszukiwania binarnego na pocztku algorytm wybiera sobie punkt nalecy do wielokta wypukego (moe to by a a a przykadowo jego wierzchoek). Nastpnie wntrze wielokta dzielone jest na trjkty (tak a a jak przedstawiono to na rysunku 3.2.a) przy uyciu rodziny pprostych p1 , p2 . . .. Jeli punkt naley do wielokta, to musi znajdowa si w jednym z powstaych trjktw. Przy uyciu a a iloczynu wektorowego, za pomoc ktrego sprawdzane jest pooenie punktu wzgldem koa lejnie wybieranych prostych, przeprowadzane jest wyszukiwanie binarne, w celu wyznaczenia pary ssiednich pprostych pk i pk+1 , pomidzy ktrymi ley testowany punkt. Jeli znajduje a si on w trjkcie wyznaczonym przez pproste pk , pk+1 oraz odpowiedni bok wielokta, to a a naley on rwnie do wielokta. Realizacja tej metody jest przedstawiona na listingach 3.24 a oraz 3.25 w postaci funkcji bool PointInsideConvexPol(vector <POINT>&, POINT) oraz a bool PointInConvexPol(vector <POINT>&, POINT). Przyjmuj one jako parametry list wierzchokw wielokta wypukego, oraz punkt, dla ktrego przeprowadzany jest test. a
Listing 3.24: Implementacja funkcji bool PointInsideConvexPol(vector<POINT>&, POINT) 01 bool PointInsideConvexPol(vector<POINT> &l, POINT p) { 02 int a = 1, b = SIZE(l) - 1, c; // Jeli odcinek (l[0] -> l[a]) ley na prawo od odcinka (l[0] -> l[b]) to // nastpuje zamiana 03 if (Det(l[0], l[a], l[b]) > 0) swap(a, b); // Jeli punkt p nie ley po prawej stronie prostej (l[0] -> l[a]) lub po // lewej stronie (l[0] -> l[b]) to nie naley do wielokta 04 if (Det(l[0], l[a], p) >= 0 || Det(l[0], l[b], p) <= 0) return false; // Wyszukiwanie binarne wycinka paszczyzny zawierajcego punkt p 05 while (abs(a - b) > 1) { 06 c = (a + b) / 2; 07 if (Det(l[0], l[c], p) > 0) b = c; 08 else a = c; 09 } // Jeli punkt p ley w trjkcie (l[0],l[a],l[b]), to naley do wielokta 10 return Det(l[a], l[b], p) < 0; 11 }

Listing 3.25: Implementacja funkcji bool PointInConvexPol(vector<POINT>&, POINT) 01 bool PointInConvexPol(vector<POINT> &l, POINT p) { 02 int a = 1, b = SIZE(l) - 1, c; // Jeli odcinek (l[0] -> l[a]) ley na prawo od odcinka (l[0] -> l[b]) to // nastpuje zamiana 03 if (Det(l[0], l[a], l[b]) > 0) swap(a, b); // Jeli punkt p ley po lewej stronie prostej (l[0] -> l[a]) lub po // prawej stronie (l[0] -> l[b]) to nie naley do wielokta 04 if (Det(l[0], l[a], p) > 0 || Det(l[0], l[b], p) < 0) return false; // Wyszukiwanie binarne wycinka paszczyzny zawierajcego punkt p 05 while (abs(a - b) > 1) {

98

Y (-7, 5)

(1, 8)

(7, 4) (-3, 4) (6, 1) X (5, -2) (-3, -4) (3, -4)

Rysunek 3.5: Przykadowy wielokt wypuky oraz zbir trzech punktw (3, 4), (6, 1) oraz (3, 4) przeda
stawiajce rne przypadki przynalenoci punktu do wielokta. a a

Listing 3.25: (c.d. listingu z poprzedniej strony) 06 c = (a + b) / 2; 07 if (Det(l[0], l[c], p) > 0) b = c; 08 else a = c; 09 } // Jeli punkt p ley w trjkcie (l[0],l[a],l[b]), to naley do wielokta 10 return Det(l[a], l[b], p) <= 0; 11 }

Listing 3.26: Przykad wykorzystania funkcji do sprawdzania przynalenoci punktw do wielok ta a na podstawie zbioru punktw z rysunku 3.5

Polygon (-3, 4) (1, 8) (6, 1) (3, -4) Inside 1 0 0 0 In 1 1 1 0 Inside 1 0 0 0

Convex Polygon In 1 1 1 0

Listing 3.27: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.26. Peny kod rdowy programu znajduje si w pliku pointincpol.cpp 01 int main() { 02 vector<POINT> pol; 03 POINT p; 04 int n; // Wczytaj liczb wierzchokw wielokta 05 cin >> n; // Wczytaj kolejne wierzchoki wielokta oraz dodaj je do wektora 06 REP(x, n) { 07 cin >> p.x >> p.y; 08 pol.PB(p); 09 }

99

Listing 3.27: (c.d. listingu z poprzedniej strony) 10 cout << "\t\t\t\t Polygon\t\t\t\t Convex Polygon" << endl; 11 cout << "\t\t Inside\t\t In\t\t Inside\t\t In" << endl; // Dla kolejnych punktw wyznacz ich przynaleno do wielokta przy uyciu // poszczeglnych funkcji 12 while(cin >> p.x >> p.y) cout << p << "\t\t" << 13 PointInsidePol(pol, p) << "\t\t\t" << 14 PointInPol(pol, p) << "\t\t\t" << 15 PointInsideConvexPol(pol, p) << "\t\t\t" << 16 PointInConvexPol(pol, p) << endl; 17 return 0; 18 }

3.4. Punkty przecicia


W niektrych sytuacjach stwierdzenie samego faktu przecinaLiteratura nia si obiektw geometrycznych jest niewystarczajce potrzebne a [WDA] - 35.1 okazuje si bowiem rwnie wyliczenie wsprzdnych punktw przecicia. W aktualnym rozdziale zajmiemy si tego typu problemami. Przedstawimy sposb wyznaczania punktw przecicia prostych, odcinkw, prostej z okrgiem oraz dwch okrgw. Rozpoczniemy od przedstawienia problemu wyznaczania punktu przecicia dwch prostych. Analizujc zbir punktw przecicia dwch prostych p1 p2 oraz l1 l2 , mog zachodzi a a trzy rne przypadki: proste mog by do siebie rwnolege i si w ogle nie przecinaj, a a mog si pokrywa czyli mie nieskoczenie wiele punktw przecicia, oraz mie tylko jeden a punkt wsplny. Pierwsze dwa przypadki w prosty sposb mona zbada, sprawdzajc warto a iloczynu wektorowego (p1 p2 ) (l1 l2 ) jeli jest on rwny 0, to znaczy, e proste s rwnolege. W takim przypadku trzeba jeszcze sprawdzi, czy si pokrywaj, co mona a a w atwy sposb zwerykowa choby poprzez zbadanie przynalenoci punktu l1 do prostej p1 p2 . Jeli proste nie s rwnolege, to istnieje dokadnie jeden punkt przecicia, ktry mona a wyznaczy, rozwizujc odpowiedni ukad rwna. Zauwamy, e dowolny punkt (x, y) naa a lecy do prostej p1 p2 mona wyrazi w postaci (bp1 .x+(1b)p2 .x, bp1 .y+(1b)p2 .y) a dla pewnej rzeczywistej liczby b. Poniewa poszukiwany punkt naley do obu prostych p1 p2 oraz l1 l2 , zatem uzyskujemy w ten sposb ukad rwna z czterema niewiadomymi x, y, b i c:

x = b x1 + (1 b) x2

Wyznaczajc z niego niewiadome x oraz y, otrzymujemy: a x= x2 (x3 y4 x4 y3 + (x4 x3 ) y1 ) + x1 (x4 y3 x3 y4 + (x3 x4 ) y2 ) (x1 x2 ) (y3 y4 ) (x3 x4 ) (y1 y2 ) 100

x = c x3 + (1 c) x4

y = b y1 + (1 b) y2

y = c y3 + (1 c) y4

y=

y2 (x3 y4 x4 y3 ) + y1 (x4 y3 x3 y4 ) + x2 y1 (y4 y3 ) + x1 y2 (y3 y4 ) (x1 x2 ) (y3 y4 ) (x3 x4 ) (y1 y2 )

Funkcj realizujc t metod jest int LineCrossPoint(POINT, POINT, POINT, POINT, a a a POINT&), ktra jako parametry przyjmuje dwie proste wyznaczone przez pary punktw oraz dodatkowy punkt przekazywany przez referencj. Zwracana przez funkcj warto moe by nastpujca: a 0 proste nie przecinaj si a 1 proste przecinaj si w jednym punkcie a 2 proste pokrywaj si (istnieje nieskoczenie wiele punktw przecicia) a

W przypadku, gdy funkcja zwraca warto 1, przekazany przez referencj punkt jest ustawiany na punkt przecicia prostych. Implementacja tej funkcji przedstawiona jest na listingu 3.28.
Listing 3.28: Implementacja funkcji int LineCrossPoint(POINT, POINT, POINT, POINT, POINT&) 1 int LineCrossPoint(POINTD p1, POINTD p2, POINTD l1, POINTD l2, POINTD & prz) { // Iloczyn wektorowy (p1 -> p2) i (l1 -> l2) 2 LL t = LL(p1.x - p2.x) * LL(l1.y - l2.y) - LL(p1.y - p2.y) * LL(l1.x - l2.x); // Iloczyn wektorowy (l2 -> p2) i (l1 -> l2) 3 LL s = LL(l2.x - p2.x) * LL(l1.y - l2.y) - LL(l2.y - p2.y) * LL(l1.x - l2.x); // Jeli proste s rwnolege (t == 0), to istnieje nieskoczenie wiele // punktw wsplnych wtw. gdy proste si pokrywaj (iloczyn wektorowy s == 0) 4 if (!t) return s ? 0 : 2; 5 double w = double (s) / double (t); // Istnieje jeden punkt wsplny - wyznacz jego wsprzdne 6 prz.x = w * p1.x + (1 - w) * p2.x; 7 prz.y = w * p1.y + (1 - w) * p2.y; 8 return 1; 9}

Problem wyznaczania punktw przecicia dwch odcinkw z teoretycznego punktu widzenia jest stosunkowo podobny, jednak istniej tu pewne rnice zwizane ze sposobem reprezena a tacji wsplnego obszaru. Gdy odcinki czciowo si pokrywaj, ich cz wsplna nie jest a ca prost je zawierajc. W takim przypadku, obszar stanowicy ich przecicie mona rea a a a a prezentowa rwnie przy uyciu odcinka. Jeli odcinki si przecinaj i nie s rwnolege, to a a sposb wyznaczania punktu przecicia jest podobny do tego, wykorzystanego w przypadku przecinania si dwch prostych. Funkcja bool SegmentCrossPoint(POINT, POINT, POINT, POINT, vector<POINT>&) przyjmuje jako parametry dwa odcinki reprezentowane przez pary punktw oraz referencj na wektor punktw v. Zwracanym wynikiem jest prawda, jeli dane odcinki maj cz wspln w takim przypadku, jeli cz wsplna skada si tylko z a a jednego punktu, to jest on umieszczany w wektorze v. W przypadku, gdy odcinki pokrywaj a si, wektor v wypeniany jest dwoma punktami odpowiednio pocztkiem oraz kocem a wsplnego obszaru. Implementacja omawianej funkcji znajduje si na listingu 3.29.

101

Listing 3.29: Implementacja funkcji bool SegmentCrossPoint(POINT, POINT, POINT, POINT, vector<POINT>&) 01 inline bool SegmentCrossPoint(POINT p1, POINT p2, POINT l1, POINT l2, 02 vector<POINTD> &r) { 03 r.clear(); 04 int w1 = sgn(Det(p1, p2, l1)), w2 = sgn(Det(p1, p2, l2)), 05 v1 = sgn(Det(l1, l2, p1)), v2 = sgn(Det(l1, l2, p2)); // Jeli punkty l1 i l2 znajduj si po tej samej stronie prostej p1 -> p2 // lub p1 i p2 znajduj si po tej samej stronie prostej l1 -> l2, // to odcinki nie przecinaj si 06 if (w1*w2 > 0 || v1*v2 > 0) return false; // Jeli punkty l1 i l2 le na prostej p1 -> p2, to odcinki // l1 -> l2 i p1 -> p2 s wspliniowe 07 if (!w1 && !w2) { // Zamiana par punktw reprezentujcych odcinki, tak aby pierwsze punkty // w parach byy mniejsze w porzdku po wsprzdnych (x,y) 08 if (OrdXY(&p2, &p1)) swap(p1, p2); 09 if (OrdXY(&l2, &l1)) swap(l1, l2); // Jeli odcinki s rozczne, to nie ma punktw przecicia 10 if (OrdXY(&p2, &l1) || OrdXY(&l2,&p1)) return false; // Wyznacz kracowe punkty wsplne 11 if (p2 == l1) r.PB(POINTD(p2.x, p2.y)); 12 else if (p1 == l2) r.PB(POINTD(l2.x, l2.y)); 13 else { 14 r.PB(OrdXY(&p1, &l1) ? l1 : p1); 15 r.PB(OrdXY(&p2, &l2) ? p2 : l2); 16 } 17 } // Jeli jeden z odcinkw jest zdegenerowany, to jest on punktem przecicia 18 else if (l1 == l2) r.PB(l1); 19 else if (p1 == p2) r.PB(p2); 20 else { // Wyznacz punkt przecicia 21 double t = double(LL(l2.x - p2.x) * LL(l1.y - l2.y) - LL(l2.y - p2.y) * LL(l1.x - l2.x)) / 22 double(LL(p1.x - p2.x) * LL(l1.y - l2.y) - LL(p1.y - p2.y) * LL(l1.x l2.x)); 23 r.PB(POINTD(t * p1.x + (1.0 - t) * p2.x, t * p1.y + (1.0 - t) * p2.y)); 24 } 25 return true; 26 }

Listing 3.30: Wynik dziaania funkcji bool SegmentCrossPoint(POINT, POINT, POINT, POINT, vector<POINT>&) oraz int LineCrossPoint(POINT, POINT, POINT, POINT, POINT&) dla zbioru odcinkw i prostych z rysunku 3.6

(4, 8) - (-7, 1) oraz (-5, 4) - (3, 4) Punkt przeciecia prostych: 1 (-2.28571, 4) Punkt przeciecia odcinkow: 1 (-2.28571, 4) 102

Listing 3.30: (c.d. listingu z poprzedniej strony)

(-6, -3) - (5, 4) oraz (-5, 4) - (3, 4) Punkt przeciecia prostych: 1 (5, 4) Punkt przeciecia odcinkow: 0 (-6, -3) - (5, 4) oraz (4, 8) - (-7, 1) Punkt przeciecia prostych: 0 Punkt przeciecia odcinkow: 0

Listing 3.31: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.30. Peny kod rdowy programu znajduje si w pliku linesegcross.cpp 01 int main() { 02 vector<POINT> b, e; 03 vector<POINTD> l; 04 int res; 05 POINT p1, p2; 06 POINTD p; // Wczytaj wszystkie pary punktw wyznaczajce proste i odcinki 07 while(cin >> p1.x >> p1.y >> p2.x >> p2.y) { 08 b.PB(p1); e.PB(p2); 09 } // Dla kadej pary punktw wykonaj funkcje LineCrossPoint oraz SegmentCrossPoint 10 REP(x, SIZE(b)) REP(y, x) { 11 cout << b[x] << " - " << e[x] << 12 " oraz " << b[y] << " - " << e[y] << endl; 13 cout << "Punkt przeciecia prostych: " << 14 (res = LineCrossPoint(b[x], e[x], b[y], e[y], p)); 15 if (res == 1) cout << " " << p; 16 cout << endl; 17 cout << "Punkt przeciecia odcinkow: " << 18 SegmentCrossPoint(b[x], e[x], b[y], e[y], l); 19 FOREACH(it, l) cout << " " << (*it); 20 cout << endl; 21 } 22 return 0; 23 }

Kolejn funkcj, suc do wyznaczania punktw przecicia, jest vector<POINTD> Linea a a a CircleCross (POINT, double, POINT, POINT). Wyznacza ona punkty przecicia prostej z okrgiem. Przyjmowane przez t funkcj parametry, to odpowiednio punkt stanowicy rodek a okrgu, jego promie, oraz dwa punkty wyznaczajce prost. Wynikiem dziaania funkcji jest a a wektor punktw przecicia. W zalenoci od wzajemnego pooenia okrgu i prostej, wektor ten zawiera zero, jeden, lub dwa elementy. W celu wyznaczenia poszukiwanych punktw przecicia midzy okrgiem o rodku w punkcie p i promieniu r oraz prostej wyznaczonej przez dwa punkty l1 oraz l2 , naley rozwiza nastpujcy ukad rwna: a a 103

Y (-5, 4) (-7, 1)

(4, 8)

(3, 4) (5, 4)

X (-6, -3)

Rysunek 3.6: Moliwe przypadki przecinania si zbioru odcinkw oraz prostych je zawierajcych a

x = b l1 .x + (1 b) l2 .x

Dziaanie funkcji vector<POINTD> LineCircleCross(POINT, double, POINT, POINT) sprowadza si w zasadzie do wyznaczenia rozwiza tego ukadu rwna. a
Listing 3.32: Implementacja funkcji vector<POINT> LineCircleCross(POINT, double, POINT, POINT) 01 vector<POINTD> LineCircleCross(POINTD p, double cr, POINTD p1, POINTD p2){ 02 double a = sqr(p1.x) + sqr(p1.y) + sqr(p2.x) + sqr(p2.y) 03 2.0 * (p1.x * p2.x + p1.y * p2.y); 04 double b = 2.0 * (p.x * (p2.x - p1.x) + p.y * (p2.y - p1.y) + 05 p1.x * p2.x + p1.y * p2.y - sqr(p2.x)-sqr(p2.y)); 06 double c = -sqr(cr) + sqr(p2.x) + sqr(p2.y) + sqr(p.x) + 07 sqr(p.y) - 2.0 * (p.x * p2.x + p.y * p2.y); 08 double d = b * b - 4.0 * a * c; 09 vector<POINTD> r; // Jeli nie istnieje rozwizanie rwnania kwadratowego, // to brak punktw przecicia 10 if (d < -EPS) return r; 11 double t = -b / (2.0 * a), e = sqrt(abs(d)) / (2.0 * a); 12 if (IsZero(d)) // Istnieje tylko jeden punkt przecicia... 13 r.PB(POINTD(t * p1.x + (1.0 - t) * p2.x, t * p1.y + (1.0 - t) * p2.y)); 14 else { // Istniej dwa punkty przecicia 15 r.PB(POINTD((t + e) * p1.x + (1.0 - t - e) * p2.x, 16 (t + e) * p1.y + (1.0 - t - e) * p2.y)); 17 r.PB(POINTD((t - e) * p1.x + (1.0 - t + e) * p2.x, 18 (t - e) * p1.y + (1.0 - t + e) * p2.y)); 19 } 20 return r; 21 }

1 2 (x p.x)2 + (y p.y)2 = r 2

y = b l .y + (1 b) l .y

104

Ostatni prezentowan funkcj suc do wyznaczania punktw przecicia jest vector a a a a a <POINT> CirclesCross(POINT, double, POINT, double). Jako parametry, funkcja ta przyjmuje rodki oraz promienie dwch okrgw (p1 , r1 , p2 , r2 ), natomiast jako wynik zwraca wektor punktw przecicia. Wyznaczenie punktw przecicia dwch okrgw sprowadza si jak i poprzednio do rozwizania odpowiedniego ukadu rwna. a
2 (x p1 .x)2 + (y p1 .y)2 = r1 2 (x p2 .x)2 + (y p2 .y)2 = r2

Listing 3.33: Implementacja funkcji vector<POINT> CirclesCross(POINT, double, POINT, double) 01 vector<POINTD> CirclesCross(POINTD c1, double c1r, POINTD c2, double c2r) { 02 vector<POINTD> r; 03 c2.x -= c1.x; 04 c2.y -= c1.y; // Jeli okrgi s wsprodkowe, to nie wyznaczamy punktw przecicia 05 if (IsZero(c2.x) && IsZero(c2.y)) return r; 06 double A = (-sqr(c2r) + sqr(c1r) + sqr(c2.x) + sqr(c2.y)) / 2.0; // Jeli rodki okrgw maj t sam wsprzdn y... 07 if (IsZero(c2.y)) { 08 double x = A / c2.x; 09 double y2 = sqr(c1r) - sqr(x); 10 if (y2 < -EPS) return r; // Jeli okrgi s styczne... 11 if (IsZero(y2)) r.PB(POINTD(c1.x + x, c1.y)); 12 else { // Okrgi przecinaj si 13 r.PB(POINTD(c1.x + x, c1.y + sqrt(y2))); 14 r.PB(POINTD(c1.x + x, c1.y - sqrt(y2))); 15 } 16 return r; 17 } 18 double a = sqr(c2.x) + sqr(c2.y); 19 double b = -2.0 * A * c2.x; 20 double c = A * A - sqr(c1r) * sqr(c2.y); 21 double d = b * b - 4.0 * a * c; 22 if (d < -EPS) return r; 23 double x = -b / (2.0 * a); // Jeli okrgi s styczne... 24 if (IsZero(d)) r.PB(POINTD(c1.x + x, c1.y + (A - c2.x * x) / c2.y)); 25 else { // Okrgi przecinaj si 26 double e = sqrt(d) / (2.0 * a); 27 r.PB(POINTD(c1.x + x + e, c1.y + (A - c2.x * (x + e)) / c2.y)); 28 r.PB(POINTD(c1.x + x - e, c1.y + (A - c2.x * (x - e)) / c2.y)); 29 } 30 return r; 31 }

105

Y (4, 5)

(-5, 0)

(2, 0) X

(-8, -3)

(8, -3)

Rysunek 3.7: Rne moliwoci przecinania si okrgw oraz prostych

Listing 3.34: Wynik dziaania funkcji vector<POINT> CirclesCross(POINT, double, POINT, double) oraz vector<POINT> LineCircleCross(POINT, double, POINT, POINT) na przykadzie rysunku 3.7

Przeciecie prostej (-8, -3) - (8, -3) i okregu: (-5, 0, 3) - (-5, -3) (2, 0, 4) - (-0.645751, -3) (4.64575, -3) (4, 5, 3) - brak punktow przeciecia Przeciecie okregow: (2, 0, 4) oraz (-5, 0, 3) - (-2, 0) (4, 5, 3) oraz (-5, 0, 3) - brak punktow przeciecia (4, 5, 3) oraz (2, 0, 4) - (5.28141, 2.28744) (1.20135, 3.91946)

Listing 3.35: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.34. Peny kod rdowy programu znajduje si w pliku linecirclecross.cpp 01 int main() { 02 vector<POINT> cCen; 03 VI cR; 04 vector<POINTD> res; 05 POINT l1, l2, p; int r; // Wczytaj wsprzdne punktw wyznaczajcych prost 06 cin >> l1.x >> l1.y >> l2.x >> l2.y; // Wczytaj opisy kolejnych okrgw 07 while(cin >> p.x >> p.y >> r) { 08 cR.PB(r); cCen.PB(p); 09 } // Wyznacz punkty przecicia prostej i okrgu 10 cout << "Przeciecie prostej " << l1 << " - " << l2 << 11 " i okregu:" << endl; 12 REP(x, SIZE(cCen)) { 13 res = LineCircleCross(cCen[x], cR[x], l1, l2); 14 cout << "\t(" << cCen[x].x << ", " << cCen[x].y << 15 ", " << cR[x] << ") - "; 16 if (!SIZE(res)) cout << "brak punktow przeciecia"; 17 FOREACH(it, res) cout << " " << (*it); 18 cout << endl; 19 }

106

Listing 3.35: (c.d. listingu z poprzedniej strony) // Wyznacz punkty przecicia dwch okrgw 20 cout << "Przeciecie okregow:" << endl; 21 REP(x, SIZE(cCen)) REP(y, x) { 22 res = CirclesCross(cCen[x], cR[x], cCen[y], cR[y]); 23 cout << "\t(" << cCen[x].x << ", " << cCen[x].y << ", " << cR[x] << 24 ") oraz (" << 25 cCen[y].x << ", " << cCen[y].y << ", " << cR[y] << ") - "; 26 if (!SIZE(res)) cout << "brak punktow przeciecia"; 27 FOREACH(it, res) cout << " " << *(it); 28 cout << endl; 29 } 30 return 0; 31 }

Zadanie: Akcja komandosw


Pochodzenie: XII Olimpiada Informatyczna Rozwizanie: a commando.cpp

Na Pustyni Bdowskiej odbywa si w tym roku Bardzo Interesujca i Widowiskowa Akcja a Komandosw (BIWAK). Podstawowym elementem BIWAK-u ma by neutralizacja bomby, ktra znajduje si gdzie na pustyni, jednak nie wiadomo dokadnie gdzie. Pierwsza cz akcji to desant z powietrza. Z helikoptera krcego nad pustyni, wya a a skakuj pojedynczo, w ustalonej kolejnoci komandosi. Gdy ktry z komandosw wylduje a a w jakim miejscu, okopuje si i ju si nie rusza z miejsca. Dopiero potem moe wyskoczy kolejny komandos. Dla kadego komandosa okrelona jest pewna odlego raenia. Jeli komandos przebywa w tej odlegoci (lub mniejszej) od bomby, to w przypadku jej ewentualnej eksplozji zginie. Dowdztwo chce zminimalizowa liczb komandosw biorcych udzia w akcji, ale chce mie a pewno, e w przypadku wybuchu bomby, przynajmniej jeden z komandosw przeyje. Na potrzeby zadania przyjmujemy, e Pustynia Bdowska jest paszczyzn, a komandoa sw, ktrzy si okopali utosamiamy z punktami. Mamy dany cig kolejno mogcych wya a skoczy komandosw. aden z nich nie moe opuci swojej kolejki, tzn. jeli i-ty komandos wyskakuje z samolotu, to wszyscy poprzedni wyskoczyli ju wczeniej. Dla kadego z komandosw znamy jego odlego zagroenia raenia oraz wsprzdne punktu, w ktrym wylduje, a o ile w ogle wyskoczy.

Zadanie
Napisz program, ktry: wczyta ze standardowego wejcia opisy komandosw, wyznaczy minimaln liczb komandosw, ktrzy musz wyskoczy, a a wypisze wynik na standardowe wyjcie.

Wejcie
W pierwszym wierszu standardowego wejcia zapisana jest jedna liczba cakowita n (2 107

n 2 000) liczba komandosw. W kolejnych n wierszach opisani s komandosi po a jednym w wierszu. Opis kadego komandosa skada si z trzech liczb cakowitych: x, y i r (1 000 x, y 1 000, 1 r 5 000). Punkt (x, y) to miejsce, gdzie wylduje komandos, a a r to jego odlego raenia. Jeli komandos znajdzie si w odlegoci r lub mniejszej od bomby, to w przypadku jej wybuchu zginie.

Wyjcie
W pierwszym i jedynym wierszu standardowego wyjcia Twj program powinien zapisa jedn a liczb cakowit - minimaln liczb komandosw, ktrzy musz wyskoczy, aby zapewni, e a a a co najmniej jeden z nich przeyje, lub jedno sowo N IE jeli nie jest moliwe, aby mie pewno, e ktry z komandosw przeyje.

Przykad
5 2 7 4 5 8 2 2 3 7 7 4 3 1 1 1

Dla nastpujcego wejcia: a Poprawnym rozwizaniem jest: a


4

Proste acm.uva.es - zadanie 191 acm.sgu.ru - zadanie 124 acm.sgu.ru - zadanie 129 acm.uva.es - zadanie 10084

rednie acm.sgu.ru - zadanie 253 spoj.sphere.pl - zadanie 102 acm.sgu.ru - zadanie 198 acm.sgu.ru - zadanie 267

wiczenia

Trudne spoj.sphere.pl - zadanie 182 spoj.sphere.pl - zadanie 332 spoj.sphere.pl - zadanie 272

3.5. Trzy punkty okrg a


W poprzednich rozdziaach przedstawilimy wiele rnych funkcji operujcych na okrgach. a Przyjmowalimy wtedy, e okrg reprezentowany jest w postaci pary (rodek okrgu, proa mie). Nie jest to jednak jedyny sposb w wielu przypadkach okrgi reprezentuje si jako trzy punkty nalece do ich obwodu. W sytuacjach, w ktrych konieczne jest korzystanie z a rnych reprezentacji okrgu (co moe by wymuszone formatem danych wejciowych w zadaniu), pojawia si potrzeba konwertowania jednej reprezentacji do innej. Konwersja reprezentacji okrgu ((p.x, p.y), r) uywanej w funkcjach z tej ksiki do reprezentacji wykorzystujcej a a trzy punkty jest prosta. Jako reprezentantw okrgu mona uy przykadowo nastpujcego a zbioru punktw: {(p.x, p.y + r), (p.x, p.y r), (p.x + r, p.y)}. Konwersja w drug stron wya daje si by znacznie bardziej skomplikowana nad ni wanie skupimy si w aktualnym a rozdziale. Zamy zatem, e mamy dane trzy punkty: l1 , l2 oraz l3 , ktre nie le na jednej prostej. a W celu wyznaczenia rodka oraz promienia okrgu opisanego na tych trzech punktach, naley rozwiza ukad rwna z trzema niewiadomymi r, p.x oraz p.y postaci: a 108

Y (2, 5)

(-3, 0)

(2, 0) X

(2, -5)

Rysunek 3.8: Rysunek prezentujcy okrgi wyznaczone przez zbir czterech punktw. S tylko trzy, a a a
nie cztery okrgi opisane na tych punkach, poniewa punkty (2, 5), (2, 0) oraz (2, 5) s a wspliniowe i nie wyznaczaj okrgu a

r 2 = (p.x l1 .x)2 + (p.y l1 .y)2

Prezentowana na listingu 3.36 funkcja bool ThreePointCircle(POINTD, POINTD, POINTD, POINTD&, double&) przyjmuje jako parametry trzy punkty oraz referencj na punkt p i zmienn r typu double. Jeli na trzech okrelonych przez parametry punktach mona opisa a okrg, funkcja zwraca prawd, punktowi p przypisywany jest rodek, a zmiennej r promie a wyznaczonego okrgu.
Listing 3.36: Implementacja funkcji bool ThreePointCircle(POINT, POINT, POINT, POINT&, double&) // Funkcja znajduje okrg wyznaczony przez trzy punkty lub zwraca fasz, // jeli taki okrg nie istnieje 01 bool ThreePointCircle(POINTD p1, POINTD p2, POINTD p3, POINTD &c, double &r){ // Wyznacz punkt przecicia symetralnych trjkta (p1, p2, p3) 02 if (LineCrossPoint(POINTD((p1.x + p2.x) / 2.0, (p1.y + p2.y) / 2.0), 03 POINTD((p1.x + p2.x) / 2.0 + p2.y - p1.y, (p1.y + p2.y) / 2.0 + p1.x p2.x), 04 POINTD((p1.x + p3.x) / 2.0, (p1.y + p3.y) / 2.0), 05 POINTD((p1.x + p2.x) / 2.0 + p3.y - p1.y, 06 (p1.y + p3.y) / 2.0 + p1.x - p3.x) ,c) != 1) 07 return false; // Wylicz promie okrgu o rodku w punkcie c 08 r = sqrt(sqr(p1.x - c.x) + sqr(p1.y - c.y)); 09 return true; 10 }

2 2 2 r = (p.x l .x)2 + (p.y l .y)2 3 3

r 2 = (p.x l .x)2 + (p.y l .y)2

Listing 3.37: Przykad uycia funkcji bool ThreePointCircle(POINT, POINT, POINT, POINT&, double&) na przykadzie zbioru punktw przedstawionych na rysunku 3.8

Punkty: (-3, 0), (2, 0), (2, 5) Srodek okregu = (-0.5, 2), promien = 3.20156 Punkty: (2, -5), (2, 0), (2, 5) 109

Y (-7, 5)

(8, 7)

(7, 4) (-3, 4) A (3, 2) d (-1, -1) (-6, -2) B X (5, -2)

(a)

(b)

Rysunek 3.9: (a) Zbir omiu punktw na paszczynie, oraz najmniejsza wypuka otoczka wyznaczana
dla tego zbioru punkw. (b) Dla kadej pary punktw oddalonych o d, z ktrych co najmniej jeden nie ley na wypukej otoczce, istnieje para punktw oddalona o l > d.

Listing 3.37: (c.d. listingu z poprzedniej strony)

Punkty sa wspolliniowe Punkty: (2, -5), (-3, 0), (2, 5) Srodek okregu = (2.5, 0.5), promien = 5.52268 Punkty: (2, -5), (-3, 0), (2, 0) Srodek okregu = (-0.5, -2.5), promien = 3.53553

Listing 3.38: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.37. Peny kod rdowy programu znajduje si w pliku threepointcircle.cpp 01 int main() { 02 vector<POINT> l; 03 POINT p; 04 POINTD po; 05 double r; 06 bool res; // Wczytaj list punktw 07 while (cin >> p.x >> p.y) l.PB(p); // Dla kadej trjki punktw, wyznacz okrg na nich opisany 08 REP(x, SIZE(l)) REP(y, x) REP(z, y) { 09 cout << "Punkty: " << l[x] << ", " << l[y] << ", " << l[z] << endl; 10 if (!ThreePointCircle(l[x], l[y], l[z], po, r)) 11 cout << "Punkty sa wspolliniowe" << endl; 12 else cout << "Srodek okregu = " << po << ", promien = " << r << endl; 13 } 14 return 0; 15 }

3.6. Wypuka otoczka


Dla danego zbioru punktw S, wypuka otoczka, jest to wielokt wypuky, ktry zawiera a 110

(a)

(b)

(c)

(d)

(e)

Rysunek 3.10: Proces konstruowania wypukej otoczki. (a) Wyznaczenie skrajnego punktu,
uporzdkowanie punktw po ktach odchylenia oraz dodanie pierwszej krawdzi. (b-c) a a Przetworzenie kolejnych wierzchokw oraz dodanie krawdzi do konstruowanej wypukej otoczki. (d) Usunicie poprzedniej krawdzi lecej na lewo od nowo dodanej. (e) a Konstrukcja wypukej otoczki zakoczona.

w swoim wntrzu wszystkie punkty ze zbioru S. Najmniejsza wypuka otoczka cechuje si dodatkowo najmniejsz moliw powierzchni. Dosy wan wasnoci najmniejszej wypukej a a a a a otoczki pomagajc w jej wyznaczaniu, jest to, e kady jej wierzchoek to rwnie punkt ze a a zbioru S (patrz rysunek 3.9.a) Istnieje wiele algorytmw sucych do wyznaczania najmnieja Literatura szej wypukej otoczki. Jednym z nich jest algorytm Grahama, ktry [WDA] - 35.3 dla n-elementowego zbioru punktw dziaa w czasie O(n log(n)). [ASD] - 8.3 Algorytm ten najpierw wyznacza skrajny punkt zbioru, a nastp[RII] - 4 nie sortuje wszystkie inne w kolejnoci rosncych ktw odchylea a nia (patrz rysunek 3.10.a). Wszystkie punkty s nastpnie przea twarzane w takiej kolejnoci dodaje si je do konstruowanej wypukej otoczki, usuwajc a jednoczenie punkty dodane poprzednio, o ile kt utworzony pomidzy trzema ostatnio doa danymi do otoczki punktami wyznacza skrt w prawo (rysunek 3.10.d). Proces konstrukcji wypukej otoczki dla przykadowego zbioru punktw zosta przedstawiony na rysunku 3.10. Przedstawiona na listingu 3.39 funkcja vector<POINT*> ConvexHull(vector<POINT>&) przyjmuje jako parametr wektor punktw, a zwraca wektor wskanikw na punkty nalece do a wypukej otoczki. Wskaniki te s uporzdkowane w kolejnoci odwrotnej do ruchu wskaza a wek zegara. Przedstawiona funkcja jest modykacj opisanego algorytmu Grahama, majcej a a na celu skrcenie implementacji oraz zwikszenie wydajnoci algorytmu. Punkty nie s sora towane po kcie odchylenia, lecz w porzdku po wsprzdnych, natomiast konstrukcja a a otoczki realizowana jest w dwch fazach dolnej oraz grnej powki.
Listing 3.39: Implementacja funkcji vector<POINT*> ConvexHull(vector<POINT>&) 01 #dene XCAL {while(SIZE(w) > m && Det((*w[SIZE(w) - 2]), (*w[SIZE(w) - 1]), \ 02 (*s[x])) <= 0) w.pop back(); w.PB(s[x]);} // Funkcja wyznaczajca wypuk otoczk dla zbioru punktw 03 vector<POINT*> ConvexHull(vector<POINT>& p) { 04 vector<POINT*> s, w; // Wypenij wektor s wskanikami do punktw, dla ktrych konstruowana jest wypuka otoczka 05 FOREACH(it, p) s.PB(&*it); // Posortuj wskaniki punktw w kolejnoci (niemalejce wsprzdne x, niemalejce wsprzdne y) 06 sort(ALL(s), OrdXY); 07 int m = 1;

111

Listing 3.39: (c.d. listingu z poprzedniej strony) // Wyznacz doln cz wypukej otoczki - czc najbardziej lewy - dolny punkt z najbardziej prawym - grnym punktem 08 REP(x, SIZE(s)) XCAL 09 m = SIZE(w); // Wyznacz grn cz otoczki 10 FORD(x, SIZE(s) - 2, 0) XCAL // Usu ostatni punkt (zosta on wstawiony do otoczki dwa razy) 11 w.pop back(); 12 return w; 13 }

Wiele problemw dla zbioru punktw mona rozwiza, wyznaczajc najpierw wypuk otoa a a czk. Przykadem moe by problem znajdowania pary najbardziej oddalonych punktw. Oczywistym jest, e punkty takie musz znajdowa si na wypukej otoczce rozpatrywanego a zbioru. Wybierajc bowiem par punktw a i b oddalonych od siebie o d, z ktrych przynaja mniej jeden nie ley na wypukej otoczce (zamy bez straty oglnoci, e jest nim punkt a), oraz prowadzc przez punkt a prost l prostopad do odcinka a b, okazuje si, e a a a dla wszystkich punktw c, lecych po przeciwnej stronie prostej l ni punkt b, zachodzi a |b c| > d (patrz rysunek 3.9.b). Analizujc kolejne pary antypodycznych punktw na wya pukej otoczce, uzyskujemy algorytm pozwalajcy na wyznaczanie pary najdalszych punktw a w czasie O(n log(n)).
Listing 3.40: Przykad dziaania funkcji vector<POINT*> ConvexHull(vector<POINT>&) dla zbioru punktw z rysunku 3.9. Do wyznaczanej wypukej otoczki nale tylko skrajne punkty kraa wdzi punkt (7, 4) nie naley do wyznaczonej otoczki.

(-7, 5) (-6, -2) (5, -2) (8, 7)

Listing 3.41: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.40. Peny kod rdowy programu znajduje si w pliku convexhull str.cpp 01 int main() { 02 int n; 03 vector<POINT> l; 04 POINT p; // Wczytaj liczb punktw 05 cin >> n; // Wczytaj wszystkie punkty 06 REP(x, n) { 07 cin >> p.x >> p.y; 08 l.PB(p); 09 } // Wyznacz wypuk otoczk 10 vector<POINT *> res = ConvexHull(l); 11 FOREACH(it, res) 12 cout << " " << (**it); 13 return 0; 14 }

112

Zadanie: Pomniki
Pochodzenie: Zadanie przygotowane na potrzeby ksiki a Rozwizanie: a monuments.cpp W Bajtocji od jakiego czasu trwa akcja, majca na celu zwikszenie atrakcyjnoci turystycza nej miasta. Sadzone s drzewa, trawa malowana jest na zielono... Suby miejskie postanowiy a wybudowa rwnie wiele ozdobnych pomnikw z Bito - Kryptu. Pojawi si jednak pewien problem w Bajtocji znajduje si potna sie Bito - serwerw, komunikujcych si przy a uyciu fal radiowych. Budowa pomnikw z Bito - Kryptu na obszarze aktywnej komunikacji radiowej moe zakca transmisj danych. Transmisja moe zosta zakcona, jeli pomnik znajduje si wewntrz pewnego trjkta, ktrego wierzchokami s lokalizacje Bito - serwea a a rw. Suby miejskie zwrciy si do Ciebie z prob o stwierdzenie, dla kadej sugerowanej a lokalizacji pomnika, czy nie bdzie ona zakca komunikacji.

Zadanie
Napisz program, ktry: wczyta lokalizacje Bito - serwerw oraz sugerowane lokalizacje pomnikw, stwierdzi dla kadego pomnika, czy moe on zakca czno, a wypisze wynik

4 5 0 0 10 0 10 10 0 10 5 5 12 1 9 9 10 5 3 10

Wejcie
Pierwszy wiersz zawiera dwie liczby cakowite n i m (1 n, m 100 000) liczb Bito - serwerw, oraz liczb sugerowanych lokalizacji pomnikw. Nastpne n wierszy zawiera po dwie liczby cakowite x, y (1 000 000 x, y 1 000 000), oznaczajcych wsprzdne kolejnych a Bito-serwerw. Kolejnych m wierszy zawiera po dwie liczby cakowite x, y (1 000 000 x, y 1 000 000), oznaczajcych wsprzdne sugerowanych lokalizacji pomnikw. a

Wyjcie
Twj program powinien wypisa dokadnie m wierszy w i-tym wierszu wyjcia powinno si znale sowo T AK, jeli lokalizacja pomnika jest bezpieczna (nie bdzie powodowaa zakce), lub sowo N IE w przeciwnym przypadku.

Przykad

Dla nastpujcego wejcia: a Poprawnym rozwizaniem jest: a


NIE TAK NIE TAK TAK

113

Proste acm.uva.es - zadanie 10078 acm.uva.es - zadanie 109 acm.uva.es - zadanie 10065

rednie acm.uva.es - zadanie 218 spoj.sphere.pl - zadanie 26 acm.sgu.ru - zadanie 227

wiczenia

Trudne spoj.sphere.pl - zadanie 228 acm.sgu.ru - zadanie 277 acm.uva.es - zadanie 10135 acm.sgu.ru - zadanie 290

3.7. Sortowanie ktowe a


Sortowanie ktowe zbioru punktw S dookoa wektora c d polega na takim uporzdkowaniu a a punktw, e dla dwch dowolnych punktw p1 oraz p2 ze zbioru S, p1 znajduje si przed a punktem p2 , jeli kty skierowane dcp1 oraz dcp2 zachowuj wasno dcp1 < dcp2 . W a przypadku, gdy dcp1 = dcp2 , umawiamy si, e jako wczeniejszy wybierany jest punkt znajdujcy si bliej od punktu c. a Sortowanie ktowe mona zrealizowa przy uyciu funkcji sort pochodzcej z biblioteki a a STL. Jedyne co trzeba zrobi, to dostarczy operator, ktry dla dwch danych punktw bdzie w stanie stwierdzi, ktry z nich jest mniejszy. Ze wzgldu jednak na konieczno rozpatrzenia wielu przypadkw, implementacja takiego operatora jest stosunkowo skomplikowana. Poza tym, funkcja sort nie przekazuje adnej informacji na temat wektora, wzgldem ktrego wykonywane jest sortowanie i trzeba by byo operatorowi porwnujcemu punkty dostarczy a informacj na temat tego wektora. Prezentowana w tym rozdziale funkcja rwnie wykorzystuje funkcj sort, jednak wczeniej dokonuje podziau zbioru punktw na dwie czci punkty znajdujce si po lewej stroa nie prostej c d, oraz reszt. Dziki wprowadzeniu takiego podziau, znika wiele przypadkw skrajnych, ktre normalnie naleaoby rozpatrze, a w rezultacie implementacja operatora wykorzystywanego przez funkcj sort jest prosta. Po posortowaniu obu zbiorw punktw wystarczy je z powrotem scali. Zoono czasowa sortowania, ze wzgldu na wykorzystanie funkcji sort to O(n log(n)). Rysunek 3.11 prezentuje proces wyznaczania porzdku a ktowego dla przykadowego zbioru punktw. a Prezentowana na listingu 3.42 funkcja vector<POINT*> AngleSort(vector<POINT>& p, POINT s, POINT k) przyjmuje jako parametry list punktw do posortowania, oraz dwa punkty c i d. Jako wynik dziaania zwracany jest wektor wskanikw na punkty w kolejnoci zgodnej z porzdkiem ktowym. a a
Listing 3.42: Implementacja funkcji vector<POINT*> AngleSort(vector<POINT>&, POINT, POINT) // Wskanik na punkt centralny (uywane przez funkcj porwnujc) 01 POINT* RSK; // Funkcja porwnujca punkty 02 bool Rcmp(POINT *a, POINT *b) { 03 LL w = Det((*RSK), (*a), (*b)); 04 if (w == 0) return abs(RSK->x - a->x) + abs(RSK->y - a->y) < 05 abs(RSK->x - b->x) + abs(RSK->y - b->y); 06 return w > 0; 07 } // Funkcja sortuje po kcie odchylenia zbir punktw wzgldem punktu centralnego // s zaczynajc od wektora s -> k 08 vector<POINT*> AngleSort(vector<POINT>& p, POINT s, POINT k) {

114

Y (8, 7) (2, 6) (-7, 5) (-3, 4) (7, 0) (-1, -1) (4, 0) X

(-6, -6)

(a)

(b)

(c)

Rysunek 3.11: (a) Zbir punktw do posortowania ktowego wzgldem wektora (0, 0) (1, 3). (b) a
Rozdzielenie zbioru punktw na dwa podzbiory i posortowanie punktw w ich obrbie. (c) Scalenie obu zbiorw

Listing 3.42: (c.d. listingu z poprzedniej strony) 09 RSK = &s; 10 vector<POINT*> l, r; // Kady punkt, ktry podlega sortowaniu, zostaje wstawiony do jednego // z wektorw l lub r, w zalenoci od tego, // czy znajduje si po lewej czy po prawej stronie prostej s -> k 11 FOREACH(it, p) { 12 LL d = Det(s, k, (*it)); 13 (d > 0 || (d==0 && 14 (s.x == it->x ? s.y < it->y : s.x < it->x))) ? l.PB(&*it) : r.PB(&*it); 15 } // Posortuj niezalenie punkty w obu wyznaczonych wektorach 16 sort(ALL(l), Rcmp); 17 sort(ALL(r), Rcmp); // Wstaw wszystkie punkty z wektora r na koniec wektora l 18 FOREACH(it, r) l.PB(*it); 19 return l; 20 }

Listing 3.43: Przykad dziaania funkcji vector<POINT*> AngleSort(vector<POINT>&, POINT, POINT) dla przykadowego zbioru punktw z rysunku 3.11

(2, 6) (-3, 4) (-7, 5) (-1, -1) (-6, -6) (4, 0) (7, 0) (8, 7)

Listing 3.44: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.43. Peny kod rdowy programu znajduje si w pliku anglesort.cpp 01 int main() { 02 POINT c, k; 03 int n;

115

Listing 3.44: (c.d. listingu z poprzedniej strony) // Wczytaj liczb punktw oraz wsprzdne wektora, wzgldem ktrego wykonywane // bdzie sortowanie 04 cin >> n >> c.x >> c.y >> k.x >> k.y; 05 vector<POINT> l(n); // Wczytaj wszystkie punkty 06 REP(x, n) cin >> l[x].x >> l[x].y; // Posortuj punkty 07 vector<POINT *> res = AngleSort(l, c, k); 08 FOREACH(it, res) cout << " " << *(*it); 09 return 0; 10 }

Zadanie: Otarze
Pochodzenie: VI Olimpiada Informatyczna Rozwizanie: a altar.cpp

Wedug chiskich wierze ludowych ze duchy mog porusza si tylko po linii prostej. Ma a to istotne znaczenie przy budowie wity. witynie s budowane na planach prostoktw a a a a o bokach rwnolegych do kierunkw pnoc - poudnie oraz wschd - zachd. adne dwa z tych prostoktw nie maj punktw wsplnych. Po rodku jednej z czterech cian jest wejcie, a a ktrego szeroko jest rwna poowie dugoci tej ciany. W centrum wityni (na przeciciu a przektnych prostokta) znajduje si otarz. Jeli znajdzie si tam zy duch, witynia zoa a a stanie zhabiona. Tak moe si zdarzy, jeli istnieje pprosta (w paszczynie rwnolegej do powierzchni terenu), ktra biegnie od otarza w centrum wityni przez otwr wejciowy a a do nieskoczonoci, nie przecinajc i nie dotykajc po drodze adnej ciany, tej lub innej a a wityni. a

Zadanie
Napisz program, ktry: wczyta opisy wity, a sprawdzi, ktre witynie mog zosta zhabione, a a wypisze ich numery na standardowe wyjcie

W pierwszym wierszu wejcia zapisana jest jedna liczba naturalna n, 1 n 1 000, bdca a liczb wity. W kadym z kolejnych n wierszy znajduje si opis jednej wityni (w i-tym a a a z tych wierszy opis wityni numer i). Opis wityni skada si z czterech nieujemnych liczb a a cakowitych, nie wikszych ni 8 000, oraz jednej litery E, W, S lub N. Pierwsze dwie liczby, to wsprzdne pnocno - zachodniego naronika wityni, a dwie nastpne, to wsprzdne a przeciwlegego, poudniowo - wschodniego naronika. Okrelajc wsprzdne punktu podaa jemy najpierw jego dugo geograczn (ktra ronie z zachodu na wschd), a nastpnie a szeroko geograczn, ktra ronie z poudnia na pnoc. Pity element opisu wskazuje a a cian, na ktrej znajduje si wejcie do wityni (E wschodni, W zachodni, S a a a poudniow, N pnocn). Kolejne elementy opisu wityni s pooddzielane pojedynczymi a a a a 116

Wejcie

odstpami.

Wyjcie
W kolejnych wierszach wyjcia, Twj program powinien zapisa w porzdku rosncym nua a mery wity, ktre mog zosta zhabione przez zego ducha, kady numer w osobnym a a wierszu.

Przykad
6 1 7 4 1 E 3 9 11 8 S 6 7 10 4 N 8 3 10 1 N 11 4 13 1 E 14 8 20 7 W

Dla nastpujcego wejcia: a Poprawnym rozwizaniem jest: a


1 2 5 6

Proste acm.uva.es - zadanie 10002

rednie acm.uva.es - zadanie 10927

wiczenia

Trudne spoj.sphere.pl - zadanie 202

3.8. Para najbliszych punktw


W rozdziale dotyczcym wyznaczania wypukej otoczki zbioru a Literatura punktw wspomnielimy o algorytmie wyznaczania pary najdal[WDA] - 35.4 szych punktw. W niniejszym rozdziale zajmiemy si podobnym [ASD] - 8.4.1 problemem dla danego zbioru punktw S na paszczynie naley wyznaczy par najbliszych punktw. Nasuwajcym si rozwizaniem tego problemu jest sprawdzenie kadej pary punktw a a oraz wybranie tych dwch, ktre s najbliej siebie. Rozwizanie takie jest bardzo proste w a a implementacji, niestety jego zoono czasowa nie jest zachwycajca i wynosi O(n2 ). a Istnieje algorytm pozwalajcy na znalezienie pary najbliszych punktw w czasie O(n a log(n)) i dziaa on w oparciu o technik dziel i rzd. Na samym pocztku, wykonywane a a s dwie kopie zbiorw punktw. Jedna z nich sortowana jest w kolejnoci niemalejcych a a wsprzdnych x (zbir ten nazwiemy Sx , druga w kolejnoci niemalejcych wsprzdnych y a 117

Y (-7, 5)

(4, 8) d (7, 4)

(-3, 4)

(3, 2) X (5, -2) X Y X X d L L Y d X

(-6, -2)

(-1, -1)

(a) (b) (c) Rysunek 3.12: (a) Zbir punktw, dla ktrych wyznaczana jest para najbliszych punktw. (b) Rozdzielenie zbioru punktw na dwa podzbiory X oraz Y . Wyznaczenie w obrbie tych zbiorw par najbliszych punktw oddalonych odpowiednio o d i e. (c) Scalenie podrozwiza a nastpuje sprawdzenie par punktw ze zbiorw X oraz Y oddalonych co najwyej o min(d, e) = d od prostej L.

(zbir Sy ). Po wykonaniu tego kroku, nastpuje przejcie do fazy dzielenia zbioru punktw. Wyznaczana jest prosta L, rwnolega do osi y, taka, e po obu jej stronach znajduje si tyle samo punktw ze zbioru S (patrz rysunek 3.12.b). W ten sposb powstaj dwa zbiory a punktw X oraz Y . X zawiera punkty pooone na lewo od prostej L, natomiast Y punkty pooone na prawo od prostej L. Rekurencyjnie rozwizywane zostaj podproblemy dla obu czci X i Y , na skutek czego a a wyznaczone zostaj dwie odlegoci d oraz e, oznaczajce odpowiednio odlego najbliszej a a pary punktw w zbiorze X oraz Y . Zamy, bez straty oglnoci, e d < e. Kolejnym krokiem algorytm jest scalania podrozwiza. Oczywistym jest, e para naja bliszych punktw dla caego zbioru S nie moe by oddalona o wicej ni d. Moliwe s a dwa przypadki albo najblisza para naley do zbioru X (wtedy jest to wyznaczona para punktw oddalonych o d), albo jeden z punktw naley do zbioru X, a drugi do zbioru Y . W przypadku tej drugiej sytuacji, punkty s oddalone od siebie o odlego mniejsz ni d, a a wic kady z nich musi lee nie dalej ni o d od prostej L podzbiory X i Y punktw znajdujcych si nie dalej ni o d oznaczamy odpowiednio przez X i Y (rysunek 3.12.c). a To, co pozostaje do zrobienia, to wyznaczenie wszystkich par punktw u X oraz v Y takich, e |u v| < d. Poniewa odlego dowolnej pary punktw, zarwno w zbiorze X , jak i Y wynosi co najmniej d, zatem liczba punktw ze zbiorw X oraz Y w dowolnym kwadracie o boku dugoci d jest ograniczona przez liczb 5. Aby wyznaczy wszystkie podane pary a punktw u i v, wystarczy dla kadego punktu ze zbioru X sprawdzi 5 najbliszych pod wzgldem wsprzdnej y punktw ze zbioru Y . Mona tego dokona w czasie liniowym ze wzgldu na sumaryczn moc zbiorw X oraz Y , dziki wczeniejszemu posortowaniu punktw a wzgldem wsprzdnej y. Implementacja opisanej wyej metody jest realizowana przez struktur NearestPoints. Do jej konstruktora przekazywany jest wektor punktw, dla ktrych chcemy znale par punktw najbliszych. Wynik najmniejsz odlego mona odczyta z pola double dist, a natomiast para najbliszych punktw jest wskazywana przez wskaniki POINT *p1, *p2. Implementacja przedstawiona jest na listingu 3.45.
Listing 3.45: Implementacja struktury NearestPoints 01 struct NearestPoints { 02 vector<POINT *> l;

118

Listing 3.45: (c.d. listingu z poprzedniej strony) // 03 // 04 // // // 05 06 07 08 09 10 // // // 11 // 12 13 // 14 // // 15 16 17 // 18 19 // // 20 21 22 23 // // 24 25 26 // // 27 { 28 29 30 Wskaniki na dwa punkty, stanowice znalezion par najbliszych punktw POINT *p1, *p2; Odlego midzy punktami p1 i p2 double dist; Funkcja usuwa z listy l wszystkie punkty, ktrych odlego od prostej x=p jest wiksza od odlegoci midzy par aktualnie znalezionych najbliszych punktw void Filter(vector<POINT *> &l, double p) { int s = 0; REP(x, SIZE(l)) if (sqr(l[x]->x - p) <= dist) l[s++] = l[x]; l.resize(s); } Funkcja realizuje faz dziel i rzd dla zbioru punktw z wektora l od pozycji p do k. Wektor ys zawiera punkty z przetwarzanego zbioru posortowane w kolejnoci niemalejcych wsprzdnych y void Calc(int p, int k, vector<POINT *> &ys) { Jeli zbir zawiera wicej ni jeden punkt, to nastpuje faza podziau if (k - p > 1) { vector<POINT *> lp, rp; Wyznacz punkt podziau zbioru int c = (k + p - 1) / 2; Podziel wektor ys na dwa zawierajce odpowiednio punkty na lewo oraz na prawo od punktu l[c] FOREACH(it, ys) if (OrdXY(l[c], *it)) rp.PB(*it); else lp.PB(*it); Wykonaj faz podziaw Calc(p, c + 1, lp); Calc(c + 1, k, rp); Nastpuje faza scalania. Najpierw z wektorw l i r usuwane s punkty pooone zbyt daleko od prostej wyznaczajcej podzia zbiorw Filter(lp, l[c]->x); Filter(rp, l[c]->x); int p = 0; double k; Nastpuje faza wyznaczania odlegoci pomidzy kolejnymi parami punktw, ktre mog polepszy aktualny wynik FOREACH(it, lp) { while (p < SIZE(rp) - 1 && rp[p + 1]->y < (*it)->y) p++; FOR(x, max(0, p - 2), min(SIZE(rp) - 1, p + 1)) Jeli odlego midzy par przetwarzanych punktw jest mniejsza od aktualnego wyniku, to zaktualizuj wynik if (dist > (k = sqr((*it)->x - rp[x]->x) + sqr((*it)->y - rp[x]->y))) dist = k; p1 = (*it); p2 = rp[x];

119

Listing 3.45: (c.d. listingu z poprzedniej strony) 31 } 32 } 33 } 34 } // Konstruktor struktury NearestPoints wyznaczajcy par najbliszych punktw 35 NearestPoints(vector<POINT> &p) { // Wypenij wektor l wskanikami do punktw z wektora p oraz posortuj te // wskaniki w kolejnoci niemalejcych wsprzdnych x 36 FOREACH(it, p) l.PB(&(*it)); 37 sort(ALL(l), OrdXY); // Jeli w zbiorze istniej dwa punkty o tych samych wsprzdnych, to punkty // te s poszukiwanym wynikiem 38 FOR(x, 1, SIZE(l) - 1) 39 if (l[x - 1]->x == l[x]->x && l[x - 1]->y == l[x]->y) { 40 dist = 0; 41 p1 = l[x - 1]; 42 p2 = l[x]; 43 return; 44 } 45 dist = double (INF) * double (INF); // Skonstruuj kopi wektora wskanikw do punktw i posortuj go w kolejnoci // niemalejcych wsprzdnych y 46 vector<POINT *> v = l; 47 sort(ALL(v), OrdYX); // Wykonaj faz dziel i rzd dla wszystkich punktw ze zbioru 48 Calc(0, SIZE(l), v); 49 dist = sqrt(dist); 50 } 51 };

Listing 3.46: Przykad wykorzystania struktury NearestPoints na zbiorze punktw z rysunku 3.12

Wyznaczona odleglosc: 4.12311 Znaleziona para najblizszych punktow: (-7, 5) (-3, 4)

Listing 3.47: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 3.46. Peny kod rdowy programu znajduje si w pliku nearestpoints str.cpp 01 int main() { 02 int n; 03 vector<POINT> l; 04 POINT p; // Wczytaj liczb punktw 05 cin >> n; // Wczytaj kolejne punkty 06 REP(x, n) {

120

Listing 3.47: (c.d. listingu z poprzedniej strony) 07 cin >> p.x >> p.y; 08 l.PB(p); 09 } // Wyznacz par najbliszych punktw oraz wypisz wynik 10 NearestPoints str(l); 11 cout << "Wyznaczona odleglosc: " << str.dist << endl; 12 cout << "Znaleziona para najblizszych punktow:" << endl; 13 cout << *(str.p1) << " " << *(str.p2) << endl; 14 return 0; 15 }

Proste acm.uva.es - zadanie 10245

wiczenia

rednie acm.sgu.ru - zadanie 120 acm.uva.es - zadanie 10750

Trudne

121

Rozdzia 4

Kombinatoryka
Wiele zada, ktre zawodnik musi rozwiza podczas zawodw a Literatura nale do klasy problemw NP - trudnych. Stosunkowo duo tego a [KDP] - 1 typu zada pojawia si na konkursach ACM czy TopCoder. Roz[SZP] - 1.2.5 poznanie zadania tego typu jest zazwyczaj stosunkowo atwe [ASP] - 3 wszystkie one charakteryzuj si bardzo cisymi limitami na wiela ko danych wejciowych (umiejtno szacowania zoonoci wymaganego algorytmu na podstawie ogranicze na wielko danych wejciowych jest bardzo istotn umiejtnoci). Jak wiadomo, nie s znane algorytmy pozwalajce na rozwizywanie a a a a a zada NP - trudnych w czasie wielomianowym czsto stosowane techniki bazuj na prograa mowaniu dynamicznym, ktre zasadniczo polega na wyznaczaniu wynikw dla pewnej klasy podproblemw, na podstawie ktrych mona obliczy wynik dla postawionego problemu. Liczba podproblemw, ktre naley rozpatrze w przypadku zada NP - trudnych jest zazwyczaj wykadnicza, co nie tylko wie si z dugim czasem ich generowania, ale rwnie a wymaga duej iloci pamici potrzebnej na spamitywanie wynikw czciowych. Programowanie dynamiczne w przypadku wielu zada nie jest rwnie proste z punktu widzenia implementacyjnego. Czasem rozsdniejszym podejciem okazuje si wygenerowanie wszystkich moliwych rozwia a za do postawionego problemu, a nastpnie wybranie spord nich najlepszego. Rozwizaniem a moe by przykadowo odpowiednie uporzdkowanie pewnych obiektw (wyznaczany porzdek a a mona reprezentowa przy uyciu permutacji), czy te wybranie pewnego ich podzbioru. W przypadku wielu zada NP - trudnych okazuje si, e umiejtno efektywnego generowania rozmaitych obiektw kombinatorycznych jest podstaw do szybkiego rozwizania a a zadania. W aktualnym rozdziale przedstawimy kilka algorytmw pozwalajcych na generoa wanie wszystkich permutacji oraz podzbiorw danego zbioru, jak rwnie podziaw liczb. wietnym rdem informacji, na bazie ktrego powstaa zawarto tego rozdziau jest ksika Kombinatoryka dla programistw, do ktrej lektury gorco zachcamy. a a

4.1. Permutacje w kolejnoci antyleksykogracznej


Pierwsz prezentowan funkcj jest void PermAntyLex::Gen a a a Literatura (VI&, void (*)(VI&)), ktrej kod rdowy przedstawiony jest [KDP] - 1.4 na listingu 4.1. Funkcja ta przyjmuje jako argumenty wektor liczb v [SZP] - 1.2.5 oraz wskanik na funkcj f . Jej zadaniem jest generowanie wszystkich permutacji zbioru v, oraz dla kadej wygenerowanej permutacji wywoanie funkcji f . Sposb uycia tej funkcji w praktyce, jak i przykadowa kolejno 123

wyznaczanych permutacji przedstawiona jest na listingu 4.2. Specyczny porzdek generoa wania permutacji okazuje si poyteczny w niektrych zadaniach, jednak gwn zalet tej a a funkcji jest jej prostota. Czas wyznaczenia wszystkich permutacji zbioru n-elementowego to O(n!), co daje optymalny algorytm, gdy redni czas przypadajcy na wygenerowanie pojedynczej permutacji a jest stay.
Listing 4.1: Implementacja funkcji void PermAntyLex:Gen(VI&, void (*)(VI&)) 01 namespace PermAntyLex { // Wskanik na wektor liczb reprezentujcych generowan permutacj 02 VI *V; // Wskanik na funkcj, ktra jest wywoywana dla kadej wygenerowanej // permutacji 03 void (*fun) (VI &); // Funkcja rekurencyjna, wyznaczajca wszystkie m-elementowe permutacje 04 void Perm(int m) { 05 if (!m) fun(*V); 06 else FOR(i, 0, m) { 07 Perm(m - 1); 08 if (i < m) { // Zamiana miejscami elementu i-tego oraz m-1-szego 09 swap((*V)[i], (*V)[m]); // Odwrcenie kolejnoci wszystkich elementw na przedziale [0..m-1] 10 reverse(&(*V)[0], &(*V)[m]); 11 } 12 } 13 } // Waciwa funkcja wywoywana z zewntrz przestrzeni nazw PermAntyLex 14 void Gen(VI & v, void (*f) (VI &)) { 15 V = &v; 16 fun = f; 17 Perm(SIZE(v) - 1); 18 } 19 };

Listing 4.2: Ci g permutacji wygenerowany przez funkcj void PermAntyLex:Gen(VI&, void a (*)(VI&)) dla zbioru liczb {1, 2, 3, 4}

1 1 1 2

2 2 3 3

3 4 4 4

4 3 2 1

2 2 3 3

1 1 1 2

3 4 4 4

4 3 2 1

1 1 1 2

3 4 4 4

2 2 3 3

4 3 2 1

3 4 4 4

1 1 1 2

2 2 3 3

4 3 2 1

2 2 3 3

3 4 4 4

1 1 1 2

4 3 2 1

3 4 4 4

2 2 3 3

1 1 1 2

4 3 2 1

124

Listing 4.3: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 4.2. Peny kod rdowy programu znajduje si w pliku permantylex.cpp // Funkcja wywoywana dla kadej wygenerowanej permutacji 01 void Display(VI & v) { 02 static int calc = 0; 03 if (!(calc++ % 6)) cout << endl; // Wypisz elementy permutacji 04 FOREACH(it, v) cout << *it << " "; 05 cout << "\t"; 06 } 07 int main() { 08 VI p; // Stwrz wektor liczb {1,2,3,4} 09 FOR(x, 1, 4) p.PB(x); // Wygeneruj permutacje zbioru {1,2,3,4} w kolejnoci antyleksykograficznej 10 PermAntyLex::Gen(p, &Display); 11 return 0; 12 }

4.2. Permutacje minimalne transpozycje


Denicja 4.2.1 Transpozycja jest to zamiana miejscami dwch elementw permutacji przykadowo transpozycjami permutacji {1, 2, 3, 4} s {2, 1, 3, 4} oraz {1, 4, 3, 2} ale nie jest a {2, 3, 1, 4}. Jak ju zauwaylimy wczeniej, nie da si szybciej wygenerowa wszystkich permutacji zbioru n elementw ni w czasie O(n!). Nie oznacza to jednak, e rozwizanie zadania a bazujcego na przeanalizowaniu wszystkich permutacji, w celu poszukiwania optymalnego a rozwizania, dziaa w takim czasie. Dla kadej permutacji bowiem musz zosta wykonane a a pewne obliczenia, zalene od rozwizywanego problemu. Jedn z moliwoci wykonywania a a tych oblicze, jest dokonywanie ich niezalenie dla kadej wyznaczonej permutacji. Podejcie takie jest najprostsze z moliwych, jednak nie uwzgldnia zalenoci, ktre mog wystpowa a pomidzy rnymi rozwizaniami, a co za tym idzie podczas sprawdzania kolejnych permua tacji, algorytm moe wykonywa pewn nadmiern prac. Uwzgldnianie zalenoci midzy a a rozwizaniami wymaga jednak pewnej wiedzy na temat sposobu wyznaczania permutacji. a W zalenoci od kolejnoci ich generowania moe okaza si, e wykorzystanie informacji z poprzedniego rozwizania, w celu wyznaczenia wyniku dla aktualnego, nie jest proste, o ile w a ogle moliwe. Przedstawiona w aktualnym rozdziale funkcja void PermMinTr::Gen(VI&, void (*)(VI&)) suy do generowania wszystkich permutacji danego zbioru w kolejnoci minimalnych transpozycji. Przykad wygenerowanej listy permutacji przedstawiony jest na listingu 4.5. Kolejne dwie permutacje powstaj poprzez zamian miejscami tylko dwch elementw a podejcie takie w wielu zadaniach bardzo uatwia sposb wyznaczania wynikw dla kolejnych rozwiza. Parametrami funkcji s odpowiednio wektor liczb podlegajcych permutacjom a a a oraz wskanik na funkcj wywoywan dla kadej wyznaczonej permutacji. Czas dziaania a algorytmu to O(n!). Implementacja przedstawiona jest na listingu 4.4.

125

Listing 4.4: Implementacja funkcji void PermMinTr::Gen(VI&, void (*)(VI&)) 01 namespace PermMinTr { // Wskanik na wektor liczb reprezentujcych generowan permutacj 02 VI* V; // Wskanik na funkcj, ktra jest wywoywana dla kadej wygenerowanej permutacji 03 void (*fun)(VI&); // Funkcja rekurencyjna, wyznaczajca wszystkie m-elementowe permutacje 04 void Perm(int m){ 05 if(m == 1) fun(*V); else 06 REP(i, m) { 07 Perm(m - 1); 08 if(i < m - 1) swap((*V)[(!(m & 1) && m > 2) ? 09 (i < m - 1) ? i : m - 3 : m - 2], (*V)[m - 1]); 10 } 11 } // Waciwa funkcja wywoywana z zewntrz przestrzeni nazw PermAntyLex 12 void Gen(VI& v, void (*f)(VI&)) { 13 V = &v; 14 fun = f; 15 Perm(SIZE(v)); 16 } 17 };

Listing 4.5: Przykad dziaania funkcji void PermMinTr::Gen(VI&, void (*)(VI&)) dla zbioru {1, 2, 3, 4}

1 4 4 4

2 3 1 3

3 2 3 2

4 1 2 1

2 3 1 3

1 4 4 4

3 2 3 2

4 1 2 1

2 3 1 3

3 2 3 2

1 4 4 4

4 1 2 1

3 2 3 2

2 3 1 3

1 4 4 4

4 1 2 1

3 2 3 2

1 4 4 4

2 3 1 3

4 1 2 1

1 4 4 4

3 2 3 2

2 3 1 3

4 1 2 1

Listing 4.6: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 4.5. Peny kod rdowy programu znajduje si w pliku permmintr.cpp // Funkcja wywoywana dla kadej wygenerowanej permutacji 01 void Display(VI & v) { 02 static int calc = 0; 03 if (!(calc++ % 6)) cout << "\n"; // Wypisz elementy permutacji 04 FOREACH(it, v) cout << *it << " "; 05 cout << "\t"; 06 } 07 int main() { 08 VI p; // Stwrz wektor liczb {1,2,3,4} 09 FOR(x, 1, 4) p.PB(x); // Wygeneruj permutacje zbioru {1,2,3,4} w kolejnoci minimalnych transpozycji

126

Listing 4.6: (c.d. listingu z poprzedniej strony) 10 PermMinTr::Gen(p, &Display); 11 return 0; 12 }

4.3. Permutacje minimalne transpozycje ssiednie a


Denicja 4.3.1 Transpozycja ssiednia jest to taka transpozycja, ktra zamienia miejscami a dwa ssiednie elementy permutacji przykadowo transpozycjami ssiednimi zbioru {1, 2, 3, 4} a a s {2, 1, 3, 4} oraz {1, 2, 4, 3}, ale nie s nimi {2, 3, 1, 4} oraz {4, 2, 3, 1}. a a W niektrych zadaniach, generowanie permutacji w kolejnoci minimalnych transpozycji moe okaza si niewystarczajce zalenoci midzy poszczeglnymi rozwizaniami s a a a na tyle skomplikowane, e proces aktualizacji wyniku dla konkretnego rozwizania nie moe a zosta zrealizowany w zadowalajcym czasie na podstawie wyniku dla innego rozwizania a a rnicego si pojedyncz transpozycj. W takich sytuacjach podanym sposobem generoa a a a wania kolejnych permutacji moe okaza si metoda wyznaczania permutacji w kolejnoci minimalnych transpozycji ssiednich. Funkcja void PermMinTrAdj(VI&, void (*)(VI&, a int)), ktrej kod rdowy przedstawiony jest na listingu 4.7, realizuje wyej omwion mea tod wyznaczania permutacji. Jej parametrami, s odpowiednio wektor liczb, oraz funkcja a a f wywoywana dla kadej wyznaczonej permutacji. Funkcja f otrzymuje dwa parametry pierwszym jest wektor liczb reprezentujcy permutacj, drugi natomiast to liczba k wyznaa czajca par elementw (k oraz k + 1), ktre zostay zamienione miejscami w celu uzyskania a nowej permutacji (k = 1 w przypadku pierwszej permutacji). Czas dziaania funkcji to O(n!). Przykad jej uycia przedstawiony jest na listingu 4.8.
Listing 4.7: Implementacja funkcji void PermMinTrAdj(VI&, void (*)(VI&, int)) // Funkcja generuje wszystkie permutacje zbioru p w kolejnoci minimalnych // transpozycji ssiednich 01 void PermMinTrAdj(VI & p, void (*fun) (VI &, int)) { 02 int x, k, i = 0, s = p.size(); 03 VI c(s, 1); 04 vector<bool> pr(s, 1); 05 c[s - 1] = 0; 06 fun(p, -1); 07 while (i < s - 1) { 08 i = x = 0; 09 while (c[i] == s - i) { 10 if (pr[i] = !pr[i]) x++; 11 c[i++] = 1; 12 } 13 if (i < s - 1) { 14 k = pr[i] ? c[i] + x : s - i - c[i] + x; // Zamie miejscami k-ty i k-1-szy element w permutacji 15 swap(p[k - 1], p[k]); 16 fun(p, k - 1); 17 c[i]++;

127

Listing 4.7: (c.d. listingu z poprzedniej strony) 18 } 19 } 20 }

Permutacja: 1 2 3 Zamiana elementow Permutacja: 2 1 3 Zamiana elementow Permutacja: 2 3 1 Zamiana elementow Permutacja: 3 2 1 Zamiana elementow Permutacja: 3 1 2 Zamiana elementow Permutacja: 1 3 2

Listing 4.8: Wynik wygenerowany przez funkcj void PermMinTrAdj(VI&, void (*)(VI&, int)) dla zbioru liczb {1, 2, 3}.

0 i 1 1 i 2 0 i 1 1 i 2 0 i 1

Listing 4.9: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 4.8. Peny kod rdowy programu znajduje si w pliku permmintradj.cpp // Funkcja wywoywana dla kadej wygenerowanej permutacji 01 void Display(VI & v, int k) { 02 if (k != -1) cout << "Zamiana elementow " << k << " i " << k + 1 << endl; // Wypisz elementy permutacji 03 cout << "Permutacja: "; 04 FOREACH(it, v) cout << *it << " "; 05 cout << endl; 06 } 07 int main() { 08 VI p; // Stwrz wektor liczb {1,2,3} 09 FOR(x, 1, 3) p.PB(x); // Wygeneruj permutacje zbioru {1,2,3} w kolejnoci minimalnych transpozycji // ssiednich 10 PermMinTrAdj(p, &Display); 11 return 0; 12 }

Zadanie: Liczby permutacyjnie - pierwsze


Pochodzenie: Eliminacje do konkursu ACM ICPC Dhaka Regional 2004 Rozwizanie: a permprime.cpp

Permutacje cigu cyfr 0 9 posiadaj bardzo ciekawe wasnoci rnica dowolnych dwch a a permutacji tej samej sekwencji cyfr jest zawsze podzielna przez 9. Jest to do interesujce a zagadnienie. Przykadowo: 128

Zadaniem nie bdzie jednak udowodnienie tego faktu (gdy jest to zbyt proste) lecz przeanalizowanie nieco innej wasnoci. Istniej pewne liczby, ktrych rnica z pewn permutacj a a a ich cyfr jest postaci 9p, gdzie p to liczba pierwsza mniejsza od 1111111. Liczby takie nazywane s permutacyjnie - pierwsze. Dla przykadu: 92 29 = 63 = 9 7, a poniewa 7 jest liczb a a pierwsz, zatem 92 jest liczb permutacyjnie - pierwsz. a a a

|458967 456879| = 2088 = 9 232

Zadanie
Napisz program, ktry: wczyta list przedziaw liczb naturalnych, wyznaczy dla kadego z przedziaw ilo liczb permutacyjnie - pierwszych. wypisze wynik.

2 1 10 2 20

Wejcie
Pierwszy wiersz wejcia zawiera jedn liczb cakowit T (0 < T < 51) oznaczajc liczb a a a a testw. Nastpnych T wierszy zawiera po dwie liczby p oraz q (0 < p q 99 999 999, q p 1 000).

Wyjcie
Dla kadego testu naley wypisa jeden wiersz zawierajcy dokadnie jedn liczb cakowit, a a a oznaczajc ilo liczb w przedziale [a, b], ktre s permutacyjnie - pierwsze. a a a

Przykad

Proste acm.uva.es - zadanie 10911 acm.uva.es - zadanie 10063 acm.uva.es - zadanie 146 acm.uva.es - zadanie 10098 spoj.sphere.pl - zadanie 379

4.4. Wszystkie podzbiory zbioru


Rozwizania niektrych problemw nie musz wyraa si w postaci permutacji elementw a a pewnego zbioru, co wymusza potrzeb poszukiwania innych sposobw reprezentacji. Jedn z a 129

Dla nastpujcego wejcia: a


0 5

Poprawnym rozwizaniem jest: a

rednie acm.uva.es - zadanie 216 spoj.sphere.pl - zadanie 399 acm.sgu.ru - zadanie 222 acm.uva.es - zadanie 10460 acm.uva.es - zadanie 10475

wiczenia

Trudne acm.sgu.ru - zadanie 224 acm.uva.es - zadanie 10012 acm.uva.es - zadanie 10252 acm.uva.es - zadanie 195

nich jest uycie podzbiorw danego zbioru w takich sytuacjach przydatna moe si okaza, podobnie jak to byo w przypadku permutacji, efektywna metoda generujca wszystkie 2n a podzbiorw zbioru n-elementowego. Prezentowana w tym rozdziale funkcja void SubsetMin(int, Literatura void (*)(vector<bool>&, int)), ktrej implementacja przed[KDP] - 1.5 stawiona jest na listingu 4.10, przyjmuje jako parametry liczb naturaln n, okrelajc liczb elementw w rozpatrywanym zbioa a a rze oraz wskanik na funkcj f , ktra wywoywana jest dla kadego wyznaczonego podzbioru. Funkcji f przekazywane s dwa parametry. Pierwszym z nich jest wektor n zmiennych loa gicznych reprezentujcych podzbir zbioru k-ty element przyjmuje warto 1, jeli k-ty a element zbioru naley do wyznaczonego podzbioru. Drugi parametr reprezentuje numer elementu, ktry zosta dodany lub usunity w procesie generowania kolejnego podzbioru. Kade dwa kolejne podzbiory wyznaczane przez funkcj rni si dokadnie jednym elementem (w a kadym kroku jeden element zostaje dodany lub usunity z generowanego podzbioru). Czas dziaania funkcji jest liniowy ze wzgldu na liczb wyznaczanych podzbiorw i wynosi O(2n ).
Listing 4.10: Implementacja funkcji void SubsetMin(VI&, void (*)(vector<bool>&,int)) // Funkcja generujca wszystkie podzbiory zbioru n-elementowego 01 void SubsetMin(int n, void (*fun) (vector<bool> &, int)) { 02 vector<bool> B(n, 0); 03 fun(B, -1); 04 for (int i = 0, p = 0, j; p < n;) { 05 for (p = 0, j = ++i; j & 1; p++) 06 j >>= 1; 07 if (p < n) { // Zmie przynaleno do podzbioru elementu p 08 B[p] = !B[p]; 09 fun(B, p); 10 } 11 } 12 }

Listing 4.11: Przykad dziaania funkcji void (*)(vector<bool>&,int)) na przykadzie 3-elementowego zbioru

SubsetMin(VI&, void

Podzbior: 0 0 0 Do zbioru zostal Podzbior: 0 1 0 Do zbioru zostal Podzbior: 1 1 0 Do zbioru zostal Podzbior: 1 1 1 Ze zbioru zostal Podzbior: 0 1 1 Ze zbioru zostal Podzbior: 0 0 1 Do zbioru zostal Podzbior: 1 0 1

dodany element 1 dodany element 0 dodany element 2 usuniety element 0 usuniety element 1 dodany element 0

130

Listing 4.12: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 4.12. Peny kod rdowy programu znajduje si w pliku subsetmin.cpp // Funkcja wywoywana dla kadego wygenerowanego podzbioru 01 void Display(vector<bool> &v, int k) { 02 if (k != -1) { 03 if (v[k]) cout << "Do zbioru zostal dodany element " << k << endl; 04 else cout << "Ze zbioru zostal usuniety element " << k << endl; 05 } 06 cout << "Podzbior: "; 07 FOREACH(it, v) cout << *it << " "; 08 cout << endl; 09 } 10 int main() { // Wyznacz wszystkie 3-elementowe podzbiory 11 SubsetMin(3, &Display); 12 return 0; 13 }

Proste spoj.sphere.pl - zadanie 147

rednie acm.sgu.ru - zadanie 249

wiczenia

Trudne acm.uva.es - zadanie 811

4.5. Podzbiory k-elementowe w kolejnoci leksykogracznej


Istnieje grupa zada, w ktrych rozwizanie mona reprezentoa Literatura wa przy uyciu podzbioru, nie s uyteczne wszystkie podzbiory, a [KDP] - 1.7 ale tylko te o odpowiedniej licznoci. W takich sytuacjach, potrzebny jest mechanizm pozwalajcy na wyznaczanie wszystkich a podzbiorw zbioru n-elementowego zawierajcych dokadnie k elementw. Zadanie takie okaa zuje si nie tylko moliwe do zrealizowania w czasie liniowym, ze wzgldu na liczb wyznaczanych podzbiorw, ale rwnie proste w implementacji. Funkcja void SubsetKLex(int, int, void (*)(VI&)) przedstawiona na listingu 4.13 pozwala na wyznaczanie wszystkich podzbiorw k-elementowych w kolejnoci leksykogracznej. Jako parametry funkcja ta przyjmuje liczb elementw w rozpatrywanym zbiorze n, wielko generowanych podzbiorw k, oraz wskanik na funkcj, ktrej jako parametr bd przekazya wane kolejno generowane podzbiory. Podzbir reprezentowany jest w postaci listy numerw elementw do niego nalecych. Elementy numerowane s od 0 do n 1. Przykad uycia tej a a funkcji zosta przedstawiony na listingu 4.15.
Listing 4.13: Implementacja funkcji void SubsetKLex(int, int, void (*)(VI&)) // Funkcja wyznacza wszystkie podzbiory k-elementowe n-elementowego zbioru 01 void SubsetKLex(int k, int s, void (*fun) (VI &)) { 02 int i, p = k;

131

Listing 4.13: (c.d. listingu z poprzedniej strony) 03 04 05 06 07 08 09 10 } VI A(k); REP(x, k) A[x] = x; while (p) { fun(A); A[k - 1] == s - 1 ? p-- : p = k; if (p) FORD(i, k, p) A[i - 1] = A[p - 1] + i - p + 1; }

Listing 4.14: Przykad wykorzystania funkcji void SubsetKLex(int, int, void (*)(VI&)) do wyznaczenia wszystkich podzbiorw 4-elementowych 7-elementowego zbioru

0 0 0 0 1 1

1 1 2 3 2 4

2 3 3 4 4 5

3 5 5 6 5 6

0 0 0 0 1 2

1 1 2 3 2 3

2 3 3 5 4 4

4 6 6 6 6 5

0 0 0 0 1 2

1 1 2 4 2 3

2 4 4 5 5 4

5 5 5 6 6 6

0 0 0 1 1 2

1 1 2 2 3 3

2 4 4 3 4 5

6 6 6 4 5 6

0 0 0 1 1 2

1 1 2 2 3 4

3 5 5 3 4 5

4 6 6 5 6 6

0 0 1 1 3

2 3 2 3 4

3 4 3 5 5

4 5 6 6 6

Listing 4.15: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 4.15. Peny kod rdowy programu znajduje si w pliku subsetklex.cpp // Funkcja wywoywana dla kadego wyznaczonego podzbioru 01 void Display(VI & v) { 02 static int calc = 0; 03 if (!(++calc % 6)) cout << "\n"; // Wypisz elementy podzbioru 04 FOREACH(it, v) cout << *it << " "; 05 cout << "\t"; 06 } 07 int main() { // Wygeneruj wszystkie 4-elementowe podzbiory 7-elementowego zbioru 08 SubsetKLex(4, 7, &Display); 09 return 0; 10 }

4.6. Podziay zbioru minimaln liczb zmian a a


Podzia zbioru polega na przyporzdkowaniu kadego elementu a Literatura tego zbioru do dokadnie jednego podzbioru. Przykadami podzia[KDP] - 1.10 w zbioru 5-elementowego {1, 2, 3, 4, 5} s {1, 5} {2, 3, 4} oraz {1} a {2} {3, 4} {5} . Wyznaczanie podziaw zbioru okazuje si poyteczne podczas rozwizywania zada, w ktrych dla danego zbioru elementw naley podzieli a je na pewne grupy. Tak jak i w przypadku permutacji, wyznaczanie wyniku dla konkretnego podziau mona dokona na podstawie wyniku wyznaczonego dla innego podobnego po132

dziau, zatem metoda wyznaczania podziaw jest do istotna. Przedstawiony tu algorytm, realizowany przez funkcj void SetPartition(int, void (*)(VI&)) z listingu 4.16 wyznacza kolejne podziay zbioru w sekwencji minimalnych zmian kolejne dwa podziay rni a si przynalenoci do rnych podzbiorw tylko jednego elementu. Funkcja jako parametry a przyjmuje liczb n okrelajc moc dzielonego zbioru, oraz wskanik na funkcj f , ktrej a a przekazywane s wszystkie wyznaczane podziay. Podzia zbioru reprezentowany jest przez a wektor n liczb, gdzie k-ta liczba reprezentuje numer podzbioru, do ktrego zosta przydzielony k-ty element. Przykad dziaania funkcji zosta przedstawiony na listingu 4.17.
Listing 4.16: Implementacja funkcji void SetPartition(int, void (*)(VI&)) // Funkcja wyznacza wszystkie podziay n-elementowego zbioru 01 void SetPartition(int n, void (*fun)(VI&)) { 02 VI B(n, 1), N(n + 1), P(n + 1); 03 vector<bool> Pr(n + 1, 1); 04 N[1] = 0; 05 fun(B); 06 for(int i, k, j = n; j > 1;) { 07 k=B[j - 1]; 08 if(Pr[j]) { 09 if(!N[k]) P[N[k] = j + (N[j] = 0)] = k; 10 if(N[k] > j) N[P[j] = k] = P[N[j] = N[k]] = j; // W tym miejscu nastpuje przydzielenie elementu j do zbioru o numerze N[k] 11 B[j - 1] = N[k]; 12 } else { // W tym miejscu nastpuje przydzielenie elementu j do zbioru o numerze P[k] 13 B[j - 1] = P[k]; 14 if(k == j) N[k] ? P[N[P[k]] = N[k]] = P[k] : N[P[k]] = 0; 15 } 16 fun(B); 17 j = n; 18 while(j > 1 && ((Pr[j] && (B[j - 1] == j)) 19 || (!Pr[j] && (B[j - 1] == 1)))) Pr[j--] = !Pr[j]; 20 } 21 }

Listing 4.17: Przykad dziaania funkcji void SetPartition(int, void (*)(VI&)) dla 4elementowego zbioru

1 1 1 1 1 2 3 2 1 2 1 1

1 1 1 4 1 2 3 3 1 2 1 2

1 1 3 4 1 2 3 4 1 2 1 4

1 1 3 3 1 2 2 4

1 1 3 1 1 2 2 2

1 2 3 1 1 2 2 1

Listing 4.18: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 4.17. Peny kod rdowy programu znajduje si w pliku setpartition.cpp // Funkcja wywoywana dla kadego wyznaczonego podziau zbioru 01 void Display(VI & v) { 02 static int calc = 0;

133

Listing 4.18: (c.d. listingu z poprzedniej strony) 03 if (!(calc++ % 6)) cout << endl; // Wypisz wyznaczony podzia zbioru 04 FOREACH(it, v) cout << *it << " "; 05 cout << "\t"; 06 } 07 int main() { // Wyznacz wszystkie podziay 4-elementowego zbioru 08 SetPartition(4, &Display); 09 return 0; 10 }

4.7. Podziay liczby w kolejnoci antyleksykogracznej


Ostatnim z serii algorytmw kombinatorycznych jest funkcja, Literatura suca do generowania wszystkich podziaw zadanej liczby w koa [KDP] - 1.11 lejnoci antyleksykogracznej. Podziaem liczby nazywamy zaprezentowanie jej w postaci sumy liczb dodatnich. Jako przykad liczb 10 mona przedstawi na wiele sposobw, midzy innymi jako 5 + 3 + 2, czy jako 5 + 5. Dwa podziay liczby s takie same, jeli w ich skad wchodz te same liczby (nie istotna jest ich a a kolejno). Generowanie podziaw liczb okazuje si potrzebne choby podczas rozwizywania a problemw zwizanych z wydawaniem reszty. a Prezentowana na listingu 4.19 funkcja void NumPart(int, void (*)(VI&, VI&) przyjmuje jako parametry liczb naturaln n, ktra ma zosta poddana podziaom, oraz wskanik a do funkcji f wywoywanej dla kadego wyznaczonego podziau. Funkcja f jako parametry otrzymuje par rwnolicznych wektorw s oraz r k-ta para elementw z tych wektorw (sk , rk ) reprezentuje k-ty skadnik sumy sk liczb rk (szczegowy sposb uycia zosta przedstawiony na listingu 4.21).
Listing 4.19: Implementacja funkcji void NumPart(int, void (*)(VI&, VI&) // Funkcja generuje wszystkie podziay liczby n 01 void NumPart(int n, void (*fun) (VI &, VI &, int)) { 02 VI S(n + 1), R(n + 1); 03 int d, sum, l; 04 S[0] = n; 05 R[0] = d = 1; 06 while (1) { 07 int summ = 0, x, s; 08 fun(R, S, d); 09 if (S[0] == 1) break; 10 sum = 0; 11 if (S[d - 1] == 1) sum += R[--d]; 12 sum += S[d - 1]; 13 R[d - 1]--; 14 l = S[d - 1] - 1; 15 if (R[d - 1]) d++; 16 S[d - 1] = l;

134

Listing 4.19: (c.d. listingu z poprzedniej strony) 17 18 19 20 21 22 23 } 24 } R[d - 1] = sum / l; l = sum % l; if (l) { S[d] = l; R[d++] = 1; }

Listing 4.20: Przykad zastosowania funkcji void NumPart(int, void (*)(VI&, VI&) do wyznaczenia podziaw liczby 5

5 4 3 3 2 2 1

+ + + + + +

1 2 1 2 1 1

+ + + +

1 1 1 + 1 1 + 1 + 1

Listing 4.21: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 4.21. Peny kod rdowy programu znajduje si w pliku numpart.cpp // Funkcja wywoywana dla kadego wyznaczonego podziau liczby 01 void Display(VI & s, VI & r, int n) { 02 bool ch = 0; // Wypisz kolejne elementy podziau 03 REP(x, n) REP(y, s[x]) { 04 if (ch) cout << " + "; 05 cout << r[x]; 06 ch = 1; 07 } 08 cout << endl; 09 } 10 int main() { // Wygeneruj wszystkie podziay liczby 5 11 NumPart(5, &Display); 12 return 0; 13 }

135

Rozdzia 5

Teoria liczb
W tym rozdziale poruszymy problematyk zwizan z teori a a a Literatura liczb. Przedstawimy implementacj takich algorytmw, jak wyzna[SZP] - 4 czanie najwikszego wsplnego dzielnika, metod szybkiego potgowania, czy wyznaczanie odwrotnoci modularnej. Zaprezentowane zostan rwnie metody testowania pierwszoci liczb oraz implementacja arytmea tyki wielkich liczb, pozwalajca na wykonywanie operacji zarwno na liczbach wikszych od a standardowych typw zmiennych, jak rwnie liczb wymiernych reprezentowanych w postaci uamka nieskracalnego p . q

5.1. Dwumian Newtona


Kady doskonale zna denicj dwumianu Newtona n = k Literatura n! . Istnieje wiele zada, w ktrych naley wyznaczy liczb k!(nk)! [MK] - 5 sposobw wyboru pewnego podzbioru elementw, speniajc doa datkowe zaoenia. W zadaniach tego typu zawsze pojawia si w ktry momencie dwumian Newtona. Wyznaczenie wartoci n jest proste wystarczy pok liczy warto liczby n!, a nastpnie podzieli j przez k!(n k)!. W tym miejscu jednak a pojawia si pewien problem. W zadaniach czsto powiedziane jest, e obliczany wynik mieci si w pewnym standardowym typie zmiennych. Nie gwarantuje to jednak, e wszystkie wykonywane porednie obliczenia nie spowoduj przepenienia wartoci zmiennej. Taka sytua acja ma miejsce podczas wyznaczania wartoci dwumianu Newtona warto n! moe by a istotnie wiksza od wyznaczanej wartoci n . Jednym z rozwiza tego problemu jest zastok sowanie arytmetyki wielkich liczb, ale nie jest to w wielu przypadkach najlepsze rozwizanie, a ze wzgldu na efektywno oraz zoono implementacji. Zmiana sposobu wyliczania wyniku moe okaza si znacznie lepszym pomysem tak jest w przypadku dwumianu Newtona. Sposb wyliczania dwumianu Newtona, ktry nie powoduje przepenienia zmiennych, o ile sama warto n mieci si w arytmetyce, polega na wyznaczeniu rozkadu na liczby pierwsze k poszczeglnych liczb n!, k! oraz (n k)!, a nastpnie wykonaniu dzielenia poprzez skracanie czynnikw uzyskanych rozkadw. Implementacja takiego algorytmu zostaa przedstawiona na listingu 5.1. Funkcja LL Binom(int, int) przyjmuje jako parametry liczby n oraz k, a zwraca jako wynik warto n . k
Listing 5.1: Implementacja funkcji LL Binom(int, int) // Funkcja wyznacza warto dwumianu Newtona 01 LL Binom(int n, int k) {

137

Listing 5.1: (c.d. listingu z poprzedniej strony) // Makro zaznacza rozkad na liczby pierwsze liczby x w tablicy p 02 #dene Mark(x,y) for(int w=x,t=2;w>1;t++) while(!(w%t)) {w/=t; p[t]+=y;} 03 if (n < k || n < 0) return 0; 04 int p[n + 1]; 05 REP(x, n + 1) p[x] = 0; // Wyznacz warto liczby n!/(n-k)! w postaci rozkadu na liczby pierwsze 06 FOR(x, n - k + 1, n) Mark(x, 1); // Podziel liczb, ktrej rozkad znajduje si w tablicy p przez k! 07 FOR(x, 1, k) Mark(x, -1); // Wylicz warto liczby na podstawie jej rozkadu na liczby pierwsze i zwr // wynik 08 LL r = 1; 09 FOR(x, 1, n) while (p[x]--) r *= x; 10 return r; 11 }

Listing 5.2: Przykad dziaania funkcji LL Binom(int, int)

Binom(40,20) = 137846528820 Binom(100000,3) = 166661666700000 Binom(10,23) = 0

Listing 5.3: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.2. Peny kod rdowy programu znajduje si w pliku binom.cpp 1 int main() { 2 int n, k; // Dla wszystkich par liczb wyznacz warto dwumianu Newtona 3 while (cin >> n >> k) 4 cout << "Binom(" << n << "," << k << ") = " << Binom(n, k) << endl; 5 return 0; 6}

Proste acm.uva.es - zadanie 369

rednie spoj.sphere.pl - zadanie 78 acm.uva.es - zadanie 10338

wiczenia

Trudne acm.uva.es - zadanie 10219

138

5.2. Najwikszy wsplny dzielnik


Najwikszy wsplny dzielnik dwch liczb a i b (N W D(a, b)), jest to najwiksza liczba naturalna, ktra dzieli bez reszty obie liczby a i b. Przykadowo, N W D(10, 15) = 5, N W D(7, 2) = 1. Dwoma interesujcymi wasnociami najwikszego wsplnego dziela nika, pozwalajcymi na jego efektywne wyliczanie s: a a N W D(a, b) = a, dla b = 0 N W D(a, b) = N W D(a%b, b), dla b = 0 Literatura [WDA] - 33.2 [SZP] - 4.5.3 [MD] - 4.6 [MK] - 4.1 [TLK] - I.2

Majc dane dwie liczby naturalne a oraz b (a a b), dla ktrych naley wyznaczy warto najwikszego wsplnego dzielnika, wystarczy powtarza proces zamiany ich wartoci, przypisujc liczbie b warto a%b, a liczbie a b, tak dugo, tak dugo, dopki b = 0. Po zakoa czeniu, poszukiwana warto wsplnego najwikszego dzielnika jest rwna liczbie a. atwo mona wykaza, e dwukrotne zastosowanie kroku zamiany powoduje co najmniej dwukrotne zmniejszenie sumy liczb a i b, a co za tym idzie, liczba wszystkich wykonywanych krokw jest logarytmiczna ze wzgldu na sum a + b. Opisany algorytm znany jest jako wyznaczanie najwikszego wsplnego dzielnika metod a Euklidesa. Realizuje go funkcja LL GCD(LL, LL), ktrej implementacja przedstawiona jest na listingu 5.4.
Listing 5.4: Implementacja funkcji int GCD(int, int) // Funkcja suca do wyznaczania najwikszego wsplnego dzielnika dwch liczb 1 LL GCD(LL x, LL y) { 2 while (y) swap(x %= y, y); 3 return x; 4}

Rozpatrujc rne wane wasnoci najwikszego wsplnego dzielnika, naley wspomnie o a tym, e dla kadej pary dwch liczb naturalnych a oraz b, istniej liczby cakowite l oraz k, a takie e nwd(a, b) = a l + b k Fakt istnienie (a dokadniej, moliwo wyznaczenia) tych dwch liczb jest uyteczny podczas rozwizywania wielu problemw. Jednym z nich jest wyznaczanie odwrotnoci modularnej, a co stanowi temat kolejnego rozdziau. Wyznaczenie liczb l oraz k jest moliwe poprzez zmodykowanie algorytmu Euklidesa. Zamy, e znamy wartoci liczb l oraz k wystpujce w a rwnaniu postaci: n = (b%a) l + a k Rozpatrujc rwnanie postaci: a n=al+bk moemy uzaleni wartoci zmiennych l i k od l i k . Poszukiwane podstawienie ma posta:

l=k k=l 139

b a

Stosujc algorytm Euklidesa, dochodzimy pod koniec jego dziaania do rwnania postaci: a a=la+k0 zatem wartociami zmiennych l oraz k, speniajcymi to rwnanie, s l = 1, k = 0. Cofajc a a a wszystkie zamiany wartoci zmiennych a oraz b wykonane przez algorytm Euklidesa oraz wykonujc za kadym razem odpowiednie podstawienia zmiennych l i k, otrzymamy w kocu a poszukiwane wspczynniki pocztkowego rwnania: a nwd(a, b) = a l + b k Algorytm realizujcy t metod zosta zaimplementowany jako funkcja int GCD(int, int, a LL&, LL&), ktrej implementacja zostaa przedstawiona na listingu 5.5. Funkcja ta przyjmuje jako parametry dwie liczby a i b, dla ktrych wyznaczany jest najwikszy wsplny dzielnik oraz referencje na dwie dodatkowe zmienne, ktrym przypisywane s wyznaczane a wartoci wspczynnikw l oraz k. Zoono czasowa algorytmu nie ulega zmianie w stosunku do oryginalnej wersji algorytmu Euklidesa i wynosi O(log(a+b)). Funkcja int GCD(int, int, LL&, LL&) jest rekurencyjna (w odrnieniu od int GCD(int, int)), a co za tym idzie, zuycie pamici jest rwnie logarytmiczne.
Listing 5.5: Implementacja funkcji int GCDW(int, int, LL&, LL&) // Funkcja wyznacza najwikszy wsplny dzielnik dwch liczb, oraz wspczynniki // l i k 01 int GCDW(int a, int b, LL & l, LL & k) { 02 if (!a) { // gcd(0,b) = 0*0 + 1*b 03 l = 0; 04 k = 1; 05 return b; 06 } // Wyznacz rekurencyjnie warto najwikszego wsplnego dzielnika oraz // wspczynniki l oraz k 07 int d = GCDW(b % a, a, k, l); // Zaktualizuj wartoci wspczynnikw oraz zwr wynik 08 l -= (b / a) * k; 09 return d; 10 }

Listing 5.6: Przykad dziaania funkcji int GCDW(int, int, LL&, LL&)

gcd(10, 15) = 5 = -1*10 + 1*15 gcd(123, 291) = 3 = -26*123 + 11*291

Listing 5.7: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.6. Peny kod rdowy programu znajduje si w pliku gcdw.cpp // Funkcja Pokaz wypisuje wynik wyznaczony przez funkcj GCDW // dla pary liczb a i b 01 void Pokaz(int a, int b) {

140

Listing 5.7: (c.d. listingu z poprzedniej strony) 02 LL l,k; 03 int gcd=GCDW(a,b,l,k); 04 cout << "gcd(" << a << ", " << b << ") = " << GCDW(a,b,l,k); 05 cout << " = " << l << "*" << a << " + " << k << "*" << b << endl; 06 } 07 int main() { 08 Pokaz(10,15); 09 Pokaz(123,291); 10 return 0; 11 }

Zadanie: Bilard
Pochodzenie: Potyczki Algorytmiczne 2005 Rozwizanie: a bil.cpp

Bajtazar i przyjaciele w pitkowy wieczr udali si do klubu na partyjk bilarda. Jak zaa zwyczaj, podczas tego typu spotka, wywizaa si sprzeczka midzy Bajtazarem, a Bitolem. a Bajtazar zarzuci Bitolowi, e jego strategia gry jest bezsensowna, gdy kula uderzana przez niego nie ma najmniejszych szans wpa do uzy. Bitol natomiast twierdzi, e gdyby uderzy kul dostatecznie mocno, to w kocu wpadaby ona do jakiej uzy. Pom rozstrzygn spr a midzy kolegami. Napisz program, ktry stwierdzi, czy faktycznie kula wpadaby do uzy, a jeli tak, to do ktrej.

Zadanie
Napisz program, ktry: wczyta wymiary stou bilardowego, pocztkow pozycj uderzanej kuli oraz wektor a a wyznaczajcy ruch kuli po uderzeniu, a wyznaczy uz, do ktrej wpadnie kula lub stwierdzi, e kula nigdy nie wpadnie do adnej uzy, wypisze wynik.

Wejcie
Pierwszy i jedyny wiersz zawiera sze liczb cakowitych sx , sy , px , py , wx , wy oddzielonych pojedynczymi znakami odstpu, gdzie sx , sy wymiary stou bilardowego, 1 sx , sy 1 000 000, sx jest parzyste; px , py wsprzdne pocztkowego pooenia kuli, 0 a px sx , 0 py sy ; wx , wy wsprzdne wektora wyznaczajcego ruch kuli, 1 000 wx , wy a 1 000. St bilardowy ma sx metrw dugoci i sy metrw szerokoci. uzy znajduj si w rogach a stou oraz na rodkach bokw o dugoci sx . Przykadowo, st o wymiarach (8, 3) ma uzy w punktach (0, 0), (4, 0), (8, 0), (0, 3), (4, 3), (8, 3). Kule nie wypadaj poza obrb stou, porua szaj si bez tarcia, a wszystkie odbicia od band podlegaj zasadzie, e kt padania rwna a a a si ktowi odbicia. Kula wpada do uzy, gdy znajdzie si dokadnie w punkcie, w ktrym a znajduje si dana uza. 141

Wyjcie
Twj program powinien wypisa jeden wiersz zawierajcy nazw uzy, do ktrej wpadnie a kula, bd sowo N IE, jeli to si nigdy nie zdarzy. Nazwy kolejnych uz s nastpujce: a a a GL dla uzy o wsprzdnych (0, sy ) GP dla uzy o wsprzdnych (sx , sy )
x GS dla uzy o wsprzdnych ( s2 , sy )

Przykad
GL GS GP

10 5 7 4 1 2

DP
DL DS DP

Proste acm.uva.es - zadanie 10104 acm.uva.es - zadanie 10179

5.3. Odwrotno modularna


Zamy, e mamy dane rwnanie modularne z jedn niewiaa dom x postaci: a a x 1 (mod m) Literatura [MD] - 3.6

Chcielibymy znale liczb x {0, 1 . . . m 1}, dla ktrej powysze rwnanie jest spenione. Problem ten znany jest jako wyznaczanie odwrotnoci modularnej. Nasuwajcym si na samym pocztku pomysem, jest sprawdzenie wszystkich m molia a woci. Takie rozwizanie sprawdza si jednak tylko w przypadku maych wartoci liczby m. a Naley zatem zastanowi si nad szybszym rozwizaniem. Rwnanie, ktre chcemy rozwiza a a 142

DL dla uzy o wsprzdnych (0, 0) DP dla uzy o wsprzdnych (sx , 0)


x DS dla uzy o wsprzdnych ( s2 , 0)

Dla nastpujcego wejcia: a

Poprawnym rozwizaniem jest: a

rednie spoj.sphere.pl - zadanie 62 acm.uva.es - zadanie 10090

wiczenia

Trudne acm.uva.es - zadanie 10294 acm.sgu.ru - zadanie 292

mona przedstawi w innej postaci poprzez wprowadzenie dodatkowej niewiadomej y, ktra moe przyjmowa tylko wartoci cakowitoliczbowe: ax+my =1 Z tej postaci od razu wida, e jeli liczby a i m nie s wzgldnie pierwsze, to rwnanie nie a ma rozwizania. Gdy jednak liczby te s wzgldnie pierwsze, to istnieje nieskoczenie wiele a a rozwiza. Zamy, e x0 i y0 s pewnym rozwizaniem tego rwnania. Wtedy x1 = x0 +mk, a a a y1 = y0 a k, k N rwnie jest rozwizaniem tego rwnania, dokonujc podstawienia, a a otrzymujemy bowiem: ax1 +my1 = a(x0 +mk)+m(y0 ak) = ax0 +my0 +amkamk = ax0 +my0 = 1 Wyznaczenia wartoci x0 oraz y0 mona dokona przy uyciu rozszerzonego algorytmu Euklidesa. Nie ma gwarancji, e wyznaczona w ten sposb liczba x0 bdzie naleaa do przedziau {0, 1 . . . m 1} (a takiego wanie rozwizania poszukujemy). Mona to jednak poprawi, a wybierajc zamiast wyznaczonej wartoci x0 , jej odpowiednik rnicy si o wielokrotno a a liczby m, ktry jak pokazalimy, rwnie jest rozwizaniem naszego rwnania. a a a Listing 5.8 przedstawia implementacj funkcji int RevMod(int, int) realizujc omwiony algorytm. Funkcja ta przyjmuje jako parametry dwie liczby a oraz m, zwraca natomiast warto zmiennej x, lub 1, jeli nie istnieje odwrotno liczby a (mod m).
Listing 5.8: Implementacja funkcji int RevMod(int, int). // Funkcja wyznacza odwrotno modularn liczby a (mod m) 1 int RevMod(int a, int m){ 2 LL x, y; 3 if (GCDW(a, m, x, y) != 1) return -1; // Dokonaj przesunicia zmiennej x, tak aby znajdowaa si w przedziale [0..m-1] 4 x %= m; 5 if (x < 0) x += m; 6 return x; 7}

Listing 5.9: Przykadowe wyniki wyliczane przez funkcj int RevMod(int, int)

Rownanie: 3*x = 1 (mod 7) x = 5 Rownanie: 5*x = 1 (mod 11) x = 9 Rownanie: 11*x = 1 (mod 143) Brak rozwiazan

Listing 5.10: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.9. Peny kod rdowy programu znajduje si w pliku revmod.cpp 01 int main() { 02 int a, m; // Dla wszystkich par liczb wyznacz rozwizanie rwnania modularnego 03 while (cin >> a >> m) {

143

Listing 5.10: (c.d. listingu z poprzedniej strony) 04 cout << "Rownanie: " << a << "*x = 1 (mod " << m << ")" << endl; 05 int sol = RevMod(a, m); 06 if (sol == -1) cout << "Brak rozwiazan" << endl; 07 else cout << "x = " << sol << endl; 08 } 09 return 0; 10 }

5.4. Kongruencje
Majc dan liczb naturaln n, mona wpa na pomys polea a a Literatura gajcy na wyznaczaniu reszt z dzielenia liczby n przez rne liczby a [MK] - 4.6, 4.7 naturalne. Wyznaczenie tych reszt nie jest trudne. Mona jednak [TLK] - I.3 zada sobie nastpnie pytanie, czy na podstawie sekwencji reszt uzyskanych w procesie dzielenia, mona odtworzy warto liczby n. Pomocne moe si tu okaza Chiskie twierdzenie o resztach, ktre mwi, e jeli mamy dany zbir liczb wzgldnie pierwszych K = {k1 , k2 , . . . km }, to dla kadej sekwencji reszt r1 , r2 , . . . , rm z dzielenia przez liczby ze zbioru K, istnieje dokadnie jedna liczba w przedziale [0..k1 k2 . . . km 1] dajca takie reszty. Proces odtwarzania wartoci n mona a wykona krokami, za kadym razem rozwizujc prosty ukad kongruencji postaci: a a

x a (mod p) x b (mod q) i uzyskujc na skutek jego rozwizania nowe rwnanie x c (mod r). a a Wyjciowy problem sprowadza si zatem do rozwizywania ukadu dwch kongruencji. a Okazuje si, e rozwizanie tego ukadu istnieje wtedy i tylko wtedy, gdy a b (mod nwd(p, q)) a i mona wyrazi je wzorem x a q + b p (mod
pq N W D(p,q) )

gdzie i to liczby cakowite speniajce rwnanie p + q = N W D(p, q). a Funkcja bool congr(int, int, int, int, int&, int&) przedstawiona na listingu 5.11 przyjmuje jako parametry liczby a, b, p i q (w takiej wanie kolejnoci), a zwraca warto logiczn, oznaczajc istnienie rozwizania ukadu kongruencji. W takiej sytuacji, wartoa a a a ci dwch ostatnich parametrw funkcji zostaj ustawione na odpowiednio liczby c oraz r a stanowice poszukiwane rozwizanie. Jeli wymagane jest rozwizanie wikszego ukadu, to a a a mona rozwizywa je parami, zastpujc za kadym razem par rozwizanych kongruencji, a a a now kongruencj postaci x c (mod r). a a
Listing 5.11: Implementacja funkcji bool congr(int, int, int, int, int&, int&) // Funkcja wyznacza rozwizanie ukadu dwch kongruencji 01 bool congr(int a, int b, int p, int q, int &c, int &r) { 02 LL x, y; 03 r = GCDW(p, q, x, y); // Jeli liczba a nie przystaje do b (mod nwd(p,q)), to nie ma

144

Listing 5.11: (c.d. listingu z poprzedniej strony) // rozwizania 04 if ((a - b) % r) return 0; // Wyznacz wartoci c oraz r zgodnie ze wzorami 05 x = LL(a) + LL(p) * LL(b - a) / r * x; 06 r = LL(p) * LL(q) / r; 07 c = x % r; 08 if (c < 0) c += r; 09 return 1; 10 }

Listing 5.12: Przykad dziaanie funkcji bool congr(int, int, int, int, int&, int&)

5 (mod 7), 9 (mod 11), rozwiazanie: 75 (mod 77) 2 (mod 4), 4 (mod 8), rozwiazanie: Brak rozwiazan

Listing 5.13: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.12. Peny kod rdowy programu znajduje si w pliku congr.cpp 01 int main() { 02 int a, b, p, q, c, v; // Dla wszystkich zestaww danych, wyznacz rozwizanie ukadu kongruencji 03 while(cin >> a >> p >> b >> q) { 04 cout << a << " (mod " << p << "), " << b << 05 " (mod " << q << "), rozwiazanie: "; 06 if (congr(a, b, p, q, c, v)) 07 cout << c << " (mod " << v << ")" << endl; 08 else cout << "Brak rozwiazan" << endl; 09 } 10 return 0; 11 }

Zadanie: Wyliczanka
Pochodzenie: IX Olimpiada Informatyczna Rozwizanie: a enum.cpp Dzieci ustawiy si w kko i bawi si w wyliczank. Dzieci s ponumerowane od 1 do n w a a ten sposb, e (dla i = 1, 2, ..., n 1) na lewo od dziecka nr i stoi dziecko nr i + 1, a na lewo od dziecka nr n stoi dziecko nr 1. Dziecko, na ktre wypadnie w wyliczance, wypada z kka. Wyliczanka jest powtarzana, a nikt nie zostanie w kku. Zasady wyliczanki s nastpujce: a a pierwsz wyliczank zaczyna dziecko nr 1. Kad kolejn wyliczank rozpoczyna dziecko a a a stojce na lewo od dziecka, ktre ostatnio wypado z kka; a wyliczanka za kadym razem skada si z k sylab. Dziecko, ktre zaczyna wyliczank, mwi pierwsz jej sylab; dziecko stojce na lewo od niego mwi drug sylab, kolejne a a a dziecko trzeci itd. Dziecko, ktre mwi ostatni sylab wyliczanki, odpada z kka. a a

145

Zadanie
Napisz program, ktry: wczyta ze standardowego wejcia opis kolejnoci, w jakiej dzieci wypaday z kka, wyznaczy najmniejsz dodatni liczb k, dla ktrej dzieci bawic si w k-sylabow a a a a wyliczank bd wypada z kka w zadanej kolejnoci lub stwierdzi, e takie k nie a istnieje, wypisze wynik na standardowe wyjcie wyznaczon liczb k lub sowo N IE w przya padku, gdy takie k nie istnieje.

4

Wejcie
W pierwszym wierszu znajduje si jedna dodatnia liczba cakowita n, 2 n 20. W drugim wierszu znajduje si n liczb cakowitych pooddzielanych pojedynczymi odstpami i-ta liczba okrela, jako ktre z kolei dziecko nr i wypado z kka.

Wyjcie
Twj program powinien wypisa w pierwszym i jedynym wierszu jedn liczb cakowit: a a najmniejsz liczb k sylab, jakie moe mie wyliczanka lub jedno sowo N IE, jeli taka a liczba nie istnieje.

Przykad
4 1 4 2 3

Dla nastpujcego wejcia: a


5

Poprawnym rozwizaniem jest: a

5 1

1 4 3
3

4 3
2 5

1 4

1 3 5

2 4

2 3 4

2
5

5.5. Szybkie potgowanie modularne


W prezentowanym rozdziale zajmiemy si problemem potgowania modularnego, czyli dla danych liczb a, b oraz q, wyliczania wartoci wyraenia: ab (mod q) Literatura [WDA] - 33.6

Rozpatrujc zagadnienie polegajce na liczeniu wartoci ab , moemy mie dwa rne przya a padki albo wyznaczany wynik mieci si w standardowej arytmetyce komputera, albo nie. W tym pierwszym przypadku, stosujc prost metod potgowania polegajc na ba a a a krotnym pomnoeniu przez siebie liczb a, liczba wykonywanych mnoe jest ograniczona 146

przez liczb bitw wykorzystywanych do reprezentacji wykorzystywanych zmiennych. Poniewa jest to staa warto (zazwyczaj 32 lub 64), wic algorytm uzyskany w ten sposb dziaa w czasie staym (dla a > 1, b nie moe by wiksze ni 32 / 64). Inna sytuacja zachodzi, gdy wyznaczany wynik nie mieci si w arytmetyce. Wtedy pojawiaj si dodatkowe a problemy, zwizane z implementacj wasnej arytmetyki (przykad takiej arytmetyki przeda a stawiony jest w rozdziale Arytmetyka wielkich liczb). Podczas zawodw, w przypadku pojawiajcej si potrzeby implementacji wasnej arytmetyki, nie ma czasu na tworzenie bara dzo efektywnych operacji. Chocia istnieje algorytm pozwalajcy na mnoenie dwch liczb a w czasie O(n log(n) log(log(n))), to zazwyczaj implementowany algorytm ma zoono O(n m), gdzie n i m odpowiednio, to liczba cyfr wystpujcych w mnoonych liczbach. W a takich sytuacjach, kada operacja mnoenia jest bardzo kosztowna i dowolne oszczdnoci w liczbie wykonywanych mnoe podczas potgowania daj due rnice. a W przypadku, gdy wykonywane potgowanie realizowane jest modularnie, to mamy gwarancj, e wynik jest mniejszy od wartoci liczby q (czyli mieci si w arytmetyce komputera), nie majc jednoczenie adnych ogranicze dotyczcych wartoci liczb a oraz b (jak byo w a a pierwszym rozpatrywanym przez nas przypadku). W takiej sytuacji, dobrym pomysem jest wykorzystanie algorytmu szybkiego potgowania modularnego. Jego zasada dziaania polega na analizie reprezentacji binarnej liczby b, oraz odpowiedniego mnoenia liczby a przez kolejne k potgi a2 : ab = a(bm bm1 ...b0 )2 = abm 2
m +b m1 +...+b 20 m1 2 0

= abm 2 abm1 2

m1

. . . ab0 2

Algorytm szybkiego potgowania modularnego wylicza wartoci kolejnych potg a2 oraz m w przypadku gdy bm jest rwne 1, mnoy wynik przez a2 . Implementacja tego algorytmu zostaa zrealizowana w funkcji int ExpMod(LL, int, int), ktrej kod rdowy przedstawiony jest na listingu 5.14. Zoono caego algorytmu to O(log(b)).
Listing 5.14: Implementacja funkcji int ExpMod(LL, int, int) // Funkcja realizujca szybkie potgowanie (mod q) 1 int ExpMod(LL a, int b, int q) { 2 LL p = 1; 3 while (b > 0) { 4 if (b & 1) p = (a * p) % q; 5 a = (a * a) % q; 6 b /= 2; 7 } 8 return p; 9}

Listing 5.15: Przykad dziaania funkcji int ExpMod(LL, int, int)

2 do potegi 100 (mod 10) = 6 3 do potegi 17 (mod 123) = 3

Listing 5.16: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.15. Peny kod rdowy programu znajduje si w pliku expmod.cpp 1 int main() { 2 int a, b, q;

147

Listing 5.16: (c.d. listingu z poprzedniej strony) // Dla wszystkich danych wejciowych, wyznacz warto liczby ab (mod q) 3 while(cin >> a >> b >> q) cout << a << " do potegi " << b << 4 " (mod " << q << ") = " << ExpMod(a,b,q) << endl; 5 return 0; 6}

Proste acm.sgu.ru - zadanie 117

rednie acm.uva.es - zadanie 10006

wiczenia

Trudne acm.uva.es - zadanie 10710

5.6. Sito liczb pierwszych


Zamy, e mamy do czynienia z zadaniem, ktrego rozwizanie wymaga sprawdzania dla a rnych liczb k z przedziau [1..n], czy k jest liczb pierwsz. W takiej sytuacji wida, e naa a wet jeeli pytania o t sam liczb nie bd si powtarza, to i tak moe okaza si opacalne a a a na pocztku dziaania programu sprawdzenie, ktre liczby s pierwsze a ktre nie, a nastpnie a a zapamitanie tych wynikw w celu pniejszego uycia (o ile tylko n jest na tyle mae, e jestemy w stanie pomieci wszystkie wyniki w pamici). Perfekcyjna do tego celu jest metoda sita Eratostenesa, ktrej zasada dziaania jest bardzo prosta: buduje si tablic liczb od 2 do n, a nastpnie wykrela si z niej kolejne wielokrotnoci jeszcze nie wykrelonych liczb, zaczynajc od liczby 2. W ten sposb dla liczby 2 zostan wykrelone liczby 4, 6, 8, . . ., dla a a liczby 3 6, 9, 12, . . ., dla liczby 5 10, 15, 20, . . .. Po zakoczeniu tego procesu, jedynymi niewykrelonymi elementami bd liczby pierwsze (co wynika bezporednio z ich denicji a liczba pierwsza nie ma dzielnikw rnych od 1 i jej samej). Funkcja realizujca ten algorytm to bit vector Sieve(int), ktrej kod rdowy zosta a przedstawiony na listingu 5.17. Funkcja ta jako parametr przyjmuje wielko zakresu, natomiast zwraca jako wynik wektor binarny, w ktrym na k-tej pozycji znajduje si warto 1, jeli liczba k jest pierwsza.
Listing 5.17: Implementacja funkcji bit vector Sieve(int) // Funkcja realizuje algorytm sita Eratostenesa 1 bit vector Sieve(int s) { 2 bit vector V(s,1); 3 V[0] = V[1] = 0; // Dla kolejnej liczby, jeli nie zostaa ona wykrelona, to wykrel wszystkie jej wielokrotnoci 4 FOR(x, 2, s) if(V[x] && LL(s) >= LL(x) * LL(x)) 5 for(int y = x * x; y <= s; y += x) V[y] = 0; 6 return V; 7}

148

Listing 5.18: Przykad dziaania funkcji bit vector Sieve(int)

0 1 2 3 4 5 6 7

nie jest liczba pierwsza nie jest liczba pierwsza jest liczba pierwsza jest liczba pierwsza nie jest liczba pierwsza jest liczba pierwsza nie jest liczba pierwsza jest liczba pierwsza

Listing 5.19: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.18. Peny kod rdowy programu znajduje si w pliku sieve.cpp 1 int main() { 2 int n; // Wczytaj wielko zakresu 3 cin >> n; 4 bit vector V = Sieve(n); // Dla kadej liczby z zakresu wypisz, czy jest ona liczb pierwsz 5 REP(x, n) cout << x << (V[x] ? " jest liczba pierwsza" 6 : " nie jest liczba pierwsza") << endl; 7 return 0; 8}

5.7. Lista liczb pierwszych


Innym sposobem przedstawienia problemu z poprzedniego rozdziau, moe by ch wyznaczenia wszystkich liczb pierwszych w danym przedziale [1 . . . n]. Podejcie takie moe okaza si przydatne w zadaniach wymagajcych wyznaczania k-tej liczby pierwszej. Sposb wya znaczania wszystkich liczb pierwszych mniejszych od zadanej wartoci, mona zrealizowa sprawdzajc dla kolejnych liczb naturalnych, czy s one pierwsze. Aby tego dokona, naley a a upewni si, e liczba nie ma adnych dzielnikw rnych od 1 oraz jej samej. Istotna, ze wzgldu na zoono czasow algorytmu, s dwie obserwacje: a a jeli liczba m posiada jakikolwiek dzielnik rny od 1 i m, to posiada rwnie dzielnik mniejszy bd rwny m (dla kadego dzielnika k liczby m, m jest te dzielnikiem m) a k dziki temu wystarczy szuka dzielnikw liczby m w przedziale [2 . . . m]. jeli liczba m posiada dzielnik k nie bdcy liczb pierwsz, to posiada rwnie dzielnik a a a k < k, bdcy liczb pierwsz. a a a

Dziki tym dwm spostrzeeniom, wystarczy poszukiwa dzielnikw liczby m wrd liczb pierwszych mniejszych bd rwnych m. a Funkcja VI PrimeList(int) przedstawiona na listingu 5.20 realizuje opisany powyej algorytm, przyjmujc jako parametr liczb n, natomiast zwracajc jako wynik wektor liczb a a pierwszych mniejszych od n.

149

Listing 5.20: Implementacja funkcji VI PrimeList(int) // Funkcja wyznacza list liczb pierwszych mniejszych od n 01 VI PrimeList(int n) { // Stwrz list liczb pierwszych zawierajc liczb 2 02 VI w(1, 2); 03 int s = 0, i = 2; // Dla kolejnej liczby sprawd, czy jest pierwsz i jeli tak, to dodaj j do // listy 04 FOR(l, 3, n - 1) { 05 i = 0; 06 while (w[s] * w[s] <= l) s++; 07 while (i < s && l % w[i]) i++; // Nie znaleziono dzielnika liczby l - jest ona pierwsza 08 if (i == s) w.PB(l); 09 } 10 return w; 11 }

Opisana metoda wyznaczania listy liczb pierwszych nie jest jedyn moliw. Innym sposoa a bem jest np. wykorzystanie wyniku wyznaczonego przez algorytm sita Eratostenesa. Na jego podstawie w prosty sposb mona wyznaczy wszystkie liczby pierwsze mniejsze od zadanego ograniczenia. Metoda taka w praktyce okazuje si okoo 5-krotnie szybsza od algorytmu a a realizowanego przez funkcj VI PrimeList(int), ze wzgldu na wykonywan przez ni stosunkowo woln operacj arytmetyczn liczenia reszty z dzielenia. a a Przedstawiona na listingu 5.21 funkcja VI PrimeListS(int) wyznacza liczby pierwsze w oparciu o algorytm sita. Jest ona szybsza od VI PrimeList(int), jednak zuywa rwnie wicej pamici. Jeli wyznaczanie liczb pierwszych wykonywane jest w zakresie nie wikszym ni [1 . . . 2 000 000 000] oraz mamy wystarczajco duo pamici, to nowo opisana metoda dziaa a istotnie lepiej od poprzedniego rozwizania. a
Listing 5.21: Implementacja funkcji VI PrimeListS(int) // Funkcja wyznacza list liczb pierwszych mniejszych od n, wykorzystujc do // tego sito Eratostenesa 1 VI PrimeListS(int s) { 2 bit vector l = Sieve(++s); 3 VI V; 4 REP(x, s) if (l[x]) V.PB(x); 5 return V; 6}

Listing 5.22: Przykad dziaania funkcji VI PrimeListS(int)

Lista liczb pierwszych mniejszych od 100: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97

150

Listing 5.23: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.22. Peny kod rdowy programu znajduje si w pliku primelist.cpp 01 int main() { 02 int n; // Wczytaj analizowany zakres 03 cin >> n; // Wyznacz list liczb pierwszych oraz wypisz wynik 04 VI l = PrimeListS(n); 05 int count = 0; 06 cout << "Lista liczb pierwszych mniejszych od " << n << ":" << endl; 07 FOREACH(it, l) { 08 if (count++) cout << ", "; 09 if (!(count % 20)) cout << endl; 10 cout << *it; 11 } 12 return 0; 13 }

5.8. Test pierwszoci


Rozwaalimy ju problem wyznaczania wszystkich liczb pierwLiteratura szych mniejszych od pewnej liczby n. W tym podrozdziale po[WDA] - 33.8 stawimy sobie inny problem dla danej liczby n naley spraw[RII] - 11 dzi, czy jest ona pierwsza. Teoretycznie, do rozwizania tego proa [TLK] - V blemu mona zastosowa jeden z algorytmw z poprzednich dwch rozdziaw, jednak podejcie takie zaaplikowane wprost byoby wyjtkowo nieefektywne. Istnieje jednak moliwo zaadoptowania poprzedniego pomysu. a Korzystajc z wasnoci dzielnikw liczb, ktre wykorzystalimy do konstrukcji algorytmu a realizowanego przez funkcj VI PrimeList(int), mona dla danej liczby n sprawdzi, czy nie ma ona adnych dzielnikw mniejszych bd rwnych n. Naley na wstpie zauwaa y jednak, e taki algorytm ma zoono O( n), co jest funkcj wykadnicz w stosunku a a do liczby cyfr wystpujcych w liczbie n. Algorytm ten zosta zrealizowany w funkcji bool a IsPrime(int), ktrej kod rdowy zosta przedstawiony na listingu 5.24. Jako parametr, funkcja przyjmuje liczb n, a zwraca prawd, jeli n jest liczb pierwsz. a a
Listing 5.24: Implementacja funkcji bool IsPrime(int) // Funkcja sprawdza, czy liczba n jest pierwsza 1 bool IsPrime(int n) { 2 for(int x=0; x * x <= n; x++) 3 if (!(n%x)) return 0; 4 return 1; 5}

Jak ju zauwaylimy wczeniej, algorytm realizowany przez funkcj bool IsPrime(int) ma zoono wykadnicz. Przez dugi czas nie by znany aden deterministyczny algorytm wiea lomianowy, pozwalajcy na stwierdzenie czy dana liczba jest pierwsza. Wprawdzie istniay a 151

rne randomizacyjne algorytmy, ktre w praktyce dziaay bardzo dobrze (takie jak test Millera - Rabina), jednak z teoretycznego punktu widzenia bardzo wanym byo pokazanie istnienia algorytmu deterministycznego. Dopiero w 2002 roku trzech naukowcw Manindra Agrawal, Neeraj Kayal oraz Nitin Saxena przedstawili algorytm wielomianowy o nazwie AKS (jak mona si domyle, nazwa pochodzi od nazwisk jego autorw). Niebagatelna zoono tego algorytmu O(log12 (n)) oraz bardzo skomplikowana implementacja powoduje jego ma przydatno praktyczn. O algorytmie tym mona przeczyta w oryginalnej pracy a a jego autorw Primes is in P [AKS]. Wielomianow metod sprawdzania pierwszoci liczb, ktr przedstawimy, jest test Mila a a lera - Rabina. Zasada dziaania tego algorytmu bazuje na maym twierdzeniu Fermata, ktre mwi, e jeeli p jest liczb pierwsz, to: a a Dobierajc za liczb a = 2, mona wykaza, e liczba 875 jest zoona: a 2875 (mod 875) = 193 (mod 875) zatem na mocy maego twierdzenia Fermata, liczba 875 jest zoona. Wybierajc do testu a liczb 17 otrzymujemy: 217 (mod 17) = 2 (mod 17) zatem mamy prawo podejrzewa, e 17 jest liczb pierwsz, co jest w istocie prawd. Jednak a a a sprawdzajc w ten sposb liczb 341, otrzymujemy: a 2341 (mod 341) = 2 (mod 341) pomimo tego, e 341 jest zoona 341 == 11 31. Dobierajc jednak inn wartoci liczby a a a = 3, otrzymamy: 3341 (mod 341) = 168 (mod 341) co jest wiadectwem tego, e liczba 341 jest zoona. Mona wykaza, e prawdopodobiestwo uzyskania takiej sytuacji, jaka miaa miejsce w przypadku pary liczb n = 341, a = 2 nie a przekracza 1 , zatem wykonujc k testw z rnymi wartociami liczby a, prawdopodobiestwo 4 otrzymania za kadym razem zej odpowiedzi wynosi 1 . 4 Niestety istniej liczby zoone n, dla ktrych wasno an a (mod n) zachodzi dla a kadego a. Liczby takie nazywane s liczbami Carmichaela. Najmniejsz z nich jest 561. a a Algorytm do testowania pierwszoci liczb bazujcy na maym twierdzeniu Fermata, zawsze a bdzie dawa z odpowied dla tych liczb. Na szczcie istnieje moliwo poprawienia tego a algorytmu poprzez dodanie dodatkowego testu, zwanego testem Millera. Podstaw tego testu a jest wasno liczb pierwszych postaci: gdzie p jest liczb pierwsz. Przy uyciu tej wasnoci mona wykaza, e liczba 561 jest a a zoona. Mianowicie: Algorytm Millera - Rabina przeprowadza seri testw bazujcych na maym twierdzea niu Fermata, wykonujc jednoczenie testy Millera dla kolejnych potg liczby a wyznaa czanych podczas szybkiego potgowania. Implementacja tego algorytmu zostaa przedstawiona na listingu 5.25 jako funkcja bool IsPrime(int). Funkcja ta wykonuje testy dla liczb a = {2, 3, 5, 7}, co gwarantuje jej prawidowe dziaanie dla wszystkich liczb z zakresu [1 . . . 2 000 000]. 152 5560 = (5280 )2 1 (mod 561) oraz 5280 67 (mod 561) x2 1 (mod p) x 1 (mod p)
k

ap a (mod p)

Listing 5.25: Implementacja funkcji bool IsPrime(int) // Funkcja przeprowadza test Millera-Rabina dla liczby x przy podstawie n 01 bool PWit(LL x, LL n) { 02 if (x >= n) return 0; 03 LL d = 1, y; 04 int t = 0, l = n - 1; 05 while (!(l & 1)) { 06 ++t; 07 l >>= 1; 08 } 09 for (; l > 0 || t--; l >>= 1) { 10 if (l & 1) d = (d * x) % n; 11 if (!l) { 12 x = d; 13 l = -1; 14 } 15 y = (x * x) % n; // Jeli test Millera wykry, e liczba nie jest pierwsza, to zwr prawd 16 if (y == 1 && x != 1 && x != n - 1) return 1; 17 x = y; 18 } // Jeli nie jest spenione zaoenie twierdzenia Fermata, to liczba jest // zoona 19 return x != 1; 20 } // Funkcja sprawdza, czy dana liczba x jest pierwsza. W tym celu wykonuje test // Millera-Rabina przy podstawie 2, 3, 5, 7 21 bool IsPrime(int x) { 22 return !(x < 2 || PWit(2, x) || PWit(3, x) || PWit(5, x) || PWit(7, x)); 23 }

Listing 5.26: Wyznaczanie liczb pierwszych na przedziale [2 000 000 000 . . .2 000 000 100] przy uyciu funkcji bool IsPrime(int)

2000000011 2000000099

2000000033

2000000063

2000000087

2000000089

Listing 5.27: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.26. Peny kod rdowy programu znajduje si w pliku isprime.cpp 1 int main() { 2 int b, e, count = 0; // Wczytaj przedzia testowanych liczb 3 cin >> b >> e; // Dla kadej liczby z przedziau sprawd, czy jest pierwsza 4 while (b < e) { 5 if (IsPrime(b)) cout << b << (!(++count % 5) ? "\n" : "\t"); 6 b++; 7 }

153

Listing 5.27: (c.d. listingu z poprzedniej strony) 8 return 0; 9}

Proste acm.sgu.ru - zadanie acm.uva.es - zadanie acm.uva.es - zadanie acm.uva.es - zadanie acm.uva.es - zadanie acm.uva.es - zadanie

113 382 160 406 543 136

rednie acm.sgu.ru - zadanie 116 acm.uva.es - zadanie 294 acm.sgu.ru - zadanie 231 acm.uva.es - zadanie 10168 acm.uva.es - zadanie 583 acm.uva.es - zadanie 10235

wiczenia

Trudne acm.uva.es - zadanie 10139 acm.sgu.ru - zadanie 200 spoj.sphere.pl - zadanie 2 spoj.sphere.pl - zadanie 134 spoj.sphere.pl - zadanie 288

5.9. Arytmetyka wielkich liczb


Zdarzaj si zadania, w ktrych zakres standardowych typw a Literatura zmiennych jest niewystarczajcy. W takich przypadkach pojawia a [SZP] - 4.4, 4.5.1 si konieczno implementowania wasnej arytmetyki wielkich liczb. W zalenoci od rozwizywanego zadania, implementacja ta poa winna wspiera wykonywanie pewnych operacji arytmetycznych. Wielkie liczby zazwyczaj przechowuje si w postaci tablicy cyfr. Ze wzgldw wydajnociowych, nie reprezentuje si ich przy podstawie 10, lecz istotnie wikszej, co ma na celu zmniejszenie liczby cyfr, a tym samym zwikszenie wydajnoci wykonywanych operacji. Dodawanie mona w prosty sposb zrealizowa, dodajc po kolei cyfry liczby z odpowiednich pozycji oraz wykonujc przenoszenie a a powstajcego nadmiaru. Podobnie realizowane jest mnoenie mnoy si warto wszystkich a par cyfr, a potem dodaje uzyskane wartoci (biorc pod uwag pozycje, na ktrych wystpuj a a mnoone pary cyfr). Przedstawiona w tym rozdziale implementacja wielkich liczb zrealizowana jako struka tura BigNum z listingu 5.28, ma do dug implementacj, co wynika z duej liczby wspomaganych operacji. Implementacja zostaa zrealizowana w taki sposb, aby umoliwi zminimalizowanie iloci kodu, ktry trzeba przepisa, aby zapewni sobie wymagan funkcjoa nalno struktury. Implementacja zostaa podzielona na sekcje: cz podstawowa, ktra jest zawsze wymagana; operatory porwnawcze pozwalajce na dokonywanie porwnywania wara toci liczb; operacje na parze (wielka liczba, zmienna typu int); operatory dziaajce na a parach (wielka liczba, wielka liczba), oraz operatory pozwalajce na wczytywanie i wypisya wanie wartoci wielkich liczb. Implementacja niektrych operatorw wykorzystuje inne w takich przypadkach komentarze znajdujce si przed implementacj operatora informuj, jaa a a kie inne operatory naley rwnie zaimplementowa. Najlepszym sposobem przeanalizowania sposobu dziaania arytmetyki BigNum jest przeledzenie jej kodu rdowego oraz komentarzy. Poniej znajduje si lista wspomaganych operacji, wraz z ich krtkim opisem. Zakadamy, e w przypadku opisu operatorw, liczba cyfr liczb, na ktrych wykonywane s operacje, to oda powiednio n i m. Przez p bdzie oznaczana natomiast podstawa, przy ktrej wykonywane s a operacje na wielkich liczbach. Bignum(int a, int b) konstruktor tworzcy now wielk liczb rwn a, majc a a a a a a zaalokowan pami na b cyfr. W przypadku, gdy na skutek wykonywanych operacji, a 154

liczba cyfr przekroczy przeznaczon na nie pami, nastpi automatyczne zaalokowanie a a nowej pamici. Jeli z gry wiemy, e wynik ktry obliczamy bdzie mia przykadowo 500 cyfr, to ze wzgldw efektywnociowych, naley poinformowa o tym konstruktor; BigNum(const BigNum&) konstruktor tworzcy now liczb rwn innej, istniejcej a a a a ju wielkiej liczbie; res(int) funkcja suy do alokowania nowej pamici dla cyfr (o ile zachodzi taka potrzeba); przen(int) implementacja typu BigNum wykorzystuje do przechowywania cyfr zmiennych typu long long oraz podstawy 1 000 000 000. Dziki temu, podczas wykonywania operacji dodawania czy mnoenia dwch liczb, nie nastpuj przepenienia wartoci a zmiennych i operatory nie musz zajmowa si przenoszeniem nadmiaru do starszych a cyfr. Procesem tym, po zakoczeniu wykonywania operacji arytmetycznej, zajmuje si funkcja przen(int), ktrej parametrem jest liczba cyfr (liczc od najmniej znaczcej), a a dla ktrych naley wykona przeniesienie; operatory porwnawcze dwch liczb typu BigNum: ==, <, >, <=, >=, != ich znaczenie jest standardowe, a zoono czasowa to O(min(n, m)); BigNum& operator=(int) przypisanie wartoci zmiennej typu int do wielkiej liczby. Zoono to O(n) (liczba cyfr wielkiej liczby, ktrej wykonywana jest zmiana wartoci); operatory void operator+=(int) oraz void operator-=(int) ich pesymistyczna zoono zaley od wykonywanych przenosze, koszt zamortyzowany wykonania sekwencji inkrementacji i dekrementacji to O(1); operatory void operator*=(int), int operator/=(int) oraz int operator %(int) ich zoono to O(n). Operator int operator/=(int) do nietypowo, dzieli warto wielkiej liczby przez zadany parametr oraz zwraca reszt z dzielenia; BigNum& operator +=(const BigNum&), BigNum operator +(const BigNum&), BigNum& operator -=(const BigNum&) oraz BigNum operator -(const BigNum&) ich zamortyzowana zoono to O(m); BigNum& operator *=(const BigNum&), BigNum operator *(const BigNum &) ich zoono to O(n m); BigNum& operator /=(const BigNum&), BigNum operator /(const BigNum&), BigNum& operator %=(const BigNum&) oraz BigNum operator %(const BigNum&) ich zoono to O(n m log(p)); BigNum& operator =(const BigNum&) zoono tego operatora to O(n + m); BigNum& operator =(string) operator ten przypisuje zmiennej BigNum warto liczby reprezentowanej przez zadany tekst. Jego zoono jest liniowa ze wzgldu na dugo tekstu; void write() oraz void write(char*) wypisywanie wartoci liczby na standardowe wyjcie / do bufora. Zoono to O(n); BigNum& operator<<=(int), BigNum operator<<(int), BigNum& operator>>=(int) oraz BigNum operator>>(int) dokonuj przesunia cia cyfr liczby o zadan liczb pozycji; a 155

BigNum sqrt() wyznacza pierwiastek cakowity z wielkiej liczby. Jego zoono to O(n2 log(p)).
Listing 5.28: Implementacja struktury BigNum

// Implementacja struktury BigNum, realizujcej arytmetyk wielkich liczb 001 struct BigNum { // Makro suce do eliminowania wiodcych zer 002 #dene REDUCE() while(len>1 && !cyf[len-1]) len; // Podstawa, przy ktrej wykonywane s obliczenia oraz liczba zer w podstawie 003 static const int BASE = 1000000000, BD = 9; // Zmienna len reprezentuje aktualn dugo liczby (liczb cyfr), a al // wielko zaalokowanej pamici do przechowywania cyfr liczby 004 int len, al; // Wskanik do tablicy cyfr liczby 005 LL *cyf; // Konstruktor liczby o wartoci v i zaalokowanej pamici dla l cyfr 006 BigNum(int v = 0, int l = 2) : len(1), al(l), cyf(new LL[l]) { 007 REP(x, al) cyf[x] = 0; 008 if ((cyf[0] = v) >= BASE) przen(1); 009 } // Konstruktor, przypisujcy warto innej liczby typu BigNum 010 BigNum(const BigNum & a) : len(a.len), al(len), cyf(new LL[al]) { 011 REP(x, al) cyf[x] = a.cyf[x]; 012 } // Destruktor 013 BigNum() { 014 delete cyf; 015 } // Funkcja przyjmuje jako parametr zapotrzebowanie na liczb cyfr i jeli // zapotrzebowanie jest wiksze od aktualnego rozmiaru tablicy cyfr, to dokonuje // realokacji 016 void Res(int l) { 017 if (l > al) { 018 LL *n = new LL[l = max(l, 2 * al)]; 019 REP(x, l) n[x] = x >= al ? 0 : cyf[x]; 020 delete cyf; 021 cyf = n; 022 al = l; 023 } 024 } // Funkcja dokonuje przenoszenia do starszych cyfr nadmiaru powstaego na skutek // wykonywania operacji. Jest to jedyne miejsce w caej strukturze, gdzie // wykonuje si t operacj. Parametr okrela liczb cyfry, do ktrej naley // wykona przenoszenie nadmiaru 025 void przen(int p) { 026 int x = 0; 027 for (; x < p || cyf[x] < 0 || cyf[x] >= BASE; x++) { 028 Res(x + 2); // W razie potrzeby wykonaj zapoyczenie od starszej cyfry...

156

Listing 5.28: (c.d. listingu z poprzedniej strony) 029 if (cyf[x] < 0) { 030 LL i = (-cyf[x] - 1) / BASE + 1; 031 cyf[x] += i * BASE; 032 cyf[x + 1] -= i; 033 } else // lub wykonaj przeniesienie powstaego nadmiaru 034 if (cyf[x] >= BASE) { 035 LL i = cyf[x] / BASE; 036 cyf[x] -= i * BASE; 037 cyf[x + 1] += i; 038 } 039 } 040 len = max(len, x + 1); 041 REDUCE(); 042 } // Od tego miejsca zaczyna si implementacja operatorw. Przepisywanie tej // czci kodu nie jest wymagane - naley przepisywa tylko te operatory, z // ktrych si korzysta. Niektre operatory korzystaj z innych - w takich // przypadkach, przy kadym operatorze napisane jest, implementacji jakich // operatorw on wymaga // Ponisze makro pozwala skrci zapis nagwkw operatorw 043 #dene OPER1(op) bool operator op (const BigNum &a) const // Operatory porwnawcze 044 OPER1(==) { 045 if (a.len != len) return 0; 046 REP(x, len) if (cyf[x] != a.cyf[x]) return 0; 047 return 1; 048 } 049 OPER1(<) { 050 if (len != a.len) return len < a.len; 051 int x = len - 1; 052 while (x && a.cyf[x] == cyf[x]) x--; 053 return cyf[x] < a.cyf[x]; 054 } // Operator ten wymaga implementacji operatora <(BigNum) 055 OPER1(>) { 056 return a < *this; 057 } // Operator ten wymaga implementacji operatora <(BigNum) 058 OPER1(<=) { 059 return !(a < *this); 060 } // Operator ten wymaga implementacji operatora <(BigNum) 061 OPER1(>=) { 062 return !(*this < a); 063 } // Operator ten wymaga implementacji operatora ==(BigNum) 064 OPER1(!=) {

157

Listing 5.28: (c.d. listingu z poprzedniej strony) 065 return !(*this == a); 066 } // Operacje dla liczb typu int 067 BigNum & operator=(int a) { 068 REP(x, len) cyf[x] = 0; 069 len = 1; 070 if (cyf[0] = a >= BASE) przen(1); 071 return *this; 072 } 073 void operator+=(int a) { 074 cyf[0] += a; 075 przen(1); 076 } 077 void operator-=(int a) { 078 cyf[0] -= a; 079 przen(1); 080 } 081 void operator*=(int a) { 082 REP(x, len) cyf[x] *= a; 083 przen(len); 084 } // Poniszy operator zwraca jako wynik reszt z dzielenia liczby typu BigNum // przez liczb typu int 085 int operator/=(int a) { 086 LL w = 0; 087 FORD(p, len - 1, 0) { 088 w = w * BASE + cyf[p]; 089 cyf[p] = w / a; 090 w %= a; 091 } 092 REDUCE(); 093 return w; 094 } 095 int operator%(int a) { 096 LL w = 0; 097 FORD(p, len - 1, 0) w = (w * BASE + cyf[p]) % a; 098 return w; 099 } // Operacje wycznie na liczbach typu BigNum 100 #dene OPER2(op) BigNum& operator op (const BigNum &a) 101 OPER2(+=) { 102 Res(a.len); 103 REP(x, a.len) cyf[x] += a.cyf[x]; 104 przen(a.len); 105 return *this; 106 } 107 OPER2(-=) { 108 REP(x, a.len) cyf[x] -= a.cyf[x];

158

Listing 5.28: (c.d. listingu z poprzedniej strony) 109 przen(a.len); 110 return *this; 111 } 112 OPER2(*=) { 113 BigNum c(0, len + a.len); 114 REP(x, a.len) { 115 REP(y, len) c.cyf[y + x] += cyf[y] * a.cyf[x]; 116 c.przen(len + x); 117 } 118 *this = c; 119 return *this; 120 } // Operator ten wymaga implementacji nastpujcych operatorw: <(BigNum), // +=(BigNum), *=(BigNum), +(BigNum), *(BigNum), <<(int), // <<=(int) 121 OPER2(/=) { 122 int n = max(len - a.len + 1, 1); 123 BigNum d(0, n), prod; 124 FORD(i, n - 1, 0) { 125 int l = 0, r = BASE - 1; 126 while (l < r) { 127 int m = (l + r + 1) / 2; 128 if (*this < prod + (a * m << i)) r = m - 1; 129 else l = m; 130 } 131 prod += a * l << i; 132 d.cyf[i] = l; 133 if (l) d.len = max(d.len, i + 1); 134 } 135 *this = d; 136 return *this; 137 } // Operator ten wymaga implementacji nastpujcych operatorw: <(BigNum), // +=(BigNum), *=(BigNum), +(BigNum), *(BigNum), <<(BigNum), // <<=(BigNum) 138 OPER2(%=) { 139 BigNum v = *this; 140 v /= a; 141 v *= a; 142 *this -= v; 143 return *this; 144 } 145 OPER2(=) { 146 Res(a.len); 147 FORD(x, len - 1, a.len) cyf[x] = 0; 148 REP(x, a.len) cyf[x] = a.cyf[x]; 149 len = a.len; 150 return *this;

159

Listing 5.28: (c.d. listingu z poprzedniej strony) 151 } // Operatory suce do wczytywania i wypisywania liczb // Funkcja przypisuje liczbie BigNum warto liczby z przekazanego wektora, // zapisanej przy podstawie p // Operator ten wymaga implementacji +=(int), *=(int) 152 void read(const VI & v, int p) { 153 *this = 0; 154 FORD(x, SIZE(v), 0) { 155 *this *= p; 156 *this += v[x]; 157 } 158 } // Funkcja przypisuje liczbie BigNum warto liczby z napisu zapisanego przy // podstawie 10 // Operator ten wymaga implementacji =(int) 159 BigNum & operator=(string a) { 160 int s = a.length(); 161 *this = 0; 162 Res(len = s / BD + 1); 163 REP(x, s) cyf[(s - x - 1) / BD] = 10 * cyf[(s - x - 1) / BD] + a[x] - 0; 164 REDUCE(); 165 return *this; 166 } // Funkcja wypisuje warto liczby BigNum zapisanej przy podstawie 10 167 void write() const { 168 printf("%d", int (cyf[len - 1])); 169 FORD(x, len - 2, 0) printf("%0*d", BD, int (cyf[x])); 170 } // Funkcja wypisuje do przekazanego bufora warto liczby zapisanej przy // podstawie 10 171 void write(char *buf) const { 172 int p = sprintf(buf, "%d", int (cyf[len - 1])); 173 FORD(x, len - 2, 0) p += sprintf(buf + p, "%0*d", BD, int (cyf[x])); 174 } // Funkcja zwraca wektor cyfr liczby zapisanej przy podstawie pod. Funkcja ta // wymaga implementacji /=(int), =(BigNum) 175 VI write(int pod) const { 176 VI w; 177 BigNum v; 178 v = *this; 179 while (v.len > 1 || v.cyf[0]) w.PB(v /= pod); 180 return w; 181 } // Operator przesunicia w prawo o n cyfr 182 BigNum & operator>>=(int n) { 183 if (n >= len) n = len; 184 REP(x, len - n) cyf[x] = cyf[x + n]; 185 FOR(x, len - n, n) cyf[x] = 0;

160

Listing 5.28: (c.d. listingu z poprzedniej strony) 186 len -= n; 187 if (len == 0) len = 1; 188 return *this; 189 } // Operator przesunicia w lewo 190 BigNum & operator<<=(int n) { 191 if (cyf[0] == 0 && len == 1) return *this; 192 Res(len + n); 193 FORD(x, len - 1, 0) cyf[x + n] = cyf[x]; 194 REP(x, n) cyf[x] = 0; 195 len += n; 196 return *this; 197 } // Funkcja wyznaczajca pierwiastek cakowity z liczby // Funkcja ta wymaga implementacji <(BigNum), +=(BigNum), *=(BigNum), // <<=(int), +(BigNum), *(BigNum), <<(int) 198 BigNum sqrt() { 199 int n = (len + 1) / 2; 200 BigNum a(0, n), sq; 201 FORD(i, n - 1, 0) { 202 int l = 0, r = BASE - 1; 203 while (l < r) { 204 int m = (l + r + 1) / 2; 205 if (*this < sq + (a * 2 * m << i) + (BigNum(m) * m << 2 * i)) r = m 1; 206 else l = m; 207 } 208 sq += (a * 2 * l << i) + (BigNum(l) * l << 2 * i); 209 a.cyf[i] = l; 210 a.len = n; 211 } 212 return a; 213 } // Makra pozwalajce na skrcenie zapisu nagwkw poniszych operatorw 214 #dene OPER3(op) BigNum operator op(const BigNum &a) \ 215 const {BigNum w=*this; w op ## = a; return w; } 216 #dene OPER4(op) BigNum operator op(int a) \ 217 {BigNum w = *this; w op ## = a; return w; } // Operator wymaga implementacji +=(BigNum) 218 OPER3(+); // Operator wymaga implementacji -=(BigNum) 219 OPER3(-); // Operator wymaga implementacji *=(BigNum) 220 OPER3(*); // Operator wymaga implementacji <(BigNum), +=(BigNum), *=(BigNum), // /=(BigNum), +(BigNum), *(BigNum), <<(int) 221 OPER3(/); // Operator wymaga implementacji <(BigNum), +=(BigNum), -=(BigNum),

161

Listing 5.28: (c.d. listingu z poprzedniej strony) // *=(BigNum), /=(BigNum), %=(BigNum), +(BigNum), *(BigNum) 222 OPER3(%); // Operator wymaga implementacji <<=(int) 223 OPER4(<<); // Operator wymaga implementacji >>=(int) 224 OPER4(>>); 225 };

Listing 5.29: Przykad wykorzystania arytmetyki wielkich liczb

a = 348732498327423984324198321740932174 b = 7653728101928872838232879143214 a+b = 348740152055525913197036554620075388 a-b = 348724844599322055451360088861788960 a*b = 2669103722504468593268937284375465614778291815203334805554806367236 a/b = 45563 sqrt(a) = 590535772267374150

Listing 5.30: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.29. Peny kod rdowy programu znajduje si w pliku bignum.cpp 01 int main() { 02 BigNum a, b; 03 string ta, tb; // Wczytaj tekst reprezentujcy dwie wielkie liczby 04 cin >> ta >> tb; // Przekonwertuj tekst na liczby oraz wykonaj podstawowe operacje 05 a = ta; 06 b = tb; 07 cout << "a = "; 08 a.write(); 09 cout << endl << "b = "; 10 b.write(); 11 cout << endl << "a+b = "; 12 (a + b).write(); 13 cout << endl << "a-b = "; 14 (a - b).write(); 15 cout << endl << "a*b = "; 16 (a * b).write(); 17 cout << endl << "a/b = "; 18 (a / b).write(); 19 cout << endl << "sqrt(a) = "; 20 a.sqrt().write(); 21 return 0; 22 }

162

Arytmetyka wielkich liczb realizowana przez struktur BigNum pozwala na wykonywanie operacji na zbiorze liczb naturalnych. Praktycznie we wszystkich zadaniach, arytmetyka taka jest wystarczajca, jednak czasem pojawia si potrzeba wykonywania operacji na zbiorze a liczb cakowitych. W tym celu zostaa stworzona struktura Integer, ktrej implementacja wykorzystuje struktur BigNum. Sposb dziaania operatorw jest w zasadzie taki sam jak w przypadku struktury BigNum. Kod rdowy przedstawiony jest na listingu 5.31.
Listing 5.31: Implementacja struktury Integer // Implementacja liczb cakowitych bazujca na typie BigNum 01 struct Integer { 02 BigNum x; // Znak liczby - -1 dla liczby ujemnej, 0 dla zera, 1 dla liczby // dodatniej 03 int Sgn; // Konstruktor, przyjmujcy jako parametry warto bezwzgldn oraz znak // tworzonej liczby 04 Integer(const BigNum & a, int znak = 1) { 05 x = a; 06 Sgn = (a == BigNum(0)) ? 0 : !znak ? 1 : znak; 07 } // Konstruktor tworzcy zmienn rwn liczbie a 08 Integer(int a = 0) { 09 x = BigNum(abs(a)); 10 Sgn = sgn(a); 11 } // Operator zwraca liczb o przeciwnym znaku 12 Integer operator-() const { 13 return Integer(x, -Sgn); 14 } // Operatory porwnawcze 15 bool operator<(const Integer & b) const { 16 if (Sgn != b.Sgn) return Sgn < b.Sgn; 17 return (Sgn == -1) ? b.x < x : x < b.x; 18 } 19 bool operator==(const Integer & b) const { 20 return Sgn == b.Sgn && x == b.x; 21 } // Makra pozwalajce na skrcenie zapisu nagwkw operatorw 22 #dene OPER5(op) Integer operator op (const Integer &b) const 23 #dene OPER6(op) Integer &operator op ## = (const Integer &b) \ 24 {return *this = *this op b;} // Operator +(Integer) 25 OPER5(+) { 26 if (Sgn == -1) return -(-(*this) + (-b)); 27 if (b.Sgn >= 0) return Integer(x + b.x, min(1, Sgn + b.Sgn)); 28 if (x < b.x) return Integer(b.x - x, -1); 29 return Integer(x - b.x, x > b.x); 30 } // Operator -(Integer), wykorzystuje +(Integer)

163

Listing 5.31: (c.d. listingu z poprzedniej strony) 31 OPER5(-) { 32 return *this + (-b); 33 } // Operator *(Integer) 34 OPER5(*) { 35 return Integer(x * b.x, Sgn * b.Sgn); 36 } // Operator /(Integer) 37 OPER5(/) { 38 return Integer(x / b.x, Sgn * b.Sgn); 39 } // Operator %(Integer), wykorzystuje -(Integer) 40 OPER5(%) { 41 return Sgn == -1 ? Integer((b.x - (x % b.x)) % b.x) : Integer(x % b.x); 42 } // Operator +=(Integer), wykorzystuje +(Integer) 43 OPER6(+) // Operator -=(Integer), wykorzystuje -(Integer) 44 OPER6(-) // Operator *=(Integer), wykorzystuje *(Integer) 45 OPER6(*) // Operator /=(Integer), wykorzystuje /(Integer) 46 OPER6(/) // Operator %=(Integer), wykorzystuje %(Integer) 47 OPER6(%) // Funkcja wypisuje liczb Integer zapisan przy podstawie 10 na // standardowe wyjcie 48 void write() const { 49 if (Sgn == -1) printf("-"); 50 x.write(); 51 } 52 };

Dopenieniem do przedstawionych dotychczas arytmetyk wielkich liczb jest struktura Frac, ktra umoliwia wykonywanie operacji na liczbach wymiernych postaci p . Struktura ta zaq wiera implementacj najczciej wykorzystywanych operatorw. Kod rdowy znajduje si na listingu 5.32.
Listing 5.32: Implementacja struktury Frac // Funkcja wyznaczajca najwikszy wsplny dzielnik dwch liczb. Jest ona uywana // do skracania uamkw 01 BigNum NatGCD(const BigNum & a, const BigNum & b) { 02 return b == BigNum(0) ? a : NatGCD(b, a % b); 03 } // Implementacja liczb uamkowych, bazujca na typie Integer oraz BigNum 04 struct Frac { // Licznik a oraz mianownik b uamka

164

Listing 5.32: (c.d. listingu z poprzedniej strony) 05 Integer a, b; // Konstruktor typu Frac, tworzy liczb o zadanym liczniku i mianowniku 06 Frac(const Integer & aa = 0, const Integer & bb = 1) { 07 a = aa; 08 b = bb; 09 if (b < 0) { a = -a; 10 b = -b; 11 } 12 Integer d = Integer(NatGCD(aa.x, bb.x)); 13 a /= d; 14 b /= d; 15 } // Operatory porwnawcze 16 bool operator<(const Frac & x) const { 17 return a * x.b < x.a * b; 18 } 19 bool operator==(const Frac & x) const { 20 return a == x.a && b == x.b; 21 } // Makra pozwalajce na skrcenie zapisu nagwkw operatorw 22 #dene OPER7(op) Frac operator op (const Frac &x) const 23 #dene OPER8(op) Frac &operator op ## =(const Frac &b) {return *this = *this op b;} // Operator +(Frac) 24 OPER7(+) { 25 return Frac(a * x.b + b * x.a, b * x.b); 26 } // Operator -(Frac) 27 OPER7(-) { 28 return Frac(a * x.b - b * x.a, b * x.b); 29 } // Operator *(Frac) 30 OPER7(*) { 31 return Frac(a * x.a, b * x.b); 32 } // Operator /(Frac) 33 OPER7(/) { 34 return Frac(a * x.b, b * x.a); 35 } // Operator +=(Frac), wykorzystuje +(Integer) 36 OPER8(+) // Operator -=(Frac), wykorzystuje -(Integer) 37 OPER8(-) // Operator *=(Frac), wykorzystuje *(Integer) 38 OPER8(*) // Operator /=(Frac), wykorzystuje /(Integer) 39 OPER8(/) // Funkcja zwraca prawd, jeli dany uamek jest rwny 0

165

Listing 5.32: (c.d. listingu z poprzedniej strony) 40 bool isZero() { 41 return a == Integer(0); 42 } // Funkcja wypisujca uamek postaci licznik/mianownik zapisany przy // podstawie 10 na standardowe wyjcie 43 void write() { 44 a.write(); 45 printf("/"); 46 b.write(); 47 } 48 };

Listing 5.33: Przykad uycia arytmetyki uamkowej

a = 2/3 b = 15/28 a+b = 101/84 a-b = 11/84 a*b = 5/14 a/b = 56/45

Listing 5.34: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.33. Peny kod rdowy programu znajduje si w pliku frac.cpp 01 int main() { // Skonstruuj dwie liczby typu Frac 02 Frac a(10, 15); 03 Frac b(30, 56); 04 cout << "a = "; 05 a.write(); 06 cout << endl << "b = "; 07 b.write(); 08 cout << endl << "a+b = "; 09 (a + b).write(); 10 cout << endl << "a-b = "; 11 (a - b).write(); 12 cout << endl << "a*b = "; 13 (a * b).write(); 14 cout << endl << "a/b = "; 15 (a / b).write(); 16 return 0; 17 }

W rozdziale dotyczcym sprawdzania pierwszoci liczb przedstawilimy implementacj tea stu Millera - Rabina, ktra dla dowolnej liczby naturalnej nie wikszej od 2 000 000 odpowiada, czy liczba ta jest pierwsza. Istnieje moliwo zaadoptowania tego algorytmu w celu 166

sprawdzania, czy dana wielka liczba jest pierwsza. Jedyna rnica w implementacji polega na zmianie liczby wykonywanych testw (w przypadku liczb z przedziau [1 . . . 2 000 000 000] wystarczyy tylko 4 2, 3, 5 oraz 7. Prawdopodobiestwo uzyskania bdnej odpowiedzi w l przypadku testowania liczby zoonej, wykonujc l testw wynosi ( 1 ) . Implementacja funka 4 cji bool IsBPrime(const BigNum&, int) przedstawiona na listingu 5.35 realizuje ten test pierwszoci.
Listing 5.35: Implementacja funkcji bool IsBPrime(const BigNum&, int) // Funkcja przeprowadza test Millera-Rabina dla liczby n przy podstawie x 01 bool PBWit(BigNum x, BigNum n) { 02 if (x >= n) return 0; 03 BigNum d = 1, y, l = n - 1; 04 int t = 0; 05 bool st = 0; 06 while ((l % 2) == 0) { 07 ++t; 08 l /= 2; 09 } 10 for (; l > 0 || t--; l /= 2) { 11 if ((l % 2) == 1) d = (d * x) % n; 12 else if (!st && l == 0) { 13 st = 1; 14 x = d; 15 } 16 y = (x * x) % n; 17 if (y == 1 && x != 1 && x != n - 1) return 1; 18 x = y; 19 } 20 return x != 1; 21 } // Funkcja sprawdza, czy dana liczba typu BigNum jest pierwsza. W tym celu // wykonuje t razy test Millera-Rabina 22 bool IsBPrime(const BigNum & x, int t) { 23 if (x < 2) return 0; 24 REP(i, t) if (PBWit(max(rand(), 2), x)) return 0; 25 return 1; 26 }

Listing 5.36: Wyznaczenie wszystkich liczb [1 000 000 000 000 000 000 000 . . .1 000 000 000 000 000 000 300] IsBPrime(const BigNum&, int)

pierwszych przy uyciu

w przedziale funkcji bool

1000000000000000000117 1000000000000000000193 1000000000000000000213 1000000000000000000217 1000000000000000000289

167

Listing 5.37: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 5.36. Peny kod rdowy programu znajduje si w pliku isbprime.cpp 01 int main() { 02 BigNum b, e; 03 string tb, te; // Wczytaj zakres testowanych liczb 04 cin >> tb >> te; 05 b = tb; 06 e = te; // Dla kadej liczby z zakresu sprawd, czy jest pierwsza 07 while (b < e) { 08 if (IsBPrime(b, 20)) { 09 b.write(); 10 cout << endl; 11 } 12 b += 1; 13 } 14 return 0; 15 }

Zadanie: acuch
Pochodzenie: VIII Olimpiada Informatyczna Rozwizanie: a chain.cpp

Bajtocja nie zawsze bya pastwem demokratycznym. W jej historii byy rwnie czarne karty. Pewnego razu, genera Bajtelski przywdca junty elazn rk rzdzcej Bajtocj a a a a a postanowi zakoczy stan wojenny, trwajcy od momentu przejcia wadzy, i zwolni a wizionych dziaaczy opozycji. Nie chcia jednak uwolni przywdcy opozycji Bajtazara. Postanowi przyku go do murw wizienia za pomoc bajtockiego acucha. Bajtocki acuch a skada si z poczonych ze sob ogniw oraz przymocowanego do muru prta. Cho ogniwa a a nie s poczone z prtem, to bardzo trudno jest je z niego zdj. a a a Generale, czemu mnie przyku do murw wizienia, miast uwolni, jako to uczynie z moimi kamratami! woa Bajtazar. Ale Bajtazarze, wszak nie jeste przykuty i z pewnoci potrasz sam zdj trzymajce a a a ci ogniwa z prta wystajcego z murw wizienia. przewrotnie stwierdzi genera Bajtelski, a po czym doda Uporaj si z tym jednak przed godzin cyfracyjn i nie dzwo acuchami a a po nocy, gdy w przeciwnym przypadku bd zmuszony wezwa funkcjonariuszy Cyfronicji Obywatelskiej. Pom Bajtazarowi! Ponumerujmy kolejne ogniwa acucha liczbami 1, 2, ..., n. Ogniwa te moemy zakada i zdejmowa z prta zgodnie z nastpujcymi zasadami: a jednym ruchem moemy zdj lub zaoy na prt tylko jedno ogniwo, a ogniwo nr 1 mona zawsze zdj lub zaoy na prt, a jeeli ogniwa o numerach 1, . . . , k 1 (dla 1 k n) s zdjte z prta, a ogniwo nr k a jest zaoone, to moemy zdj lub zaoy ogniwo nr k + 1. a

168

Zadanie
Napisz program, ktry: wczyta opis bajtockiego acucha, obliczy minimaln liczb ruchw, ktre naley wykona, aby zdj wszystkie ogniwa a a bajtockiego acucha z prta, wypisze wynik.

Wejcie
W pierwszym wierszu zapisano jedn dodatni liczb cakowit n, 1 n 10, 000. W drua a a gim wierszu zapisano n liczb cakowitych o1 , o2 , . . . , on 0, 1, pooddzielanych pojedynczymi odstpami. Jeli oi = 1, to ogniwo nr i jest zaoone na prt, a jeli oi = 0, to jest z niego zdjte.

Wyjcie
Twj program powinien wypisa jedn liczb cakowit, rwn minimalnej liczbie ruchw a a a potrzebnych do zdjcia wszystkich ogniw bajtockiego acucha z prta.

Przykad
4 1 0 1 0

Dla nastpujcego wejcia: a


6

Poprawnym rozwizaniem jest: a

Proste acm.uva.es - zadanie 424 acm.uva.es - zadanie 10018 acm.uva.es - zadanie 324 acm.uva.es - zadanie 10035 spoj.sphere.pl - zadanie 123 spoj.sphere.pl - zadanie 362 acm.sgu.ru - zadanie 112 spoj.sphere.pl - zadanie 94

rednie acm.uva.es - zadanie 10183 acm.uva.es - zadanie 495 spoj.sphere.pl - zadanie 54 spoj.sphere.pl - zadanie 328 acm.sgu.ru - zadanie 111 acm.sgu.ru - zadanie 193 acm.sgu.ru - zadanie 197 acm.sgu.ru - zadanie 299

wiczenia

Trudne acm.uva.es - zadanie 623 spoj.sphere.pl - zadanie 31 spoj.sphere.pl - zadanie 291 spoj.sphere.pl - zadanie 279 spoj.sphere.pl - zadanie 115 acm.sgu.ru - zadanie 247

169

Rozdzia 6

Struktury danych
Podstaw pomylnego rozwizania kadego zadania jest wya a Literatura mylenie odpowiedniego algorytmu. W przypadku podjcia dobrej [WDA] - 3 decyzji, rozwizanie zadania jest ju tylko kwesti czasu. Niestety, a a [ASP] - 4 podczas zawodw, trzeba si liczy dodatkowo ze cisymi ograniczeniami czasowymi. W takiej sytuacji nie tylko algorytm, ale rwnie i sposb implementacji jest niezwykle wany. Rozwizanie wielu zada wymaga uya cia rnego rodzaju struktur danych. Zazwyczaj istnieje moliwo wyboru pomidzy bardziej efektywn metod lecz trudniejsz do zaimplementowania oraz wolniejsz i prostsz. a a a a a Bardzo wan decyzj projektow, jest wybr odpowiedniej struktury danych, ktra jest wya a a starczajco efektywna, aby rozwizanie zmiecio si w limitach czasowych przygotowanych a a przez organizatorw, a jednoczenie jej implementacja bya jak najprostsza. Jest to niezwykle wana umiejtno, ktr nabywa si z czasem. Najlepsz decyzj jest po prostu uycie, a a a o ile to moliwe, gotowej struktury danych z biblioteki STL. Umiejtne wykorzystanie tej biblioteki pozwala skrci dugo implementowanego programu niekiedy i kilkukrotnie. W niektrych sytuacjach okazuje si jednak, e struktury danych udostpnione przez bibliotek STL s niewystarczajce. W aktualnym rozdziale przedstawimy implementacj kilku a a najczciej wykorzystywanych podczas zawodw struktur danych, ktre nie s udostpnione a w bibliotece STL.

171

0 1

0 1

0 2 1 4 4 5 3 5 2

0 2 1 4 3 5

4 3 5 3

(a)

(b)

(c)

(d)

Rysunek 6.1: (a) Reprezentacja struktury zbiorw rozcznych {0, 1, 3}, {2}, {4} oraz {5}. (b) Stan a
struktury danych po wykonaniu operacji zczenia zbiorw {2} i {4}. (c) Stan struktury a po zczeniu zbiorw {0, 1, 3} i {2, 4}. (d) Wyszukanie zbioru, do ktrego naley element a 4 spowodowao skompresowanie cieki od elementu 4 do korzenia jego drzewa.

6.1. Struktura danych do reprezentacji zbiorw rozcznych a


Zamy, e mamy dane n rozcznych zbiorw jednoelemena Literatura towych {0}, {1}, . . . , {n 1}. Chcielibymy wykonywa na tych [WDA] - 22 zbiorach dwie operacje: czenie ze sob dwch zbiorw oraz wya a [ASD] - 4 znaczanie zbioru, do ktrego naley dany element k {0, 1, . . . , n 1}. Jednym ze sposobw rozwizania tego zadania, jest przypisanie kadej liczbie k numeru a zbioru, do ktrego ona naley (na pocztku liczbie k przypisywany jest zbir o numerze a k). Sprawdzenie, do ktrego zbioru naley dany element sprowadza si wtedy do zwrcenia numeru przypisanego zbioru. Zczenie dwch zbiorw p i q natomiast mona zrealizowa a poprzez zamian numeru p we wszystkich elementach tego zbioru, numerem q. Wida, e wykonanie zczenia dwch zbiorw wymaga wyznaczenia wszystkich elementw nalecych do a a zczanych zbiorw, zatem operacja ta ma zoono liniow nie jest to zatem rozwizanie a a a szczeglnie efektywne. Efektywn, a zarazem do prost struktur danych, pozwalajc na wykonywanie poa a a a a wyszych dwch operacji, jest tzw. struktura Find and Union. Struktura ta reprezentuje zbiory w postaci lasu, w ktrym wierzchokami s elementy ze zbioru {0, 1, . . . , n 1}. Kady a zbir jest reprezentowany jako drzewo, w ktrym kady element przechowuje numer swojego ojca. Operacja czenia dwch zbiorw polega na dodaniu krawdzi midzy korzeniami drzew a reprezentujcych czone zbiory, natomiast operacja wyznaczania zbioru, do ktrego naley a a element k na znalezieniu numeru korzenia drzewa, do ktrego naley element k. W ten sposb, dwa elementy nale do tego samego zbioru, jeli maj wsplny korze w drzewie. a a Zamortyzowana zoono wykonania m operacji na tej strukturze danych to O(m log (m)), gdzie log jest odwrotnoci funkcji Ackermana. Funkcja ta bardzo wolno ronie a (istotnie wolniej od logarytmu) w praktycznych zastosowaniach mona zakada zatem, e zoono wykonania m operacji to po prostu O(m)). Uzyskanie takiej zoonoci jest moliwe dziki wykorzystaniu dwch metod: podczas wyszukiwania korzenia drzewa, do ktrego naley element k, cieka do korzenia ulega kompresji wszystkie wierzchoki, lece na ciece midzy k a jego korzeniem, a zostaj bezporednio poczone z korzeniem. W ten sposb wykonywanie operacji wya a szukiwania powoduje skracanie cieek w drzewach. operacja czenia dwch drzew reprezentujcych zbiory realizowana jest wedug rang, a a 172

co sprowadza si do przyczania mniejszego drzewa do korzenia drzewa wikszego, a a nie na odwrt. Takie podejcie minimalizuje sumaryczn dugo powstajcych cieek. a a Przykadowy sposb realizacji operacji czenia wyszukiwania zbiorw zosta przedstawiony a na rysunku 6.1. Implementacja struktury FAU jest przedstawiona na listingu 6.1. Operacja Union(x,y) powoduje zczenie zbiorw zawierajcych elementy x oraz y, natomiast operacja Find(x) a a pozwala na wyznaczenie numeru zbioru, do ktrego naley element x. Na listingu 6.2 przedstawiony jest sposb dziaania tej struktury dla 6-elementowago zbioru oraz sekwencji przykadowych operacji Find i Union.
Listing 6.1: Implementacja struktury FAU // Struktura danych do reprezentacji zbiorw rozcznych 01 struct FAU { 02 int *p, *w; // Konstruktor tworzcy reprezentacj n zbiorw rozcznych 0, 1, ..., n-1 03 FAU(int n) : p(new int[n]), w(new int[n]) { 04 REP(x, n) p[x] = w[x] = -1; 05 } // Destruktor zwalniajcy wykorzystywan pami 06 FAU() { 07 delete[]p; 08 delete[]w; 09 } // Funkcja zwraca numer reprezentanta zbioru, do ktrego naley element x 10 int Find(int x) { 11 return (p[x] < 0) ? x : p[x] = Find(p[x]); 12 } // Funkcja czy zbiory zawierajce elementy x oraz y 13 void Union(int x, int y) { 14 if ((x = Find(x)) == (y = Find(y))) return; 15 if (w[x] > w[y]) p[y] = x; 16 else p[x] = y; 17 if (w[x] == w[y]) w[y]++; 18 } 19 };

Listing 6.2: Przykad dziaania struktury FAU.

Zlaczenie zbiorow zawierajacych elementy Find(0) = 1 Find(1) = 1 Find(2) = 2 Zlaczenie zbiorow zawierajacych elementy Find(0) = 1 Find(1) = 1 Find(2) = 2 Zlaczenie zbiorow zawierajacych elementy Find(0) = 1 Find(1) = 1 Find(2) = 2 Zlaczenie zbiorow zawierajacych elementy Find(0) = 1 Find(1) = 1 Find(2) = 2

0 i 1 Find(3) 3 i 4 Find(3) 1 i 5 Find(3) 0 i 5 Find(3)

= 3 = 4 = 4 = 4

Find(4) = 4 Find(4) = 4 Find(4) = 4 Find(4) = 4

Find(5) = 5 Find(5) = 5 Find(5) = 1 Find(5) = 1

173

Listing 6.3: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 6.2. Peny kod rdowy programu znajduje si w pliku fau.cpp 01 int main() { 02 int n, m, e1, e2; // Wczytaj liczb elementw oraz operacji do wykonania 03 cin >> n >> m; 04 FAU fau(n); 05 REP(x, m) { // Wczytaj numery dwch elementw oraz zcz zbiory je zawierajce 06 cin >> e1 >> e2; 07 fau.Union(e1, e2); 08 cout << "Zlaczenie zbiorow zawierajacych elementy " << 09 e1 << " i " << e2 << endl; 10 REP(y, n) cout << "Find(" << y << ") = " << fau.Find(y) << " "; 11 cout << endl; 12 } 13 return 0; 14 }

Zadanie: Mapki
Pochodzenie: X Olimpiada Informatyczna Rozwizanie: a monkey.cpp

Na drzewie wisi n mapek ponumerowanych od 1 do n. Mapka z nr 1 trzyma si gazi ogonkiem. Pozostae mapki albo s trzymane przez inne mapki, albo trzymaj si innych a a mapek, albo jedno i drugie rwnoczenie. Kada mapka ma dwie przednie apki, kad moe a trzyma co najwyej jedn inn mapk (za ogon). Rozpoczynajc od chwili 0, co sekund a a a jedna z mapek puszcza jedn apk. W ten sposb niektre mapki spadaj na ziemi, gdzie a a dalej mog puszcza apki (czas spadania mapek jest pomijalnie may). a

Zadanie
Napisz program, ktry: wczyta ze standardowego wejcia opis tego, w jaki sposb mapki trzymaj si oraz w a jakiej kolejnoci puszczaj apki, a obliczy dla kadej mapki, kiedy spadnie ona na ziemi, wypisze wynik na standardowe wyjcie.

Pierwszy wiersz standardowego wejcia zawiera dwie dodatnie liczby cakowite n i m (1 n 200 000, 1 m 400 000). Liczba n oznacza liczb mapek, a liczba m czas (w sekundach) przez jaki obserwujemy mapki. Kolejne n wierszy zawiera opis sytuacji pocztkowej. W a wierszu k + 1 (1 k n) znajduj si dwie liczby cakowite oznaczajce numery mapek a a trzymanych przez mapk nr k. Pierwsza z tych liczb to numer mapki trzymanej lew apk, a a a druga praw. Liczba 1 oznacza, e mapka ma woln apk. Kolejne m wierszy opisuje a a wyniki obserwacji mapek. W i-tym spord tych wierszy (1 i m) znajduj si dwie a 174

Wejcie

liczby cakowite. Pierwsza z nich oznacza numer mapki, a druga numer apki (1 lewa, 2 prawa), ktr mapka puszcza w chwili i 1 a

Wyjcie
Twj program powinien wypisa na standardowe wyjcie dokadnie n liczb cakowitych, po jednej w wierszu. Liczba w wierszu i powinna oznacza chwil, w ktrej mapka nr i spada na ziemi, lub by rwna 1, jeli mapka nie spada na ziemi w trakcie obserwacji.

Przykad
3 2 1 3 3 1 1 2 1 2 3 1

Dla nastpujcego wejcia: a Poprawnym rozwizaniem jest: a


1 1 1

Proste acm.uva.es - zadanie 10583 acm.uva.es - zadanie 10608

rednie spoj.sphere.pl - zadanie 116 acm.sgu.ru - zadanie 174 acm.uva.es - zadanie 793

wiczenia

Trudne spoj.sphere.pl - zadanie 264 spoj.sphere.pl - zadanie 188 acm.uva.es - zadanie 10158

6.2. Drzewa poszukiwa binarnych


W niniejszym rozdziale zajmiemy si realizacj struktur daa Literatura nych, pozwalajcych na wykonywanie operacji dodawania oraz usua [ASD] - 3.3 wania elementw, na ktrych okrelony jest porzdek liniowy. W a [SZP] - 2.3 zalenoci od rodzaju przedstawianej struktury, dostpne bd rwa [ASP] - 4.4 nie inne operacje, takie jak wyznaczanie liczby elementw o war[MD] - 6.4 tociach w zadanym przedziale czy wyszukiwanie k-tego najmniejszego elementu. Zaprezentowana rwnie zostanie specjalna wzbogacalna struktura, pozwalajca w prosty sposb na tworzenie wasnych operacji. Na wstpie a naley omwi sposb reprezentacji tych struktur danych wszystkie omawiane struktury bazuj na drzewach poszukiwa binarnych. a Denicja 6.2.1 Drzewo poszukiwa binarnych jest to takie ukorzenione drzewo, ktre spenia nastpujce warunki: a kady wze w drzewie ma co najwyej 2 synw lewego, oraz prawego. Wzy drzewa nie posiadajce synw nazywane s limi, a a kady wze posiada przypisany mu element. Na zbiorze elementw okrelony jest porzdek a liniowy, dla kadego wza drzewa p zawierajcego element vp , jeli ma on lewego syna l z elea mentem vl , to vl < vp , 175

dla kadego wza drzewa p zawierajcego element vp , jeli ma on prawego syna r z a elementem vr , to vr > vp

Przykad drzewa binarnych poszukiwa przedstawiony jest na rysunku 6.2.a. Drzewa tego typu wykorzystuje si do konstrukcji rnych struktur danych, przykadem mog by sowa niki. Wstawianie elementu do sownika odbywa si poprzez dodanie do drzewa binarnych poszukiwa nowego wza zawierajcego dodawany element (miejsce umieszczenia nowego a wza w drzewie musi zosta tak dobrane, aby zachowa zgodno z denicj drzew poszukia wa binarnych). Sprawdzenie przynalenoci elementu jest rwnowane przeszukaniu drzewa od korzenia w poszukiwaniu odpowiedniego wza. Wprowadzenie takiej struktury danych, jak s drzewa binarnych poszukiwa, ma na celu a a uzyskanie efektywnych operacji. Czas potrzebny na wykonanie operacji na wle jest wprost proporcjonalny do jego gbokoci (odlegoci od korzenia). W przypadku, gdy drzewo jest zrwnowaone (gboko najpytszego licia w drzewie nie rni si wiele od gbokoci najgbszego licia), to odlego kadego wza od korzenia jest logarytmiczna w stosunku do liczby wzw w caym drzewie. Wprawdzie, w przypadku wykonywania losowych operacji na drzewie binarnych poszukiwa, pozostaje ono zrwnowaone, to wykonanie n operacji wstawienia elementu do drzewa w kolejnoci rosncych wartoci spowoduje powstanie w drzewie a acucha o dugoci n (kolejno wstawiane elementy bd dodawane jako prawy syn poprzeda niego). Istnieje moliwo zabezpieczenia si przed takimi sytuacjami poprzez implementacj zrwnowaonych drzew binarnych (takich jak drzewa czarno - czerwone, czy AVL), ktre po wykonaniu operacji modykujcej struktur drzewa, wykonuj reorganizacj ukadu wzw, a a majc na celu wyrwnanie gbokoci lici. Implementacja tych struktur danych jest jednak a a czasochonna, co podczas zawodw jest bardzo duym minusem. Na szczcie istniej inne, bardzo proste sposoby zapewnienia szybkiego czasu dziaaa nia wszystkich operacji. Jest to moliwe dziki zastosowaniu statycznych drzew binarnych poszukiwa. Przykad takiego drzewa jest przedstawiony na rysunku 6.2.b. Rnica w stosunku do zwykych drzew polega na tym, e podczas konstrukcji drzewa statycznego podaje si liczb wzw, ktre powinny znale si w takim drzewie. Narzuca to pewne ograniczenia na dziedzin, z ktrej pochodz wstawiane elementy musi by ona skoczona. W a drzewach statycznych waciwe wzy umieszczone s tylko w liciach wewntrzne wa zy drzewa reprezentuj natomiast cae zbiory elementw z ich poddrzew. Takie podejcie a uatwia realizacj rnych dodatkowych operacji wzy wewntrzne mog przechowywa a specjalne informacje wyznaczane w celu umoliwienia efektywnej realizacji tych operacji. Dodanie nowego elementu do drzewa statycznego polega na zaznaczeniu odpowiedniego licia jako aktywnego, oraz zaktualizowaniu wartoci w wzach wewntrznych drzewa, lecych a na ciece od dodawanego licia do korzenia. Podobnie realizowana jest operacja usuwania elementw. Drzewa statyczne, oprcz ograniczenia na wielko dziedziny maj jeszcze jedn wad a a zuycie pamici. Nawet, jeli liczba elementw wstawionych do drzewa jest maa, to i tak zuycie pamici jest liniowe ze wzgldu na wielko dziedziny. Poprawienie tej wasnoci jest moliwe poprzez zastosowanie tzw. drzew statycznych dynamicznie alokowanych, ktrych struktura budowy jest taka sama jak drzew statycznych, jednak przydzielaj one pami na a wzy dopiero wtedy, gdy s one potrzebne. Przykad takiego drzewa znajduje si na rysunku a 6.2.c. W tym rozdziale przedstawimy implementacj czterech prostych struktur danych bazujcych na koncepcji statycznych drzew binarnych poszukiwa: a drzewa maksimw pozwalaj na przypisywanie dodatkowych wartoci elementom a oraz wyznaczanie najwikszej wartoci w obrbie elementw z zadanego przedziau, 176

1 0 3 2 0 0-1 1

0-3 2-3 2 3

0-3 2-3 2

(a)

(b)

(c)

Rysunek 6.2: (a) Drzewo binarnych poszukiwa zawierajce cztery elementy 0, 1, 2 i 3. (b) Statyczne a
drzewo binarnych poszukiwa skonstruowane dla czterech elementw. Aktualnie w drzewie tym jest wstawiony (aktywny) tylko jeden element 2. (c) Statyczne drzewo binarne dynamicznie alokowane. Struktura takiego drzewa jest z gry okrelona, lecz nieaktywnym wzom nie jest przydzielona pami.

drzewa licznikowe pozwalaj na przypisywanie dodatkowych wartoci elementom a oraz wyznaczanie sumy wartoci w obrbie elementw z zadanego przedziau, drzewa pozycyjne pozwalaj na wyznaczanie statystyk pozycyjnych (znajdowanie a k-tego najmniejszego elementu), drzewa pokryciowe pozwalaj na dodawanie oraz usuwanie przedziaw liczb (ktre a mona rwnie traktowa jako odcinki) oraz obliczania powierzchni przez nie pokrytej na zadanym obszarze.

Na przykadzie drzew pokryciowych, przedstawimy rwnie modykacj drzew statycznych w drzewa dynamicznie alokowane. Pod koniec rozdziau przedstawimy implementacj klasycznych drzew binarnych, ktre wspomagaj dodatkowe wzbogacanie (moliwo dodawaa nia wasnych operacji). Drzewa te nie s drzewami zrwnowaonymi, co moe spowodowa, a e dla specycznych scenariuszy, czas wykonywania operacji moe by liniowy. Pokaemy rwnie realizacj randomizowanych drzew binarnych, ktre dodatkowo losowo modykuj a swoj struktur; stanowi one bardzo prost implementacj zrwnowaonej struktury danych a a a (takich jak drzewa czarno - czerwone, czy AVL).

6.2.1. Drzewa maksimw


Drzewa maksimw pozwalaj na dodawanie i usuwanie elementw, ktrym przypisane s a a dodatkowe wartoci ze zbioru liczb naturalnych. Pozwalaj one rwnie na wyznaczenie naja wikszej wartoci na zadanym spjnym przedziale elementw. Wzy wewntrzne drzewa maksimw zawieraj informacj na temat najwikszej wartoci umieszczonej w ich poddrzea wie, co pozwala na efektywne wyszukiwanie maksimw. Reprezentacja statycznego drzewa poszukiwa binarnych jest realizowana jako zwyka tablica liczb. Taka reprezentacja moe zosta wykorzystana ze wzgldu na fakt, i drzewa statyczne s pene i nie zmieniaj swej struktury w czasie. Element 1 tablicy reprezentuje a a korze drzewa, 2 to lewy syn korzenia, 3 to syn prawy . . . Wyznaczanie lewego i prawego syna dla wza o numerze k odbywa si w bardzo prosty sposb lewy syn ma przypisany numer 2 k, natomiast prawy 2 k + 1. 177

Implementacja tych drzew zrealizowana jest jako struktura MaxTree, ktrej kod rdowy przedstawiony jest na listingu 6.4. Funkcja void Set(int, int) pozwala na zmian wartoci elementw, natomiast int Find(int,int) wyznacza maksymaln warto wrd elementw a na zadanym przedziale.
Listing 6.4: Implementacja struktury MaxTree // Drzewo MaxTree umoliwia dodawanie elementw z przypisan im wartoci oraz // wyszukiwanie najwikszej wartoci na dowolnym spjnym przedziale elementw 01 struct MaxTree { 02 int *el, s; // Konstruktor przyjmuje jako parametr wysoko konstruowanego drzewa (dziedzina // elementw to [0..2size-1]) 03 MaxTree(int size) { 04 el = new int[2 * (s = 1 << size)]; 05 REP(x, 2 * s) el[x] = 0; 06 } // Destruktor zwalnia zaalokowan pami 07 MaxTree() { 08 delete[]el; 09 } // Funkcja zmienia warto elementu p na v 10 void Set(int p, int v) { // Ustaw warto elementu p na v, oraz zaktualizuj wierzchoki na // ciece do korzenia, wyliczajc dla nich maksimum z ich lewego i prawego // syna 11 for (p += s, el[p] = v, p >>= 1; p > 0; p >>= 1) 12 el[p] = max(el[p << 1], el[(p << 1) + 1]); 13 } // Funkcja wyznacza najwiksz warto na przedziale elementw [p..k] 14 int Find(int p, int k) { 15 int m = -INF; 16 p += s; 17 k += s; // Przeszukiwanie drzewa rozpoczyna si od lici reprezentujcych elementy // p i k. Dopki wze p jest rny od wza k... 18 while (p < k) { // Jeli aktualne wzy p i k zawieraj w swoich poddrzewach cay // przeszukiwany przedzia, to nastpuje aktualizacja wyniku 19 if ((p & 1) == 1) m = max(m, el[p++]); 20 if ((k & 1) == 0) m = max(m, el[k--]); // Przejd do ojcw wzw p i k 21 p >>= 1; 22 k >>= 1; 23 } 24 if (p == k) m = max(m, el[p]); 25 return m; 26 } 27 };

178

1 2 4 5 6 3 7 0 0 0

0 0 0 0 0 3 3

3 0 0 0 0 3 3

5 5 5 0

(a)

(b)

(c)

(d)

Rysunek 6.3: (a) Sposb numeracji wzw w drzewach statycznych. (b) Puste drzewo maksimw. Kolejne licie (patrzc od lewej strony) reprezentuj klucze 0, 1, 2 i 3. (c) Stan drzewa po a a dodaniu elementu 1 o wartoci 3. (d) Stan drzewa po dodaniu elementu 2 o wartoci 5.

Zmiana wartosci elementu 2 na 2 Maksimum na przedziale 0..4 = 2 Maksimum na przedziale 2..2 = 2 Maksimum na przedziale 3..15 = 0 Zmiana wartosci elementu 3 na 10 Maksimum na przedziale 0..2 = 2 Maksimum na przedziale 2..10 = 10 Zmiana wartosci elementu 3 na 3 Maksimum na przedziale 0..15 = 3

Listing 6.5: Przykad wykorzystania struktury MaxTree dla elementw z dziedziny {0, 1, . . . , 15}

Listing 6.6: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 6.5. Peny kod rdowy programu znajduje si w pliku maxtree.cpp 01 int main() { 02 int w1, w2, w3; // Skonstruuj drzewo maksimw dla elementw [0..15] 03 MaxTree tree(4); // Wykonaj list operacji... 04 while(cin >> w1 >> w2 >> w3) { 05 if (w1 == 0) { // Operacja zmiany wartoci klucza... 06 tree.Set(w2, w3); 07 cout << "Zmiana wartosci elementu " << w2 << " na " << w3 << endl; 08 } else // Operacja wypisania maksimum na przedziale [w2..w3] 09 cout << "Maksimum na przedziale " << w2 << ".." << w3 << 10 " = " << tree.Find(w2, w3) << endl; 11 } 12 return 0; 13 }

179

Proste acm.uva.es - zadanie 105 acm.uva.es - zadanie 231

rednie acm.uva.es - zadanie 10635 spoj.sphere.pl - zadanie 130

wiczenia

Trudne acm.uva.es - zadanie 497 spoj.sphere.pl - zadanie 57

6.2.2. Drzewa licznikowe


Kolejn prezentowan struktur danych s drzewa licznikowe. Pozwalaj one na przypisya a a a a wanie elementom wartoci ze zbioru liczb cakowitych oraz wyznaczanie sumy wartoci na spjnym przedziale elementw. Wzy wewntrzne drzewa licznikowego przechowuj sum a wartoci ze swoich poddrzew. Implementacja struktury CountTree przedstawiona jest na listingu 6.7, natomiast na listingu 6.8 przedstawiony jest ich przykadowy sposb uycia.
Listing 6.7: Implementacja struktury CountTree // Struktura umoliwia dodawanie elementw z przypisan wartoci oraz sprawdzanie // sumy wartoci na dowolnym przedziale elementw 01 struct CountTree { 02 int *el, s; // Konstruktor przyjmuje jako parametr wysoko konstruowanego drzewa (dziedzina // kluczy to [0..2size-2]) 03 CountTree(int size) { 04 el = new int[2 * (s = 1 << size)]; 05 REP(x, 2 * s) el[x] = 0; 06 } // Destruktor zwalnia zaalokowan pami 07 CountTree() { 08 delete[]el; 09 } // Funkcja zmienia warto klucza p na v 10 void Set(int p, int v) { // Do wszystkich wzw na ciece do korzenia dodawana jest warto v 11 for (p += s; p; p >>= 1) 12 el[p] += v; 13 } // Funkcja wyznacza sum wartoci na spjnym przedziale elementw [p..k] 14 int Find(int p, int k) { 15 int m = 0; 16 p += s; 17 k += s; 18 while (p < k) { 19 if (p & 1) m += el[p++]; 20 if (!(k & 1)) m += el[k--]; 21 p >>= 1; 22 k >>= 1; 23 } 24 if (p == k) m += el[p]; 25 return m;

180

8 0 0 0 8 8 0 0 1 1

9 8 8 0 0 1 1

1 0 0 0

(a)

(b)

(c)

Rysunek 6.4: (a) Stan drzewa licznikowego po wstawieniu elementu 2 o wartoci 8. (b) Zmiana stanu
drzewa po dodaniu elementu 1 o wartoci 1. (c) Stan drzewa po usuniciu elementu 2.

Listing 6.7: (c.d. listingu z poprzedniej strony) 26 } 27 };

Zmiana wartosci elementu 2 o 2 Suma na przedziale 0..15 = 2 Suma na przedziale 5..10 = 0 Zmiana wartosci elementu 3 o 10 Suma na przedziale 0..15 = 12 Suma na przedziale 0..2 = 2 Zmiana wartosci elementu 3 o 3 Suma na przedziale 0..15 = 15

Listing 6.8: Przykad wykorzystania struktury CountTree na przykadzie elementw z dziedziny {0, 1, . . . , 15}

Listing 6.9: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 6.8. Peny kod rdowy programu znajduje si w pliku counttree.cpp 01 int main() { 02 int w1, w2, w3; // Skonstruuj drzewo dla dziedziny elementw [0..15] 03 CountTree tree(4); // Wykonaj list operacji... 04 while(cin >> w1 >> w2 >> w3) { 05 if (w1 == 0) { // Operacja zmiany wartoci elementu... 06 tree.Set(w2, w3); 07 cout << "Zmiana wartosci elementu " << w2 << " o " << w3 << endl; 08 } else // Wyznaczenie sumy wartoci na przedziale [w2..w3] 09 cout << "Suma na przedziale " << w2 << ".." << w3 << 10 " = " << tree.Find(w2, w3) << endl; 11 } 12 return 0; 13 }

181

Proste spoj.sphere.pl - zadanie 227

wiczenia

rednie acm.uva.es - zadanie 10200

Trudne

6.2.3. Drzewa pozycyjne


Drzewa pozycyjne pozwalaj na dodawanie i usuwanie elementw oraz wyznaczanie statystyk a pozycyjnych (k-tego najmniejszego elementu). Wzy wewntrzne drzewa pozycyjnego przechowuj liczb elementw znajdujcych si w ich poddrzewach, dziki czemu w atwy sposb a a mona wyznacza statystyki pozycyjne, przeszukujc drzewo od korzenia. a Implementacja struktury PosTree przedstawiona jest na listingu 6.10, natomiast na listingu 6.11 przedstawiony jest ich przykadowy sposb uycia.
Listing 6.10: Implementacja struktury PosTree // Struktura umoliwia dodawanie elementw i sprawdzanie statystyk pozycyjnych 01 struct PosTree { 02 int *el, s; // Konstruktor przyjmuje jako parametr wysoko konstruowanego drzewa (dziedzina // elementw to [0..2size-2]) 03 PosTree(int size) { 04 el = new int[1 << ((s = size) + 1)]; 05 REP(x, 1 << (s + 1)) el[x] = 0; 06 } // Destruktor zwalnia zaalokowan pami 07 PosTree() { 08 delete[]el; 09 } // Funkcja dodaje/usuwa v wystpie elementu p 10 void Add(int p, int v) { // Dla kadego wza drzewa od licia p do korzenia, aktualizowana jest // liczba elementw w poddrzewie 11 for (p = (1 << s) + p; p > 0; p = p >> 1) 12 el[p] += v; 13 } // Funkcja wyznacza p-t statystyk pozycyjn 14 int Find(int p) { // Przeszukiwanie rozpoczynane jest od korzenia drzewa 15 int po = 1; 16 REP(x, s) { // Nastpuje przejcie do lewego syna aktualnego wza 17 po <<= 1; // Jeli aktualne poddrzewo zawiera mniej elementw, ni wynosi numer // wyszukiwanej statystyki pozycyjnej, to nastpuje przejcie do prawego // syna 18 if (el[po] < p) p -= el[po++]; 19 } // Zwracany jest numer znalezionego elementu 20 return po - (1 << s);

182

0 0 0 0 0 1 0 2 0 0 3 1 0 1 0 1

1 0 0 2 0 3 0 0 1 0 1

2 1 0 2 1 3

(a)
drzewa elementu 3.

(b)

(c)

Rysunek 6.5: (a) Stan pustego drzewa pozycyjnego (b) Dodanie do drzewa elementu 0. (c) Dodanie do

Listing 6.10: (c.d. listingu z poprzedniej strony) 21 } 22 };

Do drzewa dodano element 0 1-statystyka pozycyjna = 0 Do drzewa dodano element 7 2-statystyka pozycyjna = 7 Do drzewa dodano element 3 2-statystyka pozycyjna = 3 Z drzewa usunieto element 3 2-statystyka pozycyjna = 7

Listing 6.11: Wykorzystanie struktury PosTree na przykadzie elementw z dziedziny {0, 1, . . . , 15}

Listing 6.12: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 6.11. Peny kod rdowy programu znajduje si w pliku postree.cpp 01 int main() { 02 int w1, w2, w3; // Skonstruuj drzewo pozycyjne dla elementw [0..15] 03 PosTree tree(4); // Wykonaj list operacji... 04 while (cin >> w1 >> w2) { 05 if (w1 == 0) { // Operacja dodawania / usuwania elementw... 06 cin >> w3; 07 tree.Add(w2, w3); 08 if (w3 == 1) cout << "Do drzewa dodano element " << w2 << endl; 09 else cout << "Z drzewa usunieto element " << w2 << endl; 10 } else // Wyznaczenie statystyki pozycyjnej 11 cout << w2 << "-statystyka pozycyjna = " << tree.Find(w2) << endl; 12 } 13 return 0; 14 }

183

Zadanie: Kodowanie permutacji


Pochodzenie: II Olimpiada Informatyczna Rozwizanie: a perm.cpp

Kad permutacj A = (a1 , .., an ) liczb 1, ..., n mona zakodowa za pomoc cigu B = a a a (b1 , .., bn ), w ktrym bi jest rwne liczbie wszystkich aj takich, e: (j i oraz aj ai ) dla kadego i = 1, ..., n. Przykad: Kodem permutacji A = (1, 5, 2, 6, 4, 7, 3) jest cig: B = (0, 0, 1, 0, 2, 0, 4). a

Zadanie
Napisz program, ktry: wczytuje ze standardowego wejcia dugo n i kolejne wyrazy cigu liczb B, a sprawdza, czy jest on kodem jakiej permutacji liczb 1, ..., n, jeeli tak, to znajduje t permutacj i wypisuje j na standardowe wyjcie, a w przeciwnym przypadku wypisuje jedno sowo NIE

7 0 0 1 0 2 0 4

Wejcie
W pierwszym wierszu wejcia zapisana jest dodatnia liczba cakowita n 1 000 000. Jest to liczba wyrazw cigu B. W kadym z kolejnych n wierszy jest zapisana jedna liczba cakowita a nieujemna nie wiksza ni 1 000 000. Jest to kolejny wyraz cigu B. a

Wyjcie
Program powinien wypisa w kadym z kolejnych n wierszy jeden wyraz permutacji A, ktrej kodem jest dany cig B, lub jedno sowo N IE, jeli cig B nie jest kodem adnej permutacji. a a

Przykad

Dla nastpujcego wejcia: a


1 2 3 4 5 6 7

Poprawnym rozwizaniem jest: a

184

Dla nastpujcego wejcia: a


4 0 2 0 0

Poprawnym rozwizaniem jest: a


1 2 3 4 5 6 7

Proste acm.uva.es - zadanie 440 acm.uva.es - zadanie 151

rednie acm.uva.es - zadanie 10015

wiczenia

Trudne acm.sgu.ru - zadanie 254

6.2.4. Drzewa pokryciowe


Ostatnim przykadem drzew statycznych jakie zaprezentujemy s drzewa pokryciowe. Elea menty wstawiane do tych drzew, w odrnieniu od dotychczasowych, s przedziay, ktrych a koce nale do dziedziny [0..2n 1]. Dodatkow wspomagan operacj, jest wyznaczanie dla a a a a danego spjnego przedziau dziedziny, obszaru pokrytego przez jakikolwiek odcinek. Wierzchoki drzewa pokryciowego reprezentuj odcinki jednostkowe (0, 1), (1, 2) . . ., natomiast a wierzchoki wewntrzne drzewa przechowuj informacje dotyczce pokrytej powierzchni przez a a odcinki z ich poddrzew. Implementacja drzew pokryciowych zrealizowana jest jako struktura CoverTree, ktrej kod rdowy znajduje si na listingu 6.13. Listing 6.14 natomiast prezentuje sposb uycia struktury CoverTree w praktyce. Korzystanie ze struktury CoverTree sprowadza si do wya korzystania dwch funkcji void Mark(int p, int k, int w), ktra dodaje w wystpie odcinka (p, k) (jeli w jest ujemne, to nastpuje usunicie odpowiedniej liczby odcinkw), oraz int Find(int p,int k), ktra wyznacza pokrycie przedziau [p..k].
Listing 6.13: Implementacja struktury CoverTree. // Struktura umoliwia dodawanie i usuwanie odcinkw oraz sprawdzanie obszaru // przez nie pokrywanego 01 struct CoverTree { 02 #dene nr (wp + wk + 1) >> 1 03 int* el, *ma, s, p, k, il; // Konstruktor przyjmuje jako parametr wysoko konstruowanego drzewa (koce // odcinkw, na ktrych wykonywane s operacje nale do przedziau [0..2size-2]) 04 CoverTree(int size) { // Tablica el przechowuje liczb odcinkw pokrywajcych w caoci przedzia // reprezentowany przez odpowiedni wze 05 el = new int[s = 1 << (size + 1)]; // Tablica ma przechowuje sumaryczn pokryt powierzchni przedziau // reprezentowanego przez odpowiedni wze 06 ma = new int[s]; 07 REP(x, s) el[x] = ma[x] = 0; 08 }

185

Listing 6.13: (c.d. listingu z poprzedniej strony) // Destruktor zwalnia zaalokowan pami 09 CoverTree() { 10 delete[] el; 11 delete[] ma; 12 } // Funkcja pomocnicza wykorzystywana przez operacj dodawania i usuwania odcinkw. // Wykonuje ona aktualizacj wartoci wzw 13 void Mark(int wp, int wk, int g) { // Jeli odcinek ktry jest dodawany/usuwany, jest rozczny z przedziaem // reprezentowanym przez aktualny wze, to przerwij 14 if(k<=wp || p>=wk) return; // Jeli odcinek pokrywa w caoci przedzia aktualnego wza, to zmie warto el, 15 if(p<=wp && k>=wk) el[g] += il; else { // a jeli nie, to wykonaj aktualizacj zmiennych synw aktualnego wza 16 Mark(wp, nr, 2 * g); 17 Mark(nr, wk, 2 * g + 1); 18 } // Dokonaj aktualizacji pokrytego obszaru przetwarzanego przedziau 19 ma[g] = el[g] > 0 ? wk - wp : 20 (wk - 1 == wp ? 0 : ma[2 * g] + ma[2 * g + 1]); 21 } // Funkcja dodajca i1 wystpie odcinka [p1..k1] do drzewa. W przypadku, // gdy i1 jest wartoci ujemn, nastpuje usuwanie odcinka 22 void Add(int p1, int k1, int i1) { 23 p = p1; 24 k = k1; 25 il = i1; 26 Mark(0, s / 2, 1); 27 } // Funkcja pomocnicza suca do wyznaczania pokrycia odcinka [p..k] 28 int F(int wp, int wk, int g) { // Jeli testowany odcinek jest rozczny z aktualnym przedziaem, // to pokrycie jest rwne 0 29 if (wp >= k || wk <= p) return 0; // Jeli przedzia jest w caoci pokryty, to zwr wielko // czci wsplnej testowanego odcinka i przedziau 30 if (el[g] > 0) return min(k, wk) - max(p, wp); // Jeli odcinek pokrywa w caoci przedzia, to zwr pokrycie przedziau 31 if (p <= wp && wk <= k) return ma[g]; // Zwr jako wynik sum pokry swoich synw 32 return wp == wk - 1 ? 0 : F(wp, nr, 2 * g) + F(nr, wk, 2 * g + 1); 33 } // Waciwa funkcja realizujca wyznaczanie pokrycia przedziau [p1..k1] 34 int Find(int p1, int k1) { 35 p = p1; 36 k = k1; 37 return F(0, s / 2, 1); 38 }

186

Listing 6.13: (c.d. listingu z poprzedniej strony) 39 };

Pokrycie odcinka [0,15] = Dodanie odcinka [1,10] Pokrycie odcinka [0,15] = Pokrycie odcinka [5,15] = Dodanie odcinka [5,15] Pokrycie odcinka [0,15] = Usuniecie odcinka [1,10] Pokrycie odcinka [0,15] =

Listing 6.14: Wykorzystanie struktury CoverTree na przedziale {0, 1, . . . , 15}

0 9 5

14 10

Listing 6.15: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 6.14. Peny kod rdowy programu znajduje si w pliku covertree.cpp 01 int main() { 02 int w1, w2, w3; // Skonstruuj drzewo pokryciowe dla przedziau [0..15] 03 CoverTree tree(4); // Wczytaj polecenia i wykonaj je... 04 while(cin >> w1 >> w2 >> w3) { 05 if (w1 == 0) { // Operacja dodawania nowego odcinka 06 tree.Add(w2, w3, 1); 07 cout << "Dodanie odcinka [" << w2 << "," << w3 << "]" << endl; 08 } else if (w1 == 1) { // Operacja usuwania odcinka 09 tree.Add(w2, w3, -1); 10 cout << "Usuniecie odcinka [" << w2 << "," << w3 << "]" << endl; 11 } else // Wyznaczanie pokrycia na przedziale [w2..w3] 12 cout << "Pokrycie odcinka [" << w2 << "," << w3 << 13 "] = " << tree.Find(w2, w3) << endl; 14 } 15 return 0; 16 }

Proste

rednie spoj.sphere.pl - zadanie 61

wiczenia

Trudne

187

6.3. Binarne drzewa statyczne dynamicznie alokowane


W poprzednich kilku rozdziaach przedstawilimy bardzo proste w realizacji struktury danych, bazujce na statycznych drzewach binarnych poszukiwa. Struktura tych drzew jest a bardzo atwa w uyciu bez wikszych problemw mona zmienia zestaw operacji przez nie realizowanych. Jednak wielk wad tych drzew jest fakt, i zuycie pamici (oraz czas a a konstrukcji drzewa) jest liniowy ze wzgldu na wielko dziedziny, z ktrej pochodz elea menty. W przypadku gstego wykorzystania elementw z domeny nie stanowi to wielkiego problemu, jednak w przypadku, gdy elementy, ktre s wstawiane do drzewa umieszczone a s stosunkowo rzadko, to moe si okaza, e nie dysponujemy odpowiedni iloci pamici, a a a aby skonstruowa odpowiednie drzewo. Przygldajc si strukturze drzewa statycznego z a a rysunku 6.2.b wida, e wiele wierzchokw takich drzew jest, przez wikszo czasu, zupenie niepotrzebnych. Zmodykowan struktur danych, korzystajc z takiej sytuacji jest a a a a przedstawione w aktualnym rozdziale drzewo statyczne alokowane dynamicznie. Drzewo takie (przykad przedstawiony jest na rysunku 6.2.c) nie tworzy podczas konstrukcji wzw dla wszystkich elementw dziedziny. Wzy te s konstruowane dopiero w momencie, gdy s faka a tycznie potrzebne, co w wielu przypadkach oznacza, e znaczna ich cz nigdy nie zostanie skonstruowana. Podejcie takie jednak istotnie komplikuje implementacj struktury wzy wewntrzne musz pamita wskaniki na swoich synw w przypadku drzew statycznych a wskaniki te byy wyliczane na podstawie numeru wza synowie wza k mieli numery 2 k oraz 2 k + 1. Drzewa dynamicznie alokowane zaprezentujemy na przykadzie drzew pokryciowych, omwionych w poprzednim podrozdziale. Drzewa te umoliwiaj nam realizowanie takich operaa cji, jak wstawianie oraz usuwanie odcinkw, oraz sprawdzanie obszaru przez nie pokrytego. W przypadku struktury CoverTree, przedziay, dla jakich mona byo konstruowa tego typu drzewa byy nie wiksze ni [0..10 000 000], co wynika z dostpnej pamici dla programw podczas konkursw. W przypadku drzew alokowanych dynamicznie, nie sprawia problemu konstruowanie drzew dla dziedziny [0..2 000 000 000]. Implementacja tych drzew jest realizowana przez struktur CoverBTree, ktrej kod rdowy znajduje si na listingu 6.16. Przykadowe uycie tej struktury danych zostao zaprezentowane na listingu 6.17.
Listing 6.16: Implementacja struktury CoverBTree. // Struktura umoliwia dodawanie i usuwanie odcinkw oraz wyznaczanie obszaru // przez nie pokrywanego 01 struct CoverBTree { // Struktura wierzchoka drzewa 02 struct Vert { // Wskaniki na odpowiednio lewego i prawego syna 03 Vert *s[2]; // Zmienna v reprezentuje pokryty obszar aktualnego wierzchoka, natomiast // c zlicza liczb odcinkw, ktre pokrywaj w caoci przedzia // reprezentowany przez wierzchoek 04 int v, c; 05 Vert() { 06 s[v = c = 0] = s[1] = 0; 07 } 08 }; // Korze drzewa

188

Listing 6.16: (c.d. listingu z poprzedniej strony) 09 // // 10 // 11 12 13 14 15 16 17 // 18 19 20 // 21 22 23 // // 24 // // // 25 26 // // 27 28 29 // 30 31 32 33 // 34 35 36 37 38 // // // 39 Vert *root; Warto reprezentujca pocztek i koniec przedziau, dla ktrego zostao skonstruowane drzewo int zp, zk; Funkcja usuwa pami zaalokowan dla danego wierzchoka oraz jego poddrzewa void Rem(Vert * p) { if (p) { Rem(p->s[0]); Rem(p->s[1]); delete p; } } Destruktor zwalnia ca pami przydzielon na drzewo CoverBTree() { Rem(root); } Konstruktor tworzy nowe drzewo dla przedziau [p..k] CoverBTree(int p, int k) : zp(p), zk(k) { root = new Vert; } Zmienne pomocnicze dla operatorw (przypisywane s im odpowiednio pocztek i koniec odcinka oraz jego liczebno) int pp, kk, cc; Funkcja pomocnicza dla Add, dodajca lub usuwajca odcinek [pp..kk]. Parametry p i k oznaczaj przedzia, ktry reprezentuje wierzchoek v void Ad(int p, int k, Vert * v) { if (kk <= p || pp >= k) return; Jeli odcinek w caoci pokrywa aktualny przedzia, to nastpuje modyfikacja zmiennej c aktualnego wierzchoka if (p >= pp && k <= kk) v->c += cc; else { int c = (p + k) / 2; Jeli odcinek zachodzi na przedzia lewego syna, to aktualizuj go if (pp <= c) { if (!v->s[0]) v->s[0] = new Vert; Ad(p, c, v->s[0]); } Jeli odcinek zachodzi na przedzia prawego syna, to aktualizuj go if (kk >= c) { if (!v->s[1]) v->s[1] = new Vert; Ad(c, k, v->s[1]); } } Aktualizacja pokrycia przedziau. Jeli zmienna c jest wiksza od 0, to odcinek jest pokryty w caoci, jeli natomiast nie, to jego pokrycie jest zalene od jego synw v->v = v->c ? k - p :

189

Listing 6.16: (c.d. listingu z poprzedniej strony) 40 (v->s[0] ? v->s[0]->v : 0) + (v->s[1] ? v->s[1]->v : 0); 41 } // Funkcja dodaje lub usuwa z drzewa odcinek [p..k]. Parametr c okrela, // czy odcinek jest dodawany (1), lub usuwany (-1) 42 void Add(int p, int k, int c) { 43 pp = p; 44 kk = k; 45 cc = c; 46 Ad(zp, zk, root); 47 } // Funkcja pomocnicza, wyznaczajca pokrycie przedziau [pp..kk] 48 int Fi(int p, int k, Vert * v) { // Jeli wierzchoek nie istnieje lub jego przedzia jest rozczny z // odcinkiem to wyjd 49 if (!v || p >= kk || k <= pp) return 0; // Jeli przedzia jest pokryty w caoci, to zwr wielko przecicia z // odcinkiem 50 if (v->c) return min(k, kk) - max(p, pp); // Jeli odcinek zawiera cay przedzia, to zwr pokrycie przedziau 51 if (p >= pp && k <= kk) return v->v; // Wyznacz pokrycie dla obu synw 52 int c = (p + k) / 2; 53 return Fi(p, c, v->s[0]) + Fi(c, k, v->s[1]); 54 } // Funkcja wyznaczajca pokrycie przedziau [p..k] 55 int Find(int p, int k) { 56 pp = p; 57 kk = k; 58 return Fi(zp, zk, root); 59 } 60 };

Listing 6.17: Przykad wykorzystania struktury danych CoverBTree

Pokrycie odcinka [0,1000000000] = 0 Dodanie odcinka [1,10000] Pokrycie odcinka [0,1000000000] = 9999 Pokrycie odcinka [5,100000] = 9995 Dodanie odcinka [5,50000] Pokrycie odcinka [0,100000] = 49999 Usuniecie odcinka [1,10000] Pokrycie odcinka [0,100000] = 49995

Listing 6.18: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 6.17. Peny kod rdowy programu znajduje si w pliku coverbtree.cpp 01 int main() { 02 int w1, w2, w3;

190

Listing 6.18: (c.d. listingu z poprzedniej strony) // Skonstruuj dynamiczne drzewo pokryciowe dla przedziau [0..15] 03 CoverBTree tree(0, 1000000000); // Wczytaj polecenia i wykonaj je... 04 while(cin >> w1 >> w2 >> w3) { 05 if (w1 == 0) { // Operacja dodawania nowego odcinka 06 tree.Add(w2, w3, 1); 07 cout << "Dodanie odcinka [" << w2 << "," << w3 << "]" << endl; 08 } else if (w1 == 1) { // Operacja usuwania odcinka 09 tree.Add(w2, w3, -1); 10 cout << "Usuniecie odcinka [" << w2 << "," << w3 << "]" << endl; 11 } else // Wyznaczanie pokrycia na przedziale [w2..w3] 12 cout << "Pokrycie odcinka [" << w2 << "," << w3 << 13 "] = " << tree.Find(w2, w3) << endl; 14 } 15 return 0; 16 }

Zadanie: Marsjaskie mapy


Pochodzenie: Olimpiada Informatyczna Krajw Batyckich 2001 Rozwizanie: a mars.cpp

W roku 2051 przeprowadzonych zostao kilka ekspedycji na Marsie, w wyniku czego stworzone zostay mapy zbadanych obszarw. BaSA (Baltic Space Agency) chce stworzy map caej planety w celu okrelenia kosztw operacji, naley policzy obszar planety, dla ktrej mapy zostay ju skonstruowane. Twoim zadaniem jest napisa taki program.

Zadanie

Napisz program, ktry: wczyta ze standardowego wejcia opis map, wyznaczy sumaryczny obszar pokryty przez mapy, wypisze wynik na standardowe wyjcie.

Wejcie
Pierwszy wiersz standardowego wejcia zawiera jedn liczb cakowit n (1 a a n 10 000), oznaczajc liczb dostpnych map. Kady z kolejnych n wierszy zawiera cztery liczby cakoa a wite x1 , y1 , x2 , y2 (0 x1 x2 1 000 000, 0 y1 y2 1 000 000). (x1 , y1 ) oraz (x2 , y2 ) s wsprzdnymi odpowiednio dolnego - lewego oraz grnego - prawego rogu obszaru znaja dujcego si na mapie. Wszystkie mapy s prostoktne, a ich boki s rwnolege do osi x i a a a a y.

191

Wyjcie
Twj program powinien wypisa na standardowe wyjcie dokadnie jedn liczb cakowit a a zbadany obszar planety.

Przykad
Dla nastpujcego wejcia: a
2 10 10 20 20 15 15 25 30

Poprawnym rozwizaniem jest: a


225

Proste

rednie acm.uva.es - zadanie 688

wiczenia

Trudne

6.4. Wzbogacane drzewa binarne


Po wnikliwej analizie poprzednich rozdziaw uwany czytelnik Literatura zaobserwuje, e istnieje due podobiestwo pomidzy poszczegl[WDA] - 15 nymi rodzajami prezentowanych drzew. Pomimo rnych operacji [SZP] - 6.2.2, 6.2.3 przez nie realizowanych, w wikszoci przypadkw sposb wyznaczania dodatkowych informacji, ktre umoliwiaj efektywn realia a zacj tych dodatkowych operacji, jest taki sam warto informacji w wle wewntrznym drzewa jest zalena wycznie od tego wza oraz od jego synw. Mona w takim wypadku a stworzy szablon, pozwalajcy na konstruowanie rnego rozdziau drzew, ktrych funkcje a wstawiania oraz usuwania wzw bd identyczne, a jedyne rnice polega bd na zasobie a a informacji przechowywanych w wzach oraz na sposobie wyliczania ich wartoci. Rnice pojawia si rwnie bd w zakresie zbioru funkcji, pozwalajcych na realizacj dodatkoa a wych operacji. Implementacja tych funkcji jest jednak prosta, gdy nie dokonywane s w nich a modykacje struktury drzewa (dodawania i usuwania wierzchokw). W aktualnym rozdziale przedstawimy dwie takie struktury danych. Pierwsz z nich bd wzbogacane drzewa binara a nych poszukiwa, o ktrych wiadomo, e w pesymistycznych przypadkach czas wykonywania operacji moe by liniowy. Nastpnie przedstawimy modykacj tych drzew, ktre bd ra niy si realizacj funkcji wstawiania nowych wzw modykacja polega na wykonywaniu a losowych zmian struktury drzewa, majcych na celu zapobieganie zbytniemu rozrastaniu si a drzewa w gb. a a Przedstawiona na listingu 6.19 implementacja struktury BST stanowi klasyczn realizacj binarnego drzewa wyszukiwa. Gwn zalet tej implementacji, jest moliwo okrelenia doa a datkowych informacji, ktre naley przechowywa w wierzchokach drzewa. Podczas konstrukcji drzewa BST podaje si struktur element, ktra ma by czci tworzonych wierzchokw a oraz okrela si dwie funkcje f i g. Zadaniem funkcji f o sygnaturze int f(const element&, 192

const element&) jest okrelanie liniowego porzdku na zbiorze elementw przechowywanych a w drzewie. Jeli element okrelony przez pierwszy parametr jest wikszy od elementu drugiego, to funkcja ta powinna zwraca warto 1. Jeli element drugi jest mniejszy od pierwszego, to wartoci zwracan jest 0, natomiast jeli elementy s rwne, to funkcja powinna zwrci a a a 1. Druga funkcja g, o sygnaturze void g(element*, element*, element*) przyjmuje jako parametry trzy wskaniki na wierzchoki drzewa odpowiednio ojca oraz jego lewego i prawego syna (w przypadku, gdy ktry z synw nie istnieje, to odpowiedni wskanik ma warto 0). Zadaniem funkcji jest zaktualizowanie informacji wierzchoka - ojca na podstawie informacji z jego synw. Przykadowa implementacja drzew pozycyjnych, pozwalajca a na wyszukiwanie k-tego najmniejszego elementu w drzewie, ktra bazuje na strukturze BST zostaa przedstawiona na listingu 6.20.
Listing 6.19: Implementacja struktury BST // Implementacja struktury BST 01 template <typename T> struct BST { // Struktura wza drzewa 02 struct Ve { // Wskaniki na lewego i prawego syna 03 Ve *s[2]; // Pole e zawiera element przechowywany w wle 04 T e; 05 Ve() { 06 s[0] = s[1] = 0; 07 }}; // sygnatury funkcji do porwnywania elementw oraz do aktualizacji ich // zawartoci 08 #dene Less int (*Less)(const T&, const T&) 09 #dene Upd void (*Upd)( T*, T*, T*) 10 Less; 11 Upd; // Korze drzewa oraz dodatkowa zmienna pomocnicza 12 Ve *root, *v; // Wektor wskanikw do wzw drzewa, wykorzystywany do wyznaczania cieki od // przetwarzanego wza do korzenia drzewa 13 vector<Ve *> uV; // Funkcja usuwa dany wze wraz z jego caym poddrzewem 14 void Rem(Ve * p) { 15 if (p) { 16 REP(x, 2) Rem(p->s[x]); 17 delete p; 18 } 19 } // Destruktor drzewa zwalnia zaalokowan pami 20 BST() { 21 Rem(root); 22 } // Konstruktor drzewa przyjmuje jako parametry dwie funkcje - funkcj // okrelajc porzdek na elementach oraz funkcj suc do aktualizowania // informacji elementw

193

Listing 6.19: (c.d. listingu z poprzedniej strony) 23 24 25 // // 26 27 28 29 30 // 31 32 33 34 35 36 37 38 39 // 40 41 42 // 43 // 44 // 45 46 47 48 49 // 50 51 // 52 53 // 54 55 // 56 57 58 59 BST( Less, Upd) : Less(Less), Upd(Upd) { root = 0; } Funkcja aktualizuje wartoci elementw, znajdujcych si na ciece od ostatnio przetwarzanego wza do korzenia drzewa void Update() { for (int x = SIZE(uV) - 1; x >= 0 && (v = uV[x]); x--) Upd(&v->e, v->s[0] ? &(v->s[0]->e) : 0, v->s[1] ? &(v->s[1]->e) : 0); uV.clear(); } Funkcja pomocnicza wyszukujca wze w drzewie zawierajcy zadany element Ve *Find(const T & e, bool s) { v = root; if (s) uV.PB(v); for (int c; v && (-1 != (c = Less(v->e, e)));) { if (s) uV.PB(v); v = v->s[c]; } return v; } Funkcja wyszukuje zadany element w drzewie T *Find(const T & e) { return (v = Find(e, 0)) ? &(v->e) : 0; } Funkcja wstawia zadany element do drzewa void Insert(const T & e) { Jeli drzewo nie jest puste... if (root) { Znajd odpowiednie miejsce w drzewie dla dodawanego elementu i go dodaj Find(e, 1); v = uV.back(); int cmp = Less(v->e, e); if (cmp != -1) uV.PB(v = v->s[cmp] = new Ve); } Stwrz nowy korze drzewa z zadanym elementem else uV.PB(v = root = new Ve); v->e = e; Zaktualizuj elementy na ciece od dodanego elementu do korzenia Update(); } Funkcja usuwa z drzewa wze zawierajcy zadany element bool Delete(const T & e) { Ve *c = Find(e, 1), *k; Jeli elementu nie ma w drzewie, to przerwij if (!c) { uV.resize(0); return 0; }

194

Listing 6.19: (c.d. listingu z poprzedniej strony) 60 if (c != root) uV.PB(c); // Jeli wze ma obu synw, to musi nastpi zamiana elementw w wle // aktualnym z kolejnym elementem... 61 if (c->s[0] && c->s[1]) { 62 for (k = c->s[1]; k->s[0]; k = k->s[0]) 63 uV.PB(k); 64 uV.PB(k); 65 c->e = k->e; 66 c = k; 67 } // Usunicie odpowiednich wzw z drzewa 68 if (SIZE(uV) > 1) 69 (k = uV[SIZE(uV) - 2])->s[k->s[0] != c] = c->s[0] ? c->s[0] : c->s[1]; 70 else root = c->s[0] ? c->s[0] : c->s[1]; // Aktualizacja elementw na ciece 71 Update(); 72 delete c; 73 return 1; 74 } 75 };

Listing 6.20: Przykad wykorzystania struktury BST.

Dodawanie do drzewa elementu 10 Dodawanie do drzewa elementu 15 1 statystyka pozycyjna to: 10 2 statystyka pozycyjna to: 15 Dodawanie do drzewa elementu 1 1 statystyka pozycyjna to: 1 2 statystyka pozycyjna to: 10 3 statystyka pozycyjna to: 15 W drzewie nie ma wezla bedacego 4 statystyka pozycyjna Usuwanie z drzewa elementu 10 2 statystyka pozycyjna to: 15

Listing 6.21: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 6.20. Peny kod rdowy programu znajduje si w pliku bst.cpp // Element stanowicy zawarto wierzchokw drzewa 01 struct Element { 02 int val, stat; 03 }; // Funkcja okrelajca porzdek na wierzchokach drzewa w kolejnoci rosncych // wartoci val 04 int Cmp(const Element & a, const Element & b) { 05 return (a.val == b.val) ? -1 : a.val < b.val; 06 } // Funkcja aktualizujca warto stat wierzchoka

195

Listing 6.21: (c.d. listingu z poprzedniej strony) 07 void Update(Element * p, Element * ls, Element * rs) { 08 p->stat = 1 + (ls ? ls->stat : 0); 09 } // Funkcja zwraca wskanik do elementu drzewa, stanowicego k-t statystyk // pozycyjn 10 Element *Stat(BST<Element> &t, int k) { 11 VAR(v, t.root); 12 while (v && k != v->e.stat) { 13 if (v->s[0] && v->s[0]->e.stat >= k) v = v->s[0]; 14 else { 15 if (v->s[0]) k -= v->s[0]->e.stat; 16 k--; 17 v = v->s[1]; 18 } 19 } 20 return v ? &(v->e) : 0; 21 } 22 int main() { // Skonstruuj drzewo binarne wzbogacajc je obiektami typu Element oraz // funkcji Cmp i Update 23 BST<Element> Tree(Cmp, Update); 24 Element e, *p; 25 int cmd; // Wczytaj operacje do wykonania i je wykonaj 26 while (cin >> cmd >> e.val) { 27 if (cmd == 0) { 28 Tree.Insert(e); 29 cout << "Dodawanie do drzewa elementu " << e.val << endl; 30 } else if (cmd == 1) { 31 cout << "Usuwanie z drzewa elementu " << e.val << endl; 32 Tree.Delete(e); 33 } else { 34 p = Stat(Tree, e.val); 35 if (!p) cout << "W drzewie nie ma wezla bedacego " << e. 36 val << " statystyka pozycyjna" << endl; 37 else cout << e.val << " statystyka pozycyjna to: " << p->val << endl; 38 } 39 } 40 return 0; 41 }

Implementacja randomizowanych drzew binarnych, ktrych kod rdowy przedstawiony jest na listingu 6.22, rni si od BST tym, e podczas wykonywania operacji wstawiania do drzewa nowych elementw, wykonywane s losowe rotacje. W pozostaych operacjach, wykorzystanie a drzew RBST jest dokadnie takie samo, jak BST przykad przedstawiony zosta na listingu 6.23.

196

Listing 6.22: Implementacja struktury RBST // Implementacja struktury RBST 001 template <typename T> struct RBST { // Struktura wza drzewa 002 struct Ve { // Wskaniki na lewego i prawego syna 003 Ve *s[2]; // Pole e zawiera element przechowywany w wle 004 T e; // Zmienna wykorzystywana do realizacji rotacji na drzewie 005 int w; 006 Ve() { 007 s[0] = s[1] = 0; 008 }}; // Sygnatury funkcji do porwnywania elementw oraz do aktualizacji ich // zawartoci 009 #dene Less int (*Less)(const T&, const T&) 010 #dene Upd void (*Upd)( T*, T*, T*) 011 Less; 012 Upd; // Korze drzewa oraz dodatkowa zmienna pomocnicza 013 Ve *root, *v; // Wektor wskanikw do wzw drzewa wykorzystywany do wyznaczania cieki od // przetwarzanego wza do korzenia drzewa 014 vector<Ve *> uV; // Funkcja usuwa dany wze wraz z jego caym poddrzewem 015 void Rem(Ve * p) { 016 if (p) { 017 REP(x, 2) Rem(p->s[x]); 018 delete p; 019 } 020 } // Destruktor drzewa zwalnia zaalokowan pami 021 RBST() { 022 Rem(root); 023 } // Konstruktor drzewa przyjmuje jako parametry dwie funkcje - funkcj // okrelajc porzdek na elementach oraz funkcj suc do aktualizowania // informacji elementw 024 RBST( Less, Upd) : Less(Less), Upd(Upd) { 025 root = 0; 026 } // Funkcja aktualizuje wartoci elementw znajdujcych si na ciece od // ostatnio przetwarzanego wza do korzenia drzewa 027 void Update(vector<Ve *> &uV) { 028 for (int x = SIZE(uV) - 1; x >= 0 && (v = uV[x]); x--) { 029 v->w = 1 + (v->s[0] ? v->s[0]->w : 0) + (v->s[1] ? v->s[1]->w : 0);

197

Listing 6.22: (c.d. listingu z poprzedniej strony) 030 Upd(&v->e, v->s[0] ? &(v->s[0]->e) : 0, v->s[1] ? &(v->s[1]->e) : 0); 031 } 032 uV.clear(); 033 } // Funkcja pomocnicza wyszukujca wze w drzewie zawierajcy zadany element 034 Ve *Find(const T & e, bool s) { 035 v = root; 036 if (s) uV.PB(v); 037 for (int c; v && (-1 != (c = Less(v->e, e)));) { 038 v = v->s[c]; 039 if (s) uV.PB(v); 040 } 041 return v; 042 } // Funkcja wyszukuje zadany element w drzewie 043 T *Find(const T & e) { 044 return (v = Find(e, 0)) ? &(v->e) : 0; 045 } // Poniewa operacja wykonywania rotacji na drzewie wymaga przechowywania w // wzach informacji na temat wielkoci poddrzew, zatem informacj t mona // wykorzysta do wyznaczania statystyk pozycyjnych 046 T *StatPos(int nr) { 047 Ve *v = root; 048 if (!v || v->w < nr) return 0; 049 while (v && nr > 0) { 050 if (v->s[0] && v->s[0]->w >= nr) v = v->s[0]; 051 else { 052 if (v->s[0]) nr -= v->s[0]->w; 053 if (--nr) v = v->s[1]; 054 } 055 } 056 if (!v) return 0; 057 return &(v->e); 058 } // Funkcja pomocnicza wykonujca rotacje na drzewie w celu jego rwnowaenia 059 Ve *AtRoot(Ve * p, const T & e) { 060 Ve *d = new Ve, *a, *t, *r = 0; 061 vector<Ve *> Up[2]; 062 d->e = e; 063 int z = Less(p->e, e); 064 a = (t = p)->s[z]; 065 p->s[z] = 0; 066 d->s[1 - z] = t; 067 d->s[z] = a; 068 Up[1 - z].PB((r = d)->s[1 - z]); 069 while (a) { 070 if (Less(e, a->e) == z) {

198

Listing 6.22: (c.d. listingu z poprzedniej strony) 071 Up[z].PB(r = a); 072 a = a->s[1 - z]; 073 } else { 074 r->s[r->s[0] != a] = 0; 075 t->s[(t == d) z] = a; 076 t = r; 077 z = 1 - z; 078 } 079 } 080 REP(x, 2) Update(Up[x]); 081 uV.PB(d); 082 return d; 083 } // Funkcja wstawia zadany element do drzewa 084 void Insert(const T & e) { 085 if (!root) { 086 uV.PB(root = new Ve); 087 root->e = e; 088 } else if (v = Find(e, 1)) v->e = e; 089 else { 090 uV.clear(); 091 v = root; 092 int cmp = 0; 093 while (v && rand() % (v->w + 1)) { 094 uV.PB(v); 095 v = v->s[cmp = Less(v->e, e)]; 096 } 097 if (!v) { 098 uV.PB(v = uV.back()->s[cmp] = new Ve); 099 v->e = e; 100 } else SIZE(uV) ? uV.back()->s[cmp] = AtRoot(v, e) : root = AtRoot(v, e); 101 } 102 Update(uV); 103 } // Funkcja usuwa okrelony element z drzewa 104 bool Delete(const T & e) { 105 Ve *c = Find(e, 1), *k; 106 if (!c) { 107 uV.clear(); 108 return 0; 109 } 110 if (c->s[0] && c->s[1]) { 111 for (k = c->s[1]; k->s[0]; k = k->s[0]) 112 uV.PB(k); 113 uV.PB(k); 114 c->e = k->e; 115 c = k; 116 }

199

Listing 6.22: (c.d. listingu z poprzedniej strony) 117 118 119 120 121 122 123 } 124 }; if (SIZE(uV) >= 2) (k = uV[SIZE(uV) - 2])->s[k->s[0] != c] = c->s[0] ? c->s[0] : c->s[1]; else root = c->s[0] ? c->s[0] : c->s[1]; Update(uV); delete c; return 1;

Listing 6.23: Przykad wykorzystania struktury RBST.

W drzewie nie ma odcinka zawierajacego punktu 7 Dodawanie do drzewa odcinka (5,10) Punkt 7 zawiera odcinek (5,10) W drzewie nie ma odcinka zawierajacego punktu 2 Dodawanie do drzewa odcinka (1,20) Punkt 2 zawiera odcinek (1,20) Usuniecie z drzewa odcinka (1,20) W drzewie nie ma odcinka zawierajacego punktu 2

Listing 6.24: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 6.23. Peny kod rdowy programu znajduje si w pliku rbst.cpp // Element stanowicy zawarto wierzchokw drzewa 01 struct Element { // Lewy i prawy koniec odcinka oraz maksimum z kocw odcinkw z poddrzewa 02 int l, r, mr; 03 }; // Funkcja okrelajca porzdek liniowy na elementach 04 int Cmp(const Element &a, const Element &b) { 05 if (a.l == b.l && a.r == b.r) return -1; 06 return (a.l == b.l) ? a.r < b.r : a.l < b.l; 07 } // Funkcja aktualizujca warto mr elementw 08 void Update(Element *p, Element *ls, Element *rs){ 09 p->mr = p->r; 10 if (ls) p->mr = max(p->mr, ls->mr); 11 if (rs) p->mr = max(p->mr, rs->mr); 12 } 13 // Funkcja znajduje odcinek zawierajcy punkt o wsprzdnej k 14 Element* Find(RBST<Element> &t, int k) { 15 VAR(v, t.root); 16 while(v) { 17 if (v->e.l <= k && v->e.r >= k) return &(v->e); 18 v = (v->s[0] && v->s[0]->e.mr >= k) ? v->s[0] : v->s[1]; 19 } 20 return 0;

200

Listing 6.24: (c.d. listingu z poprzedniej strony) 21 } 22 23 int main() { // Skonstruuj randomizowane drzewo binarne wzbogacone obiektami typu Element // oraz funkcji Cmp i Update 24 RBST<Element> Tree(Cmp, Update); 25 Element e, *p; 26 int cmd; // Wczytaj operacje do wykonania i je wykonaj 27 while(cin >> cmd >> e.l) { 28 if (cmd == 0) { 29 cin >> e.r; 30 Tree.Insert(e); 31 cout << "Dodawanie do drzewa odcinka (" 32 << e.l << "," << e.r << ")" << endl; 33 } else if(cmd == 1) { 34 cin >> e.r; 35 cout << "Usuniecie z drzewa odcinka (" 36 << e.l << "," << e.r << ")" << endl; 37 Tree.Delete(e); 38 } else { 39 p = Find(Tree, e.l); 40 if (!p) cout << "W drzewie nie ma odcinka zawierajacego punktu " 41 << e.l << endl; 42 else cout << "Punkt " << e.l << " zawiera odcinek (" 43 << p->l << "," << p->r << ")" << endl; 44 } 45 } 46 return 0; 47 }

Zadanie: Puste prostopadociany


Pochodzenie: VI Olimpiada Informatyczna Prostopadocian nazwiemy regularnym, gdy: jednym z jego wierzchokw jest punkt o wsprzdnych (0, 0, 0), krawdzie wychodzce z tego wierzchoka le na dodatnich posiach ukadu wspa a rzdnych, krawdzie te maj dugoci nie wiksze ni 106 a

Rozwizanie: a cuboids.cpp

Dany jest zbir A punktw przestrzeni, ktrych wszystkie wsprzdne s cakowite i naa le do przedziau [1..106 ]. Szukamy prostopadocianu regularnego o maksymalnej objtoci, a ktry w swoim wntrzu nie zawiera adnego punktu ze zbioru A. Punkt naley do wntrza prostopadocianu, jeeli jest punktem prostopadocianu, ale nie jego ciany. 201

Zadanie
Napisz program, ktry: wczyta wsprzdne punktw ze zbioru A, wyznaczy jeden z prostopadocianw regularnych o maksymalnej objtoci, nie zawierajcy w swoim wntrzu punktw ze zbioru A, a wypisze wynik

W pierwszym wierszu znajduje si jedna cakowita nieujemna liczba n, n 5 000, bdca a liczb elementw zbioru A. W kolejnych n wierszach znajduj si trjki liczb cakowitych z a a przedziau [1..106 ] bdce wsprzdnymi (odpowiednio x, y i z) punktw ze zbioru A. Liczby a w wierszu pooddzielane s pojedynczymi odstpami. a

Wejcie

Wyjcie
W pierwszym i jedynym wierszu powinny znale si trzy liczby cakowite oddzielone pojedynczymi odstpami, bdce wsprzdnymi (odpowiednio x, y i z) wierzchoka znalezionego a prostopadocianu regularnego, ktry ma wszystkie wsprzdne dodatnie.

Przykad

Dla nastpujcego wejcia: a Poprawnym rozwizaniem jest: a


1000000 200000 1000

4 3 3 300000 2 200000 5 90000 3 2000 2 2 1000

Proste acm.uva.es - zadanie 10869 acm.sgu.ru - zadanie 193

rednie spoj.sphere.pl - zadanie 382 acm.sgu.ru - zadanie 199 acm.uva.es - zadanie 221

wiczenia

Trudne acm.sgu.ru - zadanie 263 acm.uva.es - zadanie 501 spoj.sphere.pl - zadanie 205

202

Rozdzia 7

Algorytmy tekstowe
W tym rozdziale omwimy rne algorytmy tekstowe wikLiteratura szo z nich jest zwizana z zagadnieniem wyszukiwania wzorca. a [EAT] - 5 Zaprezentujemy midzy innymi algorytm KMP, pozwalajcy na a [WDA] - 34 znajdowanie wszystkich wystpie wzorca w tekcie oraz jego moa [ASD] - 5 dykacj, umoliwiajc wyszukiwanie wielu wzorcw jednoczea a nie. Przedstawimy rwnie problemy wyznaczania maksymalnego leksykogracznie suksu sowa, wyliczania promieni palindromw, wyszukiwania minimalnej leksykogracznej cyklicznoci sowa oraz sprawdzania, czy dwa dane sowa s swoimi rwnoa wanociami cyklicznymi. Bardzo wan czci rozdziau jest prezentacja implementacji drzew suksowych a a struktury danych o liniowej wielkoci, ktra umoliwia reprezentacj wszystkich suksw danego sowa. Struktura ta pozwala na efektywne rozwizywanie wielu problemw tekstowych, a takich jak zliczanie liczby wystpie wzorca w tekcie, wyznaczanie najduszego wsplnego a podsowa wielu sw, czy znajdowanie najduszych podsw wystpujcych okrelon liczb a a razy. Dla danego sowa s o dugoci n kolejne litery tego sowa bdziemy numerowali od 1 do n i oznacza je bdziemy poprzez s1 , s2 , . . . , sn . W niektrych rozdziaach bd przedstawiane a algorytmy, ktrych czas dziaania zalee bdzie od wielkoci alfabetu (wyznaczanie krawdzi wychodzcych z wierzchokw etykietowanych literami wymaga wyszukania jej w sowniku, co a zajmuje czas logarytmiczny ze wzgldu na liczb liter). W przypadku tego typu algorytmw bdziemy pomijali logarytmiczny czynnik podczas analizy zoonoci realizowanych operacji.

7.1. Algorytm KMP


Zamy, e mamy dane dwa teksty s oraz t i chcemy znale Literatura wystpienie (ewentualnie wiele wystpie) t w s. Problem tego a a [WDA] - 34.4 typu nazywamy wyszukiwaniem wzorca t w tekcie s. [EAT] - 3.1 Wyszukiwanie wzorca w tekcie mona zrealizowa w bardzo [ASD] - 5.1.2 prosty sposb. Dla kadej pozycji przeszukiwanego tekstu mona sprawdza, czy kolejne jego litery zgadzaj si z literami wyszukia wanego wzorca. Implementacja takiego podejcia zajmuje dosownie dwie linijki, ale niestety zoono algorytmu jest w pesymistycznym przypadku kwadratowa. Wyszukujc bowiem a wzorzec postaci an b w tekcie a2n (zapis ab oznacza b-krotn konkatenacj a), dla pierwa szych n-liter bdzie wykonywanych n porwna ze wzorcem, zanim nastpi znalezienie niea 203

zgodnoci pomidzy literami a i b. Obserwujc dziaanie takiego algorytmu wida, e wiele a porwna mogo by zosta pominitych na podstawie wiedzy uzyskanej o tekstach podczas wykonywania poprzednich krokw algorytmu. Wykorzystujc t wiedz, istnieje moliwo a skonstruowania algorytmu dziaajcego w czasie liniowym. a Algorytm KMP (ktrego nazwa pochodzi od nazwisk jego autorw Knutha, Morrisa i Pratta) suy do wyszukiwania wzorca w tekcie w czasie liniowym. Jest on stosunkowo prosty w implementacji do swojego dziaania wykorzystuje tzw. tablic preksow, ktra jest a wyznaczana dla wyszukiwanego wzorca. Dla k-tej litery wzorca t, odpowiednia warto tablicy jest rwna dugoci najduszego waciwego preksu sowa t1 t2 . . . tk , bdcego rwnoczenie a jego suksem. Dla sowa abca, najduszym waciwym prekso - suksem jest a, natomiast w przypadku sowa abcab jest to ab. Rozpatrujc zatem wzorzec abcaba, warto tablicy a preksowej bdzie nastpujca: [0, 0, 0, 1, 2, 1]. a Wyznaczanie wartoci tablicy preksowej p dla tekstu t mona wykonywa przyrostowo na podstawie wartoci p[1], p[2], . . . , p[k 1] w prosty sposb wyznaczy mona warto p[k]. Zauwamy, e jeeli tp[k1]+1 = tk , to wtedy p[k] = p[k 1] + 1, czyli kolejny prekso suks stanowi wyduenie prekso - suksu dla sowa o jedn liter krtszego. Jeli natomiast a tp[k1]+1 = tk , to wwczas naley wyznaczy najduszy prekso - suksem sowa t1 t2 . . . tk1 taki, aby litera wystpujca po nim w sowie t bya rwna tk . Wyznaczenie takiego sowa a mona wykona, sprawdzajc kolejne preksy sowa t dugoci p[k 1], p[p[k 1]], . . . Jeli a w ten sposb nie zostanie znaleziony odpowiedni preks, to p[k] = 0. Poniewa w kadym kroku dugo ostatnio znalezionego prekso - suksu moe wzrasta co najwyej o 1, zatem wyznaczanie tablicy preksowej mona zrealizowa w czasie liniowym ze wzgldu na dugo wzorca. Po wyznaczeniu tablicy preksowej, wyszukiwanie wzorca w tekcie jest proste, wystarczy porwnywa kolejne litery wzorca i tekstu, pamitajc dugo zgadzajcego si fragmentu a a tekstu. Jeli porwnywane litery wzorca i tekstu s rne, to naley wykorzysta tablic a preksow w celu cofnicia si we wzorcu a do momentu znalezienia takich samych liter a (krok ten wyglda podobnie do konstrukcji tablicy preksowej). a Prezentowana na listingu 7.1 funkcja void KMP(const char*, const char*, void (*) (int)) przyjmuje jako parametry: tekst, wyszukiwany wzorzec oraz wskanik do funkcji wywoywanej dla kadego wyznaczonego wystpienia wzorca w tekcie. Przykad wykorzystania a tej funkcji zosta przedstawiony na listingu 7.2.
Listing 7.1: Implementacja funkcji void KMP(const char*, const char*, void (*)(int)) // Funkcja wyszukuje wystpienia wzorca w tekcie i dla kadego znalezionego wystpienia wywouje funkcj fun 01 void KMP(const char* str,const char* wzo, void (*fun)(int)) { 02 #dene KMPH(z) while(k>0 && wzo[k] != z[q]) k=pre[k]; if(wzo[k]==z[q]) k++; 03 int pre[strlen(wzo) + 1], k = 0, q, m; 04 pre[1] = 0; // Wyznacz funkcj prefiksow dla wyszukiwanego wzorca 05 for (q = 1; wzo[q]; q++) { 06 KMPH(wzo); 07 pre[q + 1] = k; 08 } 09 m = q; 10 k = 0; // Dla kolejnych liter przeszukiwanego tekstu... 11 for (q = 0; str[q]; q++) {

204

Listing 7.1: (c.d. listingu z poprzedniej strony) // Uywajc funkcji prefiksowej, wyznacz dugo sufiksu tekstu str[0..q], bdcego jednoczenie prefiksem wzorca 12 KMPH(str); // Jeli wyznaczony prefiks jest rwny dugoci wzorca, to wywoaj funkcj fun dla znalezionego wystpienia wzorca 13 if(m == k) { 14 fun(q - m + 1); 15 k = pre[k]; 16 } 17 } 18 }

Listing 7.2: Przykad wykorzystania funkcji void void(*)(int))

KMP(const

char*, const

char*,

Wyszukiwanie abab w Znaleziono wystapienie Znaleziono wystapienie Znaleziono wystapienie

ababcabdababab wzorca na pozycji 0 wzorca na pozycji 8 wzorca na pozycji 10

Listing 7.3: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.2. Peny kod rdowy programu znajduje si w pliku kmp.cpp // Funkcja wywoywana dla kadego znalezionego wystpienia wzorca 01 void Disp(int x) { 02 cout << "Znaleziono wystapienie wzorca na pozycji " << x << endl; 03 } 04 int main() { 05 string pattern, text; // Wczytaj tekst oraz wyszukiwany wzorzec 06 cin >> text >> pattern; 07 cout << "Wyszukiwanie " << pattern << " w " << text << endl; // Wyznacz wszystkie wystpienia wzorca w tekcie 08 KMP(text.c str(), pattern.c str(), Disp); 09 return 0; 10 }

Zadanie: Szablon
Pochodzenie: XII Olimpiada Informatyczna Rozwizanie: a template.cpp

Bajtazar chce umieci na swoim domu dugi napis. W tym celu najpierw musi wykona odpowiedni szablon z wycitymi literkami. Nastpnie przykada taki szablon we waciwe miejsce do ciany i maluje po nim farb, w wyniku czego na cianie pojawiaj si literki a a znajdujce si na szablonie. Gdy szablon jest przyoony do ciany, to malujemy od razu a wszystkie znajdujce si na nim literki (nie mona tylko czci). Dopuszczamy natomiast a moliwo, e ktra litera na cianie zostanie narysowana wielokrotnie, w wyniku rnych 205

przyoe szablonu. Literki na szablonie znajduj si koo siebie (nie ma tam przerw). Oczya wicie mona wykona szablon zawierajcy cay napis. Bajtazar chce jednak zminimalizowa a koszty i w zwizku z tym wykona szablon tak krtki, jak to tylko moliwe. a

Zadanie
Napisz program, ktry: wczyta ze standardowego wejcia napis, ktry Bajtazar chce umieci na domu, obliczy minimaln dugo potrzebnego do tego szablonu, a wypisze wynik na standardowe wyjcie.

Wejcie

W pierwszym i jedynym wierszu standardowego wejcia znajduje si jedno sowo. Jest to napis, ktry Bajtazar chce umieci na domu. Napis skada si z nie wicej ni 500 000, oraz nie mniej ni 1 maej litery alfabetu angielskiego.

Wyjcie
W pierwszym i jedynym wierszu standardowego wyjcia naley zapisa jedn liczb cakowit a a minimaln liczb liter na szablonie. a

Przykad

Dla nastpujcego wejcia: a


8

Poprawnym rozwizaniem jest: a

ababbababbabababbabababbababbaba

a b a b b a b a a b a b b a b a a b a b b a b a a b a b b a b a a b a b b a b a a b a b b a b a b b a b a b a b b a b a b a b b a b a b b a b a

7.2. Minimalny okres sowa


Tablica preksowa opisana w ramach algorytmu KMP ma wiele ciekawych wasnoci. Przy jej uyciu mona przykadowo wyznacza minimalny okres danego sowa. Denicja 7.2.1 Okresem sowa s nazywamy takie sowo t, |l| > |t|, dla ktrego istniej a sowo l oraz liczba naturalna k, takie e tk = sl. Innymi sowy, s jest preksem sowa tk . Przykadowo, dla sowa abcabcabc, okresem jest zarwno sowo abcabc, jak i abc. Minimalnym okresem sowa jest najkrtszy okres tego sowa w powyszym przykadzie, najkrtszym okresem jest abc. Przypomnijmy teraz denicj tablicy preksowej p wyznaczonej dla sowa s o dugoci l p[x], x {1, 2, . . . , l} jest rwne dugoci najduszego waciwego prekso - suksu sowa s1 s2 . . . sx . Korzystajc z tej denicji, mona w prosty sposb pokaza, e dugo a minimalnego okresu sowa s jest rwna l p[l]. 206

Funkcja int MinPeriod(const char*) przedstawiona na listingu 7.4 przyjmuje jako parametr tekst, a zwraca dugo jego minimalnego okresu. Przykad jej uycia zosta przedstawiony na listingu 7.5.
Listing 7.4: Implementacja funkcji int MinPeriod(const char*) // Funkcja wyznacza dugo minimalnego okresu sowa 01 int MinPeriod(const char *s) { 02 int p[strlen(s--) + 1], k = 0, q; 03 p[1] = 0; // Dla tekstu s wyznacz funkcj prefiksow 04 for (q = 2; s[q]; q++) { 05 while (k > 0 && s[k + 1] != s[q]) k = p[k]; 06 if (s[k + 1] == s[q]) k++; 07 p[q] = k; 08 } // Minimalny okres tekstu s jest rwny swojej dugoci pomniejszonej o // dugo swojego maksymalnego prefikso-sufiksu 09 return q - p[q - 1] - 1; 10 }

Listing 7.5: Przykad uycia funkcji int MinPeriod(const char*)

MinPeriod(abcadc) = (abcadc) MinPeriod(abcabc) = (abc) MinPeriod(aaaaaa) = (a) MinPeriod(ababa) = (ab)

Listing 7.6: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.5. Peny kod rdowy programu znajduje si w pliku minperiod.cpp 1 int main() { 2 string text; // Wczytaj kolejne teksty 3 while(cin >> text) { // Wyznacz dugo minimalnego okresu tekstu oraz wypisz znaleziony wynik 4 cout << "MinPeriod(" << text << ") = " << "(" << 5 text.substr(0,MinPeriod(text.c str())) << ")" << endl; 6 } 7 return 0; 8}

7.3. KMP dla wielu wzorcw (algorytm Bakera)


Algorytm KMP do wyszukiwania wystpie wzorca w w zadanym tekcie t wykorzystuje a do swojego dziaania tablic preksow. Dziki niej jest on w stanie w czasie staym, znajc a a najduszy suks tekstu t1 t2 . . . tk1 , bdcy rwnoczenie preksem wzorca w (oznaczmy go a przez pk1 ), wyznacza odpowiedni suks pk tekstu t1 t2 . . . tk . Znalezienie wzorca w tekcie 207

(d) (c) (b) (a) Rysunek 7.1: (a) Posta struktury mkmp po dodaniu wzorca ABA. (b) Posta struktury po dodaniu
wzorca BA (c) Posta struktury po dodaniu kolejnego wzorca ABB. (d) Wyznaczenie funkcji preksowej

jest w ten sposb rwnowane z wyznaczeniem suksu p|w| . Mona si w takiej sytuacji zastanowi, co by byo w przypadku wyszukiwania na raz wielu wzorcw w tekcie. Wida, e jest to moliwe pod warunkiem istnienia sposobu na wyznaczanie odpowiedniego suksu pm , ktry tym razem musiaby by preksem dowolnego z wyszukiwanych wzorcw. Okazuje si, e jest to rzeczywicie moliwe algorytm ten znany jest pod nazw algorytmu Bakera. a Przed rozpoczciem wyszukiwania wzorcw w tekcie, podobnie Literatura jak klasyczny algorytm KMP, algorytm Bakera musi najpierw wy[EAT] - 7.1, 7.2 znaczy posta funkcji preksowej, ktra w tym przypadku ma bar[ASD] - 5.1.3 dziej skomplikowan struktur. W tym celu budowane jest drzewo a wszystkich wyszukiwanych wzorcw (przykad przedstawiony jest na rysunkach 7.8.a-c). Kolejnym krokiem jest wyznaczanie funkcji preksowej dla kadego wza drzewa reprezentujcego tekst t1 wyznaczany jest wze reprezentujcy tekst t2 taki, e a a t2 jest najduszym waciwym suksem tekstu t1 spord wszystkich tekstw posiadajcych a wierzchoki w drzewie. Zauwamy, e w przypadku drzewa reprezentujcego jeden wzorzec, a wyznaczone pary wierzchokw (t1 , t2 ) s dokadnym odpowiednikiem tablicy preksowej z a algorytmu KM P . W tym momencie, moliwe jest ju przystpienie do procesu przeszukia wania tekstu. Dla kolejnych liter tekstu wykonywane s przesunicia wzdu odpowiednich a krawdzi w drzewie. W przypadku, gdy nie istnieje krawd dla kolejnej analizowanej litery a, wykonywane s kroki w ty wzd funkcji preksowej tak dugo, a tra si na wierzchoek a posiadajcy krawd dla litery a, lub wrci si do korzenia. Przykadowy proces wyszukiwania a wzorca przedstawiony jest na rysunku 7.2. Przedstawiona na listingu 7.7 struktura mkmp realizuje opisany algorytm Bakera. Udostpnia ona funkcje int addWord(char*) suc do dodawania kolejnego wzorca oraz void a a calcLink(), ktrej zadaniem jest wyznaczanie funkcji preksowej (naley j wywoa po doa daniu do struktury ostatniego wzorca). Czas dodawania wzorca do struktury jest liniowy ze wzgldu na jego dugo, natomiast wyznaczanie funkcji preksowej jest liniowe ze wzgldu na liczb wierzchokw w drzewie (ktra jest ograniczona przez sumaryczn dugo wszystkich a wzorcw). Do przeszukiwania tekstu su dwie funkcje: void SearchAll(const char*, void (*) a (int, int)), oraz void SearchFirst(const char*, void (*)(int, int)). Obie z nich przyjmuj jako parametry przeszukiwany tekst w oraz wskanik na funkcj f , ktra jest wywoa ywana dla kadego znalezionego wystpienia wzorca w tekcie. Funkcja f jako parametry a otrzymuje odpowiednio numer znalezionego wzorca oraz jego pozycj w tekcie. Rnica midzy tymi dwoma funkcjami polega na tym, e pierwsza z nich wyznacza wszystkie wystpienia a 208

B A|BBA

B ABB|A

B ABB|A

B ABBA|

(a)

(b)

(c)

(d)

Rysunek 7.2: (a) Rozpoczcie przeszukiwania tekstu ABBA. Po wczytaniu pierwszej litery A aktywnym wierzchokiem jest syn korzenia z liter A. (b) Wczytano kolejne dwie litery. Zosta a znaleziony wzorzec ABB. (c) Kolejn liter tekstu jest A. Poniewa aktualny wierzchoa a ek drzewa nie ma syna dla tej litery, zatem wykonywane jest przejcie zgodne z funkcj a preksow. (d) Przetworzenie ostatniej litery i znalezienie wzorca BA. a

wzorcw, podczas gdy druga dla kadego wzorca wyznacza tylko pierwsze jego wystpienie. a Zoono dziaania tych funkcji to odpowiednio O(n + m), gdzie n to dugo przeszukiwanego tekstu, a m to liczba znalezionych wystpie wzorca. a
Listing 7.7: Implementacja struktury mkmp 01 struct mkmp { // Struktura reprezentujca wierzchoek w drzewie wzorcw 02 struct leaf { // Mapa son suy do reprezentowania krawdzi wychodzcych z wierzchoka 03 map<char, leaf *> son; // Element lnk reprezentuje warto funkcji prefiksowej wierzchoka, // natomiast wo jest wskanikiem na wierzchoek najduszego wzorca, // bdcego jednoczenie sufiksem dla tekstu aktualnego wierzchoka 04 leaf *lnk, *wo; // Zmienna reprezentujca numer wzorca wierzchoka (jeli wierzchoek nie // reprezentuje adnego wzorca, jest to -1) 05 int el; 06 leaf() : el(-1) { } 07 }; // Wektor zawierajcy dugoci poszczeglnych wzorcw wstawionych do struktury 08 VI len; // Korze drzewa 09 leaf root; // Pomocnicza funkcja przetwarzajca liter tekstu i dokonujca odpowiedniego // przejcia w drzewie 10 leaf *mv(leaf * w, char l) { // Dopki wierzchoek w nie ma syna dla litery l przesuwaj si wzdu // funkcji prefiksowej 11 while (w != &root && w->son.find(l) == w->son.end()) w = w->lnk; // Jeli wierzchoek w posiada syna dla litery l, to przejd do tego // wierzchoka 12 if (w->son.find(l) != w->son.end()) w = w->son[l]; 13 return w;

209

Listing 7.7: (c.d. listingu z poprzedniej strony) 14 // 15 16 17 // // 18 19 20 21 // // 22 23 24 25 // 26 27 // // 28 // 29 30 31 // // 32 33 34 35 // 36 // // // 37 // // 38 // 39 // 40 // 41 } Funkcja wstawia nowy wzorzec do struktury int addWord(const char *s) { int l = strlen(s); leaf *p = &root; Przejd od korzenia drzewa do wierzchoka reprezentujcego cay wzorzec, ewentualnie tworzc jeszcze nie istniejce wierzchoki for (; *s; ++s) { VAR(e, p->son.find(*s)); p = (e == p->son.end())? p->son[*s] = new leaf : e->second; } Jeli aktualny wierzchoek nie reprezentuje jeszcze adnego wzorca, to przypisz mu nowy identyfikator i zapamitaj jego dugo if (p->el == -1) { p->el = SIZE(len); len.PB(l); } Zwr identyfikator wzorca return p->el; } Funkcja wyznacza funkcj prefiksow dla wierzchokw drzewa. Wylicza ona rwnie wartoci pl wo void calcLink() { Wektor l jest uywany jako kolejka dla przeszukiwania drzewa metod BFS vector<leaf *> l; leaf *w; root.lnk = root.wo = 0; Wstaw do kolejki wszystkich synw korzenia oraz ustaw ich funkcj prefiksow na korze FOREACH(it, root.son) { l.PB(it->ND); it->ND->lnk = &root; } Dla wszystkich elementw z kolejki... REP(x, SIZE(l)) { Wyliczenie pola wo - jego warto to wierzchoek wskazywany przez funkcj prefiksow (jeli reprezentuje on wzorzec), lub w przeciwnym przypadku warto pola wo tego wierzchoka l[x]->wo = (l[x]->lnk->el != -1) ? l[x]->lnk : l[x]->lnk->wo; Dla kadego syna aktualnego wierzchoka (liter prowadzc do syna reprezentuje it->ST)... FOREACH(it, l[x]->son) { Wstaw go do kolejki l.PB(it->ND); Wyznaczanie warto funkcji prefiksowej w = l[x]->lnk; Dokonaj przejcia z wierzchoka w w = mv(w, it->ST);

210

Listing 7.7: (c.d. listingu z poprzedniej strony) // Ustaw wierzchoek w jako warto funkcji prefiksowej 42 it->ND->lnk = w; 43 } 44 } 45 } // Funkcja wyszukuje wszystkie wystpienia wzorcw w zadanym tekcie i dla // kadego znalezionego wystpienia wywouje funkcj func 46 void searchAll(const char *s, void (*func) (int, int)) { 47 leaf *p = &root; // Dla kadej kolejnej litery przeszukiwanego tekstu... 48 for (int x = 0; s[x]; ++x) { // Dokonaj przejcia dla litery s[x] 49 p = mv(p, s[x]); // Jeli aktualny wierzchoek (lub pewne jego sufiksy) reprezentuj wzorzec, // to wywoaj dla nich funkcj func 50 for (VAR(r, p); r; r = r->wo) 51 if (r->el != -1) func(x - len[r->el] + 1, r->el); 52 } 53 } // Funkcja wyszukuje pierwsze wystpienia kadego ze wzorcw w zadanym tekcie i // dla kadego znalezionego wystpienia wywouje funkcj func 54 void searchFirst(const char *s, void (*func) (int, int)) { 55 leaf *p = &root, *r, *t; // Dla kadej kolejnej litery przeszukiwanego tekstu... 56 for (int x = 0; s[x]; ++x) { // Dokonaj przejcia dla litery s[x] 57 p = mv(p, s[x]); // Wywoaj funkcj func dla wszystkich znalezionych wzorcw, oraz usu // informacje o tych wzorcach, aby nie zostay znalezione ponownie 58 r = p; 59 while (r) { // Jeli wierzchoek reprezentuje wzorzec, to wywoaj funkcj func 60 if (r->el != -1) func(x - len[r->el] + 1, r->el); // Usu identyfikator wzorca, aby nie zosta on znaleziony po raz kolejny 61 r->el = -1; // Przejd do kolejnego wzorca i wyzeruj wskanik wo (aby nie wykonywa // tego ruchu w przyszoci) 62 t = r; 63 r = r->wo; 64 t->wo = 0; 65 } 66 } 67 } 68 };

211

Listing 7.8: Przykad wykorzystania struktury mkmp

Dodawanie wzorca ABA Dodawanie wzorca ABB Dodawanie wzorca BA Przeszukiwanie tekstu ABABCABCBCA Znaleziono wzorzec 0 na pozycji 0 Znaleziono wzorzec 1 na pozycji 2

Listing 7.9: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.8. Peny kod rdowy programu znajduje si w pliku mkmp.cpp // Funkcja wywoywana dla kadego znalezionego wzorca 01 void Disp(int x, int p) { 02 cout << "Znaleziono wzorzec " << x << " na pozycji " << p << endl; 03 } 04 int main() { 05 mkmp search; 06 string text; 07 int n; // Wczytaj liczb wzorcw 08 cin >> n; // Wczytaj kolejne wzorce 09 REP(x, n) { 10 cin >> text; 11 cout << "Dodawanie wzorca " << text << endl; 12 search.addWord(text.c str()); 13 } // Wyznacz funkcj prefiksow 14 search.calcLink(); 15 cin >> text; // Przeszukaj zadany tekst 16 cout << "Przeszukiwanie tekstu " << text << endl; 17 search.searchAll(text.c str(), Disp); 18 return 0; 19 }

Zadanie: MegaCube
Pochodzenie: V Obz Olimpiady Informatycznej im. A. Kreczmara Rozwizanie: a megacube.cpp

Grupa studentw pewnej uczelni, zafascynowana niedawno obejrzanym lmem Cube2Hypercube, postanowia nakrci kolejn seri tego wspaniaego lmu. Z uwagi na niski bua det, jedyne na co mog sobie pozwoli, jest dwuwymiarowa kostka (skoro dwuwymiarowa, a to niech bdzie przynajmniej prostoktna). W celu urozmaicenia lmu, kada komnata zostaa nie pomalowana na jeden z dostpnych kolorw. Gwni bohaterowie lmu jako wskazwk, umoliwiajc im wydostanie si z labiryntu, otrzymaj map z zaznaczonymi kolorami poa a a szczeglnych komnat. Bohaterowie bd chodzi po labiryncie i stara si zlokalizowa sw a a pozycj na podstawie kolorw odwiedzanych komnat oraz mapy. Poniewa jeden z aktorw 212

przez przypadek zabra ze sob komputer wic postanowi napisa program, ktry pozwoli a okreli pozycj w labiryncie. Pom mu w tym niebanalnym zadaniu.

Zadanie
Napisz program, ktry: Wczyta opis labiryntu, po ktrym poruszaj si bohaterowie oraz obszar zbadany przez a nich (znaj tylko pewien obszar labiryntu, ktry jest ksztatu prostokta), a a wyliczy, w ilu rnych miejscach mog si znajdowa bohaterowie lmu, a wypisze wynik na standardowe wyjcie.

5 1 2 1 1 2 3 2 3 2 2 3

Wejcie
W pierwszym wierszu standardowego wejcia zapisane s cztery liczby naturalne cx , cy , mx , my a (1 mx cx 1 000, 1 my cy 1 000); cx i cy to odpowiednio wymiary labiryntu (czyli rwnie jego mapy); a mx i my to wymiary zwiedzonego obszaru. W kolejnych cy wierszach znajduje si po cx liczb naturalnych (nie wikszych od 100), reprezentujcych kolory a poszczeglnych pomieszcze na mapie. Nastpnie znajduje si my wierszy zawierajcych po a mx liczb naturalnych (podobnie nie wikszych od 100), reprezentujcych kolory kolejnych a pomieszcze zwiedzonych przez bohaterw.

Wyjcie
W pierwszym i jedynym wierszu standardowego wyjcia powinna si znajdowa jedna liczba naturalna, odpowiadajca liczbie miejsc, w ktrych mog si znajdowa bohaterowie. a a

Przykad
2 2 1 2 2 1 2 2 3 3 3

Proste acm.uva.es - zadanie 10010 spoj.sphere.pl - zadanie 48

7.4. Promienie palindromw w sowie


Palindromem nazywamy sowo, ktre czytane zarwno od pocztku, jak i od koca jest a takie samo. Kade sowo o dugoci 1 jest palindromem. Odpowiedzenie na pytanie, czy 213

Dla nastpujcego wejcia: a Poprawnym rozwizaniem jest: a


3

rednie acm.uva.es - zadanie 11019 spoj.sphere.pl - zadanie 263 acm.uva.es - zadanie 10298

wiczenia

Trudne acm.uva.es - zadanie 10679 spoj.sphere.pl - zadanie 413

Dodatkowo, pk+l jest nie mniejsze od min(pkl , pk l). Zczenie tych dwch prostych a wasnoci pozwala ju na efektywne wyznaczenie wyniku. Algorytm wyznaczajcy promienie palindromw zrealizowany zosta jako funkcja VI Pala Rad (const char*, bool), ktrej kod rdowy zosta przedstawiony na listingu 7.10. Funkcja ta przyjmuje jako parametry tekst, dla ktrego wyznaczane s promienie palindromw a oraz warto logiczn p. W przypadku, gdy p przyjmuje warto prawda, algorytm wyznacza a promienie dla palindromw nieparzystych takich, ktrych rodkiem jest litera tekstu. W przypadku, gdy p ma warto fasz, wyznaczane s promienie palindromw parzystych (patrz a przykad z listingu 7.11). Przedstawiony kod znany jest jako algorytm Manachera.
Listing 7.10: Implementacja funkcji VI PalRad(const char*, bool) // Funkcja wylicza promienie palindromw danego tekstu 01 VI PalRad(const char *x, bool niep) { 02 int n = strlen(x), i = 1, j = 0, k; 03 VI r(n, 0); 04 while (i < n) { // Dopki kolejno sprawdzane litery palindromu o rodku na pozycji i s // takie same, zwiksz promie 05 while (i + j + niep <= n && i - j > 0 && x[i - j - 1] == x[i + j + niep]) 06 j++; 07 r[i] = j; 08 k = 1; // Stosujc zalenoci midzy promieniami palindromw wyznacz jak najwicej // kolejnych promieni 09 while (r[i - k] != j - k && k <= j) r[i + k] = r[i - k] <? j - k++; 10 j = 0 >? j - k; 11 i += k; 12 } 13 return r; 14 }

dane sowo jest palindromem, czy nie jest proste. Znacznie trudniejszym problemem jest znalezienie w danym tekcie wszystkich palindromw. Postawmy sobie nastpujcy problem a dla kadej litery tekstu naley obliczy maksymalny promie palindromu o rodku w tej literze. Przykadowo, dla tekstu abbabbb, poszukiwanym promieniem dla 4-tej litery jest 2, natomiast dla litery 6-tej jest 1. Wykorzystujc siln regularno tekstu jak narzuca istnienie a a a Literatura w nim palindromu mona wyznaczy poszukiwane promienie w [EAT] - 8.3 czasie liniowym. Oznaczmy przetwarzany tekst przez t = t1 t2 . . . tn , [ASD] - 5.3.4 natomiast odpowiednie promienie palindromw tego tekstu przez p1 , p2 , . . . , pn . Zamy, e wyznaczone zostay ju promienie palindromw p1 , p2 , . . . , pk . Wasno, ktra pozwala na efektywne wyznaczanie kolejnych promieni ma posta: pk+l = min(pkl , pk l), l {1, 2, . . .} , jeli pk+l pk l

Listing 7.11: Przykad wykorzystania funkcji VI PalRad(const char*, bool)

Analizowany tekst: aabaaabaabaca Palindromy parzyste: 0 1 0 0 1 1 0 0 3 0 0 0 0 214

Listing 7.11: (c.d. listingu z poprzedniej strony)

Palindromy nieparzyste:

0 0 2 0 4 0 2 0 0 1 0 1 0

Listing 7.12: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.11. Peny kod rdowy programu znajduje si w pliku palrad.cpp 01 int main() { 02 string text; // Wczytaj tekst 03 cin >> text; 04 cout << "Analizowany tekst: " << text << endl; // Wyznacz promienie palindromw parzystych 05 VI res = PalRad(text.c str(), 0); 06 cout << "Palindromy parzyste: "; 07 FOREACH(it, res) cout << " " << (*it); 08 cout << endl; // Wyznacz promienie palindromw nieparzystych 09 res = PalRad(text.c str(), 1); 10 cout << "Palindromy nieparzyste: "; 11 FOREACH(it, res) cout << " " << (*it); 12 cout << endl; 13 return 0; 14 }

Zadanie: Palindromy
Pochodzenie: II Olimpiada Informatyczna Rozwizanie: a pal.cpp

Sowo jest palindromem wtedy i tylko wtedy, gdy nie zmienia si, jeli czytamy je wspak. Palindrom jest parzysty, gdy ma dodatni parzyst liczb liter. a a Przykadem parzystego palindromu jest sowo abaaba. Rozkadem sowa na palindromy parzyste jest jego podzia na rozczne czci, z ktrych a kada jest palindromem parzystym. Rozkadami sowa: bbaabbaabbbaaaaaaaaaaaabbbaa na palindromy parzyste s: a bbaabb aabbbaaaaaaaaaaaabbbaa oraz: bb aa bb aa bb baaaaaaaaaaaab bb aa Pierwszy rozkad zawiera najmniejsz moliw liczb palindromw, drugi jest rozkadem a a na maksymaln moliw liczb palindromw parzystych. Zauwa, e sowo moe mie wiele a a rnych rozkadw na palindromy parzyste, albo nie mie adnego.

Zadanie
Napisz program, ktry: wczytuje sowo, 215

bada, czy da si je rozoy na palindromy parzyste, jeli nie, to wypisuje sowo N IE jeli tak, to zapisuje jego rozkady na minimaln i maksymaln liczb palindromw a a parzystych.

Wejcie
W pierwszym wierszu wejcia znajduje si jedno sowo zoone z co najmniej 1 i co najwyej 200 maych liter alfabetu angielskiego, zapisane w jednym wierszu bez odstpw pomidzy literami.

Wyjcie
Twj program powinien wypisa: tylko jeden wyraz N IE albo: w pierwszym wierszu cig sw oddzielonych odstpami stanowicy jeden rozkad a a danego sowa na minimaln liczb palindromw parzystych, a w drugim wierszu jeden rozkad danego sowa na maksymaln liczb palindromw a parzystych.

Przykad
Dla nastpujcego wejcia: a
bbaabbaabbbaaaaaaaaaaaabbbaa

abcde

Poprawnym rozwizaniem jest: a


bbaabb aabbbaaaaaaaaaaaabbbaa bb aa bb aa bb baaaaaaaaaaaab bb aa

Dla nastpujcego wejcia: a

Poprawnym rozwizaniem jest: a


bbaabb aabbbaaaaaaaaaaaabbbaa bb aa bb aa bb baaaaaaaaaaaab bb aa

216

A A C B A A C A A B C A C A C A BAC C ABAC B C C

AC

ABAC

(a)

(b)

Rysunek 7.3: (a) Nieskompresowane drzewo suksowe zbudowane dla tekstu BAABAC. Pogrubione
wzy reprezentuj suksy tekstu. (b) Skompresowane drzewo suksowe zbudowane dla a tego samego tekstu.

7.5. Drzewa suksowe


Drzewo suksowe jest struktur danych, umoliwiajc reprea a a Literatura zentacj wszystkich suksw danego sowa. Prosta posta takiego [EAT] - 5 drzewa ma rozmiar kwadratowy ze wzgldu na dugo sowa. Przy[CST] kad zosta przedstawiony na rysunku 7.3.a. Drzewa suksowe sta[ASD] - 5.2 nowi bardzo poyteczn struktur danych, przy uyciu ktrej a a mona rozwiza (zakadajc, e drzewo suksowe jest ju skona a struowane) bardzo wiele problemw tekstowych. Przykadowo, wyszukiwanie wzorca w tekcie mona zrealizowa przechodzc od korzenia drzewa suksowego po krawdziach reprea zentujcych kolejne litery wyszukiwanego wzorca. a Wyznaczenie drzewa suksowego w takiej postaci, w jakiej jest ono przedstawione na rysunku 7.3.a nie jest trudne. Wystarczy dobudowywa odpowiednie wierzchoki drzewa dla kolejno przetwarzanych suksw tekstu. Skonstruowane w ten sposb drzewo ma niestety kwadratowy rozmiar. Istnieje jednak moliwo przedstawienia drzewa suksowego w innej postaci skompresowanej. W drzewie takim spjne cieki bez rozgazie w wierzchokach zostaj zamienione przez jedn krawd. Przykad takiego drzewa przedstawiony jest na a a rysunku 7.3.b. Drzewo tego typu ma co najwyej dwa razy wicej wierzchokw, ni wynosi dugo tekstu, dla ktrego zostao ono zbudowane. Konstrukcj skompresowanego drzewa suksowego mona przeprowadzi w dwch fazach najpierw wyznaczy nieskompresowane drzewo suksowe, a nastpnie zamieni w nim wszystkie spjne cieki bez rozgazie na odpowiednie krawdzie. Wynikiem takiego algorytmu jest drzewo o liniowej wielkoci, ale proces konstrukcji cay czas dziaa w czasie kwadratowym. Okazuje si jednak, e istnieje inny sposb konstruowania drzew suksowych, dziaajcy a w czasie liniowym. Jedn z takich metod jest algorytm Ukonena, ktrego opis mona znale a a w [CST]. Przedstawiona na listingu 7.13 implementacja struktury SufTree jest realizacj algorytmu opisanego w [ASD] (w celu dokadnej analizy dziaania algorytmu naley odwoywa si do literatury). Drzewo suksowe SufTree zbudowane dla tekstu t skada si z wierzchokw o typie SufV. Kady wierzchoek zawiera pola int p oraz int k, ktre okrelaj podsowo tp tp+1 . . . tk1 , a bdce etykiet krawdzi prowadzcej do tego wierzchoka. Oprcz tego, pole bool s oznaa a a cza, czy wierzchoek reprezentuje peny suks (pogrubione wierzchoki na rysunku 7.3.b). Istnieje moliwo wzbogacenia kadego wierzchoka o dodatkowe pole e, w ktrym mona przechowywa specjalne informacje zalene od algorytmw, do ktrych wykorzystywane jest drzewo suksowe.

217

Listing 7.13: Implementacja struktury SuffixTree // Drzewo sufiksowe 01 template<typename T> struct SufTree { // Struktura reprezentujca wze drzewa 02 struct SufV { // Mapa synw wza 03 map<char, SufV*> sons; // Pocztek oraz koniec tekstu reprezentowanego przez krawd prowadzc do wza 04 int p, k; // Czy wze reprezentuje sufiks sowa 05 bool s; // Obiekt reprezentuje dodatkowe wzbogacenie wza, ktre moe by wykorzystywane // przez algorytmy korzystajce z drzew sufiksowych 06 T e; // Wskanik na ojca w drzewie 07 SufV *par; // Konstruktor wza 08 SufV(int p, int k, SufV *par, bool s) : p(p), k(k), par(par), s(s) {} // Destruktor wza usuwa wszystkich synw 09 SufV() { 10 FOREACH(it, sons) delete it->second; 11 } 12 }; // Struktura wykorzystywana do konstrukcji drzewa sufiksowego - suy do // reprezentacji tablic lnk i test 13 struct VlP { 14 SufV *p; 15 char l; 16 VlP(SufV *p, char l) : p(p), l(l) {} 17 bool operator<(const VlP &a) const { 18 return (a.p > p) || (a.p == p && a.l > l); 19 } 20 }; // Korze drzewa 21 SufV root; // Tekst, dla ktrego zostao zbudowane drzewo sufiksowe 22 string text; 23 SufTree(const string& t) : root(0, 0, 0, 0), text(t) { 24 map<VlP, SufV*> lnk; 25 set<VlP> test; 26 int len = t.length(); // Stwrz pierwszy wze drzewa reprezentujcy ostatni liter sowa 27 SufV *v = root.sons[t[len - 1]] = new SufV(len - 1, len, &root, 1); 28 lnk[VlP(&root, t[len - 1])] = v; 29 test.insert(VlP(&root, t[len - 1])); // Dla kolejnych sufiksw sowa (od najkrtszych do najduszych)... 30 FORD(i, len - 2, 0) { 31 char a = t[i];

218

Listing 7.13: (c.d. listingu z poprzedniej strony) // Jeli z korzenia nie wychodzi krawd dla litery sowa na pozycji i... 32 if (!root.sons[a]) { // Nastpuje aktualizacja tablicy test dla wzw na ciece od wza // reprezentujcego poprzedni sufiks do korzenia 33 SufV* it = v; 34 while(it) { 35 test.insert(VlP(it, a)); 36 it = it->par; 37 } 38 it = v; // Dodanie nowego syna dla korzenia 39 lnk[VlP(it, a)] = v = root.sons[t[i]] = new SufV(i, len, &root, 1); 40 } else { // Wykorzystujc tablice test oraz lnk nastpuje wyznaczenie krawdzi drzewa, // ktr trzeba podzieli w celu dodania kolejnego sufiksu 41 char lw; 42 SufV *head, *head2, *x, *x1; 43 int lw2 = 0, gl = 0; 44 for(x = v; x != &root && test.find(VlP(x, a)) == test.end(); 45 x = x->par) lw2 += x->k - x->p; 46 for(x1 = x; x1 && !lnk[VlP(x1, a)]; x1 = x1->par) { 47 gl += x1->k - x1->p; 48 lw = t[x1->p]; 49 } 50 if (x1) gl--; 51 SufV* head1 = x1 ? lnk[VlP(x1, a)] : &root; 52 if (x == x1) head = head1; else { 53 head2 = (!x1) ? root.sons[a] : head1->sons[lw]; 54 head = head1->sons[t[head2->p]] = 55 new SufV(head2->p, head2->p + 1 + gl, head1, 0); 56 head->sons[t[head->k]] = head2; 57 head2->p = head->k; 58 head2->par = head; 59 for(VAR(it, test.lower bound(VlP(head2, 0))); it->p == head2;) 60 test.insert(VlP(head, (it++)->l)); 61 } // Aktualizacja wartoci tablic test oraz lnk 62 for(SufV* it = v; it != x1; it = it->par) test.insert(VlP(it, a)); 63 lnk[VlP(x, a)] = head; 64 SufV *v1 = v; 65 v = head->sons[t[len - lw2]] = new SufV(len - lw2, len, head, 1); 66 lnk[VlP(v1, a)] = v; 67 } 68 } 69 } 70 };

219

W kliku kolejnych rozdziaach zostanie przedstawiony sposb wykorzystania drzew suksowych. Przy ich uyciu zostan rozwizane problemy zliczania liczby wystpie wzorca w a a a tekcie, wyznaczanie liczby rnych podsw czy szukanie najduszego sowa wystpujcego a w tekcie n razy. Wszystkie te problemy daje si rozwiza w czasie liniowym. a

7.5.1. Liczba wystpie wzorca w tekcie a


Jednym z moliwych zastosowa drzew suksowych jest wyznaczanie liczby wystpie wzorca a w tekcie. Zamy, e chcemy wyznaczy liczb wystpie wzorca p w tekcie t. Jeli do tego a celu wykorzystamy nieskompresowane drzewo suksowe, to wystarczy znale wierzchoek v drzewa suksowego, reprezentujcy wzorzec p, a nastpnie wyznaczy liczb wierzchokw a osigalnych z v, ktre reprezentuj suksy tekstu t. Przykadowo, rozpatrujc drzewo suka a a sowe dla tekstu BAABAC przedstawione na rysunku 7.3.a oraz wzorzec A, poszukiwanym wierzchokiem jest syn korzenia drzewa z etykiet A. Z wierzchoka tego osigalne s trzy a a a wierzchoki reprezentujce suksy, zatem liczba wystpie litery A w tekcie jest rwna 3. a a Przenoszc si teraz do skompresowanej postaci drzewa suksowego, mona zastosowa a ten sam algorytm. Rni si tylko sposb wyznaczania wierzchoka v. W tym przypadku wyznaczajc pozycj w drzewie suksowym reprezentujc wzorzec p, naley bra pod uwag a a a napisy umieszczone na krawdziach, a nie w wierzchokach. Pozostaje jeszcze jeden problem aby byo mona wyznaczy liczb wystpie wzorca p a w czasie O(|p|), musi istnie moliwo szybkiego wyliczania liczby osigalnych wierzchokw a reprezentujcych suksy. Mona tego dokona spamitujc wczeniej (przed przystpieniem a a a do przetwarzania wzorcw) liczb tych wierzchokw dla kadego wza drzewa suksowego. Na listingu 7.14 przedstawiony jest kod realizujcy opisany pomys. Przed przystpieniem a a do zliczania liczby wystpie wzorca, naley wywoa funkcj int STSuffixC(SufTree<int> a ::SufV& v), przekazujc jej jako parametr korze drzewa suksowego. Po zakoczeniu tej a fazy, wyznaczanie liczby wystpie wzorca mona wykonywa przy uyciu funkcji int STFinda Count (SufTree<int>&, const char*), ktrej parametrami s drzewo suksowe dla teka stu, oraz wyszukiwany wzorzec.
Listing 7.14: Implementacja funkcji int STFindCount(SufTree<int>&, const char*) // Funkcja wylicza dla kadego wza drzewa sufiksowego liczb osigalnych // wierzchokw, ktre reprezentuj sufiks sowa 01 int STSuffixC(SufTree<int>::SufV & v) { 02 v.e = v.s; 03 FOREACH(it, v.sons) v.e += STSuffixC(*(it->second)); 04 return v.e; 05 } // Funkcja wyznacza liczb wystpie wzorca t w tekcie, dla ktrego zostao // skonstruowane drzewo sufiksowe st 06 int STFindCount(SufTree<int> &st, const char *t) { 07 int tp = 0, x; 08 VAR(v, &(st.root)); // Rozpoczynajc od korzenia drzewa przesuwaj si po nim zgodnie z wczytywanymi // literami wzorca 09 while (t[tp]) { 10 if (!(v = v->sons[t[tp]])) return 0; 11 for (x = v->p; x < v->k && t[tp];)

220

Listing 7.14: (c.d. listingu z poprzedniej strony) 12 if (t[tp++] != st.text[x++]) return 0; 13 } // Jeli wzorzec wystpuje w tekcie, to liczba jego wystpie jest rwna // liczbie sufiksw osigalnych z aktualnego wza drzewa 14 return v->e; 15 }

Listing 7.15: Przykad wykorzystania funkcji int STFindCount(SufTree<int>&, const char*)

Przeszukiwany tekst: abcabcaaabcad Wzorczec: abc wystepuje 3 razy Wzorczec: a wystepuje 6 razy Wzorczec: bcda wystepuje 0 razy

Listing 7.16: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.15. Peny kod rdowy programu znajduje si w pliku stfind.cpp 01 int main() { 02 string text; // Wczytaj tekst, w ktrym bd wyszukiwane wzorce 03 cin >> text; 04 cout << "Przeszukiwany tekst: " << text << endl; // Skonstruuj drzewo sufiksowe 05 SufTree<int> tree(text.c str()); // Wyznacz liczb osigalnych wierzchokw-sufiksw dla kadego wierzchoka drzewa sufiksowego 06 STSuffixC(tree.root); // Dla poszczeglnych wzorcw wyznacz liczb ich wystpie w tekcie 07 while(cin >> text) cout << "Wzorczec: " << text << " wystepuje " << 08 STFindCount(tree, text.c str()) << " razy" << endl; 09 return 0; 10 }

7.5.2. Liczba rnych podsw sowa


Przy uyciu drzew suksowych istnieje moliwo wyznaczaLiteratura nia liczby rnych podsw wystpujcych w sowie t. W tym celu a [ASD] - 5.3.9 przypatrzmy si przykadowemu nieskompresowanemu drzewu suksowemu z rysunku 7.3.a. Kady wierzchoek v w drzewie suksowym mona utosami z tekstem uzyskanym przez konkatenacj etykiet wierzchokw na ciece od korzenia do v. Oczywistym jest, e kademu wierzchokowi zosta w ten sposb przyporzdkowany inny tekst. Co wicej, kade podsowo t ma przyporzdkowany jaki a a wierzchoek. Zatem liczba rnych podsw w t jest rwna liczbie wierzchokw w nieskompresowanym drzewie suksowym t. Przechodzc teraz do drzewa skompresowanego, wystarczy a w tym przypadku wyznaczy sumaryczn dugo wszystkich krawdzi w drzewie. a Algorytm realizujcy t metod zosta zaimplementowany jako funkcja int STSubWords a 221

(SufTree<int>::SufV&), przedstawiona na listingu 7.17. Przyjmuje ona jako parametr drzewo suksowe reprezentujce tekst, dla ktrego wyznaczana jest liczba rnych podsw. a
Listing 7.17: Implementacja funkcji int STSubWords(SufTree<int>::SufV& v) // Funkcja wyznacza liczb rnych podsw w tekcie poprzez zliczanie dugoci // krawdzi w drzewie sufiksowym 1 template <typename T> int STSubWords(SufTree<T>::SufV & v) { 2 int r = v.k - v.p; 3 FOREACH(it, v.sons) r += STSubWords<T> (*(it->second)); 4 return r; 5}

Listing 7.18: Przykad uycie funkcji int STSubWords(SufTree<int>::SufV& v)

test ma 9 podslow aaaaa ma 5 podslow abcabcaaabcad ma 69 podslow

Listing 7.19: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.18. Peny kod rdowy programu znajduje si w pliku subwords.cpp 1 int main() { 2 string text; // Wczytaj kolejne teksty 3 while (cin >> text) { // Zbuduj drzewo sufiksowe 4 SufTree<int> tree(text); // Wyznacz liczb rnych podsw 5 cout << text << " ma " << STSubWords < 6 int >(tree.root) << " podslow" << endl; 7 } 8 return 0; 9}

7.5.3. Najdusze podsowo wystpujce n razy a


Kolejnym zastosowaniem drzew suksowych, jest wyznaczanie najduszego podsowa, ktre wystpuje w sowie co najmniej n razy. Analizujc struktur drzew suksowych, szybko mona a doj do wniosku, e rozwizanie tego problemu sprowadza si do wyznaczenia w drzewie a najgbszego wierzchoka (najbardziej oddalonego od korzenia), z ktrego da si doj do co najmniej n wierzchokw reprezentujcych suksy tekstu. a Implementacja tego algorytmu zostaa zrealizowana jako struktura STLongestWord przedstawiona na listingu 7.20. Do konstruktora tej struktury naley przekaza tekst t oraz liczb n. Wynik wyznaczone najdusze podsowo, mona odczyta z pl int p oraz int k. Reprezentuj one odpowiednio pocztek i koniec podsowa tekstu t. a a

222

Listing 7.20: Implementacja struktury struct STLongestWord // Struktura wyznacza najdusze podsowo wystpujce odpowiedni liczb razy 01 struct STLongestWord { // Zmienne p oraz k reprezentuj odpowiednio pocztek oraz koniec // wyznaczonego sowa 02 int p, k, c; 03 int Find(SufTree<int>::SufV & v, int d) { // d jest gbokoci wza v 04 d += v.k - v.p; 05 v.e = v.s; // Zlicz liczb sufiksw osigalnych z wza v 06 FOREACH(it, v.sons) v.e += Find(*(it->ND), d); // Jeli liczba sufiksw jest nie mniejsza od c, oraz gboko aktualnego // wza jest wiksza od dugoci aktualnie znalezionego sowa, to zaktualizuj // wynik 07 if (v.e >= c && d > k - p) { k = v.k; 08 p = v.k - d; 09 } 10 return v.e; 11 } 12 STLongestWord(const string & t, int c) : p(0), k(0), c(c) { // Skonstruuj drzewo sufiksowe oraz wyznacz wynik 13 SufTree<int> tr(t); 14 Find(tr.root, 0); 15 } 16 };

Listing 7.21: Przykad uycia struktury STLongestWord

Podslowo slowa abcabc wystepujace 2 razy: abc Podslowo slowa aaaaaaaa wystepujace 3 razy: aaaaaa Podslowo slowa baabaabaab wystepujace 3 razy: baab

Listing 7.22: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.21. Peny kod rdowy programu znajduje si w pliku multisubword.cpp 01 int main() { 02 string text; 03 int n; // Wczytaj dane 04 while(cin >> text >> n) { // Wyznacz podsowo sowa text wystpujce n razy 05 STLongestWord str(text, n); 06 cout << "Podslowo slowa " << text << " wystepujace " << 07 n << " razy: " << endl << 08 text.substr(str.p, str.k-str.p) << endl;

223

Listing 7.22: (c.d. listingu z poprzedniej strony) 09 } 10 return 0; 11 }

Proste acm.uva.es - zadanie 10234

wiczenia

rednie spoj.sphere.pl - zadanie 220

Trudne

7.6. Maksymalny leksykogracznie suks


Drzewa suksowe maj znacznie wicej zastosowa ni te, ktre a Literatura przedstawilimy w poprzednich rozdziaach. Przy ich uyciu mona [EAT] - 3.1 wyznacza maksymalny leksykogracznie suks sowa. Przyka[ASD] - 5.3.7 dowo, maksymalnym leksykogracznie suksem dla sowa ababbaabbaba jest bbaba. Wyznaczenie takiego suksu wymaga przejcia po drzewie suksowym od korzenia do licia, wybierajc za kadym razem krawd reprea zentujc leksykogracznie najwikszy tekst. Podejcie takie jest do skomplikowane ze a a wzgldu na potrzeb konstruowania drzewa suksowego. Istnieje jednak znacznie prostsza metoda tzw. algorytm Duvala, dziaajcy podobnie do rozwizania wykorzystujcego a a a drzewa suksowe, w czasie liniowym. Jego implementacja zostaa zrealizowana jako funkcja int maxSuf(const char*) przedstawiona na listingu 7.23. Parametrem tej funkcji jest tekst, dla ktrego wyznaczony ma zosta maksymalny leksykogracznie suks, natomiast jako wynik zwracana jest pozycja w tekcie, od ktrej zaczyna si wyznaczony maksymalny leksykogracznie suks.
Listing 7.23: Implementacja funkcji int MaxSuf(const char*) // Funkcja wyznacza maksymalny leksykograficznie sufiks sowa 01 int maxSuf(const char *x) { 02 int i = 0, j = 1, k = 1, p = 1; 03 char a, b; 04 while (x[j + k - 1]) { 05 if ((a = x[i + k - 1]) < (b = x[j + k - 1])) { 06 i = j++; 07 k = p = 1; 08 } else if (a > b) { 09 j += k; 10 k = 1; 11 p = j - i; 12 } else if (a == b && k != p) k++; 13 else { 14 j += p; 15 k = 1; 16 } 17 }

224

Listing 7.23: (c.d. listingu z poprzedniej strony) 18 return i; 19 }

Listing 7.24: Przykad wykorzystania funkcji int MaxSuf(const char*)

Maksymalny suks ababbaabbaba = bbaba Maksymalny suks abaabbbabba = bbbabba Maksymalny suks algorytm = ytm

Listing 7.25: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.24. Peny kod rdowy programu znajduje si w pliku maxsuf.cpp 1 int main() { 2 string text; // Wczytaj kolejne teksty i wyznacz dla nich maksymalne leksykograficzne sufiksy 3 while (cin >> text) cout << "Maksymalny sufiks " << text << " = " << text. 4 substr(maxSuf(text.c str())) << endl; 5 return 0; 6}

7.7. Rwnowano cykliczna


Dwa sowa s = s1 s2 . . . sn i t = t1 t2 . . . tl s rwnowane cya Literatura klicznie, jeeli mona przenie pewien suks sowa s na pocztek, a [EAT] - 15.4 w taki sposb, aby s = t. Innymi sowy, istnieje liczba 1 k n, [ASD] - 5.3.5 taka e sk sk+1 . . . sn s1 s2 . . . sk1 = t. Dla danych dwch sw s i t o dugoci n, mona postawi pytanie, czy s one rwnowane cya klicznie. Majc w pamici drzewa suksowe, sformuowanie rozwizania tego problemu przy a a ich uyciu jest proste. Dla sowa s mona skonstruowa drzewo suksowe, a nastpnie wyznaczy najduszy suks s bdcy jednoczenie preksem t (zamy, e suks ten ma dugo a l). Jeli s1 s2 . . . snl = tl+1 tl+2 . . . tn , to s i t s swoimi rwnowanociami cykliczbymi. a Rozwizanie takie jest proste koncepcyjnie, ale trudne do zrealizowania ze wzgldu ima plementacyjnego. Podobnie jak w przypadku maksymalnego leksykogracznie suksu, w tym przypadku rwnie istnieje znacznie prostszy algorytm. Zosta on zaimplementowany jako funkcja bool CycEq(const char*, const char*), ktrej kod rdowy znajduje si na listingu 7.26. Funkcja ta przyjmuje jako parametry dwa teksty, a zwraca warto logiczn a rwn prawdzie wtedy, gdy podane teksty s swoimi rwnowanociami cyklicznymi. a a
Listing 7.26: Implementacja funkcji bool cycEq(const char*, const char*) // Funkcja sprawdza, czy dwa dane teksty s swoimi rwnowanociami cyklicznymi 01 bool cycEq(const char *s1, const char *s2) { 02 int n = strlen(s1), i = 0, j = 0, k = 0; 03 if (n != strlen(s2)) return 0; 04 while (i < n && j < n && k < n) { 05 k = 1; 06 while (k <= n && s1[(i + k) % n] == s2[(j + k) % n]) k++;

225

Listing 7.26: (c.d. listingu z poprzedniej strony) 07 if (k <= n) if (s1[(i + k) % n] > s2[(j + k) % n]) i += k; 08 else j += k; 09 } 10 return k > n; 11 }

Listing 7.27: Przykad wykorzystania funkcji bool cycEq(const char*, const char*)

Slowa babaa i ababa sa rownowazne cyklicznie Slowa abbab i ababa nie sa rownowazne cyklicznie Slowa abbab i babaa nie sa rownowazne cyklicznie

Listing 7.28: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.27. Peny kod rdowy programu znajduje si w pliku cyceq.cpp 01 int main() { 02 int n; // Wczytaj liczb tekstw oraz poszczeglne teksty 03 cin >> n; 04 vector<string> text(n); 05 REP(x,n) cin >> text[x]; // Dla kadej pary tekstw wyznacz, czy s one rwnowane cyklicznie 06 REP(x, n) REP(y, x) 07 cout << "Slowa " << text[x] << " i " << text[y] << 08 (cycEq(text[x].c str(), text[y].c str()) ? "" : " nie") << 09 " sa rownowazne cyklicznie" << endl; 10 return 0; 11 }

7.8. Minimalna leksykograczna cykliczno sowa


Problem z poprzedniego rozdziau mona rozwiza w inny sposb. Pomys polega na wya znaczeniu dla wszystkich tekstw, bdcych swoimi rwnowanociami cyklicznymi, wsplnej a reprezentacji. W ten sposb dwa teksty s rwnowane cyklicznie, o ile ich reprezentacje s a a identyczne. Jednym z nasuwajcych si pomysw, po przeczytaniu rozdziau dotyczcego wya a znaczania maksymalnego leksykogracznie suksu, jest wyznaczenie dla sowa maksymalnej leksykogracznej cyklicznoci... W tym rozdziale przedstawimy algorytm pozwalajcy na podejcie do problemu od innej a a strony prezentowana funkcja int minLexCyc(const char*) bdzie wyznaczaa minimaln leksykograczn cykliczno danego sowa. Przyjmowanym przez funkcj parametrem jest a sowo, natomiast wynikiem jest numer pierwszej litery, rozpoczynajcej minimaln leksya a kogracznie cykliczno sowa. Implementacja algorytmu zostaa przedstawiona na listingu 7.29. Jest on modykacj algorytmu Dovala do wyznaczania maksymalnego leksykogracza nego suksu sowa. Zauwamy, e jeeli dla danego sowa w poszukiwalibymy maksymalnego leksykogracznego suksu sowa ww, to musiaby si on zaczyna w pierwszej czci sowa i 226

mie dugo co najmniej |w +1|. Wybierajc pierwszych w liter wyznaczonego suksu, otrzya malibymy maksymaln leksykograczn cykliczno sowa w, zatem odwracajc porzdek na a a a a zbiorze liter alfabetu oraz postpujc w dokadnie taki sam sposb, uzyskalibymy minimaln a a leksykograczn rwnowano w. a
Listing 7.29: Implementacja funkcji int minLexCyc(const char*) // Funkcja wyznacza minimaln leksykograficzn rwnowano cykliczn sowa 01 int minLexCyc(const char *x) { 02 int i = 0, j = 1, k = 1, p = 1, a, b, l = strlen(x); 03 while (j + k <= (l << 1)) { 04 if ((a = x[(i + k - 1) % l]) > (b = x[(j + k - 1) % l])) { 05 i = j++; 06 k = p = 1; 07 } else if (a < b) { 08 j += k; 09 k = 1; 10 p = j - i; 11 } else if (a == b && k != p) k++; 12 else { 13 j += p; 14 k = 1; 15 } 16 } 17 return i; 18 }

Listing 7.30: Przykad wykorzystania funkcji int minLexCyc(const char*)

Minimalna rownowaznosc cykliczna aabaaabaabaca = aaabaaabaabac Minimalna rownowaznosc cykliczna baaabaabacaaa = aaabaaabaabac Minimalna rownowaznosc cykliczna algorytm = algorytm

Listing 7.31: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 7.30. Peny kod rdowy programu znajduje si w pliku minlexcyc.cpp 01 int main() { 02 string text; // Wczytaj kolejne teksty i wyznacz dla nich minimaln leksykograficzn // rwnowano cykliczn 03 while (cin >> text) { 04 int res = minLexCyc(text.c str()); 05 cout << "Minimalna rownowaznosc cykliczna " << text << 06 " = " << text.substr(res, text.length() - res) << 07 text.substr(0, res) << endl; 08 } 09 return 0; 10 }

227

Zadanie: Punkty
Pochodzenie: XII Olimpiada Informatyczna Rozwizanie: a points.cpp

Dany jest zbir punktw na paszczynie o wsprzdnych cakowitych, ktry bdziemy nazywa wzorem oraz zestaw innych zbiorw punktw na paszczynie (rwnie o wsprzdnych cakowitych). Interesuje nas, ktre z podanych zestaww s podobne do wzoru, tzn. czy mona a je tak przeksztaci za pomoc obrotw, przesuni, symetrii osiowej i jednokadnoci, aby a byy identyczne ze wzorem. Przykadowo: zbir punktw (0, 0), (2, 0), (2, 1) jest podobny do zbioru (6, 1), (6, 5), (4, 5), ale nie do zbioru (4, 0), (6, 0), (5, 1).

Zadanie
Napisz program, ktry: wczyta ze standardowego wejcia opisy: wzoru oraz zestawu badanych zbiorw punktw, wyznaczy, ktre z badanych zbiorw punktw s podobne do wzoru, a wypisze wynik na standardowe wyjcie.

Wejcie
W pierwszym wierszu standardowego wejcia zapisana jest jedna liczba cakowita k (1 k 25 000) liczba punktw tworzcych wzr. W kolejnych k wierszach zapisane s pary liczb a a cakowitych pooddzielanych pojedynczymi odstpami. W i + 1-ym wierszu s wsprzdne a i-tego punktu nalecego do wzoru: xi i yi (20 000 a xi , y i 20 000). Punkty tworzce a wzr s (parami) rne. W kolejnym wierszu zapisana jest liczba zbiorw do zbadania n a (1 n 20). Dalej nastpuje n opisw zbiorw punktw. Opis kadego zbioru rozpoczyna si od wiersza zawierajcego jedn liczb cakowit l liczb punktw w danym zbiorze a a a (1 l 25 000). Punkty te s opisane w kolejnych wierszach, po jednym w wierszu. Opis a punktu to dwie liczby cakowite oddzielone pojedynczym odstpem jego wsprzdne x i y (20 000 x, y 20 000). Punkty tworzce jeden zbir s parami rne. a a

Wyjcie
Twj program powinien wypisa na standardowe wyjcie n wierszy po jednym dla kadego badanego zbioru punktw. Wiersz nr i powinien zawiera sowo T AK, gdy i-ty z podanych zbiorw punktw jest podobny do podanego wzoru, lub sowo N IE w przeciwnym przypadku.

228

Przykad
3 0 2 2 2 3 4 6 4 3 4 6 5 0 0 1

Dla nastpujcego wejcia: a

1 5 5 0 0 1

Poprawnym rozwizaniem jest: a


TAK NIE

229

Rozdzia 8

Algebra liniowa
Przez Zp , gdzie p jest liczb pierwsz, oznacza bdziemy ciao a a Literatura liczb {0, 1, . . . , p 1} z operacjami realizowanymi modulo p. Przy[NRP] kadowo, w ciele Z3 zachodz nastpujce rwnoci: 2 + 1 = 0, a a 2 2 = 1, natomiast w ciele Z7 mamy 6 + 3 = 2, 5 4 = 6. W rozdziale tym zajmiemy si zagadnieniem rozwizywania ukadw rwna liniowych w a ciele Zp . Problem ten polega na wyznaczeniu wartoci zmiennych X = {x0 , x1 , .., xn1 } , xk {0, 1 . . . , p 1} takich, aby wszystkie zadane rwnania liniowe zmiennych ze zbioru X byy spenione. W kolejnych rozdziaach zostanie przedstawiony algorytm pozwalajcy na rozwizya a wanie takich ukadw rwna. Ciekawa sytuacja zachodzi w przypadku ciaa Z2 , w ktrym zmienne przyjmuj warto 0 oraz 1. Istnieje wiele zada, ktre mona rozwiza, przypisujc a a a wystpujcym w tych zadaniach warunkom, typu prawda - fasz, zmienne z ciaa Z2 , a nastpa nie rozwizujc odpowiedni ukad rwna. Ze wzgldu na du uyteczno rozwizywania a a a a ukadw rwna w ciele Z2 , przedstawimy implementacj algorytmu, pozwalajcego na efeka tywne rozwizywanie tego typu ukadw rwna. a Drugim problemem, jaki zostanie poruszony w tym rozdziale, jest zagadnienie programowania liniowego. Oglnie polega ono na wyznaczeniu wartoci zmiennych x0 , x1 , .., xn1 R+ takich, aby zmaksymalizowa warto funkcji liniowej f (x0 , x1 , .., xn1 ) oraz aby byy spenione zadane ograniczenia g1 , g2 , . . . , gk , bdce funkcjami liniowymi zmiennych x0 , x1 , .., a xn1 . Przy uyciu programowania liniowego istnieje moliwo rozwizywania takich problea mw, jak wyznaczanie najciszego/najlszejszego maksymalnego skojarzenia w grae [PCC]. Algorytm rozwizujcy ten problem dla grafw dwudzielnych przy uyciu innych metod zosta a a przedstawiony w rozdziale powiconym teorii grafw.

8.1. Eliminacja Gaussa


Eliminacja Gaussa jest jedn z najczciej stosowanych metod a rozwizywania ukadw rwna liniowych. Zamy, e mamy dane a m rwna z n niewiadomymi postaci:
x0 a0,0 + x1 a0,1 + . . . + xn1 a0,n1 = b0 x a + x a + ... + x 0 1,0 1 1,1 n1 a1,n1 = b1 ...

Literatura [NRP] - 2.2

x0 am1,0 + x1 am1,1 + . . . + xn1 am1,n1 = bm1

Taki ukad rwna mona zapisa w postaci macierzowej A x = b, gdzie:

231

A=

a0,0 a1,0 . . .

a0,1 a1,1 . . .

... ... .. .

a0,n1 a1,n1 . . .

am1,0 am1,1 . . . am1,n1

x=

x0 x1 . . . xn1

b=

b0 b1 . . . bm1

Metoda Gaussa wyznacza wartoci zmiennych x0 , x1 , .., xn1 , dla ktrych wszystkie zadane rwnania liniowe s spenione, bd stwierdza, e podany ukad rwna jest sprzeczny. a a Znalezienie rozwizania ukadu rwna metod Gaussa polega na takim przeksztaceniu a a rwnania postaci: A x = b do postaci rwnowanej C x = d, e macierz C jest macierz a trjktn grn: a a a
C=

c0,0 c0,1 . . . c0,n1 0 c1,1 . . . c1,n1 . . . .. . . . . . . . 0 0 . . . cm1,n1

Z takiej postaci ukadu rwna (o ile jest niesprzeczny), w atwy sposb, mona wyznaczy wartoci kolejnych niewiadomych: xn1 , xn2 , . . . , x0 . Z ostatniego rwnania mona d wyznaczy xn1 = cm1,n1 . Nastpnie wyznaczon warto mona podstawi do kolejnego a rwnania, ktre bdzie zawierao tylko jedn niewiadom xn2 ... a a W przypadku rozwizywania ukadw rwna w ciele liczb rzeczywistych, pojawia si a dodatkowy problem dokadnoci wykonywanych oblicze (wykonywane zaokrglenia mog a a si kumulowa, wpywajc znaczco na warto obliczonego rozwizania). W zadaniach kona a a kursowych, ukady rwna, ktre wymagaj rozwizania, maj specyczn konstrukcj a a a a wszystkie wartoci niewiadomych oraz wspczynnikw s zazwyczaj liczbami cakowitymi z a przedziau {0, 1, . . . , p 1} dla p bdcego liczb pierwsz, a operacje arytmetyczne wykonuje a a a si modulo p. Pozwala to zapomnie o problemie zaokrgle i skupi si tylko na operacjach a cakowitoliczbowych. W kolejnych dwch rozdziaach zostan przedstawione implementacje a eliminacji Gaussa odpowiednio w ciele Z2 oraz Zp .

8.1.1. Eliminacja Gaussa w Z2


Jako szczeglny przypadek rozwizywania ukadw rwna w a Literatura ciele Zp , ktry czsto przydaje si w zadaniach, jest rozwizywanie a [MD] - 3.6 ukadw rwna w Z2 . W ciele tym zmienne przyjmuj dwie moa liwe wartoci 0 lub 1. Implementacja algorytmu rozwizujcego a a tego typu ukad rwna zrealizowana jest przez funkcj int GaussZ2(vector<vector<unsigned int> >&, VI&, VI&), ktra zostaa umieszczona na listingu 8.1. Funkcja ta przyjmuje jako parametry tablic reprezentujc ukad rwna (macierz A zgodnie z oznaczeniami a a z poprzedniego rozdziau), wektor wartoci b oraz wektor, w ktrym ma zosta umieszczony wynik (wektor x), o ile ukad rwna jest niesprzeczny. Funkcja zwraca warto 1 w przypadku, gdy podany ukad rwna nie ma rozwizania, 0 - w przypadku, gdy ukad rwna jest a jednoznaczny, warto wiksz od 0 - w przypadku, gdy ukad rwna nie jest jednoznaczny a (zwrcona warto jest wymiarem przestrzeni rozwiza). a
Listing 8.1: Implementacja funkcji int GaussZ2(vector<vector<unsigned int> >&, VI&, VI&) // Funkcja rozwizuje ukad rwna w ciele Z2 01 template <const int s> int GaussZ2(vector < bitset<s> >equ, VI vals, 02 VI & res) {

232

Listing 8.1: (c.d. listingu z poprzedniej strony) 03 int n = SIZE(equ), a = 0, b = 0, c; // ustaw odpowiedni wielko wektora wynikowego 04 res.resize(s, 0); // Dla kolejnej niewiadomej xb... 05 for (; a < n && b < s; b++) { // Wyznacz pierwsze rwnanie spord rwna [a..n-1], w ktrym wspczynnik // przy zmiennej xb jest niezerowy 06 for (c = a; c < n && !equ[c][b]; c++); // Jeli znaleziono takie rwnanie... 07 if (c < n) { // Jeli w rwnaniu numer a wspczynnik przy xb jest rwny 0, to // dodaj (modulo 2) rwnanie numer c do a 08 if (a != c) { 09 equ[a] = equ[c]; 10 vals[a] = vals[c]; 11 } // Dla wszystkich rwna rnych od a, wyeliminuj z nich wspczynnik // przy xb 12 REP(y, n) if (a != y && equ[y][b]) { 13 equ[y] = equ[a]; 14 vals[y] = vals[a]; 15 } // Zapamitaj numer rwnania, w ktrym wspczynnik przy zmiennej xb jest // rny od 0 16 res[b] = ++a; 17 } 18 } // Dla kadej niewiadomej, jeli istnieje dla niej rwnanie z niezerowym // wspczynnikiem, to wyznacz jej warto z tego rwnania 19 REP(x, b) if (res[x]) res[x] = vals[res[x] - 1]; // Sprawd, czy wyznaczone rozwizanie spenia ukad rwna 20 REP(x, n) { 21 c = 0; 22 REP(z, s) if (equ[x][z]) c = res[z]; 23 if (c != vals[x]) return -1; 24 } // Zwr wymiar przestrzeni rozwiza 25 return s - a; 26 }

Dla nastpujcego ukadu rwna: a


x1 + x2 = 0 x 0 + 1 x 1 + 1 x 2 + 0 x 3 + 0 x 4 = 0

jego rozwizanie jest przedstawione na listingu 8.2. a

3 0 1 2 3 4 1 x +x +x =0x +1x +0x +1x +1x =0 1 3 4 0 1 2 3 4

x +x =0x +1x +0x +1x +0x =1

233

Listing 8.2: Przykad wykorzystania funkcji int GaussZ2(vector< vector <unsigned int> >, VI, VI&)

Wymiar przestrzeni rozwiazan: 2 x0 = 0 x1 = 1 x2 = 1 x3 = 0 x4 = 1

Listing 8.3: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 8.2. Peny kod rdowy programu znajduje si w pliku gaussz2.cpp 01 int main() { // Skonstruuj rozwizywany ukad rwna 02 vector < bitset < 5 > >equ(5); 03 VI vals(5), res; 04 equ[0][1] = equ[0][2] = 1; 05 vals[0] = 0; 06 equ[1][1] = equ[1][3] = 1; 07 vals[1] = 1; 08 equ[2][1] = equ[2][3] = equ[2][4] = 1; 09 vals[2] = 0; // Wyznacz jego rozwizanie 10 cout << "Wymiar przestrzeni rozwiazan: " << GaussZ2(equ, vals, res) << endl; 11 REP(z, SIZE(res)) cout << "x" << z << " = " << res[z] << endl; 12 return 0; 13 }

Zadanie: Taniec
Pochodzenie: Szwajcarska Olimpiada Informatyczna 2004 Rozwizanie: a dance.cpp

Ostatnio ogldae program telewizyjny, w ktrym piosenkarz taczy na szachownicy skaa dajcej si z kolorowych, podwietlanych od dou, pl. Kady jego krok na polu powodowa a przeczenie podwietlenia; dodatkowo wszystkie ssiadujce pola rwnie zmieniay stan a a a swojego podwietlenia. Twoim zadaniem jest sprawdzenie, czy istnieje moliwo zapalenia wszystkich wiate na szachownicy, wykonujc w tym celu odpowiedni taniec. a Na pocztku taca, niektre obszary s ju zapalone. Wolno taczy po wszystkich polach a a szachownicy. Kady krok na polu powoduje zamian stanu aktualnego pola oraz czterech pl (w przypadku pl lecych na krawdzi szachownicy odpowiednio mniej) ssiednich. a a

Zadanie

Napisz program, ktry: wczyta ze standardowego wejcia pocztkowy stan szachownicy, a stwierdzi, czy da si wykona podany taniec i jeli tak, to wypisze list krokw, ktre a naley wykona, 234

wypisze wynik na standardowe wyjcie

Wejcie
Pierwsza linia wejcia zawiera dwie liczby naturalne x i y (3 x, y 15), oznaczajce a odpowiednio szeroko i wysoko szachownicy. Kolejnych y wierszy zawiera po x znakw opisujcych stan kolejnych pl szachownicy. 0 oznacza, e wiato pod odpowiednim polem a jest zgaszone, podczas gdy 1 oznacza, e wiato jest zapalone.

Wyjcie
W pierwszym wierszu wyjcia powinna znale si jedna liczba cakowita n minimalna liczba krokw potrzebnych do wczenia wszystkich wiate na szachownicy. Kolejnych n a wierszy powinno zawiera po dwie liczby cakowite i oraz j. Kada para liczb wyznacza pojedynczy krok, jaki tancerz musi wykona nastpienie na pole w i-tym wierszu j-tej a kolumny. Jeli istnieje wiele rozwiza, Twj program powinien wypisa dowolne z nich. Jeli a rozwizanie nie istnieje, program powinien wypisa 1. a

Przykad
4 3 0111 1010 1000

8.1.2. Eliminacja Gaussa w Zp


Po przeanalizowaniu problemu rozwizywania ukadw rwa Literatura na w Z2 , zajmiemy si przypadkiem ciaa Zp , gdzie p jest liczb a [MD] - 3.6 pierwsz. a Sposb realizacji eliminacji Gaussa w tym przypadku jest podobny. Rnica polega na sposobie wykonywania operacji arytmetycznych. W przypadku ciaa Z2 wykonywanie operacji arytmetycznych jest proste. W oglnym przypadku natomiast konieczne jest wyznaczanie odwrotnoci liczb (modulo p) (bya o tym mowa w rozdziale w rozdziale powiconym teorii liczb). Dokadny opis realizacji tego algorytmu mona znale w literaturze. Przedstawiona na listingu 8.4 funkcja int Gauss(vector<VI>&, VI&, VI&, int) przyjmuje jako parametry odpowiednio tablic reprezentujc ukad rwna (A), wektor wartoci a a b, wektor x, oraz liczb pierwsz p. Funkcja zwraca 1 w przypadku, gdy podany ukad a rwna nie ma rozwiza, w przeciwnym przypadku zwraca wymiar przestrzeni rozwiza a a oraz przykadowy wynik umieszcza w wektorze x.
Listing 8.4: Implementacja funkcji int Gauss(vector<VI>&, VI&, VI&, int) // Funkcja rozwizuje dany ukad rwna w ciele Zp 01 int Gauss(vector<VI> &a, VI & b, VI & x, int P) { 02 int m = SIZE(a), n = SIZE(a[0]), k, r; 03 VI q; 04 for (k = 0; k < min(m, n); k++) { 05 int i, j;

Dla nastpujcego wejcia: a


3 2 1 3 1 3 4

Poprawnym rozwizaniem jest: a

235

Listing 8.4: (c.d. listingu z poprzedniej strony) 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 } for (j = k; j < n; j++) for (i = k; i < m; i++) if (a[i][j] != 0) goto found; break; found: if (j != k) REP(t, m) swap(a[t][j], a[t][k]); q.PB(j); if (i != k) { swap(a[i], a[k]); swap(b[i], b[k]); } FOR(j, k + 1, m - 1) if (a[j][k] != 0) { int l = (a[j][k] * RevMod(a[k][k], P)) % P; FOR(i, k + 1, n - 1) a[j][i] = (P + a[j][i] - (l * a[k][i]) % P) % P; b[j] = (P + b[j] - (l * b[k]) % P) % P; } } r = k; x.clear(); x.resize(n, 0); FOR(k, r, m - 1) if (b[k] != 0) return -1; FORD(k, r - 1, 0) { int s = b[k]; FOR(j, k + 1, r - 1) s = (P + s - (a[k][j] * x[j]) % P) % P; x[k] = (s * RevMod(a[k][k], P)) % P; } FORD(k, r - 1, 0) swap(x[k], x[q[k]]); return n - r;

Dla nastpujcego ukadu rwna: a


1 x0 + 3 x1 + 7 x2 = 0
0 1 2 x + 6 x + 8 x2 = 2 0 1

4 x + 0 x + 14 x2 = 1

jego rozwizanie w ciele Z19 jest przedstawione na listingu 8.5. a


Listing 8.5: Przykad wykorzystania funkcji int Gauss(vector<VI>&, VI&, VI&, int)

Wymiar przestrzeni rozwizan: 0 s x0 = 3 x1 = 4 x2 = 6

236

Listing 8.6: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 8.5. Peny kod rdowy programu znajduje si w pliku gausszp.cpp 01 int main() { // Skonstruuj rozwizywany ukad rwna 02 vector<VI> a(3, VI(3)); 03 VI b(3), x; 04 a[0][0] = 1, a[0][1] = 3, a[0][2] = 7; 05 b[0] = 0; 06 a[1][0] = 4, a[1][1] = 0, a[1][2] = 14; 07 b[1] = 1; 08 a[2][0] = 2, a[2][1] = 6, a[2][2] = 8; 09 b[2] = 2; // Oraz rozwi go 10 cout << "Wymiar przestrzeni rozwizan: " << Gauss(a, b, x, 19) << endl; 11 REP(i, SIZE(x)) cout << "x" << i << " = " << x[i] << endl; 12 return 0; 13 }

Zadanie: Sejf
Pochodzenie: Potyczki Algorytmiczne 2006 Rozwizanie: a safe.cpp

Bajtosmok posiada sejf, do zawartoci ktrego dostpu broni zamek skadajcy si z n szyfraa torw. Kady szyfrator jest po prostu pokrtem, ktre mona ustawi w p rnych pozycjach. Sejf otwiera si, gdy wszystkie szyfratory zostaj ustawione w odpowiedniej pozycji. a Bajtosmok dawno nie uywa sejfu, przez co zapomnia konguracji, ktra go otwiera. Uda si do zakadu produkujcego szyfratory, jednak jedyne czego si dowiedzia, to schea mat konstrukcji zamka. Zamek skada si z n szyfratorw i tylu samo blokad wszystkie one, zarwno szyfratory jak i blokady, mog by ustawione w jednej z p pozycji numerowanych a od 0 do p 1. Zamek otwiera si w momencie, gdy wszystkie blokady ustawione s w pozycji a 0. Przekrcenie i-tego szyfratora o jedn pozycj (z pozycji 0 na 1, z pozycji 1 na 2, ..., z a p 2 na p 1, z p 1 na 0) powoduje, e j-ta blokada przekrca si o ci,j pozycji (z pozycji l na pozycj (l + ci,j ) (mod p)). W celu umoliwienia rozszyfrowania kombinacji otwierajcej a sejf, Bajtosmok otrzyma rwnie nowoczesny skaner trjwymiarowy (model Wzrok Supersmoka), ktry umoliwia mu sprawdzenie w jakiej konguracji znajduj si ukryte wewntrz a a mechanizmu sejfu blokady. Jako e sejfy, ktrych uywa Bajtosmok s zawsze pierwszej klasy, mona zaoy, e a zawsze istnieje dokadnie jedna kombinacja otwierajca sejf. a

Zadanie
Napisz program, ktry: wczyta ze standardowego wejcia opis konstrukcji zamku w seje, wyznaczy ustawienie szyfratorw, przy ktrym zamek jest otwarty, wypisze wynik na standardowe wyjcie 237

Wejcie
Pierwszy wiersz zawiera dwie liczby cakowite n liczb szyfratorw, 1 n 300 oraz liczb pierwsz p liczb pozycji, w ktrych moe znajdowa si jeden szyfrator, 3 p 40 000 a (liczba pierwsza to taka, ktra ma dokadnie dwa dzielniki: 1 i sam siebie). Nastpny wiersz a zawiera n liczb cakowitych z zakresu od 0 do p1 pozycje, w ktrych s ustawione kolejne a szyfratory. Kolejny wiersz zawiera rwnie n liczb cakowitych od 0 do p 1 pozycje, w ktrych ustawione s blokady w zamku Bajtosmoka. Nastpnych n wierszy zawiera opisy a poszczeglnych szyfratorw i-ty z tych wierszy zawiera dokadnie n liczb cakowitych kolejne liczby ci,0 , ci,1 , ..., ci,n1 , 0 ci,j < p. W dowolnym wierszu liczby pooddzielane s a pojedynczymi odstpami.

Wyjcie
W pierwszym i jedynym wierszu wyjcia twj program powinien wypisa n liczb cakowitych z przedziau 0 do p1 oddzielonych pojedynczymi odstpami pozycje kolejnych szyfratorw, dla ktrych zamek jest otwarty.

Przykad
2 1 2 1 0 3 1 2 0 1

Dla nastpujcego wejcia: a Poprawnym rozwizaniem jest: a


2 2

Proste acm.uva.es - zadanie 10309 acm.sgu.ru - zadanie 260

rednie acm.uva.es - zadanie 10524 acm.uva.es - zadanie 10109

wiczenia

Trudne acm.uva.es - zadanie 10808

8.2. Programowanie liniowe


Zagadnienie programowania liniowego polega na wyznaczeniu nieujemnych liczb rzeczywistych x0 , x1 , .., xn1 maksymalizujcych a bd minimalizujcych warto danej funkcji liniowej postaci a a f (x0 , x1 , .., xn1 ) = b0 x0 + b1 x1 + ... + bn1 xn1 Literatura [NRP] - 2.2 [PCC] [WDA] - 25.5

przy jednoczesnym zachowaniu wszystkich warunkw ograniczajcych, bdcych postaci niea a rwnoci liniowych:
x0 a0,0 + x1 a0,1 + . . . + xn1 a0,n1 x a + x a + ... + x 0 1,0 1 1,1 n1 a1,n1 ...

c0 c1 cm1

x0 am1,0 + x1 am1,1 + . . . + xn1 am1,n1 238

W tym rozdziale skupimy si na problemie maksymalizacji wartoci funkcji f problem minimalizacji mona w atwy sposb rozwiza poprzez zamian znakw przy wspczynnia kach w funkcji f : f (x0 , x1 , .., xn1 ) = b0 x0 b1 x1 ... bn1 xn1 W podobny sposb, mona uzyska warunki ograniczajce postaci: a a0 x0 + a1 x1 + ... + an1 xn1 poprzez zamian znakw wspczynnikw: a0 x0 a1 x1 ... an1 xn1 c c

Podobnie jak w przypadku eliminacji Gaussa, problem mona reprezentowa w postaci macierzowej. Dla danych macierzy A oraz wektora b i c, naley wyznaczy wektor x liczb rzeczywistych dodatnich, speniajcy nierwno A x a c oraz maksymalizujcy warto a b x. Istnieje wiele rnych sposobw wyznaczania wartoci wektora x, ktre zale nie tylko a od postaci warunkw ograniczajcych, ale rwnie od ich liczby czy ewentualnych zalea noci pomidzy nimi wystpujcymi. Wicej informacji na ten temat mona uzyska w lia teraturze. My natomiast skupimy si nad implementacj funkcji vector<long double> a simplex(vector<vector<long double> >&, vector<long double>&, vector<long double>&), ktrej kod rdowy znajduje si na listingu 8.7. Funkcja przyjmuje jako parametry macierz A, wektor c, oraz wektor wspczynnikw b dla maksymalizowanej funkcji. Jako wynik dziaania, funkcja ta zwraca wektor x, dla ktrego warto funkcji f jest zmaksymalizowana, lub pusty wektor w przypadku, gdy podana lista warunkw jest sprzeczna (moe si tak rwnie zdarzy, gdy warto maksymalizowanej funkcji nie jest ograniczona). Omawiana funkcja powinna by wystarczajca w przypadku rozwizywania kadego zadania a a wymagajcego wykorzystania programowania liniowego. Po dokadn analiz rozwizywania a a a problemw programowania liniowego naley odwoywa si do literatury.
Listing 8.7: Implementacja funkcji vector<long double> simplex(vector<vector<long double> >&, vector<long double>&, vector<long double>&) 01 namespace Simplex { // Typ wykorzystywany do wykonywania oblicze - domylnie jest to long double 02 typedef long double T; 03 typedef vector<T> VT; 04 vector<VT> A; 05 VT b, c, res; 06 VI kt, N; 07 int m; 08 inline void pivot(int k, int l, int e) { 09 int x = kt[l]; 10 T p = A[l][e]; 11 REP(i, k) A[l][i] /= p; 12 b[l] /= p; 13 N[e] = 0; 14 REP(i, m) if (i != l) 15 b[i] -= A[i][e] * b[l], A[i][x] = A[i][e] * -A[l][x]; 16 REP(j, k) if (N[j]) { c[j] -= c[e] * A[l][j];

239

Listing 8.7: (c.d. listingu z poprzedniej strony) 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 REP(i, m) if (i != l) A[i][j] -= A[i][e] * A[l][j]; } kt[l] = e; N[x] = 1; c[x] = c[e] * -A[l][x]; } VT doit(int k) { VT res; T best; while (1) { int e = -1, l = -1; REP(i, k) if (N[i] && c[i] > EPS) { e = i; break; } if (e == -1) break; REP(i, m) if (A[i][e] > EPS && (l == -1 || best > b[i] / A[i][e])) best = b[l = i] / A[i][e]; if (l == -1) return VT(); pivot(k, l, e); } res.resize(k, 0); REP(i, m) res[kt[i]] = b[i]; return res; } VT simplex(vector<VT> &AA, VT & bb, VT & cc) { int n = AA[0].size(), k; m = AA.size(); k = n + m + 1; kt.resize(m); b = bb; c = cc; c.resize(n + m); A = AA; REP(i, m) { A[i].resize(k); A[i][n + i] = 1; A[i][k - 1] = -1; kt[i] = n + i; } N = VI(k, 1); REP(i, m) N[kt[i]] = 0; int pos = min element(ALL(b)) - b.begin(); if (b[pos] < -EPS) { c = VT(k, 0); c[k - 1] = -1; pivot(k, pos, k - 1); res = doit(k);

240

Listing 8.7: (c.d. listingu z poprzedniej strony) 65 66 67 68 69 70 71 72 73 74 75 76 77 78 } 79 }; if (res[k - 1] > EPS) return VT(); REP(i, m) if (kt[i] == k - 1) REP(j, k - 1) if (N[j] && (A[i][j] < -EPS || EPS < A[i][j])) { pivot(k, i, j); break; } c = cc; c.resize(k, 0); REP(i, m) REP(j, k) if (N[j]) c[j] -= c[kt[i]] * A[i][j]; } res = doit(k - 1); if (!res.empty()) res.resize(n); return res;

Dla nastpujcych ogranicze: a 0.5 x0 1 x1 + 2 x2 x0 + 2 x1 100 2

oraz funkcji celu postaci 5 x0 1.5 x1 + 0.1 x2 wyznaczony wynik jest przedstawiony na listingu 8.8.
Listing 8.8: BRAK

Najlepsze rozwiazanie : x0 = 100 Wartosc funkcji = 502.4

x1 = 0

x2 = 24

Listing 8.9: Kod rdowy programu uytego do wyznaczenia wyniku z listingu 8.8. Peny kod rdowy programu znajduje si w pliku linearprog.cpp 01 typedef long double LD; 02 int main() { // Skonstruuj odpowiednie macierze oraz wektory 03 vector < vector<LD> >A(2, vector<LD> (3)); 04 vector<LD> b(2), c(3), res; 05 A[0][0] = -0.5, A[0][1] = -1, A[0][2] = 2, b[0] = -2; 06 A[1][0] = 1, A[1][1] = 2, A[1][2] = 0, b[1] = 100; 07 c[0] = 5, c[1] = -1.5, c[2] = 0.1; // Rozwi programowanie liniowe oraz wypisz wynik 08 res = Simplex::simplex(A, b, c); 09 cout << "Najlepsze rozwiazanie : "; 10 REP(i, SIZE(res)) cout << "x" << i << " = " << res[i] << "\t"; 11 cout << endl; 12 LD acc = 0; 13 REP(i, SIZE(res)) acc += res[i] * c[i]; 14 cout << "Wartosc funkcji = " << acc << endl;

241

Listing 8.9: (c.d. listingu z poprzedniej strony) 15 return 0; 16 }

Zadanie: Szalony malarz


Pochodzenie: Problem Set Archive with Online Judge (http://acm.zju.edu.cn/) Rozwizanie: a paint.cpp Szalony malarz Henry Daub planuje namalowa nowe arcydzieo. Jak wszystkie jego obrazy, nowe malowido bdzie prostoktem o wymiarach m n cali, kady cal kwadratowy a bdzie pomalowany na pewien okrelony kolor. Henry chce zminimalizowa czas potrzebny na namalowanie obrazu. Stworzy ju szkic i teraz planuje, w jaki sposb malowa obraz. Istniej trzy podstawowe techniki malowania, ktre Henry wykorzystuje w swoich obraa zach. W kadym kroku, moe on namalowa poziom lini, pionow lini lub pojedyncze pole a a o rozmiarze cala kwadratowego. Malowanie poziomej linii koloruje pewn liczb poziomo a ssiadujcych ze sob pl na ten sam kolor, podobnie malowanie pionowej linii koloruje a a a pewn liczb pionowo - ssiadujcych ze sob pl na ten sam kolor. Malowanie pojedynczego a a a a pola koloruje tylko to pojedyncze pole. Malowanie poziomej linii zajmuje h sekund, a pionowa linia moe by namalowana w czasie v sekund. Pojedyncze pole moe zosta pomalowane w s sekund. Namalowany obraz musi dokadnie odpowiada sporzdzonemu wczeniej szkicowi. a Co wicej, nie jest dozwolone zmienianie koloru pojedynczych prostoktw mog one by a a malowane wielokrotnie, ale za kadym razem musi to by farba tego samego koloru. Pom Henremu wyznaczy czas potrzebny na namalowanie obrazu. Na pocztku, cae ptno jest a puste.

Zadanie
Napisz program, ktry: wczyta ze standardowego wejcia rozmiar obrazu oraz opis jego szkicu, wyznaczy czas oraz list ruchw, jakie naley wykona, aby pomalowa obraz, wypisze wynik na standardowe wyjcie.

Wejcie
Pierwszy wiersz wejcia zawiera liczby m, n, h, v oraz s (1 m, n 30, 1 h, v, s 105). Nastpnych m wierszy zawiera po n liter oznaczajcych kolory poszczeglnych pl obrazu a kolory reprezentowane s przez mae litery alfabetu angielskiego. a

Wyjcie
W pierwszym wierszu, program powinien wypisa dwie liczby cakowite: t czas potrzebny na pomalowanie obrazu; k liczb ruchw, jakie naley wykona. Kolejnych k wierszy musi zawiera opis ruchw. Pierwszym elementem opisu ruchu jest litera, oznaczajca rodzaj wya konywanego ruchu h (ruch poziomy), v (ruch pionowy) lub s (pojedynczy punkt). W przypadku pierwszych dwch ruchw, nastpuj cztery liczby wsprzdne grnego - lea wego i dolnego - prawego koca malowanej kreski. W ostatnim przypadku dwie liczby cakowite bdce wsprzdnymi malowanego pola. Ostatnim elementem opisu jest kolor uyty a do pomalowania pl.

242

Przykad
Dla nastpujcego wejcia: a
4 4 3 5 2 aabc aabc bbbb ccbc

Poprawnym rozwizaniem jest: a


23 8 s 1 4 s 2 4 s 4 4 h 1 1 h 2 1 h 3 1 h 4 1 v 1 3 c c c 1 2 3 4 4

2 2 4 2 3

a a b c b

Proste acm.uva.es - zadanie 10817

rednie acm.uva.es - zadanie 10498

wiczenia

Trudne acm.sgu.ru - zadanie 248

243

Rozdzia 9

Elementy strategii podczas zawodw


W kadym rzemiole niezwykle wan rol odgrywa praktyka. Nawet najwysze teoretyczne a wyksztacenie jej nie zastpi. Oczywicie, bez wiedzy, dowiadczenie rwnie nie ma swojej a wartoci. Oba te czynniki musz ze sob wspgra w odpowiednich proporcjach. Podobna a a sytuacja odnosi si do udziau w konkursach programistycznych. Bez znajomoci algorytmiki nie ma si szans na poprawne i efektywne rozwizywanie zada. Jednak znajomo optya malnego rozwizania nie poparta treningiem, nie zapewnia waciwego zaimplementowania a zadania podczas konkursu. Uczestniczenie w wielu treningach przynosi wiedz praktyczn, a o ktrej nie mona przeczyta w adnej ksice. a W aktualnym rozdziale poruszone zostay problemy, zwizane ze strategi brania udziau a a w konkursach. Wiedza ta nie pochodzi z teoretycznych rozwaa, dotyczcych sposobw przea prowadzania zawodw, lecz stanowi konstruktywne wnioski wycignite z udziau w wielu a konkursach wnioski, ktre nie tylko pomog w efektywnym rozwizywaniu zada, ale poa a zwol rwnie na uniknicie wielu problemw, z ktrych istnienia mona sobie wczeniej nie a zdawa sprawy, a ktre w istotny sposb wpywaj na szybko osigania celu. W dziale doa a datkw znajduje si niezwykle cenny materia zbir dobrych rad pochodzcych od wysokiej a klasy zawodnikw. Stanowi on uzupenienie zawartoci niniejszego rozdziau.

9.1. Szacowanie oczekiwanej zoonoci czasowej


Rozwizywanie kadego zadania skada si z trzech faz: wymylenia algorytmu, zaimplemena towania rozwizania oraz przetestowania go, poczonego zazwyczaj z dokonaniem odpowieda a nich poprawek. Czym mniej czasu spdza si sumarycznie podczas tych trzech faz, tym lepsze osiga si efekty. Czsto pocztkujcy zawodnicy, zdajc sobie spraw z tej zalenoci, zabiea a a a raj si za rozwizywanie zada w sposb zachanny chc jak najszybciej zakoczy kad a a a a z tych trzech faz. Takie podejcie doprowadza do sytuacji, w ktrych podczas implementacji programu okazuje si, e naley uwzgldni jakie dodatkowe przypadki skrajne, albo co gorsza, e zastosowany algorytm nie jest poprawny. Konsekwencj takich sytuacji jest istotne a wyduenie czasu spdzonego w drugiej i trzeciej fazie. Pomijajc aspekty poprawnoci samego rozwizania, w wielu przypadkach okazuje si, a a e zadanie mona zaimplementowa na kilka sposobw rnice czsto le w szczega ach. Niekiedy odpowiednie zainicjalizowanie zmiennych moe pozwoli, w kolejnych fazach algorytmu, na pominicie sprawdzania wielu skomplikowanych warunkw. Umiejtno dostrzegania moliwych uproszcze wymaga duo praktyki, ale nawet najbardziej dowiadczony 245

zawodnik potrzebuje czasu na ich wymylenie. Powicenie kilku dodatkowych minut w fazie projektowania algorytmu, starajc si dokadnie przemyle sposb implementacji oraz a moliwe jej uproszczenia, zawsze zwrc si w dwjnasb podczas dwch kolejnych faz. a Istotny wpyw na zoono implementowanego programu maj stosowane struktury daa nych. Rozwizujcy zadanie musi zdecydowa, czy zastosowa efektywniejsz i bardziej skoma a a plikowan struktur danych, czy te prostsz i wolniejsz. W zalenoci od charakteru kona a a kursu, w ktrym bierze si udzia, w wielu przypadkach organizatorzy nie wymagaj od a zawodnikw implementacji asymptotycznie optymalnych rozwiza. W takich przypadkach, a umiejtno szacowania oczekiwanej zoonoci jest bardzo istotna. Decyzja polegajca na a wybraniu bardziej skomplikowanej struktury danych czy algorytmu, gwarantuje zmieszczenie si w limitach czasowych, ale zwiksza czas implementacji oraz ryzyko pomyek. Wprawdzie nieefektywne podejcie pozwala na uproszczenie implementacji, ale z drugiej strony naraa na przekroczenie limitw czasowych. Pniejsze gruntowne modykacje zaimplementowanego rozwizania bywaj bardzo trudne, zatem naley wystrzega si sytuacji, ktre mog prowaa a a dzi do koniecznoci zmiany podejcia do rozwizywanego problemu. a Szacowanie oczekiwanej zoonoci czasowej rozwizania jest moliwe dziki analizie wiela koci limitw na dane wejciowe. Jeli nie ma si w tym zakresie wprawy, najlepszym sposobem jest przeanalizowanie zada konkursowych z lat ubiegych wraz z sugerowanymi rozwizaniami. a Na tej podstawie mona oszacowa, w jaki sposb bd dobierane limity w zalenoci od zoa onoci rozwiza dla zada w przyszoci. Jedyny czynnik czasowy, jaki ma wpyw na zmian a tych limitw, jest cigle zwikszajca si szybko komputerw. Dziki temu, organizatorzy a a konkursu s w stanie nieznacznie zwiksza limity na maksymaln wielko danych, przy tych a a samych oczekiwanych asymptotycznych zoonociach rozwiza. a Wyznaczenie szacunkowej zoonoci rozwizania pozwala nie tylko na dobr odpowieda nich struktur danych czy algorytmw, ale rwnie daje pewn informacj na temat metody a rozwizywania samego zadania. W ten sposb, bez czytania treci zadania, mona bez proa blemu stwierdzi, e zadanie naley do klasy problemw NP - trudnych, jego rozwizanie a mona oprze o programowanie dynamiczne lub, e wymagany jest algorytm liniowy. Zazwyczaj rozrnia si cztery podstawowe klasy zoonoci algorytmw: Programy o zoonoci O(n) O(n log(n)). Limity na dane wejciowe (oczywicie zmienia si to z czasem, o czym trzeba pamita) s rzdu 100 000 1 000 000. Istotny a jest rwnie fakt, i jury nie jest zazwyczaj w stanie rozrni rozwiza o zoonoci a O(n) od O(n log(n)), zatem majc wybr pomidzy takimi dwoma algorytmami, bez a obawy mona przyj wolniejsze rozwizanie (o ile oczywicie jest prostsze w implea a mentacji). Czynnik logarytmiczny w zoonoci pojawia si zazwyczaj ze wzgldu na konieczno posortowania danych wejciowych. Programy o zoonoci O(n2 ), ewentualnie z dodatkowymi czynnikami logarytmicznymi, maj limity nie przekraczajce 30 000 40 000. W tej klasie programw mieszcz si a a a rne rozwizania zachanne oraz proste zadania na struktury danych, ktre czsto daje a si rwnie rozwiza w zoonoci O(nlog(n)), przy uyciu skomplikowanych struktur a danych. Programy o zoonoci O(n3 ) (z ewentualnymi czynnikami logarytmicznymi). W tej klasie rozwiza znajduj si zazwyczaj algorytmy dynamiczne; zatem wielko danych a a wejciowych zaley nie tylko od zoonoci czasowej, ale rwnie i pamiciowej, ktra czsto jest rzdu O(n2 ). Przy aktualnych limitach pamiciowych 32-64 MB, ograniczenia na dane wejciowe nie przekraczaj 5 000. Naley zauway, i rozwizania a a 246

szecienne wielu zada z tej klasy, maj ma sta multiplikatywn, co pozwala na a a a a dobr stosunkowo duych limitw na dane wejciowe. Rozwizania wykadnicze, ktrych zoono czasowa moe mie we wzorze: silnie, funka cje wykadnicze i dwumiany (przykadowo O(n!), O(2n ), O( n )). W przypadku tego k typu zada, podejcie polega zazwyczaj na generowaniu rnych rozwiza do postaa wionego problemu oraz sprawdzaniu, ktre z nich jest optymalne. W tego rodzaju zadaniach mona, przy uyciu heurystyk, ogranicza przeszukiwan przestrze rozwiza, a a co w istotny sposb przyspiesza dziaanie programu. Ograniczenia przyjte w tego typu zadaniach nie przekraczaj wartoci 50, a zazwyczaj oscyluj w zakresie 10 25. a a

9.2. Strategia pracy w druynie


W przypadku brania udziau w zawodach druynowych, takich jak ACM ICPC, bardzo wane jest opracowanie strategii, zgodnie z ktr musz postpowa wszyscy czonkowie druyny. a a Potrzeba dobrej organizacji nie wynika jedynie z koniecznoci wsppracy midzy czonkami zespou, ale przede wszystkim z tego, e do dyspozycji trzech zawodnikw jest tylko jeden komputer i jego efektywne wykorzystanie jest nieodzowne. Dobra druyna powinna przeprowadza razem liczne treningi, majce na celu symulowanie zawodw. Umiejtno a rozwizywania zada mona wiczy indywidualnie i nie s do tego konieczne treningi zea a spoowe. Po kadym treningu powinno nastpi jego omwienie. Omwienie to jest tak samo a istotne jak sam trening, gdy ma ono na celu wycignicie wnioskw na przyszo i usprawa nienie wsppracy w druynie. Wanymi elementami, na jakie naley zwraca uwag podczas omwie, s: a Czy ktry z zawodnikw straci duo czasu z okrelonego powodu, co w przyszoci mona wyeliminowa albo przynajmniej ograniczy. Krytycznym elementem, z ktrym zawsze wi si problemy, jest oczekiwanie na komputer. Jednak i w tym zakresie a a istnieje moliwo wprowadzenia pewnych usprawnie, polegajcych na choby zapisya waniu programw najpierw na kartce, a dopiero potem na komputerze. Odstpstwem od tej reguy s pierwsze oraz ostatnie zadania rozwizywane podczas zawodw. Innym a a czynnikiem, ktry moe powodowa niepotrzebn strat czasu podczas prawdziwych a zawodw, jest oczekiwanie na dostarczenie przez organizatorw wydruku programu (poszukiwanie bdw w kodzie odbywa si bowiem na kartce). Dobrym pomysem jest drukowanie kadego programu podczas wysyania go do oceny (jeszcze przed uzyskaniem odpowiedzi). Jeli okae si, e rozwizanie jest nieprawidowe, to wydruk programu zoa stanie dostarczony o kilka cennych minut wczeniej i bdzie mona szybciej przystpi a do poszukiwania bdw. Czy poprawianie bdw w ktrym z zada zajo bardzo duo czasu. Sytuacje takie mog by spowodowane czterema czynnikami. Pierwszym z nich jest losowy bd w a a implementacji. Bdw takich trudno jest si wystrzec, nie ma te standardowego mechanizmu pozwalajcego na ich wychwytywanie. W tym zakresie, tylko czste treningi a s w stanie pomc. a Drugim, bardzo niebezpiecznym problemem, jest zastosowanie nieprawidowego algorytmu. Sytuacja taka moe pojawi si po zym zrozumieniu treci zadania. Zdarza si, e pomimo przekonania o poprawnoci rozwizania (co wymusza poszukiwanie bdu a w samym programie), idea algorytmu jest nieprawidowa, a wwczas ca prac naley a rozpocz od pocztku. Co wicej, sytuacje takie wprowadzaj napicie w zespole i a a a dekoncentruj ca druyn. a a 247

Trzecim problemem, ktry mona omin, jest implementowanie programu przez niea odpowiedni osob. Niektrzy zawodnicy preferuj zadania z geometrii obliczeniowej, a a inni z teorii grafw. Ogln zasad podczas zawodw jest, aby zawodnicy nie kontaka a towali si i nie wymieniali si zadaniami, o ile nie jest to konieczne. W przypadku, gdy zawodnik zdaje sobie jednak spraw, e jego kolega z druyny jest znacznie lepszy w rozwizywaniu zada okrelonego typu, naley zdecydowa si na zamian zadaniami. a Ostatnim powodem mog by czsto powtarzajce si bdy. Pojawiaj si one zazwya a a czaj podczas zawodw, kiedy pracuje si pod presj. Przykadowo, moe zdarzy si, a e zgosi si do oceny program do innego zadania. Istnieje te wiele problemw natury implementacyjnej. Jednym z nich jest wykorzystywanie zmiennych globalnych oraz lokalnych o tej samej nazwie. Wydawa by si mogo, e wykrycie takiego bdu jest proste (debugujc program na komputerze, bardzo szybko mona to wykry), lecz poda czas zawodw, gdy programy poprawia si na kartce, jest to jedna z najtrudniejszych do wychwycenia usterek. Zazwyczaj szuka si bdw we wzorach i skomplikowanych cigach instrukcji, a nie zwraca si uwagi na to, e dwie zmienne w programie maj tak a a a sam nazw. Bardzo dobrym pomysem jest sporzdzenie, na podstawie dowiadcze z a a treningw, listy powtarzajcych si bdw i za kadym razem odwoywanie si do niej a podczas debugowania programu. Krytycznym momentem jest ostatnia godzina zawodw. Jeli w druynie kady zawodnik przez cay czas pracuje nad swoimi zadaniami, to moe okaza si pod koniec konkursu, e druyna ma kilka prawie rozwizanych zada, ale nie jest moliwe zaima plementowanie w krtkim czasie adnego z nich. Dobrym pomysem jest, okoo godziny przed zakoczeniem konkursu, przerwanie na chwil rozwizywania zada i przedyskua towanie, jak wyglda sytuacja. Naley podj decyzj, nad ktrymi zadaniami bdzie a a si dalej pracowa i kto je powinien implementowa. Nawet jeli wydaje si, e istnieje szansa rozwizania wszystkich rozpocztych zada, to naley dokadnie przemyle tak a a decyzj, gdy z praktyki wynika, e tego typu szare zazwyczaj kocz si porak. a a Wan strategiczn decyzj jest rwnie sposb korzystania z rankingu. Przez pierwsze a a a godziny zawodw jest on dostpny publicznie, zatem mona z niego czerpa bardzo poyteczne informacje, takie jak ocena trudnoci zada. Na podstawie rankingu mona zadecydowa, w jakiej kolejnoci naley rozwizywa zadania. Bardzo wana jest anaa liza, ktre zadania nie zostay jeszcze rozwizane (moe si to okaza o tyle istotne, a i istniej zadania, ktre na pozr wydaj si proste, ale kryj w sobie rnego roa a a dzaju puapki). W przypadku podjcia prby rozwizywania takiego zadania, naley by a szczeglnie ostronym. Obserwowanie rankingu naley robi mdrze; jego zbyt czste a wykorzystywanie moe doprowadzi do sytuacji, w ktrej zamiast rozwizywa zadania, a analizuje si pozycje poszczeglnych druyn. Wane jest rwnie to, e w sytuacjach ekstremalnych, analizowanie rankingu wcale nie mobilizuje druyny, lecz j niepotrzeba nie stresuje i zmniejsza efektywno pracy. Jednym z moliwych strategicznych podej, jest wyznaczenie jednego zawodnika o najsilniejszych nerwach, odpowiedzialnego za sprawdzanie stanu rankingu i zasugerowanie druynie, jakie zadania naley rozwizywa a w nastpnej kolejnoci.

Kady zesp powinien opracowa swoj wasn strategi, dopasowan do charakteru poa a a szczeglnych jego czonkw. W przypadku wielu czoowych druyn, strategie s stosunkowo a podobne. Rni si wprawdzie drobnymi szczegami, ale zasadniczo przedstawiaj si naa a stpujco: a 248

Na samym pocztku zawodw, zawodnicy rozdzielaj midzy sob zadania. Sposb poa a a dziau zada powinien zosta ustalony przed zawodami. Istnieje moliwo podziau zada w zalenoci od ich typu i preferencji zawodnikw, ale podejcie takie wymaga wstpnej analizy treci zada oraz wprowadza niepotrzebn dyskusj midzy czonkami druyny. W praktyce a stosowane s inne metody. a Jedna z metod polega na rozdzieleniu zada zgodnie z zasad modulo 3. Pierwszy a zawodnik w druynie otrzymuje zadania, ktrych numery dziel si przez 3 3, 6 . . ., drugi a zawodnik dostaje zadania 1, 4 . . ., a trzeci 2, 5 . . . Podejcie takie jest o tyle wygodne, e gwarantuje rwnomierny rozkad liczby zada oraz nie wymaga dodatkowego zastanawiania si (choby sprawdzania, ile jest w sumie wszystkich zada). Niestety, metoda ta ma te powan wad. Organizatorzy zawodw rzadko umieszczaj dwa trudne zadania obok siebie a a (to samo tyczy si zada atwych). Przy podziale zada zgodnym z zasad modulo 3, a bardzo czsto zdarza si, e poziom trudnoci zada, ktre dostali poszczeglni zawodnicy, jest rny. Podczas wielu treningw przekonalimy si, e stosujc t metod bardzo, czsto a jeden z czonkw druyny dostawa najtrudniejsze zadania, co powodowao, e podczas gdy dwaj koledzy rozwizali ju po jednym zadaniu i zabierali si za kolejne, to pechowiec a cay czas zastanawia si nad rozwizaniem swojego pierwszego zadania. Sytuacje takie nie a tylko negatywnie wpywaj na atmosfer w druynie, ale rwnie pogarszaj pozycj na a a rankingu. Inn metod podziau zada, jest przydzielenie pierwszych kilku zada pierwszemu a a zawodnikowi, zada rodkowych drugiemu oraz reszty zada trzeciemu. Bardzo wanym momentem w zawodach jest ich pocztek. Jeli druyna ma wygra zaa wody na czasie, to musi mie dobry start i w cigu pierwszej godziny konkursu rozwiza 3 a a - 4 zadania. Aby byo to moliwe, rozwizywanie zada naley rozpocz od najprostszych. a a Kady z zawodnikw powinien zapozna si pobienie ze wszystkimi swoimi zadaniami oraz zdecydowa, ktre z nich jest najatwiejsze. Nie naley zaczyna ktrego zadania (nie czytajc reszty) tylko dlatego, e wie si od razu jak je rozwiza. Czsto, jak na zo, okazuje a a si, e zadanie, ktre odoyo si na koniec, jest najprostsze. Najwaniejsz zasad poda a czas zawodw bowiem jest, e naley rozwizywa zadania w kolejnoci od najprostszych, do a najtrudniejszych. Na samym pocztku konkursu trzeba rwnie rozwiza problem wykorzystywanych naa a gwkw, pliku Makefile oraz innych uywanych skryptw. Wczeniej czy pniej bdzie je trzeba przepisa na komputer, a poniewa na samym pocztku zawodw i tak nikt nie ima plementuje adnego zadania (najpierw trzeba zapozna si z ich treci), wic najlepiej jest a wybra jednego czonka druyny odpowiedzialnego za przygotowanie odpowiedniego rodowiska pracy, podczas gdy reszta druyny analizuje zadania. Ze wzgldu na konieczno efektywnego wykorzystania komputera, wane jest pisanie programw na kartce. Takie podejcie pozwala na skrcenie czasu korzystania z komputera. Rozpoczynajc pisanie programu na kartce trzeba mie pewno co do poprawnoci stosoa wanego algorytmu. Nie mona sobie rwnie pozwoli na zastanawianie si przy komputerze. Przepisanie programu z kartki zajmuje znacznie mniej czasu, oraz pozwala na dodatkow a werykacj jego poprawnoci. Odstpstwem od tej reguy jest sam pocztek zawodw. W kadym zestawie zada znaja duje si proste zadanie, ktre mona zaimplementowa bez zastanowienia. W takich sytuacjach, naley pomin implementacj na kartce. a Kilka kolejnych godzin zawodw mija w podobny sposb zawodnicy na zmian pisz a kolejne zadania na komputerze, drukuj programy, szukaj i poprawiaj bdy. a a a W kocu dochodzi do jednego z dwch moliwych scenariuszy. Jeden z nich opisany zosta ju wczeniej (kady z zawodnikw kontynuuje rozwizywanie a swoich zada, lecz czas, ktry pozosta do koca zawodw nie pozwoli na rozwizanie wszysta 249

kich rozpocztych). Drugi scenariusz polega na tym, e jest jeszcze kilka zada, ale nie wiadomo, jak je rozwiza. W takich sytuacjach przydaje si dowiadczenie z treningw. W zalenoci od a typu zada, naley odpowiednio powymienia je midzy sob, lub podj decyzj o odrzuceniu a a okrelonych zada i skoncentrowaniu si w grupie nad jednym najprostszym. W sytuacjach, w ktrych nie ma si adnych pomysw na rozwizanie, pomocne moe okaza si przejcie po a korytarzu i oderwanie si na chwil od konkursu. Po powrocie do stanowiska, wiee spojrzenie na problem moe przynie dobry rezultat.

9.3. Szablon
Jak ju wspomnielimy na pocztku ksiki, implementowane podczas zawodw programy a a korzystaj ze zbioru nagwkw. S one wsplne dla wszystkich programw, zatem ich przea a pisanie na komputer jest wymagane tylko przed rozpoczciem implementowania pierwszego zadania. Przystpujc do rozwizywania kolejnego zadania, wystarczy skopiowa poprzedni a a program i po usuniciu niepotrzebnych fragmentw kodu, wykorzysta go jako nowy szablon. Rozwizanie takie jednak nie jest najbezpieczniejsze. Zdarzao si niekiedy, e osoba a rozpoczynajca implementowanie kolejnego programu, mylia literk rozwizywanego przez a a siebie zadania, nadpisujc tym samym rozwizanie innego, zaimplementowanego ju zadania. a a Nie stanowi to wielkiego problemu, jeli nadpisane zadanie zostao ju zaakceptowane. Ale jeli kolega z druyny wanie szuka bdu w tym programie na kartce? Oznaczao by to konieczno przepisania caego programu od nowa. Ominicie tego typu problemw jest moliwe poprzez zastosowanie troch innej metodologii. Przygotowany na samym pocztku zawodw szablon mona skopiowa wielokrotnie, a tworzc w ten sposb pliki o identycznej zawartoci: a.cpp, b.cpp, . . . Pliki te stanowi a a szablony, na podstawie ktrych powstaj rozwizania odpowiednich zada. Poniewa kady a a a a program pisany podczas zawodw w jzyku C++ musi zawiera funkcj main, zwracajc warto 0, mona j rwnie umieci w szablonie. Skopiowanie szablonw dla poszczeglnych a zada mona w szybki i prosty sposb wykona pod platform Linux przy uyciu komendy: a for i in a b c ..; do cp szkielet.cpp $i.cpp done gdzie kolejne litery a, b, c, . . . reprezentuj symbole poszczeglnych zada. a

9.4. Makele
Jak pokae zawarto kolejnego rozdziau, dobrym pomysem jest kompilowanie implementowanych programw z zestawem pewnych parametrw. Aby unikn koniecznoci wpisywania a ich za kadym razem, mona stworzy plik Makefile, ktrego uywa bd wszyscy czona kowie zespou. Rozwizanie takie pozwala nie tylko zaoszczdzi czas, ale rwnie wprowadza porzdek a a w rodowisku pracy. Przykad pliku Makefile zosta przedstawiony na listingu 9.1.
Listing 9.1: Przykadowy plik Makefile %: %.cpp g++ -o $@ $< -g -W -Wall -Wshadow

250

Kompilacja zadania z wykorzystaniem pliku Makefile jest prosta, wystarczy wykona polecenie make zadanie, gdzie zadanie reprezentuje nazw kompilowanego programu.

9.5. Parametry kompilacji programw


Bardzo poytecznym pomocnikiem podczas zawodw okazuje si kompilator. Trzeba tylko umie z niego odpowiednio korzysta. Kompilator jzyka C++ dostpny podczas praktycznie kadych zawodw GCC, udostpnia wiele parametrw kompilacji, pozwalajcych na a automatyczne wykrywanie niebezpiecznych instrukcji w programie, ktre mog prowadzi do a powstawania bdw. Jednym z najczciej stosowanych przecznikw jest -Wall. Jego nazwa a sugeruje, e wcza on wszystkie udostpniane przez kompilator ostrzeenia, jednak nie jest a to prawd. Istnieje wiele przecznikw, ktre su do werykowania zgodnoci programu z a a a rnymi standardami, jak np. -Wabi generujcy ostrzeenia o moliwej niezgodnoci kodu z a C++ ABI (Application Binary Interface). W kolejnych kilku podrozdziaach przedstawione zostay rne najbardziej poyteczne z punktu widzenia zawodw opcje kompilatora.

9.5.1. -Wec++
Parametr -Wec++ wcza ostrzeenia o wystpieniach w kodzie niezgodnociach ze stylem a a pisania programw okrelonym przez Scotta Meyersa w ksice Eective C++ [ECP]. W a skad tych bdw wchodz gwnie nieprawidowe typy zwracane przez funkcje i operatory. a Najciekawszym, z punktu widzenia konkursw, jest sprawdzanie, czy struktury zawierajce zmienne wskanikowe, posiadaj zaimplementowany konstruktor kopiujcy. Brak taa a a kiego konstruktora powoduje stworzenie konstruktora domylnego, ktry kopiuje warto wskanikw wystpujcych w strukturze, nie alokujc na ich potrzeby nowej pamici. W a a ten sposb, obiekty wspdziel cz pamici, co w wielu przypadkach jest bdem implea mentacyjnym. Przykad niepoprawnego programu zosta przedstawiony na listingu 9.2.
Listing 9.2: Nieprawidowa implementacja struktury ze zmienn wskanikow , ktra nie posiada a a konstruktora kopiuj cego. a 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 #include <iostream> using namespace std; struct BigNum { int *vec; BigNum() : vec(new int[1]) { } BigNum() { cout << "Zwalnianie pamici pod adresem " << vec << endl; delete[]vec; } }; int main() { BigNum a; BigNum b = a; BigNum c(a); return 0; }

Program przedstawiony na listingu 9.2 nie jest prawidowy, gdy podczas wykonywania opera251

cji z linii 13 oraz 14, tworzone s nowa zmienne b i c, zawierajce wskaniki vec, wskazujce a a a na ten sam obszar pamici, co wskanik vec zmiennej a. Podczas wywoywania destruktora obiektw, pami wskazywana przez wskaniki vec jest zwalniana wielokrotnie. Wynik wykonania tego programu zosta przedstawiony na listingu 9.3. Kompilacja programu bez dodatkowych parametrw nie wygeneruje adnych ostrzee i zaistniay w programie problem moe zosta niezauwaony. Wykorzystanie opcji -Wec++ powoduje, e proces kompilacji, wywietli ostrzeenia przedstawione na listingu 9.4.
Listing 9.3: Wynik dziaania programu z listingu 9.2 Zwalnianie pamici pod adresem 0x3d3ba8 Zwalnianie pamici pod adresem 0x3d3ba8 Zwalnianie pamici pod adresem 0x3d3ba8

Listing 9.4: Ostrzeenia wygenerowane podczas kompilacji z w czon opcj -Wec++ programu a a a z listingu 9.2 progs/ec0.cpp:3: warning: struct BigNum has pointer data members progs/ec0.cpp:3: warning: but does not override BigNum(const BigNum&) progs/ec0.cpp:3: warning: or operator=(const BigNum&)

Ostrzeenia wygenerowane przez kompilator podczas kompilacji programu z listingu 9.2, pozwalaj na natychmiastowe poprawienie bdw. Nowa wersja programu zostaa przedstaa wiona na listingu 9.5. Dla tego programu, podczas kompilacji nie generowane s ju adne a ostrzeenia, a wynik dziaania zosta zaprezentowany na listingu 9.6.
Listing 9.5: Poprawiona wersja programu z listingu 9.2 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> using namespace std; struct BigNum { int *vec; BigNum() : vec(new int[1]) { } BigNum(const BigNum & a) : vec(new int[1]) { } BigNum & operator=(const BigNum & a) { vec = new int[1]; vec[0] = a.vec[0]; return *this; } BigNum() { cout << "Zwalnianie pamici pod adresem " << vec << endl; } }; int main() { BigNum a; BigNum b = a; BigNum c(a); return 0; }

252

Listing 9.6: Wynik dziaania programu z listingu 9.5 Zwalnianie pamici pod adresem 0xc8b8c Zwalnianie pamici pod adresem 0xc8b7c Zwalnianie pamici pod adresem 0xc8b6c

9.5.2. -Wformat
Operator -Wformat wcza werykacj parametrw przekazywanych do funkcji, takich jak a printf czy scanf. Na podstawie pierwszego argumentu tych funkcji, sprawdzane s nie tylko a prawidowe typy innych parametrw, ale rwnie werykowana jest poprawno wykonywanych konwersji. Uycie opcji -Wformat automatycznie wcza rwnie ag -Wnonnull. a Na listingu 9.7 przedstawiony jest program, ktrego kompilacja bez opcji -Wformat, nie generuje adnych ostrzee. Jednak program ten jest daleki od ideau, ze wzgldu na nieprawidowe typy zmiennych, stanowicych parametry funkcji scanf oraz printf. a W przypadku, gdy kompilacja jest wykonana z ag -Wformat, wykrywane s zaistniae a a niezgodnoci typw. Ostrzeenia wygenerowane przez kompilator zostay przedstawione na listingu 9.8. Po dokonaniu poprawek (nowa wersja programu zostaa umieszczona na listingu 9.9), kompilator nie generuje ju adnych ostrzee.
Listing 9.7: Program zawieraj cy nieprawidowe wywoania funkcji scanf i printf a 01 #include <stdio.h> 02 int main() { 03 int a; 04 char b[100]; 05 long long c; 06 scanf("%d\n", a); 07 scanf("%s\n", &b); 08 printf("%d\n", c); 09 printf("%c\n", b); 10 return 0; 11 }

Listing 9.8: Ostrzeenia generowane przez kompilator przy w czonej opcji -Wformat dla programu a z listingu 9.7 progs/format0.cpp: In function int main(): progs/format0.cpp:6: warning: format argument is not a pointer (arg 2) progs/format0.cpp:7: warning: char format, dierent type arg (arg 2) progs/format0.cpp:8: warning: int format, dierent type arg (arg 2) progs/format0.cpp:9: warning: int format, pointer arg (arg 2)

Listing 9.9: Poprawiona wersja programu z listingu 9.7 01 #include <stdio.h> 02 int main() {

253

Listing 9.9: (c.d. listingu z poprzedniej strony) 03 04 05 06 07 08 09 10 11 } int a; char b[100]; long long c; scanf("%d\n", &a); scanf("%s\n", b); printf("%lld\n", c); printf("%c\n", b[0]); return 0;

Opcja -Wformat okazuje si bardzo poyteczna podczas pisania programw, ktre korzystaj a z biblioteki stdio, zamiast iostream, (robi si tak zazwyczaj ze wzgldw wydajnociowych stdio jest istotnie szybsza od iostream). Bdy zwizane ze zymi typami przekazywaa nych parametrw, pojawiaj si zazwyczaj nie bezporednio podczas pisania programu, lecz a dopiero pniej, przy nanoszeniu poprawek. Przykadowo, jeli zawodnik po napisaniu programu dochodzi do wniosku, e zmienna przechowujca wynik powinna by typu long long, a a nie tak jak do tej pory int, to zmodykuje on typ tej zmiennej, ale moe zapomnie o odpowiednim poprawieniu wywoa funkcji printf.

9.5.3. -Wshadow
Kolejnym parametrem, ktry nie jest automatycznie wczany wraz z uyciem -Wall, jest a -Wshadow. Parametr ten suy do wyszukiwania miejsc w programie, w ktrych nastpuje przykrycie zmiennych globalnych przez zmienne lokalne. W przypadku wykrycia takich sytuacji, generowane s ostrzeenia. a Kilkakrotnie podczas treningw, mj zesp spotka si z sytuacj, w ktrej zaimplemena towany program nie dziaa poprawnie wanie ze wzgldu na ten problem. Wydawa by si mogo, e wykrycie takiego bdu jest do proste, jednak w praktyce, podczas czytania kodu rdowego na kartce, gwn uwag powica si na analiz zoonych wzorw oraz a skomplikowanych operacji, nie zwracajc uwagi na powtarzajce si nazwy zmiennych. a a Rozwizanie kwestii zwizanej z wykorzystaniem w programie zej zmiennej o takiej samej a a nazwie, moe polega na zabronieniu wielokrotnego uycia zmiennych o tych samych nazwach. Wyjtek od reguy mog stanowi czsto wykorzystywane zmienne tymczasowe, takie jak tmp, a a x, y, . . . Rczne sprawdzanie speniania tego zaoenia jest podczas zawodw niewykonalne, na szczcie kompilator jzyka C++ przychodzi z pomoc, udostpniajc parametr -Wshadow. a a Przedstawiony na listingu 9.10 program wyznacza kolejne wartoci dwumianu Newtona n k dla zadanej liczby n. Do swojego dziaania wykorzystuje funkcj Binom, ktra korzysta z globalnej zmiennej n. Funkcja ta deklaruje w linii 7 lokaln zmienn n, ktra jest uywana a a zamiast zmiennej globalnej w linii 9. Kompilacja tego programu bez dodatkowych opcji nie generuje adnych ostrzee. Wykorzystanie jednak agi -Wshadow, powoduje wygenerowanie ostrzee dotyczcych przysaniania zmiennej globalnej przez zmienn lokaln (patrz listing a a a 9.11).
Listing 9.10: Program prezentuj cy problem przykrywania zmiennej globalnej przez lokaln a a 01 #include <iostream> 02 using namespace std; 03 #dene FOR(x,n,m) for(x=n;x<=m;x++)

254

Listing 9.10: (c.d. listingu z poprzedniej strony) 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 int n; int Binom(int k) { int res = 1, x; FOR(x, 1, n) res *= x; int n; FOR(n, 1, k) res /= n; FOR(x, 1, n - k) res /= x; return res; } int main() { cin >> n; for (int x = 0; x < n; x++) cout << Binom(x) << endl; return 0; }

Listing 9.11: Ostrzeenia wygenerowane przez proces kompilacji z w czon ag -Wshadow dla a a a programu z listingu 9.10 progs/wshadow0.cpp: In function int Binom(int): progs/wshadow0.cpp:8: warning: declaration of n shadows a global declaration progs/wshadow0.cpp:4: warning: shadowed declaration is here

9.5.4. -Wsequence-point
Pisanie jak najkrtszych programw czsto wie si ze stosowaniem zagniedonych instruka cji oraz operatorw. W takich sytuacjach, bardzo poyteczny okazuje si rwnie operator przecinka, pozwalajcy na czenie kilku instrukcji w jedn. Podejcie, polegajce na pisaniu a a a a jak najkrtszych programw, jest z jednej strony podane podczas konkursw informatycza nych ze wzgldu na moliwo skrcenia czasu implementacji, z drugiej za strony w istotny sposb zamazuje czytelno programu oraz utrudnia wyszukiwanie bdw. W ramach przykadu, przeanalizujmy rozwizanie zadania Krtki program, ktre byo prac domow na a a a zajciach ze Sztuki programowania na Uniwersytecie Warszawskim.

Zadanie: Krtki program


Pochodzenie: Zajcia ze Sztuki programowania na Uniwersytecie Warszawskim Rozwizanie: a short.cpp

Zadanie polega na napisaniu jak najkrtszego program w C++, ktry szuka najkrtszej drogi w trjwymiarowym labiryncie.

Zadanie
Napisz program, ktry: wczyta opis labiryntu, wyznaczy odlego midzy punktem pocztkowym a kocowym, a 255

wypisze wynik.

Wejcie
W pierwszym wierszu wejcia znajduj si trzy liczby cakowite w, s oraz d, oznaczajce a a odpowiednio wysoko, szeroko oraz dugo labiryntu. W kolejnych wierszach znajduj si a opisy w poziomw labiryntu. Kady z nich przedstawiony jest jako s wierszy po d znakw. Puste pola labiryntu oznaczone s przez spacje, ciany labiryntu to #, start zaznaczony jest a liter S, natomiast meta przez liter M . a

Wyjcie
W pierwszym i jedynym wierszu wyjcia powinna znale si jedna liczba odlego midzy startem a met. a

Przykad
2 4 3 S # # # # ## # ### M ## #

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17

Przykadowy, krtki program rozwizujcy to zadanie zosta przedstawiony na listingu 9.12. a a Na jego przykadzie wida, jak bardzo mona skompresowa kod rdowy. Dziki temu, jest on krtki, ale niesie to ze sob istotne pogorszenie czytelnoci. Wykorzystujc zoone a a operacji, pojawia si dodatkowe zagroenie bdw zwizanych ze z kolejnoci wykonywaa a a 256

Dla nastpujcego wejcia: a Poprawnym rozwizaniem jest: a


10

Listing 9.12: Przykad krtkiego programu #include<stdio.h> int h, w, l, s, x, v, k = 1; char b[40]; main() { gets(b); sscanf(b, "%d%d%d", &h, &w, &l); char t[s = (h + 2) * (w + 2) * (l + 2)]; int d[s], e[99], que[s], dis[] = {-1, 1, -(v = w + 2), v, -v * (l + 2), v * (l + 2)}; for(x = 0; x < s; ++x) t[x] = 0, d[x] = -1; for(x = 0; x < h * w * l; ++x) (x % w ? 0 : gets(t + (v = (w + 2) * (l + 3 + 2 * (x / (w * l)) + x / w) + 1))), e[t[v + (x % w)]] = v + (x % w); for(d[que[0] = e[S]] = x = 0; x < 6 * k; ++x) if(t[v = que[x / 6] + dis[x % 6]] != # && t[v] && d[v] == -1) d[que[k++] = v] = d[que[x / 6]] + 1; printf("%d\n", d[e[M]]); }

nia operacji. Oczywistym jest, e prosta instrukcja postaci a = b++; spowoduje przypisanie zmiennej a wartoci zmiennej b, a nastpnie zwikszenie o jeden wartoci zmiennej b. W przypadku instrukcji a = ++b; kolejno wykonanych operacji zostanie odwrcona. Ale co bdzie si dziao w przypadku bardziej rozbudowanych instrukcji? Zapewne niewiele osb miao przyjemno wiadomego uycia w programie instrukcji postaci n = n++. Jej pojawienie si w programie zazwyczaj wie si z pomyleniem zmiennej, o co nietrudno w przypadku jednoliterowych a nazw. Kompilator GCC udostpnia opcj -Wsequence-point, ktra pozwala na wychwytywanie tego typu bdw. W aktualnej wersji kompilatora, opcja ta dziaa tylko dla programw implementowanych w jzyku C wspomaganie dla jzyka C++ ma by dodane w przyszoci. Przykad dziaania tej agi kompilatora dla programu z listingu 9.13 zosta przedstawiony na listingu 9.14.
Listing 9.13: Przykadowy program wykonuj cy operacje, ktrych kolejno nie jest atwa do a wyznaczenia 01 #include <stdio.h> 02 int main() { 03 int a[10], b[10], n = 0, x; 04 for (x = 0; x < 10; x++) 05 a[x] = 0, b[x] = 1; 06 n = n++; 07 a[n] = b[n++]; 08 a[n++] = n; 09 for (x = 0; x < 10; x++) 10 printf("%d ", a[x]); 11 return 0; 12 }

Listing 9.14: Ostrzeenia wygenerowane przez proces kompilacji programu z listingu 9.13 z w czon opcj -Wsequence-point a a a progs/sequence progs/sequence progs/sequence progs/sequence point.c: In function main: point.c:5: warning: operation on n may be undened point.c:6: warning: operation on n may be undened point.c:7: warning: operation on n may be undened

Opcja -Wsequence-point jest wczana automatycznie wraz z -Wall. Dla zainteresowanych, a na listingu 9.15 przedstawiony jest wynik dziaania programu z przykadu 9.13
Listing 9.15: Wynik dziaania programu z listingu 9.13 0 1 2 0 0 0 0 0 0 0

Przy okazji omawiania krtkich programw, na listingu 9.16 zaprezentowany jest bardzo krtki program (zaledwie 48 znakw) napisany w jzyku C, wyznaczajcy ostatni cyfr a a liczby n!. Program ten jest autorstwa Tomasza Idziaszka.

257

Listing 9.16: Program wyznaczaj cy ostatni cyfr liczb n! a a 1 main(n) { 2 gets(&n); 3 putchar("112640"[n > 52 ? 5 : n & 7]); 4}

9.5.5. -Wunused
Stosunkowo czstym bdem, ktry trudno jest wykry podczas analizy kodu programu na kartce, jest odwoanie si do zych zmiennych. Problem ten zosta poruszony ju w poprzednim rozdziale. W niektrych przypadkach, wykorzystanie w pewnym miejscu zej zmiennej powoduje, e inna zainicjalizowana zmienna nie jest w ogle uywana (przykadowo, program mg wyznaczy pewien wynik poredni i umieci go w zmiennej, lecz w dalszej czci programu, warto ta jest omykowo nie wykorzystywana). Kompilator rwnie i w tym przypadku umoliwia wykrywanie tego typu bdw. Naley w tym celu skorzysta z opcji -Wunused. Opcja ta zawiera w sobie kilka innych ag: -Wunused-function generuje ostrzeenie w przypadku wystpowania w programie niewykorzystywanych funkcji typu static. Niestety opcja ta nie wykrywa wszystkich niewykorzystywanych funkcji. -Wunused-label generuje ostrzeenie dla kadej zdeniowanej, nieuytej etykiety. -Wunused-parameter wykrywa brak wykorzystania parametrw funkcji. -Wunused-variable wykrywa zadeklarowane, lecz niewykorzystane zmienne. -Wunused-value wykrywa wyraenia wystpujce w programie, ktrych warto nie a jest wykorzystywana.

Niektre z wyej omwionych opcji mona wczy nie tylko przy uyciu agi -Wunused ale a rwnie -Wall. Najlepszym sposobem na wczenie wszystkich tych opcji, jest uycie dwch a ag -Wall oraz -W. Na listingu 9.17 przedstawiony zosta przykadowy program, ktry deklaruje niewykorzystan funkcj statyczn, zmienne, wyraenie oraz etykiet. Kompilacja a a tego programu bez adnych opcji nie generuje ostrzee, dopiero wczenie opcji -Wall oraz a -W pozwala na wykrycie potencjalnych bdw. Ostrzeenia generowane przez proces kompilacji dla tego programu przedstawione zostay na listingu 9.18.
Listing 9.17: Program zawieraj cy niewykorzystywane zmienne a 1 2 3 4 5 6 7 8 9 static int foo(int a) { return 10; } int main() { int a = 10, b = 5; a *a; label: return 0; }

258

Listing 9.18: Wynik kompilacji progs/unused.cpp: In function int foo(int): progs/unused.cpp:1: warning: unused parameter int a progs/unused.cpp: In function int main(): progs/unused.cpp:5: warning: unused variable int b progs/unused.cpp:7: warning: label label dened but not used progs/unused.cpp:6: warning: statement with no eect progs/unused.cpp: At top level: progs/unused.cpp:1: warning: int foo(int) dened but not used

9.5.6. -Wuninitialized
Kolejn przydatn opcj kompilatora jest aga, pozwalajca na wykrywanie zmiennych, ktre a a a a nie zostay zainicjalizowane przed pierwszym uyciem. Jest do do poyteczna opcja, gdy czsto piszc program zakada si niesusznie, i warto nowotworzonych zmiennych ustaa wiana jest na 0. Nie jest to jednak prawd. W niektrych przypadkach tak faktycznie bdzie, a gdy system operacyjny czyci przydzielan programom pami ze wzgldw bezpieczestwa. a Okaza si moe, e kompilacja programu z optymalizacjami wygeneruje program, ktry bdzie wykorzystywa ten sam obszar pamici dla rnych zmiennych. Program, ktry zakada, e nowotworzone zmienne maj warto 0 moe dziaa podczas testowania przez zawodnika, a a dopiero na komputerach jury bdy si ujawni. Uycie opcji -Wuninitialized pozwala na a wykrycie tego typu problemw. Dla programu przedstawionego na listingu 9.19, kompilacja bez adnych dodatkowych opcji kompilatora koczy si bez komunikatw. Dopiero uycie -Wuninitialized generuje ostrzeenia s one przedstawione na listingu 9.20. a
Listing 9.19: Program zawieraj cy niezadeklarowane zmienne a 01 02 03 04 05 06 07 08 09 10 11 #include <stdio.h> int foo() { int res; return res; } int main() { int a, b; b = a * a; printf("%d\n", b); return 0; }

Listing 9.20: Wynik kompilacji progs/uninitialized.cpp: In function int foo(): progs/uninitialized.cpp:3: warning: int res might be used uninitialized in this function progs/uninitialized.cpp: In function int main(): progs/uninitialized.cpp:7: warning: int a might be used uninitialized in this function

259

Listing 9.20: (c.d. listingu z poprzedniej strony) c:/djgpp/tmp/ccCxjhzr.o(.eh frame+0x11):uninitialized.cpp: undened reference to gxx personality v0 collect2: ld returned 1 exit status

9.5.7. -Woat-equal
W przypadku rozwizywania zada geometrycznych, pojawiaj si czsto dodatkowe proa a blemy, zwizane z wykonywanymi zaokrgleniami wartoci zmiennych. Powoduje to, e nie a a tylko warto wyliczonego wyniku moe by niedokadna, ale cay program moe dziaa nieprawidowo. Istnieje niebezpieczestwo, e wykonane porwnanie dwch zmiennych typu zmiennoprzecinkowego postaci a < b powinno teoretycznie zwrci prawd, jednak wykonane przez program zaokrglenia spowodoway, e zachodzi nierwno odwrotna a > b. a Porwnywanie wartoci zmiennoprzecinkowych jest szczeglnie niebezpieczne, w przypadku operacji postaci a == b. W takich sytuacjach, nawet wyliczone w identyczny sposb zmienne a i b mog mie inn warto (przyczyn mog by m.in. realizowane przez kompilator optya a a a malizacje kodu), zatem porwnanie a == b praktycznie zawsze zwraca fasz. Dlatego te, wykonywanie porwna wartoci zmiennoprzecinkowych postaci a == b jest niedozwolone w programach. W obliczu koniecznoci wykonania takiego porwnania, naley bra pod uwag pewn a granic bdu problem ten jest dokadniej omwiony w rozdziale dotyczcym geometrii a obliczeniowej. Dobrym pomysem jest dodatkowe zabezpieczenie si przed moliwoci popea nienia tego typu bdu. Opcja -Woat-equal kompilatora pozwala na werykacj poprawnoci programu pod wzgldem braku porwna zmiennoprzecinkowych postaci a == b. Na listingu 9.21 przedstawiony jest nieprawidowy program, ktrego kompilacja koczy si wygenerowaniem ostrzee umieszczonych na listingu 9.22.
Listing 9.21: Program wykonuj cy niebezpieczne porwnanie a 1 int main() { 2 oat a = 10.0, b = 8.0; 3 bool equal = (a == b); 4 return 0; 5}

Listing 9.22: Wynik kompilacji programu z listingu 9.21 z uyciem agi -Woat-equal progs/oat.cpp: In function int main(): progs/oat.cpp:3: warning: comparing oating point with == or != is unsafe

9.6. Nieustanny Time - Limit


Zdarzaj si sytuacje, w ktrych kolejne rozwizania zadania cigle otrzymuj wynik przea a a a kroczenia czasu wykonania. Jednym z powodw takiej sytuacji jest zastosowanie nieefektywnego algorytmu. W takim przypadku, zazwyczaj naley wybra szybszy algorytm i zmieni implementacj caego programu. Jedynym odstpstwem od tej reguy jest, jeli oczekiwana 260

zoono programu jest lepsza od aktualnej o czynnik logarytmiczny. W takich sytuacjach, mona zaryzykowa i nie dokonywa zmiany wykorzystywanego algorytmu. Sposobem radzenia sobie z przekroczeniem czasu, nie wymagajcym zmiany stosowanego a algorytmu, jest przeprowadzanie optymalizacji, pozwalajcych na kilkukrotne przyspieszenie a programu. Tego typu optymalizacje s szczeglnie istotne, w przypadku konkursw z rodziny a Olimpiada Informatyczna, w ktrych punktacja zada jest pynna, a wyniki s ogaszane a dopiero po zakoczeniu zawodw. Istnieje kilka prostych sposobw, pozwalajcych na przyspieszenie dziaania programu: a eliminacja dzielenia oraz obliczania reszty z dzielenia, stosowanie operatora inline, zmiana funkcji sucych do wczytywania danych, a korzystanie ze wstawek assemblerowych, kompilacja programu z optymalizacjami, modykacja programu, majca na celu lepsze wykorzystanie pamici podrcznej. a

Oprcz wyej wymienionych, istnieje jeszcze jedna metoda przyspieszania programw zwana preprocessingiem, jednak jest ona zalena od stosowanego algorytmu. Zasadniczo, polega ona na wyliczaniu pewnych wartoci wykorzystywanych przez algorytm, przed lub podczas kompilacji programu, co pozwala na zaoszczdzenie czasu podczas samego wykonania programu. Przeanalizujmy po kolei przedstawione techniki optymalizacji. Pierwsza z nich eliminacja operacji dzielenia, w przypadku zada wykonujcych liczne operacje arytmetyczne, moe a przynie ogromne korzyci. Operacja dzielenia jest bardzo kosztowna, przez co jej wyeliminowanie z programu moe przynie nawet kilkukrotne przyspieszenie. Obchodzenie si bez operacji dzielenia nie jest rzecz atw, jednak w wielu przypadkach mona sobie poradzi a a przy uyciu operacji przesunicia bitowego (>>). Przykadowo, w celu podzielenia wartoci zmiennej a przez 4, zamiast pisa a = a / 4;, mona wykona operacj a = a >> 2;. Operacja taka (w zalenoci od architektury komputera) dziaa okoo 5 razy szybciej. Jeli program jest kompilowany z optymalizacjami, to kompilator jest w stanie w wielu przypadkach sam zastpi dzielenie oraz wyznaczanie reszty z dzielenia innymi operacjami. a

9.6.1. Wczytywanie wejcia


Czas dostpu do informacji zapisanych na dysku twardym jest istotnie duszy od odwoywania si do pamici operacyjnej. W przypadku pisania programw konkursowych, sytuacja jest podobna. Czas wczytywania i wypisywania danych stanowi w wielu sytuacjach znaczn a cz czasu wykonania programu. Na efektywno tych czynnoci w duym stopniu ma wpyw sposb realizacji operacji wejcia/wyjcia. W jzyku C++ szeroko wykorzystywane s dwie metody. Jedn z nich jest uycie znanych, a a jeszcze z jzyka C, funkcji scanf oraz printf. S one stosunkowo szybkie, jednak posugiwanie a si nimi jest niewygodne ze wzgldu na konieczno podawania formatu danych, na ktrych wykonywane s operacje. a Drug metod, jest stosowanie strumieni wprowadzonych w jzyku C++. Ich uycie jest a a bardzo proste. Strumienie same okrelaj format danych, na podstawie typw zmiennych, a biorcych udzia w realizowanych operacjach. Wygoda jednak niesie ze sob rwnie wielkie a a koszty, strumienie s istotnie wolniejsze od funkcji scanf oraz printf. Rnice w ich czasie a dziaania wahaj si wraz z rnymi wersjami kompilatorw, jednak naley zakada, e a 261

strumienie s rednio piciokrotnie wolniejsze. Na listingach 9.23 oraz 9.24 przedstawione a zostay dwa programy wczytujce i wypisujce 1 000 000 liter. Czas dziaania pierwszego z a a nich na komputerze testowym to 6.5 sekund, podczas gdy drugi program potrzebuje niecae ptorej sekundy.
Listing 9.23: Program wczytuj cy i wypisuj cy 1 000 000 liczb, wykorzystuj cy do tego celu strua a a mienie 01 02 03 04 05 06 07 08 09 10 #include <iostream> using namespace std; int main() { char w; for (int x = 0; x < 1000000; x++) { cin >> w; cout << w; } return 0; }

Listing 9.24: Program wczytuj cy i wypisuj cy 1 000 000 liczb, wykorzystuj cy do tego celu funkcje a a a scanf oraz printf 1 #include <stdio.h> 2 int main() { 3 char w; 4 for (int x = 0; x < 1000000; x++) { 5 scanf("%c", &w); 6 printf("%c", w); 7 } 8 return 0; 9}

Przedstawiona analiza wiadczy o tym, e podczas rozwizywania zada konkursowych nie a naley korzysta ze strumieni, chyba e liczba wczytywanych i wypisywanych danych jest niewielka. Podczas jednego z konkursw, w ktrych zesp Warsaw Predators bra udzia, oczekiwana zoono rozwizania jednego z zada o duym wejciu bya liniowa. Jednak dziki a zoptymalizowaniu operacji wczytywania danych, udao si uzyska akceptacj rozwizania a dziaajcego w czasie O(n log(n)). Wykorzystanie funkcji scanf byo jednak niewystara czajce, co zmusio nas do wykorzystania funkcji getchar i putchar. Przykadowy program a zosta przedstawiony na listingu 9.25. Czas jego wykonania na tym samym komputerze co poprzednie programy to 0, 42 sekundy.
Listing 9.25: Program wczytuj cy i wypisuj cy 1 000 000 liter, wykorzystuj cy do tego celu funkcje a a a getchar oraz putchar 1 #include <stdio.h> 2 int main() { 3 char w; 4 for (int x = 0; x < 1000000; x++) {

262

Listing 9.25: (c.d. listingu z poprzedniej strony) 5 w = getchar(); 6 putchar(w); 7 } 8 return 0; 9}

9.6.2. Kompilacja z optymalizacjami i generowanie kodu maszynowego


Kolejn technik, pozwalajc na zwikszenie wydajnoci programu, jest korzystanie ze wstaa a a a wek assemblerowych. W jzyku C++, wstawki mona dodawa do programu przy uyciu operatora asm. Podejcie tego typu pozwala na najdokadniejsz kontrol tego, co jest wykonya wane przez implementowany program. W przypadku korzystania z instrukcji jzyka wysokiego poziomu, jakim jest C++, nie jest wiadomo, jaki kod maszynowy zostanie wygenerowany dla okrelonej sekwencji. W wielu przypadkach, czowiek jest w stanie napisa znacznie szybszy kod. Podejcie wykorzystujce wstawki assemblerowe byo do czsto stosowan technik a a a przyspieszania programw, jednak obecnie jest ona coraz rzadziej spotykana, ze wzgldu na czasochonno i atwo popeniania bdw. Jako dzisiejszych kompilatorw jest na tyle wysoka, e rczne pisanie kodu maszynowego nie daje tak wielkich rnic jak kiedy. Skoro jako generowanego kodu maszynowego podczas zawodw powierza si w caoci kompilatorowi, to naley upewni si, e zadanie jest wykonane jak najlepiej. Jedn z molia woci wpywania na ten proces jest poprzedzanie funkcji sowem kluczowym inline. Stanowi ono podpowied dla kompilatora, e tre funkcji powinna by wstawiana w miejsce jej wywoania, zamiast wykonywania skoku do miejsca, w ktrym si ona znajduje. W przypadku prostych funkcji, takich jak wyznaczanie maksimum dwch liczb, podejcie takie w istotny sposb zwiksza efektywno programu. Stosowanie operatora inline pozwala na zaoszczdzenie czasu potrzebnego na odkadanie argumentw na stosie oraz wykonywanie operacji zwizanych z wywoaniem funkcji. W przypadku kompilacji programu z optymalizacjami, a rozwijanie wywoa funkcji wykonywane jest automatycznie, jednak operator inline moe pomc kompilatorowi w podejmowaniu decyzji, ktre funkcje naley rozwija.
Listing 9.26: Program wykonuj cy 100 000 000 odwoa do tablicy liczb a 1 using namespace std; 2 int main() { 3 int a[16]; 4 for (int x = 0; x < 100000000; x++) 5 a[x & 15] = x; 6 return 0; 7}

Listing 9.27: Program wykonuj cy 100 000 000 odwoa do wektora liczb a 1 #include <vector> 2 using namespace std; 3 int main() { 4 vector<int> a(16, 0); 5 for (int x = 0; x < 100000000; x++)

263

Listing 9.27: (c.d. listingu z poprzedniej strony) 6 a[x & 15] = x; 7 return 0; 8}

Drug metod generowania kodu maszynowego wysokiej jakoci jest kompilacja programu z a a optymalizacjami (przeczniki kompilatora -O1, -O2, -O3, . . .). Optymalizacje s szczegla a nie istotne w przypadku wykorzystywania zaawansowanych konstrukcji jzyka C++, ktre znajduj szerokie zastosowanie w bibliotece STL. a Wyniki s widoczne na przykadzie programw z listingw 9.26 oraz 9.27. Pierwszy z a nich wykonuje 100 000 000 zapisw do tablicy liczb, podczas gdy drugi wykorzystuje do tego samego celu wektor z biblioteki STL. Czasy wykonania tych programw kompilowanych z optymalizacjami na poziomie -O2, to odpowiednio 0,867 i 1,154 sekundy, zatem s one poa rwnywalne. Jeli te same programy zostan skompilowane bez optymalizacji, to czasy wya konania rosn odpowiednio do 4 i 30 sekund. W przypadku brania udziau w konkursach, a w ktrych jury kompiluje programy bez optymalizacji, programy wykorzystujce bibliotek a STL maj bardzo utrudnione zadanie. Istnieje jednak metoda na obejcie tego problemu, a a polega ona na wstpnej kompilacji programu do kodu maszynowego, zanim odda si program do oceny. Zastosowanie tej techniki nie wymaga adnych zmian w sposobie pisania programu. Realizacja tego procesu wyglda nastpujco: a a Implementacja programu (nazwijmy go a.cpp). Kompilacja programu do kodu maszynowego przy uyciu polecenia gcc -S -Ox a.cpp

gdzie x oznacza wykorzystywany poziom optymalizacji. Zazwyczaj stosuje si poziom 2, gdy daje bardzo dobre efekty i nie niesie ze sob ryzyka zmiany dziaania programu. a Wysze poziomy optymalizacji mog okaza si niebezpieczne i mie wpyw na sposb a dziaania programu. Pomylnie zakoczony proces kompilacji powinien spowodowa powstanie pliku a.s, zawierajcego kod maszynowy programu. a Poniewa jury oczekuje, e rozwizania zada bd nadsyane w jzyku C++, a nie a a Assemblerze, zatem naley dokona modykacji kodu maszynowego w taki sposb, aby mona byo dokona kompilacji przy pomocy kompilatora jzyka C++. Proces ten wymaga umieszczenia na pocztku pliku a.s tekstu asm( oraz na jego kocu );. Poza a tym, naley: zamieni wystpujce w programie znaki na \, umieci na pocztku a a kadego wiersza \ oraz na kocu kadego wiersza \n". Mona tego dokona przy uyciu edytora tekstu, lub wykonujc nastpujc komend: a a a perl -pi~ -e s/\"/\\\"/g; s/(.*)/\"$1\\\n\"/ig a.s

Plik a.s mona skompilowa przy program napisany w jzyku C++. 264

9.6.3. Lepsze wykorzystanie pamici podrcznej


Wielki wpyw na czas wykonania programu moe rwnie mie kolejno, w jakiej program odwouje si do pamici. Rnice zale w istotny sposb od architektury procesora, a doa kadniej, od rodzaju oraz wielkoci jego pamici podrcznej. Szybko dziaania pamici RAM w komputerze jest okoo dziesiciokrotnie wolniejsza od prdkoci procesora. W przypadku, gdy istnieje konieczno odwoania si do pewnej komrki pamici, procesor musi czeka na sprowadzenie potrzebnych danych z pamici RAM. W celu poprawienia tej sytuacji, dzisiejsze procesory wyposaone s w pami podrczn (ang. cache), ktrej prdko jest istotnie a a wiksza od pamici RAM. Zadaniem pamici podrcznej jest przechowywanie ostatnio wykorzystywanych adresw pamici, co pozwala zaoszczdzi czas tracony w oczekiwaniu na sprowadzenie odpowiednich informacji z RAMU. W przypadku zada konkursowych, najwicej czasu przy wykonywaniu programu mona zaoszczdzi poprzez zmian kolejnoci inicjalizacji tablic. Sposb wykonywania tego procesu jest szczeglnie wany podczas rozwizywania zada na programowanie dynamiczne, ktre a czsto wielokrotnie musz aktualizowa zawarto wielowymiarowych tablic. Na listingach a 9.28 oraz 9.29 przedstawione s dwa programy, ktre wykonuj identyczne zadanie wya a peniaj 100 MB tablic wartoci 1. Jedyna rnica polega na kolejnoci przetwarzania pl a a tablicy. Pierwszy z tych programw modykuje tablic wierszami, podczas gdy drugi robi to kolumnami. W przypadku pierwszego programu, odwoania do pamici realizowane s a lokalnie kolejno modykowane bloki pamici s spjne. Drugi program natomiast wykoa nuje liczne skoki po pamici, co uniemoliwia efektywne wykorzystanie pamici podrcznej procesora. Odbija si to znaczco na czasie wykonania obu programw. Pierwszy z nich dziaa a na testowej maszynie 1.2 sekundy, podczas gdy drugi ponad 15 sekund.
Listing 9.28: Program zapisuj cy tablic wierszami a 1 char tab[10000][10000]; 2 int main() { 3 for (int x = 0; x < 10000; x++) 4 for (int y = 0; y < 10000; y++) 5 tab[x][y] = x + y; 6 return 0; 7}

Listing 9.29: Program zapisuj cy tablic kolumnami a 1 char tab[10000][10000]; 2 int main() { 3 for (int y = 0; y < 10000; y++) 4 for (int x = 0; x < 10000; x++) 5 tab[x][y] = x + y; 6 return 0; 7}

265

9.6.4. Preprocessing
Kolejn metod, pozwalajc na przyspieszenie wykonania programw, jest technika zwana a a a a preprocessingiem. Odmiennie od technik omwionych uprzednio, jest ona zalena od stosowanego algorytmu i polega na wyliczaniu pewnych danych, ktrych warto nie jest uwarunkowana od danych wejciowych programu, przed lub podczas kompilacji. W ten sposb, program nie musi wyznacza potrzebnych do swojego dziaania informacji podczas wykonania operacji wystarczy, e skorzysta ze stablicowanych wynikw wyliczonych wczeniej. Przy zastosowaniu powyszej metody, w przypadku niektrych zada, daje si zaimplementowa znacznie mniej efektywny algorytm. Wyznaczanie danych dla programu mona bowiem wykonywa podczas rozwizywania innych zada. Przykadem tego typu zadania, s Liczby a a antypierwsze z pierwszego etapu VIII Olimpiady Informatycznej.

Zadanie: Liczby antypierwsze


Pochodzenie: VIII Olimpiada Informatyczna Rozwizanie: a ant.cpp

Dodatni liczb cakowit nazywamy antypierwsz, gdy ma ona wicej dzielnikw ni kada a a a dodatnia liczba cakowita mniejsza od niej. Przykadowymi liczbami antypierwszymi s: 1, a 2, 4, 6, 12 i 24.

Zadanie
Napisz program, ktry: wczyta dodatni liczb cakowit n, a a wyznaczy najwiksz liczb antypierwsz nieprzekraczajc n, a a a a wypisze wyznaczon liczb. a

1000

Wejcie
W jedynym wierszu wejcia znajduje si jedna liczba cakowita n, 1 < n < 2 000 000 000.

Wyjcie
W jedynym wierszu wyjcia program powinien zapisa dokadnie jedn liczb cakowit a a najwiksz liczb antypierwsz nieprzekraczajc n. Efektywne rozwizanie tego zadania, wya a a a a

Przykad

maga zastosowania pewnych wasnoci rozkadu liczb na liczby pierwsze. Okazuje si jednak, e liczb antypierwszych w przedziale {1, 2 000 000 000} jest niewiele zaledwie 68. Mona zatem je wszystkie wyznaczy wczeniej, a nastpnie umieci ich list w programie. Ze wzgldu na to, e zadanie to pojawio si na pierwszym etapie Olimpiady i na jego rozwizanie zaa wodnicy mieli okoo miesica, zatem mona byo zastosowa, w zasadzie dowoln, metod a a wyznaczania liczb antypierwszych. Program prezentujcy zastosowanie techniki preprocesa singu w praktyce zosta przedstawiony na listingu 9.30

Dla nastpujcego wejcia: a


840

Poprawnym rozwizaniem jest: a

266

Listing 9.30: Rozwi zanie zadania Liczby antypierwsze wykorzystuj ce technik preprocessingu a a 01 int vals[] = { 1, 2, 4, 6, 12, 24, 36, 48, 60, 120, 180, 240, 360, 720, 840, 1260, 02 1680, 2520, 5040, 7560, 10080, 15120, 20160, 25200, 27720, 45360, 50400, 03 55440, 83160, 110880, 166320, 221760, 277200, 332640, 498960, 554400, 665280, 04 720720, 1081080, 1441440, 2162160, 2882880, 3603600, 4324320, 6486480, 05 7207200, 8648640, 10810800, 14414400, 17297280, 21621600, 32432400, 36756720, 06 43243200, 61261200, 73513440, 110270160, 122522400, 147026880, 183783600, 07 245044800, 294053760, 367567200, 551350800, 698377680, 735134400, 1102701600, 08 1396755360 }; 09 10 int main() { 11 int nr, pos = 67; 12 cin >> nr; 13 while(vals[pos] > nr) pos--; 14 cout << vals[pos] << endl; 15 return 0; 16 }

267

Rozdzia 10

Rozwizania zada a
W rozdziale tym znajduj si wskazwki dotyczce zada z poszczeglnych rozdziaw ksiki. a a a Jeli zadanie okazuje si zbyt trudne do rozwizania, czasem moe to by problem zwizany a a z implementacj programu. W takiej sytuacji, przydatna moe si okaza zawarto pyty a doczonej do ksiki. Znajduj si na niej bowiem programy, stanowice rozwizania wszysta a a a a kich zada zawartych w tej ksice, zaimplementowane przy uyciu przedstawionej biblioa teczki algorytmicznej. Problemy z rozwizaniem zadania wi si niekiedy rwnie z wymya a a leniem odpowiedniego algorytmu. W takiej sytuacji, przydatna moe si okaza zawarto aktualnego rozdziau, w obrbie ktrego mona znale podpowiedzi do poszczeglnych zada. Do kadego z nich dostpnych jest kilka podpowiedzi kada kolejna wskazwka konkretyzuje pomys naszkicowany w poprzedniej. Dziki takiemu podejciu, jeli pocztkowe a wskazwki okazuj si niewystarczajce do rozwizania zadania, mona pokusi si o przea a a czytanie nastpnych. Z uwagi na to, e kade zadanie mona rozwiza na wiele sposobw, a natomiast wskazwki nakierowuj na jedno konkretne rozwizanie, nie naley zatem przeja a mowa si zbytnio, jeli koncepcja rozwizania przedstawiona w podpowiedziach nie pokrywa a si z Twoim podejciem.

10.1. Algorytmy Grafowe


10.1.1. Mrwki i biedronka
Zoono asymptotyczna proponowanego rozwizania zadania wynosi O(n*l) a W momencie ldowania biedronki wszystkie mrwki poruszaj si w jej kierunku. Doa a brym pomysem jest wyznaczenie dla kadego licia kierunku, w ktrym naley si z niego porusza. Poniewa rozpatrywana struktura jest drzewem, zatem do wyznaczenia tych kierunkw mona zastosowa algorytm BFS. Dla kadego, kolejnego ldowania biedronki mona wyznaczy drzewo BFS, o ktrym a mowa w powyszej wskazwce, a nastpnie symulowa ruchy mrwek do momentu, w ktrym jedna z nich dotrze do biedronki. W jaki sposb mona wykona symulacj, aby czas jej wykonania by liniowy ze wzgldu na liczb lici w drzewie? Problemem w efektywnej realizacji symulacji jest stwierdzenie, czy przetwarzana mrwka powinna wykona ruch. W celu zwerykowania tego, naley sprawdzi czy na ciece midzy ni a biedronk nie ma adnej innej mrwki. Mona tego szybko dokona poa a przez zaznaczanie lici w drzewie, na ktre nie mona ju wchodzi (gdy na ciece 269

do miejsca ldowania biedronki znajduje si mrwka). Zaznaczanie lici mona wya konywa po kadym pojedynczym ruchu mrwki; wszystkie wierzchoki z poddrzewa BFS tego wierzchoka naley zaznaczy jako zablokowane. Sumaryczny czas wykonania pojedynczej symulacji jest liniowy, poniewa kady wierzchoek zaznaczany jest jako zablokowany dokadnie raz.

10.1.2. Komiwojaer Bajtazar


Istnieje moliwo rozwizania tego zadania w czasie O(n + m), lecz algorytm jest do a skomplikowany. Sugerowana zoono czasowa to O((n + m) log(n)) Podre Bajtazara pomidzy kolejnymi parami miast s niezalene, zatem zadanie a mona rozwiza wyliczajc wszystkie czciowe odlegoci, a nastpnie zwrci sum a a wyznaczonych wartoci. Z uwagi na fakt, e sie drg nigdy nie tworzy cykli oraz z kadego miasta mona dotrze do kadego innego, zatem sie ta tworzy drzewo i dla kadej pary miast istnieje dokadnie jedna cieka je czca. Rozwizanie zadania sprowadza a a a si zatem do zaprojektowania algorytmu wyznaczajcego odlego midzy dowolnymi a dwoma wierzchokami w drzewie. Jak tego dokona? W celu wyznaczania odlegoci wierzchokw w drzewie mona na wstpie ukorzeni analizowane drzewo w pewnym (dowolnym) wierzchoku r. Odlego midzy dwoma wierzchokami v oraz w (ktr oznacza bdziemy przez d(v, w)) w drzewie o korzea niu r mona wyznaczy, korzystajc z pojcia najniszego wsplnego przodka dwch a wierzchokw p = LCA(v, w). Jest to najbardziej oddalony od korzenia drzewa wierzchoek, ktry ley na ciekach r ; v oraz r ; w. Poszukiwan warto d(v, w) a mona wyrazi wtedy jako d(p, v) + d(p, w). W jaki sposb mona wyznacza w czasie logarytmicznym (O(log(n)) najniszego wsplnego przodka dwch wierzchokw v i w w drzewie? Wyznaczanie najniszego wsplnego przodka dwch wierzchokw mona dokona w czasie staym. Opis takiego algorytmu mona znale w pracy [LCA]. Algorytm ten jednake jest stosunkowo skomplikowany w implementacji. W rozwizaniu zadania umiesza czonego na pycie zastosowany zosta nieco prostszy algorytm. Zauwamy na wstpie, e wykonujc na drzewie algorytm DFS, zaczynajc od korzenia r, jestemy w stanie w a a czasie staym odpowiada na pytanie, czy wierzchoek p jest przodkiem wierzchoka v. Aby tak byo musi zachodzi: d(p) < d(v), f (p) > f (v) Znajc list wierzchokw wystpujcych na ciece od wierzchoka v do korzenia r a a mona wyznacza warto LCA(v, w), stosujc wyszukiwanie binarne. Podejcie takie a wymaga jednak skonstruowania pewnej reprezentacji cieek, ktrej wielko nie przekracza O(nlog(n)) oraz ktra pozwala efektywnie przeszukiwa ciek. Rozwizaniem a jest wyznaczenie dla kadego wierzchoka w drzewie lici oddalonych o 1, 2, 4, . . . w kierunku korzenia.

10.1.3. Drogi
Zadanie mona rozwiza w czasie O(n + m). a 270

Sie drg w Bajtocji mona przedstawi w postaci grafu skierowanego G. Problem postawiony w zadaniu jest rwnowany wyznaczeniu minimalnej liczby krawdzi, jakie naley doda do grafu G, aby z kadego wierzchoka dao si doj do kadego innego. Rozpatrujc graf H silnie spjnych skadowych dla grafu G, okazuje si e nie trzeba a dokada adnych dodatkowych drg, jeli graf H skada si z dokadnie jednego wierzchoka (wszystkie wierzchoki grafu G nale do tej samej silnie spjnej skadowej). a Aby zatem rozwiza zadanie, naley wyznaczy liczb krawdzi, jak naley doda do a a grafu G, tak aby odpowiadajcy graf silnie spjnych skadowych skada si z jednego a wierzchoka. Zamiast operowa na oryginalnym grae, mona rwnie dodawa krawdzie do grafu H (dodana krawd w grae H reprezentuje krawd czc dowolne a a a wierzchoki z odpowiednich silnie spjnych skadowych grafu G). Sprowadzenie grafu H do podanej postaci jest atwiejsze ni grafu G, gdy jest on acykliczny. a Niech liczby a i b bd odpowiednio rwne liczbie wierzchokw o stopniu wyjciowym a 0, oraz o stopniu wejciowym 0 w grae H. Poszukiwana liczba krawdzi, jak naley a doda do grafu G jest rwna max(a, b). Wynika to z faktu, i z kadej silnie spjnej skadowej grafu musi wychodzi jak i wchodzi co najmniej jedna krawd.

10.1.4. Spokojna komisja


Zadanie mona rozwiza w czasie O(n + m). a Zauwamy, e jeeli posowie v oraz w nawzajem si nie lubi, to wybranie do komisji a posa v wymusza rwnie wybranie posa z, ktry jest z posem w razem w partii. W takim przypadku mwimy, e pose z jest zaleny od posa v. Na podstawie listy par posw, ktrzy si nawzajem nie lubi mona skonstruowa graf zalenoci G a nastpie, a przy jego uyciu, wyznaczy skad komisji. Jeli do komisji zostanie wybrany pose v, to musz rwnie zosta wybrani wszyscy a posowie, ktrzy s osigalni z wierzchoka v w grae zalenoci G. Zatem dla kadej a a silnie spjnej skadowej grafu G, albo wszyscy posowie z tej skadowej nale do komia sji, albo aden z nich. Dodatkowo, jeli pewien pose z silnie spjnej skadowej x zosta wybrany do komisji, to wszyscy posowie ze skadowych osigalnych z x rwnie musz a a by w komisji. Jeli w grae G, z wierzchoka reprezentujcego posa v osigalne s a a a wierzchoki reprezentujce obu posw z tej samej partii, to v nie moe zosta wybrany a do komisji. Rozpoczynajc od pustego zbioru czonkw spokojnej komisji, oraz rozpatrujc wierza a choki silnie spjnych skadowych grafu G w porzdku odwrotnie topologicznym, mona a zastosowa metod zachann konstruowania wyniku. Jeli aktualnie przetwarzana skaa dowa nie zawiera posw, ktrych dodanie do konstruowanego wyniku powodowaoby wystpienie nie lubicej si pary posw w komisji, to grup tak mona doda do a a a wyniku. Po przetworzeniu caego grafu, jeli skonstruowany wynik spenia wymagania zadania, to naley zwrci go jako odpowied, w przeciwnym razie komisja nie moe zosta ustanowiona. Dowd poprawnoci tego algorytmu pozostawiamy czytelnikowi.

10.1.5. Wirusy
Zadanie mona rozwiza w czasie liniowym ze wzgldu na sumaryczn dugo wszysta a kich kodw wirusw. 271

W rozwizywaniu tego zadania poyteczna moe okaza si lektura rozdziau 7.3 doa tyczcego algorytmu Bakera do wyszukiwania wielu wzorcw w tekcie. a Zauwamy, e jeeli istnieje nieskoczony, bezpieczny cig zer i jedynek t, to nie zawiera a on adnego z wirusw jako swojego podcigu. W takiej sytuacji, wykonujc algorytm a a Bakera dla wzorcw, bdcych sekwencjami wirusw oraz przeszukiwanego tekstu t, a algorytm nigdy nie tra do wierzchoka w drzewie preksw wzorcw, ktry reprezentuje cay wzorzec. Czy mona wykorzysta drzewo preksw wzorcw do wyznaczenia bezpiecznego nieskoczonego cigu zer i jedynek? a Aby istnia nieskoczony cig zer i jedynek, nie zawierajcy adnego wirusa, to algorytm a a Bakera musi porusza si po takim cyklu, w obrbie drzewa preksw, ktry nie zawiera adnego wza reprezentujcego kod wirusa. Jeli taki cykl nie istnieje, to ze wzgldu na a ograniczon wielko drzewa, algorytm w kocu tra do wza reprezentujcego pewien a a wirus. Aby rozwiza zadanie, naley skonstruowa struktur mkmp dla podanych kodw wirua sw, a nastpnie sprawdzi, czy drzewo reprezentujce wzorce zawiera cykl (przy wya szukiwaniu cyklu naley traktowa wartoci funkcji preksowej jako krawdzie drzewa).

10.1.6. Linie autobusowe


Zadanie mona rozwiza w czasie O(n + m). a Oznaczmy poszukiwan liczb linii autobusowych przez k. Zastanwmy si najpierw, a kiedy istnieje moliwo zagwarantowania transportu wszystkim mieszkacom przy uyciu dokadnie jednej linii autobusowej. Ze wzgldu na fakt, i graf drg jest spjny (z kadego miasta da si dojecha do kadego innego), zatem tras autobusu przebiegajc a a przez wszystkie drogi mona utosami ze ciek Eulera. Jeli cieka Eulera istnieje, a to mona rozwiza zadanie wykorzystujc dokadnie jedn lini autobusow. a a a a ciek Eulera mona wyznaczy w spjnym grae wtedy, gdy kady wierzchoek ma stopie parzysty lub gdy liczba wierzchokw o stopniu nieparzystym (l) jest rwna 2. Ile linii autobusowych jest potrzebnych, jeli liczba l > 2? Z wasnoci cieek Eulera l wiemy, e k 1 . Czy 2 jest wystarczajc liczb linii autobusowych? a a a 2
l Faktycznie, k = 2 . Jeli bowiem do grafu dodamy specjalny wierzchoek v, ktry poczymy krawdzi z kadym wierzchokiem o nieparzystym stopniu, to uzyskany a a graf bdzie posiada cykl Eulera. Po jego wyznaczeniu, moemy rozdzieli go na zbir cieek poprzez usunicie wszystkich wystpie wierzchoka v. Spowoduje to powstanie a l a 2 cieek, ktre przechodz przez wszystkie krawdzie oryginalnego grafu, a co za tym idzie, stanowi poszukiwany zbir linii autobusowych. a

10.1.7. Przemytnicy
Sugerowana zoono czasowa rozwizania to O(m log(n)). a Przemytnicy musz przewie przez granic zoto pod postaci pewnego, okrelonego a a metalu. Aby wyznaczy jaki wybr jest najbardziej opacalny, dla kadego z metali naley wyznaczy sum: kosztu zamiany jednego kilograma zota w dany metal, ca dla jednego kilograma tego kruszcu, oraz kosztu zamiany z powrotem w zoto. Po wyznaczeniu wszystkich tych wartoci, wystarczy wybra najmniejsz z nich. a 272

Aby wyznaczy koszt zamiany 1 kg zota w okrelony metal, mona skonstruowa graf reprezentujcy koszty zamiany metali, w ktrym wierzchokami s metale, a krawdzie a a reprezentuj proces alchemii (kada krawd ma przypisan wag, rwn kosztowi przea a a prowadzenia zamiany 1 kg metalu). Na tak skonstruowanym grae, wystarczy wykona algorytm Dijkstry, rozpoczynajc z wierzchoka reprezentujcego zoto; odlegoci a a poszczeglnych wierzchokw od rda reprezentuj koszty uzyskania tych metali ze a zota. Przy uyciu jednego wywoania algorytmu Dijkstry, uzyskujemy koszty otrzymania wszystkich metali ze zota. Aby wyznaczy koszty uzyskania zota z poszczeglnych metali, mona zastosowa podobn metod jak w poprzednim punkcie, lecz tym razem algorytm Dijkstry naley a wykona na grae transponowanym (krawdzie reprezentujce poszczeglne przemiany a maj przeciwny zwrot). a

10.1.8. Skoczki
Sugerowana zoono czasowa rozwizania to O(n a

10.2. Geometria obliczeniowa na paszczynie


10.2.1. Akcja komandosw
Sugerowana zoono czasowa rozwizania to O(n2 ). a Kademu komandosowi przypisane jest pewne koo; jeli bomba znajduje si w obrbie tego obszaru, to podczas jej wybuchu komandos zginie. Dla danego zbioru komandosw istnieje moliwo takiego umieszczenia bomby, aby aden z komandosw nie przey jej wybuchu, o ile przecicie wszystkich odpowiadajcych k jest niepuste. Zatem a rozwizanie zadania polega na dodawaniu kolejnych k reprezentujcych obszary raea a nia dla komandosw tak dugo, dopki ich cz wsplna jest niepusta. W jaki sposb mona sprawdzi czy cz wsplna k k jest niepusta w czasie O(k)? Brzeg obszaru stanowicego cz wspln k k jest wyznaczony przez obwody ra a nych k. Czy mona wyznaczy pewien punkt charakterystyczny, nalecy do obwodu a jednego z k, ktry naley do czci wsplnej wszystkich k o ile jest ona niepusta? Zauwamy, e jeeli rozpatrzymy k 1 wsplnych obszarw przecicia k-tego koa z koami 1, 2, . . . , k 1 oraz dla kadego z tych obszarw wyznaczymy skrajny prawy punkt, to jeli cz wsplna k k jest niepusta, to musi do niej nalee najbardziej lewy 273

n).

Szachownica skada si z dwch rodzajw pl biaych oraz czarnych. Skoczek wykonujc ruch zawsze przemieszcza si midzy polami o rnych kolorach. a Graf, ktrego wierzchoki reprezentuj pola szachownicy, a krawdzie dozwolone rua chy skoczkw, jest dwudzielny. Problem wyznaczenia maksymalnego, nie bijcego si a zbioru skoczkw jest rwnowany wyznaczeniu maksymalnego zbioru wierzchokw nie poczonych krawdziami tzw. maksymalnego zbioru niezalenych wierzchokw. a W przypadku grafw dwudzielnych, liczno maksymalnego zbioru niezalenych wierzchokw jest rwna liczbie wierzchokw w tym grae, pomniejszona o wielko maksymalnego skojarzenia. Rozwizanie zadania jest ju proste mona zastosowa do tego a algorytm Hopcrofta - Karpa.

(o najmniejszej wsprzdnej x) punkt skrajny. Przy dowodzeniu tego faktu pomocne moe okaza si narysowanie przykadowego ukadu k.

10.2.2. Pomniki
Sugerowana zoono czasowa rozwizania to O((n + m) log(n)). a Warunek, aby wybudowany pomnik nie zakca komunikacji midzy serwerami jest rwnowany temu, e jego lokalizacja nie znajduje si wewntrz wypukej otoczki wya znaczonej przez lokalizacje Bito - serwerw. Sprawdzenie, czy dany punkt naley do wntrza wielokta wypukego mona wykona w a czasie logarytmicznym, przy uyciu przedstawionej w ksice funkcji PointInsideCona vexPol. Rozwizanie zadania sprowadza si do wyznaczenia wypukej otoczki dla zbioru lokalia zacji Bito - serwerw, a nastpnie dla kadej proponowanej lokalizacji pomnika sprawdzenie, czy ley ona wewntrz wyznaczonej wypukej otoczki. a

10.2.3. Otarze
Istnieje moliwo rozwizania tego zadanie w zoonoci O(n2 ), jednak rozwizanie a a takie jest do skomplikowane. Sugerowana zoono programu to O(n2 log(n)). Sprawdzanie, czy dana witynia moe zosta zhabiona, mona wykonywa niezalenie a dla kadej z nich. W ten sposb naley przeprowadzi n testw, z ktrych kady naley zrealizowa w zoonoci O(n log(n)). Aby sprawdzi, czy dana witynia s moe zosta zhabiona, mona zastosowa metod a zamiatania ktowego (naley posortowa witynie widziane przez wejcie do wityni a a a s po kcie odchylenia wzgldem pozycji otarza wityni s, a nastpnie sprawdzi, czy a a patrzc z pozycji otarza istnieje przewit, przez ktry mgby si przemkn duch). a a Podczas sprawdzania czy istnieje przewit, kad wityni mona reprezentowa przy a a pomocy pary jej przektnych. Lewe koce wszystkich odcinkw, widocznych przez weja cie do wityni s z otarza, mona umieci w jednej kolejce, a prawe koce w drugiej. a Po wykonaniu sortowania ktowego naley przetworzy obie kolejki za kadym razem a wybierajc z jednej z nich punkt, ktry znajduje si bardziej na lewo. Jeli w pewa nym momencie liczba przetworzonych lewych kocw jest rwna liczbie przetworzonych prawych kocw, oznacza to e znaleziony zosta przewit.

10.3. Kombinatoryka
10.3.1. Liczby permutacyjnie - pierwsze
Sprawdzenie, czy dana liczba jest permutacyjnie - pierwsza nie jest zadaniem prostym. Wymaga ono wygenerowania wszystkich permutacji cyfr danej liczby, a nastpnie sprawdzenia, czy ktra z nich zachowuje wasno podan w zadaniu. a Aby rozwiza jeden podproblem, naley sprawdzi kad liczb z zadanego przedziau, a a czy jest ona permutacyjnie - pierwsza. W tym celu potrzebna jest rwnie moliwo szybkiego sprawdzania, czy dana liczba jest pierwsza. Mona tego dokona poprzez 274

wygenerowanie listy liczb pierwszych funkcj bitvector Sieve(). Sumaryczny czas a dziaania programu to O(s t), gdzie s to liczba przeprowadzonych testw, natomiast t okrela zoono wykonania pojedynczego testu. W jakim czasie mona wykona pojedynczy test? Przy uyciu generatora permutacji w kolejnoci minimalnych transpozycji oraz wykorzystujc tablic liczb pierwszych, pojedynczy test mona wykona w czasie O(k!), gdzie a k jest liczb cyfr testowanej liczby. Po wykonaniu zamiany miejscami dwch cyfr liczby a w atwy sposb mona zaktualizowa jej warto, a nastpnie sprawdzi, czy rnica midzy ni a pocztkow liczb jest postaci 9 p. a a a a

10.4. Teoria liczb


10.4.1. Bilard
Sugerowana zoono rozwizania to O(log(n)), gdzie n to ograniczenie na wielko a wsprzdnych z zadania. Przy rozwizaniu zadania pomocne jest rozwaenie odbicia kuli od boku stou bilara dowego jak w lustrze (po odbiciu obserwowany jest obraz kuli w lustrze). W ten sposb kula nie odbija si od cian stou, lecz porusza si cay czas wzdu linii prostej. St mona utosami z siatk, ktr kula przecina podczas odbijania si od cian stou. a a Kula w oryginalnym problemie wpadnie do uzy, jeli w problemie zmodykowanym znajdzie si w punkcie kratowym siatki. W jaki sposb w zmodykowanym zadaniu wyznaczy czy i do ktrej uzy wpadnie kula? Wyznaczenia pierwszego punktu kratowego, przez ktry przetoczy si kula, mona dokona obliczajc najpierw wsprzdne kuli w chwili pierwszego przecicia pionowej a linii siatki, a nastpnie przeskalowujc te wsprzdne w taki sposb, aby wsprzdna a y-kowa rwnie znajdowaa si na poziomej linii siatki. Aby stwierdzi, do ktrej uzy wpada kula, wystarczy przeanalizowa liczb przeci pionowych oraz poziomych linii siatki.

10.4.2. Wyliczanka
Zadanie mona w prosty sposb rozwiza w zoonoci O(n2 ). a Jeli w pewnym momencie w kku stoi k dzieci oraz midzy dziemi, ktre kolejno odpadaj z kka, znajduje si m 1 innych dzieci, to dugo wyliczanki musi by a postaci l k + m, l N (podczas wyliczania, m dzieci wypowie o jedn sylab wicej a ni reszta k m dzieci stojcych w kku). a Rozpatrujc kolejne odchodzce z kka dzieci, mona skonstruowa ukad n rwna a a modularnych postaci n ml (mod kl ). Jeli ukad ten nie jest sprzeczny, to najmniejsze jego rozwizanie stanowi poszukiwan dugo wyliczanki. a a Wyznaczenie rozwizania ukadu rwna modularnych jest moliwe przy uyciu funkcji a congr. Rwnania mona rozwizywa parami, w kadym kroku eliminujc jeden skada a nik ukadu rwna. Ostatni uzyskany wynik (jeli istnieje) stanowi dugo najkrtszej moliwej wyliczanki. 275

10.4.3. acuch
Istnieje moliwo rozwizania tego zadania w czasie O(n log(n) log(log(n))), jednak a sugerowana zoono rozwizania to O(n2 ). a Zauwamy, e jeli sekwencj ruchw l prowadzc do zdjcia acucha z prta wykoa a namy w odwrconej kolejnoci, rozpoczynajc od acucha zdjtego z prta, to uzya skamy w ten sposb sytuacj pocztkow. Zatem zamiast rozpatrywa problem zdeja a mowania acucha z prta, moemy przeanalizowa zakadanie go w taki sposb, aby uzyska zadan konguracj kocow. a a Zgodnie z opisem dozwolonych ruchw, minimalna liczba krokw, ktre trzeba wykona, aby zaoy k-te ogniwo acucha na prt jest niezalena od konguracji ogniw k + 1, k + 2, . . .. Dodatkowo, w chwili gdy wykonywany jest ruch nakadania k-tego ogniwa na prt, konguracja ogniw k 1, k 2, . . . jest zdeniowana jednoznacznie ogniwo k 1 musi by zaoone na prt, a ogniwa k 2, k 3, . . . zdjte. Ile ruchw naley wykona, aby zaoy k-te ogniwo przy zaoeniu, e wszystkie wczeniejsze s zdjte z a prta? Zaoenie k-tego ogniwa wymaga 2k1 ruchw. Ze wzgldu na opisane wczeniej wasnoci ruchw zwizanych z zakadaniem i zdejmowaniem ogniw z prta, wyznaczenie liczby a potrzebnych ruchw do zdjcia caego acucha mona dokona, sumujc liczby ruchw a potrzebnych do zdjcia kolejnych ogniw (zaczynajc od ogniwa numer n). Zdejmujc a a ogniwo na pozycji k, naley pamita o zmianie stanu ogniwa k 1. Do przechowywania wyniku naley wykorzysta arytmetyk wielkich liczb.

10.5. Struktury danych


10.5.1. Mapki
Sugerowana zoono rozwizania zadania to O(n log (n)). a Sposb trzymania si mapek midzy sob mona reprezentowa przy uyciu grafu, w a ktrym wierzchokami s mapki, natomiast krawd czca dwa wierzchoki v oraz w a a a reprezentuje trzymanie si odpowiednich mapek. Puszczenie mapki w przez mapk v mona reprezentowa przez usunicie krawdzi (v, w) z grafu. Takie podejcie po usuniciu kadej krawdzi wymaga zwerykowania, ktre mapki spady na ziemi. Proste rozwizanie tego problemu polega na wyszukiwaniu wszystkich wierzchokw ze spja nej skadowej wierzchoka 1 i daje w konsekwencji algorytm o zoonoci O(n2 ). W jaki sposb mona podejcie to przyspieszy? Zamiast symulowa zdarzenia w kolejnoci ich wystpienia, mona wykonywa symua lacj w odwrconej kolejnoci w ten sposb operacja usuwania krawdzi z grafu zostaje zastpiona przez operacj wstawiania krawdzi. Zmiana podejcia w istotny a sposb uatwia proces sprawdzania, ktre wierzchoki zostay dodane do spjnej skadowej wierzchoka 1. Wykorzystujc struktur FAU mona w prosty sposb reprezentowa proces czenia a a grup mapek. Caa symulacja wymaga O(n) operacji Find oraz operacji Union, co w konsekwencji daje poszukiwan zoono algorytmu. a 276

10.5.2. Kodowanie permutacji


Oczekiwana zoono czasowa rozwizania to O(n log(n)). a Liczba bk reprezentuje liczb elementw permutacji ze zbioru {a1 , a2 , . . . , ak1 } wikszych od elementu ak . Sekwencja nie jest kodem adnej permutacji, jeli zachodzi bk k, k {1, 2, . . . , n}. Jeli sytuacja taka nie zachodzi, to kod permutacji jednoznacznie reprezentuje pewn permutacj. W jaki sposb mona wyznaczy jej elementy? a Dla danego kodu B permutacji A o liczbie elementw n istnieje moliwo wyznaczenia n-tego elementu permutacji jest on po prostu rwny n bn . Stosujc t technik a a oraz uwzgldniajc ju wykorzystane elementy permutacji, mona wyznacza kolejne a elementy an1 , an2 , . . . , a1 . W jaki sposb mona wyszukiwa k-ty najmniejszy element w danym zbiorze niewykorzystanych elementw? Korzystajc ze struktury danych PosTree, wyznaczanie kolejnych elementw permutaa cji oraz aktualizowanie struktury danych, mona wykona w czasie logarytmicznym, co w sumie daje algorytm o zoonoci O(n log(n)).

10.5.3. Marsjaskie mapy


Oczekiwana zoono czasowa rozwizania to O(n log(n)). a Wyliczenie sumarycznego obszaru pokrytego przez wszystkie mapy mona wykona stosujc technik zamiatania. Przykadowo, mona przetwarza pionowe krace (pocztki a a oraz koce) kolejnych map w kolejnoci niemalejcych wsprzdnych x. W kadym a kroku naley aktualizowa dugo przekroju w paszczynie y, ktra jest pokryta przez aktywne mapy (takie ktrych lewe koce zostay ju przetworzone, ale prawe jeszcze nie) oraz zwiksza odpowiednio warto wyniku. W jaki sposb naley przechowywa informacje o aktywnych mapach, aby mona byo szybko uzyskiwa informacj o wielkoci pokrytego przekroju? Struktur danych, ktr mona wykorzysta jest CoverBTree. Pozwala ona w czasie a a logarytmicznym na dodawanie oraz usuwanie kolejnych pionowych kracw map oraz wyznaczanie wielkoci przekroju.

10.5.4. Puste prostopadociany


Oczekiwana zoono czasowa rozwizania to O(n log(n)). a Poszukiwany w zadaniu prostopadocian musi mie najwiksz moliw objto. Pocia a a ga to za sob fakt, i kada z jego trzech cian, ktrych pozycj mona manewrowa, a a musz albo by umieszczone w odlegoci 106 od punktu (0, 0, 0), albo musz zawiera a a jeden z punktw ze zbioru A. Wyszukiwanie takiego prostopadocianu mona usystematyzowa, zakadajc przykadowo, e jego grna ciana (rwnolega do paszczyzny a OXY ) zawiera kolejne punkty ze zbioru A. Dla kadego przetwarzanego punktu naley maksymalizowa powierzchni podstawy prostopadocianu tak, aby nie zawiera on w swym wntrzu adnego punktu ze zbioru A. Jeli wysoko prostopadocianu jest ustalona, to wyznaczenie maksymalnej powierzchni podstawy mona wykona w podobny sposb poprzez wybieranie kolejnych punktw 277

lecych w cianie prostopadocianu rwnolegej do osi OY Z oraz dobieranie odpowieda niego punktu dla ciany rwnolegej do OXZ. Takie rozwizanie jednak ma zoono a O(n3 ). Na szczcie istnieje moliwo przyspieszenia sposobu wyznaczania ograniczajcych a punktw. Zauwamy, e przy ustalonej wysokoci prostopadocianu interesujce s a a tylko te punkty, ktre maj mniejsz wysoko (wsprzdn z) od wysokoci prostopaa a a docianu. Wszystkie takie punkty mona zrzutowa na paszczyzn OXY , a nastpnie wyznacza na tej paszczynie podstaw prostopadocianu, nie zawierajc adnego ze a a zrzutowanych punktw. Punkty interesujce podczas wyznaczania podstawy reprezena tuj schodki prowadzce od punktu na dodatniej posi OY (0, 1 000 000) do punktu a a na dodatniej posi OX (1 000 000, 0). Powierzchnia najwikszej podstawy prostopadocianu jest rwna najwikszemu iloczynowi wsprzdnej y pewnego punktu ze wsprzdn x kolejnego punktu wyznaczajcych schodki. Rozwizanie takie daje algorytm a a a o zoonoci czasowej O(n2 ). Punkty ze zbioru A mona przetwarza w kolejnoci niemalejcych wsprzdnych z. a Dla kolejnego punktu p naley ustali wysoko prostopadocianu oraz wybra najwiksz powierzchni podstawy bazujc na aktualnej zawartoci schodkw. Nastpa a nie naley zaktualizowa posta schodkw poprzez dodanie punktu p. Do reprezentacji schodw mona wykorzysta odpowiednio wzbogacon struktur drzewa BST, ktra ba dzie pozwalaa na szybkie wyznaczanie powierzchni podstawy, co daje w konsekwencji algorytm o zoonoci O(n log(n)). Istnieje moliwo rozwizania tego zadania w zoonoci O(n log(n)) bez wykoa rzystywania wzbogaconych drzew BST wystarczajce okazuj si struktury danych a a znajdujce si w bibliotece ST L. Algorytm taki jest zaprezentowany w programie staa nowicym rozwizanie zadania, znajdujcym si na doczonej pycie. a a a a

10.6. Algorytmy tekstowe


10.6.1. Szablon
Rozwizanie zadania mona zaimplementowa w zoonoci czasowej O(n), jednak proa ponowana zoono to O(n log(n)). Na temat liniowej implementacji rozwizania a mona przeczyta w [OI12]. Szablon tekstu musi pokrywa w caoci napis, dla ktrego jest wyznaczany. W szczeglnoci, musi on pasowa do pocztku, jak i do koca tekstu, co oznacza, e jest on a zarwno preksem, jak i suksem tekstu. W jaki sposb mona efektywnie wyznaczy wszystkie prekso - suksy danego tekstu w, celu zwerykowania czy s one szablonami a oraz wybrania najkrtszego z nich? Doskonaa do wyznaczania wszystkich prekso - suksw tekstu jest tablica preksowa wykorzystywana w algorytmie KM P . Dla kadego wyznaczonego przy jej uyciu napisu (ich wyznaczenie zajmuje czas liniowy), mona sprawdzi, choby przy uyciu algorytmu KM P , ktre z prekso - suksw s szablonami tekstu, a nastpnie wybra najkrtszy. a Takie rozwizanie jednak ma pesymistyczn zoono O(n2 ) dla tekstu postaci an a a istnieje O(n) prekso - suksw, a sprawdzenie kadego z nich zajmuje czas liniowy. Czy mona w jaki sposb zredukowa liczb sprawdzanych prekso - suksw, aby poprawi zoono rozwizania? a 278

Jeli dla tekstu t istniej dwa prekso - suksy a oraz b, zachowujce wasno |a| a a 2 |b|, to jeli a jest szablonem dla tekstu t, to b rwnie nim jest. Jest tak dlatego, e b stanowi szablon a. Korzystajc z tej wasnoci, podczas sprawdzania kolejnych prekso a - suksw tekstu t, mona wiele z nich pomin kady kolejny sprawdzany tekst a bdzie przynajmniej dwa razy krtszy od poprzedniego, co daje najwyej O(log(n)) testw oraz zoono kocow O(n log(n)). a

10.6.2. MegaCube
Zadanie mona rozwiza w czasie O(n), gdzie n reprezentuje powierzchni labiryntu. a Jeli zwiedzony obszar labiryntu jest prostoktem o jednym z bokw dugoci 1, to a rozwizanie zadania mona zrealizowa poprzez wyznaczenie wszystkich wystpie wzorca a a reprezentujcego zwiedzony obszar w tekcie opisujcym labirynt. Czy zawsze mona a a dokona redukcji problemu z zadania do takiej postaci? Sposb redukcji do problemu wyszukiwania wzorca w tekcie moe polega na przyporzdkowaniu kadej kolumnie wzorca pewnego identykatora oraz skonstruowaniu a nowej reprezentacji labiryntu, w ktrej wystpienia kolumn wzorca zostay zastpione a a ich identykatorami. W ten sposb, wyszukiwanie 2-wymiarowego wzorca w danym dwuwymiarowym tekcie, zostao zredukowane do wyszukiwania zwykego wzorca (skadajcego si z identykatorw kolumn), w zmodykowanym tekcie. Jak metod zaa a miany reprezentacji naley zastosowa, aby uzyska algorytm liniowy ze wzgldu na wielko danych wejciowych? Wykorzystujc algorytm wyszukiwania wielu wzorcw w tekcie (struktura MKMP), faz a redukcji mona wykona w czasie liniowym. Kolejne kolumny wzorca stanowi wyszua kiwane wzorce w tekcie reprezentujcym labirynt. a

10.6.3. Palindromy
Zadanie mona rozwiza w zoonoci O(n2 ), gdzie n reprezentuje dugo przetwaa rzanego tekstu. Zadanie mona rozwiza sprowadzajc je do problemu grafowego. Kady preks tekstu a a t mona reprezentowa poprzez wierzchoek w grae. Krawd midzy dwoma wierzchokami tl oraz tk istnieje wtedy, gdy podsowo [tl+1 . . . tk ] jest palindromem parzystym. Krawdzie w grae mona wyznacza stosujc algorytm do wyliczania promieni palina dromw w tekcie. Kady rozkad sowa t na palindromy parzyste ma swj odpowiednik w grae jako cieka zaczynajca si w wierzchoku t0 = i koczca si w tn = t. a a Rozkad na minimaln liczb palindromw odpowiada najkrtszej ciece midzy wierza chokiem t0 a tn , natomiast maksymalny rozkad na palindromy najduszej. W celu wyznaczenia tych cieek nie jest konieczne jawne konstruowanie grafu wystarczy zastosowa programowanie dynamiczne, polegajce na wyznaczaniu dla kolejnych prea ksw t najkrtszej oraz najduszej cieki o pocztku w t0 . a

10.6.4. Punkty
Zadanie mona rozwiza w zoonoci O(n k log(k)). a 279

Na wstpnie zakadamy, e porwnywane zbiory punktw s tej samej mocy jest to a bowiem warunek konieczny, aby zbiory punktw byy podobne. Zgodnie z treci zaa dania, dwa zbiory punktw s do siebie podobne, jeli mona uzyska jeden z drugiego a poprzez wykonanie serii operacji: obroty, przesunicia, symetrie osiowe i jednokadnoci. Sprawdzenie podobiestwa dwch zbiorw punktw moe polega na prbie zasymulowania serii przeksztace jednego z tych zbiorw. W jaki sposb mona zrealizowa to zadanie? Operacje, jakie mona wykonywa na zbiorze punktw s przemienne w tym sensie, a e jeli zbir A moemy przeksztaci na zbir B, przy uyciu sekwencji przeksztace l1 , l2 , l3 , . . ., to istnieje rwnie sekwencja przeksztace k1 , k2 , k3 , . . ., takich, e na pocztku wykonywane s tylko obroty, nastpnie przesunicia, symetrie osiowe a na a a kocu jednokadnoci. W jaki sposb mona skonstruowa (o ile istnieje) cig takich a przeksztace? Wyeliminowanie operacji przesunicia mona dokona poprzez wyznaczenie dla obu zbiorw ich rodkw cikoci, a nastpnie zrealizowa takie przesunicie, aby rodki te znalazy si w punkcie (0, 0). Takie podejcie powoduje, e nie zachodzi potrzeba wykonywania innych symetrii osiowych ni o rodku w punkcie (0, 0). Pniejsze wykonywanie obrotw, jednokadnoci oraz symetrii osiowych o rodku w punkcie (0, 0) nie zmienia pooenia rodka cikoci zbioru punktw, zatem znika potrzeba dalszego wykonywania operacji przesuni. W jaki sposb wyeliminowa kolejne operacje? Eliminacja jednokadnoci moe zosta wykonana poprzez odpowiednie przeskalowanie obu zbiorw punktw wzgldem punktu (0, 0) tak, aby ich wielkoci byy rwne (wielko moemy rozumie jako odlego najdalszego punktu od (0, 0)). Po wykonaniu takiego przeskalowania, operacje obrotw oraz symetrii osiowych nie powoduj zmiany a wielkoci zbioru. Przy zaoeniu, e nie musimy wykonywa symetrii osiowej, sprawdzenie, czy dwa zbiory punktw s identyczne z dokadnoci do obrotu, mona sprawdzi sortujc oba zbiory a a a punktw ktowo oraz wyznaczajc dla nich obwdki, na ktrych punkty le w koleja a a noci ruchu wskazwek zegara. Dwa zbiory punktw s identyczne, jeli wyznaczone a obwdki s swoimi rwnowanociami cyklicznymi. Problem symetrii osiowej mona a atwo wyeliminowa, zamieniajc kolejno jednej z obwdek oraz sprawdzajc po raz a a kolejny rwnowano cykliczn. a

10.7. Algebra liniowa


10.7.1. Taniec
Zadanie mona rozwiza w czasie O((x y)3 ). a Nastpienie na kade pole szachownicy, powoduje zmian stanu wiecenia tego pola oraz a pl ssiednich, dwukrotne nastpienie na to samo pole przywraca poprzedni stan poda a wietlenia, zatem na kade pole szachownicy naley nastpi co najwyej raz. Oznacza a to, e rozwizanie zadania sprowadza si do wyznaczenia, dla kadego pola, czy naley a na nie nastpi. a Stan podwietlenia kadego pola jest rwnie binarny zapalone bd nie. Mona si a zatem pokusi o sprowadzenie zadania do rozwizania odpowiedniego ukadu rwna a w ciele Z2 . Jak powinien wyglda taki ukad rwna? a 280

Z kadym polem szachownicy naley zwiza niewiadom, ktra przyjmuje warto 0 a a jeli na pole nie naley nastpi oraz warto 1 w przeciwnym razie. Takie podejcie a tworzy x y niewiadomych. Podobnie, dla kadego pola xi , naley stworzy rwnanie postaci xi + x1 + . . . + xk = c, gdzie x1 , x2 , . . . , xk s niewiadomymi reprezentujcymi a a ssiednie pola xi , natomiast c rwna jest 1, jeli pole xi nie jest na pocztku zapaa a lone, 0 w przeciwnym przypadku. Powstaje w ten sposb ukad x y rwna z x y niewiadomymi, ktre mona rozwiza przy pomocy funkcji GaussZ2. a

10.7.2. Sejf
Zadanie mona rozwiza w czasie O(n3 ). a Idea rozwizania tego zadania jest bardzo zbliona do poprzedniego zadania Taniec. Jea dyna rnica polega na tym, e wartoci wszystkich wspczynnikw oraz niewiadomych s z przedziau {0, 1, p 1} a nie jak poprzednio {0, 1}. a

10.7.3. Szalony malarz


Zadanie mona rozwiza przy uyciu programowania liniowego. a Kademu moliwemu krokowi malowania obrazu mona przypisa niewiadom xi , i a {1, 2, . . . , k}, ktra bdzie przyjmowaa warto 1, jeli krok ten jest wykonywany, oraz warto 0, w przeciwnym przypadku. Kadej niewiadomej xi mona przypisa wag ai , rwn czasowi wykonania odpowiedniego ruchu malowania obrazu. Wyznaczenie listy a ruchw pozwalajcych na pomalowanie obrazu w najkrtszym moliwym czasie, jest a rwnowane zminimalizowaniu wartoci funkcji a1 x1 + a2 x2 + . . . + ak xk . W jaki sposb mona wyznaczy moliwe ruchy malowania oraz jak zapewni, e kady obszar obrazu zostanie pomalowany? Jeli w optymalnym malowaniu obrazu wykonywany jest ruch pionowy bd poziomy, a ktry nie pokrywa wszystkich ssiadujcych pl obrazu o tym samym kolorze, to ruch a a taki mona wyduy nie zmieniajc jednoczenie czasu malowania obrazu. Zatem liczba a rnych ruchw ktre mog wystpowa w optymalnym malowaniu obrazu jest ogrania czona przez 3 m n. Wszystkie te ruchy naley uwzgldni w warunkach dla programowania liniowego W celu zapewnienia aby kady cal kwadratowy obrazu zosta pomalowany, naley dla kadego z nich sformuowa warunek mwicy, e suma zmiennych reprezentujcych a a ruchy malowania obrazu, ktre zawieraj ten obszar, jest nie mniejsza od 1. Ze wzgldu a na charakter wszystkich skonstruowanych w ten sposb warunkw, wartoci wyznaczonych zmiennych przez programowanie liniowe bd nalee do zbioru {0, 1}. O innych a sposobach stosowania programowania liniowego mona przeczyta w [PCC]. Po analizie tej lektury, wykazanie poprawnoci przedstawionego rozwizania powinno by ju a bardzo proste.

281

Dodatek A

Nagwki Eryka Kopczyskiego


Listing A.1: Nagwki Eryka Kopczyskiego na konkurs TopCoder 001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 #include #include #include #include #include #include #include #include #include #include <algorithm> <string> <vector> <ctype.h> <math.h> <iostream> <set> <map> <complex> <sstream>

using namespace std; typedef long long ll; typedef long double ld; typedef vector<int> vi; typedef vector<int> vll; typedef vector<string> vs; typedef complex<ll> cll; typedef complex<ld> cld; #dene Size(x) (int(x.size())) #dene LET(k,val) typeof(val) k = (val) #dene CLC(act,val) (*({act; static typeof(val) CLCR; CLCR = (val); &CLCR;})) #dene FOR(k,a,b) for(typeof(a) k=(a); k < (b); ++k) #dene FORREV(k,a,b) for(typeof(b) k=(b); (a) <= (k);) #dene FIRST(k,a,b,cond) CLC(LET(k, a); for(; k < (b); ++k) if(cond) break, k) #dene LAST(k,a,b,con) CLC(LET(k, b); while((a) <= (k)) if(con) break, k) #dene EXISTS(k,a,b,cond) (FIRST(k,a,b,cond) < (b)) #dene FORALL(k,a,b,cond) (!EXISTS(k,a,b,!(cond))) #dene FOLD0(k,a,b,init,act) CLC(LET(k, a); LET(R##k, init); \ for(; k < (b); ++k) {act;}, R##k) #dene SUMTO(k,a,b,init,x) FOLD0(k,a,b,init,R##k += (x)) #dene SUM(k,a,b,x) SUMTO(k,a,b,(typeof(x)) (0), x) #dene PRODTO(k,a,b,init,x) FOLD0(k,a,b,init,R##k *= (x))

283

Listing A.1: (c.d. listingu z poprzedniej strony) 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 #dene PROD(k,a,b,x) PRODTO(k,a,b,(typeof(x)) (1), x) #dene MAXTO(k,a,b,init,x) FOLD0(k,a,b,init,R##k >?= (x)) #dene MINTO(k,a,b,init,x) FOLD0(k,a,b,init,R##k <?= (x)) #dene QXOR(k,a,b,x) FOLD0(k,a,b,(typeof(x)) (0), R##k = x) #dene QAND(k,a,b,x) FOLD0(k,a,b,(typeof(x)) (-1), R##k &= x) #dene QOR(k,a,b,x) FOLD0(k,a,b,(typeof(x)) (-1), R##k = x) #dene FOLD1(k,a,b,init,act) CLC(LET(k, a); LET(R##k, init); \ for(++k; k < (b); ++k) act, R##k) #dene MAX(k,a,b,x) FOLD1(k,a,b,x, R##k >?= (x)) #dene MIN(k,a,b,x) FOLD1(k,a,b,x, R##k <?= (x)) #dene FIRSTMIN(k,a,b,x) (MIN(k,a,b,make pair(x,k)).second) #dene INF 1000000000 int tcize(int n) {return n<INF ? n : -1;} vi parsevi(string s) { s = s + " "; int q = 0; vi res; FOR(l,0, Size(s)) { if(s[l] == ) { res.push back(q); q = 0;} else { q = q * 10 + s[l] - 0; } } return res; } vs parsevs(string s) { s = s + " "; string q = ""; vs res; FOR(l,0, Size(s)) { if(s[l] == ) { res.push back(q); q = "";} else { q += s[l]; } } return res; } #dene MKey(x) \ typedef typeof(memo) tmemo; tmemo::key type key = (x); \ if(memo.find(key) != memo.end()) return memo[key] #dene MRet(y) return (memo[key] = y) template <class T> T operator | (complex<T> x, complex<T> y) { return (x*conj(y)).real(); } template <class T> T operator (complex<T> x, complex<T> y) {

284

Listing A.1: (c.d. listingu z poprzedniej strony) 083 return (x*conj(y)).imag(); 084 } 085 086 int bitc(ll r) {return r == 0 ? 0 : (bitc(r>>1) + (r&1));} 087 088 ll gcd(ll x, ll y) {return x ? gcd(y%x,x) : y;} 089 090 template<class T> T& operator >?= (T& x, T y) {if(y>x) x=y; return x;} 091 template<class T> T& operator <?= (T& x, T y) {if(y<x) x=y; return x;} 092 template<class T> T operator >? (T x, T y) {return x>y?x:y;} 093 template<class T> T operator <? (T x, T y) {return x<y?x:y;} 094 095 #dene Pa(xy) ((xy).rst) 096 #dene Ir(xy) ((xy).second) 097 098 string cts(char c) {string s=""; s+=c; return s;} 099 100 template<class T> ostream& operator<<(ostream& os, const vector<T>& v) { 101 os << "{"; 102 for(LET(k,v.begin()); k != v.end(); ++k) {os << (*k); os << ",";} 103 os << "}"; 104 return os; 105 } 106 107 template<class T, class U> ostream& operator<<(ostream& os, const pair<T,U>& p) { 108 return os << "(" << p.first << "," << p.second << ")"; 109 } 110 111 template<class T> ostream& operator<<(ostream& os, const set<T>& v) { 112 os << "{"; 113 for(LET(k,v.begin()); k != v.end(); ++k) {os << (*k); os << ",";} 114 os << "}"; 115 return os; 116 } 117 118 #ifdef oyd 119 FOR(k,0,100) FOR(i,0,100) FOR(j,0,100) w0[i][j] <?= w0[i][k] + w0[k][j]; 120 #endif 121 122 #dene BINFIRST(k,a,b,cond) \ 123 CLC( \ 124 LET(k##mIn, a); LET(k##mAx, b); \ 125 while(k##mIn != k##mAx) { \ 126 LET(k, (k##mIn+k##mAx)>>1); \ 127 if(cond) k##mAx = k; \ 128 else k##mIn = k+1; \ 129 }, \

285

Listing A.1: (c.d. listingu z poprzedniej strony) 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 k##mIn \ ) #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene #dene like aint != gotta man please two 2 three 3 four 4 ve 5 six 6 seven 7 eight 8 nine 9 ten 10 eleven 11 dozen 12 dozens *12 enough 999 plus +

286

Dodatek B

Sposoby na sukces w zawodach


W padzierniku 2004 roku, po eliminacjach do zawodw druynowych ACM ICPC na Uniwersytecie Warszawskim, nastpiy istotne zmiany w skadzie druyn zwizane, w gwnym a a stopniu, z odejciem zawodnikw z lat ubiegych. Z uwagi na odnoszone sukcesy poprzednikw, prof. Krzysztof Diks zwrci si do nich z prob o udzielenie swoim modszym kolegom a wskazwek zwizanych ze strategi uczestnictwa i osigania dobrych wynikw w konkursach. a a a W omawianym dodatku zostay zebrane wskazwki oraz dobre rady udzielone przez Tomka Czajk, Andrzeja Gsienic - Samka, Tomasza Malesiskiego, Krzysztofa Onaka oraz Marcina a Stefaniaka. Zgromadzone uwagi i sugestie dotycz przede wszystkim konkursw zespoowych a takich jak ACM ICPC, ale wiele z nich jest tak samo poytecznych, w przypadku innych konkursw. Bez wtpienia, bd one poyteczne dla wielu nie tylko pocztkujcych zawodnikw. a a a a Oto co mwi laureaci konkursw informatycznych: a Tomasz Czajka, trzykrotny zwycizca w konkursie TopCoder, mistrz wiata w Programowaniu Zespoowym 2003. Naley trenowa indywidualnie na http://acm.uva.es/. To najwaniejsze. Startowa wsplnie z kolegami z druyny w konkursach na http://acm.uva.es/contest/ Co, co zmienio nasz druyn nie do poznania: Osoba, ktra zrobia najmniej zaa da w konkursie, stawiaa pozostaym obiad. Jest to banalne ale bardzo wane, gdy bez tego nie ma szans traktowa konkursu powanie i z pen koncentracj. Zrobione a a zadania trzeba liczy tak jak strzelone bramki w pice nonej. Inaczej nikt nie wemie na siebie odpowiedzialnoci robienia trudnego zadania, liczc na innych. Dopuszczalna a jest nawet sytuacja, gdzie dwie osoby konkuruj nad zrobieniem tego samego, przea wanie ostatniego zadania konkursowego. Zdarzao si to nam nie raz i zawsze miao pozytywne skutki. Organizowa sobie symulowane konkursy z zadaniami z byych ACM-w (zadania i testy mona znale na stronie http://icpc.baylor.edu/ ). Naley zrobi sobie skrypt do testowania rozwiza, ktry odpowiada OK, Za odpowied . . . Po zakoczeniu a treningu naley przeprowadzi porwnanie do innych druyn, ktre w danym konkursie startoway. Zasada stawiania obiadw odnosi si rwnie do treningw. Naley omawia w obrbie zespou rne strategie, modykowa je zalenie od dowiadcze. Moliwe s rne podejcia, ale generalnie najlepsza jest strategia dzielimy si a 287

losowo zadaniami, kady rozwizuje jedno na kartce, implementuje na komputerze, wya sya, rozwizuje nastpne, implementuje i tak dalej. Ewentualnie mona odkada zadaa nia, ktre s rodzaju niezbyt lubianego przez siebie, na specjalnie wydzielony wsplny a stosik zada na pniej. Naley wywala zaakceptowane zadania pod st i zapomina o nich do koca konkursu. Naley pisa cae rozwizania zada na kartce. Komputera powinno uywa si tylko a do wpisania gotowego rozwizania i sprawdzenia czy dziaa. Jeli dziaa wysyamy, a jak nie dziaa drukujemy i zwalniamy komputer. Jeli sprawdzanie rozwizania na a serwerze trwa zbyt dugo, to w razie czego naley wydrukowa program, aby o ile okae si, e rozwizanie jest ze, nie blokowa ponownie komputera a Podczas szukania bdu w rozwizaniu, czytamy cay wydruk linijka po linijce, zaznaa czajc wszystkie znalezione bdy. Po 30 - 60 minutach program na kartce powinien a by bezbdny. Siadamy wtedy do komputera i poprawiamy bdy. Jeli okae si, e program dalej nie dziaa, to znowu drukujemy i zwalniamy komputer. Nie wolno rwnoczenie zajmowa komputera i myle. Jeli okae si (nie powinno), e zaczlimy wpisywa program i nie do koca wiemy co dalej, to drukujemy, zwalniamy komputer, dopracowujemy szczegy na kartce i czekamy na swoj kolejk dostpu do a komputera w celu zaimplementowania brakujcej czci. a Nie wolno debugowa przy komputerze. le dziaajcy program trzeba czyta na wya druku do skutku, to znaczy do znalezienia bdu. Do tego trzeba si przyzwyczai. Ewentualny wyjtek: w pewnym momencie pod koniec konkursu moemy podj decya a zj, e pracujemy ju tylko nad jednym zadaniem. Inne denitywnie odkadamy. Dopiero wtedy mona debugowa. Zostawia si komputer piszcemu rozwizanie do penej a a dyspozycji, reszta druyny idzie np. na col, eby mu nie przeszkadza. Nikt nie jest przyzwyczajony do konkursowego trybu pracy, dlatego potrzebny jest wsplny udzia w treningach. Po konkursie naley analizowa jakie byy bdy strategiczne w szczeglnoci czy kto marnowa duo czasu czekajc na komputer. a

Andrzej Gsienica - Samek, Mistrz wiata w Programowaniu Zespoowym 2003 a Zasadniczym kryterium na dobr druyn jest zebranie trzech zawodnikw i rozdzielenie a pomidzy nich zadania do rozwizania na samym pocztku zawodw (przykadowo a a pierwsza osoba otrzymuje zadania A, D, G, druga B, E, H a trzecia C, F) kady z nich stara si rozwiza swoje zadania jak najszybciej. a Dobrym pomysem jest napisanie programu najpierw na kartce. Dopiero gdy rozwizanie a zadania jest ju w peni gotowe, rozpoczyna si prac na komputerze. Nie jest moliwe wygra zawody, jeli ktry z czonkw druyny nie jest w stanie rozwiza samodzielnie 1-2 zada. a Komunikacja midzy czonkami druyny musi by bardzo efektywna. Zawodnicy powinni zna si na tyle dobrze, aby w momencie napotkania na problem szybko ustali kogo powinni zapyta o rad oraz by w stanie niezwocznie przedstawi powstay 288

problem. Bezcelowe jest zadawanie pyta, ktre doprowadz do wcignicia kolegi w a a dusz dyskusj w takiej sytuacji czsto sensowniejsze jest po prostu przekazanie a rozwizywanego zadania komu innemu. a Wygrywa ten kto ma najwikszy cig na bramk. Wysoki poziom techniczny jest a warunkiem koniecznym, ale nie wystarczajcym. Zmagania midzy druynami Uniwera sytetu Warszawskiego w latach 2002-2003 Hawks i Eagles, nie toczyy si na poziomie technicznym, bo ten by stay. Liczy si zesp i tylko w przypadku pracy caego zespou mona wygra. Niezalenie od poziomu technicznego indywidualnych zawodnikw, brak pracy dowolnego zawodnika uniemoliwia zwycistwo. Strategia: na pocztku zadania dzielone s midzy zawodnikw alfabetycznie. Potem a a kady robi swoje, a gdy zrobi wszystkie, bierze nierozpoczte zadanie od kolegi. Tylko w bardzo wyjtkowych sytuacjach mona wymienia zadania. Zadania s na tyle proste, a a e praktycznie nie ma specjalistw od okrelonej dziedziny kady moe zrobi kade zadanie. Kady powinien niezalenie realizowa swoje zadania od A do Z tylko to buduje odpowiedzialno za zadania. Po kadym treningu naley policzy ile zada kto rozwiza. Bardzo dobrym pomysem a jest, aby osoba, ktra zrobia najmniejsz liczb zada stawiaa kadorazowo obiad. W a przypadku, gdy ktremu z zawodnikw to nie odpowiada, gdy on zawsze, w takim ukadzie, bdzie stawia obiad, to lepiej wymieni takiego zawodnika na dowolnego bardziej zmotywowanego. Pojcie kapitana zespou jest bez sensu. Trzyosobowy zesp powinien rozumie si bez sw. Jeli tak nie jest, to nie ma szans na zwycistwo. Regularne treningi s bardzo potrzebne. Jeli kto nie ma na nie czasu lub ochoty, to a lepiej go wymieni. Naley wyznacza sobie wysokie cele. Jeli komu zaley tylko na pokonaniu innych druyn z uczelni, to jest to za mao. Jedyny sensowny cel to zwycistwo w caym konkursie. Mniej ambitne cele atwiej si realizuje, ale niewiele si dziki temu osiga. a Niech kady pomyli sobie jaki jest jego cel, gdy mam wraenie, e dla wikszoci druyn by nim sam wyjazd na nay.

Tomasz Malesiski, brzowy medalista Mistrzostw wiata w Programowaniu Zespoowym a 2004 Treningi nie powinny by smutnym obowizkiem. Po kadym treningu, niezalenie od a jego wynikw, druyna jest lepsza, bardziej zgrana i blisza celu. W czasie treningw sprawdza si przydatno wskazwek starszych kolegw i dopracowuje strategi. Wana jest motywacja, wiara we wasne siy i uparte denie do celu. W naach atwo a jest pokona kilkadziesit druyn, ale prawdziwa gra toczy si midzy kilkunastoma a najlepszymi. Mona z nimi wygra, ale nie przyjdzie to atwo. Komunikacj naley ograniczy do minimum. Na pocztku konkursu naley podzieli a si rwno zadaniami, kady czonek druyny zapoznaje si ze swoimi i rozwizuje je a sam w kolejnoci od najatwiejszego do najtrudniejszego. 289

Nie naley myle nad rozwizaniem zadania zanim go si dokadnie nie zrozumie. a Nie powinno si take pisa kodu, dopki nie ma pewnoci co do metody rozwizania a zadania. Nie wysya si te rozwizania, bez upewnienia si, e jest poprawne. Naley a pracowa samodzielnie, nie przeszkadzajc innym. Zadanie moe by odoone tylko a wwczas, gdy stwarza due problemy i czas pozwala na rozpoczcie innego od nowa. Jeli komputer jest zajty, kod naley pisa na kartce. Gdy przepisuje si kod do komputera, naley jeszcze raz dokadnie przeczyta, aby nie byo pomyek (przy okazji poprawi). W przypadku, gdy program nie dziaa, naley zwolni klawiatur i szuka dalej bdw na wydruku. Debugujc przy komputerze naley sprbowa wypisa wyniki czciowe a dziaania programu. Zwykle jest to skuteczniejsze od wykonywania programu krok po kroku, a dodatkowo mona te wyniki wydrukowa i przeanalizowa nie zajmujc koma putera. Kilka gotowcw warto mie, ale tak naprawd, to rzadko si one przydaj. Licz si a a umiejtnoci. Gotowce tylko je uzupeniaj. a Pod koniec konkursu zasady nieco si zmieniaj. Naley oceni, ktre zadania mona a jeszcze dokoczy i wwczas pracuje si razem. Kady zawodnik zapoznaje si z treci a zadania, otrzymujc wydruk biecej wersji programu. Mona wtedy wysya rozwizania, a a a nawet gdy s pewne wtpliwoci, co do ich poprawnoci, tylko trzeba mie wiadomo, a a e desperackie prby zaliczenia zadania rzadko skutkuj. a Trzeba umie sobie radzi z bdami. Bd staje si grony dopiero wtedy, gdy powoduje a rezygnacj z dalszej walki. Nie naley zapomina o odpoczynku. Chwila relaksu uatwia spojrzenie na problem z innej strony i zazwyczaj pozwala na znalezienie jakiego rozwizania. a

Krzysztof Onak, Mistrz wiata w Programowaniu Zespoowym 2003 Kady zawodnik w grupie musi dawa z siebie jak najwicej, nie liczc na pozostaych a i walczy tak, jakby wszystko zaleao wycznie od niego. a Nie naley ba si odpowiedzialnoci. Jeeli zadanie jest proste, naley je rozwiza, nie a czekajc na innych. a Zadanie, ktre si zaczyna naley docign do koca, ale te trzeba mie odwag a a zrezygnowa, gdy naprawd nie wychodzi. Naley nauczy si debugowa na kartce. W ten sposb oszczdza si czas, a ponadto czytajc program na papierze, czsto wychwytuje si bdy, ktre byoby trudno zdea bugowa. Jeeli co nie dziaa, to przede wszystkim naley zakada, e bd tkwi po stronie zaa wodnika, a nie po stronie jury. Wwczas atwiej zmobilizowa si do wikszego wysiku. Nie naley dyskutowa za wiele w czasie konkursu. Warto skoncentrowa si wycznie a na swoim zadaniu. Na pocztku konkursu najlepiej losowo podzieli si zadaniami i kady rozwizuje swoje. a a Oczywicie zadaniami mona si wymienia. W kocwce odrzucie zadania, co do ktrych wiecie, e ich nie zrobicie i by moe wtedy rozwizujcie wsplnie jakie zadanie. a 290

Musicie nabra pewnoci siebie i wiedzie, e moecie na sobie polega. Musicie dobrze czu si w zespole. Nie ufajcie do koca tym zasadom. Znajdcie co, co jest dobre w przypadku Waszego zespou.

Marcin Stefaniak, srebrny medalista Mistrzostw wiata w Programowaniu Zespoowym 2001 Nie uywaj debuggera. Jeli co nie dziaa, naley patrze na wydruk programu, a dostpi si owiecenia. Drukuj zadanie za kadym razem, gdy jest wysyane do oceny, a nawet jeli okae si, e rozwizanie byo poprawne, to papier przyda si do czego a innego. Bdcie biegli w tym, jak dziaa Wejcie/Wyjcie w jzykach konkursu (dokadnie w a tej wersji kompilatora jaka bdzie uywana), jak efektywnie (wane dla duych wej i wyj), jakie ma bdy i nietypowe cechy. Jest to w gruncie rzeczy jedyna biblioteka typowo informatyczna (czyli zasadniczo niedoskonaa i niepewna) z ktrej musicie korzysta. Zabierzcie na zawody papier w kratk, papier w heksy ju wyszed z mody. Po zaliczonym zadaniu, przybijajcie sobie donie, najlepiej haasujc na tyle gono, aby a zdeprymowa druyny w pobliu. Nie patrzcie na ranking! To zazwyczaj przynosi pecha. Pamitajcie, e aby wygra nay, naley wpierw wygra konkurs regionalny. Nie mwic a ju o zawodach oglnopolskich. Wypracujcie wasny, efektywny styl pracy i podzia zada. Nie dajcie sobie narzuci czego z zewntrz; kada druyna jest inna i nie ma tu jednakowej recepty. Nie wszyscy a przecie musz lubi np. geometri obliczeniow. a a Zdarza si e zadania maj nieprawidowe testy i treci. Zdarza si, ze komputery naa walaj. Mimo to walczcie! Moe to wanie wam dopisze szczcie i jeszcze na tym a skorzystacie. Nie musicie zrobi wszystkich zada, wystarczy wicej ni inni.

291

Dodatek C

Zbir zada na programowanie dynamiczne


. Literatura [ASD] - 1.8.2 [WDA] - 16

Proste acm.uva.es - zadanie 348 acm.uva.es - zadanie 674 acm.uva.es - zadanie 10003 acm.uva.es - zadanie 10081 acm.uva.es - zadanie 10131 acm.uva.es - zadanie 10198 acm.uva.es - zadanie 10259 acm.uva.es - zadanie 10271 acm.uva.es - zadanie 10304 acm.uva.es - zadanie 10482 acm.uva.es - zadanie 10529 acm.uva.es - zadanie 10811 spoj.sphere.pl - zadanie 346 spoj.sphere.pl - zadanie 365 spoj.sphere.pl - zadanie 394 spoj.sphere.pl - zadanie 402 acm.sgu.ru - zadanie 104

rednie acm.uva.es - zadanie 531 acm.uva.es - zadanie 562 acm.uva.es - zadanie 10069 acm.uva.es - zadanie 10201 acm.uva.es - zadanie 10280 acm.uva.es - zadanie 10296 acm.uva.es - zadanie 10400 acm.uva.es - zadanie 10405 acm.uva.es - zadanie 10549 acm.uva.es - zadanie 10558 acm.uva.es - zadanie 10930 spoj.sphere.pl - zadanie 292 spoj.sphere.pl - zadanie 338 spoj.sphere.pl - zadanie 345 spoj.sphere.pl - zadanie 348 spoj.sphere.pl - zadanie 364 acm.sgu.ru - zadanie 183 acm.sgu.ru - zadanie 269 acm.sgu.ru - zadanie 304

wiczenia

Trudne acm.uva.es - zadanie 116 acm.uva.es - zadanie 147 acm.uva.es - zadanie 357 acm.uva.es - zadanie 366 acm.uva.es - zadanie 711 acm.uva.es - zadanie 10032 acm.uva.es - zadanie 10154 acm.uva.es - zadanie 10157 acm.uva.es - zadanie 10261 spoj.sphere.pl - zadanie 350 spoj.sphere.pl - zadanie 366 spoj.sphere.pl - zadanie 388 acm.sgu.ru - zadanie 132 acm.sgu.ru - zadanie 278

293

Dodatek D

Zbir zada na programowanie zachanne


. Literatura [ASD] - 1.8.3 [WDA] - 17

Proste acm.uva.es - zadanie 120 acm.uva.es - zadanie 10020 acm.uva.es - zadanie 10249 acm.uva.es - zadanie 10440 acm.uva.es - zadanie 10821 acm.uva.es - zadanie 10954 acm.uva.es - zadanie 10965 spoj.sphere.pl - zadanie 661

rednie acm.uva.es - zadanie 10461 acm.uva.es - zadanie 10563 acm.uva.es - zadanie 10716 acm.uva.es - zadanie 10720 acm.uva.es - zadanie 10785 acm.uva.es - zadanie 10982 spoj.sphere.pl - zadanie 247 acm.sgu.ru - zadanie 259

wiczenia

Trudne acm.uva.es - zadanie 410 acm.uva.es - zadanie 714 acm.uva.es - zadanie 10382 acm.uva.es - zadanie 10665 acm.uva.es - zadanie 11006 spoj.sphere.pl - zadanie 417 acm.sgu.ru - zadanie 257

295

Bibliograa
[WDA] Wprowadzenie do algorytmw, Thomson H. Cormen, Charles E. Leiserson, Ronald L. Rivest, WNT, 2004 [ASD] Algorytmy i struktury danych, L. Banachowski, K. Diks, W. Rytter, WNT, 2003 [KDP] Kombinatoryka dla programistw, W. Lipski, WNT, 2004 [SZP] Sztuka Programowania, Donald E. Knuth, WNT, 2002 [ASP] Algorytmy + struktury danych = programy, Niklaus Wirth, WNT, 2001 [RII] Rzecz o istocie informatyki: Algorytmika, D. Harel, WNT, 2000 [MD] Matematyka dyskretna, K. A. Ross, C. R. B. Wright, PWN, 2000 [MK] Matematyka konkretna, R. L. Graham, D. E. Knuth, O. Patashnik, PWN, 1998 [TLK] Wykad z teorii liczb i kryptograi, N. Koblitz, WNT, 2006 [OI] Olimpiada Informatyczna (http://www.oi.edu.pl/) [OI1] I Olimpiada Informatyczna 1993/1994. Warszawa - Wrocaw, 1994 [OI2] II Olimpiada Informatyczna 1994/1995. Warszawa - Wrocaw, 1995 [OI3] III Olimpiada Informatyczna 1995/1996. Warszawa, 1996 [OI4] IV Olimpiada Informatyczna 1996/1997. Warszawa, 1997 [OI5] V Olimpiada Informatyczna 1997/1998. Warszawa, 1998 [OI6] VI Olimpiada Informatyczna 1998/1999. Warszawa, 1999 [OI7] VII Olimpiada Informatyczna 1999/2000. Warszawa, 2000 [OI8] VIII Olimpiada Informatyczna 2000/2001. Warszawa, 2001 [OI9] IX Olimpiada Informatyczna 2001/2002. Warszawa, 2002 [OI10] X Olimpiada Informatyczna 2002/2003. Warszawa, 2003 [OI11] XI Olimpiada Informatyczna 2003/2004. Warszawa, 2004 [OI12] XII Olimpiada Informatyczna 2004/2005. Warszawa, 2005 [SGU] Saratov State University :: Online Contester (http://acm.sgu.ru/) 297

[UVA] Valladolid Programming Contest Site (http://acm.uva.es/) [SPO] Sphere Online Judge (http://spoj.sphere.pl/) [SGU] Saratov State University :: Online Contester (http://acm.sgu.ru/) [PCH] Programming Challenges: The Programming Contest Training Manual, S. S. Skiena, M. A. Revilla, Springer - Verlag New York, Inc., 2003 [STL] Standard Template Library Programmers Guide (http://www.sgi.com/tech/stl/) [GCC] GCC home page (http://gcc.gnu.org/) [ECP] Eective C++, Third Edition, S. Meyers, Addison - Wesley (2005) [LCA] The LCA Problem Revisited, M. A. Bender, (http://www.cs.sunysb.edu/ bender/pub/lca.ps) M. Farach Coltion.

[AKS] Primes is in P, M. Agrawal, N. Kayal, N. Saxena, Annals of Mathematics 160(2): 781-793 (2004). (http://www.cse.iitk.ac.in/users/manindra/) [PCC] Polyhedral Combinatorics and Combinatorial Optimization, A. Schrijver, CWI and University of Amsterdam (http://homepages.cwi.nl/ lex/) [NRP] Numerical Recipes in C, The Art of Scientic Computing, W. H. Press, S. A. Teukolsky, W. T. Vetterling, B. P. Flannery, Cambridge University Press [EAT] Ecient Algorithms on Texts, M. Crochemore, W. Rytter [CON] Combinatorial Optimization : Networks and Matroids, E. Lawler, Holt, Rinehart and Winston, 1976 [CST] On - line Construction of Sux Trees, E. Ukkonen, Algorithmica, 14(3) pp249-260, 1995 [BST] Randomized Binary Search Trees, C. Martinze, S. Roura, Journal of the ACM, Vol. 45, No. 2, March 1998, pp 288-323

298

Indeks
acykliczno, 39 algorytm AKS, 151 Bakera, 207 Bellmana - Forda, 61 Dijkstry, 57 Dinica, 64 Duvala, 224 Euklidesa, 139 Grahama, 110 Hopcrofta - Karpa, 78 Knutha - Morrisa - Pratta, 203 Kruskala, 54 Manachera, 213 Millera - Rabina, 151 Prima, 54 rozszerzony Euklidesa, 139 sita, 148 sympleks, 238 arytmetyka modularna, 137, 231 wielkich liczb cakowitych, 162 naturalnych, 154 test pierwszoci, 166 wymiernych, 164 BFS, 18 bdy zaokrgle, 87 a cykl Eulera, 48 DFS, 24 drzewa AVL, 176 czarno - czerwone, 176 dynamicznie alokowane, 177, 188 licznikowe, 177, 180 maksimw, 176, 177 pokryciowe, 177, 185 pozycyjne, 177, 182 randomizowane, 177, 196 statyczne, 176 wzbogacane, 177, 192 zrwnowaone, 176 drzewo najkrtszych cieek, 57 poszukiwa binarnych, 175 przeszukiwa w gb, 24 a przeszukiwa wszerz, 18 rozpinajce, 54 a minimalne, 54 dwumian Newtona, 137 dwuspjna skadowa, 42 dzielnik, 139 eliminacja Gaussa, 231 funkcja Ackermana, 172 preksowa, 206, 207 geometria obliczeniowa, 87 graf, 13 acykliczny, 35, 39 dwudzielny, 73 indukowany, 14 nieskierowany, 13 reprezentacja, 14 silnie spjny, 29 skierowany, 13 iloczyn wektorowy, 90 kongruencja, 144 koo reprezentacja, 87 krawd grafu, 13 drzewowa, 18 multikrawd, 13 nasycona, 64 niedrzewowa, 18 nienasycona, 64 299

potgowanie modularne, 146 programowanie dynamiczne, 123, 293 liniowe, 238 zachanne, 295 prosta liczba Carmichaela, 152 reprezentacja, 87 liczby pierwsze przepustowo sieci, 64 gsto zbioru, 149 przeszukiwanie grafu lista, 149 w gb, 24 a sito, 148 wszerz, 18 test, 151 przynaleno punktu test randomizowany, 151 do koa, 95 do odcinka, 94 maksymalne skojarzenie, 73 do prostokta, 94 a maksymalny niezaleny zbir wierzchokw, do wielokta, 96 a 273 do wielokta wypukego, 98 a maksymalny przepyw, 64 punkt jednostkowy, 68 artykulacji, 42 najtaszy, 70 przecicia metoda wgierska, 82 odcinkw, 101 minimalne drzewo rozpinajce, 54 a okrgw, 105 most, 42 okrgu i prostej, 103 multikrawd, 13 prostych, 101 reprezentacja, 87 najwikszy wsplny dzielnik, 139 punkty najblisze, 117 obiekt geometryczny, 87 najdalsze, 112 odcinek reprezentacja, 87 silnie spjna skadowa, 29 odlego punktu skojarzenie od prostej, 91 doskonae, 73 odwrotno modularna, 142 maksymalne, 73, 76 okrg a najdrosze, 82 reprezentacja, 87 sortowanie okres sowa, 206 ktowe, 114 a otoczka wypuka, 110 topologiczne, 31, 35 STL, 9, 10 ptla w grae, 13 nieskierowana, 13 poprzeczna, 19 powrotna, 19 skierowana, 13 w przd, 19 permutacja antyleksykogracznie, 123 minimalna transpozycja, 125 podgraf, 14 podzbiory k-elementowe, 131 wszystkie, 129 podzia liczby, 134 zbioru, 132 porzdek topologiczny, 35 a cieka, 14 Eulera, 48 naprzemienna, 76 poszerzajca, 64, 68 a tablica preksowa, 206 test Millera, 152 pierwszoci liczby, 151 transpozycja, 125 ssiednia, 127 a 300

twierdzenie chiskie o resztach, 144 Fermata, 152 ujcie przepywu, 68 wielokt a powierzchnia, 92 reprezentacja, 87 wypuky, 92 wierzchoek grafu, 13 zadanie acuch, 168 Akcja komandosw, 107 Bilard, 141 Drogi, 34 Kodowanie permutacji, 184 Komiwojaer Bajtazar, 28 Krtki program, 255 Liczby antypierwsze, 266 Liczby permutacyjnie - pierwsze, 128 Linie autobusowe, 53 Marsjaskie mapy, 191 Mapki, 174 MegaCube, 212 Mrwki i biedronka, 22 Otarze, 116 Palindromy, 215 Pomniki, 113 Przemytnicy, 60 Punkty, 228 Puste prostopadociany, 201 Sejf, 237 Skoczki, 80 Spokojna komisja, 38 Szablon, 205 Szalony malarz, 242 Taniec, 234 Wirusy, 41 Wyliczanka, 145 rdo przepywu, 68

301

You might also like