Professional Documents
Culture Documents
Piotr Staczyk
Nr albumu: 209291
Praca wykonana pod kierunkiem dra hab. Krzysztofa Diksa Instytut Informatyki
Czerwiec 2006
Data
Data
Streszczenie
Brak
Sowa kluczowe
Brak
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
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
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.
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.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.
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
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; }
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
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.
0: 1: 2: 3: 4: 5:
= = = = = =
-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
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
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
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)
0: 1: 2: 3: 4: 5: 6:
= = = = = = =
-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)
0: 1: 2: 3: 4: 5: 6:
= = = = = = =
0, 1, 4, 7, 5, 9, 8,
= = = = = = =
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 }
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
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
rednie acm.uva.es - zadanie 871 acm.uva.es - zadanie 10802 spoj.sphere.pl - zadanie 38 spoj.sphere.pl - zadanie 372
wiczenia
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, >}; 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
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:
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
6 7
wiczenia
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
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
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
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
38
wiczenia
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
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.
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
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
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 }
wiczenia
0 2 1 6
3 4 5
0 2 1 6
3 4 5
(a)
(b)
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 }
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
Proste acm.uva.es - zadanie 117 acm.uva.es - zadanie 10129 acm.sgu.ru - zadanie 101
wiczenia
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
Proste acm.uva.es - zadanie 534 acm.uva.es - zadanie 10034 spoj.sphere.pl - zadanie 368
wiczenia
Trudne acm.uva.es - zadanie 10147 spoj.sphere.pl - zadanie 148 acm.sgu.ru - zadanie 206
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.36: Wynik wywoania funkcji void Graph<V,E>::Dijkstra(0) dla grafu przedstawionego na rysunku 2.10
0: 1: 2: 3: 4: 5:
od od od od od od
= = = = = =
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
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
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
0: 1: 2: 3: 4:
od od od od od
= = = = =
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
wiczenia
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 }
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)
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 }
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.
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
wiczenia
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.
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)
0 1 2 3 4 5 6 7 8
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 }
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
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 }
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
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
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
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
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
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
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
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
Listing 3.9: Odlegoci poszczeglnych punktw z rysunku 3.1 od prostej y = x 1, wyznaczone przy uyciu funkcji double PointLineDist(POINT&, POINT&, POINT&)
(-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}
wiczenia
92
Y (2, 6) (-3, 4)
(2, 6)
(8, 7)
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>&)
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
wiczenia
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
(4, 8) (7, 7)
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
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)
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)
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
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 }
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=
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
(-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)
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 }
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
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
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
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 }
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)
(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.
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 }
(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.
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
113
Proste acm.uva.es - zadanie 10078 acm.uva.es - zadanie 109 acm.uva.es - zadanie 10065
wiczenia
Trudne spoj.sphere.pl - zadanie 228 acm.sgu.ru - zadanie 277 acm.uva.es - zadanie 10135 acm.sgu.ru - zadanie 290
114
(-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
wiczenia
Y (-7, 5)
(4, 8) d (7, 4)
(-3, 4)
(-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
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 }
wiczenia
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
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 }
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
127
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 }
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
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
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 }
wiczenia
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 }
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 }
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
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.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}
wiczenia
138
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:
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&)
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
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
wiczenia
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
5 1
1 4 3
3
4 3
2 5
1 4
1 3 5
2 4
2 3 4
2
5
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.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}
wiczenia
148
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}
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}
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 }
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
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
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
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 };
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 };
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)
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
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.
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 };
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
= 3 = 4 = 4 = 4
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
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
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).
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
wiczenia
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.
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
wiczenia
Trudne
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
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
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
184
wiczenia
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
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] =
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
wiczenia
Trudne
187
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 };
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 }
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
Proste
wiczenia
Trudne
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 };
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;
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 }
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
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.
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 }
KMP(const
char*, const
char*,
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
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
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.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}
(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
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
rednie acm.uva.es - zadanie 11019 spoj.sphere.pl - zadanie 263 acm.uva.es - zadanie 10298
wiczenia
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
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
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.
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
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 }
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 }
(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.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}
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 };
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
wiczenia
Trudne
224
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}
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 }
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 }
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
1 5 5 0 0 1
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.
231
A=
a0,0 a1,0 . . .
a0,1 a1,1 . . .
... ... .. .
a0,n1 a1,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=
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 .
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 }
233
Listing 8.2: Przykad wykorzystania funkcji int GaussZ2(vector< vector <unsigned int> >, VI, VI&)
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
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
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;
4 x + 0 x + 14 x2 = 1
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
wiczenia
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
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;
oraz funkcji celu postaci 5 x0 1.5 x1 + 0.1 x2 wyznaczony wynik jest przedstawiony na listingu 8.8.
Listing 8.8: BRAK
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
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
2 2 4 2 3
a a b c b
wiczenia
243
Rozdzia 9
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
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.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 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
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
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
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.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
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.
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
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.
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.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.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.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
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.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.
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
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
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
281
Dodatek A
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
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
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
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