Sunteți pe pagina 1din 519

ALGORITMI SI STRUCTURI DE DATE Note de curs

(uz intern - draft v2.3)

Prefat a
Cnd dorim s reprezentm obiectele din lumea real a a a a ntr-un program pe calculator, trebuie s avem vedere: a n modelarea obiectelor din lumea real sub forma unor entiti matematice a at abstracte i tipuri de date, s operatiile pentru nregistrarea, accesul i utilizarea acestor entiti, s at reprezentarea acestor entiti memoria calculatorului, i at n s algoritmii pentru efectuarea acestor operatii. Primele dou elemente sunt esent de natur matematic i se refer la a n a a a s a ce structuri de date i operatii trebuie s folosim, iar ultimile dou elemente s a a implic faza de implementare i se refer la cum s realizm structurile de date a s a a a i operatiile. Algoritmica i structurile de date nu pot separate. Dei algoritmica s s s i programarea pot separate, noi nu vom face acest lucru, ci vom implementa s algoritmii ntr-un limbaj de programare (Pascal, C/C++, Java). Din aceast cauz a a acest curs este i o initiere algoritmic i programare. s n as Scopul cursului este subordonat scopului specializrii (informatic, cazul a a n nostru) care este s pregteasc specialiti competenti, cu a a a s nalt calicare a n domeniul informaticii, cadre didactice competente acest domeniu (profesor de n informatic gimnaziu i liceu), informaticieni diverse domenii cu prol tehnic, a n s n economic, etc. ce pot ncepe lucrul imediat dup absolvirea facultii.Dezideratul a at nal este deci competenta. Competenta ntr-un domeniu de activitate implic a experient rezolvarea problemelor din acel domeniu de activitate. Att a n a competenta ct i experienta rezolvarea problemelor se pot obtine numai dac a s n a permanent se ntreprind eforturi pentru sirea de noi cunotinte. De exemplu, nsu s orice informatician (programator sau profesor) care elaboreaz programe pentru a rezolvarea unor probleme diverse, trebuie s aib competente conform schemei1 : a a
PROBLEMA (model fizic)

ALGORITMICA (model virtual)

PROGRAMARE

Gandire algoritmica

Experienta (rezolvarea de probleme)

Cursul de Algoritmi i structuri de date este util (i chiar necesar) pentru s s formarea competentelor i abilitilor unui bun programator sau profesor de s at informatic. Pentru a vedea care sunt aceste competente i abiliti putem, de a s at
1 M. Vlada; E-Learning i Software educational; Conferinta National de aamnt Virtual, s a Invt a Bucureti, 2003 s

exemplu, s citim Programa pentru informatic - Concursul national unic pentru a a ocuparea posturilor didactice declarate vacante aamntul preuniversitar.2 n nvt a Intr-un fel, primul semestru al cursului Algoritmi i structuri de date este s echivalent cu ceea ce se pred la informatic clasa a IX-a iar al doilea semestru cu a a n clasa a X-a (specializarea: matematic-informatic, intensiv informatic). Diferenta a a a este dat primul rnd de dicultatea problemelor abordate de ctre noi cadrul a n a a n acestui curs. Din aceast cauz vom avea vedere i ce prevede Pograma olar a a n s s a pentru clasa a IX-a, Prol real, Specializarea: Matematic-informatic, intensiv a a informatic. De asemenea, merit s vedem ce preri au cei care au terminat de a a a a curnd o facultate cu un prol de informatic i care au un a a s nceput de carier a reuit. Vom elege de ce acest curs este orientat pe rezolvarea de probleme. s nt Alegerea limbajului Java pentru prezentarea implementrilor algoritmilor a a fost fcut din cteva considerente. Java veric validitatea indicilor tablourilor a a a a (programele nu se pot termina printr-o violare de memorie sau eroare de sistem). Java realizeaz gestiunea automat a memoriei (recupereaz automat memoria a a a care nu mai este necesar programului) ceea ce simplic scrierea programelor a a i permite programatorului s se concentreze asupra esentei algoritmului. Exist s a a documentatie pe internet. Compilatorul de Java este gratuit. Un program scris n Java poate executat pe orice calculator (indiferent de arhitectur sau sistem de a operare). Studentii nu sunt obligati s realizeze implementrile algoritmilor Java; a a n ei pot folosi Pascal sau C/C++. Algoritmii prezentati curs sunt descrii limbaj n s n natural sau limbaj algoritmic iar implementrile sunt limbajul de programare n a n Java. Java este un limbaj orientat-obiect, dar noi vom utiliza foarte putin aceast a particularitate. Sunt prezentate toate elementele limbajului de programare Java necesare pentru acest curs dar ecesta nu este un curs de programare Java. n Cunotintele minimale acceptate la sfritul cursului rezult din Legea nr. s as a 288 din 24 iunie 2004 privind organizarea studiilor universitare i, de exemplu, s din Ghidul calitii aamntul superior3 . Aici se precizeaz faptul c diploma at n nvt a a a de licent se acord unui absolvent al programului de studii care: demonstreaz a a a acumulare de cunotinte i capacitatea de a elege aspecte din domeniul s s nt de studii care s-a format, poate folosi att cunotintele acumulate precum n a s i capacitatea lui de elegere a fenomenelor printr-o abordare profesional s nt a domeniul de activitate, a acumulat competente necesare demonstrrii, n a argumentrii i rezolvrii problemelor din domeniul de studii considerat, i-a a s a s dezvoltat deprinderi de aare necesare procesului de educatie continu. nvt a

prin O.M:Ed.C. nr.5287/15.11.2004 Universitii din Bucureti, 2004; Capitolul 4, Calitatea programelor de studii uniat s versitare, Prof.univ.dr. Gabriela M. Atanasiu - Universitatea Tehnic Gh.Asachi din Iai a s
3 Editura

2 Aprobat a

Cuprins
1 Notiuni fundamentale 1.1 Programe ciudate . . . . . . . . . . . . . 1.1.1 Un program ciudat Pascal . . n 1.1.2 Un program ciudat C++ . . . n 1.1.3 Un program ciudat Java . . . n 1.1.4 Structura unui program Java . . 1.2 Conversii ale datelor numerice . . . . . 1.2.1 Conversia din baza 10 baza 2 . n 1.2.2 Conversia din baza 2 baza 10 . n 1.2.3 Conversii ntre bazele 2 i 2r . . s 2 Structuri de date 2.1 Date i structuri de date . . . . . . . . s 2.1.1 Date . . . . . . . . . . . . . . . 2.1.2 Structuri de date . . . . . . . . 2.2 Structuri i tipuri de date abstracte . . s 2.2.1 Structuri de date abstracte . . 2.2.2 Tipuri de date abstracte . . . . 2.3 Structuri de date elementare . . . . . 2.3.1 Liste . . . . . . . . . . . . . . . 2.3.2 Stive i cozi . . . . . . . . . . . s 2.3.3 Grafuri . . . . . . . . . . . . . 2.3.4 Arbori binari . . . . . . . . . . 2.3.5 Heap-uri . . . . . . . . . . . . . 2.3.6 Structuri de multimi disjuncte 3 Algoritmi 3.1 Etape rezolvarea problemelor . n 3.2 Algoritmi . . . . . . . . . . . . . 3.2.1 Ce este un algoritm? . . . 3.2.2 Proprietile algoritmilor at 3.2.3 Tipuri de prelucrri . . . a v . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 2 3 4 5 5 6 6 7 7 7 9 10 10 10 11 11 12 13 14 15 16 17 17 18 18 20 20

3.3

3.4

3.5

Descrierea algoritmilor . . . . . . . . . . . . . . . 3.3.1 Limbaj natural . . . . . . . . . . . . . . . 3.3.2 Scheme logice . . . . . . . . . . . . . . . . 3.3.3 Pseudocod . . . . . . . . . . . . . . . . . Limbaj algoritmic . . . . . . . . . . . . . . . . . . 3.4.1 Declararea datelor . . . . . . . . . . . . . 3.4.2 Operatii de intrare/ieire . . . . . . . . . s 3.4.3 Prelucrri liniare . . . . . . . . . . . . . . a 3.4.4 Prelucrri alternative . . . . . . . . . . . a 3.4.5 Prelucrri repetitive . . . . . . . . . . . . a 3.4.6 Subalgoritm . . . . . . . . . . . . . . . . . 3.4.7 Probleme rezolvate . . . . . . . . . . . . . 3.4.8 Probleme propuse . . . . . . . . . . . . . Instructiuni corespondente limbajului algoritmic 3.5.1 Declararea datelor . . . . . . . . . . . . . 3.5.2 Operatii de intrare/ieire . . . . . . . . . s 3.5.3 Prelucrri liniare . . . . . . . . . . . . . . a 3.5.4 Prelucrri alternative . . . . . . . . . . . a 3.5.5 Prelucrri repetitive . . . . . . . . . . . . a 3.5.6 Subprograme . . . . . . . . . . . . . . . . 3.5.7 Probleme rezolvate . . . . . . . . . . . . . 3.5.8 Probleme propuse . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

20 21 22 22 23 23 23 24 24 25 26 27 30 32 32 34 35 35 35 36 37 52 55 55 57 57 58 58 60 61 62 62 62 62 63 64 66 67 67 69

4 Analiza complexitii algoritmilor at 4.1 Scopul analizei complexitii . . . . . . . . . . . . . . . . at 4.1.1 Complexitatea spatiu . . . . . . . . . . . . . . . 4.1.2 Complexitatea timp . . . . . . . . . . . . . . . . 4.2 Notatia asimptotic . . . . . . . . . . . . . . . . . . . . a 4.2.1 Denire i proprieti . . . . . . . . . . . . . . . s at 4.2.2 Clase de complexitate . . . . . . . . . . . . . . . 4.2.3 Cazul mediu i cazul cel mai defavorabil . . . . . s 4.2.4 Analiza asimptotic a structurilor fundamentale a 4.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Calcularea maximului . . . . . . . . . . . . . . . 4.3.2 Sortarea prin selectia maximului . . . . . . . . . 4.3.3 Sortarea prin insertie . . . . . . . . . . . . . . . . 4.3.4 Sortarea rapid (quicksort) . . . . . . . . . . . . a 4.3.5 Problema celebritii . . . . . . . . . . . . . . . . at 4.4 Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4.1 Probleme rezolvate . . . . . . . . . . . . . . . . . 4.4.2 Probleme propuse . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

5 Recursivitate 5.1 Functii recursive . . . . . . . 5.1.1 Functii numerice . . . 5.1.2 Functia lui Ackerman 5.1.3 Recursii imbricate . . 5.2 Proceduri recursive . . . . . .

. . . . .

. . . . .

. . . . .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

71 71 71 74 74 75 77 77 78 78 80 80 81 82 84 87 93 93 93 94 95 95 95 96 97 97 98 98 98 100 100 100 101 101 102 104 104 105 106 107 107 107

6 Analiza algoritmilor recursivi 6.1 Relatii de recurent . . . . . . . . a 6.1.1 Ecuatia caracteristic . . . a 6.1.2 Solutia general . . . . . . a 6.2 Ecuatii recurente neomogene . . . 6.2.1 O form simpl . . . . . . . a a 6.2.2 O form mai general . . . a a 6.2.3 Teorema master . . . . . . 6.2.4 Transformarea recurentelor 6.3 Probleme rezolvate . . . . . . . . .

7 Algoritmi elementari 7.1 Operatii cu numere . . . . . . . . . . . . . . . . 7.1.1 Minim i maxim . . . . . . . . . . . . . s 7.1.2 Divizori . . . . . . . . . . . . . . . . . . 7.1.3 Numere prime . . . . . . . . . . . . . . 7.2 Algoritmul lui Euclid . . . . . . . . . . . . . . . 7.2.1 Algoritmul clasic . . . . . . . . . . . . . 7.2.2 Algoritmul lui Euclid extins . . . . . . . 7.3 Operatii cu polinoame . . . . . . . . . . . . . . 7.3.1 Adunarea a dou polinoame . . . . . . . a 7.3.2 Inmultirea a dou polinoame . . . . . . a 7.3.3 Calculul valorii unui polinom . . . . . . 7.3.4 Calculul derivatelor unui polinom . . . . 7.4 Operatii cu multimi . . . . . . . . . . . . . . . 7.4.1 Apartenenta la multime . . . . . . . . . 7.4.2 Diferenta a dou multimi . . . . . . . . a 7.4.3 Reuniunea i intersectia a dou multimi s a 7.4.4 Produsul cartezian a dou multimi . . . a 7.4.5 Generarea submultimilor unei multimi . 7.5 Operatii cu numere ntregi mari . . . . . . . . . 7.5.1 Adunarea i scderea . . . . . . . . . . . s a 7.5.2 Inmultirea i artirea . . . . . . . . . s mp 7.5.3 Puterea . . . . . . . . . . . . . . . . . . 7.6 Operatii cu matrice . . . . . . . . . . . . . . . . 7.6.1 Inmultirea . . . . . . . . . . . . . . . . . 7.6.2 Inversa unei matrice . . . . . . . . . . .

8 Algoritmi combinatoriali 8.1 Principiul includerii i al excluderii i aplicatii s s 8.1.1 Principiul includerii i al excluderii . . s 8.1.2 Numrul functiilor surjective . . . . . a 8.1.3 Numrul permutrilor fr puncte xe a a aa 8.2 Principiul cutiei lui Dirichlet i aplicatii . . . s 8.2.1 Problema subsecventei . . . . . . . . . 8.2.2 Problema subirurilor strict monotone s 8.3 Numere remarcabile . . . . . . . . . . . . . . 8.3.1 Numerele lui Fibonacci . . . . . . . . 8.3.2 Numerele lui Catalan . . . . . . . . . 8.4 Descompunerea factori primi . . . . . . . . n 8.4.1 Functia lui Euler . . . . . . . . . . . . 8.4.2 Numrul divizorilor . . . . . . . . . . a 8.4.3 Suma divizorilor . . . . . . . . . . . . 8.5 Partitia numerelor . . . . . . . . . . . . . . . 8.5.1 Partitia lui n exact k termeni . . . n 8.5.2 Partitia lui n cel mult k termeni . . n 8.5.3 Partitii multiplicative . . . . . . . . . 8.6 Partitia multimilor . . . . . . . . . . . . . . . 8.7 Probleme rezolvate . . . . . . . . . . . . . . . 9 Algoritmi de cutare a 9.1 Problema cutrii . . a a 9.2 Cutarea secvential a a 9.3 Cutare binar . . . a a 9.4 Inserare tabel . . n a 9.5 Dispersia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

109 109 109 110 112 113 113 114 114 115 116 119 119 121 121 122 122 123 123 123 124 127 127 127 129 130 131 133 133 134 139 139 141 142 143 145 145 151 155 157 159

10 Algoritmi elementari de sortare 10.1 Introducere . . . . . . . . . . . . . . . . . . . 10.2 Sortare prin selectie . . . . . . . . . . . . . . 10.3 Sortare prin insertie . . . . . . . . . . . . . . 10.3.1 Insertie direct . . . . . . . . . . . . . a 10.3.2 Insertie binar . . . . . . . . . . . . . a 10.4 Sortare prin interschimbare . . . . . . . . . . 10.5 Sortare prin micorarea incrementului - shell s 11 Liste 11.1 Liste liniare . . . . . . . . . . . 11.2 Cozi . . . . . . . . . . . . . . . 11.3 Stive . . . . . . . . . . . . . . . 11.4 Evaluarea expresiilor aritmetice 11.5 Operatii asupra listelor . . . . . . . . . . . . . . . . . . . . . . . prexate . . . . . . . . . . . . . . . .

12 Algoritmi divide et impera 12.1 Tehnica divide et impera . . . . . . . . . . . 12.2 Ordinul de complexitate . . . . . . . . . . . 12.3 Exemple . . . . . . . . . . . . . . . . . . . . 12.3.1 Sortare prin partitionare - quicksort 12.3.2 Sortare prin interclasare - MergeSort 12.3.3 Placa cu guri . . . . . . . . . . . . a 12.3.4 Turnurile din Hanoi . . . . . . . . . 12.3.5 Injumtire repetat . . . . . . . . a at a

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

163 163 164 165 165 166 168 169 173 177 177 180 180 185 191 197 204 208 213 213 214 215 215 216 217 217 217 218 219 219 220 220 220 228 230 241 246 250 255 260

13 Algoritmi BFS-Lee 13.1 Prezentare general . . . . . . . . . . . . . . . . a 13.2 Probleme rezolvate . . . . . . . . . . . . . . . . 13.2.1 Romeo i Julieta - OJI2004 clasa a X-a s 13.2.2 Sudest - OJI2006 clasa a X-a . . . . . . 13.2.3 Muzeu - ONI2003 clasa a X-a . . . . . . 13.2.4 Pianjen ONI2005 clasa a X-a . . . . . a 13.2.5 Algoritmul Edmonds-Karp . . . . . . . 13.2.6 Cuplaj maxim . . . . . . . . . . . . . . 14 Metoda optimului local - greedy 14.1 Metoda greedy . . . . . . . . . . . . . . . . . 14.2 Algoritmi greedy . . . . . . . . . . . . . . . . 14.3 Exemple . . . . . . . . . . . . . . . . . . . . . 14.3.1 Problema continu a rucsacului . . . . a 14.3.2 Problema plasrii textelor pe o band a a 14.3.3 Problema plasrii textelor pe m benzi a 14.3.4 Maximizarea unei sume de produse . . 14.3.5 Problema statiilor . . . . . . . . . . . 14.3.6 Problema cutiilor . . . . . . . . . . . . 14.3.7 Problema subirurilor . . . . . . . . . s 14.3.8 Problema intervalelor disjuncte . . . . 14.3.9 Problema alegerii taxelor . . . . . . . 14.3.10 Problema acoperirii intervalelor . . . . 14.3.11 Algoritmul lui Prim . . . . . . . . . . 14.3.12 Algoritmul lui Kruskal . . . . . . . . . 14.3.13 Algoritmul lui Dijkstra . . . . . . . . . 14.3.14 Urgenta - OJI2002 cls 11 . . . . . . . 14.3.15 Reactivi - OJI2004 cls 9 . . . . . . . . 14.3.16 Pal - ONI2005 cls 9 . . . . . . . . . . 14.3.17 Sant - ONI2006 cls 9 . . . . . . . . . . 14.3.18 Cezar - OJI2007 cls 11 . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

15 Metoda backtracking 15.1 Generarea produsului cartezian . . . . . . . . . . . 15.1.1 Generarea iterativ a produsului cartezian . a 15.1.2 Generarea recursiv a produsului cartezian a 15.2 Metoda bactracking . . . . . . . . . . . . . . . . . 15.2.1 Bactracking iterativ . . . . . . . . . . . . . 15.2.2 Backtracking recursiv . . . . . . . . . . . . 15.3 Probleme rezolvate . . . . . . . . . . . . . . . . . . 15.3.1 Generarea aranjamentelor . . . . . . . . . . 15.3.2 Generarea combinrilor . . . . . . . . . . . a 15.3.3 Problema reginelor pe tabla de ah . . . . . s 15.3.4 Turneul calului pe tabla de ah . . . . . . . s 15.3.5 Problema colorrii hrtilor . . . . . . . . . a a 15.3.6 Problema vecinilor . . . . . . . . . . . . . . 15.3.7 Problema labirintului . . . . . . . . . . . . 15.3.8 Generarea partitiilor unui numr natural . a 15.3.9 Problema parantezelor . . . . . . . . . . . . 15.3.10 Algoritmul DFS de parcurgere a grafurilor . 15.3.11 Determinarea componentelor conexe . . . . 15.3.12 Determinarea componentelor tare conexe . 15.3.13 Sortare topologic . . . . . . . . . . . . . . a 15.3.14 Determinarea nodurilor de separare . . . . 15.3.15 Determinarea muchiilor de rupere . . . . . 15.3.16 Determinarea componentelor biconexe . . . 15.3.17 Triangulatii - OJI2002 clasa a X-a . . . . . 15.3.18 Partitie - ONI2003 clasa a X-a . . . . . . . 15.3.19 Scuta - ONI2003 clasa a X-a . . . . . . . . 16 Programare dinamic a 16.1 Prezentare general . . . . . . . . . . . . . . . a 16.2 Probleme rezolvate . . . . . . . . . . . . . . . 16.2.1 Inmultirea optimal a matricelor . . . a 16.2.2 Subir cresctor maximal . . . . . . . s a 16.2.3 Sum maxim triunghi de numere . a a n 16.2.4 Subir comun maximal . . . . . . . . . s 16.2.5 Distanta minim de editare . . . . . . a 16.2.6 Problema rucsacului (0 1) . . . . . . 16.2.7 Problema schimbului monetar . . . . . 16.2.8 Problema traversrii matricei . . . . . a 16.2.9 Problema segmentrii vergelei . . . . . a 16.2.10 Triangularizarea poligoanelor convexe 16.2.11 Algoritmul Roy-Floyd-Warshall . . . . 16.2.12 Oracolul decide - ONI2001 cls 10 . . . 16.2.13 Pavri - ONI2001 clasa a X-a . . . . . a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

269 269 269 274 277 279 279 280 280 284 294 296 298 301 303 306 310 311 313 314 316 320 321 323 326 330 336 343 343 345 345 348 352 353 360 366 367 368 370 373 374 375 381

xi 16.2.14 Balanta ONI2002 clasa a X-a . 16.2.15 Aliniere ONI2002 clasa a X-a . 16.2.16 Munte - ONI2003 cls 10 . . . . 16.2.17 Lcusta - OJI2005 clasa a X-a a 16.2.18 Avere ONI2005 cls 10 . . . . . 16.2.19 Suma - ONI2005 cls 10 . . . . 17 Potrivirea irurilor s 17.1 Un algoritm inecient . . . . . . . . 17.2 Un algoritm ecient - KMP . . . . . 17.3 Probleme rezolvate . . . . . . . . . . 17.3.1 Circular - Campion 2003-2004 17.3.2 Cifru - ONI2006 baraj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383 387 393 401 412 416 429 429 431 436 436 438 445 445 446 446 446 447 448 448 452 462 463 463 463 477 482 483 487

. . . . . . . . . . . . . . . . . . Runda 6 . . . . . .

18 Geometrie computational a 18.1 Determinarea orientrii . . . . . . . . . . . . . a 18.2 Testarea convexitii poligoanelor . . . . . . . . at 18.3 Aria poligoanelor convexe . . . . . . . . . . . . 18.4 Pozitia unui punct fat de un poligon convex . a 18.5 Pozitia unui punct fat de un poligon concav . a 18.6 aurtoarea convex . . . . . . . . . . . . . . Infs a a 18.6.1 Impachetarea Jarvis . . . . . . . . . . . 18.6.2 Scanarea Craham . . . . . . . . . . . . . 18.7 Dreptunghi minim de acoperire a punctelor . . 18.8 Cerc minim de acoperire a punctelor . . . . . . 18.9 Probleme rezolvate . . . . . . . . . . . . . . . . 18.9.1 Seceta - ONI2005 clasa a IX-a . . . . . 18.9.2 Antena - ONI2005 clasa a X-a . . . . . 18.9.3 Moia lui Pcal - OJI2004 clasa a XI-a s a a 18.9.4 Partitie - ONI2006 baraj . . . . . . . . . 18.9.5 Triunghi - ONI2007 cls 9 . . . . . . . .

19 Teoria jocurilor 493 19.1 Jocul NIM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493 19.1.1 Prezentare general . . . . . . . . . . . . . . . . . . . . . . 493 a 19.1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493 20 Alti algoritmi 20.1 Secvent de sum maxim . . . . . . . . . . . . . . . . . . . a a a 20.1.1 Prezentare general . . . . . . . . . . . . . . . . . . a 20.1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . 20.2 Algoritmul Belmann-Ford . . . . . . . . . . . . . . . . . . . 20.2.1 Algoritmul Belmann-Ford pentru grafuri neorientate 20.2.2 Alg Belmann-Ford pentru grafuri orientate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495 495 495 495 495 495 498

xii 20.2.3 Alg Belmann-Ford pentru grafuri orientate aciclice . . . . . 501

Capitolul 1

Notiuni fundamentale
general, studentii din anul I au cunotinte de programare Pascal sau In s n C/C++. Noi vom prezenta implementrile algoritmilor Java. Nu are prea mare a n important dac este Java, C/C++, Pascal sau alt limbaj de programare. Oricare a a ar limbajul de programare, trebuie s tim primul rnd cum se reprezint a s n a a numerele memoria calculatorului. Altfel putem avea surprize ciudate. n

1.1

Programe ciudate

Dac nu suntem atenti la valorile pe care le pot lua variabilele cu care lucrm, a a putem obtine rezultate greite chiar dac modalitatea de rezolvare a problemei este s a corect. Prezentm astfel de situatii Pascal, C/C++ i Java. a a n s

1.1.1

Un program ciudat Pascal n

Iat un program Pascal care dorim s calculm suma 20.000 + 30.000. a n a a var x,y,z:integer; BEGIN x:=20000; y:=30000; z:=x+y; write(x,+,y,=,z); END. Dei ne ateptam s apar ca rezultat 50.000, surpriza este c pe ecran apare s s a a a 20000+30000=-15536 1

CAPITOLUL 1. NOTIUNI FUNDAMENTALE

Figura 1.1: Un program ciudat Pascal n

1.1.2

Un program ciudat C++ n


#include<iostream.h> int main() { int x,y,z; x=20000; y=30000; z=x+y; cout << x <<"+"<<y<<"="<<z; return 0; }

Iat un program C++ care dorim s calculm suma 20.000 + 30.000. a n n a a

Dei ne ateptam s apar ca rezultat 50.000, surpriza este c pe ecran apare s s a a a 20000+30000=-15536

Figura 1.2: Un program ciudat C++ n

1.1. PROGRAME CIUDATE

1.1.3

Un program ciudat Java n

class Ciudat { public static void main(String args[]) { int x,y,z; x=200000; y=300000; z=x*y; System.out.println(x+"*"+y+"="+z); } }

Iat un program C++ care dorim s calculm suma 200.000 300.000. a n n a a

Dei ne ateptam s apar ca rezultat 60.000.000.000, surpriza este c pe ecran s s a a a apare 200000*300000=-129542144

Figura 1.3: Un program ciudat Java n Calculul cu numerele ntregi este relativ simplu. Calculele sunt fcute a ntr-o aritmetic modulo N = 2n unde n este numrul de biti ai cuvntului main. a a a s a Exist maini pe 16, 32 i 64 biti pentru care N este aproximativ egal cu 6 104 , a s s 4 109 i respectiv 2 1019 . s Se pot reprezenta i numerele s ntregi negative. Modul curent de reprezentare este complement fat de 2. notatie binar, bitul cel mai semnicativ este n a In a bitul de semn. Numerele negative sunt cuprinse ntre 2n1 i 2n1 1. s Atunci cnd valorile obtinute din calcule depesc marginile permise de tipul a as variabilelor implicate respectivele calcule, se pot obtine rezultate eronate. n

CAPITOLUL 1. NOTIUNI FUNDAMENTALE

1.1.4

Structura unui program Java

Un program simplu Java are urmtoarea structur: n a a class numeClasa { public static void main(String args[]) { // declarri de variabile a // instructiuni } } Programul prezentat sectiunea anterioar se poate scrie sub forma: n a class Ciudat { public static void main(String args[]) { // declarri de variabile a int x,y,z; // instructiuni x=200000; y=300000; z=x*y; System.out.println(x+*+y+=+z); } } Clasa este elementul de baz Java. Cel mai simplu program Java este a n n format dintr-o clas (numele clasei este la latitudinea programatorului; singura a recomandare este s a nceap cu liter mare) i functia main. a a s exemplul de mai sus sunt declarate trei variabile (x, y i z) de tip int In s (adic de tip a ntreg cu semn). Spatiul alocat variabilelor de tip int este de 4 octeti (32 biti). Aceasta nseamn c o astfel de variabil poate avea valori a a a ntre 263 i s 263 1. Valoarea maxim este de aproximativ 2 miliarde. a programul anterior x are valoarea 200.000 iar y are valoarea 300.000, deci In produsul are valoarea 60 miliarde care depete cu mult valoarea maxim de 2 as s a miliarde. binar, 60 miliarde se scrie (folosint 36 biti) sub forma In 110111111000010001110101100000000000 dar sunt retinuti numai 32 biti din partea dreapt, adic a a 11111000010001110101100000000000 Primul bit reprezint bitul de semn (1 reprezint semnul - iar 0 reprezint a a a semnul +). Aceast reprezentare trebuie gndit ca ind o reprezentare cod a a a n

1.2. CONVERSII ALE DATELOR NUMERICE

complementar (ea este memoria calculatorului i toate numerele n s ntregi cu semn sunt reprezentate acest cod). n Reprezentarea cod direct se obtine din reprezentarea cod complementar n n (mai precis, trecnd prin reprezentarea cod invers i adunnd, binar, 1): a n s a n 11111000010001110101100000000000 (cod complementar) 10000111101110001010011111111111 (cod invers) 10000111101110001010100000000000 (cod direct) Din codul direct se obtine -129542144 baza 10. Aceasta este explicatia acelui n rezultat ciudat!

1.2

Conversii ale datelor numerice

1.2.1

Conversia din baza 10 baza 2 n

Fie x = an ...a0 numrul scris baza 10. Conversia baza 2 a numrului x a n n a se efectueaz dup urmtoarele reguli: a a a Se mparte numrul x la 2 iar restul va reprezenta cifra de ordin 0 a a numrului scris noua baz (b0 ). a n a Ctul obtinut la artirea anterioar se a mp a mparte la 2 i se obtine s cifra de ordin imediat superior a numrului scris noua baz. Secventa a n a de artiri se repet pn cnd se ajunge la ctul 0. mp a a a a a Restul de la a k-a artire va reprezenta cifra bk1 . Restul de mp la ultima artire reprezint cifra de ordin maxim reprezentarea mp a n numrului baza 2. a n Metoda conduce la obtinerea rezultatului dup un numr nit de artiri, a a mp ntruct mod inevitabil se ajunge la un ct nul. plus, toate resturile obtinute a n a In apartin multimii {0, 1}. Exemplu. Fie x = 13 numrul baza 10. Secventa de artiri este: a n mp (1) se mparte 13 la 2 i se obtine ctul 6 i restul 1 (deci b0 = 1) s a s (2) se mparte 6 la 2 i se obtine ctul 3 i restul 0 (deci b1 = 0) s a s (3) se mparte 3 la 2 i se obtine ctul 1 i restul 1 (deci b2 = 1) s a s (4) se mparte 1 la 2 i se obtine ctul 0 i restul 1 (deci b3 = 1). s a s Prin urmare (13)10 = (1101)2 .

CAPITOLUL 1. NOTIUNI FUNDAMENTALE

1.2.2

Conversia din baza 2 baza 10 n

Dac y = bn ...b1 b0 este un numr baza 2 , atunci reprezentarea baza 10 a a n n se obtine efectund calculul ( baza 10): a n x = bn 2n + ... + b1 2 + b0 . Exemplu. Fie y = 1100. Atunci reprezentarea baza 10 va n x = 1 23 + 1 22 + 0 21 + 0 20 = 12.

1.2.3

Conversii ntre bazele 2 i 2r s

Pentru conversia unui numr din baza p baza q se poate converti numrul a n a din baza p baza 10, iar acesta se convertete baza q. n s n cazul conversiei unui numr din baza p = 2 baza q = 2r se poate evita In a n trecerea prin baza 10 procedndu-se modul urmtor: se formeaz grupuri de a n a a cte r cifre pornind de la ultima cifr din dreapta, a a nspre stnga. Fiecare grup de a r cifre va convertit ntr-o cifr a bazei q. a Fie, spre exemplu: p = 2, q = 16 = p4 i x = (1011010)2 . s Se obtin urmtoarele grupuri de cte 4 cifre binare: a a (1010)2 = A16 i (0101)2 = 516 . s Deci scrierea numrului x baza 16 este: (5A)16 . a n Se observ c a fost completat cu 0, spre stnga, cea mai din stnga grup, a a a a a a pn la formarea grupei complete de 4 cifre binare. a a cazul conversiei unui numr din baza p = 2r baza q = 2 se poate de In a n asemenea evita trecerea prin baza 10 procedndu-se modul urmtor: ecare cifr a n a a din reprezentarea baza p = 2r se n nlocuiete cu r cifre binare care reprezint s a scrierea respectivei cifre baza 2. n Fie, spre exemplu: p = 16 = 24 , q = 2 i x = (3A)16 . s Se fac urmtoarele a nlocuiri de cifre: 3 0011, A 1010. Deci scrierea numrului x baza 2 este: (111010)2 . a n Se observ c nu apar cifrele 0 din stnga scrierii brute (00111010)2 obtinute a a a prin nlocuiri.

Capitolul 2

Structuri de date
Inainte de a elabora un algoritm, trebuie s ne gndim la modul care a a n reprezentm datele. a

2.1

Date i structuri de date s

2.1.1

Date

Datele sunt entiti purttoare de informatie. informatic, o dat este un at a In a a model de reprezentare a informatiei, accesibil unui anumit procesor (om, unitate central, program), model cu care se poate opera pentru a obtine noi informatii a despre fenomenele, procesele i obiectele lumii reale. functie de modul lor de s In organizare, datele pot : elementare (simple) sau structurate. Datele elementare au caracter atomic, sensul c nu pot descompuse n a n alte date mai simple. Astfel de date sunt cele care iau ca valori numere sau iruri s de caractere. O dat elementar apare ca o entitate indivizibil att din punct de a a a a vedere al informatiei pe care o reprezint ct i din punct de vedere al procesorului a a s care o prelucreaz. a O dat elementar poate privit la nivel logic (la nivelul procesorului uman) a a a sau la nivel zic (la nivelul calculatorului). Din punct de vedere logic, o dat poate denit ca un triplet de forma a a (identif icator, atribute, valori). Din punct de vedere zic, o dat poate denit ca o zon de memorie de o a a a anumit lungime, situat la o anumit adres absolut, care sunt memorate a a a a a n n timp i s ntr-o form specic valorile datei. a a 7

CAPITOLUL 2. STRUCTURI DE DATE

Identicatorul este un simbol asociat datei pentru a o distinge de alte date i s pentru a o putea referi cadrul programului. n Atributele sunt proprietii ale datei i precizeaz modul care aceasta va at s a n tratat cadrul procesului de prelucrare. Dintre atribute, cel mai important este a n atributul de tip care denetete apartenenta datei la o anumit clas de date. s a a O clas de date este denit de natura i domeniul valorilor datelor care fac a a s parte din clasa respectiv, de operatiile specice care se pot efectua asupra datelor a i de modelul de reprezentare intern a datelor. Astfel, exist date de tip s a a ntreg, de tip real, de tip logic, de tip ir de caractere, etc. s O multime de date care au aceleai caracteristici se numete tip de date. s s Evident, un tip de date este o clas de date cu acelai mod de interpretare logic a s a i reprezentare zic i se caracterizeaz prin valorile pe care le pot lua datele i s as a s prin operatiile care pot efectuate cu datele de tipul respectiv. De exemplu, tipul ntreg se caracterizeaz prin faptul c datele care apartin a a i pot lua doar valori ntregi, i asupra lor pot efectuate operatii aritmetice clasice s (adunare, scdere, a nmultire, artire multimea numerelor mp n ntregi, comparatii). Se poate considera c datele organizate sub forma tablourilor unidimensionale a formeaz tipul vector iar datele organizate sub forma tablourilor bidimensionale a formeaz tipul matrice. a functie de natura elementelor care o compun, o structur de date poate : In a omogen, atunci cnd toate elementele au acelai tip; a a s neomogen, atunci cnd elementele componente au tipuri diferite. a a static, atunci cnd numrul de componente este xat; a a a

functie de numrul datelor care o compun, o structur de date poate : In a a dinamic, atunci cnd numrul de componente este variabil. a a a

Din punct de vedere al modului care sunt utilizate datele pot : n Constante. Valoarea lor nu este i nu poate modicat cadrul s a n algoritmului, ind xat de la a nceputul acestuia. O constant este a o dat care pstreaz aceeai valoare pe tot parcursul procesului de a a a s prelucrare. Pentru constantele care nu au nume, ai valoarea lor nss este cea prin care se identic. Constante care au nume (identicator) a sunt initializate cu o valoare momentul declarrii. n a Variabile. Valoarea lor poate modicat cadrul algoritmului. a n momentrul declarrii lor, variabilele pot initializate (li se atribuie In a o valoare) sau pot neinitializate (nu li se atribuie nici o valoare). O variabil este o dat care nu pstreaz neaprat aceeai valoare pe a a a a a s parcursul procesului de prelucrare. Tipul unei date trebuie s e precizat, cadrul programului de prelucrare, a n printr-o declaratie de tip ce precede utilizarea respectivei constante sau variabile. Valorile datei pot numere, sau valori de adevr, sau iruri de caractere, etc. a s

2.1. DATE SI STRUCTURI DE DATE

2.1.2

Structuri de date

Datele apar frecvent sub forma unor colectii de date de diferite tipuri, menite s faciliteze prelucrarea cadrul rezolvrii unei anumite probleme concrete. a n a Datele structurate, numite uneori i structuri de date, sunt constituite din mai s multe date elementare (uneori de acelai tip, alteori de tipuri diferite), grupate cu s un anumit scop i dup anumite reguli. s a Exemple. 1. Un ir nit de numere reale a1 , a2 , ..., an poate reprezentat ca o dat s a structurat (tablou unidimensional sau vector). a 2. O matrice a1,1 a2,1 am,1 a1,2 a2,2 am,1 .. . a1,n a2,n am,n

poate reprezentat ca o dat structurat (tablou bidimensional) specic ecare a a a nd element prin doi indici (de linie i de coloan). s a O structur de date este deci o colectie de date, eventual de tipuri diferite, a pe care s-a denit o anumit organizare i creia este specic un anumit mod de a s a i identicare a elementelor componente. Componetele unei structuri de date pot identicate prin nume sau prin ordinea pe care o ocup cadrul structurii. a n Dac accesul la o anumit component a structurii de date se poate face fr a a a aa s inem seama de celelalte componente, vom spune c structura de date este cu at a acces direct. schimb, dac accesul la o component a structurii de date se poate In a a face numai innd cont de alte cmpuri ale structurii ( conformitate cu ordinea t a a n structurii, printr-un proces de traversare) atunci vom spune c structura este cu a acces secvential. Structurile de date pot create pentru a depozitate memoria intern n a (aceste structuri de date se numesc structuri interne) sau memoria extern (se n a numesc structuri externe, sau iere). Structurile interne au un caracter de date s temporare (ele dispar odat cu a ncetarea activitii de prelucrare) iar cele externe at au un caracter de date permanente (mai bine spus, de lung durat). a a Dac pe lng componentele structurii se a a a nregistreaz pe suport i alte date a s suplimentare care s materializeze relatia de ordonare, atunci structura de date a respectiv este explicit, caz contrar este implicit. De exemplu, structura de a a n a date de tip tablou este o structur implicit de date iar structura de date de tip a a list liniar este o structur explicit de date. a a a a Asupra structurilor de date se pot efectua operatii care se refer structura a respectiv sau la valorile datelor componente. Cele mai importante operatii sunt: a operatia de creare, care const memorarea pe suportul de memorie a a n structurii de date forma sa initial, n a

10

CAPITOLUL 2. STRUCTURI DE DATE

operatia de consultare, care const accesul la elementele structurii a n n vederea prelucrrii valorilor acestora, i a s operatia de actualizare, care const adugarea de noi elemente, sau a n a eliminarea elementelor care nu mai sunt necesare, sau modicarea valorilor unor componente ale structurii. Toate structurile de date la fel organizate i pe care s-au denit aceleai s s operatii, poart numele de tip de structur de date. Dac analizm a operatiile a a a a ns care se efectueaz asupra unei structuri de date, vom putea vedea c toate acestea a a se reduc la executarea, eventual repetat, a unui grup de operatii specice numite a operatii de baz. a

2.2

Structuri i tipuri de date abstracte s

2.2.1

Structuri de date abstracte

Abstractizarea datelor reprezint de fapt concentrarea asupra esentialului, a ignornd detaliile (sau altfel spus, conteaz ce nu cum). a a Stpnirea aplicatiilor complexe se obtine prin descompunerea module. a a n Un modul trebuie s e simplu, cu complexitatea ascuns interiorul lui, i s a a n s a aib o interfat simpl care s permit folosirea lui fr a cunoate implementarea. a a a a a aa s O structur de date abstract este un modul constnd din date i operatii. a a a s Datele sunt ascunse interiorul modulului i pot accesate prin intermediul n s operatiilor. Structura de date este abstract deoarece este cunoscut numai interfata a a structurii, nu i implementarea (operatiile sunt date explicit, valorile sunt denite s implicit, prin intermediul operatiilor).

2.2.2

Tipuri de date abstracte

Procesul de abstractizare se refer la dou aspecte: a a abstractizarea procedural, care separ proprietile logice ale unei actiuni de a a at detaliile implementrii acesteia a abstractizarea datelor, care separ proprietile logice ale datelor de detaliile a at reprezentrii lor a O structur de date abstracte are un singur exemplar (o singur instanta). a a Pentru a crea mai multe exemplare ale structurii de date abstracte se denete un s tip de date abstract. Java, de exemplu, clasa asigur un mod direct de denire In a a oricrui tip de date abstract. a

2.3. STRUCTURI DE DATE ELEMENTARE

11

2.3

Structuri de date elementare

2.3.1

Liste

O list este o colectie de elemente de informatie (noduri) aranjate a ntr-o anumit ordine. Lungimea unei liste este numrul de noduri din list. Structura a a a corespunzatoare de date trebuie s ne permit s determinm ecient care este a a a a primul/ultimul nod structur i care este predecesorul/succesorul unui nod dat n as (dac exist). Iat cum arat cea mai simpl list, lista liniar: a a a a a a a
capul listei coada listei

Figura 2.1: List liniar a a O list circular este o list care, dup ultimul nod, urmeaz primul nod, a a a n a a deci ecare nod are succesor i predecesor. s Cteva dintre operatiile care se efectueaz asupra listelor sunt: inserarea a a (adugarea) unui nod, extragerea (tergerea) unui nod, concatenarea unor liste, a s numrarea elementelor unei liste etc. a Implementarea unei liste se realizeaz dou moduri: secvential i antuit. a n a s n Implementarea secvential se caracterizeaz prin plasarea nodurilor locatii a a n succesive de memorie, conformitate cu ordinea lor list. Avantajele acestui n n a mod de implementare sunt accesul rapid la predecesorul/succesorul unui nod i s gsirea rapid a primului/ultimului nod. Dezavantajele sunt modalitile relativ a a at complicate de inserarea/tergere a unui nod i faptul c, general, nu se folosete s s a n s ntreaga memorie alocat listei. a Implementarea antuit se caracterizeaz prin faptul c ecare nod contine nl a a a dou prti: informatia propriu-zis i adresa nodului succesor. Alocarea memoriei a a as pentru ecare nod se poate face mod dinamic, timpul rulrii programului. n n a Accesul la un nod necesit parcurgerea tuturor predecesorilor si, ceea ce conduce a a la un consum mai mare de timp pentru aceast operatie. schimb, operatiile a In de inserare/tergere sunt foarte rapide. Se consum exact att spatiu de memorie s a a ct este necesar dar, evident, apare un consum suplimentar de memorie pentru a nregistrarea legturii ctre nodul succesor. Se pot folosi dou adrese loc de a a a n una, astfel at un nod s contin pe lang adresa nodului succesor i adresa nc a a a s nodului predecesor. Obtinem astfel o list dublu inlntuit, care poate traversat a a a a ambele directii. n Listele antuite pot reprezentate prin tablouri. acest caz, adresele nl In nodurilor sunt de fapt indici ai tabloului.

12

CAPITOLUL 2. STRUCTURI DE DATE

O alternativ este s folosim dou tablouri val i next astfel: s memorm a a a s a a informatia ecarui nod i locatia val[i], iar adresa nodului su succesor locatia n a n next[i]. Indicele locatiei primului nod este memorat variabila p. Vom conveni ca, n pentru cazul listei vide, s avem p = 0 i next[u] = 0 unde u reprezint ultimul nod a s a din list. Atunci, val[p] va contine informatia primului nod al listei, next[p] adresa a celui de-al doilea nod, val[next[p]] informatia din al doilea nod, next[next[p]] adresa celui de-al treilea nod, etc. Acest mod de reprezentare este simplu dar apare problema gestionrii locatiilor libere. O solutie este s reprezentm locatiile a a a libere tot sub forma unei liste nlantuite. Atunci, tergerea unui nod din lista s initial implic inserarea sa lista cu locatii libere, iar inserarea unui nod lista a a n n initial implic tergerea sa din lista cu locatii libere. Pentru implementarea listei a as de locatii libere, putem folosi aceleai tablouri dar avem nevoie de o alt variabil, s a a f reehead, care s contin indicele primei locatii libere din val i next. Folosim a a s aceleai conventii: dac f reehead = 0 s a nseamn c nu mai avem locatii libere, iar a a next[ul] = 0 unde ul reprezint ultima locatie liber. a a Vom descrie in continuare dou tipuri de liste particulare foarte des folosite. a

2.3.2

Stive i cozi s

O stiv este o list liniar cu proprietatea c operatiile de inserare/extragere a a a a a nodurilor se fac n/din coada listei. Dac nodurile A, B, C sunt inserate a ntr-o stiv aceast ordine, atunci primul nod care poate ters/extras este C. mod a n a s In echivalent, spunem c ultimul nod inserat este singurul care poate ters/extras. a s Din acest motiv, stivele se mai numesc i liste LIFO (Last In First Out). s Cel mai natural mod de reprezentare pentru o stiv este implementarea a secvential a ntr-un tablou S[1..n], unde n este numrul maxim de noduri. Primul a nod va memorat S[1], al doilea S[2], iar ultimul S[top], unde top este n n n o variabil care contine adresa (indicele) ultimului nod inserat. Initial, cnd stiva a a este vid, avem (prin conventie) top = 0. a O coad este o list liniar care inserrile se fac doar capul listei, iar a a a n a n tergerile/extragerile se fac doar din coada listei. Din acest motiv, cozile se mai s numesc i liste FIFO (First In First Out). s O reprezentare secvential pentru o coad se obtine prin utilizarea unui a a tablou C[0..n 1], pe care tratm ca i cum ar circular: dup locatia C[n 1] l a s a urmeaz locatia C[0]. Fie tail variabila care contine indicele locatiei predecesoare a primei locatii ocupate i e head variabila care contine indicele locatiei ocupate s ultima oar. Variabilele head i tail au aceeai valoare atunci i numai atunci cnd a s s s a coada este vid. Initial, avem head = tail = 0. a Trebuie s observm faptul c testul de coad vid este acelai cu testul de a a a a a s coad plin. Dac am folosi toate cele n locatii la un moment dat, atunci nu am a a a putea distinge ntre situatia de coad plin i cea de coad vid, deoarece a a s a a n ambele situatii am avea head = tail. consecint, vom folosi efectiv, orice In a n moment, cel mult n 1 locatii din cele n ale tabloului C.

2.3. STRUCTURI DE DATE ELEMENTARE

13

2.3.3

Grafuri

Un graf este o pereche G =< V, M >, unde V este o multime de vrfuri, iar a M V V este o multime de muchii. O muchie de la vrful a la vrful b este a a notat cu perechea ordonat (a, b), dac graful este orientat, i cu multimea {a, b}, a a a s dac graful este neorientat. a Dou vrfuri unite printr-o muchie se numesc adiacente. Un vrf care este a a a extremitatea unei singure muchii se numete vrf terminal. s a Un drum este o succesiune de muchii de forma (a1 , a2 ), (a2 , a3 ), ..., (an1 , an ) sau de forma {a1 , a2 }, {a2 , a3 }, ..., {an1 , an } dup cum graful este orientat sau neorientat. Lungimea drumului este egal cu a a numrul muchiilor care constituie. Un drum simplu este un drum care nici un a l n vrf nu se repet. Un ciclu este un drum care este simplu, cu exceptia primului i a a s ultimului vrf, care coincid. Un graf aciclic este un graf fr cicluri. a aa Un graf neorientat este conex, dac a ntre oricare dou vrfuri exist un drum. a a a Pentru grafuri orientate, aceast notiune este arit: un graf orientat este tare a nt a conex, dac a ntre oricare dou vrfuri i i j exist un drum de la i la j i un drum a a s a s de la j la i. Vrfurilor unui graf li se pot ataa informatii (numite valori), iar muchiilor a s li se pot ataa informatii numite uneori lungimi sau costuri. s Exist cel putin trei moduri de reprezentare ale unui graf: a Printr-o matrice de adiacenta A, care A[i, j] = true dac vrfurile i i j n a a s sunt adiacente, iar A[i, j] = f alse caz contrar. O alt variant este s-i dm lui n a a a a A[i, j] valoarea lungimii muchiei dintre vrfurile i i j, considerand A[i, j] = + a s atunci cnd cele dou vrfuri nu sunt adiacente. Cu aceast reprezentare, putem a a a a verica uor dac dou vrfuri sunt adiacente. Pe de alt parte, dac dorim s s a a a a a a am toate vrfurile adiacente unui vrf dat, trebuie s analizm o a a a a a ntreag linie a din matrice. Aceasta necesit n operatii (unde n este numrul de vrfuri graf), a a a n independent de numrul de muchii care conecteaz vrful respectiv. a a a Prin liste de adiacenta, adic prin ataarea la ecare vrf i a listei de vrfuri a s a a adiacente (pentru grafuri orientate, este necesar ca muchia s plece din i). a Intrun graf cu m muchii, suma lungimilor listelor de adiacent este 2m, dac graful a a este neorientat, respectiv m, dac graful este orientat. Dac numrul muchiilor a a a n graf este mic, aceast reprezentare este preferabil din punct de vedere al memoriei a a necesare. Totui, pentru a determina dac dou vrfuri i i j sunt adiacente, trebuie s a a a s s analizm lista de adiacent a lui i (i, posibil, lista de adiacent a lui j), ceea ce a a a s a este mai putin ecient dect consultarea unei valori logice matricea de adiacenta. a n Printr-o list de muchii. Aceast reprezentare este ecient atunci cnd a a a a avem de examinat toate muchiile grafului.

14

CAPITOLUL 2. STRUCTURI DE DATE

2.3.4

Arbori binari

Un arbore este un graf neorientat, aciclic i conex. Sau, echivalent, un arbore s este un graf neorientat care exist exact un drum n a ntre oricare dou vrfuri. a a Un arbore reprezentat pe niveluri se numete arbore cu rdcin. Vrful s a a a a plasat pe nivelul 0 se numete rdcina arborelui. Pe ecare nivel i > 0 sunt s a a plasate vrfurile pentru care lungimea drumurilor care le leag de rdcin este i. a a a a a Vrfurile de pe un nivel i > 0 legate de acelai vrf j de pe nivelul i 1 se a s a numesc descendentii directi (ii) vrfului j iar vrful j se numete ascendent direct a a s (tat) al acestor vrfuri. a a Dac exist un drum de la un vrf i de pe nivelul ni la un vrf j de pe nivelul a a a a nj > ni, atunci vrful i se numete ascendent al lui j, iar vrful j se numete a s a s descendent al lui i. Un vrf terminal (sau frunz) este un vrf fr descendenti. Vrfurile care a a a aa a nu sunt terminale se numesc neterminale. Un arbore care orice vrf are cel mult doi descendenti se numete arbore n a s binar. Intr-un arbore cu rdcin (reprezentat pe niveluri), adncimea unui vrf a a a a a este lungimea drumului dintre rdcin i acest vrf iar altimea unui vrf este a a as a n a lungimea celui mai lung drum dintre acest vrf i un vrf terminal. a s a altimea arborelui este altimea rdcinii. In n a a Intr-un arbore binar, numrul maxim de vrfuri aate pe nivelul k este 2k . a a Un arbore binar de altime k are cel mult 2k+1 1 vrfuri, iar dac are exact n a a 2k+1 1 vrfuri, se numete arbore plin. a s Varfurile unui arbore plin se numeroteaza ordinea nivelurilor. Pentru acelai n s nivel, numerotarea se face arbore de la stnga la dreapta. n a
1
nivelul 0

nivelul 1

nivelul 2

10

11

12

13

14

15

nivelul 3

Figura 2.2: Arbore binar plin Un arbore binar cu n vrfuri i de altime k este complet, dac se obtine a s n a din arborele binar plin de altime k, prin eliminarea, dac este cazul, a vrfurilor n a a numerotate cu n + 1, n + 2, ..., 2k+1 1.

2.3. STRUCTURI DE DATE ELEMENTARE

15

Acest tip de arbore se poate reprezenta secvential folosind un tablou T , punnd vrfurile de adncime k, de la stnga la dreapta, pozitiile T [2k ], T [2k +1], a a a a n ..., T [2k+1 1] (cu posibila exceptie a ultimului nivel care poate incomplet).
1
nivelul 0

nivelul 1

nivelul 2

10

11

12

nivelul 3

Figura 2.3: Arbore binar complet

Tatl unui vrf reprezentat T [i], i > 0, se a T [i/2]. Fiii unui vrf a a n a n a reprezentat T [i] se a, dac exist, T [2i] i T [2i + 1]. n a a a n s

2.3.5

Heap-uri

Un max-heap (heap=gramad ordonat, traducere aproximativ) este a a n a un arbore binar complet, cu urmtoarea proprietate: valoarea ecarui vrf este a a mai mare sau egal cu valoarea ecrui u al su. a a a Un min-heap este un arbore binar complet care valoarea ecarui vrf este n a mai mic sau egal cu valoarea ecrui u al su. a a a a
11
nivelul 0

10

nivelul 1

nivelul 2

nivelul 3

Figura 2.4: Max-heap

16

CAPITOLUL 2. STRUCTURI DE DATE Acelai heap poate reprezentat secvential prin urmtorul tablou: s a 11 7 10 7 7 9 2 4 6 5 7 3

Caracteristica de baz a acestei structuri de date este c modicarea valorii a a unui vrf se face foarte ecient, pstrndu-se proprietatea de heap. a a a De exemplu, ntr-un max-heap, dac valoarea unui vrf crete, astfel at a a s nc depete valoarea tatlui, este sucient s schimbm as s a a a ntre ele aceste dou valori a i s continum procedeul mod ascendent, pn cnd proprietatea de heap este s a a n a a a restabilit. Dac, dimpotriv, valoarea vrfului scade, astfel at devine mai mic a a a a nc a dect valoarea cel putin a unui u, este sucient s schimbm intre ele valoarea a a a modicat cu cea mai mare valoare a iilor, apoi s continum procesul mod a a a n descendent, pn cnd proprietatea de heap este restabilit. a a a a Heap-ul este structura de date ideal pentru extragerea maximului/minimului a dintr-o multime, pentru inserarea unui vrf, pentru modicarea valorii unui vrf. a a Sunt exact operatiile de care avem nevoie pentru a implementa o list dinamic a a de prioriti: valoarea unui vrf va da prioritatea evenimentului corespunzator. at a Evenimentul cu prioritatea cea mai mare/mic se va aa mereu la radacina a heap-ului, iar prioritatea unui eveniment poate modicat mod dinamic. a n

2.3.6

Structuri de multimi disjuncte

S presupunem c avem N elemente, numerotate de la 1 la N . Numerele care a a identic elementele pot , de exemplu, indici intr-un tablou unde sunt memorate a valorile elementelor. Fie o partitie a acestor N elemente, format din submultimi a dou cte dou disjuncte: S1, S2, ... . Presupunem c ne intereseaz reuniunea a a a a a a dou submultimi, Si Sj . a Deoarece submultimile sunt dou cte dou disjuncte, putem alege ca etichet a a a a pentru o submultime oricare element al ei. Vom conveni ca elementul minim al unei multimi s e eticheta multimii respective. Astfel, multimea {3, 5, 2, 8} va numit a a multimea 2. Vom aloca tabloul set[1..N ], care ecrei locatii set[i] i se atribuie eticheta n a submultimii care contine elementul i. Avem atunci proprietatea: set[i] i, pentru 1 i N . Reuniunea submultimilor etichetate cu a i b se poate realiza astfel: s procedure reuniune(a, b) i a; jb if i > j then interschimb i i j a s for k j to N do if set[k] = j then set[k] i

Capitolul 3

Algoritmi

3.1

Etape rezolvarea problemelor n


Principalele etape care se parcurg rezolvarea unei probleme sunt: n (a) Stabilirea datelor initiale i a obiectivului (ce trebuie determinat). s (b) Alegerea metodei de rezolvare. (c) Aplicarea metodei pentru date concrete. Exemplu. S presupunem c problema este rezolvarea, R, a ecuatiei x2 3x + 2 = 0. a a n (a) Datele initiale sunt reprezentate de ctre coecientii ecuatiei iar a obiectivul este determinarea rdcinilor reale ale ecuatiei. a a (b) Vom folosi metoda de rezolvare a ecuatiei de gradul al doilea avnd a forma general ax2 + bx + c = 0. Aceast metod poate descris a a a a astfel: Pasul 1. Se calculeaz discriminantul: = b2 4ac. a Pasul 2. Dac > 0 a atunci ecuatia are dou rdcini reale distincte: x1,2 = a a a altfel, dac = 0 a atunci ecuatia are o rdcina real dubl: x1,2 = a a a a altfel ecuatia nu are rdcini reale. a a (c) Aplicarea metodei pentru datele problemei (a = 1, b = 3, c = 2) conduce la rezultatul: x1 = 1, x2 = 2. 17
b 2a b 2a

18

CAPITOLUL 3. ALGORITMI

3.2

Algoritmi

3.2.1

Ce este un algoritm?

Un algoritm este o succesiune de operatii aritmetice i/sau logice care, s aplicate asupra unor date, permit obtinerea rezultatului unei probleme din clasa celor pentru care a fost conceput. S observm c nu apare denitie cuvntul calculator; algoritmii nu au a a a n a neaprat legtur cu calculatorul. Totui, acest curs ne vom concentra aproape a a a s n exclusiv pe algoritmi care pot implementati rezonabil pe calculator. Altfel spus, ecare pas din algoritm trebuie astfel gndit at ori este suportat direct de ctre a nc a limbajul de programare favorit (operatii aritmetice, cicluri, recursivitate, etc) ori este asemntor cu ceva aat mai a a nvt nainte (sortare, cutare binar, parcurgere a a adncime, etc). n a Secventa de pai prin care este descris metoda de rezolvare a ecuatiei de s a gradul al doilea (prezentat sectiunea anterioar) este un exemplu de algoritm. a n a Calculul efectuat la Pasul 1 este un exemplu de operatie aritmetic, iar analiza a semnului discriminantului (Pasul 2) este un exemplu de operatie logic. a Descrierea unui algoritm presupune precizarea datelor initiale i descrierea s prelucrrilor efectuate asupra acestora. Astfel, se poate spune c: a a algoritm = date + prelucrri a Al-Khwarizmi a fost cel care a folosit pentru prima dat reguli precise i clare a s pentru a descrie procese de calcul (operatii aritmetice fundamentale) lucrarea n sa Scurt carte despre calcul algebric. Mai trziu, aceast descriere apare sub a a a denumirea de algoritm Elementele lui Euclid. Algoritmul lui Euclid pentru n calculul celui mai mare divizor comun a dou numere naturale este, se pare, primul a algoritm cunoscut matematic. n a matematic notiunea de algoritm a primit mai multe denitii: algoritmul In a normal al lui A. A. Markov, algoritmul operational al lui A. A. Leapunov, maina s Turing, functii recursive, sisteme POST. S-a demonstrat c aceste denitii sunt a echivalente din punct de vedere matematic. informatic exist de asemenea mai multe denitii pentru notiunea de In a a algoritm. De exemplu, [35] notiunea de algoritm se denete astfel: n s Un algoritm este sistemul virtual A = (M, V, P, R, Di, De, M i, M e) constituit din urmtoarele elemente: a M - memorie intern format din locatii de memorie i utilizat pentru a a s a stocarea temporar a valorilor variabilelor; a

3.2. ALGORITMI V - multime de variabile denite conformitate cu rationamentul R, n care utilizeaz memoria M pentru stocarea valorilor din V ; a P - proces de calcul reprezentat de o colectie de instructiuni/comenzi exprimate ntr-un limbaj de reprezentare (de exemplu, limbajul pseudocod); folosind memoria virtual M i multimea de variabile a s V , instructiunile implementeaz/codic tehnicile i metodele care a a s constituie rationamentul R; executia instructiunilor procesului de calcul determin o dinamic a valorilor variabilelor; dup executia a a a tuturor instructiunilor din P , solutia problemei se a anumite a n locatii de memorie corespunztoare datelelor de ieire De; a s R - rationament de rezolvare exprimat prin diverse tehnici i metode s specice domeniului din care face parte clasa de probleme supuse rezolvrii (matematic, zic, chimie etc.), care a a a mbinate cu tehnici de programare corespunztoare realizeaz actiuni/procese logice, a a utiliznd memoria virtual M i multimea de variabile V ; a a s Di - date de intrare care reprezint valori ale unor parametri care a caracterizeaz ipotezele de lucru/strile initiale ale problemei i a a s care sunt stocate memoria M prin intermediul instructiunilor n de citire/intrare care utilizeaz mediul de intrare M i; a De - date de ieire care reprezint valori ale unor parametri care s a caracterizeaz solutia problemei/strile nale; valorile datelor de a a ieire sunt obtinute din valorile unor variabile generate de executia s instructiunilor din procesul de calcul P , sunt stocate memoria M , n i s nregistrate pe un suport virtual prin intermediul instructiunilor de scriere/ieire care utilizeaz mediul de ieire M e; ; s a s M i - mediu de intrare care este un dispozitiv virtual de intrare/citire pentru preluarea valorilor datelor de intrare i stocarea acestora s n memoria virtual M ; a M e - mediu de ieire care este un dispozitiv virtual de ieire/scriere s s pentru preluarea datelor din memoria virtual M i a s nregistrarea acestora pe un suport virtual (ecran, hrtie, disc magnetic, etc.). a

19

Un limbaj este un mijloc de transmitere a informatiei. Exist mai multe tipuri de limbaje: limbaje naturale (englez, romn, etc), a a a a limbaje tiintice (de exemplu limbajul matematic), limbaje algoritmice, limbaje s de programare (de exemplu Pascal, C, Java), etc. Un limbaj de programare este un limbaj articial, riguros ntocmit, care permite descrierea algoritmilor astfel at s poat transmii nc a a s calculatorului cu scopul ca acesta s efectueze operatiile specicate. a Un program este un algoritm tradus ntr-un limbaj de programare.

20

CAPITOLUL 3. ALGORITMI

3.2.2

Proprietile algoritmilor at

Principalele proprieti pe care trebuie s le aib un algoritm sunt: at a a Generalitate. Un algoritm trebuie s poat utilizat pentru o clas a a a ntreag de probleme, nu numai pentru o problem particular. Din a a a aceast cauz, o metod de rezolvare a unei ecuatii particulare nu poate a a a considerat algoritm. a Finitudine. Orice algoritm trebuie s permit obtinerea rezultatului a a dup un numr nit de prelucrri (pai). Din aceast cauz, o metod a a a s a a a care nu asigur obtinerea rezultatului dup un numr nit de pai nu a a a s poate considerat algoritm. a Determinism. Un algoritm trebuie s prevad, fr ambiguiti i a a aa at s fr neclariti, modul de solutionare a tuturor situatiilor care pot s aa at a apar rezolvarea problemei. Dac cadrul algoritmului nu intervin a n a n elemente aleatoare, atunci ori de cte ori se aplic algoritmul aceluiai a a s set de date de intrare trebuie s se obtin acelai rezultat. a a s

3.2.3

Tipuri de prelucrri a

Prelucrrile care intervin a ntr-un algoritm pot simple sau structurate. Prelucrrile simple sunt atribuiri de valori variabilelor, eventual prin a evaluarea unor expresii; Prelucrrile structurate pot de unul dintre tipurile: a Liniare. Sunt secvente de prelucrri simple sau structurate a care sunt efectuate ordinea care sunt specicate; n n Alternative. Sunt prelucrri caracterizate prin faptul c a a n functie de realizarea sau nerealizarea unei conditii se alege una din dou sau mai multe variante de prelucrare; a Repetitive. Sunt prelucrri caracterizate prin faptul c a a aceeai prelucrare (simpl sau structurat) este repetat ct s a a a a timp este ndeplinit o anumit conditie. a a

3.3

Descrierea algoritmilor

Algoritmii nu sunt programe, deci ei nu trebuie specicati ntr-un limbaj de programare. Detaliile sintactice, de exemplu din Pascal, C/C++ sau Java, nu au nici o important elaborarea/proiectarea algoritmilor. a n Pe de alt parte, descrierea limba romn (ca i limba englez [15]) a n a a s n a n mod uzual nu este o idee mai bun. Algoritmii au o serie de structuri - special a n

3.3. DESCRIEREA ALGORITMILOR

21

conditionale, repetitive, i recursivitatea - care sunt departe de a putea descrise s prea uor limbaj natural. La fel ca orice limb vorbit, limba romna este plin s n a a a a de ambiguiti, sub elesuri i nuante de semnicatie, iar algoritmii trebuie s e at nt s a descrii cu o acuratete maxim posibil. s a Cea mai bun metod de a descrie un algoritm este utilizarea limbajului a a pseudocod. Acesta folosete structuri ale limbajelor de programare i matematicii s s pentru a descompune algoritmul pai elementari (propozitii simple), dar care n s pot scrise folosind matematica, romna curat, sau un amestec al celor dou. a a a Modul exact de structurare a pseudocodului este o alegere personal. a O descriere foarte bun a algoritmului arat structura intern a acestuia, a a a ascunde detaliile care nu sunt semnicative, i poate implementat uor de s a s ctre orice programator competent orice limbaj de programare, chiar dac el a n a nu elege ce face acel algoritm. Un pseudocod bun, la fel ca i un cod bun, face nt s algoritmul mult mai uor de eles i analizat; el permite de asemenea, mult mai s nt s uor, descoperirea greelilor. s s Pe de alt parte, proba clar se poate face numai pe baza unui program a a care s dea rezultatele corecte! Oamenii sunt oameni! Cineva poate s insiste c a a a algoritmul lui este bun dei ... nu este! Si atunci ... programm! s a

3.3.1

Limbaj natural

Exemple. 1. Algoritmul lui Euclid. Permite determinarea celui mai mare divizor comun (cmmdc) a dou numere naturale a i b. Metoda de determinare a cmmdc poate a s descris limbaj natural dup cum urmeaz. a n a a Se mparte a la b i se retine restul r. Se consider ca nou de artit vechiul s a mp artitor i ca nou mp s mpartitor restul obtinut la artirea anterioar. Operatia mp a de artire continu pn se obtine un rest nul. Ultimul rest nenul (care a fost mp a a a i ultimul artitor) reprezint rezultatul. s mp a Se observ c metoda descris a a a ndeplinete proprietile unui algoritm: poate s at aplicat oricrei perechi de numere naturale iar numrul de prelucrri este nit a a a a (dup un numr nit de artiri se ajunge la un rest nul). a a mp De asemenea se observ c prelucrarea principal a algoritmului este una a a a repetitiv, conditia utilizat pentru a analiza dac s-a terminat prelucrarea ind a a a egalitatea cu zero a restului. 2. Schema lui Horner. Permite determinarea ctului i restului artirii unui a s mp polinom P [X] = an X n + an1 X n1 + ... + a1 X + a0 = 0 la un binom de forma X b. O modalitate simpl de a descrie metoda de rezolvare este schema urmtoare: a a an an1 ... ak ... a2 a1 a0 b an bcn1 + an1 ... bck + ak ... bc2 + a2 bc1 + a1 bc0 + a0 ... ... cn1 cn2 ... ck1 ... c1 c0 P [b]

22

CAPITOLUL 3. ALGORITMI

Valorile cn1 , cn2 , ..., c1 , c0 reprezint coecientii ctului, iar ultima valoare a a calculat reprezint valoarea restului (valoarea polinomului calculat b). a a a n Si acest caz prelucrarea principal este una repetitiv constnd evaluarea n a a a n expresiei bck + ak pentru k lund, aceast ordine, valorile n 1, n 2, ..., 2, 1, 0. a n a

3.3.2

Scheme logice

Scrierea unui program pornind de la un algoritm descris ntr-un limbaj mai mult sau mai putin riguros, ca exemplele de mai sus, este dicil n a ntruct nu a sunt pui evident foarte clar paii algoritmului. s n a s Modaliti intermediare de descriere a algoritmilor, at ntre limbajul natural sau cel matematic i un limbaj de programare, sunt schemele logice i limbajele s s algoritmice. Schemele logice sunt descrieri grace ale algoritmilor care ecrui n a pas i se ataeaz un simbol grac, numit bloc, iar modul de antuire s a nl a blocurilor este specicat prin segmente orientate. Schemele logice au avantajul c sunt sugestive dar i dezavantajul c pot a s a deveni dicil de urmrit cazul unor prelucrri prea complexe. Acest dezavantaj, a n a dar i evolutia modului de concepere a programelor, fac ca schemele logice s e s a din ce ce mai putin folosite ( favoarea limbajelor algoritmice). n n

3.3.3

Pseudocod

Un limbaj algoritmic este o notatie care permite exprimarea logicii algorit milor ntr-un mod formalizat fr a necesare reguli de sintax riguroase, ca aa a n cazul limbajelor de programare. Un limbaj algoritmic mai este denumit i pseudocod. Un algoritm descris s pseudocod contine att enunturi care descriu operatii ce pot traduse direct n a ntr-un limbaj de programare (unui enunt limbaj algoritmic corespunde o n i instructiune program) ct i enunturi ce descriu prelucrri ce urmeaz a n a s a a detaliate abia momentul scrierii programului. n Nu exist un anumit standard elaborarea limbajelor algoritmice, ecare a n programator putnd s conceap propriul pseudocod, cu conditia ca acesta s a a a a permit o descriere clar i neambigu a algoritmilor. Se poate folosi sintaxa lima as a bajului de programare preferat, care apar enunturi de prelucrri. De exemplu: n a for ecare vrf v din V a { culoare[v] = alb; distanta[v] = innit; predecesor[v]=-1; }

3.4. LIMBAJ ALGORITMIC

23

3.4

Limbaj algoritmic
continuare prezentm un exemplu de limbaj algoritmic. In a

3.4.1

Declararea datelor

Datele simple se declar sub forma: a <tip> <nume>; unde <tip> poate lua una dintre valorile: byte, short, int, long, oat, double, boolean, char. Tablourile unidimensionale se declar sub forma: a <tip> <nume> [n1 ..n2 ]; Elementele vectorului pot accesate cu ajutorul unui indice, care poate lua valori ntre n1 i n2 , sub forma: s <nume>[i] unde i poate lua orice valoare ntre n1 i n2 . s cazul tablourilor bidimensionale, o declaratie de forma: In <tip> <nume>[m1 ..m2 ][n1 ..n2 ]; specic o matrice cu m2 m1 + 1 linii i n2 n1 + 1 coloane. Fiecare element se a s specic prin doi indici: a <nume>[i][j] unde i reprezint indicele liniei i poate avea orice valoare a s ntre m1 i m2 iar j s reprezint indicele coloanei i poate avea orice valoare a s ntre n1 i n2 . s

3.4.2

Operatii de intrare/ieire s

Preluarea valorilor pentru datele de intrare este descris sub forma: a read v1 , v2 , ...; unde v1 , v2 , ... sunt nume de variabile. Aarea rezultatelor este descris sub forma: s a write e1 , e2 , ...;

24

CAPITOLUL 3. ALGORITMI

unde e1 , e2 , ... sunt expresii ( particular pot constante sau variabile). n Operatia de atribuire. Operatia de atribuire a unei valori ctre o variabil a a se descrie prin: v = <expresie>; unde v este un nume de variabil, <expresie> desemneaz o expresie aritmetic a a a sau logic, iar = este operatorul de atribuire. Pentru acesta din urm pot a a folosite i alte simboluri, ca de exemplu := sau . Expresiile pot descrise s conform regulilor utilizate matematic. n a

3.4.3

Prelucrri liniare a

O secvent de prelucrri se descrie modul urmtor: a a n a <prel 1>; <prel 2>; ... <prel n>; sau <prel 1>; <prel 2>; ... <prel n>; O astfel de scriere indic faptul c momentul executiei prelucrrile se a a n a efectueaz ordinea care sunt specicate. a n n

3.4.4

Prelucrri alternative a

O prelucrare alternativ complet (cu dou ramuri) este descris prin: a a a a if <conditie> sau sub forma if <conditie> then <prel 1> else <prel 2>; unde <conditie> este o expresie relational. Aceast prelucrare trebuie eleas a a nt a modul urmtor: dac conditia este adevrat atunci se efectueaz prelucrarea n a a a a a <prel 1>, altfel se efectueaz <prel 2>. a O prelucrare alternativ cu o singur ramur se descrie prin: a a a if <conditie> <prel>; sau if <conditie> then <prel>; iar executia ei are urmtorul efect: dac conditia este satisfacut atunci se efectueaz a a a a prelucrarea specicat, altfel nu se efectueaz nici o prelucrare ci se trece la a a urmtoarea prelucrare a algoritmului. a <prel 1> else <prel 2>;

3.4. LIMBAJ ALGORITMIC

25

3.4.5

Prelucrri repetitive a

Prelucrrile repetitive pot de trei tipuri: a cu test initial, cu test nal i s cu contor. Prelucrarea repetitiv cu test initial se descrie prin: Prelucrarea repetitiv cu a a test initial se descrie prin: while <conditie> <prel>; sau while <conditie> do <prel>; momentul executiei, att timp ct conditia este adevarat, se va executa In a a a instructiunea. Dac conditia nu este la a nceput satisfcut, atunci instructiunea a a nu se efectueaz niciodat. a a Prelucrarea repetitiv cu test nal se descrie prin: a do <prel> while <conditie>; Prelucrarea se repet pn cnd conditia specicat devine fals. acest caz a a a a a a In prelucrarea se efectueaz cel putin o dat, chiar dac conditia nu este satisfacut a a a a la nceput. Prelucrarea repetitiv cu contor se caracterizeaz prin repetarea prelucrrii a a a de un numr prestabilit de ori i este descris prin: a s a for i = i1 , i2 , ..., in <prel>; sau for i = i1 , i2 , ..., in do <prel>; unde i este variabila contor care ia, pe rnd, valorile i1 , i2 , ..., in aceast a n a ordine, prelucrarea ind efectuat pentru ecare valoare a contorului. a Alte forme utilizate sunt: for i = vi to vf do <prel>;

care contorul ia valori consecutive cresctoare n a ntre vi i vf , i s s for i = vi downto vf do <prel>;

care contorul ia valori consecutive descresctoare n a ntre vi i vf . s

26

CAPITOLUL 3. ALGORITMI

3.4.6

Subalgoritm

cadrul unui algoritm poate s apar necesitatea de a specica de mai In a a multe ori i diferite locuri un grup de prelucrri. Pentru a nu le descrie s n a n mod repetat ele pot constitui o unitate distinct, identicabil printr-un nume, a a care este numit subalgoritm. Ori de cte ori este necesar efectuarea grupului a a a de prelucrri din cadrul subalgoritmului se specic numele acestuia i, eventual, a a s datele curente asupra crora se vor efectua prelucrarile. Aceast actiune se numete a a s apel al subalgoritmului, iar datele specicate alturi de numele acestuia i asupra a s crora se efectueaz prelucrarile se numesc parametri. a a urma traducerii In ntr-un limbaj de programare un subalgoritm devine un subprogram. Un subalgoritm poate descris felul urmtor: n a <nume subalg> (<tip> <nume p1>, <tip> <nume p2>, ... ) { ... /* prelucrri specice subalgoritmului */ a ... return <nume rezultat>; } unde <nume subalg> reprezint numele subalgoritmului iar nume p1, nume p2, a ... reprezint numele parametrilor. Ultimul enunt, prin care se returneaz rezultatul a a calculat cadrul subalgoritmului, este optional. n Modul de apel depinde de modul care subalgoritmul returneaz rezultatele n a sale. Dac subalgoritmul returneaz efectiv un rezultat, printr-un enunt de forma a a return <nume rezultat>; atunci subalgoritmul se va apela felul urmtor: n a v=<nume subalg>(nume p1, nume p2, ...); Aceti subalgoritmi corespund subprogramelor de tip functie. s Dac subalgoritm nu apare un astfel de enunt, atunci el se va apela prin: a n <nume subalg>(nume p1, nume p2, ...); variant care corespunde subprogramelor de tip procedur. a a Observatie. Prelucrrile care nu sunt detaliate cadrul algoritmului sunt a n descrise limbaj natural sau limbaj matematic. Comentariile suplimentare vor n cuprinse ntre /* i */. Dac pe o linie a descrierii algoritmului apare simbolul // s a atunci tot ce urmeaz dup acest simbol, pe aceeai linie cu el, este interpretat ca a a s ind un comentariu (deci, nu reprezint o prelucrare a algoritmului). a

3.4. LIMBAJ ALGORITMIC

27

3.4.7

Probleme rezolvate

1. Algoritmului lui Euclid. Descrierea pseudocod a algoritmului lui Euclid este urmtoarea: n a int a, b, d, i, r; read a, b; if (a<b) { d=a; i=b; } else { d=b; i=a; }; r = d % i; while (r ! = 0) { d=i; i=r; r=d % i; }; write i; 2. Schema lui Horner. Descrierea pseudocod a schemei lui Horner este urmtoarea: n a int n, a, b, i; read n, a, b; int a[0..n], c[0..n-1]; for i=n,0,-1 read a[i]; c[n-1]=b*a[n]; for i=1,n-1 c[n-i-1]=b*c[n-i]+a[n-i]; val:=b*c[1]+a[1]; write val; 3. Conversia unui numr natural din baza 10 baza 2. a n Fie n un numr a ntreg pozitiv. Pentru a determina cifrele reprezentarii n baza doi a acestui numr se poate folosi urmtoarea metod: a a a Se mparte n la 2, iar restul va reprezenta cifra de rang 0. Ctul obtinut la a mpartirea anterioar se a mparte din nou la 2, iar restul obtinut va reprezenta cifra de ordin 1 .a.m.d. Secventa de artiri continu p a la obtinerea unui ct nul. s mp a n a Descrierea pseudocod a acestui algoritm este: n int n, d, c, r; read n; d = n; c = d / 2; /* ctul artirii a mp ntregi a lui d la 2 */ r = d % 2; /* restul artirii mp ntregi a lui d la 2 */ write r; while (c != 0) { d = c; c = d / 2; /* ctul artirii a mp ntregi a lui d la 2 */ r = d % 2; /* restul artirii mp ntregi a lui d la 2 */ write r; } 4. Conversia unui numr a ntreg din baza 2 baza 10. n

28

CAPITOLUL 3. ALGORITMI

Dac bk bk1 ...b1 b0 reprezint cifrele numrului baza 2, atunci valoarea a a a n n baza 10 se obtine efectu calculul: nd (bk bk1 ...b1 b0 )10 = bk 2k + bk1 2k1 + ... + b1 2 + b0 Dei calculul de mai sus este similar cu evaluarea pentru X = 2 a polinomului s P [X] = bk X k + bk1 X k1 + ... + b1 X + b0 prelucrare pentru care ar putea folosit algoritmul corespunztor schemei lui a Horner, continuare prezentm o alt variant de rezolvare a acestei probleme, n a a a care folosete un subalgoritm pentru calculul puterilor unui numr s a ntreg: int k, i, s; putere(int a, int n) read k; { int b[0..k]; int i, p; read b; p = 1; s = 0; for i=2,n p = p*a; for i=0,k s = s+b[i] * putere(2,i); return p; write s; } 5. S se scrie un algoritm pentru determinarea tuturor divizorilor naturali ai a unui numr a ntreg. Rezolvare. Fie n numrul ai crui divizori trebuie determinati. Evident 1 i a a s |n| sunt divizori ai lui n. Pentru a determina restul divizorilor este sucient ca acetia s e cutati printre elementele multimii {2, 3, ..., [|n|]} cu [x] desemnnd s a a a partea ntreag a lui x. a Algoritmul poate descris modul urmtor: n a int n, d; read n; write 1; /* aarea primului divizor */ s for d = 2, [|n|/2] if (d divide pe n) then write d; write |n| /* aarea ultimului divizor */ s 6. S se scrie un algoritm pentru determinarea celui mai mare element dintra un ir de numere reale. s Rezolvare. Fie x1 , x2 , ..., xn irul analizat. Determinarea celui mai mare eles ment const initializarea unei variabile de lucru max (care va contine valoarea a n maximului) cu x1 i compararea acesteia cu ecare dintre celelalte elemente ale s irului. Dac valoarea curent a irului, xk , este mai mare dect valoarea variaas a a s a bilei max atunci acesteia din urm i se va da valoarea xk . Astfel, dup a k 1 a a comparatie variabila max va contine valoarea maxim din subirul x1 , x2 , ..., xk . a s Algoritmul poate descris modul urmtor: n a

3.4. LIMBAJ ALGORITMIC int k, n; read n; double x[1..n], max; /* vectorul i variabila de lucru */ s read x; /* preluarea elementelor irului */ s max = x[1]; for k = 2, n if (max < x[k]) then max = x[k]; write max; 7. S se aproximeze, cu precizia , limita irului a s
n

29

sn =
k=0

1 . k!

Rezolvare. Calculul aproximativ (cu precizia ) al limitei irului sn const s a n 1 calculul sumei nite sk , unde ultimul termen al sumei, tk = k! , are proprietatea tk a a tk < . Intruct tk+1 = k+1 , aceast relatie va folosit pentru calculul valorii a termenului curent (permitnd micorarea numrului de calcule). a s a double eps, t, s; int k; k=1; /* initializare indice */ t=1; /* initializare termen */ s=1; /* initializare suma */ do { s=s+t; /* adugarea termenului curent */ a k=k+1; t=t/k; /* calculul urmtorului termen */ a } while (t eps); s=s+t; (* adugarea ultimului termen *) a write s; 8. Fie A o matrice cu m linii i n coloane, iar B o matrice cu n linii i p coloane, s s ambele avnd elemente reale. S se determine matricea produs C = A B. a a Rezolvare. Matricea C va avea m linii i p coloane, iar ecare element se s determin efectund suma: a a
n

ci,j =
k=1

ai,k bk,j ,

1 i m, 1 j p.

felul acesta calculul elementelor matricei C se efectueaz prin trei cicluri In a imbricate (unul pentru parcurgerea liniilor matricei C, unul pentru parcurgerea coloanelor matricei C, iar unul pentru efectuarea sumei specicate mai sus).

30

CAPITOLUL 3. ALGORITMI int m, n, p; /* dimensiunile matricelor */ read m, n, p; double a[1..m][1..n], b[1..n][1..p], c[1..m][1..p]; int i, j, k; /* indici */ read a; /* citirea matricei a */ read b; /* citirea matricei b */ for i=1,m for j=1,p { c[i,j]=0; for k=1,n c[i][j]=c[i][j]+a[i][k]*b[k][j]; } write c;

/* matrice */

3.4.8

Probleme propuse

1. Fie D o dreapt de ecuatie ax+by+c = 0 i (C) un cerc de centru O(x0 , y0 ) a s i raz r. S se stabileasc pozitia dreptei fat de cerc. s a a a a Indicatie. Se calculeaz distanta de la centrul cercului la dreapta D utiliznd a a formula: |ax0 + by0 + c| . d= a2 + b2 Dac d r+ atunci dreapta este exterioar cercului, dac d r atunci dreapta a a a este secant, iar dac r < d < r + atunci este tangent (la implementarea a a a egalitatea ntre dou numere reale ...). a 2. S se genereze primele n elemente ale irurilor ak i bk date prin relatiile a s s de recurent: a ak+1 = 5ak + 3 , ak + 3 bk = ak + 3 , ak + 1 k 0, a0 = 1.

3. S se determine rdcina ptrat a unui numr real pozitiv a cu precizia a a a a a u = 0.001, folosind relatia de recurent: a xn+1 = 1 2 xn + a xn , x1 = a.

Precizia se consider atins cnd |xn+1 xn | < . a a a 4. Fie A o matrice ptratic de dimensiune n. S se transforme matricea A, a a a prin interrschimbri de linii i de coloane, astfel at elementele de pe diagonala a s nc principal s e ordonate cresctor. a a a

3.4. LIMBAJ ALGORITMIC

31

5. S se determine cel mai mare divizor comun al unui ir de numere a s ntregi. 6. S se calculeze coecientii polinomului a P [X] = (aX + b)n , a, b Z, n N.

7. Fie A o matrice ptratic. S se calculeze suma elementelor din ecare zon a a a a (diagonala principal, diagonala secundar, etc.) marcat gura urmtoare: a a a n a

a.

b.

c.

Figura 3.1: Zone matrice ptratic n a a

8. Fie x1 , x2 , ..., xn Z rdcinile unui polinom cu coecienti a a ntregi: P [X] = an X n + an1 X n1 + ... + a1 X + a0 . S se determine coecientii polinomului. a 9. S se determine toate rdcinile rationale ale polinomului P [X] care are a a a coecienti ntregi. 140. Fie [P1 , P2 , ..., Pn ] un poligon convex dat prin coordonatele carteziene ale vrfurilor sale ( ordine trigonometric). S se calculeze aria poligonului. a n a a 11. Fie f : [a, b] R o functie continu cu proprietatea c exist un unic a a a (a, b) care are proprietatea c f () = 0. S se aproximeze cu precizia = 0.001 a a utiliznd metoda bisectiei. a 12. Fie P i Q polinoame cu coecienti s ntregi. S se determine toate rdcinile a a a rationale comune celor dou polinoame. a 13. S se determine toate numerele prime cu maxim 6 cifre care rmn prime a a a i dup rsturnarea lor (rsturnatul numrului abcdef este f edcba). s a a a a

32

CAPITOLUL 3. ALGORITMI

3.5

Instructiuni corespondente limbajului algorit mic

3.5.1

Declararea datelor

Datele simple se declar sub forma: a <tip> <nume>; sau <tip> <nume>= literal; unde <tip> poate lua una dintre urmtoarele valori: byte, short, int, long, oat, a double, boolean, char. exemplul urmtor sunt prezentate cteva modaliti In a a at de declarare pentru diferite tipuri de variabile. class Literali { public static void main(String args[]) { long l1 = 5L; long l2 = 12l; int i1hexa = 0x1; int i2hexa = 0X1aF; int i3octal = 01; long i4octal = 012L; long i5LongHexa = 0xAL; float f1 = 5.40F; float f2 = 5.40f; float f3 = 5.40e2f; float f4 = 5.40e+12f; float f5 = 5.40; // da eroare, trebuie cast double d1 = 5.40; // implicit este double ! double d2 = 5.40d; double d3 = 5.40D; double d4 = 5.40e2; double d5 = 5.40e+12d; char c1 = r; char c2 = \u4567; } }

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC

33

Java denete mai multe tipuri primitive de date. Fiecare tip are o anumit s a dimensiune, care este independent de caracteristicile mainii gazd. Astfel, spre a s a deosebire de C/C++, unde un ntreg poate reprezentat pe 16, 32 sau 64 de biti, n functie de arhitectura mainii, o valoare de tip s ntreg Java va ocupa n ntotdeauna 32 de biti, indiferent de maina pe care ruleaz. Aceast consecvent este esential s a a a a deoarece o aceeai aplicatie va trebui s ruleze pe maini cu arhitectur pe 16, 32 s a s a sau 64 de biti i s produc acelai rezultat pe ecare main parte. s a a s s a n Tip byte short int long oat double boolean char Dimensiune (octeti) 1 2 4 8 4 8 1 2 Valoare minima 27 215 231 263 +-1.4E-45 +-4.94E-324 Valoare maxima 27 1 215 1 231 1 263 1 +-3.4E+38 +-1.79E+308 Valoare initiala 0 0 0 0 0 0 f alse null Cifre semnicative

6-7 14-15

Tabelul 3.1: Tipurile primitive de date Java n Variabilele pot initializate la declararea lor sau momentul utilizrii lor n a efective. Dac valoarea nu este specicat explicit atunci variabila se initializeaz a a a cu o valoare initial implicit. Tabelul anterior prezint cteva exemple acest a a a a n sens. Conversiile ntre diferitele tipuri sunt permise (acolo unde au semnicatie). Se vede din tabel c unele tipuri de variabile au posibilitatea s reprezinte un a a spectru mai mare de numere dect altele. a afara tipurilor de baz, limbajul Java suport i tipuri de date create In a a s de utilizator, de pild variabile de tip clas, interfata sau tablou. Ca i celelalte a a s variabile, dac nu sunt explicit initializate, valoarea atribuit implicit este null. a a Modicatorul static este folosit pentru a specica faptul c variabila are a o singur valoare, comun tuturor instantelor clasei care ea este declarat. a a n a Modicarea valorii acestei variabile din interiorul unui obiect face ca modicarea s e vizibil din celelalte obiecte. Variabilele statice sunt initializate la arcarea a a nc codului specic unei clase i exist chiar i dac nu exist nici o instant a clasei s a s a a a respective. Din aceast cauz, ele pot folosite de metodele statice. a a Tablourile unidimensionale se declar sub forma: a <tip>[ ] <nume> =new <tip>[n]; sau <tip> <nume>[ ] =new <tip>[n];

34

CAPITOLUL 3. ALGORITMI Elementele vectorului pot accesate cu ajutorul unui indice, sub forma: <nume>[i]

unde i poate lua orice valoare ntre 0 i n 1. s cazul tablourilor bidimensionale, o declaratie de forma: In <tip>[ ] [ ] <nume> = new <tip>[m][n]; sau <tip> <nume> [ ] [ ] = new <tip>[m][n]; specic o matrice cu m linii i n coloane. Fiecare element se specic prin doi a s a indici: <nume>[i][j] unde i reprezint indicele liniei i poate avea orice valoare a s ntre 0 i m 1 iar j s reprezint indicele coloanei i poate avea orice valoare a s ntre 0 i n 1. s

3.5.2

Operatii de intrare/ieire s

Preluarea unei valori de tip int de la tastatur se poate face sub forma: a BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); int vi=Integer.parseInt(br.readLine()); iar dintr-un ier (de exemplu fis.in), sub forma: s StreamTokenizer st = new new new st.nextToken(); int vi = StreamTokenizer( BufferedReader( FileReader("fis.in"))); (int) st.nval;

Scrierea valorii unei variabile v pe ecran se poate face sub forma: System.out.print(v); iar ntr-un ier (de exemplu fis.out), sub forma: s PrintWriter out = new PrintWriter( new BufferedWriter( new FileWriter("fis.out"))); out.print(v); out.close();

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC

35

3.5.3

Prelucrri liniare a

O secvent de prelucrri se descrie modul urmtor: a a n a <instr 1>; <instr 2>; ... <instr n>; sau <instr 1>; <instr 2>; ... <instr n>; O astfel de scriere indic faptul c momentul executiei instructiunile se a a n efectueaz ordinea care sunt specicate. a n n

3.5.4

Prelucrri alternative a

O prelucrare alternativ complet (cu dou ramuri) este descris prin: a a a a if (<conditie>) <instr 1> else <instr 2>;

unde <conditie> este o expresie relational. Aceast prelucrare trebuie eleas a a nt a modul urmtor: dac conditia este adevrat atunci se efectueaz prelucrarea n a a a a a a <instr 1>, altfel se efectueaz <instr 2>. O prelucrare alternativ cu o singur ramur se descrie prin: a a a if (<conditie>) <instr>; iar executia ei are urmtorul efect: dac conditia este satisfacut atunci se efectueaz a a a a instructiunea specicat, altfel nu se efectueaz nici o prelucrare ci se trece la a a urmtoarea prelucrare a algoritmului. a

3.5.5

Prelucrri repetitive a

Prelucrrile repetitive pot de trei tipuri: a cu test initial, cu test nal i s cu contor. Prelucrarea repetitiv cu test initial se descrie prin: a while (<conditie>) <instr>; momentul executiei, att timp ct conditia este adevarat, se va executa In a a a prelucrarea. Dac conditia nu este la a nceput satisfcut, atunci prelucrarea nu se a a efectueaz niciodat. a a Prelucrarea repetitiv cu test nal se descrie prin: a

36

CAPITOLUL 3. ALGORITMI do <instr> while (<conditie>);

Instructiunea se repet pn cnd conditia specicat devine fals. acest a a a a a a In caz prelucrarea se efectueaz cel putin o dat, chiar dac conditia nu este satisfaa a a cut la a nceput. Prelucrarea repetitiv cu contor se caracterizeaz prin repetarea prelucrrii a a a de un numr prestabilit de ori i este descris prin: a s a for(<instr1> ; <conditie>; <instr2>) <instr3>; general <instr1> reprezint etapa de initializare a contorului, <instr2> In a reprezint etapa de incrementare a contorului, <instr3> reprezint instructiunea a a care se execut mod repetat ct timp conditia <conditie> are valoarea true. a n a

3.5.6

Subprograme

cadrul unui program poate s apar necesitatea de a specica de mai multe In a a ori i diferite locuri un grup de prelucrri. Pentru a nu le descrie mod repetat s n a n ele pot constitui o unitate distinct, identicabil printr-un nume, care este numit a a a subprogram sau, mai precis, functie (dac returneaz un rezultat) sau procedur a a a (dac nu returneaz nici un rezultat). Java functiile i procedurile se numesc a a In s metode. Ori de cte ori este necesar efectuarea grupului de prelucrri din cadrul a a a programului se specic numele acestuia i, eventual, datele curente asupra crora a s a se vor efectua prelucrarile. Aceast actiune se numete apel al subprogramului, a s iar datele specicate alturi de numele acestuia i asupra crora se efectueaz a s a a prelucrarile se numesc parametri. Un subprogram poate descris felul urmtor: n a <tipr> <nume sp> (<tipp1> <numep1>, <tipp2> <numep2>, ... ) { ... /* prelucrri specice subprogramului */ a ... return <nume rezultat>; } unde <tipr> reprezint tipul rezultatului returnat (void dac subprogramul nu a a returneaz nici un rezultat), <nume sp> reprezint numele subprogramului, iar a a numep1, numep2, ... reprezint numele parametrilor. Ultimul enunt, prin care se a returneaz rezultatul calculat cadrul subprogramului, trebuie pus numai dac a n a <tipr> nu este void. Modul de apel depinde de modul care subprogramul returneaz rezultatele n a sale. Dac subprogramul returneaz efectiv un rezultat, printr-un enunt de forma a a return <nume rezultat>;

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC atunci subprogramul se va apela felul urmtor: n a v=<nume sp>(nume p1, nume p2, ...);

37

Aceste subprograme corespund subprogramelor de tip functie. Dac subprogram nu apare un astfel de enunt, atunci el se va apela prin: a n <nume sp>(nume p1, nume p2, ...); variant care corespunde subprogramelor de tip procedur. a a Observatie. Prelucrrile care nu sunt detaliate cadrul algoritmului sunt a n descrise limbaj natural sau limbaj matematic. Comentariile suplimentare vor n cuprinse ntre /* i */. Dac pe o linie a descrierii algoritmului apare simbolul // s a atunci tot ce urmeaz dup acest simbol, pe aceeai linie cu el, este interpretat ca a a s ind un comentariu (deci, nu reprezint o prelucrare a programului). a

3.5.7

Probleme rezolvate

1. Descompunere Fibonacci. S se descompun un numr natural, de cel mult a a a 18-19 cifre, sum de ct mai putini termeni Fibonacci. n a a Rezolvare: Programul urmtor calculeaz i aeaz primii 92 de termeni a a s s a din irul Fibonacci (mai mult nu este posibil fr numere mari!), i descompune s aa s numrul x introdus de la tastatur. Metoda static int maxFibo ( long nr ) a a returneaz indicele celui mai mare element din irul lui Fibonacci care este mai a s mic sau egal cu parametrul nr. import java.io.*; class DescFibo { static int n=92; static long[] f=new long[n+1]; public static void main (String[]args) throws IOException { long x,y; int iy, k, nrt=0; BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); System.out.print("x = "); x=Long.parseLong(br.readLine()); f[0]=0; f[1]=1; f[2]=1; for(k=3;k<=n;k++) f[k]=f[k-1]+f[k-2]; for(k=0;k<=n;k++) System.out.println(k+" : "+f[k]); System.out.println(" "+Long.MAX_VALUE+" = Long.MAX_VALUE");

38

CAPITOLUL 3. ALGORITMI System.out.println("x = "+x); while(x>0) { iy=maxFibo(x); y=f[iy]; nrt++; System.out.println(nrt+" : "+x+" f["+iy+"] = "+y); x=x-y; } } static int maxFibo(long nr) { int k; for(k=1;k<=n;k++) if (f[k]>nr) break; return k-1; }

} De exemplu, pentru x = 5678 pe ecran apare: 1 2 3 4 5 6 7 : : : : : : : 5678 f[19] = 418 1497 f[16] = 987 510 f[14] = 377 133 f[11] = 89 44 f[9] = 34 10 f[6] = 8 2 f[3] = 2

s a a ntregi 2. Fie Sn = xn +xn unde x1 i x2 sunt rdcinile ecuatiei cu coecienti 2 1 ax + bx + c = 0 (vom considera a = 1!). S se aeze primii 10 termeni ai irului a s s Sn i s se precizeze dreptul ecrui termen dac este numr prim, iar dac nu s a n a a a a este numr prim s se aeze descompunerea factori. a a s n Rezolvare:
2

class e02 { public static void main(String[] args) { int a, b, c, nnp=0, s, p, n=10, k; long[] ss=new long[n+1]; a=1;b=1;c=2; s=-b/a;

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC p=c/a; ss[1]=s; ss[2]=s*s-2*p; for(k=3;k<=n;k++) ss[k]=s*ss[k-1]-p*ss[k-2]; for(k=1;k<=n;k++) if(esteprim(Math.abs(ss[k]))) System.out.println(k+" : "+ss[k]+" PRIM "+(++nnp)); else { System.out.print(k+" : "+ss[k]+" = "); descfact(Math.abs(ss[k])); } System.out.println("nnp = "+nnp); }// main static void descfact(long nr) { long d=2; if((nr==0)||(nr==1)){System.out.println(); return;} while(nr%d==0){System.out.print(d+""); nr=nr/d;} d=3; while((d*d<=nr)&&(nr!=1)) { while(nr%d==0){System.out.print(d+" "); nr=nr/d;} d=d+2; } if(nr!=1) System.out.println(nr); else System.out.println(); } static boolean esteprim(long nr) { if((nr==0)||(nr==1)) return false; if((nr==2)||(nr==3)) return true; if(nr%2==0) return false; long d=3; while((nr%d!=0)&&(d*d<=nr)) d=d+2; if(nr%d==0) return false; else return true; } }// class Pe ecran apar urmtoarele rezultate: a 1 : -1 = 2 : -3 PRIM 1

39

40 3 : 5 PRIM 2 4 : 1 = 5 : -11 PRIM 3 6 : 9 = 3 3 7 : 13 PRIM 4 8 : -31 PRIM 5 9 : 5 PRIM 6 10 : 57 = 3 19 nnp = 6 Press any key to continue...

CAPITOLUL 3. ALGORITMI

3. Se consider functia f (x) = P (x)ex unde P (x) este un polinom de grad n a cu coecienti ntregi. S se aeze toate derivatele pn la ordinul m ale functiei f , a s a a i, dreptul coecientilor polinoamelor care apar aceste derivate, s se precizeze s n n a dac respectivul coecient este numr prim, iar dac nu este numr prim s se a a a a a aeze descompunerea factori. De asemenea, s se aeze care este cel mai mare s n a s numr prim care apare, i care este ordinul derivatei care apare acest cel mai a s n mare numr prim. a Rezolvare: Derivata functiei f are forma Q(x)ex unde Q este un polinom de acelai grad cu polinomul P . Toat rezolvarea problemei se reduce la determinarea s a coecientilor polinomului Q functie de coecientii polinomului P . n class e03 { static long npmax=1,pmax=0; public static void main(String[] args) { int n=7, m=10, alfa=1, k; long[] p=new long[n+1]; p[7]=1; p[3]=1; p[0]=1; afisv(p,0); for(k=1;k<=m;k++) { System.out.print("derivata = "+k); p=deriv(p,alfa); afisv(p,k); } System.out.println(npmax+" "+pmax); System.out.println("GATA!!!"); } static long[] deriv(long[] a,int alfa)

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC { int n=a.length-1, k; long[] b=new long[n+1]; b[n]=a[n]*alfa; for(k=0;k<=n-1;k++) b[k]=(k+1)*a[k+1]+a[k]*alfa; return b; } static void afisv(long[] x,int ppp) { int n=x.length-1; int i; System.out.println(); for(i=n;i>=0;i--) if(esteprim(Math.abs(x[i]))) { System.out.println(i+" : "+x[i]+" PRIM "); if(npmax<Math.abs(x[i])) { npmax=Math.abs(x[i]); pmax=ppp; } } else { System.out.print(i+" : "+x[i]+" = "); descfact(Math.abs(x[i])); } System.out.println(); } static void descfact(long nr) { long d=2; if((nr==0)||(nr==1)) { System.out.println(); return; } while(nr%d==0) { System.out.print(d+" "); nr=nr/d; }

41

42 d=3; while((d*d<=nr)&&(nr!=1)) { while(nr%d==0) { System.out.print(d+" "); nr=nr/d; } d=d+2; } if(nr!=1) System.out.println(nr); else System.out.println(); }

CAPITOLUL 3. ALGORITMI

static boolean esteprim(long nr) { if((nr==0)||(nr==1)) return false; if((nr==2)||(nr==3)) return true; if(nr%2==0) return false; long d=3; while((nr%d!=0)&&(d*d<=nr)) d=d+2; if(nr%d==0) return false; else return true; } }// class

4. Rdcini rationale. S se determine toate rdcinile rationale ale unei a a a a a ecuatii cu coecienti ntregi. Rezolvare: Se caut rdcini rationale formate din fractii care numrtorul a a a n aa este divizor al termenului liber iar numitorul este divizor al termenului dominant. Programul care urmeaz genereaz coecientii ecuatiei, plecnd de la fractii date a a a (ca rdcini), i apoi determin rdcinile rationale a a s a a a class RadaciniRationale // generez p_i/q_i { static int k=0; public static void main(String[] args) { int[] p={1,1,2,3, 3, 1}, q={2,3,3,2,-2,-1}; int[] a=genPol(p,q); int n=a.length-1,alfa,beta; int moda0=Math.abs(a[0]),modan=Math.abs(a[n]); for(alfa=1;alfa<=moda0;alfa++)

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC {

43

if(moda0%alfa!=0) continue; for(beta=1;beta<=modan;beta++) { if(modan%beta!=0) continue; if(cmmdc(alfa,beta)!=1) continue; if (f(a,alfa,beta)==0) System.out.println("x["+(++k)+"] = "+alfa+"/"+beta+" "); if (f(a,-alfa,beta)==0) System.out.println("x["+(++k)+"] = -"+alfa+"/"+beta+" "); }// for beta }// for alfa }// main static int[] genPol(int[] a, int[] b) // X-a_i/b_i==>b_i X - a_i { int n=a.length; int[] p={-a[0],b[0]},//p=b[0] X -a[0] q={13,13}; // q initializat "aiurea" - pentru dimensiune ! afisv(p); for(int k=1;k<n;k++) { q[0]=-a[k]; q[1]=b[k]; p=pxq(p,q); afisv(p); } return p; }// genPol() static int[] pxq(int[] p,int[] q) { int gradp=p.length-1, gradq=q.length-1; int gradpq=gradp+gradq; int[] pq=new int[gradpq+1]; int i,j,k; for(k=0;k<=gradpq;k++) pq[k]=0; for(i=0;i<=gradp;i++) for(j=0;j<=gradq;j++) pq[i+j]+=p[i]*q[j]; return pq; } static int f(int[]a,int alfa, int beta) {

44

CAPITOLUL 3. ALGORITMI int n=a.length-1,k,s=0; for(k=0;k<=n;k++) s+=a[k]*putere(alfa,k)*putere(beta,n-k); return s; } static int putere(int a, int n) { int p=1; for(int k=1;k<=n;k++) p*=a; return p; } static int cmmdc(int a, int b) { int d,i,c,r; if (a>b) {d=a; i=b;} else {d=b; i=a;} r=123; // ca sa inceapa while !!! while (r > 0){c=d/i; r=d%i; d=i; i=r;} return d; }

static void afisv(int[] a) { for(int i=a.length-1;i>=0;i--) System.out.print(a[i]+" "); System.out.println(); }// afisv() }// class 5. S se aeze frecventa cifrelor care apar a s n
n

f (n) =
k=0

1 n C 2k n+k

netinnd cont de faptul c f (n) are o expresie mult mai simpl, i anume 2n . Suma a a a s trebuie calculat simulnd operatiile de adunare, a a nmultire i artire la 2, cu s mp numere mari. Rezolvare: Functia se pune sub forma: 1 f (n) = n 2
n n 2nk Cn+k k=0

Se calculeaz suma, i apoi se fac n artiri succesive la 2. a s mp class e05

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC { public static void main (String[]args) { int n, k; int[] s; int[] p; for(n=10;n<=12;n++) { s=nrv(0); for(k=0;k<=n;k++) { p=inm(comb(n+k,n),putere(2,n-k)); s=suma(s,p); } afisv(s); for(k=1;k<=n;k++) s=impartLa2(s); System.out.print(n+" : "); afisv(s); fcifre(s); } System.out.println("GATA"); }//main() static int[] impartLa2(int[] a) { int na,nb,k,t=0; na=a.length-1; if(a[na]==1) nb=na-1; else nb=na; int[] b=new int[nb+1]; if(na==nb) for(k=na;k>=0;k--) {a[k]+=10*t; b[k]=a[k]/2; t=a[k]%2;} else { t=a[na]; for(k=na-1;k>=0;k--){a[k]+=10*t; b[k]=a[k]/2; t=a[k]%2;} } return b; } static void fcifre(int[] x) { int i; int[] f=new int[10]; for(i=0;i<x.length;i++) f[x[i]]++;

45

46

CAPITOLUL 3. ALGORITMI System.out.println(); for(i=0;i<=9;i++) System.out.println(i+" : "+f[i]); System.out.println(); } static int[] suma(int[] x, int[] y) { int i, j, t, ncx=x.length, ncy=y.length, ncz; if(ncx>ncy) ncz=ncx+1; else ncz=ncy+1; int[] xx=new int[ncz]; int[] yy=new int[ncz]; int[] z=new int[ncz]; for(i=0;i<ncx;i++) xx[i]=x[i]; for(j=0;j<ncy;j++) yy[j]=y[j]; t=0; for(i=0;i<ncz;i++){z[i]=xx[i]+yy[i]+t; t=z[i]/10; z[i]=z[i]%10;} if(z[ncz-1]!= 0) return z; else { int[]zz=new int[ncz-1]; for(i=0;i<=ncz-2;i++) zz[i]=z[i]; return zz; } } static int[] inm(int[]x,int[]y) { int t, n=x.length, m=y.length, i, j; int[][]a=new int[m][n+m]; int[]z=new int[m+n]; for(j=0;j<m;j++) { t=0; for(i=0;i<n;i++) { a[j][i+j]=y[j]*x[i]+t; t=a[j][i+j]/10; a[j][i+j]=a[j][i+j]%10; } a[j][i+j]=t; } t=0; for(j=0;j<m+n;j++) {

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC z[j]=0; for(i=0;i<m;i++) z[j]=z[j]+a[i][j]; z[j]=z[j]+t; t=z[j]/10; z[j]=z[j]%10; } if(z[m+n-1]!= 0) return z; else { int[]zz=new int[m+n-1]; for(i=0;i<=m+n-2;i++) zz[i]=z[i]; return zz; } } static void afisv(int[]x) { int i; for(i=x.length-1;i>=0;i--) System.out.print(x[i]); System.out.print(" *** "+x.length); System.out.println(); } static int[] nrv(int nr) { int nrrez=nr, nc=0; while(nr!=0) {nc++; nr=nr/10;} int[]x=new int [nc]; nr=nrrez; nc=0; while(nr!=0){x[nc]=nr%10; nc++; nr=nr/10;} return x; } static int[] putere (int a, int n) { int[] rez; int k; rez=nrv(1); for(k=1;k<=n;k++) rez=inm(rez,nrv(a)); return rez; }

47

48

CAPITOLUL 3. ALGORITMI static int[] comb (int n, int k) { int[] rez; int i, j, d; int[]x=new int[k+1]; int[]y=new int[k+1]; for(i=1;i<=k;i++) x[i]=n-k+i; for(j=1;j<=k;j++) y[j]=j; for(j=2;j<=k;j++) { for(i=1;i<=k;i++) { d=cmmdc(y[j],x[i]); y[j]=y[j]/d; x[i]=x[i]/d; if(y[j]==1) break; } } rez=nrv(1); for(i=1;i<=k;i++) rez=inm(rez,nrv(x[i])); return rez; }

static int cmmdc (int a,int b) { int d,i,c,r; if (a>b) {d=a;i=b;} else{d=b;i=a;} while (i!=0){c=d/i; r=d%i; d=i; i=r;} return d; } }// class 6. S se aeze S(n, 1), S(n, 2), ..., S(n, m) (inclusiv suma cifrelor i numrul a s s a cifrelor pentru ecare num) tiind c a s a S(n + 1, m) = S(n, m 1) + mS(n, m) i s S(n, 1) = S(n, n) = 1, n m. Se vor implementa operatiile cu numere mari. Rezolvare: Matricea de calcul este subdiagonal. Se completeaz cu 1 prima a a coloan i diagonala principal, iar apoi se determin celelalte elemente ale matricei as a a folosind relatia dat (aranjat putin altfel!). Matricea de calcul va avea de fapt trei a a

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC

49

dimensiuni (numerele devin foarte mari, aa c elementul Si,j trebuie s contin s a a a vectorul cifrelor valorii sale). class e06 { public static void main(String[] args) { int n=50, m=40, i, j; int[][][] s=new int[n+1][m+1][1]; for(i=1;i<=n;i++) { if(i<=m) s[i][i]=nr2v(1); s[i][1]=nr2v(1); for(j=2;j<=min(i,m);j++) s[i][j]=suma(s[i-1][j-1],inm(nr2v(j),s[i-1][j])); if(i<=m) s[i][i]=nr2v(1); } for(i=1;i<=m;i++) { System.out.print("\n"+i+" : "+s[n][i].length+" "); afissumac(s[n][i]); afisv(s[n][i]); } } static static static static int[] suma(int[] x,int[] y){...} int[] nr2v(int nr){...} int[] inm(int[]x, int[]y){...} void afisv(int[]x){...}

static void afissumac(int[]x) { int i,s=0; for(i=x.length-1;i>=0;i--) s+=x[i]; System.out.print(s+" "); } static int min(int a, int b) { return (a<b)?a:b; } }// class Pe ecran apar urmtoarele valori (numerele devin foarte mari!): a 1 : 1 1 1 2 : 15 64 562949953421311

50

CAPITOLUL 3. ALGORITMI

3 : 24 102 119649664052358811373730 4 : 29 138 52818655359845224561907882505 5 : 33 150 740095864368253016271188139587625 6 : 37 170 1121872763094011987454778237712816687 7 : 39 172 355716059292752464797065038013137686280 8 : 41 163 35041731132610098771332691525663865902850 9 : 43 189 1385022509795956184601907089700730509680195 10 : 44 205 26154716515862881292012777396577993781727011 11 : 45 177 267235754090021618651175277046931371050194780 12 : 46 205 1619330944936279779154381745816428036441286410 13 : 46 232 6238901276275784811492861794826737563889288230 14 : 47 205 16132809270066494376125322988035691981158490930 15 : 47 162 29226457001965139089793853213126510270024300000 16 : 47 216 38400825365495544823847807988536071815780050940 17 : 47 198 37645241791600906804871080818625037726247519045 18 : 47 225 28189332813493454141899976735501798322277536165 19 : 47 165 16443993651925074352512402220900950019217097000 20 : 46 237 7597921606860986900454469394099277146998755300 21 : 46 198 2820255028563506149657952954637813048172723380 22 : 45 189 851221883077356634241622276646259170751626380 23 : 45 198 211092494149947371195608696099645107168146400 24 : 44 192 43397743800247894833556570977432285162431400 25 : 43 168 7453802153273200083379626234837625465912500 26 : 43 186 1076689601597672801650712654209772574328212 27 : 42 189 131546627365808405813814858256465369456080 28 : 41 155 13660054661277961013613328658015172843800 29 : 40 165 1210546686654900169010588840430963387720 30 : 38 185 91860943867630642501164254978867961752 31 : 37 155 5985123385551625085090007793831362560 32 : 36 164 335506079163614744581488648870187520 33 : 35 153 16204251384884158932677856617905110 34 : 33 144 674833416425711522482381379544960 35 : 32 126 24235536318546124501501767693750 36 : 30 135 750135688292101886770568010795 37 : 29 141 19983209983507514547524896035 38 : 27 132 457149347489175573737344245 39 : 25 114 8951779743496412314947000 40 : 24 93 149377949042637543000150 7. S se aeze B1 , B2 , ..., Bn tiind c a s s a
n

Bn+1 =
k=0

k Cn Bk , B0 = 1.

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC

51

Se vor implementa operatiile cu numere mari. Rezolvare: Vectorul de calcul va avea de fapt dou dimensiuni (numerele devin a foarte mari, aa c elementul Bi trebuie s contin vectorul cifrelor valorii sale). s a a a class e07 { public static void main(String[] args) { int n=71; // n=25 ultimul care incape pe long int k,i; int[][] b=new int[n+1][1]; int[] prod={1}; b[0]=nr2v(1); for(i=1;i<=n;i++) { b[i]=nr2v(0);; for(k=0;k<=i-1;k++) { prod=inm(comb(i-1,k),b[k]); b[i]=suma(b[i],prod); } System.out.print(i+" : "); afisv(b[i]); } System.out.println(" "+Long.MAX_VALUE); System.out.println("... Gata ..."); } static static static static int[] suma(int[] x,int[] y){...} int[] nr2v(int nr){...} int[] inm(int[]x, int[]y){...} void afisv(int[]x){...}

static int[] comb(int n,int k) { int i,j,d; int[] rez; int[] x=new int[k+1]; int[] y=new int[k+1]; for(i=1;i<=k;i++) x[i]=n-k+i; for(j=1;j<=k;j++) y[j]=j; for(j=2;j<=k;j++) for(i=1;i<=k;i++)

52 { d=cmmdc(y[j],x[i]); y[j]=y[j]/d; x[i]=x[i]/d; if(y[j]==1) break;

CAPITOLUL 3. ALGORITMI

} rez=nr2v(1); for(i=1;i<=k;i++) rez=inm(rez,nr2v(x[i])); return rez; } static int cmmdc(int a,int b) {...} }

3.5.8

Probleme propuse

s a a 1. Fie Sn = xn +xn +xn unde x1 , x2 i x3 sunt rdcinile ecuatiei cu coecienti 3 2 1 ntregi ax3 +bx2 +cx+d = 0 (vom considera a = 1!). S se aeze primii 10 termeni a s ai irului Sn i s se precizeze dreptul ecrui termen dac este numr prim, iar s s a n a a a dac nu este numr prim s se aeze descompunerea factori. a a a s n 2. S se aeze frecventa cifrelor care apar a s n
n1

f (n) =
k=0

k Cn1 nn1k (k + 1)!

netinnd cont de faptul c f (n) are o expresie mult mai simpl, i anume nn . Suma a a a s trebuie calculat simulnd operatiile cu numere mari. a a 3. S se aeze frecventa cifrelor care apar a s n
n1

f (n) = nn1 +
k=1

k Cn k k1 (n k)nk

netinnd cont de faptul c f (n) are o expresie mult mai simpl, i anume nn . Suma a a a s trebuie calculat simulnd operatiile cu numere mari. a a 4. S se calculeze a f (n) = n 1 1 p1 1 1 p2 ... 1 1 pm

3.5. INSTRUCTIUNI CORESPONDENTE LIMBAJULUI ALGORITMIC a n unde n = pi1 pi2 ...pim reprezint descompunerea factori primi a lui n. m 1 2 5. S se calculeze a (n) = card {k N/1 k n, cmmdc(k, n) = 1} . 6. S se calculeze a f (n) =
d|n

53

(n)

unde este functia de la exercitiul anterior, netinnd cont de faptul c f (n) are o a a expresie mult mai simpl, i anume n. a s 7. S se calculeze a f (n) = n! 1 8. S se calculeze a
m

1 1 (1)n + ... + 1! 2! n!

f (m, n, 1 , 2 , ..., n ) =

1 k (1)mk Cm Ck k=1

2 Ck+1

n ... Ck+n1

9. S se calculeze a
1 g(m, n, 1 , 2 , ..., n ) = Cm 1 2 Cm+1 2 n ... Cm+n1 n

implementnd operatiile cu numere mari. a 10. S se calculeze a f (n) = 1 1 2 (2n)! Cn 2(2n 1)! + Cn 22 (2n 2)! ... + (1)n 2n n! . 2n

1 Cn n + 1 2n implementnd operatiile cu numere mari. a Cn = 12. S se aeze P (100, 50) (inclusiv suma cifrelor i numrul cifrelor) tiind a s s a s c a P (n + k, k) = P (n, 1) + P (n, 2) + ... + P (n, k)

11. S se calculeze a

54 i s

CAPITOLUL 3. ALGORITMI

P (n, 1) = P (n, n) = 1, n k 1. Se vor implementa operatiile cu numere mari. 13. S se determine cel mai mic numr natural r, astfel at pr = e, unde p a a nc este o permutare dat i e este permutarea identic. as a 14. S se aeze C100 tiind c a s s a
n

Cn =
k=1

Ck1 Cnk , C0 = 1.

Se vor implementa operatiile cu numere mari. 15. S se aeze E100 tiind c a s s a En = E2 En1 + E3 En2 + ... + En1 E2 , E1 = E2 = 1. Se vor implementa operatiile cu numere mari. 16. S se calculeze a S(n, m) = 1 m!
m1 k (1)k Cm (m k)n

k=0

17. S se aeze C100 tiind c a s s a


n

Cn =
k=1

k Cn Fk .

unde Fk este termen Fibonacci. Se vor implementa operatiile cu numere mari. 18. S se aeze C100 tiind c a s s a
n

Cn =
k=1

k Cn 2k Fk .

unde Fk este termen Fibonacci. Se vor implementa operatiile cu numere mari. 19. S se determine puterea a zecea a unui polinom dat. a

Capitolul 4

Analiza complexitii at algoritmilor


4.1 Scopul analizei complexitii at

general exist mai multi algoritmi care rezolv aceeai problem. Dorim In a a s a s exprimm ecienta algoritmilor sub forma unui criteriu care s ne permit s a a a a a alegem din mai multi algoritmi pe cel optim. Exist mai multe moduri care a n putem exprima ecienta: prin timpul necesar pentru executia algoritmului sau prin alte resurse necesare (de exemplu memoria). ambele cazuri a, avem o In ns dependent de dimensiunea cazului studiat. a Se pune problema de alegere a unei uniti de msur pentru a exprima at a a ecienta teoretic a unui algoritm. O important deosebit rezolvarea acestei a a a n probleme o are principiul invariantei. Acesta ne arat c nu este necesar s folosim a a a o astfel de unitate. Principiul invariantei: dou implementri diferite ale aceluiai algoritm a a s nu difer ecienta cu mai mult de o constant multiplicativ. a n a a Implementarea unui algoritm presupune elementele legate de calculatorul folosit, de limbajul de programare si ndemnarea programatorului (cu conditia ca a acesta s nu modice algoritmul). Datorit principiului invariantei vom exprima a a ecienta unui algoritm limitele unei constante multiplicative. n Un algoritm este compus din mai multe instructiuni, care la rndul lor sunt a compuse din mai multe operatii elementare. Datorit principiului invariantei nu a ne intereseaz timpul de executie a unei operatii elementare, ci numai numrul lor, a a dar ne intereseaz care i ce sunt operatiile elementare. a s Denitia 1 O operatie elementar este o operatie al crui timp de executie poate a a mrginit superior de o constant care depinde numai de particularitatea implea a mentrii (calculator, limbaj de programare etc). a 55

56

CAPITOLUL 4. ANALIZA COMPLEXITATII ALGORITMILOR

Deoarece ne intereseaz timpul de executie limita unei constante multia n plicative, vom considera doar numrul operatiilor elementare executate a ntr-un algoritm, nu i timpul exact de executie al operatiilor respective. s Este foarte important ce anume denim ca operatie elementar. Este adunarea a o operatie elementara? Teoretic nu este, pentru c depinde de lungimea celor doi a operanzi. Practic, pentru operanzi de lungime rezonabil putem s considerm c a a a a adunarea este o operatie elementar. Vom considera continuare c adunrile, a n a a scderile, a nmultirile, artirile, operatiile modulo (restul aartirii mp mp ntregi), operatiile booleene, comparatiile i atribuirile sunt operatii elementare. s Uneori ecienta difer dac inem cont numai de unele operatii elementare i a at s le ignorm pe celelalte (de exemplu la sortare: comparatia i interschimbarea). De a s aceea analiza unor algoritmi vom considera o anumit operatie elementar, care n a a este caracteristic algoritmului, ca operatie barometru, neglijndu-le pe celelalte. a a De multe ori, timpul de executie al unui algoritm poate varia pentru cazuri de mrime identic. De exemplu la sortare, dac introducem un ir de n numere a a a s gata sortat, timpul necesar va cel mai mic dintre timpii necesari pentru sortarea oricarui alt ir format din n numere. Spunem c avem de-a face cu cazul cel mai s a favorabil. Dac irul este introdus ordine invers, avem cazul cel mai defavorabil as n a i timpul va cel mai mare dintre timpii de sortare a irului de n numere. s s Exist algoritmi care timpul de executie nu depinde de cazul considerat. a n Dac dimensiunea problemei este mare, a mbuntirea ordinului algoritmului a at este esential, timp ce pentru timpi mici este sufcient performanta hardware. a n a Elaborarea unor algoritmi ecienti presupune cunotinte din diverse domenii s (informatic, matematic i cunotiinte din domeniul cruia apartine problema a as s a i practic a crui model este studiat, atunci cnd este cazul). a a a Exemplul 1 Elaborati un algoritm care returneaz cel mai mare divizor comun a (cmmdc) a doi termeni de rang oarecare din irul lui Fibonacci. s Sirul lui Fibonacci, fn = fn1 + fn2 , este un exemplu de recursivitate n cascad i calcularea efectiv a celor doi termeni fm fn , urmat de calculul celui as a a mai mare divizor al lor, este total neindicat. Un algoritm mai bun poate obtinut a dac inem seama de rezultatul descoperit de Lucas 1876: at n cmmdc(fm , fn ) = fcmmdc(m,n) Deci putem rezolva problema calculnd un singur termen al irului lui Fibonacci. a s Exist mai multi algoritmi de rezolvare a unei probleme date. Prin urmare, a se impune o analiz a acestora, scopul determinrii ecientei algoritmilor de a n a rezolvare a problemei i pe ct posibil a optimalitii lor. Criteriile functie s a at n de care vom stabili ecienta unui algoritm sunt complexitatea spatiu (memorie utilizat) i complexitatea timp (numrul de operatiilor elementare). a s a

4.1. SCOPUL ANALIZEI COMPLEXITATII

57

4.1.1

Complexitatea spatiu

Prin complexitate spatiu elegem dimensiunea spatiului de memorie utilizat nt de program. Un program necesit un spatiu de memorie constant, independent de datele a de intrare, pentru memorarea codului, a constantelor, a variabilelor i a structurilor s de date de dimensiune constant alocate static i un spatiu de memorie variabil, a s a crui dimensiune depinde de datele de intrare, constnd din spatiul necesar a a pentru structurile de date alocate dinamic, a cror dimensiune depinde de instanta a problemei de rezolvat i din spatiul de memorie necesar apelurilor de proceduri i s s functii. Progresele tehnologice fac ca importanta criteriului spatiu de memorie utilizat s scad, prioritar devenind criteriul timp. a a

4.1.2

Complexitatea timp

Prin complexitate timp elegem timpul necesar executiei programului. nt Inainte de a evalua timpul necesar executiei programului ar trebui s avem a informatii detaliate despre sistemul de calcul folosit. Pentru a analiza teoretic algoritmul, vom presupune c se lucreaz pe un a a calculator clasic, sensul c o singur instructiune este executat la un moment n a a a dat. Astfel, timpul necesar executiei programului depinde numai de numrul de a operatii elementare efectuate de algoritm. Primul pas analiza complexitii timp a unui algoritm este determinarea n at operatiilor elementare efectuate de algoritm i a costurilor acestora. s Considerm operatie elementar orice operatie al crei timp de executie este a a a independent de datele de intrare ale problemei. Timpul necesar executiei unei operatii elementare poate diferit de la o operatie la alta, dar este xat, deci putem spune c operatiile elementare au timpul a mginit superior de o constant. a a Fr a restrnge generalitatea, vom presupune c toate operatiile elementare aa a a au acelai timp de executie, ind astfel necesar doar evaluarea numrului de s a a operatii elementare, nu i a timpului total de executie a acestora. s Analiza teoretic ignor factorii care depind de calculator sau de limbajul a a de programare ales i se axeaz doar pe determinarea ordinului de mrime a s a a numrului de operati elementare. a Pentru a analiza timpul de executie se folosete deseori modelul Random s Access Machine (RAM), care presupune: memoria const a ntr-un ir innit de s celule, ecare celul poate stoca cel mult o dat, ecare celul de memorie poate a a a accesat a ntr-o unitate de timp, instructiunile sunt executate secvential i toate s instructiunile de baz se execut a a ntr-o unitate de timp. Scopul analizei teoretice a algoritmilor este de fapt determinarea unor functii care s limiteze superior, respectiv inferior comportarea timp a algoritmului. a n Functiile depind de caracteristicile relevante ale datelor de intrare.

58

CAPITOLUL 4. ANALIZA COMPLEXITATII ALGORITMILOR

4.2

Notatia asimptotic a

4.2.1

Denire i proprieti s at

Denitia 2 Numim ordinul lui f , multimea de functii O(f ) = {t : N R+ |c > 0, n0 N a. t(n) cf (n), n > n0 } . (4.2.1)

Rezult c O(f ) este multimea tuturor functiilor mrginite superior de un a a a multiplu real pozitiv al lui f , pentru valori sucient de mari ale argumentului. Dac t(n) O(f ) vom spune c t este de ordinul lui f sau ordinul lui f . a a n Fie un algoritm dat i o functie t : N R+ , astfel at o anumit imples nc a mentare a algoritmului s necesite cel mult t(n) uniti de timp pentru a rezolva a at un caz de marime n. Principiul invariantei ne asigur c orice implementare a algoritmului necesit a a a un timp ordinul lui t. Mai mult, acest algoritm necesit un timp ordinul lui n a n f pentru orice functie f : N R+ pentru care t O(f ). particular t O(t). In Vom cuta s gsim cea mai simpl functie astfel at t O(f ). a a a a nc Pentru calculul ordinului unei functii sunt utile urmtoarele proprieti: a at Proprietatea 1 O(f ) = O(g) f O(g) i g O(f ) s Proprietatea 2 O(f ) O(g) f O(g) i g O(f ) s / Proprietatea 3 O(f + g) = O(max(f, g)) Pentru calculul multimilor O(f ) i O(g) este util proprietatea urmtoare: s a a Proprietatea 4 Fie f, g : N R+ . Atunci
n

lim

f (n) R+ O(f ) = O(g) g(n) f (n) = 0 O(f ) O(g). g(n)

lim

Reciproca nu este general valabil. n a Fie de exemplu, t(n) = n2 + 3n + 2, atunci n2 + 3n + 2 = 1 = O(n2 + 3n + 2) = O(n2 ) n n2 lim n2 + 3n + 2 = 0 = O(n2 + 3n + 2) O(n3 ) n n3 lim

4.2. NOTATIA ASIMPTOTICA ln(n) = lim n n n lim


1 n 1

59

2 n

2 = lim = 0 = O(ln(n)) O( n) n n

dar O( n) O(ln(n) Dac p este un polinom de gradul m variabila n, atunci O(p) = O(nm ). a n Notatia asimptotic denete o relatie de ordine partial a s a ntre functii. Pentru f, g : N R notm f g dac O(f ) O(g). a a Aceast relatie are proprietile corespunztoare unei relatii de ordine, adic: a at a a a) reexivitate: f f b) antisimetrie: dac f g i g f atunci f = g a s c) tranzitivitate: f g i g h, implic f h. s a Dar nu este o relatie de ordine! Exist i functii astfel at f g (f O(g)) as nc / i g f (g O(f )). De exemplu f (n) = n, g(n) = n1+sin(n) . s / Putem deni i o relatie de echivalent: f g, dac O(f ) = O(g). s a a In multimea O(f ) putem nlocui orice functie cu o functie echivalent cu ea. De a exemplu: ln(n) log(n) log2 (n). Notnd cu O(1) multimea functiilor mrginite superior de o constant i a a a s considernd m N , m 2, obtinem ierarhia: a O(1) O(log(n)) O( n) O(n) O(n log(n)) O(nm ) O(2n ) O(n!) i evident O(n2 ) O(n3 ) ... O(nm ) pentru m 4. s Aceast ierarhie corespunde ierarhiei algoritmilor dup criteriul performantei. a a Pentru o problem dat, dorim sa realizm un algoritm cu un ordin situat ct mai a a a a stnga aceast ierarhie. n a n a Notatia O(f ) este pentru a delimita superior timpul necesar unui algoritm. Notm TA (n) timpul necesar executiei algoritmului A. a a a Fie f : N R o functie arbitrar. Spunem c algoritmul este de ordinul + lui f (n) (i notm TA (n) O(f (n))), dac i numai dac exist c > 0 i n0 N, s a as a a s astfel at TA (n) c f (n), n n0 . nc De exemplu: a) Dac TA (n) = 3n+2, atunci TA (n) O(n), pentru c 3n+2 4n, n 2. a a Mai general, dac TA (n) = a n + b, a > 0, atunci TA (n) O(n) pentru c a a exist c = a + 1 > 0 i n0 = b N, astfel at a n + b (a + 1) n, n b. a s nc b) Dac TA (n) = 10n2 + 4n + 2, atunci TA (n) O(n2 ), pentru c 10n2 + a a 4n + 2 11n2 , n 5. Mai general, dac T A(n) = an2 + bn + c, a > 0, atunci T A(n) O(n2 ), a pentru c an2 + bn + c (a + 1)n2 , n max(b, c) + 1. a c) Dac TA (n) = 6 2n + n2 , atunci T A(n) O(2n ), pentru c TA (n) a a n 7 2 ,n 4. Dac TA (n) = ak nk + ak1 nk1 + ... + a1 n + a0 , atunci T A(n) O(nk ). a Aceasta rezult din: TA (n) = |TA (n)| = |ak nk + ak1 nk1 + ... + a1 n + a0 | a |ak |nk + |ak1 |nk1 + ... + |a1 |n + |a0 | (|ak | + |ak1 | + ... + |a1 | + |a0 |)nk , n 1 i alegnd c = |ak | + |ak1 | + ... + |a1 | + |a0 | i n = 1 rezult TA (n) O(nk ). s a s a

60

CAPITOLUL 4. ANALIZA COMPLEXITATII ALGORITMILOR

4.2.2

Clase de complexitate

Notatia O ofer o limit superioar a timpului de executie a unui algoritm. a a a Un algoritm cu TA (n) O(1) necesit un timp de executie constant. Un a algoritm cu TA (n) O(n) se numete liniar. Dac TA (n) O(n2 ) algoritmul se s a numete ptratic, iar dac TA (n) O(n3 ), cubic. Un algoritm cu TA (n) O(nk ) s a a se numete polinomial, iar dac TA (n) O(2n ) algoritmul se numete exponential. s a s Tabelul urmtor ilustreaz comportarea a cinci din cele mai importante a a functii de complexitate. O(log(n)) (logaritmic) 0 1 2 3 4 5 O(n) (liniar) 1 2 4 8 16 32 O(n.log(n)) (log-liniar) 0 2 8 24 64 160 O(n2 ) (ptratic) a 1 4 16 64 256 1024 O(n3 ) cubic 1 8 64 512 4096 32768 O(2n ) (exponential) 2 4 16 256 65536 4294967296

Tabelul 4.1: Functii de complexitate Dac TA (n) O(2n ), pentru n = 40, pe un calculator care face 109 de operatii a pe secund, sunt necesare aproximativ 18 minute. Pentru n = 50, acelai program a s va rula 13 zile pe acest calculator, pentru n = 60, vor necesari peste 310 ani, iar pentru n = 100 aproximativ 4.1013 ani. Utilitatea algoritmilor polinomiali de grad mare este de asemenea limitat. a De exemplu, pentru O(n10 ), pe un calculator care execut 109 operatii pe secund a a sunt necesare 10 secunde pentru n = 10, aproximativ 3 ani pentru n = 100 i circa s 3.1013 ani pentru n = 1000. Uneori este util s determinm i o limit inferioar pentru timpul de executie a a s a a a unui algoritm. Notatia matematic este . a Denitie: Spunem c TA (n) (f (n)) dac i numai dac c > 0 i n0 N a as a s astfel at TA (n) c f (n), n n0 . nc De exemplu: a) dac TA (n) = 3n + 2, atunci TA (n) (n), pentru c 3n + 2 3n, n 1; a a b) dac TA (n) = 10n2 +4n+2, atunci TA (n) (n), pentru c 10n2 +4n+2 a a n2, n 1; c) dac TA (n) = 6 2n + n2 , atunci TA (n) (2n ), pentru c 6 2n + n2 2n , a a n 1. Exist functii f care constituie att o limit superioar ct i o limit infea a a a a s a rioar a timpului de executie a algoritmului. De exemplu, dac TA (n) = ak nk + a a ak1 nk1 + ... + a1 n + a0 , ak > 0 atunci TA (n) (nk ). Denitie : Spunem c TA (n) (f (n)) dac i numai dac c1 , c2 > 0 i a as a s n0 N astfel at c1 f (n) TA (n) c2 f (n), n n0 . nc

4.2. NOTATIA ASIMPTOTICA

61

acest caz f (n) constituie att o limit inferioar ct i o limit superioar In a a a a s a a pentru timpul de executie a algoritmului. Din acest motiv se poate numi ordin exact. Se poate arta uor c (f (n)) = O(f (n)) (f (n)). De asemenea, dac a s a a TA (n) = ak nk + ak1 nk1 + ... + a1 n + a0 , ak > 0 atunci TA (n) (nk ).

4.2.3

Cazul mediu i cazul cel mai defavorabil s

Am artat c timpul de executie al unui algoritm este direct proportional cu a a numrul de operatii elementare i am stabilit o notatie asimptotic pentru timpul a s a de executie. Totui, numrul de operatii elementare efectuate de algoritm poate s a varia considerabil pentru diferite seturi de date de intrare. Determinarea complexitii timp a algoritmului ca o functie de caracteristiat cile datelor de intrare este o sarcin uoar doar pentru algoritmi relativ simpli, a s a dar general problema este dicil i din aceast cauz analizm complexitatea n as a a a algoritmilor medie sau cazul cel mai defavorabil. n n Complexitatea cazul cel mai defavorabil este numrul maxim de operatii n a elementare efectuate de algoritm. Dar chiar dac este cunoscut cazul cel mai defavorabil, datele utilizate efectiv a practic pot conduce la timpi de executie mult mai mici. Numeroi algoritmi n a s foarte utili au o comportare convenabil practic, dar foarte proast cazul a n a a n cel mai defavorabil. Cel mai cunoscut exemplu este algoritmul de sortare rapid (quicksort) care a are complexitatea cazul cel mai defavorabil de O(n2 ), dar pentru datele alnite n nt practic functioneaz O(n log n). n a a n Determinarea complexitii medie necesit cunoaterea repartitiei probaat n a s bilistice a datelor de intrare i din acest motiv analiza complexitii medie este s at n mai dicil de realizat. Pentru cazuri simple, de exemplu un algoritm de sortare care actioneaza asupra unui tablou cu n componente ntregi aleatoare sau un algoritm geometric pe o multime de N puncte plan de coordonate aleatoare cuprinse n n intervalul [0, 1], putem caracteriza exact datele de intrare. Dac notm: a a D - spatiul datelor de intrare p(d) - probabilitatea aparitiei datei d D la intrarea algoritmului TA (d) - numrul de operatii elementare efectuate de algoritm pentru d D a atunci complexitatea medie este p(d) TA (d).

dD

62

CAPITOLUL 4. ANALIZA COMPLEXITATII ALGORITMILOR

4.2.4

Analiza asimptotic a structurilor fundamentale a

Considerm problema determinrii ordinului de complexitate cazul cel mai a a n defavorabil pentru structurile algoritmice: secvential, alternativ i repetitiv. a as a Presupunem c structura secvential este constituit din prelucrrile A1 , A2 , a a a a ..., Ak i ecare dintre acestea are ordinul de complexitate O(gi (n)), 1 i n. s Atunci structura va avea ordinul de complexitate O(max{g1 (n), ..., gk (n)}). Dac conditia unei structuri alternative are cost constant iar prelucrrile a a celor dou variante au ordinele de complexitate O(g1 (n)) respectiv O(g2 (n)) atunci a costul structurii alternative va O(max{g1 (n), g2 (n)}). cazul unei structuri repetitive pentru a determina ordinul de complexitate In cazul cel mai defavorabil se consider numrul maxim de iteratii. Dac acesta n a a a este n iar corpul ciclului prelucrrile sunt de cost constant atunci se obtine n a ordinul O(n).

4.3

Exemple

4.3.1

Calcularea maximului

Fiind date n elemente a1 , a2 , ..., an , s se calculeze max{a1 , a2 , ..., an }. a max = a[1]; for i = 2 to n do if a[i] > max then max = a[i]; Vom estima timpul de executie al algoritmului functie de n, numrul de n a date de intrare. Fiecare iteratie a ciclului for o vom considera operatie elementar. a Deci complexitatea algoritmului este O(n), att medie ct i cazul cel mai a n a s n defavorabil.

4.3.2

Sortarea prin selectia maximului

Sortm cresctor vectorul a, care are n componente. a a for j=n,n-1,...,2 { max=a[1]; pozmax=1; for i=2,3,...,j { if a[i]>max { a[i]=max; pozmax=i; }

4.3. EXEMPLE a[pozmax]=a[j]; a[j]=max; } }

63

Estimm complexitatea algoritmului functie de n, dimensiunea vectorului. a n La ecare iteratie a ciclului for exterior este calculat max{a1 , a2 , ..., aj } i plasat s pe pozitia j, elementele de la j + 1 la n ind deja plasate pe pozitiile lor denitive. Conform exemplului anterior, pentru a calcula max{a1 , a2 , ..., aj } sunt necesare j 1 operatii elementare, total 1 + 2 + ... + (n 1) = n(n 1)/2. Deci n complexitatea algoritmului este de O(n2 ). S observm c timpul de executie este a a a independent de ordinea initial a elementelor vectorului. a

4.3.3

Sortarea prin insertie

Este o metod de asemenea simpl, pe care o utilizm adesea cnd ordonm a a a a a crtile la jocuri de crti. a a for i=2,3,...,n { val=a[i]; poz=i; while a[poz-1]>val { a[poz]=a[poz-1]; poz=poz-1; } a[poz]=val; } Analizm algoritmul functie de n, dimensiunea vectorului ce urmeaz a a n a sortat. La ecare iteratie a ciclului for elementele a1 , a2 , ..., ai1 sunt deja ordonate i trebuie s inserm valorea a[i] pe pozitia corect irul ordonat. cazul cel mai s a a a n s In defavorabil, cnd vectorul este initial ordonat descresctor, ecare element a[i] va a a plasat pe prima pozitie, deci ciclul while se execut de i 1 ori. Considernd drept a a operatie elementar comparatia a[poz 1] > val urmat de deplasarea elementului a a de pe pozitia poz 1, vom avea cazul cel mai defavorabil 1 + 2 + ... + (n 1) = n n(n 1)/2 operatii elementare, deci complexitatea algoritmului este de O(n2 ). S analizm comportarea algoritmului medie. Considerm c elementele a a n a a vectorului sunt distincte i c orice permutare a lor are aceeai probabilitate de s a s aparitie. Atunci probabilitatea ca valoarea ai s e plasat pe pozitia k irul a a n s a1 , a2 , ..., ai , k {1, 2, ...., i} este 1/i. Pentru i xat, numrul mediu de operatii a elementare este:
i

k=1

1 1 (k 1) = i i

k=1

(k 1) =

1 i

i(i + 1) i 2

i+1 i1 1= 2 2

64

CAPITOLUL 4. ANALIZA COMPLEXITATII ALGORITMILOR Pentru a sorta cele n elemente sunt necesare
n i=2

i1 1 = 2 2

n(n + 1) 1 (n 1) 2

n 2

(n + 1) 1 2

n(n 1) 4

operatii elementare. Deci complexitatea algoritmului medie este tot O(n2 ). n

4.3.4

Sortarea rapid (quicksort) a

Acest algoritm a fost elaborat de C.A.R. Hoare 1960 i este unul dintre cei n s mai utilizati algoritmi de sortare. void quicksort(int st, int dr) { int m; if st<dr { m=divide(st, dr); quicksort(st, m-1); quicksort(m+1, dr); } } Initial apelm quicksort(1,n). a Functia divide are rolul de aplasa primul element (a[st]) pe pozitia sa corect a irul ordonat. stnga sa se vor gsi numai elemente mai mici, iar dreapta n s In a a n numai elemente mai mari dect el. a int divide(int st, int dr) { int i, j, val; val=a[st]; i=st; j=dr; while(i<j) { while((i<j) && (a[j] >= val)) j=j-1; a[i]=a[j]; while((i<j) && (a[i] <= val)) i=i+1; a[j]=a[i]; } a[i]=val; return i; } Observatie : Vectorul a este considerat variabil global. a a

4.3. EXEMPLE

65

cazul cel mai defavorabil, cnd vectorul a era initial ordonat, se fac n 1 In a apeluri succesive ale procedurii quicksort, cu parametrii (1, n), (1, n1), ..., (1, 2) (dac vectorul a era initial ordonat descresctor) sau (1, n), (2, n), ..., (n 1, n) a a (dac vectorul a era ordonat cresctor). a a La ecare apel al procedurii quicksort este apelat functia divide(1,i) a (respectiv divide(i, n)) care efectueaz i 1, (respectiv n i 1) operatii a elementare. total numrul de operatii elementare este (n 1) + (n 2) + ... + 1 = In a n(n 1)/2. Complexitatea algoritmului cazul cel mai defavorabil este de O(n2 ). n S analizm comportarea algoritmului medie. Vom considerm c orice a a n a a permutare a elementelor vectorului are aceeai probabilitate de aparitie i notm s s a cu Tn numrul de operatii elementare efectuate pentru a sorta n elemente. a Probabilitatea ca un element al vectorului s e plasat pe pozitia k vectorul a n ordonat, este de 1/n. 0,
1 n n k=1

Tn =

(Tk1 + Tnk ) + (n 1),

dac n = 0 sau n = 1 a dac n > 1 a

(pentru a ordona cresctor n elemente, determinm pozitia k vectorul ordonat a a a n primului element, ceea ce necesit n1 operatii elementare, sortm elementele din a a stnga, ceea ce necesit Tk1 operatii elementare, apoi cele din dreapta, necesitnd a a a Tnk operatii elementare). Problema se reduce la a rezolva relatia de recurent de mai sus. Mai ai a nt observm c a a T0 + T1 + ... + Tn1 = T n 1 + ... + T1 + T0 . Deci, Tn = n 1 + 2 n
n

Tk1
k=1

Inmultim ambii membri ai acestei relatii cu n. Obtinem:


n

nTn = n(n 1) + 2

Tk1
k=1

Scznd din aceast relatie, relatia obtinut pentru n 1, adic a a a a a


n1

(n 1)Tn1 = (n 1)(n 2) + 2 obtinem

Tk1
k=1

nTn (n 1)Tn1 = n(n 1) (n 1)(n 2) + 2Tn1 de unde rezult a nTn = 2(n 1) + (n + 1)Tn1

66

CAPITOLUL 4. ANALIZA COMPLEXITATII ALGORITMILOR

artind ambii membri cu n(n + 1) obtinem Imp Tn Tn1 2(n 1) Tn2 2(n 1) 2(n 2) T2 = + = + + = ... = +2 n+1 n n(n + 1) n 1 n(n + 1) (n 1)n 3 Deci Tn T2 = +2 n+1 3
n n

k=3

k1 k(k + 1)

k=3

1 1 1 + k+1 k k+1

T2 2 + +2 3 n+1

k=3

1 2 k

k=1

1 2 ln n k

Deci, medie, complexitatea algoritmului este de O(n log n). n

4.3.5

Problema celebritii at

Numim celebritate o persoan care este cunoscut de toat lumea, dar nu a a a cunoate pe nimeni. Se pune problema de a identica o celebritate, dac exist, s a a ntr-un grup de n persoane pentru care relatiile dintre persoane sunt cunoscute. Putem reformula problema limbaj de grafuri astfel: ind dat un digraf cu n n vrfuri, vericati dac exist un vrf cu gradul exterior 0 i gradul interior n 1. a a a a s Reprezentm graful asociat problemei prin matricea de adiacent ann a a ai,j = 1, 0, dac persoana i cunoaste persoana j; a altfel.

O prim solutie ar s calculm pentru ecare persoan p din grup numrul a a a a a de persoane pe care p le cunoate (out) i numrul de persoane care cunosc pers s a soana p (in). Cu alte cuvinte, pentru ecare vrf din digraf calculm gradul interior a a i gradul exterior. Dac gsim o persoan pentru care out = 0 i in = n1, aceasta s a a a s va celebritatea cutat. a a celebritate=0; for p=1,2,...,n { in=0; out=0; for j=1,2,...,n { in=in+a[j][p]; out=out+a[p][j]; } if (in=n-1) and (out = 0) celebritate=p; } if celebritate=0 writeln(Nu exista celebritati !) else writeln(p, este o celebritate.);

4.4. PROBLEME

67

Se poate observa cu uurint c algoritmul este de O(n2 ). Putem s a a mbunti a at algoritmul fcnd observatia c atunci cnd testm relatiile dintre persoanele x i a a a a a s y apar urmtoarele posibilitii: a at a[x, y] = 0 i acest caz y nu are nici o ans s e celebritate, sau s n s a a a[x, y] = 1 i acest caz x nu poate celebritate. s n Deci la un test eliminm o persoan care nu are anse s e celebritate. a a s a Fcnd succesiv n 1 teste, nal vom avea o singur persoan candidat a a n a a la celebritate. Rmne s calculm numrul de persoane cunoscute i numrul de a a a a a s a persoane care cunosc pe acest candidat, singura celebritate posibil. l a candidat=1; for i=2,n if a[candidat][i]=1 candidat=i; out=0; in=0; for i=1,n { in=in+a[i][candidat]; out=out+a[candidat][i]; } if (out=0) and (in=n-1) write(candidat, este o celebritate .) else write(Nu exista celebritati.); acest caz algoritmul a devenit liniar. In

4.4

Probleme

4.4.1

Probleme rezolvate

Problema 1 Care armatii sunt adevarate: a) n2 O(n3 ) b) n3 O(n2 ) c) 2n+1 O(2n ) d) (n + 1)! O(n!) e) f : N R , f O(n) = f 2 O(n2 ) f ) f : N R , f O(n) = 2f O(2n ) Rezolvare: n2 a) Armatia este adevarat pentru c: limn n3 = 0 = n2 O(n3 ). a a n3 b) Armatia este fals pentru c: limn n2 = a a

68

CAPITOLUL 4. ANALIZA COMPLEXITATII ALGORITMILOR


n+1

c) Armatia este adevarat pentru c: limn 22n = 2 = O(2n+1 ) = a a n O(2 ). d) Armatia este fals pentru c: limn (n+1)! = limn n+1 = a a n! 1 e) Armatia este adevarat pentru c: f O(n) = c > 0 i n0 N a a s astfel at f (n) < c n, n > n0 . Rezult c c1 = c2 astfel a f 2 (n) < c1 n2 , nc a a nc n > n0 , deci f 2 O(n2 ). e) Armatia este adevarat pentru c: f O(n) = c > 0 i n0 N astfel a a s at f (n) < c n, n > n0 . Rezult c c1 = 2c astfel a 2f (n) < 2cn = 2c 2n = nc a a nc c1 2n , n > n0 , deci 2f O(2n ). / Problema 2 Artati c log n O( n) dar n O(log n). a a Indicatie: Prelungim domeniile functiilor pe R+ , pe care sunt derivabile, i aplicm s a relula lui LHspital pentru log n/ n. o Problema 3 Demonstrati urmtoarele armatii: a i) ii)
i=1 n

loga (logb n), pentru oricare a, b > 1


n

ik (nk+1 ), pentru oricare k N 1 (n log n) i

iii)
i=1

iv)

log n! (n log n)
i=1

Indicatii: La punctul iii) se ine cont de relatia t 1 + ln n i

unde 0.5772 este constanta lui Euler. La punctul iv) din n! < nn , rezult log n! < n log n, deci log n! O(n log n). a Trebuie s gsim i o margine inferioar. Pentru 0 i n 1 este adevrat a a s a a a relatia (n i)(i + 1) n Deoarece (n!)2 = (n 1)((n 1) 2)((n 2) 3)...(2 (n 1))(1 n) nn rezult 2 log n! n log n, adic log n! 0.5n log n, deci log n! (n log n). a a Relatia se poate demonstra i folosind aproximarea lui Stirling s n! 2n n e
n

(1 + (1/n))

4.4. PROBLEME

69

4.4.2

Probleme propuse

1. Artati c: a a a) n3 + 106 n (n3 ) n n b) n2 + 6 2n ()(n2 ) c) 2n2 + n log n (n2 ) d) nk + n + nk log n (nk log n), k 1 e) loga n (logb n), a, b > 0, a = 1, b = 1. 2. Pentru oricare doua functii f, g : N R demonstrati c a O(f + g) = O(max(f, g)) unde suma i maximul se iau punctual. s 3. Fie f, g : N R+ Demonstrati c: a i) lim
n

(4.4.1)

f (n) R+ O(f ) = O(g), g(n)

ii) lim

f (n) = 0 O(f ) O(g) g(n)

Observatie: Implicatiile inverse nu sunt general adevrate, deoarece se n a poate ntampla ca limitele s nu existe. a 4. Demonstrati prin inductie c pentru a determina maximul a n numere sunt a necesare n 1 comparatii. 5. Care este timpul de executie a algoritmului quicksort pentru un vector cu n componente egale? 6. S considerm urmtorul algoritm de sortare a unui vector a cu n compoa a a nente: do { ok=true; for i=1,n-1 if a[i]>a[i+1] { aux=a[i]; a[i]=a[i+1]; a[i+1]= aux; } ok=false; } while !ok; Analizati algoritmul medie i cazul cel mai defavorabil. n s n 7. Analizati complexitatea algoritmului de interclasare a doi vectori ordonati, a cu n componente, respectiv b cu m componente : i=1; j=1; k=0; while (i <= n) and (j <= m) { k=k+1; if a[i] < b[j] { c[k]=a[i]; i=i+1; } else { c[k]=b[j]; j=j+1; }

70

CAPITOLUL 4. ANALIZA COMPLEXITATII ALGORITMILOR

} for t=i,n { k=k+1; c[k]=a[t]; } for t=j,m { k=k+1; c[k]=b[t]; } 8. Fiind dat a, un vector cu n componente distincte, vericati dac o valoare a dat x se gsete sau nu vector. Evaluati complexitatea algoritmului cazul a a s n n cel mai defavorabil i medie. s n 9. Se d a un vector cu n componente. Scrieti un algoritm liniar care s a a determine cea mai lung secvent de elemente consecutive de valori egale. a a 10. Fie T un text. Vericati timp liniar dac un text dat T este o permutare n a circular a lui T . a 11. Fie X = (x1 , x2 , ..., xn ) o secvent de numere a ntregi. Fiind dat x, vom numi multiplicitate a lui x X numrul de aparitii ale lui x X. Un element se n a n numete majoritar dac multiplicitatea sa este mai mare dect n/2. Descrieti un s a a algoritm liniar care s determine elementul majoritar dintr-un ir, dac un astfel a s a de element exist. a 12. Fie {a1 , a2 , ..., an } i {b1 , b2 , ..., bm }, dou multimi de numere s a ntregi, nenule (m < n). S se determine {x1 , x2 , ..., xm }, o submultime a multimii {a1 , a2 , ..., an } a pentru care functia f (x1 , x2 , ..., xm ) = a1 x1 + a2 x2 + ... + an xm ia valoare maxim, a prin doi algoritmi de complexitate diferit. a

Capitolul 5

Recursivitate
Denitiile prin recurent sunt destul de curente matematic: progresia a n a aritmetic, progresia geometric, irul lui Fibonacci, limite de iruri, etc. a a s s

5.1

Functii recursive

5.1.1

Functii numerice

Pentru calculul termenilor irului lui Fibonacci, a transcriere literal a fors a mulei este urmtoarea: a static int if (n <= return else return } fib(int n) { 1) 1; fib(n-1) + fib(n-2);

fib este o functie care utilizeaz propriul nume denitia proprie. De asemenea, a n dac argumentul n este mai mic dect 1 returneaz valoarea 1 iar caz contrar a a a n returneaz f ib(n 1) + f ib(n 2). a Java este posibil, ca de altfel multe alte limbaje de programare (Fortran, In n Pascal, C, etc), s denim astfel de functii recursive. Dealtfel, toate irurile denite a s prin recurent se scriu aceast manier Java, cum se poate observa din a n a a n urmtoarele dou exemple numerice: factorialul i triunghiul lui Pascal. a a s 71

72 static int if (n != return else return } fact(int n) { 1) 1; n * fact (n1);

CAPITOLUL 5. RECURSIVITATE

fibo(4)

fact(4)

fibo(3)

fibo(2)

fact(3)

fibo(2)

fibo(1)

fibo(1)

fibo(0)

fact(2)

fibo(1)

fibo(0)

fact(1)

static int comb(int n, int p) { if ((p == 0) || (p == n)) return 1; else return comb(n-1, p) + comb(n-1, p-1); }

comb(4,2)

comb(3,2)

comb(3,1)

comb(2,2)

comb(2,1)

comb(2,1)

comb(2,0)

comb(1,1)

comb(1,0)

comb(1,1)

comb(1,0)

Ne putem ntreba cum efectueaz Java calculul functiilor recursive. Putem a s rspundem prin urmrirea calculelor cazul calculului lui f ibo(4). Reamintim a a a n c argumentele sunt transmise prin valoare acest caz, iar un apel de functie a n const evaluarea argumentului, apoi lansarea executie a functiei cu valoarea a n n

5.1. FUNCTII RECURSIVE argumentului. Deci f ibo(4) f ibo(3) + f ibo(2) (f ibo(2) + f ibo(1)) + f ibo(2) ((f ibo(1) + f ibo(1)) + f ibo(1)) + f ibo(2) ((1 + f ibo(1)) + f ibo(1)) + f ibo(2) ((1 + 1) + f ibo(1)) + f ibo(2) (2 + f ibo(1)) + f ibo(2) (2 + 1) + f ibo(2) 3 + f ibo(2) 3 + (f ibo(1) + f ibo(1)) 3 + (1 + f ibo(1)) 3 + (1 + 1) 3+2 5

73

Exist deci un numr semnicativ de apeluri succesive ale functiei f ib (9 a a apeluri pentru calculul lui f ibo(4)). S notm prin Rn numrul apelurilor functiei a a a f ibo pentru calculul lui f ibo(n). Evident R0 = R1 = 1, i Rn = 1 + Rn1 + Rn2 s pentru n > 1. Punnd Rn = Rn + 1, obtinem c Rn = Rn1 + Rn2 pentru n > 1, a a a s a i R1 = R0 = 2. Rezult Rn = 2 f ibo(n) i de aici obtinem c Rn = 2 f ibo(n) 1. s Numrul de apeluri recursive este foarte mare! Exist o metod iterativ simpl a a a a a care permite calculul lui f ibo(n) mult mai repede. f ibo(n) f ibo(n 1) = 1 1 1 0 u v = f ibo(n 1) f ibo(n 2) 1 1 1 0 = ... = u0 v0 1 1 1 0
n

1 0

static int fibo(int n) { int u, v; int u0, v0; int i; u = 1; v = 1; for (i = 2; i <= n; ++i) { u0 = u; v0 = v; u = u0 + v0; v = v0; } return u; }

74

CAPITOLUL 5. RECURSIVITATE

Se poate calcula i mai repede folosind ultima form i calculnd puterea s a s a matricei ... Pentru a rezuma, o regul bun este s nu a a a ncercm s intrm meandrele a a a n detaliilor apelurilor recursive pentru a elege sensul unei functii recursive. nt In general este sufucient s elegem sintetic functia. Functia lui Fibonacci este un a nt caz particular care calculul recursiv este foarte lung. Cam la fel se ampl n nt a (dac nu chiar mai ru!) i cu triunghiul lui Pascal. Dar nu aceasta este situatia a a s general. Nu numai c scrierea recursiv se poate dovedi ecace, dar ea este n a a totdeauna natural i deci cea mai estetic. Ea nu face dect s respecte denitia as a a a matematic prin recurent. Este o metod de programare foarte puternic. a a a a

5.1.2

Functia lui Ackerman

Sirul lui Fibonacci are o cretere exponential. Exist functii recursive care au s a a o cretere mult mai rapid. Prototipul este functia lui Ackerman. loc s denim s a In a matematic aceast functie, este de asemenea simplu s dm denitia recursiv a a a a n Java. static int ack(int m, int n) { if (m == 0) return n+1; else if (n == 0) return ack (m-1, 1); else return ack(m-1, ack(m, n-1)); } Se poate verica c ack(0, n) = n + 1, ack(1, n) = n + 2, ack(2, n) 2n, a ack(3, n) 2n , ack(5, 1) ack(4, 4) 265536 > 1080 , adic numrul atomilor din a a univers [11].

5.1.3

Recursii imbricate

Functia lui Ackerman contine dou apeluri recursive imbricate ceea ce deter a min o cretere rapid. Un alt exemplu este functia 91 a lui MacCarty [11]: a s a static int f(int n) { if (n > 100) return n-10; else return f(f(n+11)); }

5.2. PROCEDURI RECURSIVE Pentru aceast functie, calculul lui f (96) d a a f (96) = f (f (107)) = f (97) = ... = f (100) = f (f (111)) = f (101) = 91.

75

Se poate arta c aceast functie va returna 91 dac n 100 i n 10 dac a a a a s a n > 100. Aceast functie anecdotic, care folosete recursivitatea imbricat, este a a s a interesant pentru cu este evident c o astfel de denitie d d acelai rezultat. a n a a a s Un alt exemplu este functia lui Morris [11] care are urmtoarea form: a a static int if (m == return else return } g(int m, int n) { 0) 1; g(m-1, g(m, n));

Ce valoare are g(1, 0)? Efectul acestui apel de functie se poate observa din denitia ei: g(1, 0) = g(0, g(1, 0)). Se declaneaz la nesfrit apelul g(1, 0). Deci, s a as calculul nu se va termina niciodat! a

5.2

Proceduri recursive

Procedurile, la fel ca i functiile, pot recursive i pot suporta apeluri recurs s sive. Exemplul clasic este cel al turnurilor din Hanoi. Pe 3 tije din fata noastr, a numerotate 1, 2 i 3 de la stnga la dreapta, sunt n discuri de dimensiuni diferite s a plasate pe tija 1 formnd un con cu discul cel mai mare la baz i cel mai mic a as n vrf. Se dorete mutarea discurilor pe tija 3, mutnd numai cte un singur disc i a s a a s neplasnd niciodat un disc mai mare peste unul mai mic. Un rationament recura a siv permite scrierea solutiei cteva rnduri. Dac n 1, problema este trivial. n a a a a Presupunem problema rezolvat pentru mutarea a n 1 discuri de pe tija i pe a tija j (1 i, j 3). Atunci, exist o solutie foarte simpl pentru mutarea celor n a a discuri de pe tija i pe tija j: 1. se mut primele n1 discuri (cele mai mici) de pe tija i pe tija k = 6ij, a 2. se mut cel mai mare disc de pe tija i pe tija j, a 3. se mut cele n 1 discuri de pe tija k pe tija j. a static void hanoi(int n, int i, int j) { if (n > 0) { hanoi (n-1, i, 6-(i+j)); System.out.println (i + " -> " + j); hanoi (n-1, 6-(i+j), j); } }

76

CAPITOLUL 5. RECURSIVITATE

Aceste cteva linii de program arat foarte bine cum generaliznd problema, a a a adic mutarea de pe oricare tij i pe oricare tijj, un program recursiv de cteva a a a a linii poate rezolva o problem apriori complicat. Aceasta este forta recursivitii a a at i a rationamentului prin recurent. s a
pasul 1

a)

b) pasul 2

pasul 3

B d)

B c)

Capitolul 6

Analiza algoritmilor recursivi


Am vzut capitolul precedent ct de puternic i util este recursivitatea a n a as a elaborarea unui algoritm. Cel mai important ctig al exprimrii recursive este n as a faptul c ea este natural i compact. a as a Pe de alt parte, apelurile recursive trebuie folosite cu discernmnt, deoarece a a a solicit i ele resursele calculatorului (timp si memorie). as Analiza unui algoritm recursiv implic rezolvarea unui sistem de recurente. a Vom vedea continuare cum pot rezolvate astfel de recurente. n

6.1

Relatii de recurent a

O ecuatie care necunoscutele sunt termenii xn , xn+1 , ...xn+k ai unui ir n s de numere se numete relatie de recurenta de ordinul k. Aceast ecuatie poate s a satisfcut de o innitate de iruri. Ca s putem rezolva ecuatia (relatia de a a s a recurent) mai avem nevoie i de conditii initiale, adic de valorile termenilor a s a x0 , x1 , ..., xk1 . De exemplu relatia de recurent a (n + 2)Cn+1 = (4n + 2)Cn , pentru n 0, C0 = 1 este de ordinul 1. Dac un ir xn de numere satisface o formul de forma a s a a0 xn + a1 xn+1 + ... + ak xn+k = 0, k 1, ai R, a0 , ak = 0 (6.1.1)

atunci ea se numete relatie de recurenta de ordinul k cu coecienti constanti. s Coecientii sunt constanti sensul c nu depind de valorile irului xn . n a s 77

78

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

O astfel de formul este de exemplu Fn+2 = Fn+1 + Fn , F0 = 0, F1 = 1, a adic relatia de recurent care denete irul numerelor lui Fibonacci. Ea este o a a s s relatie de recurent de ordinul 2 cu coecienti constanti. a

6.1.1

Ecuatia caracteristic a

Gsirea expresiei lui xn care s satisfac relatia de recurent se numete a a a a s rezolvarea relatiei de recurenta. Fcnd substitutia a a xn = rn obtinem urmtoarea ecuatie, numit ecuatie caracteristic: a a a a0 + a1 r + a2 r2 + ... + ak rk = 0 (6.1.2)

6.1.2

Solutia general a

Solutia general a relatiei de recurent omogen de ordinul k cu coecienti a a a constanti este de forma
k

xn =
i=1 (i

ci x(i) n

(6.1.3)

(se mai numesc i sistem fundamental de solutii). Pentru determinarea acestor s solutii distingem urmtoarele cazuri: a Ecuatia caracteristic admite rdcini reale i distincte a a a s n Dac r1 , r2 , ..., rk sunt rdcini reale ale ecuatiei caracteristice, atunci ri sunt a a a solutii ale relatiei de recurent. a n n a Intr-adevr, introducnd expresiile ri relatia de recurent, obtinem: a a
n+k n+2 n+1 2 k n n = ri a0 + a1 ri + a2 ri + ... + ak ri = 0 + ... + ak ri + a2 ri a0 ri + a1 ri

unde xn )|i {1, 2, ..., k} sunt solutii liniar independente ale relatiei de recurent a

Dac rdcinile ri (i = 1, 2, ..., k) sunt distincte, atunci relatia de recurent a a a a are solutia general a n n n xn = c1 r1 + c2 r2 + ... + ck rk (6.1.4) unde coecientii c1 , c2 , ..., ck se pot determina din conditiile initiale. Ecuatia caracteristic admite rdcini reale multiple a a a Fie r o rdcin multipl de ordinul p a ecuatiei caracteristice. Atunci a a a a rn , nrn , n2 rn , ..., np1 rn sunt solutii liniar independente ale relatiei de recurent i a s xn = c1 + c2 n + ... + cp1 np1 rn (6.1.5)

6.1. RELATII DE RECURENTA

79

este o solutie a relatiei de recurent. Acest lucru se mai poate demonstra uor dac a s a inem cont de faptul c o rdcin multipl de ordinul p a unui polinom P (x) este t a a a a a rdcin i a polinoamelor derivate P (x), P (x), ..., P (p1) (x). a a as Solutia general este suma dintre solutia general corespunztoare rdcinilor a a a a a simple ale ecuatiei caracteristice i solutia general corespunztoare rdcinilor s a a a a multiple. Dac ecuatia caracteristic are rdcinile simple r1 , r2 , ..., rs i rdcinile mula a a a s a a tiple rs1 , rs+2 , ..., rs+t de multiplicitate p1 , p2 , ..., pt (s+p1 +p2 +...+pt = k), atunci solutia general a relatiei de recurent este a a xn
n n n = c1 r1 + c2 r2 + ... + cs rs + (1) (1) (1)

c1 + c2 n + ... + cp1 1 np1 1 + ... (t) (t) (1) c1 + c2 n + ... + cpt 1 npt 1 + unde c1 , ..., cs , c1 , ..., cp1 1 , ..., c1 , ..., cpt 1 sunt constante, care se pot determina din conditiile initiale. Ecuatia caracteristic admite rdcini complexe simple a a a Fie r = aeib = a(cos b + i sin b) o rdcin complex. Ecuatia caracteristic a a a a a are coecienti reali, deci i conjugata r = aeib = a(cos b i sin b) este rdcin s a a a pentru ecuatia caracteristic. Atunci solutiile corespunztoare acestora sistemul a a n fundamental de solutii pentru recurenta liniar i omogen sunt as a x(1) = an cos bn, x(2) = an sin bn. n n Ecuatia caracteristic admite rdcini complexe multiple Dac a a a a ecuatia caracteristic admite perechea de rdcini complexe a a a r = aeib , r = aeib b = 0 de ordin de multiplicitate k, atunci solutiile corespunztoare acestora sistemul a n fundamental de solutii sunt x(1) = an cos bn, x(2) = nan cos bn, ..., x(k) = nk1 an cos bn, n n n
(k+2) (2k) x(k+1) = an sin bn, xn = nan sin bn, ..., xn = nk1 an sin bn, n (1) (1) (t) (t)

Pentru a obtine solutia general a recurentei omogene de ordinul n cu coecienti a constanti se procedeaz astfel: a 1. Se determin rdcinile ecuatiei caracteristice a a a 2. Se scrie contributia ecrei rdcini la solutia general. a a a a 3. Se nsumeaz i se obtine solutia general functie de n constante as a n arbitrare. 4. Dac sunt precizate conditiile initiale atunci se determin constantele a a i se obtine o solutie unic. s a

80

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

6.2

Ecuatii recurente neomogene

6.2.1

O form simpl a a

Considerm acum recurente de urmtoarea form mai general a a a a a0 tn + a1 tn1 + ... + ak tnk = bn p(n) unde b este o constant, iar p(n) este un polinom n de grad d. Ideea general a n a este s reducem un astfel de caz la o form omogen. a a a De exemplu, o astfel de recurent poate : a tn 2tn1 = 3n acest caz, b = 3 i p(n) = 1. In s Inmultim recurenta cu 3, i obtinem s 3tn 6tn1 = 3n+1 Inlocuind pe n cu n + 1 recurenta initial, avem n a tn+1 2tn = 3n+1 Scdem aceste dou ecuatii a a tn+1 5tn + 6tn1 = 0 Am obtinut o recurent omogen. Ecuatia caracteristic este: a a a x2 5x + 6 = 0 adic (x 2)(x 3) = 0. Intuitiv, observm c factorul (x 2) corespunde prtii a a a a stngi a recurentei initiale, timp ce factorul (x 3) a aprut ca rezultat al a n a calculelor efectuate pentru a scpa de partea dreapt. a a Generaliznd acest procedeu, se poate arta c, pentru a rezolva ecuatia a a a initial, este sucient s lum urmtoarea ecuatie caracteristic: a a a a a (a0 xk + a1 xk1 + + ak )(x b)d+1 = 0 Odat ce s-a obtinut aceast ecuatie, se procedeaz ca cazul omogen. a a a n Vom rezolva acum recurenta corespunzatoare problemei turnurilor din Hanoi: tn = 2tn1 + 1, n = 1 iar t0 = 0. Rescriem recurenta astfel tn 2tn1 = 1

6.2. ECUATII RECURENTE NEOMOGENE

81

care este de forma general prezentat la a a nceput, cu b = 1 si p(n) = 1. Ecuatia caracteristic este atunci (x 2)(x 1) = 0, cu solutiile 1 i 2. Solutia general a a s a recurentei este: tn = c1 1n + c2 2n Avem nevoie de dou conditii initiale. Stim c t0 = 0; pentru a gsi cea de-a a a a doua conditie calculm a t1 = 2t0 + 1 = 1. Din conditiile initiale, obtinem tn = 2n 1. Dac ne intereseaz doar ordinul lui tn , nu este necesar s calculm efectiv a a a a constantele solutia general. Dac tim c tn = c1 1n + c2 2n , rezult tn O(2n ). n a as a a Din faptul c numrul de mutri a unor discuri nu poate negativ sau a a a constant, deoarece avem mod evident tn n, deducem c c2 > 0. Avem atunci n a tn (2n ) i deci, tn (2n ). Putem obtine chiar ceva mai mult. Substituind s solutia general a napoi recurenta initial, gsim n a a 1 = tn 2tn1 = c1 + c2 2n 2(c1 + c2 2n1 ) = c1 Indiferent de conditia initial, c1 este deci 1. a

6.2.2

O form mai general a a

O ecuatie recurent neomogen de form mai general este: a a a a


k j=0

aj T n j = bn pd1 (n) + bn pd2 (n) + ... 2 1

care n pd (n) = nd + c1 nd1 + ... + cd Ecuatia caracteristic complet este: a a


k

Exemplul 3: Tn = 2T (n 1) + n + 2n , n 1, T0 = 0. Acestui caz corespund b1 = 1, p1 (n) = n, d1 = 1 i b2 = 2, p2 (n) = 1, i s d2 = 0, iar ecuatia caracteristic complet este: a a (r 2)(r 1)2 (r 2) = 0

j=0

aj rkj (r b1 )

d1 +1

(r b2 )

d2 +1

... = 0

82 cu solutia:

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

T (n) = c1 1n + c2 n 2n + c3 2n + c4 n 2n

T (0) = 0 T (1) = 2T (0) + 1 + 21 = 3 T (2) = 2T (1) + 2 + 22 = 12 T (3) = 2T (2) + 3 + 23 = 35 Deci

c3 c1 + c + c + 2c + 2c 1 2 3 4 c1 + 2c2 + 4c3 + 8c4 c1 + 3c2 + 8c3 + 24c4

=0 c1 c =3 2 c3 = 12 = 35 c4

= 2 = 1 =2 =1

T (n) = 2 n + 2n+1 + n 2n = O(n 2n ).

6.2.3

Teorema master

De multe ori apare relatia de recurent de forma a T (n) = aT (n/b) + f (n) (6.2.6)

unde a i b sunt constante iar f (n) este o functie (aplicarea metodei Divide et s Impera conduce de obicei la o astfel de ecuatie recurent). Aa numita teorem a s a Master d o metod general pentru rezolvarea unor astfel de recurente cnd f (n) a a a a este un simplu polinom. Solutia dat de teorema master este: a 1. dac f (n) = O nlogb (a) cu > 0 atunci T (n) = nlogb a a 2. dac f (n) = nlogb a atunci T (n) = nlogb a lg n a 3. dac f (n) = nlogb (a+) i af a s
n b

Din pcate, teorema Master nu functioneaz pentru toate functiile f (n), i a a s multe recurente utile nu sunt de forma (6.2.6). Din fericire a, aceasta este o ns tehnic de rezolvare a celor mai multe relatii de recurent provenite din metoda a a Divide et Impera.

cf (n) cu c < 1 atunci T (n) = (f (n)).

Pentru a rezolva astfel de ecuatii recurente vom reprezenta arborele generat de ecuatia recursiv. Rdcina arborelui contine valoarea f (n), i ea are noduri a a a s descendente care sunt noduri rdcin pentru arborele provenit din T (n/b). a a a

6.2. ECUATII RECURENTE NEOMOGENE

83

Pe nivelul i se a nodurile care contin valoarea ai f (n/bi ). Recursivitatea se a oprete cnd se obtine un caz de baz pentru recurent. s a a a Presupunem c T (1) = f (1). a Cu aceast reprezentare este foarte clar c T (n) este suma valorilor din a a nodurile arborelui. Presupunnd c ecare nivel este plin, obtinem a a T (n) = f (n) + af (n/b) + a2 f (n/b2 ) + a3 f (n/b3 ) + ... + ak f (n/bk ) unde k este adncimea arborelui de recursivitate. Din n/bk = 1 rezult k = logb n. a a Ultimul termen diferit de zero sum este de forma ak = alogb n = nlogb a (ultima n a egalitate ind alnit liceu!). nt a n Acum putem uor enunta i demonstra teorema Master. s s Teorema 1 (Teorema Master) Relatia de recurenta T (n) = aT (n/b)+f (n) are urmtoarea solutie: a dac af (n/b) = f (n) unde < 1 atunci T (n) = (f (n)); a dac af (n/b) = f (n) unde > 1 atunci T (n) = (nlogb a ); a dac af (n/b) = f (n) atunci T (n) = (f (n) logb n); a Demonstratie: Dac f (n) este un factor constant mai mare dect f (b/n), atunci a a prin inductie se poate arta c suma este a unei progresii geometrice descresctoare. a a a Suma acest caz este o constant n a nmultit cu primul termen care este f (n). a Dac f (n) este un factor constant mai mic dect f (b/n), atunci prin inductie a a se poate arta c suma este a unei progresii geometrice cresctoare. Suma acest a a a n caz este o constant a nmultit cu ultimul termen care este nlogb a . a

84

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

Dac af (b/n) = f (n), atunci prin inductie se poate arta c ecare din cei a a a k + 1 termeni din sum sunt egali cu f (n). a Exemple. 1. Selectia aleatoare: T (n) = T (3n/4) + n. Aici af (n/b) = 3n/4 iar f (n) = n, rezult = 3/4, deci T (n) = (n). a 2. Algoritmul de multiplicare al lui Karatsuba: T (n) = 3T (n/2) + n. Aici af (n/b) = 3n/2 iar f (n) = n, rezult = 3/2, deci T (n) = (nlog2 3 ). a 3. Mergesort: T (n) = 2T (n/2) + n. Aici af (n/b) = n, iar f (n) = n, rezult = 1 deci T (n) = (n log2 n). a Folosind acceai tehnic a arborelui recursiv, putem rezolva recurente pentru s a care nu se poate aplica teorema Master.

6.2.4

Transformarea recurentelor

La Mergesort am avut o relaie de recurent de forma T (n) = 2T (n/2) + n i a s am obtinut solutia T (n) = O(n log2 n) folosind teorema Master (metoda arborelui de recursivitate). Aceast modalitate este corect dac n este o putere a lui 2, dar a a a pentru alte valori ale lui n aceast recurent nu este corect. Cnd n este impar, a a a a recurenta ne cere s sortm un numr elemente care nu este a a a ntreg! Mai ru chiar, a dac n nu este o putere a lui 2, nu vom atinge niciodat cazul de baz T (1) = 0. a a a Pentru a obtine o recurent care s e valid pentru orice valori a a a ntregi ale lui n, trebuie s determinm cu atentie marginile inferioar i superioar: a a as a T (n) = T (n/2) + T (n/2) + n. Metoda transformrii domeniului rescrie functia T (n) sub forma S(f (n)), a unde f (n) este o functie simpl i S() are o recurent mai uoar. as a s a Urmtoarele inegaliti sunt evidente: a at T (n) 2T (n/2) + n 2T (n/2 + 1) + n. Acum denim o nou functie S(n) = T (n + ), unde este o constant a a necunoscut, aleas astfel at s e satisfcut recurenta din teorema Master a a nc a a a S(n) S(n/2) + O(n). Pentru a obtine valoarea corect a lui , vom compara a dou versiuni ale recurentei pentru functia S(n + ): a S(n) 2S(n/2) + O(n) T (n) 2T (n/2 + 1) + n T (n + ) 2T (n/2 + ) + O(n) T (n + ) 2T ((n + )/2 + 1) + n +

Pentru ca aceste dou recurente s e egale, trebuie ca n/2+ = (n+)/2+1, a a care implic = 2. Teorema Master ne spune acum c S(n) = O(n log n), deci a a T (n) = S(n 2) = O((n 2) log(n 2) = O(n log n).

6.2. ECUATII RECURENTE NEOMOGENE

85

Un argument similar d o ajustare a marginii inferioare T (n) = (n log n). a Deci, T (n) = (n log n) este un rezultat ntemeiat dei am ignorat marginile s inferioar i superioar de la as a nceput! Transformarea domeniului este util pentru aturarea marginilor inferioar a nl a i superioar, i a termenilor de ordin mic din argumentele oricrei recurente care s a s a se potrivete un pic cu teorema master sau metoda arborelui de recursivitate. s Exist geometria computational o structur de date numit arbore pliat, a n a a a pentru care costul operatiei de cutare a ndeplinete relatia de recurent s a T (n) = T (n/2) + T (n/4) + 1. Aceasta nu se potrivete cu teorema master, pentru c cele dou subprobleme s a a au dimensiuni diferite, i and metoda arborelui de recursivitate nu obtinem s utiliz dect nite margini slabe n << T (n) << n. a s Dac nu au forma standard, ecuatiile recurente pot aduse la aceast form a a a printr-o schimbare de variabil. O schimbare de variabil aplicabil pentru ecuatii a a a de recurent de tip multiplicativ este: a n = 2k k = log n De exemplu, e T (n) = 2 T (n/2) + n log n, n > 1 Facem schimbarea de variabil t(k) = T (2k ) i obtinem: a s t(k) 2 t(k 1) = k 2k , deci b = 2, p(k) = k, d = 1 Ecuatia caracteristic complet este: a a (r 2)3 = 0 cu solutia t(k) = c1 2k + c2 k 2k + c3 k 2 2k Deci T (n) = c1 n + c2 n log n + c3 n log2 n O(n log2 n|n = 2k ) Uneori, printr-o schimbare de variabil, putem rezolva recurente mult mai a complicate. exemplele care urmeaz, vom nota cu T (n) termenul general al In a recurentei si cu tk termenul noii recurente obtinute printr-o schimbare de variabil. a Presupunem pentru nceput c n este o putere a lui 2. a Un prim exemplu este recurenta T (n) = 4T (n/2) + n, n > 1

86

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

care n nlocuim pe n cu 2k , notam tk = T (2k ) = T (n) i obtinem s tk = 4tk1 + 2k Ecuatia caracteristic a acestei recurente liniare este a (x 4)(x 2) = 0 i deci, tk = c1 4k + c2 2k . s Inlocuim la loc pe k cu log2 n T (n) = c1 n2 + c2 n Rezult a T (n) O(n2 |n este o putere a lui 2) Un al doilea exemplu reprezint ecuatia l a T (n) = 4T (n/2) + n2 , n > 1 Procednd la fel, ajungem la recurenta a tk = 4tk1 + 4k cu ecuatia caracteristic a i solutia general tk = c1 42 + c2 k42 . s a Atunci, T (n) = c1 n2 + c2 n2 lg n i obtinem s sfrit, s considerm i exemplul In a s a a s T (n) O(n2 log n|n este o putere a lui 2) T (n) = 3T (n/2) + cn, n > 1 c ind o constant. Obtinem succesiv a T (2k ) = 3T (2k1 ) + c2k tk = 3tk1 + c2k cu ecuatia caracteristic a tk = c1 3k + c2 2k T (n) = c1 3lg n + c2 n (x 3)(x 2) = 0 (x 4)2 = 0

6.3. PROBLEME REZOLVATE i, deoarece s alg b = blg a obtinem T (n) = c1 nlg 3 + c2 n deci, T (n) O(nlg 3 |n este o putere a lui 2)

87

Putem enunta acum o proprietate care este util ca retet pentru analiza a a algoritmilor cu recursiviti de forma celor din exemplele precedente. at Fie T : N R+ o functie eventual nedescresctoare a T (n) = aT (n/b) + cnk , n > n0 unde: n0 1, b 2 si k 0 sunt ntregi; a este o putere a lui b. Atunci avem (nk ), T (n) (nk log n), (nlogb a ), i c sunt numere reale pozitive; n/n0 s pentru a < bk ; pentru a = bk ; pentru a > bk ;

6.3

Probleme rezolvate

1. S se rezolve ecuatia: a Fn+2 = Fn+1 + Fn , F0 = 0, F1 = 1. Ecuataia caracteristic corespunztoare a a r2 r 1 = 0 are solutiile 1 5 1+ 5 , r2 = . r1 = 2 2 1+ 5 2


n

Solutia general este a Fn = c1 + c2 1 5 2


n

Determinm constantele c1 i c2 din conditiile initiale F0 = 0 i F1 = 1. a s s Rezolvnd sistemul a c1 + c2 = 0 c1


1+ 5 2

+ c2

1 5 2

=1

88

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

1 1 obtinem c1 = 5 i c1 = 5 . s Deci, solutia relatiei de recurent care denete numerele lui Fibonacci este: a s n n 1 1+ 5 1 5 1 Fn = 2 2 5 5

2. S se rezolve relatia de recurent: a a xn+3 = xn+2 + 8xn+1 12xn , Ecuatia caracteristic corespunztoare este: a a r3 r2 8r + 12 = 0 i are solutiile: r1 = r2 = 2 i r3 = 3. s s Solutia general este de forma: a xn = (c1 + nc2 )2n + c3 (3)n . Din conditiile initiale rezult constantele: c1 = 1 , c2 = a 5 Solutia general este: a xn = n 1 + 2 5 1 2n (3)n . 5
1 2

x0 = 0, x1 = 2, x2 = 3.

i c3 = 1 . s 5

3. S se rezolve relatia de recurent: a a xn+3 = 6xn+2 12xn+1 + 8xn , Ecuatia caracteristic corespunztoare este: a a r3 6r2 + 12r 8 = 0 i are solutiile: r1 = r2 = r3 = 2. s Solutia general este de forma: a xn = (c1 + c2 n + c3 n2 )2n . Din conditiile initiale rezult constantele: c1 = 0, c2 = a Solutia general este: a xn =
3 2

x0 = 0, x1 = 2, x2 = 4.

i c3 = 1 . s 2

3 1 n n2 2n = (3n n2 )2n1 . 2 2

4. S se rezolve relatia de recurent: a a xn+2 = 2xn+1 2xn , x0 = 0, x1 = 1.

6.3. PROBLEME REZOLVATE Ecuatia caracteristic corespunztoare este: a a r2 2r + 2 = 0

89

i are solutiile: r1 = 1 + i i r2 = 1 i care se pot scrie sub form trigonometric s s a a astfel: r1 = 2 cos + i sin , r2 = 2 cos i sin . 4 4 4 4 Solutiile fundamentale sunt: x(1) = n 2
n

cos

n (2) 2 , xn = 4

sin

n . 4

Solutia general este de forma: a xn = 2


n

c1 cos

n n . + c2 sin 4 4

Din conditiile initiale rezult constantele: c1 = 0 si c2 = 1. a Solutia general este: a xn = 2


n

sin

n . 4

5. S se rezolve relatia de recurent: a a xn+3 = 4xn+2 6xn+1 + 4xn , Ecuatia caracteristic corespunztoare este: a a r3 4r2 + 6r 4 = 0 i are solutiile: r1 = 2, r2 = 1 + i i r3 = 1 i. s s Solutia general este de forma: a xn = c1 2n + c2 2
n

x0 = 0, x1 = 1, x2 = 1.

cos

n 2 + c3 4

sin

n . 4

3 1 Din conditiile initiale rezult constantele: c1 = 2 , c2 = 1 si c3 = 2 . a 2 Solutia general este: a n 2 n n n1 xn = 2 + cos . + 3 sin 2 4 4

6. S se rezolve relatia de recurent: a a T (n) 3T (n 1) + 4T (n 2) = 0, n 2, T (0) = 0, T (1) = 1.

90

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI Ecuatia caracteristic r2 3r + 4 = 0 are solutiile r1 = 1, r2 = 4, deci a T (n) = c1 (1)n + c2 4n Constantele se determin din conditiile initiale: a c1 + c2 = 0 c1 + 4c2 = 1
1 c1 = 5 c2 = 1 5

Solutia este: T (n) =

1 n [4 (1)n ] . 5

7. S se rezolve relatia de recurent: a a T (n) = 5T (n 1) 8T (n 2) + 4T (n 3), n 3, cu T (0) = 0, T (1) = 1, T (2) = 2. Ecuatia caracteristic: a r3 5r2 + 8r 4 = 0 r1 = 1, r2 = r3 = 2 deci T (n) = c1 1n + c2 2n + c3 n2n Determinarea constantelor c1 + c2 c + 2c2 + 2c3 1 c1 + 4c2 + 8c3 Deci T (n) = 2 + 2n+1

n n 2 = 2n+1 n2n1 2. 2

c1 = 2 =0 = 1 c2 = 2 1 =2 c3 = 2

8. S se rezolve relatia de recurent: a a T (n) = 4T (n/2) + n lg n. acest caz, avem af (n/b) = 2n lg n 2n, care nu este tocmai dublul lui In f (n) = n lg n. Pentru n sucient de mare, avem 2f (n) > af (n/b) > 1.9f (n). Suma este mrginit i inferior i superior de ctre serii geometrice cresctoare, a as s a a deci solutia este T (n) = (nlog2 4 ) = (n2 ). Acest truc nu merge cazurile doi i n s trei ale teoremei Master. 9. S se rezolve relatia de recurent: a a T (n) = 2T (n/2) + n lg n.

6.3. PROBLEME REZOLVATE

91

Nu putem aplica teorema Master pentru c af (n/b) = n/(lg n 1) nu este a egal cu f (n) = n/ lg n, iar diferenta nu este un factor constant. a Trebuie s calculm suma pe ecare nivel i suma total alt mod. Suma a a s a n tuturor nodurilor de pe nivelul i este n/(lg n i). particular, aceasta In nseamn a c adncimea arborelui este cel mult lg n 1. a a
lg n1

T (n) =
i=0

n = lg n i

lg n

j=1

n = nHlg n = (n lg lg n). j

10. (Quicksort aleator). S se rezolve relatia de recurent: a a T (n) = T (3n/4) + T (n/4) + n. acest caz nodurile de pe acelai nivel al arborelui de recursivitate au diferite In s valori. Nodurile din orice nivel complet (adic, deasupra oricrei frunze) au suma a a n, deci este la fel ca ultimul caz al teoremei Master i orice frunz are nivelul n s a ntre log4 n i log4/3 n. s Pentru a obtine o margine superioar, vom supraevalua T (n) ignornd cazurile a a de baz i extinznd arborele jos ctre nivelul celei mai adnci frunze. as a n a a Similar, pentru a obtine o margine inferioar pentru T (n), vom subevalua a T (n) contoriznd numai nodurile din arbore pn la nivelul frunzei care este cea a a a mai putin adnc. Aceste observatii ne dau marginile inferioar i superioar: a a as a n log4 n T (n) n log4/3 n. Deoarece aceste margini difer numai printr-un factor constant, avem c a a T (n) = (n log n). 11. (Selectie determinist). S se rezolve relatia de recurent: a a a T (n) = T (n/5) + T (7n/10) + n. Din nou, avem un arbore recursiv trunchiat. Dac ne uitm numai la a a nivelurile complete ale arborelui, observm c suma pe nivel formeaz o serie a a a geometric descresctoare T (n) = n + 9n/10 + 81n/100 + ..., deci este ca primul a a n caz al teoremei Master. Putem s obtinem o margine superioar ignornd cazurile a a a de baz totalitate i crescnd arborele spre innit, i putem obtine o margine a n s a s inferioar contoriznd numai nodurile din nivelurile complete. ambele situatii, a a In seriile geometrice sunt majorate de termenul cel mai mare, deci T (n) = (n). 12. S se rezolve relatia de recurent: a a T (n) = 2 n T ( n) + n. Avem cel mult lg lg n niveluri dar acum avem nodurile de pe nivelul i care au suma 2i n. Avem o serie geometric cresctoare a sumelor nivelurilor, la fel ca a a

92

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

cazul doi din teorema Master, deci T (n) este majorat de suma nivelurilor cele n a mai adnci. Se obtine: a T (n) = (2lg lg n n) = (n log n). 13. S se rezolve relatia de recurent: a a T (n) = 4 n T ( n) + n. Suma nodurilor de pe nivelul i este 4i n. Avem o serie geometric cresctoare, a a la fel ca cazul doi din teorema master, deci nu trebuie dect s avem grij de n a a a aceste niveluri. Se obtine T (n) = (4lg lg n n) = (n log2 n).

Capitolul 7

Algoritmi elementari
7.1 Operatii cu numere

7.1.1

Minim i maxim s

S presupunem c dorim s determinm valorile minim i maxim dintru-un a a a a as a vector x[1..n. Procedm astfel: a vmin = x[1]; vmax = x[1]; for i=2, n vmin = minim(vmin, x[i]) vmax = maxim(vmax, x[i]) Evident se fac 2n 2 comparatii. Se poate mai repede? Da! artim irul Imp s n dou i determinm vmin i vmax cele dou zone. Comparm vmin1 cu vmin2 as a s n a a i stabilim vminm. La fel pentru vmax. Prelucrarea se repet pentru cele dou s a a zone (deci se folosete recursivitatea). Apar cte dou comparatii plus de ecare s a a n dat. Dar cte sunt minus? Presupunem c n este o putere a lui 2 i T (n) este a a n a s numrul de comparatii. Atunci a T (n) = 2T (n/2) + 2 i T (2) = 1. s Cum rezolvm aceast relatie de recurent? Bnuim c solutia este de forma a a a a a T (n) = an + b. Atunci a i b trebuie s satisfac sistemul de ecuatii s a a 2a + b = 1 an + b = 2(an/2 + b) + 2 93

94

CAPITOLUL 7. ALGORITMI ELEMENTARI

care are solutia b = 2 i a = 3/2, deci (pentru n putere a lui 2), T (n) = 3n/2 2, s adic 75% din algoritmul anterior. Se poate demonstra c numrul de comparatii a a a este 3 n/2 2 pentru a aa minimum i maximum. s O idee similar poate aplicat pentru varianta secvential. Presupunem c a a a a irul are un numr par de termeni. Atunci, algoritmul are forma: s a vmin = minim(x[1],x[2]) vmax = maxim(x[1],x[2]) for(i=3;i<n;i=i+2) cmin = minim(x[i],x[i+1]) cmax = maxim(x[i],x[i+1]) if cmin < vmin vmin = cmin if vmax > cmax vmax = cmax Fiecare iteratie necesit trei comparatii, iar initializarea variabilelor necesit a a o comparatie. Ciclul se repet de (n 2)/2 ori, deci avem un total de 3n/2 2 a comparatii pentru n par.

7.1.2

Divizori

Fie n un numr natural. Descompunerea facori primi a n n = p1 p2 ... pk 2 1 k (7.1.1)

se numete descompunere canonic. Dac notm prin d(n) numrul divizorilor lui s a a a a n N, atunci: d(n) = (1 + 1 )(1 + 2 )...(1 + k ) (7.1.2) Pentru calculul lui d(n) putem folosi urmtorul algoritm: a static int ndiv(int n) { int d,p=1,nd; d=2;nd=0; while(n%d==0){nd++;n=n/d;} p=p*(1+nd); d=3; while(d*d<=n) { nd=0; while(n%d==0){nd++;n=n/d;}

7.2. ALGORITMUL LUI EUCLID p=p*(1+nd); d=d+2; } if(n!=1) p=p*2; return p; }

95

7.1.3

Numere prime

Pentru testarea primaliti unui numr putem folosi urmtorul algoritm: at a a static boolean estePrim(int nr) { int d; if(nr<=1) return false; if(nr==2) return true; if(nr%2==0) return false; d=3; while((d*d<=nr)&&(nr%d!=0)) d=d+2; if(d*d>nr) return true; else return false; }

7.2

Algoritmul lui Euclid

7.2.1

Algoritmul clasic

Un algoritm pentru calculul celui mai mare divizor comun (cmmdc) a dou a numere naturale poate descompunerea lor factori i calculul produsului tuturor n s divizorilor comuni. De exemplu dac a = 1134 = 2 3 3 3 3 7 i b = 308 = a s 2 2 7 11 atunci cmmdc(a, b) = 2 7 = 14. Descompunerea factori a unui numr natural n poate necesita n a ncercarea tuturor numerelor naturale din intervalul [2, n]. Un algoritm ecient pentru calculul cmmdc(a, b) este algoritmul lui Euclid. static int cmmdc(int a, int b} { int c; if (a < b) { c = a; a = b; b = c; } while((c=a%b) != 0) { a = b; b = c;} return b; }

96 Pentru a = 1134 i s a0 = 1134, a1 = 308, a2 = 210, a3 = 98,

CAPITOLUL 7. ALGORITMI ELEMENTARI b = 308 se obtine: b0 = 308; b1 = 210; b2 = 98; b3 = 14.

Lema 1 cmmdc(a x b, b) = cmmdc(a, b). Demonstratie: Pentru nceput artm c cmmdc(a x b, b) >= cmmdc(a, b). aa a Presupunem c d divide a b, deci a = c1 d i b = c2 d. Atunci d divide a x b a s s pentru c a x b = (c1 x c2 ) d. a Demonstrm i inegalitatea contrar cmmdc(a x b, b) <= cmmdc(a, b). a s a Presupunem c d divide a x b i b, deci a x b = c3 d i b = c2 d. Atunci d a s s divide a pentru c a = (a x b) + x b = (c3 + x c2 ) d. De aici rezult c a a a cmmdc(b, c) = cmmdc(c, b) = cmmdc(a mod b, b) = gcd(a, b). Prin inductie rezult c cel mai mare divizor comun al ultimelor dou numere a a a este egal cu cel mai mare divizor comun al primelor dou numere. Dar pentru cele a dou numere a i b din nal, cmmdc(a, b) = b, pentru c b divide a. a s a

7.2.2

Algoritmul lui Euclid extins

Pentru orice dou numere intregi pozitive, exist x i y (unul negativ) astfel a a s at x a + y b = cmmdc(a, b). Aceste numere pot calculate parcurgnd nc a napoi algoritmul clasic al lui Euclid. Fie ak i bk valorile lui a i b dup k iteratii ale buclei din algoritm. Fie xk i yk s s a s numerele care indeplinesc relatia xk ak + yk bk = cmmdc(ak , bk ) = cmmdc(a, b). Prin inductie presupunem c xk yk exist, pentru c la sfrit, cnd bk divide a s a a as a ak , putem lua xk = 0 yk = 1. s Presupunnd c xk i yk sunt cunoscute, putem calcula xk1 i yk1 . a a s s ak = bk1 i bk = ak1 mod bk1 = ak1 dk1 bk1 , unde s dk1 = ak1 /bk1 ( artire mp ntreag). a Substituind aceste expresii pentru ak i bk obtinem s cmmdc(a, b) = xk ak + yk bk = xk bk1 + yk (ak1 dk1 bk1 ) = yk ak1 + (xk yk dk1 ) bk1 . Astfel, innd cont de relatia xk1 ak1 +yk1 bk1 = cmmdc(a, b), obtinem t a xk1 = yk , yk1 = xk yk dk1 . Pentru 1134 i 308, obtinem: s a0 = 1134, b0 = 308, d0 = 3; a1 = 308, b1 = 210, d1 = 1; a2 = 210, b2 = 98, d2 = 2; a3 = 98, b3 = 14, d3 = 7.

7.3. OPERATII CU POLINOAME

97

i de asemenea, valorile pentru xk i yk : s s x3 = 0, y3 = 1; x2 = 1, y2 = 012 = 2; x1 = 2, y1 = 1+21 = 3; x0 = 3, y1 = 2 3 3 = 11. Desigur relatia 3 1134 11 308 = 14 este corect. Solutia nu este unic. a a S observm c (3 + k 308) 1134 (11 + k 1134) 308 = 14, pentru orice k, a a a ceea ce arat c valorile calculate pentru x = x0 i y = y0 nu sunt unice. a a s

7.3

Operatii cu polinoame

Toate operatiile cu polinoame obinuite se fac utiliznd iruri de numere s a s care reprezint coecientii polinomului. Notm cu a si b vectorii coecientilor a a polinoamelor cu care se opereaz i cu m i n gradele lor. Deci as s a(X) = am X m + ... + a1 X + a0 i b(X) = bn X n + ... + b1 X + b0 . s

7.3.1

Adunarea a dou polinoame a

Este asemntoare cu adunarea numerelor mari. a a static int[] sumap(int[] a, int[] b) { int m,n,k,i,j,minmn; int[] s; m=a.length-1; n=b.length-1; if(m<n) {k=n; minmn=m;} else {k=m; minmn=n;} s=new int[k+1]; for(i=0;i<=minmn;i++) s[i]=a[i]+b[i]; if(minmn<m) for(i=minmn+1;i<=k;i++) s[i]=a[i]; else for(i=minmn+1;i<=k;i++) s[i]=b[i]; i=k; while((s[i]==0)&&(i>=1)) i--; if(i==k) return s; else { int[] ss=new int[i+1]; for(j=0;j<=i;j++) ss[j]=s[j]; return ss; } }

98

CAPITOLUL 7. ALGORITMI ELEMENTARI

7.3.2

Inmultirea a dou polinoame a

Evident, gradul polinomului produs p = ab este m+n iar coecientul pk este suma tuturor produselor de forma ai bj unde i + j = k, 0 i m i 0 j n. s static int[] prodp(int[] a, int[] b) { int m,n,i,j; int[] p; m=a.length-1; n=b.length-1; p=new int[m+n+1]; for(i=0;i<=m;i++) for(j=0;j<=n;j++) p[i+j]+=a[i]*b[j]; return p; }

7.3.3

Calculul valorii unui polinom

Valoarea unui polinom se calculeaz ecient cu schema lui Horner: a a(x) = (...((an x + an1 ) x + an2 ) x + ... + a1 ) x + a0 static int valp(int[] a, int x) { int m,i,val; m=a.length-1; val=a[m]; for(i=m-1;i>=0;i--) val=val*x+a[i]; return val; }

7.3.4
Fie

Calculul derivatelor unui polinom

b(X) = bn X n + bn1 X n1 + ... + b1 X + b0 derivata de ordinul 1 a polinomului a(X) = am X m + am1 X m1 + ... + a1 X + a0 .

7.3. OPERATII CU POLINOAME Dar a (X) = m am X m1 + (m 1) am1 X m2 + ... + 2 a2 X + a1 . Rezult c a a n=m1 i s bi = (i + 1) ai+1 pentru 0 i n. static int[] derivp(int[] a) { int m,n,i; int[] b; m=a.length-1; n=m-1; b=new int[n+1]; for(i=0;i<=n;i++) b[i]=(i+1)*a[i+1]; return b; }

99

Pentru calculul valorii v = a (x) a derivatei polinomului a x este sucient n apelul v=valp(derivp(a),x);. Dac vrem s calculm derivata de ordinul k 0 a polinomului a, atunci a a a static int[] derivpk(int[] a,int k) { int i; int[] b; m=a.length-1; b=new int[m+1]; for(i=0;i<=n;i++) b[i]=a[i]; for(i=1;i<=k;i++) b=derivp(b); return b; } Pentru calculul valorii v = a(k) (x) a derivatei de ordinul k a polinomului a x este sucient apelul n v=valp(derivpk(a,k),x);.

100

CAPITOLUL 7. ALGORITMI ELEMENTARI

7.4

Operatii cu multimi

O multime A se poate memora ntr-un vector a, ale crui elemente sunt a distincte. Folosind vectorii putem descrie operatiile cu multimi.

7.4.1

Apartenenta la multime

Testul de apartenent a unui element x la o multime A, este prezentat a n algoritmul urmtor: a static boolean apartine(int[] a, int x) { int i,n=a.length; boolean ap=false; for(i=0;i<n;i++) if(a[i]==x) {ap=true; break;} return ap; }

7.4.2

Diferenta a dou multimi a

Diferenta a dou multimi este dat de multimea a a C = A B = {x|x A, x B} / Notm card A = m. a static int[] diferenta(int[] a, int[] b) { int i, j=0, m=a.length; int[] c=new int[m]; for(i=0;i<m;i++) if(!apartine(b,a[i]) c[j++]=a[i]; if(j==m) return c; else { int[] cc=new int[j]; for(i=0;i<j;i++) cc[i]=c[i]; return cc; } }

7.4. OPERATII CU MULTIMI

101

7.4.3

Reuniunea i intersectia a dou multimi s a

Reuniunea a dou multimi este multimea: a C = A B = A (B A). Introducem C toate elementele lui A i apoi elementele lui B A. n s static int[] reuniune(int[] a, int[] b) { int i, j, m=a.length, n=b.length; int[] c=new int[m+n]; for(i=0;i<m;i++) c[i]=a[i]; j=m; for(i=0;i<n;i++) if(!apartine(a,b[i]) c[j++]=b[i]; if(j==m+n) return c; else { int[] cc=new int[j]; for(i=0;i<j;i++) cc[i]=c[i]; return cc; } } Intersectia a dou multimi este multimea: a C = A B = {x|x A i x B} s static int[] reuniune(int[] a, int[] b) { int i, j, m=a.length; int[] c=new int[m]; j=0; for(i=0;i<m;i++) if(apartine(b,a[i]) c[j++]=a[i]; if(j==m) return c; else { int[] cc=new int[j]; for(i=0;i<j;i++) cc[i]=c[i]; return cc; } }

7.4.4

Produsul cartezian a dou multimi a

102

CAPITOLUL 7. ALGORITMI ELEMENTARI Produs cartezian a doua multimi este multimea: A B = {(x, y)|x A i y B} s

Putem stoca produsul cartezian sub forma unei matrice C cu dou linii i a s m n coloane. Fiecare coloan a matricei contine cte un element al produsului a a cartezian. static int[][] prodc(int[] a, int[] b) { int i, j, k, m=a.length, n=b.length; int[][] c=new int[2][m*n]; k=0; for(i=0;i<m;i++) for(j=0;j<n;j++) { c[0][k]=a[i]; c[1][k]=b[j]; k++; } return c; } De exemplu, pentru A = {1, 2, 3, 4} i B = {1, 2, 3}, matricea C este s linia 0 linia 1 0 1 1 1 1 2 2 1 3 3 2 1 4 2 2 5 2 3 6 3 1 7 3 2 8 3 3 9 4 1 10 4 2 11 4 3

7.4.5

Generarea submultimilor unei multimi

Generarea submultimilor unei multimi A = {a1 , a2 , ..., an }, este identic cu a generarea submultimilor multimii de indici {1, 2, ..., n}. O submultime se poate memora sub forma unui vector cu n componente, unde ecare component poate avea valori 0 sau 1. Componenta i are valoarea 1 a dac elementul ai apartine submultimii i 0 caz contrar. O astfel de reprezentare a s n se numete reprezentare prin vector caracteristic. s Generarea tuturor submultimilor nseamn generarea tuturor combinatiilor a de 0 i 1 care pot retinute de vectorul caracteristic V , adic a tuturor numerelor s a baza 2 care se pot reprezenta folosind n cifre. n Pentru a genera adunarea binar, inem cont c trecerea de la un ordin la n t a urmtorul se face cnd se obtine suma egal cu 2, adic 1 + 1 = (10)2 . a a a a pozitia 1 2 3 4 De exemplu, pentru n = 4, vom folosi un vector v valoarea

7.4. OPERATII CU MULTIMI initial 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 1 2 0 1 0 i adunm 1 s a 1 i adunm 1 s a

103

obtinem 0 obtinem 0 obtinem 0 obtinem 0 obtinem 0 obtinem 0 obtinem 0 obtinem obtinem 1

2 care nu este permis, i trecem la ordinul urmtor s a 0 i adunm 1 s a 1 i adunm 1 s a 2 care nu este permis, i trecem la ordinul urmtor s a 0 care nu este permis, i trecem la ordinul urmtor s a 0 i aa mai departe s s pn cnd a a a 1

Aceste rezultate se pot retine ntr-o matrice cu n linii i 2n coloane. s a1 a2 a3 a4 0 0 0 0 0 1 0 0 0 1 2 0 0 1 0 3 0 0 1 1 4 0 1 0 0 5 0 1 0 1 6 0 1 1 0 7 0 1 1 1 8 1 0 0 0 9 1 0 0 1 A 1 0 1 0 B 1 0 1 1 C 1 1 0 0 D 1 1 0 1 E 1 1 1 0 F 1 1 1 1 0 1 2 3

Ultima coloan contine numrul liniei din matrice. Coloana 0 reprezint a a a multimea vid, coloana F reprezint a a ntreaga multime, i, de exemplu, coloana 5 s reprezint submultimea {a2 , a4 } iar coloana 7 reprezint submultimea {a2 , a3 , a4 }. a a static int[][] submultimi(int n) { int i, j, nc=1; int[] v=new int[n+1]; int[][] c; for(i=1;i<=n;i++) nc*=2; c=new int[n][nc]; for(i=1;i<=n;i++) v[i]=0; j=0; while(j<nc) { v[n]=v[n]+1; i=n; while(v[i]>1) { v[i]=v[i]-2; v[i-1]=v[i-1]+1; i--; } for(i=1;i<=n;i++) c[j][i-1]=v[i]; j++; } return c; }

104

CAPITOLUL 7. ALGORITMI ELEMENTARI

7.5

Operatii cu numere ntregi mari

Operatiile aritmetice sunt denite numai pentru numere reprezentate pe 16, 32 sau 64 biti. Dac numerele sunt mai mari, operatiile trebuie implementate de a utilizator.

7.5.1

Adunarea i scderea s a

Adunarea i scderea sunt directe: aplicnd metodele din coala elementar. s a a s a static int[] suma(int[] x, int[] y) { int nx=x.length; int ny=y.length; int nz; if(nx>ny) nz=nx+1; else nz=ny+1; int[] z=new int[nz]; int t,s,i; t=0; for (i=0;i<=nz-1;i++) { s=t; if(i<=nx-1) s=s+x[i]; if(i<=ny-1) s=s+y[i]; z[i]=s%10; t=s/10; } if(z[nz-1]!=0) return z; else { int[] zz=new int[nz-1]; for (i=0;i<=nz-2;i++) zz[i]=z[i]; return zz; } }

7.5. OPERATII CU NUMERE INTREGI MARI

105

7.5.2

Inmultirea i artirea s mp

Metoda aat oal este corect. nvt a n s a a static int[] produs(int[]x,int[]y) { int nx=x.length; int ny=y.length; int nz=nx+ny; int[] z=new int[nz]; int[] [] a=new int[ny][nx+ny]; int i,j; int t,s; for(j=0;j<=ny-1;j++) { t=0; for(i=0;i<=nx-1;i++) { s=t+y[j]*x[i]; a[j][i+j]=s%10; t=s/10; } a[j][i+j]=t; } t=0; for(j=0;j<=nz-1;j++) { s=0; for(i=0;i<=ny-1;i++) s=s+a[i][j]; s=s+t; z[j]=s%10; t=s/10; } if(z[nz-1]!=0) return z; else { int[] zz=new int [nz-1]; for(j=0;j<=nz-2;j++) zz[j]=z[j]; return zz; } }

106

CAPITOLUL 7. ALGORITMI ELEMENTARI

7.5.3

Puterea

Presupunem c vrem s calculm xn . Cum facem acest lucru? Este evident a a a c urmtoarea secvent functioneaz: a a a a for (p = 1, i = 0; i < n; i++) p *= x; Presupunnd c toate a a nmultirile sunt efectuate ntr-o unitate de timp, acest algoritm are complexitatea O(n). Totui, putem s facem acest lucru mai repede! s a Presupunnd, pentru a nceput, c n = 2k , urmtorul algoritm este corect: a a for (p = x, i = 1; i < n; i *= 2) p *= p; Aici numrul de treceri prin ciclu este egal cu k = log2 n. a Acum, s considerm cazul general. Presupunem c n are expresia binar a a a a (bk , bk1 , ..., b1 , b0 ). Atunci putem scrie
k

n=
i=0,bi =1

2i .

Deci,
k

xn =
i=0,bi =1

x2 .

int exponent_1(int x, int n) { int c, z; for (c = x, z = 1; n != 0; n = n / 2) { if (n & 1) /* n este impar */ z *= c; c *= c; } return z; } int exponent_2(int x, int n) { if (n == 0) return 1; if (n & 1) /* n este impar */ return x * exponent_2(x, n - 1); return exponent_2(x, n / 2) * exponent_2(x, n / 2); }

7.6. OPERATII CU MATRICE int exponent_3(int x, int n) { int y; if (n == 0) return 1; if (n & 1) /* n este impar */ return x * exponent_3(x, n - 1); y = exponent_3(x, n / 2); return y * y; }

107

7.6

Operatii cu matrice

7.6.1

Inmultirea

O functie scris C/C++: a n void matrix_product(int** A, int** B, int** C) { for (i = 0; i < n; i++) for (j = 0; j < n; j++) { C[i][j] = 0; for (k = 0; k < n; k++) C[i][j] += A[i][k] * B[k][j]; } }

7.6.2

Inversa unei matrice

O posibilitate este cea din coal. Aceasta presupune calculul unor determinanti. s a Determinantul det(A) se denete recursiv astfel: s
n1

det(A) =
i=0

(1)i+j ai,j det(Ai,j ).

unde ai,j este element al matricei iar Ai,j este submatricea obtinut prin eliminarea a liniei i i a coloanei j. s

108

CAPITOLUL 7. ALGORITMI ELEMENTARI

int determinant(int n, int[][] a) { if (n == 1) return a[0][0]; int det = 0; int sign = 1; int[][] b = new int[n - 1][n - 1]; for (int i = 0; i < n; i++) { for (int j = 0; j < i; j++) for (int k = 1; k < n; k++) b[j][k - 1] = a[j][k]; for (int j = i + 1; j < n; j++) for (int k = 1; k < n; k++) b[j - 1][k - 1] = a[j][k]; det += sign * a[i][0] * determinant(n - 1, b); sign *= -1; } } Folosind determinanti, inversa matricei se poate calcula folosind regula lui Cramer. Presupunem c A este inversabil i e B = (bi,j ) matricea denit prin a as a bi,j = (1)i+j det(Ai,j )/ det(A). Atunci A1 = B T , unde B T este transpusa matricei B.

Capitolul 8

Algoritmi combinatoriali
8.1 Principiul includerii i al excluderii i aplicatii s s

8.1.1

Principiul includerii i al excluderii s

Fie A i B dou multimi nite. Notm prin |A| cardinalul multimii A. Se s a a deduce uor c: s a |A B| = |A| + |B| |A B|. Fie A o multime nit i A1 , A2 , ..., An submultimi ale sale. Atunci numrul as a elementelor lui A care nu apar nici una din submultimile Ai (i = 1, 2, ..., n) este n egal cu:
n

|A|

i=1

|Ai |+

1i<jn

|Ai Aj |

1i<j<kn

|Ai Aj Ak |+...+(1)n |A1 A2 ...An |

Se pot demonstra prin inductie matematic urmtoarele formule: a a


n n n

i=1 n

Ai | =

i=1 n

|Ai |

1i<jn

|Ai Aj |+

1i<j<kn

|Ai Aj Aj |...+(1)n+1 |

i=1 n

Ai |

i=1

Ai | =

i=1

|Ai |

1i<jn

|Ai Aj |+

1i<j<kn

|Ai Aj Aj |...+(1)n+1 |

i=1

Ai |

109

110

CAPITOLUL 8. ALGORITMI COMBINATORIALI

8.1.2

Numrul functiilor surjective a

Se dau multimile X = {x1 , x2 , ..., xm } i Y = {y1 , y2 , ..., yn }. s Fie Sm,n numrul functiilor surjective f : X Y . a Fie A = {f |f : X Y } (multimea tuturor functiilor denite pe X cu valori Y ) i Ai = {f |f : X Y, yi f (X)} (multimea functiilor pentru care yi nu n s / este imaginea nici unui element din X). Atunci
n

Sm,n = |A| |

i=1

Ai |

Folosind principiul includerii i al excluderii, obtinem s


n

Sm,n = |A|

i=1

|Ai | +

1i<jn

|Ai Aj | ... + (1)n |A1 A2 ... An |

etc.

Se poate observa uor c |A| = nm , |Ai | = (n 1)m , |Ai Aj | = (n 2)m , s a Din Y putem elimina k elemente Cn moduri, deci n k
k 1i1 <i2 <...<ik n j=1

k Aij | = Cn (n k)m

Rezult: a
1 2 n1 Sm,n = nm Cn (n 1)m + Cn (n 2)m + ... + (1)n1 Cn

Observatii: 1. Deoarece A1 A2 ... An = i pentru c nu poate exista o functie care s a s nu ia nici o valoare, ultimul termen lipsete. a s 2. Dac n = m atunci numrul functiilor surjective este egal cu cel al functiilor a a injective, deci Sm,n = n! i se obtine o formul interesant: s a a
n1

n! =
k=0

k (1)k Cn (n k)n

class Surjectii { public static void main (String[]args) { int m, n=5, k, s; for(m=2;m<=10;m++) { s=0;

8.1. PRINCIPIUL INCLUDERII SI AL EXCLUDERII SI APLICATII for(k=0;k<=n-1;k++) s=s+comb(n,k)*putere(-1,k)*putere(n-k,m); System.out.println(m+" : "+s); } System.out.println("GATA"); } static int putere (int a, int n) { int rez=1, k; for(k=1;k<=n;k++) rez=rez*a; return rez; } static int comb (int n, int k) { int rez, i, j, d; int[] x=new int[k+1]; int[] y=new int[k+1]; for(i=1;i<=k;i++) x[i]=n-k+i; for(j=1;j<=k;j++) y[j]=j; for(j=2;j<=k;j++) for(i=1;i<=k;i++) { d=cmmdc(y[j],x[i]); y[j]=y[j]/d; x[i]=x[i]/d; if(y[j]==1) break; } rez=1; for(i=1;i<=k;i++) rez=rez*x[i]; return rez; } static int cmmdc (int a,int b) { int d,i,c,r; if (a>b) {d=a;i=b;} else{d=b;i=a;} while (i!=0) { c=d/i; r=d%i; d=i; i=r; } return d; } }

111

112

CAPITOLUL 8. ALGORITMI COMBINATORIALI

8.1.3

Numrul permutrilor fr puncte xe a a a a

Fie X = {1, 2, ..., n}. Dac p este o permutare a elementelor multimii X, a spunem c numrul i este un punct x al permutrii p, dac p(i) = i (1 i n). a a a a Se cere s se determine numrul D(n) al permutrilor fr puncte xe, ale a a a aa multimii X. S notm cu Ai multimea celor (n1)! permutri care admit un punct a a a x i (dar nu obligatoriu numai acest punct x!). Folosind principiul includerii n i al excluderii, numrul permutrilor care admit cel putin un punct x este egal s a a cu:
n n

|A1 A2 ... An | = Dar

i=1

|Ai |

1i<jn

|Ai Aj | + ... + (1)n1 |

i=1

Ai |.

|Ai1 Ai2 ... Aik | = (n k)! n deoarece o permutare din multimea Ai1 Ai2 ... Aik are puncte xe pozitiile i1 , i2 , ..., ik , celelalte pozitii continnd o permutare a celor n k elemente rmase a a (care pot avea sau nu puncte xe!). Cele k pozitii i1 , i2 , ..., ik pot alese Cn n k moduri, deci
1 2 n |A1 A2 ... An | = Cn (n 1)! Cn (n 2)! + ... + (1)n1 Cn .

Atunci D(n) = n! |A1 A2 ... An | = 1 2 n = n! Cn (n 1)! + Cn (n 2)! +... + (1)n Cn 1 1 (1)n 1 . = n! 1 + + ... + 1! 2! 3! n!

De aici rezult c a a

D(n) = e1 , n! deci, pentru n mare, probabilitatea ca o permutare a n elemente, aleas aleator, a s nu aib puncte xe, este de e1 0.3678. a a Se poate demonstra uor c: s a
n

lim

D(n + 1) D(n + 1)

(n + 1)D(n) + (1)n+1

= n (D(n) + D(n 1)) .

class PermutariFixe { public static void main(String [] args) {

8.2. PRINCIPIUL CUTIEI LUI DIRICHLET SI APLICATII long n=10,k,s=0L,xv,xn; // n=22 maxim pe long ! if((n&1)==1) xv=-1L; else xv=1L; s=xv; for(k=n;k>=3;k--) { xn=-k*xv; s+=xn; xv=xn; } System.out.println("f("+n+") = "+s); } }

113

8.2

Principiul cutiei lui Dirichlet i aplicatii s


Acest principiu a fost formulat prima dat de Dirichle (1805-1859). a forma cea mai simpl acest principiu se enunt astfel: In a a Dac n obiecte trebuie artite mai putin de n multimi, atunci a mp n exist cel putin o multime care vor cel putin dou obiecte. a n a Mai general, principiul lui Dirichlet se poate enunta astfel: Fiind date m obiecte, care trebuie artite n multimi, i un numr mp n s a natural k astfel at m > kn, atunci, cazul oricrei artiri, va nc n a mp exista cel putin o multime cu cel putin k + 1 obiecte. Pentru k = 1 se obtine formularea anterioar. a Cu ajutorul functiilor, principiul cutiei se poate formula astfel: Fie A i B dou multimi nite cu |A| > |B| i functia f : A B. s a s Atunci, exist b B cu proprietatea c |f 1 (b)| 2. Dac notm a a a a |A| = n i |B| = r atunci |f 1 (b)| n . s r Demonstrm ultima inegalitate. Dac aceasta nu ar adevrat, atunci a a a a |f 1 (b)| < n , b B. r n =n r

Dar multimea B are r elemente, deci n=


bB

|f 1 (b)| < r

ceea ce este o contradictie.

8.2.1

Problema subsecventei

Se d un ir nit a1 , a2 , ..., an de numere a s ntregi. Exist o subsecvent ai , ai+1 , ..., aj a a cu proprietatea c ai + ai+1 + ... + aj este un multiplu de n. a

114

CAPITOLUL 8. ALGORITMI COMBINATORIALI S considerm urmtoarele sume: a a a s1 s2 = a1 , = a1 + a2 ,

... sn = a1 + a2 + ... + an . Dac exist un k astfel sk este multiplu de n atunci i = 1 i j = k. a a s Dac nici o sum partial sk nu este multiplu de n, atunci resturile artirii a a a mp acestor sume partiale la n nu pot dect multimea {1, 2, ..., n 1}. Pentru c a n a avem n sume partiale i numai n 1 resturi, s nseamn c exist cel putin dou a a a a s a a s sume partiale (sk1 i sk2 , unde k1 < k2 ) cu acelai rest. Atunci subsecventa cutat se obtine lund i = k1 + 1 i j = k2 . a s

8.2.2

Problema subirurilor strict monotone s

Se d irul de numere reale distincte a1 , a2 , ..., amn+1 . Atunci, irul contine as s un subir cresctor de m + 1 elemente: s a ai1 < ai2 < ... < aim+1 unde 1 i1 < i2 < ... < im+1 mn + 1, sau un subir descresctor de n + 1 elemente s a aj1 < aj2 < ... < ajn+1 unde 1 j1 < j2 < ... < jn+1 mn + 1, sau ambele tipuri de subiruri. s Fiecrui element al irului asociem perechea de numere naturale (xi , yi ) a s i unde xi este lungimea maxim a subirurilor cresctoare care a s a ncep cu ai iar yi este lungimea maxim a subirurilor descresctoare care a s a ncep ai . n Presupunem c armatia problemei nu este adevrat, adic: pentru toate a a a a numerele naturale xi i yi avem 1 xi m i 1 yi n. Atunci perechile de s s numere (xi , yi ) pot avea mn elemente distincte. Deoarece irul are mn + 1 termeni, exist un ai i un aj pentru care perechile s a s de numere (xi , yi ) i (xj , yj ) sunt identice (xi = xj , yi = yj ), dar acest lucru este s imposibil (cei doi termeni ai i aj ar trebui s coincid), ceea ce este o contraditie. s a a Deci exist un subir cresctor cu m + 1 termeni sau un subir descresctor a s a s a cu n + 1 termeni.

8.3

Numere remarcabile

8.3. NUMERE REMARCABILE

115

8.3.1

Numerele lui Fibonacci

Numerele lui Fibonacci se pot deni recursiv prin: F0 = 0, F1 = 1, Fn = Fn1 + Fn2 pentru n 2. Primele numere Fibonacci sunt: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, ... Se poate arta c a a Fn = 2n 1 5 1+ 5
n

(8.3.1)

Numerele lui Fibonacci satisfac multe identiti interesante, ca de exemplu: at 1 1 1 0


n

= =

Fn+1 Fn (1)n

Fn Fn1

(8.3.2) (8.3.3) (8.3.4) (8.3.5) (8.3.6)

2 Fn+1 Fn1 Fn

Fn+m Fnk

= Fm Fn+1 + Fm1 Fn = multiplu de Fk

i s F2 + F4 + ... + F2n F1 + F3 + ... + F2n1


2 2 2 F1 + F2 + ... + Fn F1 F2 + F2 F3 + ... + F2n1 F2n

= F2n+1 1 = F2n = Fn Fn+1 2 = F2n


2 = F2n+1 1

(8.3.7) (8.3.8) (8.3.9) (8.3.10) (8.3.11)

F1 F2 + F2 F3 + ... + F2n F2n+1

Teorema 2 Orice numr natural n se poate descompune a ntr-o sum de numere a Fibonacci. Dac nu se folosesc descompunere numerele F0 i F1 i nici dou a n s s a numere Fibonacci consecutive, atunci aceast descompunere este unic abstractie a a fcnd de ordinea termenilor. a a Folosind aceast descompunere, numerele naturale pot reprezentate asemntor a a a reprezentrii baza 2. De exemplu a n 19 = 1 13 + 0 8 + 1 5 + 0 3 + 0 2 + 1 1 = (101001)F aceast scriere nu pot exista dou cifre 1 alturate. In a a a

116

CAPITOLUL 8. ALGORITMI COMBINATORIALI

import java.io.*; class DescFibo { static int n=92; static long[] f=new long[n+1]; public static void main (String[]args) throws IOException { int iy, k, nrt=0; long x,y; // x=1234567890123456789L; cel mult! BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); System.out.print("x = "); x=Long.parseLong(br.readLine()); f[0]=0; f[1]=1; f[2]=1; for(k=3;k<=n;k++) f[k]=f[k-1]+f[k-2]; for(k=0;k<=n;k++) System.out.println(k+" : "+f[k]); System.out.println(" "+Long.MAX_VALUE+" = Long.MAX_VALUE"); System.out.println(" x = "+x); while(x>0) { iy=maxFibo(x); y=f[iy]; nrt++; System.out.println(nrt+" : "+x+" f["+iy+"] = "+y); x=x-y; } } static int maxFibo(long nr) { int k; for(k=1;k<=n;k++) if (f[k]>nr) break; return k-1; } }

8.3.2

Numerele lui Catalan

Numerele Cn =

1 Cn n + 1 2n

8.3. NUMERE REMARCABILE

117

se numesc numerele lui Catalan. Ele apar multe probleme, ca de exemplu: n numrul arborilor binari, numrul de parantezri corecte, numrul drumurilor sub a a a a diagonal care unesc punctele (0, 0) i (n, n) formate din segmente orizontale i a s s verticale, numrul secventelor cu n biti care numrul cifrelor 1 nu depete a n a as s numrul cifrelor 0 nici o pozitie plecnd de la stnga spre dreapta, numrul a n a a a segmentelor care unesc 2n puncte plan fr s se intersecteze, numrul irurilor n aa a a s (x1 , x2 , ..., x2n ) care xi {1, 1} i x1 + x2 + ... + x2n = 0 cu proprietatea n s x1 + x2 + ... + xi 0 pentru orice i = 1, 2, ..., 2n 1, numrul modurilor de a a triangulariza un poligon, i multe altele. s Numerele lui Catalan sunt solutie a urmtoarei ecuatii de recurent: a a Cn+1 = C0 Cn + C1 Cn1 + ... + Cn C0 , pentru n 0 i C0 = 1. s Numerele lui Catalan veric i relatia: as Cn+1 = 4n + 2 Cn n+2

O implementare cu numere mari este: class Catalan { public static void main (String[]args) { int n; int[] x; for(n=1;n<=10;n++) { x=Catalan(n); System.out.print(n+" : "); afisv(x); } } static int[] inm(int[]x,int[]y) { int i, j, t, n=x.length, m=y.length; int[][]a=new int[m][n+m]; int[]z=new int[m+n]; for(j=0;j<m;j++) { t=0; for(i=0;i<n;i++) { a[j][i+j]=y[j]*x[i]+t; t=a[j][i+j]/10;

118

CAPITOLUL 8. ALGORITMI COMBINATORIALI a[j][i+j]=a[j][i+j]%10; } a[j][i+j]=t; } t=0; for(j=0;j<m+n;j++) { z[j]=t; for(i=0;i<m;i++) z[j]=z[j]+a[i][j]; t=z[j]/10; z[j]=z[j]%10; } if(z[m+n-1]!= 0) return z; else { int[]zz=new int[m+n-1]; for(i=0;i<=m+n-2;i++) zz[i]=z[i]; return zz; }

} static void afisv(int[]x) { int i; for(i=x.length-1;i>=0;i--) System.out.print(x[i]); System.out.print(" *** "+x.length); System.out.println(); } static int[] nrv(int nr) { int nrrez=nr; int nc=0; while(nr!=0) { nc++; nr=nr/10; } int[]x=new int [nc]; nr=nrrez; nc=0; while(nr!=0) { x[nc]=nr%10; nc++; nr=nr/10; } return x; } static int[] Catalan(int n) { int[] rez;

8.4. DESCOMPUNEREA FACTORI PRIMI IN int i, j, d; int[] x=new int[n+1]; int[] y=new int[n+1]; for(i=2;i<=n;i++) x[i]=n+i; for(j=2;j<=n;j++) y[j]=j; for(j=2;j<=n;j++) for(i=2;i<=n;i++) { d=cmmdc(y[j],x[i]); y[j]=y[j]/d; x[i]=x[i]/d; if(y[j]==1) break; } rez=nrv(1); for(i=2;i<=n;i++) rez=inm(rez,nrv(x[i])); return rez; } static int cmmdc (int a,int b) { int d,i,c,r; if (a>b) {d=a;i=b;} else{d=b;i=a;} while (i!=0) { c=d/i; r=d%i; d=i; i=r; } return d; } }

119

8.4

Descompunerea factori primi n

8.4.1

Functia lui Euler

Functia (n) a lui Euler ne d numrul numerelor naturale mai mici ca n i a a s prime cu n. Numrul n poate descompus factori primi sub forma: a n n = p1 p2 ...pm m 2 1 Notm cu Ai multimea numerelor naturale mai mici ca n care sunt multipli de pi . a Atunci avem: n n n |Ai | = , |Ai Aj | = , |Ai Aj Ak | = , ... pi pi pj pi pj pk

120 Rezult: a
m

CAPITOLUL 8. ALGORITMI COMBINATORIALI

(n) = n

i=1

n + pi

1i<jm

n pi pj

1i<j<km

n n + ... + (1)m pi pj pk p1 p2 ...pm

care este tocmai dezvoltarea produsului (n) = n 1 1 p1 1 1 p2 ... 1 1 pm

class Euler { static long[] fact; public static void main (String[]args) { long n=36L; // Long.MAX_VALUE=9.223.372.036.854.775.807; long nrez=n; long[] pfact=factori(n); // afisv(fact); // afisv(pfact); int k,m=fact.length-1; for(k=1;k<=m;k++) n/=fact[k]; for(k=1;k<=m;k++) n*=fact[k]-1; System.out.println("f("+nrez+") = "+n); } static long[] factori(long nr) { long d, nrrez=nr; int nfd=0; // nr. factori distincti boolean gasit=false; while((nr!=1)&(nr%d==0)) { nr=nr/d; gasit=true; } if(gasit) {nfd++;gasit=false;} d=3; while(nr!=1) { while((nr!=1)&(nr%d==0)) { nr=nr/d; gasit=true; } if(gasit) {nfd++;gasit=false;} d=d+2; } nr=nrrez; fact=new long[nfd+1]; long[] pf=new long[nfd+1]; int pfc=0; // puterea factorului curent nfd=0; // nr. factori distincti

8.4. DESCOMPUNEREA FACTORI PRIMI IN gasit=false; d=2; while((nr!=1)&(nr%d==0)) { nr=nr/d; gasit=true; pfc++; } if(gasit) {fact[++nfd]=d;pf[nfd]=pfc;gasit=false;pfc=0;} d=3; while(nr!=1) { while((nr!=1)&(nr%d==0)) { nr=nr/d; gasit=true; pfc++; } if(gasit) {fact[++nfd]=d;pf[nfd]=pfc;gasit=false;pfc=0;} d=d+2; } return pf; }//descfact static void afisv(long[] a) { for(int i=1;i<a.length;i++) System.out.print(a[i]+" "); System.out.println(); } }

121

8.4.2
Fie

Numrul divizorilor a
e e e n = f1 1 f2 2 ... fk k

descompunerea lui n factori primi i ndiv(n) numrul divizorilor lui n. Atunci n s a ndiv(n) = (1 + e1 ) (1 + e2 ) ... (1 + ek ).

8.4.3
Fie

Suma divizorilor
e e e n = f1 1 f2 2 ... fk k

descompunerea lui n factori primi i sdiv(n) suma divizorilor lui n. Atunci n s sdiv(n) =
d|n

d=

f1

(1+e1 )

k f 1 1 f2 2 1 ... k . f1 1 f2 1 fk 1

(1+e )

(1+e )

Demonstratie: Fie n = ab cu a = b i cmmdc(a, b) = 1. Pentru orice divizor d al lui n, s d = ai bj , unde ai este divizor al lui a iar bj este divizor al lui b. Divizorii lui a sunt: 1, a1 , a2 , ..., a. Divizorii lui b sunt: 1, b1 , b2 , ..., b.

122

CAPITOLUL 8. ALGORITMI COMBINATORIALI Sumele divizorilor lui a i b sunt: s sdiv(a) = 1 + a1 + a2 + ... + a sdiv(b) = 1 + b1 + b2 + ... + b. Dar sdiv(ab) =
d|ab

d=
i,j

ai bj = (
i

ai ) (

bj ) = sdiv(a) sdiv(b)

De aici rezult c: a a
e e e e e e sdiv(f1 1 f2 2 ... fk k ) = sdiv(f1 1 ) sdiv(f2 2 ) ... sdiv(fk k )

i mai departe rezult relatia dat initial! s a a

8.5

Partitia numerelor

8.5.1

Partitia lui n exact k termeni n

Fie P (n, k) numrul modalitilor de a descompune numrul natural n ca a at a sum de exact k termeni nenuli, considernd dou descompuneri ca ind distincte a a a dac difer prin cel putin un termen (deci, fr a ine cont de ordinea termenilor). a a aa t Atunci P (n, k) = P (n k, 1) + P (n k, 2) + ... + P (n k, k) unde P (i, 1) = P (i, i) = 1 pentru ()i 1 i s n = a1 + a2 + ... + ak ; a1 a2 ... ak 1. Demonstratie: Ultimul termen ak poate 1 sau 2. Pentru ak = 1 avem P (n 1, k 1) posibiliti de alegere a factorilor a1 , a2 , at ..., ak1 astfel at n 1 = a1 + a2 + ... + ak1 i a1 a2 ... ak1 1 nc s Pentru ak 2 putem s scdem 1 din toti factorii descompunerii lui n i a a s a obtinem n k = a + a + ... + a unde a a ... a 1, deci numrul 2 1 2 1 k k descompunerilor lui n cu ak 2 este P (n k, k). Obtinem relatia P (n, k) = P (n 1, k 1) + P (n k, k)

8.6. PARTITIA MULTIMILOR care poate transformat astfel: a P (n, k) = P (n 1, k 1) + P (n k, k) P (n 1, k 1) = P (n 2, k 2) + P (n k, k 1)

123

P (n 2, k 2) = P (n 3, k 3) + P (n k, k 2) ... P (n k + 3, 3) = P (n k + 2, 2) + P (n k, 3) P (n k + 2, 2) = P (n k + 1, 1) + P (n k, 2)

Prin reducere i innd cont c P (n k + 1, 1) = 1 = P (n k, 1) obtinem s t a a relatia dat la a nceput.

8.5.2

Partitia lui n cel mult k termeni n

Fie A(n, k) numrul modalitilor de a descompune numrul natural n ca a at a sum de cel mult k termeni nenuli, considernd dou descompuneri ca ind disa a a tincte dac difer prin cel putin un termen (deci, fr a ine cont de ordinea a a aa t termenilor). Atunci A(n, k) = A(n, k 1) + A(n k, k) Evident A(i, j) = A(i, i) pentru orice j > i iar A(n, n) reprezint numrul a a partitiilor lui n. (1) Numrul partitiilor cel mult k factori este egal cu numrul partitiilor a n a exact k factori plus numrul partitiilor cel mult k 1 termeni. n a n (2) Dat ind o partitie a lui n exact k termeni nenuli, putem scdea 1 a n a din ecare termeni, obtinnd o partitie a lui n k cel mult k termeni nenuli. a n Astfel, exist o corespondent bijectiv a a a ntre partitiile lui n exact k termeni i n s partitiile lui n k cel mult k factori. n

8.5.3

Partitii multiplicative

Fie A(n, k) numrul modalitilor de a descompune numrul natural n ca a at a sum de cel mult k termeni nenuli, considernd dou descompuneri ca ind disa a a tincte dac difer prin cel putin un termen (deci, fr a ine cont de ordinea a a aa t termenilor).

8.6

Partitia multimilor

124

CAPITOLUL 8. ALGORITMI COMBINATORIALI

Fie S(n, k) numrul modalitilor de a partitiona o multime A cu n elemente a at k submultimi nevide, considernd dou partitii ca ind distincte dac difer n a a a a prin cel putin o submultime (deci, fr a ine cont de ordinea submultimilor). aa t Atunci S(n, k) = S(n 1, k 1) + k S(n 1, k) unde S(i, 1) = S(i, i) = 1 pentru ()i 1 i s A = A1 A2 ... Ak ; Demonstratie: Fie A = {a1 , a2 , ..., an } o multime cu n elemente i s A1 , A2 , ..., Ak o partitie oarecare. Elementul an poate (1) ntr-o submultime cu un singur element (chiar el!), sau (2) ntr-o submultime cu cel putin 2 elemente (printre care se gete i el!). as s s Numrul partitiilor de tipul (1) este S(n 1, k 1) (fr elementul an rmn a aa a a n 1 elemente i k 1 submultimi partitie). s n Numrul partitiilor de tipul (2) este kS(n1, k) (eliminnd elementul an din a a submultimea care se a, acea submultime rmne nevid!). Se pare c nu este n a a a a a prea clar (sau evident!) de ce apare totui factorul k expresia k S(n 1, k)! S s n a privim putin altfel lucrurile: considerm toate partitiile multimii {a1 , a2 , ..., an1 } a care au k submultimi; numrul acestora este S(n 1, k); introducerea elementului a an aceste partitii se poate face oricare din cele k submultimi; deci kS(n1, k) n n reprezint numrul partitiilor din cazul (2). a a Ai Aj = pentru i = j.

8.7

Probleme rezolvate

1. S se determine numrul arborilor binari cu n vrfuri. a a a Rezolvare: Fie b(n) numrul arborilor binari cu n vrfuri. Prin conventie a a b0 = 1. Prin desene b1 = 1, b2 = 2, b3 = 5, i: dac xm rdcina arborelui, s a a a a ne mai rmn n 1 vrfuri care pot aprea subarborele stng sau drept; dac a a a a n a a subarborele stng sunt k vrfuri, subarborele drept trebuie s e n 1 k n a a n a

8.7. PROBLEME REZOLVATE

125

vrfuri; cu aceti subarbori se pot forma total bk bn1k arbori; adunnd aceste a s n a valori pentru k = 0, 1, ..., n 1 vom obtine valoarea lui bn . Deci, pentru n 1
n1

bn = b0 bn1 + b1 bn2 + ... + bn1 b0 =


k=0

bk bn1k

1 Cn n + 1 2n 2. Care este numrul permutrilor a n obiecte cu p puncte xe? a a s Rezolvare: Deoarece cele p puncte xe pot alese Cn moduri, i cele n p n p puncte rmase nu mai sunt puncte xe pentru permutare, rezult c numrul a a a a permutrilor cu p puncte xe este egal cu a bn =
p Cn D(n p)

Se obtine

deoarece pentru ecare alegere a celor p puncte xe exist D(n p) permutri ale a a obiectelor rmase, fr puncte xe. a aa 3. Fie X = {1, 2, ..., n}. Dac E(n) reprezint numrul permutrilor pare1 a a a a ale multimii X fr puncte xe, atunci aa E(n) = 1 D(n) + (1)n1 (n 1) . 2

Rezolvare: Fie Ai multimea permutrilor pare p astfel at p(i) = i. Deoarece a nc a a numrul permutrilor pare este 1 n!, rezult c a a 2 E(n) = = Tinnd cont c a a |Ai1 Ai2 ... Aik | = (n k)! rezult formula cerut. a a 1 n! |A1 ... An | = 2 1 1 2 n n! Cn (n 1)! + Cn (n 2)! + ... + (1)n Cn . 2

1 Dac i < j, i permutare i apare dup j, spunem c avem o inversiune. O permutare este a s n a a par dac are un numr par de inversiuni a a a

126

CAPITOLUL 8. ALGORITMI COMBINATORIALI

Capitolul 9

Algoritmi de cutare a
9.1 Problema cutrii a a

Problema cutrii este: se d un vector a cu n elemente i o valoare x de a a a s acelai tip cu elementele din a. S se determine p astfel at x = a[p] sau 1 dac s a nc a nu exist un element cu valoarea v a. a n O tabel cu cmpuri care nu sunt de acelai tip se poate organiza cu ajutorul a a s vectorilor dac numrul de coloane este sucient de mic. De exemplu, o tabel cu a a a trei informatii: numr curent, nume, telefon poate organizat cu ajutorul a doi a a vectori (nume i telefon) iar numrul curent este indicele din vector. s a

9.2

Cutarea secvential a a

static int cauta (String x) { for (int i = 0; i < N; ++i) if (x.equals(nume[i])) return telefon[i]; return 1; } se poate scrie i sub forma: s static int cauta (String x) { int i = 0; while (i < N && !x.equals(nume[i])) ++i; if (i < N) return telefon[i]; else return 1; } 127

128

CAPITOLUL 9. ALGORITMI DE CAUTARE O alt posibilitate este de a pune o santinel captul tabelei. a a n a

static int cauta (String x) { int i = 0; nume[N] = x; telefon[N] = 1; while (! x.equals(nume[i])) ++i; return tel[i]; } Scrierea procedurii de cutare a ntr-un tabel de nume este acest caz mai n ecient, pentru c nu se face dect un singur test plus aici ( loc de dou teste). a a a n n a Cutarea secvential se mai numete i cutare linear, pentru c se execut N/2 a a s s a a a a operatii medie, i N operatii cazul cel mai defavorabil. n s n Intr-un tablou cu 10.000 elemente, cutarea execut 5.000 operatii medie, ceea ce a a n nseamn un a consum de timp de aproximativ 0.005 ms (milisecunde). Iat un program complet care utilizeaz cutarea linear a a a a ntr-o tabel. a class Tabela { final static int N = 6; static String nume[] = new String[N+1]; static int telefon[] = new int[N+1]; static void nume[0] = nume[1] = nume[2] = nume[3] = nume[4] = nume[5] = } initializare() { "Paul"; telefon[0] "Robert"; telefon[1] "Laura"; telefon[2] "Ana"; telefon[3] "Tudor"; telefon[4] "Marius"; telefon[5]

= = = = = =

2811; 4501; 2701; 2702; 2805; 2806;

static int cauta(String x) { for (int i = 0; i < N; ++i) if (x.equals(nume[i])) return tel[i]; return 1; } public static void main (String args[]) { initializare(); if (args.length == 1) System.out.println(cauta(args[0])); } }

9.3. CAUTARE BINARA

129

Cel mai simplu algoritm care rezolv aceast problem este cutarea liniar, a a a a a care testeaz elementele vectorului unul dupa altul, a ncepnd cu primul. a p=-1; for(i=0;i<n;i++) if(x==a[i]) { p=i; break; } S analizm complexitatea acestui algoritm pe cazul cel mai defavorabil, acela a a care v nu se gsete a. acest caz se va face o parcurgere complet a lui a. n a s n In a Considernd ca operatie elementar testul v == a[i], numrul de astfel de operatii a a a elementare efectuate va egal cu numarul de elemente al lui a, adic n. Deci a complexitatea algoritmului pentru cazul cel mai defavorabil este O(n).

9.3

Cutare binar a a

O alt tehnic de cutare tabele este cutarea binar. Presupunem c a a a n a a a tabela de nume este sortat ordine alfabetic (cum este cartea de telefoane). a n a loc de a cuta secvential, se compar cheia de cutare cu numele care se af In a a a a la mijlocul tabelei de nume. Dac acesta este acelai, se returneaz numrul de a s a a telefon din mijloc, altfel se re ncepe cutarea prima jumtate (sau a doua) a n a n dac numele cutat este mai mic (respectiv, mai mare) dect numele din mijlocul a a a tabelei. static int cautareBinara(String x) { int i, s, d, cmp; s = 0; d = N1; do { i = (s + d) / 2; cmp = x.compareTo(nume[i]); if (cmp == 0) return telefon[i]; if (cmp < 0) d = i - 1; else s = i + 1; } while (s <= d); return -1; } Numrul CN de comparatii efectuate pentru o tabel de dimensiune N este a a CN = 1 + CN/2 , unde C0 = 1. Deci CN log2 N . De acum naninte, log2 N va scris mai simplu log N . Dac tabela are 10.000 elemente, atunci CN 14. Acesta reprezint un a a beneciu considerabil raport cu 5.000 de operatii necesare la cutarea linear. n a a

130

CAPITOLUL 9. ALGORITMI DE CAUTARE

Desigur cutarea secvential este foarte uor de programat, i ea se poate a a s s folosi pentru tabele de dimensiuni mici. Pentru tabele de dimensiuni mari, cutarea a binar este mult mai interesant. a a Se poate obtine un timp sub-logaritmic dac se cunoate distributia obiectelor. a s De exemplu, cartea de telefon, sau dictionar, se tie apriori c un nume care n n s a ncepe cu litera V se a ctre sfrit. Presupunnd distributia uniform, putem a a as a a face o regul de trei simpl pentru gsirea indicelui elementului de referinta a a a pentru comparare, loc s alegem mijlocul, i s urmm restul algoritmului de n a s a a cutare binar. Aceast metod se numete cutare prin interpolare. Timpul de a a a a s a cutare este O(log log N ), ceea ce a nseamn cam 4 operatii pentru o tabel de a a 10.000 elemente i 5 operatii pentru 109 elemente tabel. Este spectaculos! s n a O implementare iterativ a cutrii binare a a a ntr-un vector ordonat (cresctor a sau descresctor) este: a int st, dr, m; boolean gasit; st=0; dr=n-1; gasit=false; while((st < dr) && !gasit) { m=(st+dr)/2; if(am]==x) gasit=true; else if(a[m] > x) dr=m-1; else st=m+1; } if(gasit) p=m; else p=-1; Algoritmul poate descris, foarte elegant, recursiv.

9.4

Inserare tabel n a

La cutarea secvential sau binar nu am fost preocupati de inserarea a a a n tabel a unui nou element. Aceasta este destul de rar cazul crtii de telefon a a n a dar alte aplicatii, de exemplu lista utilizatorilor unui sistem informatic, este n frecvent utilizat. a Vom vedea cum se realizeaz inserarea unui element a ntr-o tabel, cazul a n cutrii secventiale i binare. a a s Pentru cazul secvential este sucient s adugm la captul tabelei noul a a a a element, dac are loc. Dac nu are loc, se apeleaz o procedur error care aeaz a a a a s a un mesaj de eroare.

9.5. DISPERSIA void inserare(String x, int val) { ++n; if (n >= N) error ("Depasire tabela"); numem[n] = x; telefon[n] = val; }

131

Inserarea se face deci timp constant, de ordinul O(1). n cazul cutrii binare, trebuie mentinut tabelul ordonat. Pentru inserarea In a a unui element nou tabel, trebuie gsit pozitia sa printr-o cutare binar (sau n a a a a a secvential), apoi se deplaseaz toate elementele din spatele ei spre dreapta cu o a a pozitie pentru a putea insera noul element locul corect. Aceasta necesit log n+n n a operetii. Inserarea ntr-o tabel ordonat cu n elemente este deci de ordinul O(n). a a

9.5

Dispersia

O alt metod de cutare tabele este dispersia. Se utilizeaz o functie h a a a n a de grupare a cheilor (adesea iruri de caractere) s ntr-un interval de numere ntregi. Pentru o cheie x, h(x) este locul unde se af x tabel. Totul este ordine dac a n a n a h este o aplicatie injectiv. De asemenea, este bine ca functia aleas s permit a a a a evaluarea cu un numr mic de operatii. O astfel de functie este a h(x) = x1 B m1 + x2 B m2 + ... + xm1 B + xm mod N.

De obicei se ia B = 128 sau B = 256 i se presupune c dimensiunea tabelei s a N este un numr prim. De ce? Pentru c a a nmultirea puterilor lui 2 se poate face foarte uor prin operatii de deplasare pe biti, numerele ind reprezentate binar. s n general, la mainile (calculatoarele) moderne, aceste operatii sunt net mai rapide In s dect a nmultirea numerelor oarecare. Ct despre alegerea lui N , aceasta se face a pentru a evita orice interferent a ntre ntre nmultirile prin B i artirile prin s mp N. Intr-adevr, dac de exemplu B = N = 256, atunci h(x) = x(m) este functia h a a care nu depinde dect de ultimul caracter al lui x. Scopul este de a avea o functie h, a de dispersie, simplu de calculat i avnd o bun distributie pe intervalul [0, N 1]. s a a Calculul functiei h se face prin functia h(x,m), unde m este lungimea irului x, s static int h(String x){ int r = 0; for (int i = 0; i < x.length(); ++i) r = ((r * B) + x.charAt(i)) % N; return r; }

132

CAPITOLUL 9. ALGORITMI DE CAUTARE

Deci functia h d pentru toate cheile x o intrare posibil tabel. Se poate a a n a apoi verica dac x = nume[h(x)]. Dac da, cutarea este terminat. Dac nu, a a a a a nseamn c tabela contine o alt cheie astfel at h(x ) = h(x). Se spune atunci a a a nc c exist o coliziune, i tabela trebuie s gestioneze coliziunile. O metod simpl a a s a a a este de a lista coliziunile ntr-un tabel col paralel cu tabelul nume. Tabela de coliziuni d o alt intrare i tabela de nume unde se poate gsi cheia cutat. a a n a a a Dac nu se gsete valoarea x aceast nou intrare i, se continu cu intrarea i a a s n a a a dat de i = col[i]. Se continu astfel ct timp col[i] = 1. a a a static int cauta(String x) { for (int i = h(x); i != 1; i = col[i]) if (x.equals(nume[i])) return telefon[i]; return 1; } Astfel, procedura de cutare consum un timp mai mare sau egal ca lungimea a a medie a claselor de echivalent denite pe tabel de valorile h(x), adic de lungimea a a a medie a listei de coliziuni. Dac functia de dispersie este perfect uniform, nu apar a a coliziuni i determin toate elementele printr-o singur comparatie. Acest caz este s a a foarte putin probabil. Dac numrul mediu de elemente avnd aceeai valoare de a a a s dispersie este k = N/M , unde M este numrul claselor de echivalent denite a a de h, cutarea ia un timp de N/M . Dispersia nu face dect s reduc printr-un a a a a factor constant timpul cutrii secventiale. Interesul fat de dispersie este pentru a a a c adesea este foarte ecace, i uor de programat. a s s

Capitolul 10

Algoritmi elementari de sortare


Tablourile sunt structuri de baz informatic. Un tablou reprezint, a n a a n functie de dimensiunile sale, un vector sau o matrice cu elemente de acelai tip. s Un tablou permite accesul direct la un element, i noi vom utiliza intens aceast s a proprietate algoritmii de sortare pe care vom considera. n i

10.1

Introducere

Ce este sortarea? Presupunem c se d un ir de N numere a a s ntregi ai , i s se dorete aranjarea lor ordine cresctoare, sens larg. De exemplu, pentru s n a n N = 10, irul s 18, 3, 10, 25, 9, 3, 11, 13, 23, 8 va deveni 3, 3, 8, 9, 10, 11, 13, 18, 23, 25. Aceast problem este clasic informatic i a fost studiat detaliu, de a a a n as a n exemplu, [23]. proctic se alnete adesea aceast problem. De exemplu, n In a nt s a a stabilirea clasamentului ntre studenti, construirea unui dictionar, etc. Trebuie fcut o distintie a a ntre sortarea unui numr mare de elemente i a unui numr mic a s a de elemente. acest al doilea caz, metoda de sortare este putin important. In a Un algoritm amuzant const a vedea dac setul de crti de joc din mn a n a a a a este deja ordonat. Dac nu este, se d cu ele de pmnt i se re a a a a s ncepe. Dup a un anumit timp, exist riscul de a avea crtile ordonate. Desigur, poate s nu se a a a termine niciodat, pentru noi, aceast sortare. a a 133

134

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE

O alt tehnic (discutnd serios de data aceasta), frecvent utilizat la un joc a a a a de crti, const a vedea dac exist o transpozitie de efectuat. Dac exist, se a a n a a a a face interschimbarea crtilor de joc i se caut o alt transpozitie. Procedeul se a s a a repet pn cnd nu mai exist transpozitii. Aceast metod functioneaz foarte a a a a a a a a bine pentru o bun distributie a crtilor. a a Este uor de intuit c numrul obiectelor de sortat este important. Nu este s a a cazul s se caute o metod sosticat pentru a sorta 10 elemente. Cu alte cuvinte, a a a nu se trage cu tunul ntr-o musc! a Exemplele tratate ntr-un curs sunt ntotdeauna de dimensiuni limitate, din considerente pedagogice nu este posibil de a prezenta o sortare a mii de elemente.

10.2

Sortare prin selectie

cele ce urmeaz, presupunem c trebuie s sortm un numr de In a a a a a ntregi care se gsesc a ntr-un tablou (vector) a. Algoritmul de sortare cel mai simplu este prin selectie. El const gsirea pozitiei tablou a elementului cu valoarea cea mai a n a n mic, adic a a ntregul m pentru care ai am pentru orice i. Odat gsit aceast a a a a pozitie m, se schimb a ntre ele elementele a1 i am . s Apoi se re ncepe aceast operatie pentru irul (a2 , a3 , ..., aN ), tot la fel, a s cutndu-se elementul cel mai mic din acest ir i interschimbndu-l cu a2 . Si a a s s a aa mai departe pn la un moment dat cnd irul va contine un singur element. s a a a s Cutarea celui mai mic element a ntr-un tablou este un prim exercitiu de programare. Determinarea pozitiei acestui element este foarte simpl, ea se poate a efectua cu ajutorul instructiunilor urmtoare: a m = 0; for (int j = 1; j < N; ++j) if (a[j] < a[m]) m = i; Schimbarea ntre ele a celor dou elemente necesit o variabil temporar t a a a a i se efectueaz prin: s a t = a[m]; a[m] = a[1]; a[1] = t; Acest set de operatii trebuie reluat prin nlocuirea lui 1 cu 2, apoi cu 3 i s aa mai departe pn la N . Aceasta se realizeaz prin introducerea unei variabile s a a a i care ia toate valorile ntre 1 i N . s Toate acestea sunt artate programul care urmeaz. a n a De aceast dat vom prezenta programul complet. a a Procedurile de achizitionare a datelor i de returnare a rezultatelor sunt s deasemenea prezentate. Pentru alti algoritmi, ne vom limita la descrierea efectiv a sortrii. a a

10.2. SORTARE PRIN SELECTIE class SortSelectie { final static int N = 10; static int[] a = new int[N]; static void initializare() { int i; for (i = 0; i < N; ++i) a[i] = (int) (Math.random() * 128); } static void afisare() { int i; for (i = 0; i < N; ++i) System.out.print (a[i] + " "); System.out.println(); } static void sortSelectie() { int min, t; int i, j; for (i = 0; i < N 1; ++i) { min = i; for (j = i+1; j < N; ++j) if (a[j] < a[min]) min = j; t = a[min]; a[min] = a[i]; a[i] = t; } } public static void main (String args[]) { initializare(); afisare(); sortSelectie(); afisare(); } }

135

136

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE Un exemplu de executii este: 1 2 3 4 5 6 7 12 4 30 3 4 23 14 m 1 2 3 4 5 6 7 12 4 30 7 4 23 14 i m 1 2 3 4 5 6 7 4 12 30 7 4 23 14 i m 1 2 3 4 5 6 7 4 4 30 7 12 23 14 i m 1 2 3 4 5 6 7 4 4 7 30 12 23 14 i m 1 2 3 4 5 6 7 4 4 7 12 30 23 14 i m 1 2 3 4 5 6 7 4 4 7 12 14 23 30 im 1 2 3 4 5 6 7 4 4 7 12 14 23 30

0 7 i 0 3

0 3 i 0 3

1 12

2 4

3 30

0 3

0 3

1 4 i 1 4

0 3

0 3

1 4

2 12 m 2 4 i 2 4

4 7 m 3 4 30 7

5 4

6 23

7 14

5 4

6 23

7 14

3 30

0 3

0 3

1 4

2 4

3 7 i 3 7

0 3

0 3

1 4

2 4

3 7

0 3

0 3

1 4

2 4

3 7

0 3

0 3

1 4

2 4

3 7

5 6 12 23 m 4 5 6 30 12 23 m 4 5 6 12 30 23 i m 4 5 6 12 14 23 i 4 5 6 12 14 23 im 4 5 6 12 14 23

4 7

7 14

7 14

7 14

7 30 m 7 30

7 30

Este uor de calculat numrul de operatii necesare. La ecare iteratie, se s a pleac de la elementul ai i se compar succesiv cu ai+1 , ai+2 , ..., aN . Se fac deci a s a N i comparatii. Se ncepe cu i = 1 i se termin cu i = N 1. Deci, se fac s a (N 1) + (N 2) + ... + 2 + 1 = N (N 1)/2 comparatii i N 1 interschimbri. s a Sortarea prin selectie execut un numr de comparatii de ordinul N 2 . a a O variant a sortrii prin selectie este metoda bulelor. Principiul ei este de a a a parcurge irul (a1 , a2 , ..., aN ) inversnd toate perechile de elemente consecutive s a (aj 1, aj ) neordonate. Dup prima parcurgere, elementul maxim se va aa pe a pozitia N . Se re ncepe cu prexul (a, a2 , ..., aN 1 ), ..., (a1 , a2 ). Procedura corespunztoare utilizeaz un indice i care marcheaz sfritul a a a as prexului sortare, i un indice j care permite deplasarea ctre marginea i. n s a

10.2. SORTARE PRIN SELECTIE

137

Se poate calcula de asemenea foarte uor numrul de operatii i se obtine un s a s numr de ordinul O(N 2 ) comparatii i, eventual interschimbri (dac, de exemplu, a s a a tabloul este initial ordine descresctoare). n a static void sortBule() { int t; for (int i = N1; i >= 0; i) for (int j = 1; j <= i; ++j) if (a[j1] > a[j]) { t = a[j1]; a[j1] = a[j]; a[j] = t; } } 0 7 1 12 2 4 j 2 12 3 30 4 3 5 4 6 23 7 14 i 7 14 i 7 14 i 7 14 i 7 14 ji a ajuns pe 7 30 0 7 1 4 2 3 4 5 12 30 3 4 j 2 3 4 5 12 3 30 4 j 2 3 4 5 12 3 4 30 j 2 3 4 5 12 3 4 23 7 14 i 6 7 23 14 i 6 7 23 14 i 6 7 30 14 j i 6 7 14 30 ji vector). 6 7 14 30 i 6 7 14 30 i 6 23

0 7

1 4

3 30

0 7

1 4

2 12

3 3

4 3 j 4 30

5 4

6 23

0 7

1 4

0 7

1 4

2 12

3 3

4 4

5 4 j 5 30

6 23

0 7

1 4

0 7

1 4

2 12

3 3

4 4

5 23

6 23 j 6 30

0 7

1 4

0 7

1 4

2 12

3 3

4 4

5 23

0 7

0 4

Dup prima parcurgere 30 a 1 2 3 4 5 6 4 12 3 4 23 14 j i 1 2 3 4 5 6 7 7 12 3 4 23 14 30 j i

locul su (ultimul loc a n 0 1 2 3 4 5 4 7 12 3 4 23 j 0 1 2 3 4 5 4 7 3 12 4 23 j

138 0 4 1 7 2 3

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE 3 12 4 4 j 4 12 5 23

6 7 0 1 2 3 4 5 6 7 14 30 4 7 3 4 12 23 14 30 i j i 0 1 2 3 5 6 7 0 1 2 3 4 5 6 7 4 7 3 4 23 14 30 4 7 3 4 12 14 23 30 ji ji Dup a doua parcurgere 23 a ajuns pe locul su (penultimul loc vector). a a n 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 4 7 3 4 12 14 23 30 4 3 7 4 12 14 23 30 j i j i 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 4 3 7 4 12 14 23 30 4 3 4 7 12 14 23 30 j i j i Dup a treia parcurgere 14 a ajuns pe locul su (de fapt era deja pe locul a a su de la a nceputul acestei parcurgeri; s-au mai aranjat totui cteva elemente!). s a 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 4 3 4 7 12 14 23 30 3 4 4 7 12 14 23 30 j i j i Dup a patra parcurgere 12 a ajuns pe locul su (de fapt era deja pe locul su a a a de la nceputul acestei parcurgeri; oricum, la aceast parcurgere s-au mai aranjat a cteva elemente!). a La urmtoarea parcurgere nu se efectueaz nici o interschimbare de elemente. a a Vectorul este deja sortat, aa c urmtoarele parcurgeri se fac, de asemenea, fr s a a aa s se execute nici o interschimbare de elemente. O idee bun este introducerea unei a a variabile care s contorizeze numrul de interschimbri din cadrul unei parcurgeri. a a a Dac nu s-a efectuat nici o interschimbare atunci vectorul este deja sortat aa c a s a se poate ntrerupe executia urmtoarelor parcurgeri. Programul modicat este: a static void sortBule() { int t, k; for (int i = N1; i >= 0; i) { k=0; for (int j = 1; j <= i; ++j) if (a[j1] > a[j]) {t = a[j1]; a[j1] = a[j]; a[j] = t; k++;} if(k==0) break; } }

10.3. SORTARE PRIN INSERTIE

139

10.3

Sortare prin insertie

O metod complet diferit este sortarea prin insertie. Aceasta este metoda a a utilizat pentru sortarea unui pachet de crti de joc. Se ia prima carte, apoi a a a doua i se aranjeaz ordine cresctoare. Se ia a treia carte i se plaseaz pe s a n a s a pozitia corespunztoare fat de primele dou crti, i aa mai departe. Pentru a a a a s s cazul general, s presupunem c primele i 1 crti sun deja sortate cresctor. Se a a a a ia a i-a carte i se plaseaz pe pozitia corespunztoare relativ la primele i 1 crti s a a a deja sortate. Se continu pn la i = N . a a a

10.3.1

Insertie direct a

Dac determinarea pozitiei corespunztoare, subirul format de primele a a n s i 1 elemente, se face secvential, atunci sortarea se numete sortare prin insertie s direct. Procedura care realizeaz aceastortare este: a a s static void sortInsertie() { int j, v; for (int i = 1; i < N; ++i) { v = a[i]; j = i; while (j > 0 && a[j-1] > v) { a[j] = a[j-1]; j; } a[j] = v; } } Pentru plasarea elementului ai din vectorul nesortat (elementele de pe pozitiile din stnga ind deja sortate) se parcurge vectorul spre stnga plecnd de la pozitia a a a i 1. Elementele vizitate se deplaseaz cu o pozitie spre dreapta pentru a permite a plasarea elementului ai (a crui valoare a fost salvat n variabila temporar v) pe a a a pozitia corespunztoare. Procedura contine o mic eroare, dac ai este cel mai mic a a a element din tablou, cci va iei din tablou spre stnga. Se poate remedia aceast a s a a situatie plasnd un element a0 de valoare max int. Se spune c a fost plasat a a a o santinel la stnga tabloului a. Aceasta nu este a a ntotdeauna posibil, i atunci s trebuie adugat un test asupra indicelui j bucla while. Un exemplu numeric a n este cel care urmeaz: a Inserarea lui 12 nu necesit deplasri de elemente. a a 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 7 12 4 30 3 4 23 14 4 7 12 30 3 4 23 14 j i j i

140

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE Inserarea lui 30 nu necesit deplasri de elemente. a a

4 5 6 7 30 4 23 14 i 1 2 3 5 6 7 1 2 3 4 5 6 7 4 7 12 4 23 14 4 4 7 12 30 23 14 j i j i 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 3 4 4 7 12 30 23 14 3 4 4 7 12 23 30 14 j i j i 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 3 4 4 7 12 23 30 14 3 4 4 7 12 14 23 30 j i j i Numrul de comparatii pentru inserarea unui element secventa deja sortat a n a este egal cu numrul de inversiuni plus 1. a Fie ci numrul de comparatii. Atunci a ci = 1 + card{aj |aj > ai , j < i} Pentru o permutare corespunztoare unui ir de sortri, unde numrul de a s a a inversiuni este inv(), numrul total de comparatii pentru sortarea prin insertie a este
N

0 4 j 0 3

1 7

2 12

3 30

4 3 i 4 30

5 4

6 23

7 14

0 3 j 0 3

1 4

2 7

3 12

C =
i=2

ci = N 1 + inv().

Deci, numrul mediu de comparatii este a CN = 1 N! C = N 1 + N (N 1) N (N + 3) = 1 4 4

SN

unde Sn reprezint grupul permutrilor de n elemente. a a Dei ordinul de cretere este tot N 2 , aceast metod de sortare este mai s s a a ecient dect sortarea prin selectie. Cea mai bun metod de sortare necesit a a a a a n log2 n comparatii. plus, ea are o proprietate foarte bun: numrul de operatii depinde puternic In a a de ordinea initial din tablou. cazul care tabloul este aproape ordonat, i drept a In n s urmare exist putine inversiuni i sunt necesare putine operatii, spre deosebire de a s primele dou metode de sortare. a Sortarea prin inserare este deci o metod de sortare bun dac tabloul de a a a sortat are ansa de a aproape ordonat. s

10.3. SORTARE PRIN INSERTIE

141

10.3.2

Insertie binar a

Metoda anterioar se poate a mbunati dac inem cont de faptul c secventa at at a care se face inserarea este deja ordonat, iar loc s se fac insertia direct n a n a a a n aceast secvent, cutarea pozitiei pe care se face inserarea se face prin cutare a a a a binar. Un program complet este: a import java.io.*; class SortInsBinApl { static int[] a={3,8,5,4,9,1,6,4}; static void afiseaza() { int j; for(j=0; j<a.length; j++) System.out.print(a[j] + " "); System.out.println(); } static int pozitiaCautBin(int p, int u, int x) { int i=u+1; while (p <= u) { i=(p+u)/2; if (x>a[i]) p=i+1; else if (x<a[i]) u=i-1; else return i; } return p; } static void deplasare(int k, int i) { if (i != k) { int x=a[k]; for(int j=k; j>=i+1; j--) a[j]=a[j-1]; a[i]=x; } }

142

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE

static void sorteaza() { int N=a.length,i; for(int k=1;k<=N-1;k++) { i=pozitiaCautBin(0,k-1,a[k]); deplasare(k,i); } } public static void main(String[] args) { afiseaza(); sorteaza(); afiseaza(); } }

10.4

Sortare prin interschimbare

Aceast metod folosete interschimbarea ca i caracteristic principal a a a s s a a metodei de sortare. cadrul acestei metode se compar i se interschimb perechi In as a adiacente de chei pan cnd toate elementele sunt sortate. a a static void interschimbare(int a[]) { int x, i, n=a.length; boolean schimb = true; while (!schimb) { schimb=false; for(i=0; i<n-1; i++) if (a[i]>a[i+1]) { x = a[i]; a[i] = a[i+1]; a[i+1] = x; schimb=true; } } }

10.5. SORTARE PRIN MICSORAREA INCREMENTULUI - SHELL

143

10.5

Sortare prin micorarea incrementului - shell s

Prezentm metoda pe urmtorul ir: a a s 44, 55, 12, 42, 94, 18, 6, 67. Se grupeaz elementele aate la 4 pozitii distant, i se sorteaz separat. a a s a Acest proces este numit numit 4 sort. Rezult irul: as 44, 18, 06, 42, 94, 55, 12, 67 Apoi se sorteaz elementele aate la 2 pozitii distant. Rezult: a a a 6, 18, 12, 42, 44, 55, 94, 97 Apoi se sorteaz sirul rezultat a ntr-o singur trecere: 1 - sort a 6, 12, 18, 42, 44, 55, 94, 97 Se observ urmtoarele: a a un proces de sortare i sort combin 2 grupuri sortate procesul 2i sort a n anterior exemplul anterior s-a folosit secventa de incrementi 4, 2, 1 dar orice n secvent, cu conditia ca cea mai n sortare s e 1 sort. cazul cel a a a In mai defavorabil, ultimul pas se face totul, dar cu multe comparatii i n s interschimbri. a dac cei t incrementi sunt h1 , h2 , .. ht , ht = 1 i hi+1 < hi , ecare hi -sort a s se poate implementa ca i o sortate prin insertie direct. s a void shell(int a[], int n) { static int h[] = {9, 5, 3, 1}; int m, x, i, j, k, n=a.length; for (m=0; m<4; m++) { k = h[m]; /* sortare elemente aflate la distanta k in tablul a[] */ for (i=k; i<n; i++) { x = a[i]; for (j = i-k; (j>=0) && (a[j]>x); j-=k) a[j+k] = a[j]; a[j+k] = x; } } }

144

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE

Capitolul 11

Liste
Scopul listelor este de a genera un ansamblu nit de elemente al crui numr a a nu este xat apriori. Elementele acestui ansamblu pot numere ntregi sau reale, iruri de caractere, obiecte informatice complexe, etc. Nu suntem acum interesati s de elementele acestui ansamblu ci de operatiile care se efectueaz asupra acestuia, a independent de natura elementelor sale. Listele sunt obiecte dinamice, sensul c numrul elementelor variaz n a a a n cursul executiei programului, prin adugri sau tergeri de elemente pe parcursul a a s prelucrrii. Mai precis, operatiile permise sunt: a testarea dac ansamblul este vid a adugarea de elemente a vericarea dac un element este ansamblu a n tergerea unui element s

11.1

Liste liniare

Fiecare element al listei este continut ntr-o celul care contine plus adresa a n elementului urmtor, numit i pointer. Java permite realizarea listelor cu ajutorul a s claselor i obiectelor: celulele sunt obiecte (adic instante ale unei clase) care s a n un cmp contine o referint ctre celula urmtoare. Referinta ctre prima celul a a a a a a este continut a ntr-o variabil. a class Lista { int continut; Lista urmator; 145

146 Lista (int x, Lista a) { continut = x; urmator = a; } }

CAPITOLUL 11. LISTE

Instructiunea new Lista(x,a) construiete o nou celul cu cmpurile x i s a a a s a. Func tia Lista(x,a) este un constructor al clasei Lista (un constructor este o functie nestatic care se distinge prin tipul rezultatului su care este cel al clasei a a curente, i prin absenta numelui de identicare). Obiectul null apartine tuturor s claselor i reprezint cazul listelor marcajul de sfrit de list. De asemenea s a n as a new Lista(2, new Lista(7, new Lista(11,null))) reprezint lista 2, 7, 11. a static boolean esteVida (Lista a) { return a == null; } Procedura adauga insereaz un element capul listei. Aceast modalitate a n a de a introduce elemente capul listei este util pentru ca numrul de operatii n a a necesare adugrii de elemente s e independent de mrimea listei; este sucient a a a a a modicarea valorii capului listei, ceea ce se face simplu prin: static Lista adaug (int x, Lista a) { return new Liste (x, a); // capul vechi se va regasi dupa x }
cap cap

e4

e3

e2

e1

e4

e3

e2

e1

Figura 11.1: Adugarea unui element list a n a Functia cauta, care veric dac elementul x este lista a, efectueaz o parcurgere a a n a a listei. Variabila a este modicat iterativ prin a=a.urmator pentru a parcurge a elementele listei pn se gsete x sau pn se gsete sfritul listei (a = null). a a a s a a a s as static boolean cauta (int x, Lista a) { while (a != null) { if (a.continut == x) return true; a = a.urmator; } return false; }

11.1. LISTE LINIARE Functia cauta poate scris mod recursiv: a n static boolean cauta (int x, Lista a) { if (a == null) return false; else if (a.continut == x) return true; else return cauta(x, a.urmator); } sau sub forma: static boolean cauta (int x, Lista a) { if (a == null) return false; else return (a.continut == x) || cauta(x, a.urmator); } sau sub forma: static boolean cauta (int x, Lista a) { return a != null && (a.continut == x || cauta(x, a.urmator)); }

147

Aceast sciere recursiv este sistematic pentru functiile care opereaz asupra a a a a listelor. Tipul list veric ecuatia urmtoare: a a a Lista = {Lista vida} Element Lista unde este sau exclusiv iar este produsul cartezian. Toate procedurile sau functiile care opereaz asupra listelor se pot scrie recursiv aceast manier. De a n a a exemplu, lungimea listei se poate determina prin: static int lungime(Lista a) { if (a == null) return 0; else return 1 + longime(a.urmator); } care este mai elegant dect scrierea iterativ: a a a static int lungime(Lista a) { int lg = 0; while (a != null) { ++lg; a = a.urmator; } return lg; }

148

CAPITOLUL 11. LISTE

Alegerea ntre maniera recursiv sau iterativ este o problem subiectiv a a a a n general. Pe de alt parte se spune c scrierea iterativ este mai ecient pentru c a a a a a folosete mai putin memorie. Gratie noilor tehnici de compilare, acest lucru este s a mai putin adevrat; ocuparea de memorie suplimentar, pentru calculatoarele de a a astzi, este o problem foarte putin critic. a a a Eliminarea unei celule care contine x se face modicnd valoarea cmpului a a urmator continut predecesorul lui x: succesorul predecesorului lui x devine n succesorul lui x. Un tratament particular trebuie fcut dac elementul care trebuie a a eliminat este primul element din list. Procedura recursiv de eliminare este foarte a a compact denitia sa: a n static Lista elimina (int x, Lista a) { if (a != null) if (a.continut == x) a = a.urmator; else a.urmator = elimina (x, a.urmator); return a; }

cap

cap

e4

e3

e2

e1

e4

e3

e2

e1

Figura 11.2: Eliminarea unui element din list a

O procedur iterativ solicit un plus de atentie. a a a static Lista elimina (int x, Lista a) { if (a != null) if (a.continut == x) a = a.urmator; else { Lista b = a ; while (b.urmator != null && b.urmator.continut != x) b = b.urmator; if (b.urmator != null) b.urmator = b.urmator.urmator; } return a; }

11.1. LISTE LINIARE

149

cadrul functiilor anterioare, care modic lista a, nu se dispune de dou In a a liste distincte, una care contine x i alta identic cu precedenta dar care nu mai s a contine x. Pentru a face acest lucru, trebuie recopiat o parte a listei a a ntr-o nou list, cum face programul urmtor, unde exist o utilizare putin important a a a a a de memorie suplimentar. Oricum, spatiul de memorie pierdut este recuperat de a culegtorul de spatiu de memorie GC (Garbage Colection) din Java, dac nu mai a a este folosit lista a. Cu tehnicile actuale ale noilor versiuni ale GC, recuperarea a se efectueaz foarte rapid. Aceasta este o diferent important fat de limbajele a a a a de programare precum Pascal sau C, unde trebuie s m preocupati de spatiul de a memorie pierdut, dac trebuie s- l reutilizm. a a a static Lista elimina (int x, Lista a) { if (a != null) return null; else if (a.continut == x) return a.urmator; else return new Lista (a.continut, elimina (x, a.urmator)); } Exist o tehnic, utilizat adesea, care permite evitarea unor teste pentru a a a eliminarea unui element dintr-o list i, general, pentru simplicarea programrii a s n a operatiilor asupra listelor. Aceasta const utilizarea unui fanion / santinel a n a care permite tratarea omogen a listelor indiferent dac sunt vide sau nu. a a In reprezentarea anterioar lista vid nu a avut aceeai structur ca listele nevide. Se a a s a utilizeaz o celul plasat la a a a nceputul listei, care nu are informatie semnicativ a cmpul continut; adresa adevratei prime celule se a cmpul urmator al n a a a n a acestei celule. Astfel obtinem o list pzit, avantajul ind c lista vid contine a a a a a numai garda i prin urmare un numr de programe, care fceau un caz special din s a a lista vid sau din primul element din list, devin mai simple. Aceast notiune este a a a un pic echivalent cu notiunea de santinel pentru tablouri. Se utilizeaz aceast a a a a tehnic proceduri asupra irurilor prea lungi. a n s De asemenea, se pot deni liste circulare cu gard / paznic care prima a n n celul cmpul urmator este ultima celul din list. a n a a a Pentru implementarea listelor se pot folosi perechi de tablouri : primul tablou, numit continut, contine elementele de informatii, iar al doilea, numit urmator, contine adresa elementului urmtor din tabloul continut. a Procedura adauga efectueaz numai 3 operatii elementare. Este deci foarte a ecient. Procedurile cauta i elimina sunt mai lungi pentru c trebuie s parcurg a s a a a ntreaga list. Se poate estima c numrul mediu de operatii este jumtate din a a a a lungimea listei. Cutarea binar efectueaz un numr logaritmic iar cutarea cu a a a a a tabele hash (de dispersie) este i mai rapid. s a Exemplu 1. Considerm construirea unei liste de numere prime mai mici a dect un numr natural n dat. Pentru construirea acestei liste vom a a ncepe, n

150

CAPITOLUL 11. LISTE

prima faz, prin adugarea numerelor de la 2 la n a a ncepnd cu n i terminnd cu a s a 2. Astfel numerele vor list ordine cresctoare. Vom utiliza metoda clasic a n a n a a ciurului lui Eratostene: considerm succesiv elementele listei ordine cresctoare a n a i suprimm toti multiplii lor. Aceasta se realizeaz prin procedura urmtoare: s a a a static Lista Eratostene (int n) { Lista a=null; int k; for(int i=n; i>=2; --i) { a=adauga(i,a); } k=a.continut; for(Lista b=a; k*k<=n; b=b.urmator) { k=b.continut; for(int j=k; j<=n/k; ++j) a=elimina(j*k,a); } return a; } Exemplu 2. Pentru ecare intrare i din intervalul [0..n 1] se construiete s o list simplu antuit format din toate cheile care au h(x) = i. Elementele a nl a a listei sunt intrri care permit accesul la tabloul de nume i numere de telefon. a s Procedurile de cutare i inserare a unui element x a s ntr-un tablou devin proceduri de cutare i adugare a s a ntr-o list. Tot astfel, dac functia h este ru aleas, a a a a numrul de coliziuni devine important, multe liste devin de dimensiuni enorme i a s frmitarea risc s devin la fel de costisitoare ca i cutarea aa a a a s a ntr-o list simplu a antuit obinuit. Dac h este bine aleas, cutarea este rapid. nl a s a a a a a Lista al[] = new Lista[N1]; static void insereaza (String x, int val) { int i = h(x); al[i] = adauga (x, al[i]); } static int cauta (String x) { for (int a = al[h(x)]; a != null; a = a.urmator) { if (x.equals(nume[a.continut])) return telefon[a.continut]; } return -1; }

11.2. COZI

151

11.2

Cozi

Cozile sunt liste liniare particulare utilizate programare pentru gestionarea n obiectelor care sunt ateptarea unei prelucrri ulerioare, de exemplu procesele n s a de ateptare a resurselor unui sistem, nodurile unui graf, etc. Elementele sunt s adugate sistematic la coad i sunt eliminate din capul listei. englez se spune a as In a strategie FIFO (First In First Out), opozitie cu strategia LIFO (Last In Firs n Out) utilizat la stive. a Mai formalizat, considerm o multime de elemente E, multimea cozilor cu a elemente din E este notat Coada(E), coada vid (care nu contine nici un element) a a este C0 , iar operatiile asupra cozilor sunt: esteV ida, adauga, valoare, elimina: esteV ida este o aplicatie denit pe Coada(E) cu valori {true, f alse}, a n esteV ida(C) aste egal cu true dac i numai dac coada C este vid. a as a a adauga este o aplicatie denit pe E Coada(E) cu valori Coada(E), a n adauga(x, C) este coada obtinut plecnd de la coada C i insernd elementul a a s a x la sfritul ei. as valoare este o aplicatie denit pe Coada(E)C0 cu valori E care asociaz a n a unei cozi C, nevid, elementul aat cap. a n elimina este o aplicatie denit pe Coada(E)C0 cu valori Coada(E) care a n asociaz unei cozi nevide C o coad obtinut plecnd de la C i eliminnd a a a a s a primul element. Operatiile asupra cozilor satisfac urmtoarele relatii: a Pentru C = C0 elimina(adauga(x, C)) = adauga(x, elimina(C)) valoare(adauga(x, C)) = valoare(C) Pentru toate cozile C esteV ida(adauga(x, C)) = f alse Pentru coada C0 elimina(adauga(x, C0 )) = C0 valoare(adauga(x, C0 )) = x esteV ida(C0 ) = true O prim idee de realizare sub form de programe a operatiilor asupra cozilor a a este de a mprumuta tehnica folosit locurile unde clientii stau la coad pentru a n a a serviti, de exemplu la gar pentru a lua bilete, sau la cas a a ntr-un magazin.

152

CAPITOLUL 11. LISTE

Fiecare client care se prezint obtine un numr i clientii sunt apoi chemati de ctre a a s a functionarul de la ghieu ordinea cresctoare a numerelor de ordine primite la s n a sosire. Pentru gestionarea acestui sistem, gestionarul trebuie s cunoasc dou a a a numere: numrul obtinut de ctre ultimul client sosit i numrul obtinut de ctre a a s a a ultimul client servit. Notm aceste dou numere inceput i sf arsit i gestionm a a s s a sistemul modul urmtor: n a coada de ateptare este vid dac i numai dac inceput = sf arsit, s a as a atunci cnd sosete un nou client, se incrementeaz sf arsit i se d acest a s a s a numr clientului respectiv, a atunci cnd functionarul este liber el poate servi un alt client, dac coada a a nu este vid, incrementeaz inceput i cheam posesorul acestui numr. a a s a a cele ce urmeaz sunt prezentate toate operatiile Java utiliznd tehnicile In a n a urmtoare: se reatribuie numrul 0 unui nou client atunci cnd se depete un a a a as s anumit prag pentru valoarea sf arsit. Se spune c este un tablou (sau tampon) a circular, sau coad circular. a a class Coada { final static int MaxC = 100; int inceput; int sfarsit; boolean plina, vida; int continut[]; Coada () { inceput = 0; sfarsit = 0; plina= false; vida = true; info = new int[MaxC]; } static Coada vida(){ return new Coada(); } static void facVida (Coada c) { c.inceput = 0; c.sfarsit = 0; c.plina = false; c.vida = true; } static boolean esteVida(Coada c) { return c.vida; }

11.2. COZI

153

static boolean estePlina(Coada c) { return c.plina; } static int valoare(Coada c) { if (c.vida) erreur ("Coada Vida."); return c.info[f.inceput]; } private static int succesor(int i) { return (i+1) % MaxC; } static void adaug(int x, Coada c) { if (c.plina) erreur ("Coada Plina."); c.info[c.sfarsit] = x; c.sfarsit = succesor(c.sfarsit); c.vida = false; c.plina = c.sfarsit == c.inceput; } static void elimina (Coada c) { if (c.vida) erreur ("Coada Vida."); c.inceput = succesor(c.inceput); c.vida = c.sfarsit == c.inceput; c.plina = false; } }

inceput

sfarsit

e1

e2

...

en

Figura 11.3: Coad de ateptare implementat ca list a s a a

O alt modalitate de gestionare a cozilor const utilizarea listelor antuite cu a a n nl

154

CAPITOLUL 11. LISTE

gard care se cunosc adresele primului i ultimului element. Aceasta d operatiile a n s a urmtoare: a class Coada { Lista inceput; Lista sfarsit; Coada (Lista a, Lista b) { inceput = a; sfarsit = b; } static Coada vida() { Lista garda = new Lista(); return new Coada (garda, garda); } static void facVida (Coada c) { Lista garda = new Lista(); c.inceput = c.sfarsit = garda; } static boolean esteVida (Coada c) { return c.inceput == c.sfarsit; } static int valoare (Coada c) { Lista b = c.inceput.next; return b.info; } static void adauga (int x, Coada c) { Lista a = new Lista (x, null); c.sfarsit.next = a; f.sfarsit = a; } static void elimina (Coada c) { if (esteVida(c)) erreur ("Coada Vida."); c.inceput = c.inceput.next; } } Cozile se pot implementa e cu ajutorul tablourilor e cu ajutorul listelor sim-

11.3. STIVE

155

plu antuite. Scrierea programelor const primul rnd alegerea structurilor nl a n a n de date pentru reprezentarea cozilor. Ansamblul functiilor care opereaz asupra a cozilor se pot plasa ntr-un modul care manipuleaz tablouri sau liste. Utilizarea a cozilor se face cu ajutorul functiilor vida, adauga, valoare, elimina. Aceasta este deci interfata cozilor care are important programe complexe. a n

11.3

Stive

Notiunea de stiv intervine mod curent programare, rolul su principal a n n a ind la implementarea apelurilor de proceduri. O stiv se poate imagina ca o cutie a care sunt plasate obiecte i din care se scot ordinea invers fata de cum au n s n a fost introduse: obiectele sunt puse unul peste altul cutie i se poate avea acces n s numai la obiectul situat vrful stivei. mod formalizat, se consider o multime n a In a E, multimea stivelor cu elemente din E este notat Stiva(E), stiva vid (care nu a a contine nici un element) este S0 , operatiile efectuate asupra stivelor sunt vida, adauga, valoare, elimina, ca i la re. De aceast dat, relatiile satisfcute sunt s a a a urmtoarele: a elimina(adauga(x, S)) = S esteV ida(adauga(x, S)) = f alse valoare(adauga(x, S)) = x esteV ida(S0 ) = true Cu ajutorul acestor relatii se pot exprima toate operatiile relative la stive. Realizarea operatiilor asupra stivelor se poate face utiliznd un tablou care a contine elementele i un indice care indic pozitia vrfului stivei. s a a class Stiva { final static int maxP = 100; int inaltime; Element continut[]; Stiva() { inaltime = 0; continut = new Element[maxP]; } static Fir vid () { return new Stiva(); }

156 static void facVida (Stiva s) { s.inaltime = 0; } static boolean esteVida (Stiva s) { return s.inaltime == 0; } static boolean estePlina (Stiva s) { return s.inaltime == maxP; }

CAPITOLUL 11. LISTE

static void adauga (Element x, Stiva s) throws ExceptionStiva { if (estePlina (s)) throw new ExceptionStiva("Stiva plina"); s.continut[s.inaltime] = x; ++ s.inaltime; } static Element valoare (Stiva s) throws ExceptionStiva { if (esteVida (s)) throw new ExceptionStiva("Stiva vida"); return s.continut[s.inaltime-1]; } static void supprimer (Stiva s) throws ExceptionStiva { if (esteVida (s)) throw new ExceptionStiva ("Stiva vida"); s.inaltime; } } unde class ExceptionStiva extends Exception { String text; public ExceptionStiva (String x) { text = x; } } Stivele se pot implementa att cu ajutorul tablourilor (vectorilor) ct i cu a a s ajutorul listelor simplu antuite. nl

11.4. EVALUAREA EXPRESIILOR ARITMETICE PREFIXATE

157

11.4

Evaluarea expresiilor aritmetice prexate

Vom ilustra utilizarea stivelor printr-un program de evaluare a expresiilor aritmetice scrise sub o form particular. programare o expresie aritmetic a a In a poate contine numere, variabile i operatii aritmetice (ne vom limita numai la + s i *). Vom considera ca intrri numai numere naturale. s a Expresiile prexate contin simbolurile: numere naturale, +, *, ( i ). Dac e1 s a i e2 sunt expresii prexate atunci (+e1 e2 ) i (e1 e2 ) sunt expresii prexate. s s Pentru reprezentarea unei expresii prexate Java, vom utiliza un tablou ale n crui elemente sunt entitile expresiei. Elementele tabloului sunt obiecte cu trei a at cmpuri: primul reprezint natura entitii (simbol sau numr), al doilea reprezint a a at a a valoarea entitii dac aceasta este numr, iar al treilea este este un simbol dac at a a a entitatea este simbol. class Element { boolean esteOperator; int valoare; char simbol; } Vom utiliza functiile denite pentru stiv i procedurile denite cele ce a s n urmeaz. a static int calcul (char a, int x, int y) { switch (a) { case +: return x + y; case *: return x * y; } return 1; } Procedura de evaluare const stivuirea rezultatelor intermediare, stiva a n continnd operatori i numere, dar niciodat nu va contine consecutiv numere. a s a Se examineaz succesiv entitile expresiei dac entitatea este un operator sau un a at a numr i dac vrful stivei este un operator, atunci se plaseaz stiv. Dac a s a a a n a a este un numr i vrful stivei este de asemenea un numr, actioneaz operatorul a s a a a care precede vrful stivei asupra celor dou numere i se repet operatia asupra a a s a rezultatului gsit. a De exemplu, pentru expresia (+ (* (+ 35 36) (+ 5 6)) (* (+ 7 8) (*9 9 ))) evaluarea decurge astfel:

158

CAPITOLUL 11. LISTE 9 * 15 * 781 +

* +

+ * +

35 + * +

71 * +

+ 71 * +

5 + 71 * +

781 +

* 781 +

+ * 781 +

7 + * 781 +

* 15 * 781 +

static void insereaza (Element x, Stiva s) throws ExceptionStiva { Element y, op; while (!(Stiva.esteVida(s) || x.esteOperator || Stiva.valoare(s).esteOperator)) { y = Stiva.valoare(s); Stiva.elimina(s); op = Stiva.valoare(s); Stiva.elimina(s); x.valoare = calcul(op.valsimb, x.valoare, y.valoare); } Stiva.adauga(x,s); } static int calcul (Element u[]) throws ExceptionStiva { Stiva s = new Stiva(); for (int i = 0; i < u.length ; ++i) { insereaza(u[i], s); } return Stiva.valoare(s).valoare; } acest caz, este util prezentarea unui program principal care utilizeaz In a a aceste functii. public static void main (String args[]) { Element exp[] = new Element [args.length]; for (int i = 0; i < args.length; ++i) { String s = args[i]; if (s.equals("+") || s.equals("*")) exp[i] = new Element (true, 0, s.charAt(0)); else exp[i] = new Element (false, Integer.parseInt(s), ); } try { System.out.println(calcul(exp)); } catch (ExceptionStiva x) { System.err.println("Stiva " + x.nom); } }

11.5. OPERATII ASUPRA LISTELOR

159

11.5

Operatii asupra listelor

aceast sectiune sunt prezentati civa algoritmi de manipulare a listelor. In a at Acetia sunt utilizati limbajele de programare care au listele ca structuri de s n baz. Functia Tail este o primitiv clasic; ea suprim primul element al listei. a a a a
a Tail(a)

e1

e2

e3

e4

e1

e2

e3

e4

Figura 11.4: Suprimarea primului element din list a

class Lista { Object info; Lista next; Lista(Object x, Lista a) { info = x; next = a; } static Lista cons (Object x, Lista a) { return new Lista (x, a); } static Object head (Lista a) { if (a == null) erreur ("Head dune liste vide."); return a.info; } static Lista tail (Lista a) { if (a == null) erreur ("Tail dune liste vide."); return a.next; } Procedurile asupra listelor construiesc o list plecnd de la alte dou liste, a a a pun o list la captul celeilalte liste. prima procedur append, cele dou liste a a In a a nu sunt modicate; a doua procedur, concat, prima list este transformat n a a a pentru a retine rezultatul. Totusi, se poate remarca faptul c, dac append copiaz a a a primul su argument, partajeaz nalul listei rezultat cu al doilea argument. a a

160
a b

CAPITOLUL 11. LISTE

a1

a2 append(a,b}

a3

b1

b2

b3

b4

a1

a2

a3

Figura 11.5: Concatenarea a dou liste prin append a

static Lista append (Lista a, Lista b) { if (a == null) return b; else return adauga(a.info, append (a.next, b)) ; }
a b

a1

a2 concat(a,b}

a3

b1

b2

b3

b4

Figura 11.6: Concatenarea a dou liste prin concat a

static Lista concat(Lista a, Lista b) { if (a == null) return b; else { Lista c = a; while (c.next != null) c = c.next; c.next = b; return a; } }

11.5. OPERATII ASUPRA LISTELOR Aceast ultim procedur se poate scrie recursiv: a a a static Lista concat (Lista a, Lista b) { if (a == null) return b; else { a.next = concat (a.next, c); return a; } }

161

Procedura de calcul de oglindire a unei liste a const construirea unei a n liste care elementele listei a sunt ordine invers. Realizarea acestei proceduri n n a este un exercitiu clasic de programare asupra listelor. Dm aici dou soluti, una a a iterativ, cealalt recursiv, complexitatea este O(n2 ) deci ptratic, dar clasic. a a a a a a Noua metod nReverse modic argumentul su, timp ce Reverse nu-l modic a a a n a ci construiete o alt list pentru rezultat. s a a static Lista nReverse (Lista a) { Lista b = null; while (a != null) { Lista c = a.next; a.next = b; b = a; a = c; } return b; }
c

e1

e2

e3

e4 a

e5

nill

Figura 11.7: Transformarea listei prin inversarea legturilor a

static Lista Reverse (Lista a) { if (a == null) return a; else return append(Reverse (a.next), adauga(a.info, null)); }

162

CAPITOLUL 11. LISTE

Se poate scrie o versiune iterativ a versiunii recursive, gratie unei functii auxiliare a care acumuleaz rezultatul a ntr-unul din argumentele sale: static Lista nReverse (Lista a) { return nReverse1(null, a); } static Lista nReverse1 (Lista b, Lista a) { if (a == null) return b; else return nReverse1(adauga(a.info, b), a.next); } Un alt exercitiu formator const a administra liste care elementele sunt a n n aranjate ordine cresctoare. Procedura de adugare devine ceva mai complex n a a a pentru c trebuie gsit pozitia celulei care trebuie adugat dup parcurgerea a a a a a a unei prti a listei. a Nu tratm acest exercitiu dect cazul listelor circulare cu gard. Pentru o a a n a astfel de list, valoarea cmpului inf o din prima celul nu are nici o semnicatie a a a (este celula de gard). Cmpul next din ultima celul contine adresa primei celule. a a a

a1 garda

a2

a3

a4

a5

a6

Figura 11.8: List circular cu gard a a a

static Lista insereaza (int v, Lista a) { Lista b = a; while (b.next != a && v > head(b.next)) b = b.next; b.next = adauga(v, b.next); a.info = head(a) + 1; return a; }

Capitolul 12

Algoritmi divide et impera


12.1 Tehnica divide et impera

Divide et impera1 este o tehnic de elaborare a algoritmilor care const a a n: Descompunerea repetat a problemei (subproblemei) ce trebuie rezolvat a a n subprobleme mai mici. Rezolvarea acelai mod (recursiv) a tuturor subproblemelor. n s Compunerea subsolutiilor pentru a obtine solutia problemei (subproblemei) initiale. Descompunerea problemei (subproblemelor) se face pn n momentul care a a n se obtin subprobleme de dimensiuni att de mici at au solutie cunoscut sau a nc a pot rezolvate prin tehnici elementare. Metoda poate descris astfel: a procedure divideEtImpera(P, n, S) if (n n0 ) then rezolv subproblema P prin tehnici elementare a else mparte P P1 , ..., Pk de dimensiuni n1 , ..., nk n divideEtImpera(P1 , n1 , S1 ) ... divideEtImpera(Pa , nk , Sk ) combin S1 , ..., Sk pentru a obtine S a Exemplele tipice de aplicare a acestei metode sunt algoritmii de parcurgere a arborilor binari i algoritmul de cutare binar. s a a
1 divide-and-conquer,

englez n a

163

164

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

12.2

Ordinul de complexitate

Vom presupune c dimensiunea ni a subproblemei i satisface relatia ni n , a b unde b > 1. Astfel, pasul de divizare reduce o subproblem la altele de dimensiuni a mai mici, ceea ce asigur terminarea subprogramului recursiv. a Presupunem c divizarea problemei subprobleme i compunerea solutiilor a n s subproblemelor necesit un timp de ordinul O(nk ). Atunci, complexitatea timp a T (n) a algoritmului divideEtImpera este dat de relatia de recurent: a a T (n) = O(1) a T ( n ) + O(nk ) b , dac n n0 a , dac n > n0 a (12.2.1)

Teorema 3 Dac n > n0 atunci: a O(nlogb a ) T (n) = O(nk logb n) O(nk )

, dac a > bk a , dac a = bk a , dac a < bk a

(12.2.2)

Demonstratie: Putem presupune, fr a restrnge generalitatea, c n = bm n0 . De aa a a asemenea, presupunem c T (n) = c nk dac n n0 i T (n) = a T ( n ) + c nk a a s 0 b dac n > n0 . Pentru n > n0 avem: a n T (n) = aT + cnk b = aT bm1 n0 + cnk = a aT bm2 n0 + c = a2 T bm2 n0 = ... = am T (n0 ) + c am1 n bm1
m k

n b n +c a b

+ cnk + nk n b
k

+ ... + a
k

+ nk

= am cnk + c am1 bk nk + ... + a bm1 0 0 = cnk am 1 + 0


m

nk + (bm )k nk 0 0

bk + ... + a
i

bk a

= cam
i=0

bk a

unde am notat cnk prin c. Distingem cazurile: 0 1. a > bk . Seria


m i=0 bk a i

este convergent i deci irul sumelor partiale este as s

convergent. De aici rezult c T (n) = O(am ) = O(alogb n ) = O(nlogb a ) a a

12.3. EXEMPLE

165

2. a = bk . Rezult c am = bkm = cnk i de aici T (n) = O(nk m) = O(nk logb n). a a s 3. a < bk . Avem T (n) = O(am ( ba )m ) = O(bkm ) = O(nk ).
k

12.3

Exemple

Dintre problemele clasice care se pot rezolva prin metoda divide et impera mentionm: a cutare binar a a sortare rapid (quickSort) a problema turnurilor din Hanoi sortare prin interclasare - Merge sort dreptunghi de arie maxim placa cu guri, etc a n a

12.3.1

Sortare prin partitionare - quicksort

Se bazeaz pe metoda interschimbrii, a din nou, interschimbarea se face a a ns pe distante mai mari. Astfel, avnd tabloul a[], se aplic urmtorul algoritm: a a a 1. se alege la amplare un element x al tabloului nt 2. se scaneaz tabloul a[] la stnga lui x pn cnd se gsete un element ai > x a a a a a a s 3. se scaneaz tabloul la dreapta lui x pn cnd se gsete un element aj < x a a a a a s 4. se interschimb ai cu aj a 5. se repet paii 2, 3, 4 pn cnd scanrile se vor alni pe undeva la mijlocul a s a a a a nt tabloului. acel moment, tabloul a[] va partitionat 2 astfel, la stnga In n a lui x se vor gsi elemente mai mici ca i x, la dreapta, elemente mai mari ca a s i x. Dup aceasta, se aplic acelai proces subirurilor de la stnga i de la s a a s s a s dreapta lui x, pn cnd aceste subiruri sunt sucient de mici (se reduc la a a a s un singur element). class QuickSort { static int x[]={3,5,2,6,4,1,8,2,4,3,5,3}; public static void main(String[]args) {

166

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

quickSort(0,x.length-1,x); for(int i=0;i<x.length;i++) System.out.print(x[i]+" "); }//main static void quickSort(int st,int dr,int a[]) { int i=st, j=dr, x, temp; x=a[(st+dr)/2]; do { while (a[i]<x) i++; while (a[j]>x) --j; if(i<=j) { temp=a[i]; a[i]=a[j]; a[j]=temp; j--; i++; } } while(i<=j); if(st<j) quickSort(st,j,a); if(i<dr) quickSort(i,dr,a); }// quickSort(...) }//class Aceasta metoda are complexitatea n log n, practic ( medie)! n a n

12.3.2

Sortare prin interclasare - MergeSort

Ideea este de a utiliza interclasarea etapa de asamblare a solutiilor. n urma rezolvrii recursive a subproblemelor rezult vectori ordonati i prin In a a s interclasarea lor obtinem vectorul initial sortat. Vectorii de lungime 1 sunt evident considerati sortati. Pasul de divizare se face timpul O(1). Faza de asamblare se face timpul n n O(m1 + m2 ) unde n1 i n2 sunt lungimile celor doi vectori care se interclaseaz. s a Din Teorema 3 pentru a = 2, b = 2 i k = 1 rezult c ordinul de complexitate s a a al algoritmului MergeSort este O(n log2 n) unde n este dimensiunea vectorului initial supus sortrii. a class MergeSort { static int x[]={3,5,2,6,4,1,8,2,4,3,5,3}; public static void main(String[]args)

12.3. EXEMPLE { mergeSort(0,x.length-1,x); for(int i=0;i<x.length;i++) System.out.print(x[i]+" "); }//main public static int[] mergeSort(int st,int dr,int a[]) { int m,aux; if((dr-st)<=1) { if(a[st]>a[dr]) { aux=a[st];a[st]=a[dr];a[dr]=aux;} } else { m=(st+dr)/2; mergeSort(st,m,a); mergeSort(m+1,dr,a); interclasare(st,m,dr,a); } return a; }// mergeSort(...) public static int[] interclasare(int st,int m,int dr,int a[]) { // interclasare pozitii st,...,m cu m+1,...,dr int i,j,k; int b[]=new int[dr-st+1]; i=st; j=m+1; k=0; while((i<=m)&&(j<=dr)) { if(a[i]<=a[j]) { b[k]=a[i]; i++;} else { b[k]=a[j]; j++; } k++; } if(i<=m) for(j=i;j<=m; j++) { b[k]=a[j]; k++; } else for(i=j;i<=dr;i++) { b[k]=a[i]; k++; } k=0; for(i=st;i<=dr;i++) { a[i]=b[k]; return a; }//interclasare(...) }//class

167

k++; }

168

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

12.3.3

Placa cu guri a

Se consider o plac dreptunghiular care exist n guri punctiforme de a a a n a a coordonate cunoscute. S se determine pe aceast plac un dreptunghi de arie a a a maxim, cu laturile paralele cu laturile plcii, care s nu contin guri interior a a a a a n ci numai eventual pe laturi. Vom considera placa ntr-un sistem cartezian. Considerm o subproblem de a a forma urmtoare: dreptunghiul determinat de diagonala (x1, y1) (din stnga-jos) a a i (x2, y2) din dreapta-sus este analizat pentru a vedea dac interior contine vreo s a n gaur; dac nu contine, este o solutie posibil (se va alege cea de arie maxim); dac a a a a a exist o gaur interiorul su, atunci se consider patru subprobleme generate a a n a a de cele 4 dreptunghiuri obtinute prin descompunerea pe orizontal i pe vertical as a de cele dou drepte paralele cu axele care trec prin punctul care reprezint gaura. a a
1 2 3

Figura 12.1: Dreptunghi de arie maxim placa cu guri a n a

import java.io.*; class drArieMaxima { static int x1,y1,x2,y2,n,x1s,y1s,x2s,y2s,amax; static int[] x; static int[] y; public static void main (String[] args) throws IOException { int i; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("dreptunghi.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("dreptunghi.out"))); st.nextToken(); st.nextToken(); st.nextToken(); st.nextToken(); st.nextToken(); x1=(int) st.nval; y1=(int) st.nval; x2=(int) st.nval; y2=(int) st.nval; n=(int) st.nval;

12.3. EXEMPLE

169

x=new int [n+1]; y=new int [n+1]; for(i=1;i<=n;i++) { st.nextToken(); x[i]=(int) st.nval; st.nextToken(); y[i]=(int) st.nval; } dr(x1,y1,x2,y2); out.println(amax); out.println(x1s+" "+y1s+" "+x2s+" "+y2s); out.close(); } static void dr(int x1,int y1,int x2,int y2) { int i,s=(x2-x1)*(y2-y1); if(s<=amax) return; boolean gasit=false; for(i=1;i<=n;i++) if((x1<x[i])&&(x[i]<x2)&&(y1<y[i])&&(y[i]<y2)) { gasit=true; break; } if(gasit) { dr(x1,y1,x[i],y2); dr(x[i],y1,x2,y2); dr(x1,y[i],x2,y2); dr(x1,y1,x2,y[i]); } else { amax=s; x1s=x1; y1s=y1; x2s=x2; y2s=y2; } } }

12.3.4

Turnurile din Hanoi

Se dau trei tije verticale A, B2 i C. Pe tija A se gsesc n discuri de diametre s a diferite, aranjate ordine descresctoare a diametrelor de la baz spre vrf. Se cere n a a a s se gseasc o strategie de mutare a discurilor de pe tija A pe tija C respectnd a a a a urmtoarele reguli: a

170

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA la un moment dat se va muta un singur disc (cel care se a deasupra a celorlalte discuri pe o tij); a un disc poate aezat doar peste un disc de diametru mai mare dect s a al s sau pe o tij goal. a a a artim problema astfel: Imp se mut primele n 1 discuri de pe tija surs A pe tija intermediar B a a a se mut ultimul disc de pe tija surs A pe tija destinatie C a a se mut cele n 1 discuri de pe tija intermediar B pe tija destinatie C a a
pasul 1

a)

b) pasul 2

pasul 3

B d)

B c)

import java.io.*; class Hanoi { static int nrMutare=0; public static void main(String[] args) throws IOException { int nrDiscuri; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Introduceti numarul discurilor: "); nrDiscuri=Integer.parseInt(br.readLine()); solutiaHanoi(nrDiscuri, A, B, C); }// main() static void solutiaHanoi(int nrDiscuri, char tijaSursa, char tijaIntermediara, char tijaDestinatie) { if(nrDiscuri==1)

12.3. EXEMPLE

171

System.out.println(++nrMutare + " Disc 1 " + tijaSursa + " ==> "+ tijaDestinatie); else { solutiaHanoi(nrDiscuri-1,tijaSursa,tijaDestinatie,tijaIntermediara); System.out.println(++nrMutare + " Disk " + nrDiscuri + " " + tijaSursa + " --> "+ tijaDestinatie); solutiaHanoi(nrDiscuri-1,tijaIntermediara,tijaSursa,tijaDestinatie); } }// solutiaHanoi() }// class Introduceti 1 Disc 1 A 2 Disk 2 A 3 Disc 1 B 4 Disk 3 A 5 Disc 1 C 6 Disk 2 C 7 Disc 1 A 8 Disk 4 A 9 Disc 1 B 10 Disk 2 B 11 Disc 1 C 12 Disk 3 B 13 Disc 1 A 14 Disk 2 A 15 Disc 1 B numarul discurilor: 4 ==> B --> C ==> C --> B ==> A --> B ==> B --> C ==> C --> A ==> A --> C ==> B --> C ==> C

import java.io.*; class HanoiDisc { static int nrMutare=0; static final int MAX_NR_DISCURI=9; static int[] a=new int[MAX_NR_DISCURI], b=new int[MAX_NR_DISCURI], c=new int[MAX_NR_DISCURI]; static int na,nb,nc; // na = nr. discuri pe tija A; etc. ... static int discMutat; public static void main(String[] args) throws IOException { BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

172

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

int nrDiscuri=0; while((nrDiscuri<1)||(nrDiscuri>MAX_NR_DISCURI)) { System.out.print("Numar discuri"+"[1<=...<="+MAX_NR_DISCURI+"]: "); nrDiscuri = Integer.parseInt(br.readLine()); } na=nrDiscuri; nb=0; nc=0; for(int k=0;k<na;k++) a[k]=na-k; afisareDiscuri(); System.out.println(); solutiaHanoi(nrDiscuri, A, B, C); }// main() static void solutiaHanoi(int nrDiscuri, char tijaSursa, char tijaIntermediara, char tijaDestinatie) { if (nrDiscuri==1) { mutaDiscul(tijaSursa, tijaDestinatie); System.out.print(++nrMutare + " Disc " + "1" + " " + tijaSursa + " ==> "+ tijaDestinatie + " "); afisareDiscuri(); System.out.println(); } else { solutiaHanoi(nrDiscuri-1,tijaSursa,tijaDestinatie,tijaIntermediara); mutaDiscul(tijaSursa, tijaDestinatie); System.out.print(++nrMutare + " Disc " + nrDiscuri + " " + tijaSursa + " --> "+ tijaDestinatie + " "); afisareDiscuri(); System.out.println(); solutiaHanoi(nrDiscuri-1, tijaIntermediara, tijaSursa, tijaDestinatie); } } // solutiaHanoi() static void mutaDiscul(char tijaSursa, char tijaDestinatie) { switch (tijaSursa) { case A: discMutat=a[(na--)-1]; break; case B: discMutat=b[(nb--)-1]; break; case C: discMutat=c[(nc--)-1]; }

12.3. EXEMPLE

173

switch (tijaDestinatie) { case A: a[(++na)-1]=discMutat; break; case B: b[(++nb)-1]=discMutat; break; case C: c[(++nc)-1]=discMutat; } }// mutaDiscul() static void afisareDiscuri() { System.out.print(" A("); for(int i=0;i<na;i++) System.out.print(a[i]); System.out.print(") "); System.out.print(" B("); for(int i=0;i<nb;i++) System.out.print(b[i]); System.out.print(") "); System.out.print(" C("); for(int i=0;i<nc;i++) System.out.print(c[i]); System.out.print(") "); }// afisareDiscuri() }// class Numar discuri[1<=...<=9]: 4 A(4321) 1 Disc 1 A ==> B A(432) 2 Disc 2 A --> C A(43) 3 Disc 1 B ==> C A(43) 4 Disc 3 A --> B A(4) 5 Disc 1 C ==> A A(41) 6 Disc 2 C --> B A(41) 7 Disc 1 A ==> B A(4) 8 Disc 4 A --> C A() 9 Disc 1 B ==> C A() 10 Disc 2 B --> A A(2) 11 Disc 1 C ==> A A(21) 12 Disc 3 B --> C A(21) 13 Disc 1 A ==> B A(2) 14 Disc 2 A --> C A() 15 Disc 1 B ==> C A()

B() B(1) B(1) B() B(3) B(3) B(32) B(321) B(321) B(32) B(3) B(3) B() B(1) B(1) B()

C() C() C(2) C(21) C(21) C(2) C() C() C(4) C(41) C(41) C(4) C(43) C(43) C(432) C(4321)

12.3.5

Injumtire repetat a at a

174

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

Se consider un ir de numere naturale x1 , x2 , ..., xn i o succesiune de decizii a s s d1 , d2 , ... unde di {S, D} au urmtoarea semnicatie: la pasul i, irul rmas a s a n acel moment se mparte dou subiruri cu numr egal elemente (dac numrul de n a s a a a elemente este impar, elementul situat la mijloc se elimin) i se elimin jumtatea a s a a din partea stng a irului dac di = S (dac di = D se elimin jumtatea din a a s a a a a partea drept). a Deciziile se aplic ordine, una dup alta, pn cnd rmne un singur a n a a a a a a element. Se cere acest element. class Injumatatire1 { static int n=10; static char[] d={X,S,D,S,S}; // nu folosesc d_0 ... ! public static void main(String[]args) { int st=1,dr=n,m,i=1; boolean impar; while(st<dr) { System.out.println(st+" ... "+dr+" elimin "+d[i]); m=(st+dr)/2; if((dr-st+1)%2==1) impar=true; else impar=false; if(d[i]==S) // elimin jumatatea stanga { st=m+1; } else // elimin jumatatea dreapta { dr=m; if(impar) dr--; } i++; System.out.println(st+" ... "+dr+" a ramas! \n"); } }//main }//class 1 ... 10 elimin S 6 ... 10 a ramas! 6 ... 10 elimin D

12.3. EXEMPLE 6 ... 7 a ramas! 6 ... 7 elimin S 7 ... 7 a ramas! Toate elementele care pot rmne: a a class Injumatatire2 { static int n=10; static char[] d=new char[10]; public static void main(String[]args) { divide(1,n,1); }//main static void divide(int st0,int dr0, int i) { int st,dr,m; boolean impar; if(st0==dr0) { for(m=1;m<=i;m++) System.out.print(d[m]); System.out.println(" --> "+st0); return; } m=(st0+dr0)/2; if((dr0-st0+1)%2==1) impar=true; else impar=false; // elimin jumatatea stanga st=m+1; d[i]=S; if(st<=dr0) divide(st,dr0,i+1); // elimin jumatatea dreapta dr=m; if(impar) dr--; d[i]=D; if(st0<=dr) divide(st0,dr,i+1); }// divide(...) }//class

175

176 SSS SSD SDS SDD DSS DSD DDS DDD --> --> --> --> --> --> --> --> 10 9 7 6 5 4 2 1

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

Capitolul 13

Algoritmi BFS-Lee

13.1

Prezentare general a

import java.io.*; class BFS // nodurile sunt de la 1 la n { // distanta minima dintre nod "sursa" si nod "dest" static final int oo=0x7fffffff; // infinit static final int WHITE=0, GRAY=1, BLACK=2; static int[][] a; // matricea de adiacenta static int[] color; // pentru bfs static int[] p; // predecesor static int[] d; // distante catre sursa static int[] q; // coada static int ic; // inceput coada = prima pozitie ocupata din care scot ! static int sc; // sfarsit coada = prima pozitie libera pe care voi pune ! static int n,m; // varfuri, muchii public static void main(String[] args) throws IOException { int i,j,k,nods,nodd; // nod_sursa, nod_destinatie StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("bfs.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("bfs.out")));

177

178 st.nextToken(); st.nextToken(); st.nextToken(); st.nextToken();

CAPITOLUL 13. ALGORITMI BFS-LEE n=(int)st.nval; m=(int)st.nval; nods=(int)st.nval; nodd=(int)st.nval;

a=new int[n+1][n+1]; color=new int[n+1]; p=new int[n+1]; d=new int[n+1]; q=new int[m+1]; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; a[i][j]=1; a[j][i]=1; } bfs(nods,nodd); System.out.println("Distanta("+nods+","+nodd+") = "+d[nodd]); System.out.print("drum : "); drum(nodd); System.out.println(); out.close(); }//main static void videzcoada() { ic=0; sc=0; // coada : scot <-- icoada...scoada <-- introduc } static boolean coadaEsteVida() { return (sc==ic); } static void incoada(int v) { q[sc++]=v; color[v]=GRAY; } static int dincoada()

13.1. PREZENTARE GENERALA { int v=q[ic++]; color[v]=BLACK; return v; } static void bfs(int start, int fin) { int u, v; for(u=1; u<=n; u++) { color[u]=WHITE; d[u]=oo; } color[start]=GRAY; d[start]=0; videzcoada(); incoada(start); while(!coadaEsteVida()) { u=dincoada();

179

// Cauta nodurile albe v adiacente cu nodul u si pun v in coada for(v=1; v<=n; v++) { if(a[u][v]==1) // v in Ad[u] { if(color[v]==WHITE) // neparcurs deja { color[v]=GRAY; d[v]=d[u]+1; p[v]=u; if(color[fin]!=WHITE) break; // optimizare; ies din for incoada(v); } } }//for color[u]=BLACK; if(color[fin]!=WHITE) break; }//while }//bfs

// am ajuns la nod_destinatie

180

CAPITOLUL 13. ALGORITMI BFS-LEE

static void drum(int u) // nod_sursa ---> nod_destinatie { if(p[u]!=0) drum(p[u]); System.out.print(u+" "); } }//class /* 9 12 2 8 2 5 1 2 2 3 3 1 3 4 4 5 5 6 6 4 7 8 8 9 9 7 7 4 */

Distanta(2,8) = 4 drum : 2 3 4 7 8

13.2

Probleme rezolvate

13.2.1

Romeo i Julieta - OJI2004 clasa a X-a s

ultima ecranizare a celebrei piese shakespeariene Romeo i Julieta triesc In s a ntr-un ora modern, comunic prin e-mail i chiar s programeze. s a s nvata a Intro secvent tulburtoare sunt prezentate frmtrile interioare ale celor doi eroi a a a aa ncercnd fr succes s scrie un program care s determine un punct optim de a aa a a alnire. nt Ei au analizat harta oraului i au reprezentat-o sub forma unei matrice cu s s n linii i m coloane, matrice ind marcate cu spatiu zonele prin care se poate s n trece (strzi lipsite de pericole) i cu X zonele prin care nu se poate trece. De a s asemenea, matrice au marcat cu R locul care se a locuinta lui Romeo, iar n n a cu J locul care se a locuinta Julietei. n a Ei se pot deplasa numai prin zonele care sunt marcate cu spatiu, din pozitia curent oricare dintre cele 8 pozitii a n nvecinate (pe orizontal, vertical sau dia a agonale).

13.2. PROBLEME REZOLVATE

181

Cum lui Romeo nu place s atepte i nici s se lase ateptat n-ar tocmai i a s s a s bine, ei au hotrt c trebuie s aleag un punct de alnire care att Romeo, aa a a a nt n a ct i Julieta s poat ajunge acelai timp, plecnd de acas. Fiindc la alniri a s a a n s a a a nt amndoi vin a ntr-un suet, ei estimeaz timpul necesar pentru a ajunge la alnire a nt prin numrul de elemente din matrice care constituie drumul cel mai scurt de acas a a pn la punctul de alnire. Si cum probabil exist mai multe puncte de alnire a a nt a nt posibile, ei vor s aleag pe cel care timpul necesar pentru a ajunge la punctul a l a n de alnire este minim. nt Cerint a Scrieti un program care s determine o pozitie pe hart la care Romeo i a a s Julieta pot s ajung acelai timp. Dac exist mai multe solutii, programul a a n s a a trebuie s determine o solutie pentru care timpul este minim. a Datele de intrare Fiierul de intrare rj.in contine: s pe prima linie numerele naturale N M , care reprezint numrul de linii i a a s respectiv de coloane ale matricei, separate prin spatiu; pe ecare dintre urmtoarele N linii se a M caractere (care pot doar a a R, J, X sau spatiu) reprezentnd matricea. a Datele de ieire s Fiierul de ieire rj.out va contine o singur linie pe care sunt scrise trei s s a numere naturale separate prin cte un spatiu tmin x y, avnd semnicatia: a a x y reprezint punctul de alnire (x - numrul liniei, y - numrul coloanei); a nt a a tmin este timpul minim care Romeo (respectiv Julieta) ajunge la punctul n de alnire. nt Restrictii i precizri s a 1 < N, M < 101 Liniile i coloanele matricei sunt numerotate s ncepnd cu 1. a Pentru datele de test exist a ntotdeauna solutie. Exemple rj.in 5 8 XXR XXX X X X J X X X XX XXX XXXX rj.out 4 4 4

182

CAPITOLUL 13. ALGORITMI BFS-LEE

Explicatie: Traseul lui Romeo poate : (1,3), (2,4), (3,4), (4,4). Timpul necesar lui Romeo pentru a ajunge de acas la punctul de alnire este 4. a nt Traseul Julietei poate : (3,1), (4,2), (4,3), (4,5). Timpul necesar Julietei pentru a ajunge de acas la punctul de alnire este deasemenea 4. a nt plus 4, este punctul cel mai apropiat de ei cu aceast proprietate. In a Timp maxim de executare: 1 secund/test a Indicatii de rezolvare * Mihai Stroe, Ginfo nr. 14/4 aprilie 2004 Problema se rezolv folosind algoritmul lui Lee. a Se aplic acest algoritm folosind ca puncte de start pozitia lui Romeo i a s pozitia Julietei. Vom folosi o matrice D care vom pune valoarea 1 peste tot pe unde nu se n poate trece, valoarea 2 pozitia care se a Romeo initial, valoarea 3 pozitia n n a n care se a Julieta initial i valoarea 0 rest. n a s n La ecare pas k vom parcurge aceast matrice i pozitiile vecine celor care a s n au valoarea 2 vom pune valoarea 2, dac acestea au valoarea 0 sau 3. Dac o a a pozitie are valoare 3, nseamn c la un moment de timp anterior Julieta se putea a a aa pozitia respectiv. La acelai pas k vom mai parcurge matricea o dat i n a s as pozitiile vecine celor care au valoarea 3 vom pune valoarea 3, dac acestea au n a valoarea 0. Dac la pasul k pozitia vecin uneia care are valoarea 3, are valoarea 2, atunci a a ne vom opri i k reprezint momentul minim de timp dup care cei doi se alnesc, s a a nt iar pozitia care are valoare 2 reprezint locul alnirii. a nt La prima vedere s-ar prea c numrul k nu reprezint momentul de timp a a a a minim la care cei doi se alnesc. Vom demonstra c algoritmul este corect prin nt a metoda reducerii la absurd. Pentru aceasta avem vedere c pozitiile marcate n a cu 2 reprezint toate locurile care se poate aa Romeo dup cel mult k pai, a n a s iar cele marcate cu 2 reprezint toate locurile care se poate aa Julieta dup a n a cel mult k pai. Dac k nu reprezint momentul de timp minim la care cei doi se s a a alnesc nt nseamn c acesta a fost determinat mai devreme i algoritmul s-a oprit a a s deja. Analiza complexitii at Ordinul de complexitate al operatiei de citire a datelor de intrare este O(M N ). Ordinul de complexitate al acestui algoritm este O(kM N ), unde k reprezint a momentul care cei doi se alnesc. n nt Ordinul de complexitate al operatiei de scriere a rezultatului este O(1). concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei In probleme este O(kM N ).

13.2. PROBLEME REZOLVATE Rezolvare detaliat a

183

Codul surs * a

import java.io.*; class RJ { static final int zid=10000; static int m,n; static int[][] xr,xj; static int[] qi=new int[5000]; static int[] qj=new int[5000]; static int ic, sc;

// coada sau coada circulara mai bine ! // coada // ic=inceput coada

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j,ir=0,jr=0,ij=0,jj=0, imin, jmin, tmin; String s; BufferedReader br=new BufferedReader(new FileReader("rj.in")); StreamTokenizer st=new StreamTokenizer(br); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("rj.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; xr=new int[m+2][n+2]; xj=new int[m+2][n+2]; // matrice bordata cu zid ! // matrice bordata cu zid !

br.readLine(); // citeste CRLF !!! for(i=1;i<=m;i++) { s=br.readLine(); for(j=1;j<=n;j++) if(s.charAt(j-1)==X) xr[i][j]=xj[i][j]=zid; else if(s.charAt(j-1)==R) {ir=i; jr=j; xr[i][j]=1;}

// zid!

184

CAPITOLUL 13. ALGORITMI BFS-LEE else if(s.charAt(j-1)==J) {ij=i; jj=j; xj[i][j]=1;} } for(i=0;i<=m+1;i++) xr[i][0]=xr[i][n+1]=xj[i][0]=xj[i][n+1]=zid; // E si V for(j=0;j<=n+1;j++) xr[0][j]=xr[m+1][j]=xj[0][j]=xj[m+1][j]=zid; // N si S ic=sc=0; qi[sc]=ir; qj[sc]=jr; sc++; while(ic!=sc) { i=qi[ic]; j=qj[ic]; ic++; fill(xr,i,j); } // coada vida; // (ir,jr) --> coada

// scot din coada

ic=sc=0; // coada vida; qi[sc]=ij; qj[sc]=jj; sc++; // (ij,jj) --> coada while(ic!=sc) { i=qi[ic]; j=qj[ic]; ic++; // scot din coada fill(xj,i,j); } tmin=10000; imin=jmin=0; for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(xr[i][j]==xj[i][j]) if(xj[i][j]!=0) // pot exista pozitii ramase izolate ! if(xr[i][j]<tmin) {tmin=xr[i][j]; imin=i; jmin=j;} out.println(tmin+" "+imin+" "+jmin); out.close(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() static void fill(int[][] x,int i, int j) { int t=x[i][j]; // timp if(x[i-1][j]==0) {x[i-1][j]=t+1; qi[sc]=i-1; qj[sc]=j; sc++;} // N if(x[i-1][j+1]==0) {x[i-1][j+1]=t+1; qi[sc]=i-1; qj[sc]=j+1; sc++;} // NE if(x[i-1][j-1]==0) {x[i-1][j-1]=t+1; qi[sc]=i-1; qj[sc]=j-1; sc++;} // NV if(x[i+1][j]==0) {x[i+1][j]=t+1; qi[sc]=i+1; qj[sc]=j; sc++;} // S

13.2. PROBLEME REZOLVATE if(x[i+1][j+1]==0) if(x[i+1][j-1]==0) if(x[i][j+1]==0) if(x[i][j-1]==0) }// fil(...) }// class

185

{x[i+1][j+1]=t+1; qi[sc]=i+1; qj[sc]=j+1; sc++;} // SE {x[i+1][j-1]=t+1; qi[sc]=i+1; qj[sc]=j-1; sc++;} // SV {x[i][j+1]=t+1; {x[i][j-1]=t+1; qi[sc]=i; qi[sc]=i; qj[sc]=j+1; sc++;} // E qj[sc]=j-1; sc++;} // V

13.2.2

Sudest - OJI2006 clasa a X-a

Fermierul Ion detine un teren de form ptrat, artit N N ptrate a a a mp n a de latur unitate, pe care cultiv carto. Pentru recoltarea cartolor fermierul a a folosete un robot special proiectat acest scop. Robotul pornete din ptratul s n s a din stnga sus, de coordonate (1, 1) i trebuie s ajung ptratul din dreapta a s a a n a jos, de coordonate (N, N ). Traseul robotului este programat prin memorarea unor comenzi pe o cartel a magnetic. Fiecare comand specic directia de deplasare (sud sau est) i numrul a a a s a de ptrate pe care le parcurge directia respectiv. Robotul strnge recolta doar a n a a din ptratele care se oprete a n s ntre dou comenzi. a Din pcate, cartela pe care se a programul s-a deteriorat i unitatea de a a s citire a robotului nu mai poate distinge directia de deplasare, ci numai numrul a de pai pe care trebuie s-i fac robotul la ecare comand. Fermierul Ion trebuie s a a a s introduc manual, pentru ecare comand, directia de deplasare. a a a Cerint a Scrieti un program care s determine cantitatea maxim de carto pe care a a o poate culege robotul, ipoteza care Ion specic manual, pentru ecare n n a comand, directia urmat de robot. Se va determina i traseul pe care se obtine a a s recolta maxim. a Datele de intrare Fiierul de intrare sudest.in are urmtoarea structur: s a a Pe linia 1 se a numrul natural N , reprezentnd dimensiunea parcelei de a a a teren. Pe urmtoarele N linii se a cte N numere naturale, separate prin spatii, a a a reprezentnd cantitatea de carto din ecare ptrat unitate. a a Pe linia N + 2 se a un numr natural K reprezentnd numrul de comenzi a a a a aate pe cartela magnetic. a Pe linia N + 3 se a K numerele naturale C1 , ...,CK , separate prin spatii, a reprezentnd numrul de pai pe care trebuie s-i efectueze robotul la ecare a a s a comand. a

186

CAPITOLUL 13. ALGORITMI BFS-LEE

Datele de ieire s Fiierul de iesire sudest.out va contine pe prima linie cantitatea maxim s a de carto recoltat de robot. Pe urmtoarele K + 1 linii vor scrise, ordine, a a n coordonatele ptratelor unitate ce constituie traseul pentru care se obtine cantia tate maxim de carto, cte un ptrat unitate pe o linie. Coordonatele scrise pe a a a aceeai linie vor separate printr-un spatiu. Primul ptrat de pe traseu va avea s a coordonatele 11, iar ultimul va avea coordonatele N N . Dac sunt mai multe trasee a pe care se obtine o cantitate maxim de carto recoltat se va aa unul dintre a a s acestea. Restrictii i precizri s a 5 N 100 2K 2N 2 1 C1 , ..., CK 10 Cantitatea de carto dintr-un ptrat de teren este numr natural a a ntre 0 i s 100. Pentru ecare set de date de intrare se garanteaz c exist cel putin un a a a traseu. Se consider c robotul strnge recolta i din ptratul de plecare (1, 1) i din a a a s a s cel de sosire (N, N ). Pentru determinarea corect a cantitii maxime recoltate se acord 50% a at a din punctajul alocat testului respectiv; pentru cantitate maxim recoltat i a as traseu corect se acord 100%. a Exemplu sudest.in 6 121041 133511 2 2 1 2 1 10 453926 113201 10 2 4 6 5 10 5 22141

sudest.out 29 11 31 51 61 65 66

Explicatii Un alt traseu posibil este: 11 13 15 25 65 66 dar costul su este 1 + 1 + 4 + 1 + 5 + 10 = 22 a

Timp maxim de executie/test: 1 secund a

13.2. PROBLEME REZOLVATE Indicatii de rezolvare * solutia comisiei Reprezentarea informatiilor N - numrul de linii a K - numrul de comenzi a A[N max][N max]; - memoreaz cantitatea de produs a

187

C[N max][N max]; - C[i][j] = cantitatea maxim de carto culeas pe un a a traseu ce pornete din (1, 1) i se termin (i, j), respectnd conditiile s s a n a problemei P [N max][N max]; - P [i][j] = pasul la care am ajuns pozitia i, j culegnd n a o cantitate maxim de carto a M ove[2 N max]; - memoreaz cele K comenzi a Parcurg irul celor k mutri. La ecare mutare marchez pozitiile care pot s a n ajunge la mutarea respectiv. a Mai exact, parcurg toate pozitiile care am putut ajunge la pasul precedent n (cele marcate matricea P corespunztor cu numrul pasului precedent) i pentru n a a s ecare pozitie veric dac la pasul curent pot s execut mutarea la sud. a a caz armativ, veric dac acest caz obtin o cantitate de carto mai In a n mare dect cea obtinut pn la momentul curent (dac da, retin noua cantitate, a a a a a i marchez matricea P pozitia care am ajuns cu indicele mutrii curente). s n n a mod similar procedez pentru o mutare spre est. In Codul surs * a Variant dup solutia ocial: a a a import java.io.*; class Sudest1 { static StreamTokenizer st; static PrintWriter out; static final int Nmax=101; static int N, K; static int[][] A=new int[Nmax][Nmax]; static int[][] C=new int[Nmax][Nmax]; static int[][] P=new int[Nmax][Nmax]; static int[] Move=new int[2*Nmax];

// // // //

A[i][j]=cantitatea de cartofi C[i][j]=cantitatea maxima ... pas comenzile

188

CAPITOLUL 13. ALGORITMI BFS-LEE

public static void main(String[] args) throws IOException { st=new StreamTokenizer( new BufferedReader(new FileReader("sudest.in"))); out=new PrintWriter(new BufferedWriter(new FileWriter("sudest.out"))); read_data(); init(); solve(); }// main(...) static void read_data() throws IOException { int i,j,cmd; st.nextToken(); N=(int)st.nval; for(i=1;i<=N;i++) for(j=1;j<=N;j++) { st.nextToken(); A[i][j]=(int)st.nval;} st.nextToken(); K=(int)st.nval; for(cmd=1;cmd<=K;cmd++) { st.nextToken(); Move[cmd]=(int)st.nval;} }// read_data() static void init() { int i,j; for(i=1;i<=N;i++) for(j=1;j<=N;j++) {C[i][j]=-1; P[i][j]=255;} }// init() static boolean posibil(int x,int y) {return 1<=x && 1<=y && x<=N && y<=N;} static void solve() { int i,j, cmd; P[1][1]=0; C[1][1]=A[1][1]; for(cmd=1; cmd<=K; cmd++) for(i=1; i<=N; i++) for(j=1; j<=N; j++) if(P[i][j]==cmd-1) { if(posibil(i+Move[cmd],j)) // SUD if(C[i][j]+A[i+Move[cmd]][j]>C[i+Move[cmd]][j]) { P[i+Move[cmd]][j]=cmd; C[i+Move[cmd]][j]=C[i][j]+A[i+Move[cmd]][j]; }

13.2. PROBLEME REZOLVATE if(posibil(i,j+Move[cmd])) // EST if(C[i][j]+A[i][j+Move[cmd]]>C[i][j+Move[cmd]]) { P[i][j+Move[cmd]]=cmd; C[i][j+Move[cmd]]=C[i][j]+A[i][j+Move[cmd]]; } }// if out.println(C[N][N]); drum(N,N,K); out.close(); }// solve()

189

static void drum(int x, int y, int pas) { int i; boolean gasit; if(x==1 && y==1) out.println("1 1"); else { gasit=false; if(posibil(x,y-Move[pas])) if(C[x][y-Move[pas]]==C[x][y]-A[x][y] && P[x][y-Move[pas]]==pas-1) { drum(x,y-Move[pas],pas-1); out.println(x+" "+y); gasit=true; } if(!gasit) if(posibil(x-Move[pas],y)) if(C[x-Move[pas]][y]==C[x][y]-A[x][y] && P[x-Move[pas]][y]==pas-1) { drum(x-Move[pas],y,pas-1); out.println(x+" "+y); } }// else }// drum(...) }// class Variant folosind coad: a a import java.io.*; // 11, ...,nn --> 1,...,n*n pozitii in matrice ! class Sudest2 { static StreamTokenizer st; static PrintWriter out; static final int nmax=101; static int n, k;

190 static static static static static static

CAPITOLUL 13. ALGORITMI BFS-LEE int[][] a=new int[nmax][nmax]; // int[][] c=new int[nmax][nmax]; // int[][] p=new int[nmax][nmax]; // int[] move=new int[2*nmax]; // int ic,sc,scv,qmax=100; int[] q=new int[qmax]; // coada a[i][j]=cantitatea de cartofi c[i][j]=cantitatea maxima ... pozitia anterioara optima comenzile

public static void main(String[] args) throws IOException { st=new StreamTokenizer(new BufferedReader(new FileReader("sudest.in"))); out=new PrintWriter(new BufferedWriter(new FileWriter("sudest.out"))); read_data(); init(); solve(); }// main(...) static void read_data() throws IOException { int i,j,cmd; st.nextToken(); n=(int)st.nval; for(i=1;i<=n;i++) for(j=1;j<=n;j++) { st.nextToken(); a[i][j]=(int)st.nval;} st.nextToken(); k=(int)st.nval; for(cmd=1;cmd<=k;cmd++) { st.nextToken(); move[cmd]=(int)st.nval;} }// read_data() static void init() { int i,j; for(i=1;i<=n;i++) for(j=1;j<=n;j++) {c[i][j]=-1; p[i][j]=255;} }// init() static void solve() { int i,j,ii,jj,cmd; p[1][1]=0; c[1][1]=a[1][1]; ic=0; sc=1; q[ic]=1; // pozitia [1][1] --> q scv=1; // ultimul din coada la distanta 1 de [1][1] for(cmd=1; cmd<=k; cmd++) { while(ic!=scv) { i=(q[ic]-1)/n+1; j=(q[ic]-1)%n+1; ic=(ic+1)%qmax; // scot din coada // propag catre Sud

13.2. PROBLEME REZOLVATE ii=i+move[cmd]; jj=j; if((ii>=1)&&(ii<=n)) if(c[i][j]+a[ii][jj]>c[ii][jj]) { c[ii][jj]=c[i][j]+a[ii][jj]; p[ii][jj]=(i-1)*n+j; q[sc]=(ii-1)*n+jj; sc=(sc+1)%qmax; // pun in coada } // propag catre Est jj=j+move[cmd]; ii=i; if((jj>=1)&&(jj<=n)) if(c[i][j]+a[ii][jj]>c[ii][jj]) { c[ii][jj]=c[i][j]+a[ii][jj]; p[ii][jj]=(i-1)*n+j; q[sc]=(ii-1)*n+jj; sc=(sc+1)%qmax; // pun in coada } }// while scv=sc; }// for out.println(c[n][n]); drum(n,n); out.close(); }// solve() static void drum(int i, int j) { if(i*j==0) return; drum((p[i][j]-1)/n+1,(p[i][j]-1)%n+1); out.println(i+" "+j); }// drum(...) }// class

191

13.2.3

Muzeu - ONI2003 clasa a X-a

Sunteti un participant la Olimpiada National de Informatic. pro a a In gramul olimpiadei intr i cteva activitii de divertisment. Una dintre ele este as a at vizitarea unui muzeu. Acesta are o structur de matrice dreptunghiular cu M a a linii i N coloane; din orice camer se poate ajunge camerele vecine pe directiile s a n nord, est, sud i vest (dac aceste camere exist). Pentru pozitia (i, j) deplasarea s a a spre nord presupune trecerea pozitia (i 1, j), spre est (i, j + 1), spre sud n n n

192 (i + 1, j) i spre vest (i, j 1). s n

CAPITOLUL 13. ALGORITMI BFS-LEE

Acest muzeu are cteva reguli speciale. Fiecare camer este marcat cu un a a a numr a ntre 0 i 10 inclusiv. Mai multe camere pot marcate cu acelai numr. s s a Camerele marcate cu numrul 0 pot vizitate gratuit. a Intr-o camer marcat a a cu numrul i (i > 0) se poate intra gratuit, dar nu se poate iei din ea dect a s a dac artati supraveghetorului un bilet cu numrul i. Din fericire, orice camer a a a a cu numrul i (i > 0) ofer spre vnzare un bilet cu numrul i; o dat cumprat a a a a a a acest bilet, el este valabil toate camerele marcate cu numrul respectiv. Biletele n a pot avea preturi diferite, dar un bilet cu numrul i va avea acelai pret toate a s n camerele care este oferit spre vnzare. n a Dumneavoastr intrati muzeu prin coltul de Nord-Vest (pozitia (1, 1) a a n matricei) i doriti s ajungeti la ieirea situat coltul de Sud-Est (pozitia (M, N ) s a s a n a matricei). O dat ajuns acolo primiti un bilet gratuit care v permite s vizitati a a a tot muzeul. Pozitiile (1, 1) i (M, N ) sunt marcate cu numrul 0. s a Cerint a Cunoscndu-se structura muzeului, determinati o strategie de parcurgere a a camerelor, astfel at s ajungeti camera (M, N ) pltind ct mai putin. Dac nc a n a a a exist mai multe variante, alegeti una care este parcurs un numr minim de a n a camere (pentru a c?tiga timp i pentru a avea mai mult timp pentru vizitarea a s integral a muzeului). a Date de intrare Prima linie a ierului de intrare muzeu.in contine dou numere s a ntregi M i N , separate printr-un spatiu, numrul de linii, respectiv de coloane, al matricei s a care reprezint muzeul. Urmtoarele M linii contin structura muzeului; ecare a a contine N numere ntregi ntre 0 i 10 inclusiv, separate prin spatii. Linia M + 2 s contine 10 numere ntregi ntre 0 i 10000 inclusiv, reprezentnd costurile biletelor s a 1, 2, 3, ...10 aceast ordine. n a Date de ieire s ierul muzeu.out veti aa: In s s pe prima linie suma minim necesar pentru a ajunge din (1, 1) (M, N ); a a n pe a doua linie numrul minim de mutri L efectuate dintr-o camer a a a ntr-o camer vecin, pentru a ajunge din (1, 1) (M, N ); a a n pe a treia linie L caractere din multimea N , E, S, V reprezentnd deplasri a a spre Nord, Est, Sud sau Vest. Restrictii i precizri s a Exemplu: 2 N 50

13.2. PROBLEME REZOLVATE muzeu.in muzeu.out 56 12 000002 9 011143 EEEEESSSS 010000 015100 000100 1000 5 7 100 12 1000 1000 1000 1000 1000 Timp maxim de executare: 1 secund/test. a Indicatii de rezolvare *

193

Mihai Stroe, GInfo nr. 13/6 - octombrie 2003 Se observ c numrul variantelor de cumprare a biletelor (cumpr / nu a a a a a cumpr biletul 1, biletul 2, ..., biletul 10) este 210 = 1024. a Se genereaz ecare din aceste variante. Pentru o astfel de variant, se cala a culeaz costul i se transform matricea initial a s a a ntr-o matrice cu 0 i 1, care 0 s n reprezint o camer pentru care se pltete biletul (sau nu e nevoie de bilet) i 1 a a a s s reprezint o camer pentru care nu se cumpra bilet (deci care nu se intr). a a a n a Pentru o astfel de matrice, problema determinrii celui mai scurt drum de la a (1, 1) la (M, N ) se rezolv cu algoritmul lui Lee. a Se rezolv aceast problem pentru toate cele 1024 variante. Eventual, dac a a a a la un moment dat exist o solutie cu un cost mai bun dect al variantei curente, a a aceasta nu mai este abordat. a Evident, problema determinrii celui mai scurt drum se poate rezolva i pe a s matricea initial, innd cont la ecare pas de biletele cumprate; cum algoritmul a t a a lui Lee este folosit de obicei pentru o matrice cu 0 i 1, am folosit initial conventia s respectiv pentru a uura a s nelegerea. Se alege solutia de cost minim i, caz de egalitate, cea cu numr minim de s n a camere. Analiza complexitii at Operatia de citire a datelor are ordinul de complexitate O(M N ). Fie C numrul de numere de marcaj diferite din matrice. Pentru ecare dintre a cele 2C variante, gsirea drumului minim cu algoritmul lui Lee are complexitatea a O(M N ). Operatia de scriere a solutiei are ordinul de complexitate O(M N ) pentru cazul cel mai defavorabil. Ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O(M N 2C ). Algoritmul functioneaz datorit restrictiei impuse pentru C (cel mult 10 a a numere de marcaj). Considernd 2C o constant, ordinul de complexitate devine a a O(M N ).

194 Codul surs a

CAPITOLUL 13. ALGORITMI BFS-LEE

import java.io.*; class Muzeu { static final int qdim=200; // dimensiune coada circulara static final int sus=1, dreapta=2, jos=3, stanga=4; static static static static static static static static int m,n; // dimensiuni muzeu int ncmin; // nr camere minim int cc,cmin=50*50*10000/2; // cost curent/minim boolean amAjuns; int[][] int[][] int[][] int[][] x=new int[51][51]; // muzeu c=new int[51][51]; // costul (1,1) --> (i,j) d=new int[51][51]; // directii pentru "intoarcere" dsol=new int[51][51];

static int[] cb=new int[11]; // costuri bilete static boolean[] amBilet=new boolean[11]; static int[] qi=new int[qdim]; static int[] qj=new int[qdim]; static int ic, sc; // coada pentru i din pozitia (i,j) // coada pentru j din pozitia (i,j) // ic=inceput coada

public static void main(String[] args) throws IOException { int i,j; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("10-muzeu.in"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; for(i=1;i<=m;i++) for(j=1;j<=n;j++) { st.nextToken(); x[i][j]=(int)st.nval; } for(i=1;i<=10;i++) {st.nextToken(); cb[i]=(int)st.nval; } amBilet[0]=true; // 0 ==> gratuit for(i=0;i<=1023;i++) { bilete(i);

13.2. PROBLEME REZOLVATE cc=0; for(j=1;j<=10;j++) if(amBilet[j]) cc+=cb[j]; if(cc>cmin) continue; amAjuns=false; matriceCosturi(); if(!amAjuns) continue; if(cc>cmin) continue; if(cc<cmin) { cmin=cc; ncmin=c[m][n]; copieDirectii(); } else // costuri egale if(c[m][n]<ncmin) { ncmin=c[m][n]; copieDirectii(); } } d=dsol; // schimbare "adresa" ... ("pointer"!) ... afisSolutia(); }// main() static void bilete(int i) { int j; for(j=1;j<=10;j++) { if(i%2==1) amBilet[j]=true; else amBilet[j]=false; i/=2; } }// bilete(...)

195

static void matriceCosturi() { int i,j; for(i=1;i<=m;i++) for(j=1;j<=n;j++) c[i][j]=0; // curat "numai" traseul ! ic=sc=0; // coada vida qi[sc]=1; qj[sc]=1; sc=(sc+1)%qdim; // (1,1) --> coada circulara ! c[1][1]=1; // cost 1 pentru pozitia (1,1) (pentru marcaj!) while(ic!=sc) // coada nevida { i=qi[ic]; j=qj[ic]; ic=(ic+1)%qdim; vecini(i,j); if(amAjuns) break; } }//matriceCosturi() static void copieDirectii()

196 {

CAPITOLUL 13. ALGORITMI BFS-LEE

int i,j; for(i=1;i<=m;i++) for(j=1;j<=n;j++) dsol[i][j]=d[i][j]; }// copieDirectii() static void vecini(int i, int j) { int t=c[i][j]; // "timp" = nr camere parcurse if((i-1>=1)&&(c[i-1][j]==0)&&amBilet[x[i-1][j]]) // N { c[i-1][j]=t+1; qi[sc]=i-1; qj[sc]=j; sc=(sc+1)%qdim; d[i-1][j]=jos; if((i-1==m)&&(j==n)) {amAjuns=true; return;} } if((j+1<=n)&&(c[i][j+1]==0)&&amBilet[x[i][j+1]]) // E { c[i][j+1]=t+1; qi[sc]=i; qj[sc]=j+1; sc=(sc+1)%qdim; d[i][j+1]=stanga; if((i==m)&&(j+1==n)) {amAjuns=true; return;} } if((i+1<=m)&&(c[i+1][j]==0)&&amBilet[x[i+1][j]])// S { c[i+1][j]=t+1; qi[sc]=i+1; qj[sc]=j; sc=(sc+1)%qdim; d[i+1][j]=sus; if((i+1==m)&&(j==n)) {amAjuns=true; return;} } if((j-1>=1)&&(c[i][j-1]==0)&&amBilet[x[i][j-1]]) // V { c[i][j-1]=t+1; qi[sc]=i; qj[sc]=j-1; sc=(sc+1)%qdim; d[i][j-1]=dreapta; if((i==m)&&(j-1==n)) {amAjuns=true; return;} } }// vecini(...) static void afisSolutia() throws IOException { int i,j; PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter("muzeu.out")));

13.2. PROBLEME REZOLVATE out.println(cmin); out.println(ncmin-1);

197

i=m; j=n; // (m,n) --> (1,1) while((i!=1)||(j!=1)) // folosesc "c" care nu mai e necesara ! if(d[i][j]==sus) { c[i-1][j]=jos; i--; } else if(d[i][j]==jos) { c[i+1][j]=sus; i++;} else if(d[i][j]==dreapta) { c[i][j+1]=stanga; j++; } else if(d[i][j]==stanga) { c[i][j-1]=dreapta; j--; } else System.out.println("Eroare la traseu ... (m,n)-->(1,1)!"); i=1; j=1; // (1,1) --> (m,n) while((i!=m)||(j!=n)) { if(c[i][j]==sus) {out.print("N"); i--;} else if(c[i][j]==jos) {out.print("S"); i++;} else if(c[i][j]==dreapta) {out.print("E"); j++;} else if(c[i][j]==stanga) {out.print("V"); j--;} else System.out.println("Eroare la traseu ... (1,1)--(m,n)!"); } out.close(); }//afisSolutia() }// class

13.2.4

Pianjen ONI2005 clasa a X-a a

Un pianjen a esut o plas, care nodurile sunt dispuse sub forma unui a t a n caroiaj cu m linii (numerotate de la 0 la m 1) i n coloane (numerotate de la 0 la s n 1) ca gur. Initial, oricare dou noduri vecine (pe orizontal sau vertical) n a a a a erau unite prin segmente de plas de lungime 1. timp unele portiuni ale plasei sa In au deteriorat, devenind nesigure. Pe plas, la un moment dat, se gsesc pianjenul a a a i o musc, noduri de coordonate cunoscute. s a n

198
0 1 2 3 4 5 6

CAPITOLUL 13. ALGORITMI BFS-LEE

0 1 2 3 4 5 6 7 8

pozitie paianzen

pozitie musca

Cerint a S se determine lungimea celui mai scurt traseu pe care trebuie s-l parcurg a a a pianjenul, folosind doar portiunile sigure ale plasei, pentru a ajunge la musc. De a a asemenea, se cere un astfel de traseu. Datele de intrare Fiierul de intrare paianjen.in contine: s pe prima linie dou numere naturale m n, separate printr-un spatiu, a reprezentnd numrul de linii i respectiv numrul de coloane ale plasei; a a s a pe a doua linie dou numere naturale lp cp, separate printr-un spatu, a reprezentnd linia i respectiv coloana nodului care se a initial pianjenul; s n a a pe linia a treia dou numere naturale lm cm separate printr-un spatiu, a reprezentnd linia i respectiv coloana pe care se a initial musca; a s a pe linia a patra, un numr natural k, reprezentnd numrul de portiuni a a a de plas deteriorate; a pe ecare dintre urmtoarele k linii, cte patru valori naturale l1 c1 l2 c2, a a separate prin cte un spatiu, reprezentnd coordonatele capetelor celor k portiuni a a de plas deteriorate (linia i apoi coloana pentru ecare capt). a s a Datele de ieire s Fiierul de ieire paianjen.out va contine pe prima linie un numr natural s s a min reprezentnd lungimea drumului minim parcurs de pianjen, exprimat a a n numr de segmente de lungime 1. Pe urmtoarele min + 1 linii sunt scrise nodurile a a prin care trece pianjenul, cte un nod pe o linie. Pentru ecare nod sunt scrise a a linia i coloana pe care se a, separate printr-un spatiu. s a Restrictii i precizri s a 1 m, n 140 1 k 2 (m n m n + 1) Lungimea drumului minim este cel mult 15000 Pentru datele de test exist a ntotdeauna solutie. Dac problema are mai a multe solutii, se va aa una singur. s a

13.2. PROBLEME REZOLVATE

199

Portiunile nesigure sunt specicate ierul de intrare n s ntr-o ordine oarecare. Oricare dou portiuni nesigure orizontale se pot intersecta cel mult a ntr-un capt. De asemenea, oricare dou portiuni nesigure verticale se pot intersecta cel a a mult ntr-un capt. a Se acord 30% din punctaj pentru determinarea lungimii drumului minim a i 100% pentru rezolvarea ambelor cerinte. s Exemplu paianjen.in 97 23 74 8 2425 2333 3031 3335 4454 6465 6575 7273 paianjen.out 8 23 22 32 42 52 62 63 73 74 Explicatie Problema corespunde gurii de mai sus. Traseul optim este desenat cu linie groas, a iar portiunile nesigure sunt desenate punctat.

Timp maxim de executie/test: 1 secund pentru Windows i 0.1 secunde a s pentru Linux. Indicatii de rezolvare * prof. Carmen Popescu, C. N. Gh. Lazr Sibiu a Plasa de pianjen se memoreaz a a ntr-o matrice A cu M linii i N coloane, s ecare element reprezentnd un nod al plasei. A[i, j] va codica pe patru biti a directiile care se poate face deplasarea din punctul (i, j): bitul 0 este 1 dac n a pianjenul se poate deplasa sus, bitul 1 este 1 dac se poate deplasa la dreapta, a n a bitul 2 - jos, bitul 3 - la stnga. n a Rezolvarea se bazeaz pe parcurgerea matriciei i retinerea nodurilor parcurse a s ntr-o structur de date de tip coad (parcurgere BF - algoritm Lee). a a Drumul minim al pianjenului se retine a ntr-o alt matrice B, unde B[i, j] este a 0 dac nodul (i, j) nu este atins, respectiv o valoare pozitiv reprezentnd pasul a a a la care a ajuns paianjenul drumul lui spre musc. Deci elementul B[lm, cm] va n a contine lungimea drumului minim. Reconstituirea drumului minim se face pornind de la pozitia mutei, utiliznd, s a de asemenea un algoritm de tip BF, cu oprirea cutrii jurul nodului curent a a n n momentul detectrii unui nod de pas mai mic cu o unitate dect cel al nodului a a curent.

200

CAPITOLUL 13. ALGORITMI BFS-LEE TESTE Obs dimensiune mic, re putine rupte a solutie unic, foarte multe re rupte a caz particular, nici un r rupt re putine rupte, dimensiune mare multe re rupte, dimensiune mare traseu spiral solutie unic n a a traseu scurt, greu de gsit cu backtracking a multe re rupte, drum serpentine n rele interioare rupte, traseul pe frontier a o barier pe mijlocul tablei, cu o fant a a

# 0 1 2 3 4 5 6 7 8 9

m 10 10 2 140 131 100 100 138 140 138

n 8 8 2 140 131 130 7 138 140 138

k 11 46 0 20 2000 12771 23 9381 38365 273

min 16 74 2 278 103 12999 15 9050 555 274

O solutie backtracking simplu obtine maxim 20 de puncte, iar mbuntit a at maxim 30 de puncte. Codul surs a

import java.io.*; // test 3 eroare date lm=144 ??? class Paianjen4 // coada circulara (altfel trebuie dimensiune mare !) { // traseu fara recursivitate (altfel depaseste stiva !) static final int qdim=200; // dimensiune coada circulara static final int sus=1, dreapta=2, jos=4, stanga=8; static int[][] p=new int[140][140]; // plasa static int[][] c=new int[140][140]; // costul ajungerii in (i,j) static int[][] d=new int[140][140]; // directii pentru intoarcere de la musca! static static static static static static int m,n; // dimensiunile plasei int lp,cp; // pozitie paianjen(lin,col) int lm,cm; // pozitie musca(lin,col) int[] qi=new int[qdim]; // coada pentru i din pozitia (i,j) int[] qj=new int[qdim]; // coada pentru j din pozitia (i,j) int ic, sc; // inceput/sfarsit coada

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); citescDate(); matriceCosturi(); afisSolutia(); t2=System.currentTimeMillis();

13.2. PROBLEME REZOLVATE System.out.println("TIME = "+(t2-t1)+" millisec "); }// main() static void citescDate() throws IOException { int nrSegmenteDeteriorate, k,i,j,l1,c1,l2,c2; int i1,i2,j1,j2; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("paianjen.in"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; st.nextToken(); lp=(int)st.nval; st.nextToken(); cp=(int)st.nval; st.nextToken(); lm=(int)st.nval; st.nextToken(); cm=(int)st.nval; st.nextToken(); nrSegmenteDeteriorate=(int)st.nval; for(i=0;i<m;i++) for(j=0;j<n;j++) p[i][j]=0xF; for(k=1;k<=nrSegmenteDeteriorate;k++) { st.nextToken();l1=(int)st.nval; st.nextToken();c1=(int)st.nval; st.nextToken();l2=(int)st.nval; st.nextToken();c2=(int)st.nval; i1=min(l1,l2); i2=max(l1,l2); j1=min(c1,c2); j2=max(c1,c2);

201

// 1111=toate firele !

if(j1==j2) // ruptura verticala { p[i1][j1]^=jos; // sau ... p[i1][j1]-=jos; ... !!! for(i=i1+1;i<=i2-1;i++) { p[i][j1]^=jos; // 0 pe directia jos p[i][j1]^=sus; // 0 pe directia sus } p[i2][j1]^=sus; } else if(i1==i2) // ruptura orizontala { p[i1][j1]^=dreapta; // 0 pe directia dreapta for(j=j1+1;j<=j2-1;j++)

202 { p[i1][j]^=dreapta; p[i1][j]^=stanga;

CAPITOLUL 13. ALGORITMI BFS-LEE

// 0 pe directia stanga } else System.out.println("Date de intrare ... eronate !"); }// for k }//citescDate() static void matriceCosturi() { int i,j; ic=sc=0; // coada vida qi[sc]=lp; qj[sc]=cp; sc=(sc+1)%qdim; // (lp,cp) --> coada ! c[lp][cp]=1; // cost 1 pentru pozitie paianjen (pentru marcaj!) while(ic!=sc) // coada nevida { i=qi[ic]; j=qj[ic]; ic=(ic+1)%qdim; fill(i,j); if(c[lm][cm]!=0) break; // a ajuns deja la musca ! }// while }//matriceCosturi() static void fill(int i, int j) { int t=c[i][j]; // timp ! if((i-1>=0)&&(c[i-1][j]==0)&&ok(i,j,sus)) { c[i-1][j]=t+1; qi[sc]=i-1; qj[sc]=j; sc=(sc+1)%qdim; d[i-1][j]=jos; } // N

} p[i1][j2]^=stanga;

if((j+1<=n-1)&&(c[i][j+1]==0)&&ok(i,j,dreapta)) // E { c[i][j+1]=t+1; qi[sc]=i; qj[sc]=j+1; sc=(sc+1)%qdim; d[i][j+1]=stanga;

13.2. PROBLEME REZOLVATE } if((i+1<=m-1)&&(c[i+1][j]==0)&&ok(i,j,jos)) // S { c[i+1][j]=t+1; qi[sc]=i+1; qj[sc]=j; sc=(sc+1)%qdim; d[i+1][j]=sus; } if((j-1>=0)&&(c[i][j-1]==0)&&ok(i,j,stanga)) // V { c[i][j-1]=t+1; qi[sc]=i; qj[sc]=j-1; sc=(sc+1)%qdim; d[i][j-1]=dreapta; } }// fill(...) static boolean ok(int i, int j, int dir) { if((p[i][j]&dir)!=0) return true; else return false; }// ok(...) static int min(int a, int b) { if(a<b) return a; else return b; } static int max(int a, int b) { if(a>b) return a; else return b; } static void afisSolutia() throws IOException { int i,j; PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("paianjen.out"))); out.println(c[lm][cm]-1); i=lm;

203

204

CAPITOLUL 13. ALGORITMI BFS-LEE j=cm; while((i!=lp)||(j!=cp)) // folosesc matricea if(d[i][j]==sus) { c[i-1][j]=jos; i--; } else if(d[i][j]==jos) { c[i+1][j]=sus; i++;} else if(d[i][j]==dreapta) { c[i][j+1]=stanga; else if(d[i][j]==stanga) { c[i][j-1]=dreapta; else System.out.println("Eroare la traseu ...

c care nu mai e necesara !

j++; } j--; } !");

i=lp; j=cp; while((i!=lm)||(j!=cm)) { out.println(i+" "+j); if(c[i][j]==sus) i--; else if(c[i][j]==jos) i++; else if(c[i][j]==dreapta) j++; else if(c[i][j]==stanga) j--; else System.out.println("Eroare la traseu ... !"); } out.println(i+" "+j); // pozitia pentru musca ! out.close(); }//afisSolutia() }// class

13.2.5

Algoritmul Edmonds-Karp

import java.io.*; class FluxMaxim { static final int WHITE=0, GRAY=1, BLACK=2; static final int MAX_NODES=10; static final int oo=0x7fffffff; static int n, m; // nr noduri, nr arce static int[][] c=new int[MAX_NODES+1][MAX_NODES+1]; // capacitati static int[][] f=new int [MAX_NODES+1][MAX_NODES+1]; // flux static int[] color=new int[MAX_NODES+1]; // pentru bfs static int[] p=new int[MAX_NODES+1]; // predecesor (ptr. drum crestere) static int ic, sc; // inceput coada, sfarsit coada static int[] q=new int[MAX_NODES+2]; // coada public static void main(String[] args) throws IOException { int s,t,i,j,k,fluxm; // fluxm=flux_maxim StreamTokenizer st=new StreamTokenizer(

13.2. PROBLEME REZOLVATE

205

new BufferedReader(new FileReader("fluxMaxim.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("fluxMaxim.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; st.nextToken(); s=(int)st.nval; st.nextToken(); t=(int)st.nval; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); c[i][j]=(int)st.nval; } fluxm=fluxMax(s,t); System.out.println("\nfluxMax("+s+","+t+") = "+fluxm+" :"); for(i=1;i<=n;i++) { for(j=1;j<=n;j++) System.out.print(maxim(f[i][j],0)+"\t"); System.out.println(); } out.print(fluxm); out.close(); }// main() static int fluxMax(int s, int t) { int i, j, u, min, maxf = 0; for(i=1; i<=n; i++) for(j=1; j<=n; j++) f[i][j]=0; // Cat timp exista drum de crestere a fluxului (in graful rezidual), // mareste fluxul pe drumul gasit while(bfs(s,t)) { // Determina cantitatea cu care se mareste fluxul min=oo; for(u=t; p[u]!=-1; u=p[u]) min=minim(min,c[p[u]][u]-f[p[u]][u]); // Mareste fluxul pe drumul gasit for(u=t; p[u]!=-1; u=p[u]) { f[p[u]][u]+=min; f[u][p[u]]-=min; // sau f[u][p[u]]=-f[p[u]][u];

206 }

CAPITOLUL 13. ALGORITMI BFS-LEE

maxf += min; System.out.print("drum : "); drum(t); System.out.println(" min="+min+" maxf="+maxf+"\n"); }// while(...) // Nu mai exista drum de crestere a fluxului ==> Gata !!! System.out.println("Nu mai exista drum de crestere a fluxului !!!"); return maxf; }// fluxMax(...) static boolean bfs(int s, int t) // s=sursa t=destinatie { // System.out.println("bfs "+s+" "+t+" flux curent :"); // afism(f); int u, v; boolean gasitt=false; for(u=1; u<=n; u++) { color[u]=WHITE; p[u]=-1; } ic=sc=0; // coada vida incoada(s); p[s]=-1; while(ic!=sc) { u=dincoada(); // Cauta nodurile albe v adiacente cu nodul u si pune v in coada // cand capacitatea reziduala a arcului (u,v) este pozitiva for(v=1; v<=n; v++) if(color[v]==WHITE && ((c[u][v]-f[u][v])>0)) { incoada(v); p[v]=u; if(v==t) { gasitt=true; break;} } if(gasitt) break; }//while return gasitt; }// bfs(...)

13.2. PROBLEME REZOLVATE

207

static void drum(int u) { if(p[u]!=-1) drum(p[u]); System.out.print(u+" "); }// drum(...) static void afism(int[][] a) { int i,j; for(i=1;i<=n;i++) { for(j=1;j<=n;j++) System.out.print(a[i][j]+"\t"); System.out.println(); } // System.out.println(); }// afism(...) static int minim(int x, int y) { return (x<y) ? x : y; } static int maxim(int x, int y) { return (x>y) ? x : y; } static void incoada(int u) { q[sc++]=u; color[u]=GRAY; } static int dincoada() { int u=q[ic++]; color[u]=BLACK; return u; } }// class /* 6 10 1 6 1 2 16 1 3 13 2 3 4 2 4 12 3 2 10 3 5 14

drum : 1 2 4 6 min=12 maxf=12 drum : 1 3 5 6 min= 4 maxf=16 drum : 1 3 5 4 6 min= 7 maxf=23 Nu mai exista drum de crestere a fluxului !!! fluxMax(1,6) = 23 : 0 12 11 0 0 0 0 0 0 12 0 0

208 4 3 9 4 6 20 5 4 7 5 6 4 */ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 0

CAPITOLUL 13. ALGORITMI BFS-LEE 11 0 0 0 0 19 4 0

13.2.6

Cuplaj maxim

/* Culori - Descrierea problemei: Doi elfi au pus pe o masa n patratele si m cerculete. Unul a ales patratelele si celalalt cerculetele si au desenat pe ele mai multe benzi colorate. Apoi au inceput sa se joace cu patratelele si cerculetele. Au decis ca un cerculet poate fi amplasat pe un patratel daca exista cel putin o culoare care apare pe ambele. Ei doresc sa formeze perechi din care fac parte un cerculet si un patratel astfel incat sa se obtina cat mai multe perechi. Date de intrare: Fisierul de intrare de intrare contine pe prima linie numarul n al patratelelor. Pe fiecare dintre urmatoarele n linii sunt descrise benzile corespunzatoare unui patratel. Primul numar de pe o astfel de linie este numarul b al benzilor, iar urmatoarele b numere reprezinta codurile celor b culori. Urmatoarea linie contine numarul m al cerculetelor. Pe fiecare dintre urmatoarele m linii sunt descrise benzile corespunzatoare unui cerculet. Primul numar de pe o astfel de linie este numarul b al benzilor, iar urmatoarele b numere reprezinta codurile celor b culori. Numerele de pe o linie vor fi separate prin cte un spatiu. Patratelele si cerculetele vor fi descrise in ordinea data de numarul lor de ordine. Date de iesire: Fisierul de iesire trebuie sa contina pe prima linie numarul k al perechilor formate. Fiecare dintre urmatoarele k va contine cate doua numere, separate printr-un spatiu, reprezentand numerele de ordine ale unui patratel, respectiv cerc, care formeaza o pereche. Restrictii si precizari: numarul patratelelor este cuprins intre 1 si 100; numarul cerculetelor este cuprins intre 1 si 100; patratelele sunt identificate prin numere cuprinse intre 1 si n; cerculetele sunt identificate prin numere cuprinse intre 1 si m; numarul benzilor colorate de pe cerculete si patratele este cuprins intre 1 si 10; un patratel sau un cerc nu poate face parte din mai mult decat o pereche; daca exista mai multe solutii trebuie determinata doar una dintre acestea. Exemplu INPUT.TXT OUTPUT.TXT 3 2 1 1 1 1 1 . 1 \ 1 2 3 2 / . \

13.2. PROBLEME REZOLVATE 1 4 2 1 2 1 3 1 2 3 3 4 4 . 2 . . 3 . . 4 Timp de executie: 0,5 s=0 - 2 \ 3 --- n+m+1=t / / / / / / secunde/test

209

*/

import java.io.*; // u=0 ==> v=1,2,...,n class CuplajMaximCulori // u=1,..,n ==> v=n+1,..,n+m { // u=n+1,..,n+m ==> v=1,2,.,n sau n+m+1(=t) static final int WHITE=0, GRAY=1, BLACK=2; static final int oo=0x7fffffff; static int n, m, ic, sc; static int[][] c, f; // capacitati, flux static boolean[][] cp, cc; // cp = culoare patratel, cc = culoare cerc static int[] color, p, q; // predecesor, coada public static void main(String[] args) throws IOException { citire(); capacitati(); scrie(fluxMax(0,n+m+1)); }// main() static void citire() throws IOException { int i,j,k,nc; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("CuplajMaximCulori.in"))); st.nextToken(); n=(int)st.nval; cp=new boolean[n+1][11]; for(i=1;i<=n;i++) { st.nextToken(); nc=(int)st.nval; for(k=1;k<=nc;k++) { st.nextToken(); j=(int)st.nval; cp[i][j]=true; } } st.nextToken(); m=(int)st.nval; cc=new boolean[m+1][11]; for(i=1;i<=m;i++) { st.nextToken(); nc=(int)st.nval; for(k=1;k<=nc;k++)

210 {

CAPITOLUL 13. ALGORITMI BFS-LEE

st.nextToken(); j=(int)st.nval; cc[i][j]=true; } } }// citire() static void capacitati() { int i,ic,j,jc; c=new int[n+m+2][n+m+2]; for(i=1;i<=n;i++) { c[0][i]=1; for(ic=1;ic<=10;ic++) if(cp[i][ic]) for(j=1;j<=m;j++) if(cc[j][ic]) c[i][j+n]=1; } for(j=1;j<=m;j++) c[j+n][n+m+1]=1; }// capacitati() static int fluxMax(int s, int t) { int i,j,u,min,maxf=0; f=new int[n+m+2][n+m+2]; p=new int[n+m+2]; q=new int[n+m+2]; color=new int[n+m+2]; for(i=0;i<=n+m+1;i++) for(j=0;j<=n+m+1;j++) f[i][j]=0; while(bfs(s,t)) { min=oo; for(u=t;p[u]!=-1;u=p[u]) min=minim(min,c[p[u]][u]-f[p[u]][u]); for(u=t;p[u]!=-1;u=p[u]) { f[p[u]][u]+=min; f[u][p[u]]-=min; // sau f[u][p[u]]=-f[p[u]][u]; }

13.2. PROBLEME REZOLVATE maxf+=min; }// while(...) return maxf; }// fluxMax(...) static boolean bfs(int s, int t) { int u, v; boolean gasitt=false; for(u=0;u<=n+m+1;u++) {color[u]=WHITE; p[u]=-1;} ic=sc=0; q[sc++]=s; color[s]=GRAY; // s --> coada p[s]=-1; while(ic!=sc) { u=q[ic++]; color[u]=BLACK; if(u==0) { for(v=1;v<=n;v++) if((color[v]==WHITE)&&((c[u][v]-f[u][v])>0)) { q[sc++]=v; color[v]=GRAY; // incoada(v); p[v]=u; } } else if(u<=n) { for(v=n+1;v<=n+m;v++) if((color[v]==WHITE)&&((c[u][v]-f[u][v])>0)) { q[sc++]=v; color[v]=GRAY; // incoada(v); p[v]=u; } } else { for(v=n+m+1;v>=1;v--) if((color[v]==WHITE)&&((c[u][v]-f[u][v])>0)) { q[sc++]=v; color[v]=GRAY; // incoada(v); p[v]=u; if(v==t) {gasitt=true; break;} }

211

212

CAPITOLUL 13. ALGORITMI BFS-LEE if(gasitt) break; // din while ! } }// while()

return gasitt; }// bfs()

static int minim(int x, int y) { return (x<y) ? x : y; } static int maxim(int x, int y) { return (x>y) ? x : y; } static void scrie(int fluxm) throws IOException { int i,j; PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("CuplajMaximCulori.out"))); out.println(fluxm); for (i=1;i<=n;i++) if(f[0][i]>0) for(j=1;j<=m;j++) if(f[i][j+n]>0) { out.println(i+" "+j); break; } out.close(); }// scrie(...) }// class

Capitolul 14

Metoda optimului local greedy


14.1 Metoda greedy

Metoda Greedy are vedere rezolvarea unor probleme de optim care n n optimul global se determin din estimri succesive ale optimului local. a a Metoda Greedy se aplic urmtorului tip de problem: dintr-o multime de a a a elemente C (candidati la construirea solutiei problemei), se cere s se determine a o submultime S, (solutia problemei) care ndeplinete anumite conditii. Deoarece s este posibil s existe mai multe solutii se va alege solutia care maximizeaz sau a a minimizeaz o anumit functie obiectiv. a a O problem poate rezolvat prin tehnica (metoda) Greedy dac a a a ndeplinete s proprietatea: dac S este o solutie, iar S este inclus S, atunci i S este o solutie. a a n s Pornind de la aceast conditie, initial se presupune c S este multimea vid a a a i se adaug succesiv elemente din C S, ajungnd la un optim local. Succesiunea s a n a de optimuri locale nu asigur, general, optimul global. Dac se demonstreaz c a n a a a succesiunea de optimuri locale conduce la optimul global, atunci metoda Greedy este aplicabil cu succes. a Exist urmtoarele variante ale metodei Greedy: a a 1. Se pleac de la solutia vid pentru multimea S i se ia pe rnd cte un a a s a a element din multimea C. Dac elementul ales a ndeplinete conditia de optim s local, el este introdus multimea S. n 2. Se ordoneaz elementele multimii C i se veric dac un element a s a a ndeplinete s conditia de apartenent la multimea S. a 213

214

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

14.2

Algoritmi greedy

Algoritmii greedy sunt general simpli i sunt folositi la rezolvarea unor n s probleme de optimizare. cele mai multe situatii de acest fel avem: In o multime de candidati (lucrri de executat, vrfuri ale grafului, etc.) a a o functie care veric dac o anumit multime de candidati constituie o a a a solutie posibil, nu neaprat optim, a problemei a a a o functie care veric dac o multime de candidati este fezabil, adic dac a a a a a este posibil s completm aceast multime astfel at s obtinem o solutie a a a nc a posibil, nu neaprat optim, a problemei a a a o functie de selectie care indic la orice moment care este cel mai promittor a a dintre candidatii a nefolositi nc o functie obiectiv care d valoarea unei solutii (timpul necesar executrii a a tuturor lucrrilor a ntr-o anumit ordine, lungimea drumului pe care l-am a gsit, etc) i pe care urmrim s o optimizm (minimizm/maximizm) a s a a a a a Pentru a rezolva problema de optimizare, cutm o solutie posibil care s a a a a optimizeze valoarea functiei obiectiv. Un algoritm greedy construiete solutia pas cu pas. s Initial, multimea candidatilor selectati este vid. a La ecare pas, ncercm s adugm la aceast multime pe cel mai promitator a a a a a candidat, conform functiei de selectie. Dac, dup o astfel de adugare, multimea a a a de candidati selectati nu mai este fezabil, eliminm ultimul candidat adugat; a a a acesta nu va mai niciodat considerat. Dac, dup adugare, multimea de a a a a candidati selectati este fezabil, ultimul candidat adugat va rmne de acum a a a a ncolo ea. De ecare dat cnd lrgim multimea candidatilor selectati, vericm n a a a a dac aceast multime constituie o solutie posibil a problemei. Dac algoritmul a a a a greedy functioneaz corect, prima solutie gsit va considerat solutie optim a a a a a a problemei. Solutia optim nu este mod necesar unic: se poate ca functia obiectiv s a n a a aib aceeai valoare optim pentru mai multe solutii posibile. a s a Descrierea formal a unui algoritm greedy general este: a function greedy(C) // C este multimea candidatilor S // S este multimea care construim solutia n while not solutie(S) and C = do x un element din C care maximizeaz/minimizeaz select(x) a a C C {x} if f ezabil(S {x}) then S S {x} if solutie(S) then return S else return nu exist solutie a

14.3. EXEMPLE

215

14.3

Exemple

Dintre problemele clasice care se pot rezolva prin metoda greedy mentionm: a plata restului cu numr minim de monezi, problema rucsacului, sortare prin selectie, a determinarea celor mai scurte drumuri care pleac din acelai punct (algoritmul lui a s Dijkstra), determinarea arborelui de cost minim (algoritmii lui Prim i Kruskal), s determinarea multimii dominante, problema colorrii intervalelor, codicarea Hu a man, etc.

14.3.1

Problema continu a rucsacului a

Se consider n obiecte. Obiectul i are greutatea gi i valoarea vi (1 i n). a s O persoan are un rucsac. Fie G greutatea maxim suportat de rucsac. Persoana a a a cauz dorete s pun rucsac obiecte astfel at valoarea celor din rucsac s n a s a a n nc a e ct mai mare. Se permite fractionarea obiectelor (valoarea prtii din obiectul a a fractionat ind direct proportional cu greutatea ei!). a Notm cu xi [0, 1] partea din obiectul i care a fost pus rucsac. Practic, a a n trebuie s maximizm functia a a
n

f (x) =
i=1

xi ci .

Pentru rezolvare vom folosi metoda greedy. O modalitate de a ajunge la solutia optim este de a considera obiectele ordinea descresctoare a valorilor a n a i utilitilor lor date de raportul vi (i = 1, ..., n) i de a le arca at s nc ntregi rucsac n g pn cnd acesta se umple. Din aceast cauz presupunem continuare c a a a a a n a v2 vn v1 ... . g1 g2 gn Vectorul x = (x1 , x2 , ..., xn ) se numete solutie posibil dac s a a xi [0, 1], i = 1, 2, ..., n n i=1 xi gi G iar o solutie posibil este solutie optim dac maximizeaz functia f . a a a a Vom nota Gp greutatea permis de a se arca rucsac la un moment dat. a nc n Conform strategiei greedy, procedura de rezolvare a problemei este urmtoarea: a procedure rucsac() Gp G for i = 1, n if Gp > gi then Gp Gp gi

216

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY else Gp xi gi for j = i + 1, n xj 0 Vectorul x are forma x = (1, ..., 1, xi , 0, ..., 0) cu xi (0, 1] i 1 i n. s

Propozitia 1 Procedura rucsac() furnizeaz o solutie optim. a a Demonstratia se gsete, de exemplu, [25] la pagina 226. a s n

14.3.2

Problema plasrii textelor pe o band a a

S presupunem c trebuie s plasm n texte T1 , T2 , ..., Tn , de lungimi date a a a a L1 , L2 , ..., Ln , pe o singur band sucient de lung. Atunci cnd este necesar a a a a a citirea unui text sunt citite toate textele situate naintea lui pe band. a Modalitatea de plasare a celor n texte pe band corespunde unei permutri p a a a multimii {1, 2, ..., n}, textele ind aezate pe band ordinea Tp(1) Tp(2) ...Tp(n) . s a n Intr-o astfel de aranjare a textelor pe band, timpul mediu de citire a unui a text este: k n 1 f (p) = Lp(i) n i=1
k=1

Se dorete determinarea unei permutri care s asigure o valoare minim a s a a a timpului mediu de citire. Rezolvarea problemei este foarte simpl: a se sorteaz cresctor textele functie de lungimea lor i a a n s se plaseaz pe band ordinea dat de sortare. a a n a Urmtoarea propozitie ([25] pagina 99) ne permite s m siguri c acest a a a n mod ajungem la o plasare optim. a Propozitia 2 Dac L1 L2 ... Ln atunci plasarea textelor corespunztoare a a permutarii identice este optim. a Demonstratie: Fie p = (p1 , p2 , ..., pn ) o plasare optim. Dac exist i < j cu a a a Lp(i) Lp(j) atunci considerm permutarea p obtinut din p prin permutarea a a elementelor de pe pozitiile i i j. Atunci s f (p ) f (p) = (n j + 1)(Lp(i) Lp(j) ) + (n i + 1)(Lp(j) Lp(i) ) deci Cum p este o plasare optim i f (p ) f (p) rezult c f (p ) = f (p) deci i p as a a s este o plasare optim. Aplicnd de un numr nit de ori acest rationament, trecem a a a de la o permutare optim la alt permutare optim pn ajungem la permutarea a a a a a identic. Rezult c permutarea identic corespunde unei plasri optime. a a a a a f (p ) f (p) = (Lp(j) Lp(i) )(j i) 0.

14.3. EXEMPLE

217

14.3.3

Problema plasrii textelor pe m benzi a

S presupunem c trebuie s plasm n texte T1 , T2 , ..., Tn , de lungimi date a a a a L1 , L2 , ..., Ln , pe m benzi sucient de lungi. Atunci cnd este necesar citirea unui a a text sunt citite toate textele situate naintea lui pe banda respectiv. a Se dorete determinarea unei plasri a celor n texte pe cele m benzi care s s a a asigure o valoare minim a timpului mediu de citire. Rezolvarea problemei este a foarte simpl: a se sorteaz cresctor textele functie de lungimea lor i a a n s

plasarea textelor pe benzi se face ordinea dat de sortare, n a ncepnd cu primul text (cu cea mai mic lungime) care se a a plaseaz pe prima band, iar mai departe a a ecare text se plaseaz pe banda urmtoare celei pe care a a a fost plasat textul anterior. Presupunnd c L1 L2 ... Ln , se poate observa c textul Ti va plasat a a a n a a a pe banda i i1 m continuarea celor aate deja pe aceast band iar dup m textul Ti , pe aceeai band cu el, vor plasate textele i + m, i + 2m, ..., deci a s a nc ni texte (demonstratiile se pot consulta, de exemplu, [25]). n m

14.3.4

Maximizarea unei sume de produse

Se dau multimile de numere ntregi A = {a1 , a2 , ..., an } i B = {b1 , b2 , ..., bm }, s unde n m. S se determine o submultime B = {x1 , x2 , ..., xn } a lui B astfel a at valoarea expresiei E = a1 x1 + a2 x2 + an xn s e maxim. nc a a Vom sorta cresctor elementele celor dou multimi. Putem presupune acum a a c a1 a2 ... an i b1 b2 ... bm . a s Dac toate elementele din A sunt pozitive vom lua xn = bm , xn1 = bm1 , a i aa mai departe. s s Dac toate elementele din A sunt negative vom lua x1 = b1 , x2 = b2 , i aa a s s mai departe. Dac primele n1 elemente din A sunt strict negative i ultimele n2 elemente a s din A sunt pozitive sau nule (n1 + n2 = n) atunci vom lua x1 = b1 , x2 = b2 , ..., s xn1 = bn1 i xn = bm , xn1 = bm1 , ..., xnn2 +1 = bmn2 +1 .

14.3.5

Problema statiilor

Se consider o multime de numere naturale A = {a1 , a2 , ..., an } care reprezint a a coordonatele a n statii pe axa real. Vom presupune a1 < a2 < ... < an . S se a a

218

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

determine o submultime cu numr maxim de statii cu proprietatea c distanta a a dintre dou statii alturate este cel putin d (o distant dat). a a a a Rezolvarea problemei este urmtoarea: a se alege statia 1,

se parcurge irul statiilor i se alege prima statie alnit care este s s nt a la distant cel putin d fat de statia aleas anterior; se repet acest a a a a pas pn cnd s-au vericat toate statiile. a a a Propozitia 3 Exist o solutie optim care contine statia 1. a a Demonstratie: Fie B = {ai1 , ai2 , ..., aim } o solutie a problemei care nu contine nlocui cu statia statia 1 (deci ai1 > a1 ). Evident |ai2 ai1 | d. Statia i1 se poate 1 pentru c |ai2 a1 | = |ai2 ai1 + ai1 a1 | = |ai2 ai1 | + |ai1 a1 | > d. a Dup selectarea statie 1 se pot selecta (pentru obtinerea unei solutii optime) a numai statii situate la distante cel putin d fat de statia 1. Pentru aceste statii a repetm strategia sugerat de propozitia anterioar. a a a

14.3.6

Problema cutiilor

Se dorete obtinerea unei conguratii de n numere plasate pe n + 1 pozitii (o s pozitie ind liber) dintr-o conguratie initial dat care exist o pozitie liber a a a n a a i cele n numere plasate alt ordine. O mutare se poate face dintr-o anumit s n a a pozitie numai pozitia liber. n a Prezentm algoritmul de rezolvare pe baza urmtorului exemplu: a a 1 2 3 4 5 6 7 initial 3 1 2 6 5 4 nal 1 2 3 4 5 6 rezolvat Vom proceda astfel: cutia goal din conguratia initial se a pe pozitia 3 a a a dar pe aceast pozitie, conguratia nal, se a numrul 3; cutm numrul 3 a n a a a a a a din conguratia initial ( gsim pe pozitia 1) i mutm pe pozitia cutiei goale; a l a s l a acum, cutia goal se a pe pozitia 1; vom repeta acest rationament pn cnd a a a a a pozitiile cutiilor goale, cele dou conguratii, coincid. n a 1 2 3 4 5 6 7 modicat 1 3 2 6 5 4 nal 1 2 3 4 5 6 rezolvat 1 2 3 4 5 6 7 modicat 1 3 2 6 5 4 nal 1 2 3 4 5 6 rezolvat

14.3. EXEMPLE 1 2 modicat 1 2 nal 1 2 rezolvat Acum vom cuta o cutie a cutia goal. a 1 2 modicat 1 2 nal 1 2 rezolvat 3 4 5 3 6 3 4 nerezolvat a 3 3 3 4 6 5 4

219 6 7 5 4 5 6 i vom muta numrul din acea cutie s a n 6 5 5 7 4 6

Repetm rationamentul prezentat la a nceput i vom continua pn cnd toate s a a a numerele ajung pe pozitiile din conguratia nal. a 1 2 3 4 5 6 7 modicat 1 2 3 6 4 5 nal 1 2 3 4 5 6 rezolvat 1 2 3 4 5 6 7 modicat 1 2 3 4 5 6 nal 1 2 3 4 5 6 rezolvat

14.3.7

Problema subirurilor s

S se descompun un ir de n numere a a s ntregi subiruri strict cresctoare n s a astfel at numerele lor de ordine din irul initial s e ordonate cresctor nc s a a n subirurile formate i numrul subirurilor s e minim. s s a s a Metota de rezolvare este urmtoarea: se parcurg elementele irului initial unul a s dup altul i pentru ecare element xi se caut un subir existent la care xi se a s a s poate aduga la sfrit; dac nu exist un astfel de subir se creeaz un subir nou a as a a s a s care va contine ca prim element pe xi ; dac exist mai multe subiruri la care se a a s poate aduga xi , se va alege acela care are cel mai mare ultim element. a Observatie: Ultimele elemente din ecare subir (care sunt i elemente maxime s s din aceste subiruri) formeaz un ir descresctor. Acest fapt permite utilizarea s a s a cutrii binare pentru determinarea subirului potrivit pentru elementul xi a a s atunci cnd prelucrm acest element. a a

14.3.8

Problema intervalelor disjuncte

Se consider n intervale a nchise pe axa real [a1 , b1 ], [a2 , b2 ], ..., [an , bn ]. Se a cere selectarea unor intervale disjuncte astfel at numrul acestora s e maxim. nc a a Metoda de rezolvare este urmtoarea: se sorteaz intervalele cresctor dup a a a a captul din dreapta; se selecteaz primul interval; se parcurg intervalele, unul a a

220

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

dup altul, pn se gsete un interval [ai , bi ] disjunct cu ultimul interval ales i a a a a s s se adaug acest interval la solutie, el devenind astfel ultimul interval ales; acest a procedeu continu pn cnd nu mai rmn intervale de analizat. a a a a a a Propozitia 4 Exist o solutie optim care contine primul interval dup sortare. a a a

14.3.9

Problema alegerii taxelor

Se dau dou iruri cu cte 2n numere as a ntregi ecare, x = (x1 , x2 , ..., x2n ) i s y = (y1 , y2 , ..., y2n ) reprezentnd taxe. S se determine irul z = (z1 , z2 , ..., z2n ), a a s unde zi {xi , yi } (1 i 2n), astfel at suma tuturor elementelor din irul z nc s s e minim i acest ir s contin exact n elemente din irul x i n elemente din a as s a a s s irul y. s Metoda de rezolvare este urmtoarea: se construiete irul t = x y ( care a s s n ti = xi yi ) i se sorteaz cresctor; se aleg din irul x elementele corespunztoare s a a s a primelor n elemente din irul t sortat iar celelalte n elemente se iau din irul y. s s

14.3.10

Problema acoperirii intervalelor

Se consider n intervale a nchise [a1 , b1 ], [a2 , b2 ], ..., [an , bn ]. S se determine o a multime cu numr minim de alemente C = {c1 , c2 , ..., cm } care s acopere toate a a cele n intervale date (spunem c ci acoper intervalul [ak , bk ] dac ci [ak , bk ]). a a a Metoda de rezolvare este urmtoarea: se sorteaz intervalele cresctor dup a a a a captul din dreapta. Presupunem c b1 b2 ... bn . Primul punct ales este a a c1 = b1 . Parcurgem intervalele pn cand gsim un interval [ai , bi ] cu ai > c1 i a a a s alegem c2 = bi . Parcurgem mai departe intervalele pn cand gsim un interval a a a [aj , bj ] cu aj > c2 i alegem c3 = bj . Repetm acest procedeu pn cnd terminm s a a a a a de parcurs toate intervalele.

14.3.11

Algoritmul lui Prim

Determinarea arborelui minim de acoperire pentru un graf neorientat. Alg prim de ordinul O(n3 ) import java.io.*; // arbore minim de acoperire: algoritmul lui Prim class Prim // O(n^3) { static final int oo=0x7fffffff; static int n,m; static int[][] cost; static boolean[] esteInArbore;

14.3. EXEMPLE static int[] p; // predecesor in arbore

221

public static void main(String[]args) throws IOException { int nods=3; // nod start int i, j, k, costArbore=0,min,imin=0,jmin=0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("prim.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("prim.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; cost=new int[n+1][n+1]; esteInArbore=new boolean [n+1]; p=new int[n+1]; for(i=1;i<=n;i++) for(k=1;k<=m;k++) { st.nextToken(); st.nextToken(); st.nextToken(); } for(j=1;j<=n;j++) cost[i][j]=oo;

i=(int)st.nval; j=(int)st.nval; cost[i][j]=cost[j][i]=(int)st.nval;

esteInArbore[nods]=true; for(k=1;k<=n-1;k++) // sunt exact n-1 muchii in arbore !!! O(n) { min=oo; for(i=1;i<=n;i++) // O(n) { if(!esteInArbore[i]) continue; for(j=1;j<=n;j++) // O(n) { if(esteInArbore[j]) continue; if(min>cost[i][j]) { min=cost[i][j]; imin=i; jmin=j; } }//for j }//for i esteInArbore[jmin]=true; p[jmin]=imin; costArbore+=min;

222 }//for k

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

for(k=1;k<=n;k++) if(p[k]!=0) out.println(k+" "+p[k]); out.println("cost="+costArbore); out.close(); }//main }//class /* 6 7 1 2 1 3 2 3 3 4 4 5 5 6 4 6 */

3 1 2 1 1 3 2

1 3 2 3 4 3 5 4 6 4 cost=7

Alg Prim cu distante import java.io.*; // arbore minim de acoperire: algoritmul lui Prim class PrimDist // O(n^2) { static final int oo=0x7fffffff; static int n,m; static int[][] cost; static boolean[] esteInArbore; static int[] p; // predecesor in arbore static int[] d; // distante de la nod catre arbore public static void main(String[]args) throws IOException { int nodStart=3; // nod start int i, j, k, costArbore=0,min,jmin=0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("prim.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("prim.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval;

14.3. EXEMPLE cost=new int[n+1][n+1]; esteInArbore=new boolean [n+1]; p=new int[n+1]; d=new int[n+1]; for(i=1;i<=n;i++) for(j=1;j<=n;j++) cost[i][j]=oo; for(i=1;i<=n;i++) d[i]=oo; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost[i][j]=cost[j][i]=(int)st.nval; } d[nodStart]=0; for(k=1;k<=n;k++) // O(n) { min=oo; for(j=1;j<=n;j++) // O(n) { if(esteInArbore[j]) continue; if(min>d[j]) { min=d[j]; jmin=j; } }//for j esteInArbore[jmin]=true; d[jmin]=0; costArbore+=min;

223

for(j=1;j<=n;j++) // actualizez distantele nodurilor O(n) { if(esteInArbore[j]) continue; // j este deja in arbore if(cost[jmin][j]<oo) // am muchia (jmin,j) if(d[jmin]+cost[jmin][j]<d[j]) { d[j]=d[jmin]+cost[jmin][j]; p[j]=jmin; } } }//for k

224

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

for(k=1;k<=n;k++) if(p[k]!=0) out.println(k+" "+p[k]); out.println("cost="+costArbore); out.close(); }//main }//class /* 6 7 1 2 1 3 2 3 3 4 4 5 5 6 4 6 */

3 1 2 1 1 3 2

1 3 2 3 4 3 5 4 6 4 cost=7

Alg Prim cu heap import java.io.*; // arbore minim de acoperire: algoritmul lui Prim class PrimHeap // folosesc distantele catre arbore { // pastrate in MinHeap ==> O(n log n) ==> OK !!! static final int oo=0x7fffffff; static int n,m; static int[][] w; // matricea costurilor static int[] d; // distante de la nod catre arbore static int[] p; // predecesorul nodului in arbore static int[] hd; // hd[i]=distanta din pozitia i din heap static int[] hnod; // hnod[i]= nodul din pozitia i din heap static int[] pozh; // pozh[i]=pozitia din heap a nodului i public static void main(String[]args) throws IOException { StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("prim.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("prim.out"))); int i,j,k,cost,costa=0,nods; st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; st.nextToken(); nods=(int)st.nval; w=new int[n+1][n+1];

14.3. EXEMPLE for(i=1;i<=n;i++) for(j=1;j<=n;j++) w[i][j]=oo; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost=(int)st.nval; w[j][i]=w[i][j]=cost; } prim(nods); for(i=1;i<=n;i++) // afisez muchiile din arbore if(i!=nods) {out.println(p[i]+" "+i);costa+=w[p[i]][i];} out.println("costa="+costa); out.close(); }//main static void prim(int nods) { int u,v,q,aux; d=new int [n+1]; p=new int [n+1]; hd=new int[n+1]; hnod=new int[n+1]; pozh=new int[n+1]; for(u=1;u<=n;u++) { hnod[u]=pozh[u]=u; hd[u]=d[u]=oo; p[u]=0; }

225

q=n; // q = noduri nefinalizate = dimensiune heap d[nods]=0; hd[pozh[nods]]=0; urcInHeap(pozh[nods]); while(q>0) // la fiecare pas adaug un varf in arbore { u=extragMin(q); if(u==-1) { System.out.println("graf neconex !!!"); break; }

226

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY q--; for(v=1;v<=q;v++) if(w[u][hnod[v]]<oo) relax(u,hnod[v]);

// noduri nefinalizate = in heap 1..q // cost finit ==> exista arc (u,v) // relax si refac heap

} }//prim(...) static void relax(int u,int v) { if(w[u][v]<d[v]) { d[v]=w[u][v]; p[v]=u; hd[pozh[v]]=d[v]; urcInHeap(pozh[v]); } }//relax(...) static int extragMin(int q) // in heap 1..q { // returnez valoarea minima (din varf!) si refac heap in jos // aducand ultimul in varf si coborand ! int aux,fiu1,fiu2,fiu,tata,nod; aux=hd[1]; hd[1]=hd[q]; hd[q]=aux; aux=hnod[1]; hnod[1]=hnod[q]; hnod[q]=aux; pozh[hnod[q]]=q; pozh[hnod[1]]=1; tata=1; fiu1=2*tata; fiu2=2*tata+1; while(fiu1<=q-1) //refac heap de sus in jos pana la q-1 { fiu=fiu1; if(fiu2<=q-1) if(hd[fiu2]<hd[fiu1]) fiu=fiu2; if(hd[tata]<=hd[fiu]) // 1 <---> q

14.3. EXEMPLE break; pozh[hnod[fiu]]=tata; pozh[hnod[tata]]=fiu; aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux; aux=hnod[fiu]; hnod[fiu]=hnod[tata]; hnod[tata]=aux; tata=fiu; fiu1=2*tata; fiu2=2*tata+1; } return hnod[q]; }// extragMin(...) // hnod[1] a ajuns deja (!) in hnod[q]

227

static void urcInHeap(int nodh) { int aux,fiu,tata,nod; nod=hnod[nodh]; fiu=nodh; tata=fiu/2; while((tata>0)&&(hd[fiu]<hd[tata])) { pozh[hnod[tata]]=fiu; hnod[fiu]=hnod[tata]; aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux; fiu=tata; tata=fiu/2; } pozh[nod]=fiu; hnod[fiu]=nod; } }//class /* 6 7 3 1 2 3 1 3 1

3 1 3 2

228 2 3 3 4 4 5 5 6 4 6 */ 2 1 1 3 2

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY 3 4 4 5 4 6 costa=7

14.3.12

Algoritmul lui Kruskal

import java.io.*; // Arbore minim de acoperire : Kruskal class Kruskal { static int n,m,cost=0; static int[] x,y,z,et; public static void main(String[] args) throws IOException { int k; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("kruskal.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("kruskal.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; x=new int[m+1]; y=new int[m+1]; z=new int[m+1]; et=new int[n+1]; for(k=1;k<=m;k++) { st.nextToken(); x[k]=(int)st.nval; st.nextToken(); y[k]=(int)st.nval; st.nextToken(); z[k]=(int)st.nval; } kruskal(); System.out.println("cost="+cost); out.println(cost); out.close(); }//main

14.3. EXEMPLE

229

static void kruskal() { int nm=0,k,etg1,etg2; for(k=1;k<=n;k++) et[k]=k; qsort(1,m,z); for(k=1;k<=m;k++) { if(et[x[k]]!=et[y[k]]) { nm++; cost+=z[k]; System.out.println(x[k]+" "+y[k]); etg1=et[x[k]]; etg2=et[y[k]]; for(int i=1;i<=n;i++) if(et[i]==etg2) et[i]=etg1; } if(nm==n-1)break; } }//kruskal static void qsort(int p, int u, int []x) { int k=poz(p,u,x); if(p<k-1) qsort(p,k-1,x); if(k+1<u) qsort(k+1,u,x); } static void invers(int i, int j, int x[]) { int aux; aux=x[i]; x[i]=x[j]; x[j]=aux; } static int poz(int p, int u, int z[]) { int k,i,j; i=p; j=u; while(i<j) {

230

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY while((z[i]<=z[j])&&(i<j)) i++; while((z[i]<=z[j])&&(i<j)) j--; if(i<j) { invers(i,j,z); invers(i,j,x); invers(i,j,y); }

} return i; //i==j }//poz }//class

14.3.13

Algoritmul lui Dijkstra

Alg Dijkstra cu distante, graf neorientat n import java.io.*; // drumuri minime de la nodSursa class Dijkstra // O(n^2) { static final int oo=0x7fffffff; static int n,m; static int[][] cost; static boolean[] esteFinalizat; static int[] p; // predecesor in drum static int[] d; // distante de la nod catre nodSursa public static void main(String[]args) throws IOException { int nodSursa=1; // nod start int i, j, k, min,jmin=0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("dijkstraNeorientat.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("dijkstraNeorientat.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; cost=new int[n+1][n+1]; esteFinalizat=new boolean [n+1]; p=new int[n+1]; d=new int[n+1]; for(i=1;i<=n;i++) for(j=1;j<=n;j++) cost[i][j]=oo; for(i=1;i<=n;i++) d[i]=oo; for(k=1;k<=m;k++)

14.3. EXEMPLE { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost[i][j]=cost[j][i]=(int)st.nval; } d[nodSursa]=0;

231

for(k=1;k<=n;k++) // O(n) { min=oo; for(j=1;j<=n;j++) // O(n) { if(esteFinalizat[j]) continue; if(min>d[j]) { min=d[j]; jmin=j; } }//for j esteFinalizat[jmin]=true; for(j=1;j<=n;j++) // actualizez distantele nodurilor // O(n) { if(esteFinalizat[j]) continue; // j este deja in arbore if(cost[jmin][j]<oo) // am muchia (jmin,j) if(d[jmin]+cost[jmin][j]<d[j]) { d[j]=d[jmin]+cost[jmin][j]; p[j]=jmin; } } }//for k for(k=1;k<=n;k++) { System.out.print(nodSursa+"-->"+k+" dist="+d[k]+" drum: "); drum(k); System.out.println(); } out.close(); }//main static void drum(int k) // s --> ... --> k { if(p[k]!=0) drum(p[k]); System.out.print(k+" "); } }//class /* 6 7 1-->1 dist=0 drum: 1

232 1 2 1 3 2 3 3 4 5 4 5 6 4 6 */ 4 1 2 1 1 3 2

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY 1-->2 1-->3 1-->4 1-->5 1-->6 dist=3 dist=1 dist=2 dist=3 dist=4 drum: drum: drum: drum: drum: 1 1 1 1 1 3 3 3 3 3 2 4 4 5 4 6

Alg Dijkstra cu heap, graf neorientat n import java.io.*; // distante minime de la nodSursa class DijkstraNeorientatHeap // pastrate in MinHeap ==> O(n log n) ==> OK !!! { static final int oo=0x7fffffff; static int n,m; static int[][]w; // matricea costurilor static int[]d; // distante de la nod catre arbore static int[]p; // predecesorul nodului in arbore static int[] hd; // hd[i]=distanta din pozitia i din heap static int[] hnod; // hnod[i]= nodul din pozitia i din heap static int[] pozh; // pozh[i]=pozitia din heap a nodului i public static void main(String[]args) throws IOException { int i,j,k,cost,costa=0,nodSursa; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("dijkstraNeorientat.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("dijkstraNeorientat.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; w=new int[n+1][n+1]; for(i=1;i<=n;i++) for(j=1;j<=n;j++) w[i][j]=oo; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost=(int)st.nval; w[j][i]=w[i][j]=cost; } nodSursa=1; dijkstra(nodSursa);

14.3. EXEMPLE

233

for(k=1;k<=n;k++) { System.out.print(nodSursa+"-->"+k+" dist="+d[k]+" drum: "); drum(k); System.out.println(); } out.close(); }//main static void dijkstra(int nods) { int u,v,q,aux; d=new int [n+1]; p=new int [n+1]; hd=new int[n+1]; hnod=new int[n+1]; pozh=new int[n+1]; for(u=1;u<=n;u++) { hnod[u]=pozh[u]=u; hd[u]=d[u]=oo; p[u]=0; } q=n; // q = noduri nefinalizate = dimensiune heap d[nods]=0; hd[pozh[nods]]=0; urcInHeap(pozh[nods]); while(q>0) // la fiecare pas adaug un varf in arbore { u=extragMin(q); if(u==-1) { System.out.println("graf neconex !!!"); break; } q--; for(v=1;v<=q;v++) // noduri nefinalizate = in heap 1..q if(w[u][hnod[v]]<oo) // cost finit ==> exista arc (u,v) relax(u,hnod[v]); // relax si refac heap } }// dijkstra() static void relax(int u,int v)

234 {

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

if(d[u]+w[u][v]<d[v]) { d[v]=d[u]+w[u][v]; p[v]=u; hd[pozh[v]]=d[v]; urcInHeap(pozh[v]); } }// relax(...) static int extragMin(int q) // in heap 1..q { // returnez valoarea minima (din varf!) si refac heap in jos int aux,fiu1,fiu2,fiu,tata,nod; aux=hd[1]; hd[1]=hd[q]; hd[q]=aux; // 1 <---> q aux=hnod[1]; hnod[1]=hnod[q]; hnod[q]=aux; pozh[hnod[q]]=q; pozh[hnod[1]]=1; tata=1; fiu1=2*tata; fiu2=2*tata+1; while(fiu1<=q-1) //refac heap de sus in jos pana la q-1 { fiu=fiu1; if(fiu2<=q-1) if(hd[fiu2]<hd[fiu1]) fiu=fiu2; if(hd[tata]<=hd[fiu]) break; pozh[hnod[fiu]]=tata; pozh[hnod[tata]]=fiu; aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux; aux=hnod[fiu]; hnod[fiu]=hnod[tata]; hnod[tata]=aux; tata=fiu; fiu1=2*tata; fiu2=2*tata+1; } return hnod[q]; // hnod[1] a ajuns deja (!) in hnod[q] } // extragMin(...)

14.3. EXEMPLE

235

static void urcInHeap(int nodh) { int aux,fiu,tata,nod; nod=hnod[nodh]; fiu=nodh; tata=fiu/2; while((tata>0)&&(hd[fiu]<hd[tata])) { pozh[hnod[tata]]=fiu; hnod[fiu]=hnod[tata]; aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux; fiu=tata; tata=fiu/2; } pozh[nod]=fiu; hnod[fiu]=nod; } // urcInHeap(...) static void drum(int k) // s --> ... --> k { if(p[k]!=0) drum(p[k]); System.out.print(k+" "); } }//class /* 6 7 1 2 1 3 2 3 3 4 5 4 5 6 4 6 */

4 1 2 1 1 3 2

1-->1 1-->2 1-->3 1-->4 1-->5 1-->6

dist=0 dist=3 dist=1 dist=2 dist=3 dist=4

drum: drum: drum: drum: drum: drum:

1 1 1 1 1 1

3 3 3 3 3

2 4 4 5 4 6

Alg Dijkstra cu distante, graf orientat n import java.io.*; // drumuri minime de la nodSursa class Dijkstra // O(n^2) {

236 static static static static static static

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY final int oo=0x7fffffff; int n,m; int[][] cost; boolean[] esteFinalizat; int[] p; // predecesor in drum int[] d; // distante de la nod catre nodSursa

public static void main(String[]args) throws IOException { int nodSursa=1; // nod start int i, j, k, min,jmin=0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("dijkstraOrientat.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("dijkstraOrientat.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; cost=new int[n+1][n+1]; esteFinalizat=new boolean [n+1]; p=new int[n+1]; d=new int[n+1]; for(i=1;i<=n;i++) for(j=1;j<=n;j++) cost[i][j]=oo; for(i=1;i<=n;i++) d[i]=oo; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost[i][j]=(int)st.nval; } d[nodSursa]=0; for(k=1;k<=n;k++) // O(n) { min=oo; for(j=1;j<=n;j++) // O(n) {

14.3. EXEMPLE

237

if(esteFinalizat[j]) continue; if(min>d[j]) { min=d[j]; jmin=j; } }//for j esteFinalizat[jmin]=true; for(j=1;j<=n;j++) // actualizez distantele nodurilor // O(n) { if(esteFinalizat[j]) continue; // j este deja in arbore if(cost[jmin][j]<oo) // am muchia (jmin,j) if(d[jmin]+cost[jmin][j]<d[j]) { d[j]=d[jmin]+cost[jmin][j]; p[j]=jmin; } } }//for k for(k=1;k<=n;k++) { System.out.print(nodSursa+"-->"+k+" dist="+d[k]+" drum: "); if(d[k]<oo) drum(k); else System.out.print("Nu exista drum!"); System.out.println(); } out.close(); }//main static void drum(int k) // s --> ... --> k { if(p[k]!=0) drum(p[k]); System.out.print(k+" "); } }//class /* 6 7 1-->1 dist=0 drum: 1 1 2 4 1-->2 dist=4 drum: 1 2 1 3 1 1-->3 dist=1 drum: 1 3 2 3 2 1-->4 dist=2 drum: 1 3 4 3 4 1 1-->5 dist=2147483647 drum: Nu exista drum! 5 4 1 1-->6 dist=4 drum: 1 3 4 6 5 6 3 4 6 2 */ Alg Dijkstra cu heap, graf orientat n import java.io.*; class DijkstraOrientatHeap // distante minime de la nodSursa // pastrate in MinHeap ==> O(n log n) ==> OK !!!

238 {

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

static final int oo=0x7fffffff; static int n,m; static int[][]w; static int[]d; static int[]p; static int[] hd; static int[] hnod; static int[] pozh; // matricea costurilor // distante de la nod catre arbore // predecesorul nodului in arbore // hd[i]=distanta din pozitia i din heap // hnod[i]= nodul din pozitia i din heap // pozh[i]=pozitia din heap a nodului i

public static void main(String[]args) throws IOException { int i,j,k,cost,costa=0,nodSursa; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("dijkstraOrientat.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("dijkstraOrientat.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; w=new int[n+1][n+1]; for(i=1;i<=n;i++) for(j=1;j<=n;j++) w[i][j]=oo; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost=(int)st.nval; w[i][j]=cost; } nodSursa=1; dijkstra(nodSursa); for(k=1;k<=n;k++) { if(d[k]<oo) {

14.3. EXEMPLE

239

System.out.print(nodSursa+"-->"+k+" dist="+d[k]+" drum: "); drum(k); } else System.out.print(nodSursa+"-->"+k+" Nu exista drum! "); System.out.println(); } out.close(); }//main static void dijkstra(int nods) { int u,v,q,aux; d=new int [n+1]; p=new int [n+1]; hd=new int[n+1]; hnod=new int[n+1]; pozh=new int[n+1]; for(u=1;u<=n;u++) { hnod[u]=pozh[u]=u; hd[u]=d[u]=oo; p[u]=0; } q=n; // q = noduri nefinalizate = dimensiune heap d[nods]=0; hd[pozh[nods]]=0; urcInHeap(pozh[nods]); while(q>0) // la fiecare pas adaug un varf in arbore { u=extragMin(q); if(u==-1) { System.out.println("graf neconex !!!"); break; } q--; for(v=1;v<=q;v++) if(w[u][hnod[v]]<oo) relax(u,hnod[v]); } }//dijkstra(...) static void relax(int u,int v) { if(d[u]+w[u][v]<d[v]) {

// noduri nefinalizate = in heap 1..q // cost finit ==> exista arc (u,v) // relax si refac heap

240

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY d[v]=d[u]+w[u][v]; p[v]=u; hd[pozh[v]]=d[v]; urcInHeap(pozh[v]);

} }// relax(...) static int extragMin(int q) // in heap 1..q { // returnez valoarea minima (din varf!) si refac heap in jos int aux,fiu1,fiu2,fiu,tata,nod; aux=hd[1]; hd[1]=hd[q]; hd[q]=aux; // 1 <---> q aux=hnod[1]; hnod[1]=hnod[q]; hnod[q]=aux; pozh[hnod[q]]=q; pozh[hnod[1]]=1; tata=1; fiu1=2*tata; fiu2=2*tata+1; while(fiu1<=q-1) //refac heap de sus in jos pana la q-1 { fiu=fiu1; if(fiu2<=q-1) if(hd[fiu2]<hd[fiu1]) fiu=fiu2; if(hd[tata]<=hd[fiu]) break; pozh[hnod[fiu]]=tata; pozh[hnod[tata]]=fiu; aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux; aux=hnod[fiu]; hnod[fiu]=hnod[tata]; hnod[tata]=aux; tata=fiu; fiu1=2*tata; fiu2=2*tata+1; } return hnod[q]; // hnod[1] a ajuns deja (!) in hnod[q] } // extragMin(...) static void urcInHeap(int nodh) { int aux,fiu,tata,nod;

14.3. EXEMPLE

241

nod=hnod[nodh]; fiu=nodh; tata=fiu/2; while((tata>0)&&(hd[fiu]<hd[tata])) { pozh[hnod[tata]]=fiu; hnod[fiu]=hnod[tata]; aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux; fiu=tata; tata=fiu/2; } pozh[nod]=fiu; hnod[fiu]=nod; } // urcInHeap(...) static void drum(int k) // s --> ... --> k { if(p[k]!=0) drum(p[k]); System.out.print(k+" "); } }//class /* 6 7 1 2 1 3 2 3 3 4 5 4 5 6 4 6 */

4 1 2 1 1 3 2

1-->1 1-->2 1-->3 1-->4 1-->5 1-->6

dist=0 drum: 1 dist=4 drum: 1 2 dist=1 drum: 1 3 dist=2 drum: 1 3 4 Nu exista drum! dist=4 drum: 1 3 4 6

14.3.14

Urgenta - OJI2002 cls 11

Autoritile dintr-o zon de munte intentioneaz s stabileasc un plan de at a a a a urgent pentru a reactiona mai ecient la frecventele calamiti naturale din zon. a at a acest scop au identicat N puncte de interes strategic i le-au numerotat distinct In s de la 1 la N . Punctele de interes strategic sunt conectate prin M ci de acces avnd a a

242

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

prioriti functie de important. at n a Intre oricare dou puncte de interes strategic a exist cel mult o cale de acces ce poate parcurs ambele sensuri i cel putin a a n s un drum (format din una sau mai multe ci de acces) ce le conecteaz. a a cazul unei calamiti unele ci de acces pot temporar In at a ntrerupte i astfel s ntre anumite puncte de interes nu mai exist legtur. Ca urmare pot rezulta mai a a a multe grupuri de puncte aa fel at n s nc ntre oricare dou puncte din acelai grup a s s existe mcar un drum i a a s ntre oricare dou puncte din grupuri diferite s nu a a existe drum. Autoritile estimeaz gravitatea unei calamiti ca ind suma prioritilor at a at at cilor de acces distruse de aceasta i doresc s determine un scenariu de gravitate a s a maxim, care punctele de interes strategic s e artite a n a mp ntr-un numr de K a grupuri. Date de intrare Fiierul de intrare URGENTA.IN are urmtorul format: s a N M K i1 j1 p1 - ntre punctele i1 i j1 exist o cale de acces de prioritate p1 s a i2 j2 p2 - ntre punctele i2 i j2 exist o cale de acces de prioritate p2 s a ... iM jM pM - ntre punctele iM i jM exist o cale de acces de prioritate pM s a

Date de ieire s Fiierul de ieire URGENTA.OUT va avea urmtorul format: s s a gravmax - gravitatea maxim a C - numrul de ci de acces a a ntrerupte de calamitate k1 h1 - ntre punctele k1 i h1 a fost s ntrerupt calea de acces a k2 h2 - ntre punctele k2 i h2 a fost s ntrerupt calea de acces a ... kC hC - ntre punctele kC i hC a fost s ntrerupt calea de acces a Restrictii i precizri s a 0 < N < 256 N 2 < M < 32385 0<K <N +1 Prioritile cilor de acces sunt at a ntregi strict pozitivi mai mici dect 256. a Un grup de puncte poate contine ntre 1 i N puncte inclusiv. s Dac exist mai multe solutii, programul va determina una singur. a a a Exemplu

14.3. EXEMPLE URGENTA.IN 7 11 4 121 132 173 243 342 351 361 375 455 564 673 URGENTA.OUT 27 8 13 17 24 34 37 45 56 67

243

Timp maxim de executare: 1 secund / test a Codul surs a import java.io.*; // arbore minim de acoperire: algoritmul lui Prim O(n^2) class Urgenta // sortare O(n^2) ... si una slaba merge! { static final int oo=0x7fffffff; static int n,m,ncc,gravmax,costmax,nrm; // ncc = nr componente conexe static int[][] cost; static boolean[] esteInArbore; static int[] p; // predecesor in arbore static int[] d; // distante de la nod catre arbore static int[] a1; // a1[k]=varful 1 al muchiei k din arbore static int[] a2; // a2[k]=varful 2 al muchiei k din arbore static int[] ac; // a1[k]=costul muchiei k din arbore public static void main(String[]args) throws IOException { int nodStart=3; // nod start int i, j, k, costArbore=0,min,jmin=0,aux; StreamTokenizer st= new StreamTokenizer( new BufferedReader(new FileReader("urgenta.in"))); PrintWriter out= new PrintWriter( new BufferedWriter(new FileWriter("urgenta.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; st.nextToken(); ncc=(int)st.nval;

244

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

cost=new int[n+1][n+1]; esteInArbore=new boolean [n+1]; p=new int[n+1]; d=new int[n+1]; a1=new int[n]; a2=new int[n]; ac=new int[n]; for(i=1;i<=n;i++) for(j=1;j<=n;j++) cost[i][j]=oo; for(i=1;i<=n;i++) d[i]=oo; costmax=0; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost[i][j]=cost[j][i]=(int)st.nval; costmax+=cost[i][j]; } // alg Prim d[nodStart]=0; for(k=1;k<=n;k++) // O(n) { min=oo; for(j=1;j<=n;j++) // O(n) { if(esteInArbore[j]) continue; if(min>d[j]) { min=d[j]; jmin=j; } }//for j esteInArbore[jmin]=true; d[jmin]=0; costArbore+=min; for(j=1;j<=n;j++) // actualizez distantele nodurilor // { if(esteInArbore[j]) continue; // j este deja in arbore if(cost[jmin][j]<oo) // am muchia (jmin,j) if(d[jmin]+cost[jmin][j]<d[j]) { d[j]=d[jmin]+cost[jmin][j]; O(n)

14.3. EXEMPLE p[j]=jmin; } } }//for k k=0; for(i=1;i<=n;i++) if(p[i]!=0) { //System.out.println(i+" "+p[i]+" --> "+cost[i][p[i]]); k++; a1[k]=i; a2[k]=p[i]; ac[k]=cost[i][p[i]]; } //System.out.println("cost="+costArbore); gravmax=costmax-costArbore; // deocamdata, ... //System.out.println("gravmax ="+gravmax);

245

// trebuie sa adaug la gravmax primele ncc-1 costuri mari (sort!) // din arborele minim de acoperire // sortez descrescator ac (pastrand corect a1 si a2) // care are n-1 elemente for(k=1;k<=n-1;k++) // de n-1 ori (bule) { for(i=1;i<=n-2;i++) if(ac[i]<ac[i+1]) { aux=ac[i]; ac[i]=ac[i+1]; ac[i+1]=aux; aux=a1[i]; a1[i]=a1[i+1]; a1[i+1]=aux; aux=a2[i]; a2[i]=a2[i+1]; a2[i+1]=aux; } } // primele ncc-1 ... for(i=1;i<=ncc-1;i++) gravmax+=ac[i]; // sterg muchiile ramase in arbore ... for(i=ncc;i<=n-1;i++) { cost[a1[i]][a2[i]]=cost[a2[i]][a1[i]]=oo; //sterg

246 }

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

out.println(gravmax); // determin numarul muchiilor ... nrm=0; for(i=1;i<=n-1;i++) for(j=i+1;j<=n;j++) if(cost[i][j] < oo) nrm++; out.println(nrm); // afisez muchiile ... for(i=1;i<=n-1;i++) for(j=i+1;j<=n;j++) if(cost[i][j] < oo) out.println(i+" "+j); out.close(); }//main }//class

14.3.15

Reactivi - OJI2004 cls 9

Intr-un laborator de analize chimice se utilizeaz N reactivi. a Se tie c, pentru a evita accidentele sau deprecierea reactivilor, acetia tres a s buie s e stocati conditii de mediu speciale. Mai exact, pentru ecare reactiv x, a n se precizeaz intervalul de temperatur [minx , maxx ] care trebuie s se a a n a ncadreze temperatura de stocare a acestuia. Reactivii vor plasati frigidere. n Orice frigider are un dispozitiv cu ajutorul cruia putem stabili temperatura a (constant) care va interiorul acelui frigider (exprimat a n a ntr-un numr a ntreg de grade Celsius). Cerint a Scrieti un program care s determine numrul minim de frigidere necesare a a pentru stocarea reactivilor chimici. Date de intrare Fiierul de intrare react.in contine: s pe prima linie numrul natural N , care reprezint numrul de reactivi; a a a pe ecare dintre urmtoarele N linii se a min max (dou numere a a a ntregi separate printr-un spatiu); numerele de pe linia x + 1 reprezint temperatura a minim, respectiv temperatura maxim de stocare a reactivului x. a a

14.3. EXEMPLE

247

Date de ieire s Fiierul de ieire react.out va contine o singur linie pe care este scris s s a numrul minim de frigidere necesar. a Restrictii i precizri s a 1 N 8000 100 minx maxx 100 (numere ntregi, reprezentnd grade Celsius), a pentru orice x de la 1 la N un frigider poate contine un numr nelimitat de reactivi a Exemple react.in 3 -10 10 -25 20 50 react.out 2 react.in 4 25 57 10 20 30 40 react.out 3 react.in 5 -10 10 10 12 -20 10 7 10 78 react.out 2

Timp maxim de executie: 1 secund/test a Indicatii de rezolvare - descriere solutie * Solutie prezentat de Mihai Stroe, GInfo nr. 14/4 a Problema se poate rezolva prin metoda greedy. O variant mai explicit a enuntului este urmtoarea: a a a Se consider N intervale pe o ax. S se aleag un numr minim de puncte a a a a a astfel at ecare interval s contin cel putin unul dintre punctele alese. nc a a Facem o prim observatie: pentru orice solutie optim exist o solutie cu a a a acelai numr de puncte (frigidere), care ecare punct s e sfritul unui ins a n a as terval. Aceasta se poate obtine mutnd ecare punct spre dreapta, pn cnd ar a a a a ajunge la sfritul intervalului care se termin cel mai repede, dintre intervalele as a care contin. Se observ c noua solutie respect restrictiile din enunt. l a a a continuare ne vom concentra pe gsirea unei solutii de acest tip. In a Sortm reactivii dup sfritul intervalului. Pentru intervalul care se termin a a as a cel mai repede, alegem ultimul punct al su ca temperatur a unui frigider. Se a a observ c aceast alegere este cea mai bun, dintre toate alegerile unui punct a a a a intervalul respectiv, sensul c multimea intervalelor care contin punctul este n n a mai mare (conform relatiei de incluziune), dect multimea corespunztoare oricrei a a a alte alegeri. Acest fapt este adevrat, deoarece mutarea punctului mai la stnga a a nu duce la selectarea unor noi intervale. Intervalele care contin punctul respectiv sunt eliminate (reactivii corespunztori a pot plasati ntr-un frigider), iar procesul continu cu intervalele rmase, acelai a a n s

248 mod.

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Analiza complexitii at Notm cu D numrul de temperaturi a a ntregi din intervalul care contine tem peraturile din enunt. Se observ c D este cel mult 201. a a Citirea datelor de intrare are ordinul de complexitate O(N ). Sortarea intervalelor dup captul din dreapta are ordinul de complexitate a a O(N logN ). Urmeaz F pai, unde F este numrul de frigidere selectate. Deoarece ecare a s a frigider este setat la o temperatur a ntreag, F D. a cadrul unui pas, determinarea intervalului care se termin cel mai repede, In a pe baza vectorului sortat, are ordinul de complexitate O(1). Eliminarea intervalelor care contin un anumit punct (sfritul intervalului care se termin cel mai repede) as a are ordinul de complexitate O(N ). Aarea rezultatului are ordinul de complexitate O(1). s concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei In probleme este O(N D + N logN ); deoarece general D > logN , considerm n a ordinul de complexitate ca ind O(N D). Codul surs a

import java.io.*; class Reactivi { static int n; static int ni=0; static int nf=0; static int[] ngf; static int[] x1,x2;

// // // // //

n=nr reactivi ni=nr interschimbari in quickSort n=nr frigidere ng=nr grade in frigider limite inferioara/superioara

public static void main(String[] args) throws IOException { int i,j; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("Reactivi.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("Reactivi.out"))); st.nextToken(); n=(int)st.nval; x1=new int[n+1]; x2=new int[n+1]; ngf=new int[n+1]; for(i=1;i<=n;i++) {

14.3. EXEMPLE st.nextToken(); x1[i]=(int)st.nval; st.nextToken(); x2[i]=(int)st.nval; } sol(); out.println(nf); out.close(); }// main static void sol() { int i; quickSort(1,n); i=1; nf=1; ngf[nf]=x2[i]; i++; while(i<n) { while((i<=n)&&(x1[i]<=ngf[nf])) i++; if(i<n) ngf[++nf]=x2[i++]; } } static void quickSort(int p, int u) { int i,j,aux; i=p; j=u; while(i<j) { while((i<j)&&((x2[i]<x2[j])|| ((x2[i]==x2[j])&&(x1[i]>=x1[j])))) i++; if(i!=j) { aux=x1[i]; x1[i]=x1[j]; x1[j]=aux; aux=x2[i]; x2[i]=x2[j]; x2[j]=aux; } while((i<j)&&((x2[i]<x2[j])|| ((x2[i]==x2[j])&&(x1[i]>=x1[j])))) j--; if(i!=j) { aux=x1[i]; x1[i]=x1[j]; x1[j]=aux; aux=x2[i]; x2[i]=x2[j]; x2[j]=aux; } } if(p<i-1) quickSort(p,i-1); if(i+1<u) quickSort(i+1,u);

249

250 } }

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

14.3.16

Pal - ONI2005 cls 9

Autor: Silviu Gnceanu a Printul Algorel este n ncurctur din nou: a fost prins de Spnul cel a a a Negru n ncercarea sa de a o salva pe printes i acum este as nchis Turnul cel n Mare. Algorel poate evada dac gsete combinatia magic cu care poate deschide a a s a poarta turnului. Printul tie cum se formeaz aceast combinatie magic: trebuie s utilizeze s a a a a toate cifrele scrise pe ua turnului pentru a obtine dou numere palindroame, s a astfel at suma lor s e minim, iar aceast sum este combinatia magic ce va nc a a a a a deschide ua. s Primul numr palindrom trebuie s aib cel putin L cifre, iar cel de-al doilea a a a poate avea orice lungime diferit de 0. Numerele palindroame formate nu pot a ncepe cu cifra 0. Acum interveniti dumneavoastr poveste, ind prietenul su a n a cel mai priceput algoritmi. n Prin noul super-telefon al su, printul transmite numrul de aparitii a ecrei a a a cifre de pe ua turnului precum i lungimea minim L a primului numr, iar s s a a dumneavoastr trebuie s-i trimiteti ct mai repede numerele cu care poate obtine a a a combinatia magic. a Cerint a Avnd datele necesare, aati dou numere palindroame cu care se poate a a obtine combinatia magic. a Date de intrare Prima linie a ierului pal.in contine un numr s a ntreg L reprezentnd luna gimea minim a primului numr. Urmeaz 10 linii: pe linia i + 2 se va aa un a a a numr a ntreg reprezentnd numrul de aparitii ale cifrei i, pentru i cu valori de la a a 0 la 9. Date de ieire s Prima linie a ierului de ieire pal.out contine primul numr palidrom, iar s s a cea de-a doua linie contine cel de-al doilea numr palindrom. Dac exist mai a a a multe solutii se va scrie doar una dintre ele. Restrictii i precizri s a total vor cel mult 100 de cifre In 1 L < 100 i L va mai mic dect numrul total de cifre s a a Pentru datele de test va exista ntotdeauna solutie: se vor putea forma din cifrele scrise pe ua turnului dou numere care s a ncep cu o cifr diferit de 0, iar a a primul numr s aib cel putin L cifre a a a

14.3. EXEMPLE

251

Un numr este palindrom dac el coincide cu rsturnatul su. De exemplu a a a a 12321 i 7007 sunt numere palindroame, timp ce 109 i 35672 nu sunt. s n s Pentru 30% dintre teste, numrul total de cifre va cel mult 7; pentru alte a 40% din teste numrul total de cifre va cel mult 18, iar pentru restul de 30% din a teste numrul total de cifre va mai mare sau egal cu 30. a Fiecare linie din ierul de intrare i din ierul de ieire se termin cu s s s s a marcaj de sfrit de linie. as Exemplu pal.in pal.out 5 10001 3 222 2 3 0 0 0 0 0 0 0 Explicatie Pentru acest exemplu avem L = 5, 3 cifre de 0, 2 cifre de 1i 3 cifre de 2. s Cifrele de la 3 la 9 lipsesc de pe ua turnului. s Cele dou palindroame cu care a se genereaz combinatia magic a a sunt 10001 i 222. s Combinatia magic va suma acestora a i anume 10223 (care este suma minim s a pe care o putem obtine).

Timp maxim de executie/test: 1 sec sub Windows i 1 sec sub Linux s Indicatii de rezolvare - descriere solutie Solutia ocial, Silviu Gnceanu a a Problema se rezolv utiliznd tehnica greedy. Notm numrul total de cifre a a a a cu N C. Se observ c, pentru ca suma celor dou numere palindroame s e a a a a minim, trebuie ca lungimea maxim a celor dou numere s e ct mai mic. a a a a a a Aadar, pentru s nceput, se stabilete lungimea exact a primului numr (care va s a a cel mai lung), functie de L modul urmtor: n n a 1. dac L < N C/2, atunci lungimea primului palindrom va aleas astfel a a at cele dou numere s e ct mai apropiate ca lungime nc a a a 2. dac L >= N C/2 apar cazuri particulare i se stabilete dac lungimea a s s a primului palindrom crete sau nu cu o unitate. s Avnd lungimile numerelor stabilite putem merge mai departe utilznd stratea a gia greedy, observnd c cifrele mai mici trebuie s ocupe pozitii ct mai semnia a a a cative. Pentru a realiza acest lucru se vor completa paralel cele dou numere n a cu cifrele parcurse ordine cresctoare stabilind care din cele dou numere n a n a se vor pozitiona. De asemenea, trebuie avut vedere ca niciunul din cele dou n a numere s nu a nceap cu cifra 0. a Datele de test au fost construite astfel at i solutii neoptime s obtin nc s a a puncte, gradat, functie de optimizrile efectuate asupra implementrii. n a a

252 Codul surs a

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

import java.io.*; // la inceput cifre mici in p1 class Pal // apoi in ambele in paralel! { static int L; static int NC=0; static int nc1,nc2; static int ncfi=0; // nr cifre cu frecventa impara static int[] fc=new int[10]; // frecventa cifrelor static int[] p1; // palindromul 1 static int[] p2; // palindromul 2 public static void main(String []args) throws IOException { int i; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("pal.in"))); PrintWriter out = new PrintWriter ( new BufferedWriter( new FileWriter("pal.out"))); st.nextToken();L=(int)st.nval; for(i=0;i<=9;i++) { st.nextToken(); fc[i]=(int)st.nval;} for(i=0;i<=9;i++) NC+=fc[i]; // nr total cifre for(i=0;i<=9;i++) ncfi=ncfi+(fc[i]%2); // nr cifre cu frecventa impara nc1=L; nc2=NC-nc1; while(nc1<nc2) {nc1++; nc2--;} if((ncfi==2)&&(nc1%2==0)) {nc1++; nc2--;} p1=new int[nc1]; p2=new int[nc2]; switch(ncfi) { case 0: impare0(); break; case 1: impare1(); break; case 2: impare2(); break; default: System.out.println("Date de intrare eronate!"); } corectez(p1); corectez(p2);

14.3. EXEMPLE

253

for(i=0;i<p1.length;i++) out.print(p1[i]); out.println(); for(i=0;i<p2.length;i++) out.print(p2[i]); out.println(); int[] p12=suma(p1,p2);// pentru ca in teste rezultat = suma! for(i=p12.length-1;i>=0;i--) out.print(p12[i]); out.println(); out.close(); }//main static void impare0() // cea mai mare cifra la mijloc { int i,k1,k2; int imp1=nc1/2, imp2=nc2/2; if(nc1%2==1) // numai daca nc1 si nc2 sunt impare ! for(i=9;i>=0;i--) if(fc[i]>0) { p1[imp1]=i; fc[i]--; p2[imp2]=i; fc[i]--; break; } k1=0; k2=0; while(k1<nc1-nc2) {incarcPozitia(k1,p1); k1++;}// incarc numai p1 while((k1<imp1)||(k2<imp2)) { if(k1<imp1) {incarcPozitia(k1,p1); k1++;} if(k2<imp2) {incarcPozitia(k2,p2); k2++;} } } static void impare1() { int i,k1,k2; int imp1=nc1/2, imp2=nc2/2; for(i=0;i<=9;i++) if(fc[i]%2==1) { p1[imp1]=i; fc[i]--; break;} for(i=0;i<=9;i++) if(fc[i]%2==1) { p2[imp2]=i; fc[i]--; break;}

254

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY k1=0; k2=0; while(k1<nc1-nc2) {incarcPozitia(k1,p1); k1++;} // incarc numai p1 while((k1<imp1)||(k2<imp2)) { if(k1<imp1) {incarcPozitia(k1,p1); k1++;} if(k2<imp2) {incarcPozitia(k2,p2); k2++;} }

} static void impare2() { int i,k1,k2; int imp1=nc1/2, imp2=nc2/2; for(i=0;i<=9;i++) if(fc[i]%2==1) { p1[imp1]=i; fc[i]--; break;} for(i=0;i<=9;i++) if(fc[i]%2==1) { p2[imp2]=i; fc[i]--; break;} k1=0; k2=0; while(k1<nc1-nc2) {incarcPozitia(k1,p1); k1++;}// incarc numai p1 while((k1<imp1)||(k2<imp2)) { if(k1<imp1) { incarcPozitia(k1,p1); k1++; } if(k2<imp2) { incarcPozitia(k2,p2); k2++; } } } static void corectez(int[] x) { int pozdif0,val, n=x.length; pozdif0=0; while(x[pozdif0]==0) pozdif0++; if(pozdif0>0) { val=x[pozdif0]; x[0]=x[n-1]=val; x[pozdif0]=x[n-pozdif0-1]=0; } } static void incarcPozitia(int k, int[] x)

14.3. EXEMPLE { int i; int n=x.length; for(i=0;i<=9;i++) if(fc[i]>0) { x[k]=i; fc[i]--; x[n-k-1]=i; fc[i]--; break; } }

255

static int[] suma(int[] x, int[] y) { int[] z=new int[(x.length>y.length) ? (x.length+1) : (y.length+1)]; int k,t=0; for(k=0;k<=z.length-2;k++) { z[k]=t; if(k<x.length) z[k]+=x[k]; if(k<y.length) z[k]+=y[k]; t=z[k]/10; z[k]=z[k]%10; } z[z.length-1]=t; if(z[z.length-1]!=0) return z; else { int[] zz=new int[z.length-1]; for(k=0;k<zz.length;k++) zz[k]=z[k]; return zz; } } }//class

14.3.17

Sant - ONI2006 cls 9

Cei n detinuti ai unei nchisori, numerotati de la 1 la n, trebuie s sape un a ant dispus linie dreapt s n a ntre dou puncte A i B, situate la distanta de 250 a s km unul de cellalt, pe care exist 251 borne kilometrice numerotate de la 0 la a a 250. Fiecare detinut are a o pretentie, e doar democratie, nu?: el dorete s ns s a sape doar undeva zona dintre borna x i borna y. Pe lng aceste pretentii n s a a nchisoarea se confrunt i cu o alt problem: nu are sucienti paznici angajati. as a a

256

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Cerint a Cunoscndu-se numrul n de detinuti i pretentiile lor, s se determine locul a a s a (locurile) unde vor pui detinutii s sape s a ntr-o zi de munc, respectndu-se a a pretentiile lor, astfel at numrul de paznici necesari pentru a pzi cei n detinuti nc a a s e minim. Intervalul care poate pzi un paznic nu poate contine dou sau a n a a mai multe zone disjuncte dintre cele exprimate de detinuti preferintele lor. n Date de intrare Fiierul de intrare sant.in are pe prima linie numrul n de detinuti. Pe ecare s a dintre urmtoarele n linii exist cte dou numere naturale, ai bi , separate printra a a a un spatiu (ai bi ), care reprezint pretentia detinutului. Mai exact pe linia i + 1 a (1 i n) este descris pretentia detinutului cu numrul de ordine i. a a Date de ieire s Fiierul de ieire sant.out va contine pe prima linie numrul natural k s s a reprezentnd numrul minim de paznici necesari pentru paza celor n detinuti scoi a a s la lucru. Pe urmtoarele 2k linii vor descrise locurile unde vor pui s sape a s a detinutii, astfel: ecare pereche de linii (2j, 2j+1) va contine pe linia 2j trei numere naturale p xj yj , separate prin cte un spatiu reprezentnd numrul de ordine al a a a paznicului i bornele kilometrice xj c si yj unde va pzi paznicul p, iar pe linia s a 2j + 1 vor scrise numerele de ordine ale detinutilor care sap aceast zon, a n a a separate prin cte un spatiu, ordonate cresctor. a a Restrictii i precizri s a 1 n 10000 0 ai bi 250, pentru orice i, 1 i n 0 xj yj 250, pentru orice j, 1 j k un detinut poate s sape i a s ntr-un singur punct ( dreptul bornei kilomen trice numerotat cu x) a cazul care exist mai multe solutii se va aa una singur n n a s a numerele de ordine ale paznicilor vor scrise ier ordine cresctoare n s n a numerotarea paznicilor ncepe de la 1 Exemplu

14.3. EXEMPLE .in 3 0 20 8 13 30 60 4 10 203 2 53 30 403 5 7 33 .out 2 1 8 13 12 2 30 60 3 3 1 10 20 1 255 24 3 30 40 3 2 1 30 30 1234 2 27 28 5 Explicatie sunt necesari 2 paznici: paznicul 1 va pzi a ntre borna 8 i borna 13, iar detinutii pziti sunt 1 i 2; s a s paznicul 2 va pzi a ntre borna 30 i borna 60, iar s detinutul pzit este 3 a sunt necesari 3 paznici: paznicul 1 va pzi a ntre borna 10 i borna 20, iar detinutul pzit este 1; s a paznicul 2 va pzi la borna 5, iar detinutii pziti a a sunt 2 i 4; paznicul 3 va pzi s a ntre borna 30 i s borna 40, iar detinutul pzit este 3 a

257

5 10 30 30 32 0 30 27 30 27 28

sunt necesari 2 paznici: paznicul 1 va pzi la a borna 30, iar detinutii pziti sunt 1, 2, 3 i 4; a s paznicul 2 va pzi a ntre borna 27 i borna 28, s iar detinutul pzit este 5 a !Solutia nu este unic! a

Timp maxim de executie/test: 1 secund (Windows), 0.5 secunde (Linux) a Indicatii de rezolvare - descriere solutie Solutia comisiei Problema cere, de fapt, determinarea numrului minim de intersectii a ntre segmentele determinate de kilometrul minim i maxim s ntre care sap un detinut. a Pentru a le determina procedez astfel: ordonez intervalele cresctor dup kilometrul minim i descresctor dup a a s a a kilometrul maxim pun primul detinut (deci cel cu intervalul de spare cel mai mare) grija a n primului paznic pentru toate celelalte caut un paznic care mai pzete detinuti i care poate pzi i acest a s s a s detinut (adic intersectia segmentelor s e nevid) a a a dac gsesc a a ajustez intervalul de spare ca s poat spa i acest detinut a a a a s altfel (dac nu gsesc) a a mai am nevoie de un paznic i dau grija acestuia s l n

258

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Datorit faptului c intervalele sunt sortate, cresctor dup captul inferior, a a a a a momentul cnd un interval nu se mai intersecteaz cu cel deja determinat, este n a a sigur c nici un alt interval ce urmeaz nu se mai intersecteaz, lucru ce asigur a l a a a determinarea numrului minim de paznici. a Codul surs a

import java.io.*; class Sant { static int n; static int[] x1=new int[10001]; static int[] x2=new int[10001]; static int[] o=new int[10001]; static void qsort(int li,int ls) { int val,aux,i,j; i=li; j=ls; val=x2[(i+j)/2]; while(i<j) { while(x2[i]<val) i++; while(x2[j]>val) j--; if(i<=j) { aux=x1[i]; x1[i]=x1[j]; x1[j]=aux; aux=x2[i]; x2[i]=x2[j]; x2[j]=aux; aux=o[i]; o[i]=o[j]; o[j]=aux; i++; j--; } }// while if(li<j) qsort(li,j); if(i<ls) qsort(i,ls); }// qsort(...) public static void main(String[] args) throws IOException

14.3. EXEMPLE { int k,np,xp; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("sant.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("sant.out"))); st.nextToken(); n=(int)st.nval; for(k=1;k<=n;k++) { st.nextToken(); x1[k]=(int)st.nval; st.nextToken(); x2[k]=(int)st.nval; o[k]=k; } qsort(1,n); np=1; xp=x2[1]; for(k=2;k<=n;k++) if(x1[k]>xp) { np++; xp=x2[k]; } out.println(np); // inca o data pentru ...! np=1; xp=x2[1]; out.println(np+" "+xp+" "+xp); out.print(o[1]+" "); for(k=2;k<=n;k++) { if(x1[k]>xp) { out.println(); np++; xp=x2[k]; out.println(np+" "+xp+" "+xp); out.print(o[k]+" "); } else out.print(o[k]+" "); }// for k out.close(); }// main(...) }// class

259

260

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

14.3.18

Cezar - OJI2007 cls 11

Roma antic exist n aezri senatoriale distincte, cte una pentru ecare In a a s a a dintre cei n senatori ai Republicii. Aezrile senatoriale sunt numerotate de la 1 s a la n, ntre oricare dou aezri existnd legturi directe sau indirecte. O legtur a s a a a a a este direct dac ea nu mai trece prin alte aezri senatoriale intermediare. Edilii a a s a au pavat unele dintre legturile directe dintre dou aezri (numind o astfel de a a s a legtur pavat strad), astfel at a a a a nc ntre oricare dou aezri senatoriale s a s a a existe o singur succesiune de strzi prin care se poate ajunge de la o aezare a a s senatorial la cealalt. a a Toti senatorii trebuie s participe la edintele Senatului. acest scop, ei se a s In deplaseaz cu lectica. Orice senator care se deplaseaz pe o strad pltete 1 ban a a a a s pentru c a fost transportat cu lectica pe acea strad. a a La alegerea sa ca prim consul, Cezar a promis c va dota Roma cu o lectic a a gratuit care s circule pe un numr de k strzi ale Romei astfel at orice senator a a a a nc care va circula pe strzile respective, s poat folosi lectica gratuit fr a plti. a a a a aa a Strzile pe care se deplaseaz lectica gratuit trebuie s e legate a a a a ntre ele (zborul, metroul sau teleportarea neind posibile la acea vreme). plus, Cezar a promis s stabileasc sediul slii de edinte a Senatului In a a a s ntruna dintre aezrile senatoriale aate pe traseul lecticii gratuite. Problema este de s a a alege cele k strzi i amplasarea sediului slii de edinte a Senatului astfel at, a s a s nc prin folosirea transportului gratuit, senatorii, drumul lor spre sala de edinte, n s s fac economii ct mai a a a nsemnate. calculul costului total de transport, pentru In toti senatorii, Cezar a considerat c ecare senator va cltori exact o dat de la a aa a aezarea sa pn la sala de edinte a Senatului. s a a s Cerint a Scrieti un program care determin costul minim care se poate obtine prin a alegerea adecvat a celor k strzi pe care va circula lectica gratuit i a locului de a a as amplasare a slii de edint a Senatului. a s a Date de intrare Fiierul cezar.in contine s pe prima linie dou valori n k separate printr-un spatiu reprezentnd a a numrul total de senatori i numrul de strazi pe care circul lectica gratuit a s a a a pe urmtorele n1 linii se a cte dou valori i j separate printr-un spatiu, a a a a reprezentnd numerele de ordine a dou aezri senatoriale a a s a ntre care exist strad. a a Date de ieire s Pe prima linie a ierului cezar.out se va scrie costul total minim al transs portrii tuturor senatorilor pentru o alegere optim a celor k strzi pe care va a a a circula lectica gratuit i a locului unde va amplasat sala de edinte a Senatuas a s lui. Restrictii i precizri s a

14.3. EXEMPLE

261

1 < n 10000, 0 < k < n 1 i, j n , i = j Oricare dou perechi de valori de pe liniile 2, 3, ..., n din ierul de intrare a s reprezint dou strzi distincte. a a a Perechile din ierul de intrare sunt date astfel at respect conditiile din s nc a problem. a Pentru 25% din teste n 30, pentru 25% din teste 30 < n 1000, pentru 25% din teste 1000 < n 3000, pentru 10% din teste 3000 < n 5000, pentru 10% din teste 5000 < n 10000. Exemplu

cezar.in 13 3 12 23 28 78 75 54 56 89 8 10 10 11 10 12 10 13

cezar.out 11

Explicatie Costul minim se obtine, de exemplu, pentru alegerea celor 3 strzi a ntre aezrile s a 5-7, 7-8, 8-10 i a slii de edinte a Senatului s a s aezarea 8 (dup cum este evidentiat n s a desen). n Exist i alte alegeri pentru care se obtine as solutia 11.

Timp maxim de executie/test: 0.5 secunde Indicatii de rezolvare - descriere solutie * O implementare posibil utilizeaz metoda GREEDY, O(n (n k)) sau a a O((n k) log(n)).

262

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Se elimin succesiv, dintre frunzele existente la un moment dat, frunza de a cost minim. Toate nodurile au costul initial 1. La eliminarea unei frunze, se incre menteaz cu 1 costul tatlui acesteia. Validitatea metodei rezult din observatia a a a c, la eliminarea unei frunze oarecare, tatl acesteia poate deveni frunz la rndul a a a a lui, dar cu un cost strict mai mare dect al frunzei eliminate. a Se poate retine arborele cu ajutorul listelor de adiacent (liniare sau organi a zate ca arbori de cutare), iar frunzele se pot memora a ntr-un minheap de costuri, structur care se actualizeaz timp logaritmic. a a n Codul surs a

Varianta 1: import java.io.*; class cezar { static StreamTokenizer st; static PrintWriter out; static int n, ns; static static static static static int[] int[] int[] int[] int[] v1 = new int[10001]; v2 = new int[10001]; g = new int[10001]; ca = new int[10001]; nde = new int[10001];

// gradele // ca[k]=costul acumulat in nodul k !!! // nde[k]=nr "descendenti" eliminati pana in k

static void citire() throws IOException { int i,j,k; st.nextToken(); n=(int)st.nval; st.nextToken(); ns=(int)st.nval; for(i=1;i<=n;i++) g[i]=ca[i]=nde[i]=0; for(k=1;k<=n-1;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; v1[k]=i; v2[k]=j; // curatenie ...

14.3. EXEMPLE g[i]++; g[j]++; } }// citire(...) static int tata(int i) // mai bine cu lista de adiacenta ... { int k,t; t=-1; // initializarea aiurea ... for(k=1;k<=n-1;k++) // n-1 muchii { if( (v1[k]==i) && (g[v2[k]] > 0) ) {t=v2[k]; break;} else if( (v2[k]==i) && (g[v1[k]] > 0) ) {t=v1[k]; break;} } return t; } static void eliminm() { int i, imin, timin; int min; // frunze(g=1) cu cost=min

263

min=Integer.MAX_VALUE; imin=-1; // for(i=1;i<=n;i++) // if(g[i]==1) // if(nde[i]+1<min) // { min=nde[i]+1; imin=i; }

initializare aiurea ... mai bine cu heapMIN ... i=frunza ... aici este testul CORECT ... !!!

timin=tata(imin); g[imin]--; g[timin]--; ca[timin]=ca[timin]+ca[imin]+nde[imin]+1; nde[timin]=nde[timin]+nde[imin]+1; // nr descendenti eliminati }// elimin() public static void main(String[] args) throws IOException { int k,senat=0,cost=0; st=new StreamTokenizer(new BufferedReader(new FileReader("cezar20.in"))); out=new PrintWriter(new BufferedWriter(new FileWriter("cezar.out")));

264 citire();

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

for(k=1;k<=n-1-ns;k++) eliminm(); for(k=1;k<=n;k++) if(g[k]>0) { cost+=ca[k]; senat=k; } System.out.println("\n"+cost+" "+senat); out.println(cost+" "+senat); out.close(); //afisv(g,1,n); }// main }// class

Varianta 2: import java.io.*; class cezar // cost initial = 1 pentru toate nodurile ... { static StreamTokenizer st; static PrintWriter out; static int n, ns,s=0; static static static static int[] v1 = new int[10001]; int[] v2 = new int[10001]; int[] g = new int[10001]; int[] ca = new int[10001];

// gradele // ca[k]=costul acumulat in nodul k !!!

static void afisv(int[] v,int i1, int i2) { int i; for(i=i1;i<=i2;i++) { System.out.print(v[i]+" "); if(i%50==0) System.out.println(); } System.out.println(); }

14.3. EXEMPLE

265

static void citire() throws IOException { int i,j,k; st.nextToken(); n=(int)st.nval; st.nextToken(); ns=(int)st.nval; for(i=1;i<=n;i++) { g[i]=0; ca[i]=1; } // curatenie ...

// cost initial in nodul i

for(k=1;k<=n-1;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; v1[k]=i; v2[k]=j; g[i]++; g[j]++; } //afisv(v1,1,n-1); //afisv(v2,1,n-1); //afisv(g,1,n); }// citire(...) static int tata(int i) // mai bine cu liste de adiacenta ... { int k,t; t=-1; // initializarea aiurea ... for(k=1;k<=n-1;k++) // este mai bine cu lista de adiacenta ? { if( (v1[k]==i) && (g[v2[k]]>0)) {t=v2[k]; break;} else if( (v2[k]==i) && (g[v1[k]]>)) {t=v1[k]; break;} } return t; } static void eliminm() { // frunze(g=1) cu cost=min

266

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY int i, imin, timin; int min; min=Integer.MAX_VALUE; imin=-1; for(i=1;i<=n;i++) if(g[i]==1) if(ca[i]<min) { min=ca[i]; imin=i; } timin=tata(imin); g[imin]--; g[timin]--; ca[timin]=ca[timin]+min; s+=min;

// // // //

initializare aiurea ... mai bine cu heapMIN ... i=frunza cost acumulat

//System.out.println(" Elimin nodul "+imin+ // " timin = "+timin+" ca = "+ca[timin]); }// elimin() public static void main(String[] args) throws IOException { int k,senat=0; st=new StreamTokenizer(new BufferedReader(new FileReader("cezar20.in"))); out=new PrintWriter(new BufferedWriter(new FileWriter("cezar.out"))); citire(); for(k=1;k<=n-1-ns;k++) { eliminm(); } //afisv(c,1,n); for(k=1;k<=n;k++) if(g[k]>0) { senat=k; break;

14.3. EXEMPLE } System.out.println("\n"+s+" "+senat); out.println(s+" "+senat); out.close(); }// main }// class

267

268

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Capitolul 15

Metoda backtracking
Metoda backtracking se utilizeaz pentru determinarea unei submultimi a a unui produs cartezian de forma S1 S2 ... Sn (cu multimile Sk nite) care are anumite proprieti. Fiecare element (s1 , s2 , ..., sn ) al submultimii produsului at cartezian poate interpretat ca solutie a unei probleme concrete. majoritatea cazurilor nu oricare element al produsului cartezian este solutie In ci numai cele care satisfac anumite restrictii. De exemplu, problema determinrii a tuturor combinrilor de m elemente luate cte n (unde 1 n m) din multimea a a {1, 2, ..., m} poate reformulat ca problema determinrii submultimii produsului a a cartezian {1, 2, ..., m}n denit astfel: a {(s1 , s2 , ..., sn ) {1, 2, ..., m}n |si = sj , i = j, 1 i, j n}. Metoda se aplic numai atunci cnd nu exist nici o alt cale de rezolvare a a a a a problemei propuse, deoarece timpul de executie este de ordin exponential.

15.1

Generarea produsului cartezian

Pentru nceptori, nu metoda backtracking sine este dicil de eles ci a n nt modalitatea de generare a produsului cartezian.

15.1.1

Generarea iterativ a produsului cartezian a

Presupunem c multimile Si sunt formate din numere a ntregi consecutive cuprinse ntre o valoare min i o valoare max. De exemplu, dac min = 0, max = 9 s a atunci Si = {0, 1, ..., 9}. Dorim s generm produsul cartezian S1 S2 ... Sn a a 269

270

CAPITOLUL 15. METODA BACKTRACKING

(de exemplu, pentru n = 4). Folosim un vector cu n elemente a = (a1 , a2 , ..., an ) i vom atribui ecrei componente ai valori cuprinse s a ntre min i max. s 0 1 2 3 4 5 0 1 ... ... n n+1 Ne trebuie un marcaj pentru a preciza faptul c o anumit component este a a a goal (nu are plasat ea o valoare valid). acest scop folosim variabila gol care a a n a In poate initializat cu orice valoare a ntreag care nu este intervalul [min, max]. a n Este totui util initializarea gol=min-1;. s a Cum ncepem generarea produsului cartezian? O prim variant este s atribuim tuturor componentelor ai valoarea min i a a a s avem astfel un prim element al produsului cartezian, pe care putem s-l aam. a s 0 1 2 3 4 5 0 0 0 0 0 1 ... ... n n+1 Trebuie s construim urmtoarele elemente ale produsului cartezian. Este a a util, acest moment, imaginea kilometrajelor de maini sau a contoarelor de a n s energie electic, de ap, de gaze, etc. Urmtoarea conguratie a acestora este a a a 0 1 2 3 4 5 0 0 0 1 dup care urmeaz a a 0 1 ... ... n n+1 0 1 2 3 4 5 0 0 0 2 0 1 ... ... n n+1 Folosim o variabil k pentru a urmri pozitia din vector pe care se schimb a a a valorile. La nceput k = n i, pe aceast pozitie k, valorile se schimb crescnd de s a a a la min ctre max. Se ajunge astfel la conguratia (k = 4 = n aici!) a 0 1 2 3 4 5 0 0 0 9 0 1 ... ... n n+1 Ce se ampl mai departe? Apare conguratia nt a 0 1 2 3 4 5 0 0 1 0 ntr-un mod ciudat! 0 1 ... ... n n+1 Ei bine, se poate spune c aici este cheia metodei backtraching! Dac reuim a a s s elegem acest pas de trecere de la o conguratie la alta i s formalizm ce a nt s a a am observat, atunci am descoperit singuri aceast metod! a a Pe pozitia k = n, odat cu aparitia valorii 9 = max, s-au epuizat toate a valorile posibile, aa c vom considera c pozitia k este goal i vom trece pe o s a a a s pozitie mai la stnga, deci k = k 1 (acum k = 3). a 0 1 2 3 4 5 0 0 0 0 1 ... ... n n+1

15.1. GENERAREA PRODUSULUI CARTEZIAN

271

Pe pozitia k = 3 se af valoarea 0 care se va schimba cu urmtoarea valoare a a (dac exist o astfel de valoare max), deci acest caz cu 1. a a n 0 1 2 3 4 5 0 0 1 0 1 ... ... n n+1 Dup plasarea unei valori pe pozitia k, se face pasul spre dreapta (k = k + 1). a 0 1 2 3 4 5 0 0 1 0 1 ... ... n n+1 Aici (dac nu am ieit afara vectorului, urma pasului fcut spre dreapta!), a s n n a cazul nostru k = 4 i pozitia este goal, se plaseaz prima valoare valid (deci n s a a a valoarea min). 0 1 2 3 4 5 0 0 1 0 0 1 ... ... n n+1 Cum trecerea de la o valoare la alta se face prin creterea cu o unitate a valorii s vechi iar acum facem trecerea gol min, elegem de ce este util s initializm nt a a variabila gol cu valoarea min 1. S considerm c s-a ajuns la conguratia a a a 0 1 2 3 4 5 0 0 9 9 0 1 ... ... n n+1 Aici k = 4 = n i 9 = max. Pe pozitia k nu se mai poate pune o nou valoare s a i atunci: s se pune gol pe pozitia k se face pasul spre stnga, deci k = k 1 a 0 1 2 3 4 5 0 0 9 0 1 ... ... n n+1 Aici k = 3 i 9 = max. Pe pozitia k nu se mai poate pune o nou valoare i s a s atunci: se pune gol pe pozitia k se face pasul spre stnga, deci k = k 1 a 0 1 2 3 4 5 0 0 0 1 ... ... n n+1 Aici k = 2 i 0 < max. Pe pozitia k se poate pune o nou valoare i atunci: s a s se pune 0 + 1 = 1 = urmtoarea valoare pe pozitia k a se face pasul spre dreapta, deci k = k + 1 0 1 2 3 4 5 0 1 0 1 ... ... n n+1 Aici k = 3 i gol=-1< max. Pe pozitia k se poate pune o nou valoare: s a se pune gol+1= 1 + 1 = 0 = urmtoarea valoare pe pozitia k a

272

CAPITOLUL 15. METODA BACKTRACKING

se face pasul spre dreapta, deci k = k + 1 0 1 2 3 4 5 0 1 0 0 1 ... ... n n+1 Aici k = 4 i gol=-1< max. Pe pozitia k se poate pune o nou valoare: s a se pune gol+1= 1 + 1 = 0 = urmtoarea valoare pe pozitia k a se face pasul spre dreapta, deci k = k + 1 0 1 2 3 4 5 0 1 0 0 0 1 ... ... n n+1 iar aici k = n + 1 i am ajuns afara vectorului! Nu-i nimic! Se aeaz s n s a solutia 0100 i se face pasul la stnga. Aici k = 4 i 0 < max. Pe pozitia k se s a s poate pune o nou valoare: a se pune 0 + 1 = 1 = urmtoarea valoare pe pozitia k a se face pasul spre dreapta, deci k = k + 1 0 1 2 3 4 5 0 1 0 1 0 1 ... ... n n+1 A aprut mod evident urmtoarea regul: ajungnd pe pozitia k n, a n a a a ncercm s punem urmtoarea valoare (gol are ca urmtoarea valoare pe min) a a a a pe aceast pozitie i a s dac se poate: se pune urmtoarea valoare i se face pasul spre dreapta; a a s dac nu se poate: se pune gol=min 1 i se face pasul spre stnga. a s a Presupunem c s-a ajuns la conguratia a 0 1 2 3 4 5 9 9 9 9 care se aeaz ca solutie. Ajungem la k = 4 s a 0 1 ... ... n n+1 0 1 2 3 4 5 9 9 9 9 se golete pozitia i ajungem la k = 3 s s 0 1 ... ... n n+1 0 1 2 3 4 5 9 9 9 se golete pozitia i ajungem la k = 2 s s 0 1 ... ... n n+1 0 1 2 3 4 5 9 9 se golete pozitia i ajungem la k = 1 s s 0 1 ... ... n n+1 0 1 2 3 4 5 9 se golete pozitia i ajungem la k = 0 s s 0 1 ... ... n n+1 0 1 2 3 4 5 iar aici k = 0 i am ajuns afara vectorului! s n 0 1 ... ... n n+1 Nu-i nimic! Aici s-a terminat generarea produsului cartezian!

15.1. GENERAREA PRODUSULUI CARTEZIAN

273

Putem descrie acum algoritmul pentru generarea produsului cartezian S n , unde S = {min, min + 1, ..., max}: structuri de date: a[1..n] vectorul solutie k pozitia vectorul solutie, cu conventiile k = 0 precizeaz terminarea n a generrii, iar k = n + 1 precizeaz faptul c s-a construit o solutie care poate a a a aat; s a proces de calcul: 1. se construiete prima solutie s 2. se plaseaz pozitia afara vectorului ( dreapta) a n n 3. ct timp nu s-a terminat generarea a 4. dac este deja construit o solutie a a 5. se aeaz solutia i se face pasul spre stnga s a s a 6. altfel, dac se poate mri valoarea pe pozitia curent a a a 7. se mrete cu 1 valoarea i se face pasul spre dreapta a s s 8. altfel, se golete pozitia curent i se face pasul spre stnga s as a 3. revenire la 3. Implementarea acestui algoritm Java este urmtoarea: n a class ProdCartI1 // 134.000 ms pentru 8 cu 14 { static int n=8, min=1, max=14, gol=min-1; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } public static void main (String[] args) { int i, k; long t1,t2; t1=System.currentTimeMillis(); for(i=1;i<=n;i++) a[i]=min; k=n+1; while (k>0) if (k==n+1) {/* afis(a); */ --k;} else { if (a[k]<max) ++a[k++]; else a[k--]=gol; } t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } }

274

CAPITOLUL 15. METODA BACKTRACKING

O alt variant ar putea initializarea vectorului solutie cu gol i plasarea a a s pe prima pozitie vector. Nu mai avem acest caz o solutie gata construit dar n n a algoritmul este acelai. Implementarea acest caz este urmtoarea: s n a class ProdCartI2 // 134.000 ms pentru 8 cu 14 { static int n=8, min=1, max=14, gol=min-1; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } public static void main (String[] args) { int i, k; long t1,t2; t1=System.currentTimeMillis(); for(i=1;i<=n;i++) a[i]=gol; k=1; while (k>0) if (k==n+1) {/* afis(a); */ --k;} else { if (a[k]<max) ++a[k++]; else a[k--]=gol; } t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } }

15.1.2

Generarea recursiv a produsului cartezian a

class ProdCartR1 // 101.750 ms pentru 8 cu 14 { static int n=8, min=1,max=14; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); }

15.1. GENERAREA PRODUSULUI CARTEZIAN

275

static void f(int k) { int i; if (k>n) {/* afis(a); */ return; }; for(i=min;i<=max;i++) { a[k]=i; f(k+1); } } public static void main (String[] args) { long t1,t2; t1=System.currentTimeMillis(); f(1); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } } class ProdCartR2 // 71.300 ms pentru 8 cu 14 { static int n=8, min=1,max=14; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } static void f(int k) { int i; for(i=min;i<=max;i++) { a[k]=i; if (k<n) f(k+1); } } public static void main (String[] args) { long t1,t2; t1=System.currentTimeMillis(); f(1); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } }

276

CAPITOLUL 15. METODA BACKTRACKING

class ProdCartCar1 { static char[] x; static int n,m; static char[] a={0,A,X}; public static void main(String[] args) { n=4; m=a.length-1; x=new char[n+1]; f(1); } static void f(int k) { int i; for(i=1;i<=m;i++) { x[k]=a[i]; if(k<n) f(k+1); else afisv(); } } static void afisv() { int i; for(i=1; i<=n; i++) System.out.print(x[i]); System.out.println(); } }//class class ProdCartCar2 { static char[] x; static int n; static int[] m; static char[][] a = { {0}, {0,A,B}, {0,1,2,3} };

15.2. METODA BACTRACKING public static void main(String[] args) { int i; n=a.length-1; m=new int[n+1]; for(i=1;i<=n;i++) m[i]=a[i].length-1; x=new char[n+1]; f(1); } static void f(int k) { int j; for(j=1;j<=m[k];j++) { x[k]=a[k][j]; if(k<n) f(k+1); else afisv(); } } static void afisv() { int i; for(i=1; i<=n; i++) System.out.print(x[i]); System.out.println(); } }// class

277

15.2

Metoda bactracking

Se folosete pentru rezolvarea problemelor care s ndeplinesc urmtoarele conditii: a 1. nu se cunoate o alt metod mai rapid de rezolvare; s a a a 2. solutia poate pus sub forma unui vector x = (x1 , x2 , ..., xn ) cu xi Ai , a i = 1, ..., n; 3. multimile Ai sunt nite. Tehnica backtracking pleac de la urmtoarea premis: a a a dac la un moment dat nu mai am nici o ans s ajung la solutia a s a a cutat, renunt s continui pentru o valoare pentru care tiu c nu a a a s a ajung la nici un rezultat.

278

CAPITOLUL 15. METODA BACKTRACKING

Specicul metodei const maniera de parcurgere a spatiului solutiilor. a n solutiile sunt construite succesiv, la ecare etap ind completat cte o a a a component; a alegerea unei valori pentru o component se face a ntr-o anumit ordine a astfel at s e asigurat o parcurgere sistematic a spatiului A1 A2 ... An ; nc a a a la completarea componentei k se veric dac solutia partial (x1 , x2 , ..., xk ) a a a veric conditiile induse de restrictiile problemei (acestea sunt numite conditii de a continuare); dac au fost a ncercate toate valorile corespunztoare componentei k i a a s nc nu a fost g sit o solutie, sau dace dorete determinarea unei noi solutii, se revine a a s s la componenta anterioar (k1) i se a s ncearc urmtoarea valoare corespunz toare a a a acesteia, .a.m.d. s procesul de cutare i revenire este continuat pn cnd este gsit o solutie a s a a a a a (dac este sucient o solutie) sau pn cnd au foste testate toate conguratiile a a a a a posibile (dac sunt necesare toate solutiile). a gur este ilustrat modul de parcurgere a spatiului solutiilor cazul In a n generrii produsului cartezian{0, 1}4 . a cazul care sunt specicate restrictii (ca de exemplu, s nu e dou In n a a componente alturate egale) anumite ramuri ale structurii arborescente asociate a sunt abandonate nainte de a atinge lungimea n.

x1

x2

x3

x4

x1

x2

x3

x4

15.2. METODA BACTRACKING

279

aplicarea metodei pentru o problem concret se parcurg urmtoarele In a a a etape: se alege o reprezentare a solutiei sub forma unui vector cu n componente; se identic multimile A1 , A2 , ..., An i se stabilete o ordine ntre elemente a s s care s indice modul de parcurgere a ecrei multimi; a a pornind de la restrictiile problemei se stabilesc conditiile de validitate ale solutiilor partiale (conditiile de continuare). aplicatiile concrete solutia nu este obtinut numai cazul care k = n In a n n ci este posibil s e satisfcut o conditie de gsire a solutiei pentru k < n (pentru a a a a anumite probleme nu toate solutiile contin acelai numr de elemente). s a

15.2.1

Bactracking iterativ

Structura general a algoritmului este: a for(k=1;k<=n;k++) x[k]=gol; initiarizarea vectorului solutie k=1; pozitionare pe prima component a while (k>0) ct timp exist componente de analizat a a if (k==n+1) dac x este solutie a {as(x); k;} atunci: aare solutie i pas stnga s s a else altfel: { if(x[k]<max[k]) dac exist elemente de a a ncercat if(posibil(1+x[k])) dac 1+x[k] este valid a a ++x[k++]; atunci mresc i fac pas dreapta a s else ++x[k]; altfel mresc i rmn pe pozitie a s a a else x[k]=gol; altfel golesc i fac pas dreapta s } Vectorul solutie x contine indicii din multimile A1 , A2 , ..., An . Mai precis, x[k] = i nseamn c pe pozitia k din solutia x se a ai din Ak . a a a

15.2.2

Backtracking recursiv

Varianta recursiv a algoritmului backtracking poate realizat dup o a a a schem asemntoare cu varianta recursiv. Principiul de functionare al functiei a a a a f (primul apel este f (1)) corespunztor unui nivel k este urmtorul: a a situatia care avem o solutie, o aam i revenim pe nivelul anterior; n n s s caz contrar, se initializeaz nivelul i se caut un succesor; n a s a cnd am gsit un succesor, vericm dac este valid. caz armativ, a a a a In procedura se autoapeleaz pentru k + 1; caz contrar urmnd a se continua a n a cutarea succesorului; a dac nu avem succesor, se trece la nivelul inferior k 1 prin ieirea din a s procedura recursiv f . a

280

CAPITOLUL 15. METODA BACKTRACKING

15.3

Probleme rezolvate

15.3.1

Generarea aranjamentelor

Reprezentarea solutiilor: un vector cu n componente. Multimile Ak : {1, 2, ..., m}. Restrictiile i conditiile de continuare: Dac x = (x1 , x2 , ..., xn ) este o solutie s a ea trebuie s respecte restrictiile: xi = xj pentru oricare i = j. Un vector cu k a elemente (x1 , x2 , ..., xk ) poate conduce la o solutie numai dac satisface conditiile a de continuare xi = xj pentru orice i = j, unde i, j {1, 2, ..., k}. Conditia de gsire a unei solutii: Orice vector cu n componente care respect a a restrictiile este o solutie. Cnd k = n + 1 a fost gsit o solutie. a a a Prelucrarea solutiilor: Fiecare solutie obtinut este aat. a s a class GenAranjI1 { static int n=2, min=1,max=4, gol=min-1; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } static boolean gasit(int val, int pozmax) { for(int i=1;i<=pozmax;i++) if (a[i]==val) return true; return false; } public static void main (String[] args) { int i, k; for(i=1;i<=n;i++) a[i]=gol; k=1; while (k>0) if (k==n+1) {afis(a); --k;} else

15.3. PROBLEME REZOLVATE {

281

if(a[k]<max) if(!gasit(1+a[k],k-1)) ++a[k++]; // maresc si pas dreapta else ++a[k]; // maresc si raman pe pozitie else a[k--]=gol; } } } class GenAranjI2 { static int n=2, min=1,max=4; static int gol=min-1; static int[] a=new int[n+1]; static void afis() { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } static boolean posibil(int k) { for(int i=1;i<k;i++) if (a[i]==a[k]) return false; return true; } public static void main (String[] args) { int i, k; boolean ok; for(i=1;i<=n;i++) a[i]=gol; k=1; while (k>0) { ok=false; while (a[k] < max) // caut o valoare posibila { ++a[k]; ok=posibil(k); if(ok) break; } if(!ok) k--; else if (k == n) afis();

282 else a[++k]=0; } } }

CAPITOLUL 15. METODA BACKTRACKING

class GenAranjR1 { static int n=2, m=4, nsol=0; static int[] a=new int[n+1]; static void afis() { System.out.print(++nsol+" : "); for(int i=1;i<=n;i++) System.out.print(a[i]+" "); System.out.println(); } static void f(int k) { int i,j; boolean gasit; for(i=1; i<=m; i++) { if(k>1) // nu este necesar ! { gasit=false; for(j=1;j<=k-1;j++) if(i==a[j]) { gasit=true; break; // in for j } if(gasit) continue; // in for i } a[k]=i; if(k<n) f(k+1); else afis(); } } public static void main(String[] args) { f(1); } }// class

15.3. PROBLEME REZOLVATE class GenAranjR2 { static int[] a; static int n,m; public static void main(String[] args) { n=2; m=4; a=new int[n+1]; f(1); }

283

static void f(int k) { boolean ok; int i,j; for(i=1;i<=m;i++) { ok=true; // k=1 ==> nu am in stanga ... for nu se executa ! for(j=1;j<k;j++) if(i==a[j]) { ok=false; break; } if(!ok) continue; a[k]=i; if(k<n) f(k+1); else afisv(); } } static void afisv() { int i; for(i=1; i<=n; i++) System.out.print(a[i]); System.out.println(); } }

284

CAPITOLUL 15. METODA BACKTRACKING

15.3.2

Generarea combinrilor a

Sunt prezentate mai multe variante (iterative i recursive) cu scopul de a s vedea diferite modaliti de a ctiga timp executie (mai mult sau mai putin at as n semnicativ!). class GenCombI1a // 4550 ms cu 12 22 // { // 7640 ms cu 8 14 cu afis static int n=8, min=1,max=14; static int gol=min-1,nv=0; static int[] a=new int[n+1]; static void afis(int[] a) { int i; System.out.print(++nv+" : "); for(i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } public static void main (String[] args) { int i, k; long t1,t2; t1=System.currentTimeMillis(); for(i=0;i<=n;i++) a[i]=gol; // initializat si a[0] care nu se foloseste!! k=1; while (k>0) if (k==n+1) { afis(a); --k; } else { if(a[k]<max) if(1+a[k]>a[k-1]) ++a[k++]; // maresc si pas dreapta (k>1) ... else ++a[k]; // maresc si raman pe pozitie else a[k--]=gol; } t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } }// class 40 ms cu 8 14 fara afis

15.3. PROBLEME REZOLVATE class GenCombI1b // 3825 ms 12 22 sa nu mai merg la n+1 !!!! { static int n=12, min=1,max=22; static int gol=min-1; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); }

285

public static void main (String[] args) { int i; long t1,t2; t1=System.currentTimeMillis(); for(i=0;i<=n;i++) a[i]=gol; // initializat si a[0] care nu se foloseste!! int k=1; while (k>0) { if(a[k]<max) if(1+a[k]>a[k-1]) if(k<n) ++a[k++]; // maresc si pas dreapta (k>1) ... else { ++a[k]; /* afis(a); */}// maresc, afisez si raman pe pozitie else ++a[k]; // maresc si raman pe pozitie else a[k--]=gol; } t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } }// class class GenCombI2a // 1565 ms 12 22 { static int n=12, min=1,max=22; static int gol=min-1; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); }

286

CAPITOLUL 15. METODA BACKTRACKING

public static void main (String[] args) { int i, k; long t1,t2; t1=System.currentTimeMillis(); for(i=0;i<=n;i++) a[i]=gol; // initializat si a[0] care nu se foloseste!! k=1; while (k>0) if (k==n+1) {/* afis(a); */ --k;} else { if(a[k]<max-(n-k)) // optimizat !!! if(1+a[k]>a[k-1]) ++a[k++]; // maresc si pas dreapta (k>1) ... else ++a[k]; // maresc si raman pe pozitie else a[k--]=gol; } t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } } class GenCombI2b // 1250 ms 12 22 { static int n=12, min=1,max=22; static int gol=min-1; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } public static void main (String[] args) { int i, k; long t1,t2; t1=System.currentTimeMillis(); for(i=0;i<=n;i++) a[i]=gol; // initializat si a[0] care nu se foloseste!! k=1; while (k>0)

15.3. PROBLEME REZOLVATE {

287

if(a[k]<max-(n-k)) // optimizat !!! if(1+a[k]>a[k-1]) if(k<n) ++a[k++]; // maresc si pas dreapta (k>1) ... else { ++a[k]; /* afis(a); */}// maresc, afisez si raman pe pozitie else ++a[k]; // maresc si raman pe pozitie else a[k--]=gol; } t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } } class GenCombI3a // 835 ms 12 22 { static int n=12, min=1, max=22, gol=min-1; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } public static void main (String[] args) { int i, k; // si mai optimizat !!! long t1,t2; t1=System.currentTimeMillis(); for(i=0;i<=n;i++) a[i]=i-gol-1; // initializat si a[0] care nu se foloseste!! k=1; while (k>0) if (k==n+1) {/* afis(a); */ --k;} else { if(a[k]<max-(n-k)) // optimizat !!! if(1+a[k]>a[k-1]) ++a[k++]; // maresc si pas dreapta (k>1) ... else ++a[k]; // maresc si raman pe pozitie else a[k--]=k-gol-1; // si mai optimizat !!! } t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } }

288

CAPITOLUL 15. METODA BACKTRACKING

class GenCombI3b // 740 ms 12 22 { static int n=12, min=1, max=22, gol=min-1; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } public static void main (String[] args) { int i, k; long t1,t2; t1=System.currentTimeMillis(); for(i=0;i<=n;i++) a[i]=i-gol-1; // si mai optimizat !!! k=1; while (k>0) { if(a[k]<max-(n-k)) // optimizat !!! { ++a[k]; // maresc if(a[k]>a[k-1]) if(k<n) k++; // pas dreapta /* else afis(a); */ // afisez } else a[k--]=k-gol-1; // si mai optimizat !!! } t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } } class GenCombR1a // 2640 ms 12 22 { static int[] x; static int n,m; public static void main(String[] args) { long t1,t2; t1=System.currentTimeMillis(); n=12; m=22;

15.3. PROBLEME REZOLVATE x=new int[n+1]; f(1); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } static void f(int k) { for(int i=1;i<=m;i++) { if(k>1) if(i<=x[k-1]) continue; x[k]=i; if(k<n) f(k+1);/* else afisv(); */ } } static void afisv() { for(int i=1; i<=n; i++) System.out.print(x[i]); System.out.println(); } }

289

class GenCombR1b // 2100 ms 12 22 { static int n=12,m=22; static int[] a=new int[n+1]; static void afis(int[] a) { for(int i=1;i<=n;i++) System.out.print(a[i]); System.out.println(); } static void f(int k) { for(int i=1;i<=m;i++ ) { if (i<=a[k-1]) continue; // a[0]=0 deci merge ! a[k]=i; if(k<n) f(k+1); /* else afis(a); */ } }

290

CAPITOLUL 15. METODA BACKTRACKING

public static void main (String [] args) { long t1,t2; t1=System.currentTimeMillis(); f(1); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); }//main }//class class GenCombR2a // 510 ms 12 22 { static int n=12, m=22, nsol=0, ni=0; ; static int[] x=new int[n+1]; static void afisx() { System.out.print(++nsol+" ==> "); for(int i=1;i<=n;i++) System.out.print(x[i]+" "); System.out.println(); } static void afis(int k) // pentru urmarirea executiei ! { // ni = numar incercari !!! int i; System.out.print(++ni+" : "); for(i=1;i<=k;i++) System.out.print(x[i]+" "); System.out.println(); } static void f(int k) { for(int i=k; i<=m-n+k; i++) // imbunatatit ! { if(k>1) { // afis(k-1); if(i<=x[k-1]) continue; } x[k]=i; if(k<n) f(k+1); /* else afisx(); */ } }

15.3. PROBLEME REZOLVATE

291

public static void main(String[] args) { long t1,t2; t1=System.currentTimeMillis(); f(1); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } }// class class GenCombR2b // 460 ms 12 22 { static int n=12, m=22, nsol=0,ni=0; static int[] x=new int[n+1]; static void afisx() { System.out.print(++nsol+" ==> "); for(int i=1;i<=n;i++) System.out.print(x[i]+" "); System.out.println(); } static void afis(int k) // pentru urmarirea executiei ! { // ni = numar incercari !!! System.out.print(++ni+" : "); for(int i=1;i<=k;i++) System.out.print(x[i]+" "); System.out.println(); } static void f(int k) { for(int i=k; i<=m-n+k; i++) // imbunatatit ! { if(i<=x[k-1]) continue; x[k]=i; if(k<n) f(k+1); /* else afisx(); */ } } public static void main(String[] args) { long t1,t2; t1=System.currentTimeMillis();

292

CAPITOLUL 15. METODA BACKTRACKING f(1); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms");

} }// class class GenCombR3a // 165 ms 12 22 { static int n=12; static int m=22; static int[] x=new int[n+1]; static int nsol=0,ni=0; static void afisx() { int i; System.out.print(++nsol+" ==> "); for(i=1;i<=n;i++) System.out.print(x[i]+" "); System.out.println(); } static void afis(int k) { int i; System.out.print(++ni+" : "); for(i=1;i<=k;i++) System.out.print(x[i]+" "); System.out.println(); } static void f(int k) { int i; for (i=x[k-1]+1; i<=m-n+k; i++) // si mai imbunatatit !!! { if(k>1) { // afis(k-1); if(i<=x[k-1]) continue; } x[k]=i; if(k<n) f(k+1); /* else afisx(); */ } }

15.3. PROBLEME REZOLVATE public static void main(String[] args) { long t1,t2; t1=System.currentTimeMillis(); f(1); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } }// class class GenCombR3b // 140 ms 12 22 { static int n=12; static int m=22; static int[] x=new int[n+1]; static int nsol=0; static void afisx() { int i; System.out.print(++nsol+" : "); for(i=1;i<=n;i++) System.out.print(x[i]+" "); System.out.println(); } static void f(int k) { int i; for (i=x[k-1]+1; i<=m-n+k; i++) { x[k]=i; if(k<n) f(k+1); /* else afisx(); */ } } public static void main(String[] args) { long t1,t2; t1=System.currentTimeMillis(); f(1); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } }// class

293

294

CAPITOLUL 15. METODA BACKTRACKING

15.3.3

Problema reginelor pe tabla de ah s

O problem clasic de generare a conguratiilor ce respect anumite restrictii a a a este cea a amplasrii damelor pe o tabl de ah astfel at s nu se atace reciproc. a a s nc a Reprezentarea solutiei: un vector x unde xi reprezint coloana pe care se a a a regina dac linia este i. a Restrictii i conditii de continuare: xi = xj pentru oricare i = j (reginele nu s se atac pe coloan) i |xi xj | = |i j| (reginele nu se atac pe diagonal). a a s a a Sunt prezentate o variant iterativ i una recursiv. a as a class RegineI1 { static int[] x; static int n,nv=0; public static void main(String[] args) { n=5; regine(n); } static void regine(int n) { int k; boolean ok; x=new int[n+1]; for(int i=0;i<=n;i++) x[i]=i; k=1; x[k]=0; while (k>0) { ok=false; while (x[k] <= n-1) // caut o valoare posibila { ++x[k]; ok=posibil(k); if(ok) break; } if(!ok) k--; else if (k == n) afis(); else x[++k]=0; } }//regine()

15.3. PROBLEME REZOLVATE

295

static boolean posibil(int k) { for(int i=1;i<=k-1;i++) if ((x[k]==x[i])||((k-i)==Math.abs(x[k]-x[i]))) return false; return true; } static void afis() { int i; System.out.print(++nv+" : "); for(i=1; i<=n; i++) System.out.print(x[i]+" "); System.out.println(); } }// class

class RegineR1 { static int[] x; static int n,nv=0; public static void main(String[] args) { n=5; x=new int[n+1]; f(1); } static void f(int k) { boolean ok; int i,j; for(i=1;i<=n;i++) { ok=true; for(j=1;j<k;j++) if((i==x[j])||((k-j)==Math.abs(i-x[j]))) {ok=false; break;} if(!ok) continue; x[k]=i; if(k<n) f(k+1); else afisv(); } }

296

CAPITOLUL 15. METODA BACKTRACKING

static void afisv() { int i; System.out.print(++nv+" : "); for(i=1; i<=n; i++) System.out.print(x[i]+" "); System.out.println(); } }

15.3.4

Turneul calului pe tabla de ah s

import java.io.*; class Calut { static final int LIBER=0,SUCCES=1,ESEC=0,NMAX=8; static final int[] a={0,2,1,-1,-2,-2,-1,1,2}; // miscari posibile pe Ox static final int[] b={0,1,2,2,1,-1,-2,-2,-1}; // miscari posibile pe Oy static int[][] tabla=new int[NMAX+1][NMAX+1]; // tabla de sah static int n,np,ni=0; // np=n*n public static void main(String[] args) throws IOException { BufferedReader br=new BufferedReader( new InputStreamReader(System.in)); while ((n<3)||(n>NMAX)) { System.out.print("Dimensiunea tablei de sah [3.."+NMAX+"] : "); n=Integer.parseInt(br.readLine()); } np=n*n; for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) tabla[i][j]=LIBER; tabla[1][1]=1; if(incerc(2,1,1)==SUCCES) afisare(); else System.out.println("\nNu exista solutii !!!"); System.out.println("\n\nNumar de incercari = "+ni); } // main static void afisare() { System.out.println("\r----------------------------------");

15.3. PROBLEME REZOLVATE for(int i=1;i<=n;i++) { System.out.println(); for(int j=1;j<=n;j++) System.out.print("\t"+tabla[i][j]); } }// afisare()

297

static int incerc(int i, int x, int y) { int xu,yu,k,rezultatIncercare; ni++; k=1; rezultatIncercare=ESEC; while ((rezultatIncercare==ESEC)&&(k<=8))// miscari posibile cal { xu=x+a[k]; yu=y+b[k]; if((xu>=1)&&(xu<=n)&&(yu>=1)&&(yu<=n)) if(tabla[xu][yu]==LIBER) { tabla[xu][yu]=i; // afisare(); if (i<np) { rezultatIncercare=incerc(i+1,xu,yu); if(rezultatIncercare==ESEC) tabla[xu][yu]=LIBER; } else rezultatIncercare=SUCCES; } k++; }// while return rezultatIncercare; }// incerc() }// class Pe ecran apar urmtoarele rezultate: a Dimensiunea tablei de sah [3..8] : 5 ---------------------------------1 14 19 8 25 6 9 2 13 18 15 20 7 24 3 10 5 22 17 12 21 16 11 4 23

Numar de incercari = 8839

298

CAPITOLUL 15. METODA BACKTRACKING

15.3.5

Problema colorrii hrtilor a a

Se consider o hart cu n ri care trebuie colorat folosind m < n culori, a a ta a astfel at oricare dou ri vecine s e colorate diferit. Relatia de vecintate nc a ta a a dintre ri este retinut ta a ntr-o matrice n n ale crei elemente sunt: a vi,j = 1 dac i este vecin cu j a a 0 dac i nu este vecin cu j a a

Reprezentarea solutiilor: O solutie a problemei este o modalitate de col orare a rilor i poate reprezentat printru-un vector x = (x1 , x2 , ..., xn ) cu ta s a xi {1, 2, ..., m} reprezentnd culoarea asociat rii i. Multimile de valor ale a a ta elementelor sint A1 = A2 = ... = An = {1, 2, ..., m}. Restrictii i conditii de continuare: Restrictia ca dou ri vecine s e col s a ta a orate diferit se specic prin: xi = xj pentru orice i i j avnd proprietatea vi,j = 1. a s a Conditia de continuare pe care trebuie s o satisfac solutia partial (x1 , x2 , ..., xk ) a a a este: xk = xi pentru orice i < k cu proprietatea c vi,k = 1. a class ColorareHartiI1 { static int nrCulori=3;// culorile sunt 1,2,3 static int[][] harta= {// CoCaIaBrTu {2,1,1,1,1},// Constanta (0) {1,2,1,0,0},// Calarasi (1) {1,1,2,1,0},// Ialomita (2) {1,0,1,2,1},// Braila (3) {1,0,0,1,2} // Tulcea (4) }; static int n=harta.length; static int[] culoare=new int[n]; static int nrVar=0; public static void main(String[] args) { harta(); if(nrVar==0) System.out.println("Nu se poate colora !"); } // main static void harta() { int k; // indicele pentru tara boolean okk;

15.3. PROBLEME REZOLVATE k=0; // prima pozitie culoare[k]=0; // tara k nu este colorata (inca) while (k>-1) // -1 = iesit in stanga !!! { okk=false; while(culoare[k] < nrCulori)// selectez o culoare { ++culoare[k]; okk=posibil(k); if (okk) break; } if (!okk) k--; else if (k == (n-1)) afis(); else culoare[++k]=0; } } // harta static boolean posibil(int k) { for(int i=0;i<=k-1;i++) if((culoare[k]==culoare[i])&&(harta[i][k]==1)) return false; return true; } // posibil static void afis() { System.out.print(++nrVar+" : "); for(int i=0;i<n;i++) System.out.print(culoare[i]+" "); System.out.println(); } // scrieRez } // class Pentru 3 culori rezultatele care apar pe ecran sunt: 1 2 3 4 5 6 1 1 2 2 3 3 2 3 1 3 1 2 3 2 3 1 2 1 2 3 1 3 1 2 3 2 3 1 2 1

299

300

CAPITOLUL 15. METODA BACKTRACKING

class ColorareHartiR1 { static int[] x; static int[][] a= { {0,0,0,0,0,0}, {0,2,1,1,1,1},// {0,1,2,1,0,0},// {0,1,1,2,1,0},// {0,1,0,1,2,1},// {0,1,0,0,1,2} // }; static int n,m,nv=0;

Constanta (1) Calarasi (2) Ialomita (3) Braila (4) Tulcea (5)

public static void main(String[] args) { n=a.length-1; m=3; // nr culori x=new int[n+1]; f(1); } static void f(int k) { boolean ok; int i,j; for(i=1;i<=m;i++) { ok=true; for(j=1;j<k;j++) if((i==x[j])&&(a[j][k]==1)) {ok=false; break;} if(!ok) continue; x[k]=i; if(k<n) f(k+1); else afisv(); } } static void afisv() { System.out.print(++nv+" : "); for(int i=1; i<=n; i++) System.out.print(x[i]+" "); System.out.println(); } }

15.3. PROBLEME REZOLVATE

301

15.3.6

Problema vecinilor

Un grup de n persoane sunt aezate pe un rnd de scaune. s a Intre oricare doi vecini izbucnesc conicte. Rearanjati persoanele pe scaune astfel at nc ntre oricare doi vecini certati s existe una sau cel mult dou persoane cu care nu au apucat a a s se certe! Aati toate variantele de reaezare posibile. a s s Vom rezolva problema prin metada backtracking. Presupunem c persoanele a sunt numerotate la nceput, de la stanga la dreapta cu 1, 2, ..., n. Considerm ca a solutie un vector x cu n componente pentru care xi reprezint pozitia pe care se a va aa persoana i dup reaezare. a s Pentru k > 1 dat, conditiile de continuare sunt: a) xj = xk , pentru j = 1, ..., k 1 (x trebuie s e permutare) a b) |xk xk1 | = 2 sau 3 ( ntre persoana k i vecinul su anterior trebuie s se s a a ae una sau dou persoane) a locul solutiei x vom lista permutarea y = x1 unde yi reprezint persoana In a care se aseaz pe locul i. a class VeciniR1 { static int[] x; static int n,m,nsol=0; public static void main(String[] args) { n=7, m=7; x=new int[n+1]; f(1); } static void f(int k) { boolean ok; int i,j; for(i=1;i<=m;i++) { ok=true; for(j=1;j<k;j++) if(i==x[j]) {ok=false; break;} if(!ok) continue; if((Math.abs(i-x[k-1])!=2)&&(Math.abs(i-x[k-1])!=3)) continue; x[k]=i; if(k<n) f(k+1); else afis(); }

302 }

CAPITOLUL 15. METODA BACKTRACKING

static void afis() { int i; System.out.print(++nsol+" : "); for(i=1; i<=n; i++) System.out.print(x[i]+" "); System.out.println(); } }

import java.io.*; class Vecini { static int nrVar=0,n; static int[] x,y; public static void main(String[] args) throws IOException { BufferedReader br=new BufferedReader( new InputStreamReader(System.in)); while ((n<3)||(n>50)) { System.out.print("numar persoane [3..50] : "); n=Integer.parseInt(br.readLine()); } x=new int[n]; y=new int[n]; reasez(0); if(nrVar==0) System.out.println("Nu se poate !!!"); } // main static void reasez(int k) { int i,j; boolean ok; if (k==n) scrieSolutia();// n=in afara vectorului !!! else for(i=0;i<n;i++) { ok=true; j=0; while ((j<k-1) && ok) ok=(i != x[j++]); if (k>0)

15.3. PROBLEME REZOLVATE

303

ok=(ok&&((Math.abs(i-x[k-1])==2)||(Math.abs(i-x[k-1])==3))); if (ok) { x[k]=i; reasez(k+1);} } } // reasez static void scrieSolutia() { int i; for(i=0;i<n;i++) y[x[i]]=i;// inversarea permutarii !!! System.out.print(++nrVar+" : "); for(i=0;i<n;i++) System.out.print(++y[i]+" "); // ca sa nu fie 0,1,...,n-1 System.out.println(); // ci 1,2,...,n (la afisare) } // scrieRez } // class

15.3.7

Problema labirintului

Se d un labirint sub form de matrice cu m linii i n coloane. Fiecare element a a s al matricei reprezint o camer a labirintului. a a Intr-una din camere, de coordonate x0 i y0 se g sete un om. Se cere s se g seasc toate ieirile din labirint. s a s a a a s O prim problem care se pune este precizarea modului de codicare a a a ieirilor din ecare camer a labirintului. s a Dintr-o pozitie oarecare (i, j) din labirint, deplasarea se poate face patru n directii: Nord, Est, Sud i Vest considerate aceast ordine (putem alege oricare s n a alt ordine a celor patru directii, dar odat aleas aceasta se pstreaz pn la a a a a a a a sfritul problemei). as Fiecare camer din labirint poate caracterizat printr-un ir de patru cifre a a s binare asociate celor patru directii, avnd semnicatie faptul c acea camer are a a a sau nu ieiri pe directiile considerate. s De exemplu, dac pentru camera cu pozitia (2, 4) exist ieiri la N i S, ei a a s s i va corespunde irul 1010 care reprezint numrul 10 baza zece. s a a n Prin urmare, codicm labirintul printr-o matrice a[i][j] cu elemente a ntre 1 ai 15. Pentru a testa uor ieirea din labirint, matricea se bordeaz cu dou linii s s s a a i dou coloane de valoare egal cu 16. s a a Ne punem problema determinrii ieirilor pe care le are o camer. a s a O camer are ieirea numai spre N dac i numai dac a[i][j]&&8 = 0. a s as a Drumul parcurs la un moment dat se retine ntr-o matrice cu dou linii, d, a care: n d[1][k] reprezint linia camerei la care s-a ajuns la pasul k; a d[2][k] reprezint coloana camerei respective. a La gsirea unei ieiri din labirint, drumul este aat. a s s Principiul algoritmului este urmtorul: a

304

CAPITOLUL 15. METODA BACKTRACKING

se testeaz dac s-a ieit din labiritn (adic a[i][j] = 16); a a s a caz armativ se aeaz drumul gsit; n s a a caz contrar se procedeaz astfel: n a se retin matricea d coordonatele camerei vizitate; n se veric dac drumul arcurs a mai trecut prin aceast camer, caz a a a a n care se iese din procedur; a se testeaz pe rnd ieirile spre N, E, S, V i acolo unde este gsit o astfel a a s s a a de ieire se reapeleaz procedura cu noile coordonate; s a naintea ieirii din procedur se decrementeaz valoarea lui k. s a a import java.io.*; class Labirint { static final char coridor=., start=x, gard=H, pas=*, iesire=E; static char[][] l; static int m,n,x0,y0; static boolean ok; public static void main(String[] args) throws IOException { int i,j,k; StreamTokenizer st = new StreamTokenizer( new BufferedReader(new FileReader("labirint.in"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; l=new char[m][n]; for(i=0;i<m;i++) { st.nextToken(); for(j=0;j<n;j++) l[i][j]=st.sval.charAt(j); } st.nextToken(); x0=(int)st.nval; st.nextToken(); y0=(int)st.nval; ok=false; gi(x0,y0); l[x0][y0]=start; PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter("labirint.out"))); for(i=0;i<m;i++) { if (i>0) out.println();

15.3. PROBLEME REZOLVATE for(j=0;j<n;j++) out.print(l[i][j]); } if (!ok) out.println("NU exista iesire !"); out.close(); }// main()

305

static void gi(int x,int y) { if ((x==0)||(x==n-1)||(y==0)||(y==m-1)) ok=true; else { l[x][y]=pas; if(l[x][y+1]==coridor||l[x][y+1]==iesire) gi(x,y+1); if(!ok&&(l[x+1][y]==coridor||l[x+1][y]==iesire)) gi(x+1,y); if(!ok&&(l[x][y-1]==coridor||l[x][y-1]==iesire)) gi(x,y-1); if(!ok&&(l[x-1][y]==coridor||l[x-1][y]==iesire)) gi(x-1,y); l[x][y]=coridor; } if (ok) l[x][y]=pas; } }// class De exemplu, pentru sierul de intrare: labirint.in 8 8 HHHHHHEH H....H.H H.HHHH.H H.HHHH.H H....H.H H.HHHH.H H......H HHHHHHEH 2 2 se obtine ierul de ieire: labirint.out s s HHHHHHEH H....H.H H*xHHH.H H*HHHH.H H*...H.H H*HHHH.H H******H HHHHHH*H

306

CAPITOLUL 15. METODA BACKTRACKING

15.3.8

Generarea partitiilor unui numr natural a

S se aeze toate modurile de descompunere a unui numr natural n ca a s a sum de numere naturale. a Vom folosi o procedur f care are doi parametri: componenta la care s-a a ajuns (k) i o valoare v (care contine diferenta care a mai rmas pn la n). s a a a Initial, procedura este apelat pentru nivelul 1 i valoarea n. Imediat ce este a s apelat, procedura va apela o alta pentru aarea vectorului (initial aeaz n). a s s a Din valoarea care se gsete pe un nivel, S[k], se scad pe rnd valorile a s a 1, 2, ..., S[k] 1, valori cu care se apeleaz procedura pentru nivelul urmtor. a a La revenire se reface valoarea existent. a class PartitieNrGenerare // n = suma de numere { static int dim=0, nsol=0, n=6; static int[] x=new int[n+1]; public static void main(String[] args) { long t1,t2; t1=System.currentTimeMillis(); f(n,n,1); t2=System.currentTimeMillis(); System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n"); } static void f(int val, int maxp, int poz) { if(maxp==1) { nsol++; dim=poz-1; afis2(val,maxp); return; } if(val==0) { nsol++; dim=poz-1; afis1(); return; } int maxok=min(maxp,val); for(int i=maxok;i>=1;i--) { x[poz]=i; f(val-i,min(val-i,i),poz+1); } } static void afis1() { System.out.print("\n"+nsol+" : "); for(int i=1;i<=dim;i++) System.out.print(x[i]+" "); }

15.3. PROBLEME REZOLVATE

307

static void afis2(int val,int maxip) { int i; System.out.print("\n"+nsol+" : "); for(i=1;i<=dim;i++) System.out.print(x[i]+" "); for(i=1;i<=val;i++) System.out.print("1 "); } static int min(int a,int b) { return (a<b)?a:b; } } Pentru descompunerea ca sum de numere impare, programul este: a class PartitieNrImpare1 // n = suma de numere impare { // nsol = 38328320 1Gata!!! timp = 8482 static int dim=0,nsol=0; static int[] x=new int[161]; public static void main(String[] args) { long t1,t2; int n=160, maxi=((n-1)/2)*2+1; t1=System.currentTimeMillis(); f(n,maxi,1); t2=System.currentTimeMillis(); System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n"); } static void f(int val, int maxip, int poz) { if(maxip==1) { nsol++; // dim=poz-1; // afis2(val,maxip); return; } if(val==0) { nsol++; // dim=poz-1; afis1(); return; }

308

CAPITOLUL 15. METODA BACKTRACKING int maxi=((val-1)/2)*2+1; int maxiok=min(maxip,maxi); for(int i=maxiok;i>=1;i=i-2) { x[poz]=i; f(val-i,min(maxiok,i),poz+1); }

} static void afis1() { System.out.print("\n"+nsol+" : "); for(int i=1;i<=dim;i++) System.out.print(x[i]+" "); } static void afis2(int val,int maxip) { int i; System.out.print("\n"+nsol+" : "); for(i=1;i<=dim;i++) System.out.print(x[i]+" "); for(i=1;i<=val;i++) System.out.print("1 "); } static int max(int a,int b) { return (a>b)?a:b; } static int min(int a,int b) { return (a<b)?a:b; } }// class O versiune optimizat este: a class PartitieNrImpare2 // n = suma de numere impare ; { // optimizat timp la jumatate !!! static int dim=0,nsol=0; // nsol = 38328320 2Gata!!! static int[] x=new int[161];

timp = 4787

public static void main(String[] args) { long t1,t2; int n=160, maxi=((n-1)/2)*2+1; t1=System.currentTimeMillis(); f(n,maxi,1); t2=System.currentTimeMillis(); System.out.println("nsol = "+nsol+" timp= "+(t2-t1)); }

15.3. PROBLEME REZOLVATE static void f(int val, int maxip, int poz) { if(maxip==1) { nsol++; // dim=poz-1; afis2(val); return; } if(val==0) { nsol++; // dim=poz-1; afis1(); return; } int maxi=((val-1)/2)*2+1; int maxiok=min(maxip,maxi); for(int i=maxiok;i>=3;i=i-2) { x[poz]=i; f(val-i,i,poz+1); } nsol++; // dim=poz-1; // afis2(val); } static void afis1() { System.out.print("\n"+nsol+" : "); for(int i=1;i<=dim;i++) System.out.print(x[i]+" "); } static void afis2(int val) { System.out.print("\n"+nsol+" : "); for(int i=1;i<=dim;i++) System.out.print(x[i]+" "); for(int i=1;i<=val;i++) System.out.print("1 "); } static int max(int a,int b) { return (a>b)?a:b; } static int min(int a,int b) { return (a<b)?a:b; } }// class

309

310

CAPITOLUL 15. METODA BACKTRACKING

15.3.9

Problema parantezelor

Problema cere generarea tuturor combinatiilor de 2n paranteze (n paranteze de deschidere i n paranteze de s nchidere) care se nchid corect.

class ParantezeGenerare // 2*n paranteze { static int nsol=0; static int n=4; static int n2=2*n; static int[] x=new int[n2+1]; public static void main(String[] args) { long t1,t2; t1=System.currentTimeMillis(); f(1,0,0); t2=System.currentTimeMillis(); System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n"); } static void f(int k, int npd, int npi) { if(k>n2) afis(); else { if(npd<n) { x[k]=1; f(k+1,npd+1,npi); } if(npi<npd) { x[k]=2; f(k+1,npd,npi+1); } } } static void afis() { int k; System.out.print(++nsol+" : "); for(k=1;k<=n2;k++) if(x[k]==1) System.out.print("( "); else System.out.print(") "); System.out.println(); } }// class

15.3. PROBLEME REZOLVATE

311

15.3.10

Algoritmul DFS de parcurgere a grafurilor

Algoritmul DFS de parcurgerea in adncime a grafurilor neorientate foloseste a tehnica backtrackink. import java.io.*; // arborele DFS (parcurgere in adancime) class DFS // momentele de descoperire si finalizare a nodurilor { // drum intre doua varfuri static final int WHITE=0, GRAY=1, BLACK=2; static int n,m,t; static int[] d,f,p,color; // descoperit,finalizat,predecesor,culoare static int[][] a; public static void main(String[] args) throws IOException { StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("dfs.in"))); int i,j,k,nods,nodd; // nods=nod_start_DFS, nod_destinatie (drum!) st.nextToken(); st.nextToken(); st.nextToken(); st.nextToken(); n=(int)st.nval; m=(int)st.nval; nods=(int)st.nval; nodd=(int)st.nval;

a=new int[n+1][n+1]; d=new int[n+1]; f=new int[n+1]; p=new int[n+1]; color=new int[n+1]; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; a[i][j]=1; a[j][i]=1; } for(i=1;i<=n;i++) // oricum erau initializati implicit, dar ... !!! { color[i]=WHITE; p[i]=-1; }

312

CAPITOLUL 15. METODA BACKTRACKING

t=0; dfs(nods); System.out.print("drum : "); drum(nodd); System.out.println(); System.out.print("Descoperit :\t"); afisv(d); System.out.print("Finalizat :\t"); afisv(f); }//main static void dfs(int u) { int v; color[u]=GRAY; d[u]=++t; for(v=1;v<=n;v++) if(a[u][v]==1) if(color[v]==WHITE) { p[v]=u; dfs(v); } color[u]=BLACK; f[u]=++t; }//dfs

// listele de adiacenta ... !!! // v in Ad(u) !!!

static void drum(int u) // nod_sursa ---> nod_destinatie { if(p[u]!=-1) drum(p[u]); System.out.print(u+" "); }// drum(...) static void afisv(int[] x) { int i; for(i=1;i<=n;i++) System.out.print(x[i]+"\t"); System.out.println(); } }//class /* 6 7 3 4 1 4 4 6 6 1

drum : 3 1 4 Descoperit : Finalizat :

2 11

5 6

1 12

3 10

4 7

8 9

15.3. PROBLEME REZOLVATE 5 3 2 5 1 3 4 5 */

313

15.3.11

Determinarea componentelor conexe

import java.io.*; // determinarea componentelor conexe class CompConexe { static int n,m,ncc; static int [] cc; static int[][] a; public static void main(String[] args) throws IOException { int i,j,k; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("compConexe.in"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; a=new int[n+1][n+1]; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; a[i][j]=a[j][i]=1; } cc=new int[n+1]; ncc=0; for(i=1;i<=n;i++) if(cc[i]==0) { ncc++; conex(i); } for(i=1;i<=ncc;i++)

314 {

CAPITOLUL 15. METODA BACKTRACKING

System.out.print(i+" : "); for(j=1;j<=n;j++) if(cc[j]==i) System.out.print(j+" "); System.out.println(); } }//main static void conex(int u) { cc[u]=ncc; for(int v=1;v<=n;v++) if((a[u][v]==1)&&(cc[v]==0)) conex(v); }//conex }//class /* 9 7 1 2 2 3 3 1 4 5 6 7 7 8 8 9 */

1 : 1 2 3 2 : 4 5 3 : 6 7 8 9

15.3.12

Determinarea componentelor tare conexe

// determinarea componentelor tare conexe (in graf orientat!) // Algoritm: 1. dfs(G) pentru calculul f[u] // 2. dfs(G_transpus) in ordinea descrescatoare a valorilor f[u] // OBS: G_transpus are arcele din G "intoarse ca sens" // Lista este chiar o sortare topologica !!! import java.io.*; class CompTareConexe { static final int WHITE=0, GRAY=1, BLACK=2; static int n,m,t=0,nctc,pozLista; static int [] ctc,f,color,lista; static int[][] a; // matricea grafului

15.3. PROBLEME REZOLVATE static int[][] at;

315

// matricea grafului transpus (se poate folosi numai a !)

public static void main(String[] args) throws IOException { int i,j,k; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("compTareConexe.in"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; a=new int[n+1][n+1]; f=new int[n+1]; at=new int[n+1][n+1]; lista=new int[n+1]; ctc=new int[n+1]; color=new int[n+1];

for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; a[i][j]=1; at[j][i]=1; // transpusa } for(i=1;i<=n;i++) color[i]=WHITE; pozLista=n; for(i=1;i<=n;i++) if(color[i]==WHITE) dfsa(i); nctc=0; for(i=1;i<=n;i++) color[i]=WHITE; for(i=1;i<=n;i++) if(color[lista[i]]==WHITE) { nctc++; dfsat(lista[i]); } for(i=1;i<=nctc;i++) { System.out.print(i+" : "); for(j=1;j<=n;j++) if(ctc[j]==i) System.out.print(j+" "); System.out.println(); } }//main static void dfsa(int u) { int v; color[u]=GRAY;

316

CAPITOLUL 15. METODA BACKTRACKING for(v=1;v<=n;v++) if((a[u][v]==1)&&(color[v]==WHITE)) dfsa(v); color[u]=BLACK; f[u]=++t; lista[pozLista--]=u;

} static void dfsat(int u) // se poate folosi "a" inversand arcele ! { int j; color[u]=GRAY; ctc[u]=nctc; for(j=1;j<=n;j++) if((at[u][lista[j]]==1)&&(color[lista[j]]==WHITE)) dfsat(lista[j]); //"at" //if((a[lista[j]][u]==1)&&(color[lista[j]]==WHITE)) dfsat(lista[j]); //"a" color[u]=BLACK; } }//class /* 9 10 1 2 2 3 3 1 4 5 6 7 7 8 8 9 5 4 7 6 8 7 */

1 2 3 4

: : : :

6 7 8 9 4 5 1 2 3

15.3.13

Sortare topologic a

Folosind parcurgerea adncime n a // // // // // // // // Sortare Topologica = ordonare lineara a varfurilor (in digraf) (u,v)=arc ==> "... u ... v ..." in lista ("u se termina inaintea lui v") Algoritm: 1. DFS pentru calcul f[u], u=nod 2. cand u=terminat ==> plasaz in lista pe prima pozitie libera de la sfarsit catre inceput Solutia nu este unica (cea mai mica lexicografic = ???) O(n*n)= cu matrice de adiacenta O(n+m)= cu liste de adiacenta

import java.io.*;

15.3. PROBLEME REZOLVATE

317

class SortTopoDFS { static final int WHITE=0, GRAY=1, BLACK=2; static int n,m,t,pozl; // varfuri, muchii, time, pozitie in lista static int[] d; // descoperit static int[] f; // finalizat static int[] color; // culoare static int[] lista; // lista static int[][] a; // matricea de adiacenta public static void main(String[] args) throws IOException { int i,j,k,nods; // nods=nod_start_DFS StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("sortTopo.in"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; a=new int[n+1][n+1]; d=new int[n+1]; f=new int[n+1]; color=new int[n+1]; lista=new int[n+1]; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; a[i][j]=1; } for(i=1;i<=n;i++) color[i]=WHITE; t=0; pozl=n; for(nods=1;nods<=n;nods++) if(color[nods]==WHITE) dfs(nods); for(i=1;i<=n;i++) System.out.print(lista[i]+" "); System.out.println(); }//main static void dfs(int u) { int v; color[u]=GRAY; d[u]=++t;

318

CAPITOLUL 15. METODA BACKTRACKING

for(v=1;v<=n;v++) // mai bine cu liste de adiacenta ... !!! if(a[u][v]==1) // v in Ad(u) !!! if(color[v]==WHITE) dfs(v); color[u]=BLACK; f[u]=++t; lista[pozl]=u; --pozl; }//dfs }//class /* 6 4 6 3 1 2 3 4 5 6 */

5 6 3 4 1 2

Folosind gradele interioare // // // // // // // // Sortare Topologica = ordonare lineara a varfurilor (in digraf) (u,v)=arc ==> "... u ... v ..." in lista ("u se termina inaintea lui v") Algoritm: cat_timp exista noduri neplasate in lista 1. aleg un nod u cu gi[u]=0 (gi=gradul interior) 2. u --> lista (pe cea mai mica pozitie neocupata) 3. decrementez toate gi[v], unde (u,v)=arc OBS: pentru prima solutie lexicografic: aleg u="cel mai mic" (heap!) OBS: Algoritm="stergerea repetata a nodurilor de grad zero"

import java.io.*; class SortTopoGRAD { static final int WHITE=0, BLACK=1; // color[u]=BLACK ==> u in lista static int n,m,pozl; // varfuri, muchii, pozitie in lista static int[] color; // culoare static int[] lista; // lista static int[] gi; // grad interior static int[][] a; // matricea de adiacenta public static void main(String[] args) throws IOException { int u,i,j,k; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("sortTopo.in")));

15.3. PROBLEME REZOLVATE

319

st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; a=new int[n+1][n+1]; color=new int[n+1]; lista=new int[n+1]; gi=new int[n+1]; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; a[i][j]=1; gi[j]++; } for(i=1;i<=n;i++) color[i]=WHITE; pozl=1; for(k=1;k<=n;k++) // pun cate un nod in lista { u=nodgi0(); micsorezGrade(u); lista[pozl++]=u; color[u]=BLACK; } for(i=1;i<=n;i++) System.out.print(lista[i]+" "); System.out.println(); }//main static int nodgi0() // nod cu gradul interior zero { int v,nod=-1; for(v=1;v<=n;v++) // coada cu prioritati (heap) este mai buna !!! if(color[v]==WHITE) if(gi[v]==0) {nod=v; break;} return nod; } static void micsorezGrade(int u) { int v; for(v=1;v<=n;v++) // lista de adiacenta este mai buna !!!

320

CAPITOLUL 15. METODA BACKTRACKING if(color[v]==WHITE) if(a[u][v]==1) gi[v]--;

} }//class /* 6 4 6 3 1 2 3 4 5 6 */

1 2 5 6 3 4

15.3.14

Determinarea nodurilor de separare


// determinarea nodurilor care strica conexitatea // in graf neorientat conex

import java.io.*; class NoduriSeparare { static int n,m; static int [] cc; static int[][] a;

public static void main(String[] args) throws IOException { int i,j,k; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("noduriSeparare.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("noduriSeparare.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; a=new int[n+1][n+1]; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; a[i][j]=a[j][i]=1; } for(i=1;i<=n;i++) if(!econex(i)) System.out.print(i+" "); out.close(); }//main static boolean econex(int nodscos) {

15.3. PROBLEME REZOLVATE int i, ncc=0; int[] cc=new int[n+1]; for(i=1;i<=n;i++) if(i!=nodscos) if(cc[i]==0) { ncc++; if(ncc>1) break; conex(i,ncc,cc,nodscos); } if(ncc>1) return false; else return true; }// econex() static void conex(int u,int et,int[]cc,int nodscos) { cc[u]=et; for(int v=1;v<=n;v++) if(v!=nodscos) if((a[u][v]==1)&&(cc[v]==0)) conex(v,et,cc,nodscos); }//conex }//class

321

15.3.15

Determinarea muchiilor de rupere

import java.io.*; // determinarea muchiilor care strica conexitatea class MuchieRupere // in graf neorientat conex { static int n,m; static int [] cc; static int[][]a; public static void main(String[] args) throws IOException { int i,j,k; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("muchieRupere.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("muchieRupere.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; a=new int[n+1][n+1]; for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval;

322

CAPITOLUL 15. METODA BACKTRACKING

a[i][j]=1; a[j][i]=1; } for(i=1;i<=n;i++) for(j=i+1;j<=n;j++) { if(a[i][j]==0) continue; a[i][j]=a[j][i]=0; if(!econex()) System.out.println(i+" "+j); a[i][j]=a[j][i]=1; } out.close(); }//main static boolean econex() { int i, ncc; cc=new int[n+1]; ncc=0; for(i=1;i<=n;i++) { if(cc[i]==0) { ncc++; if(ncc>1) break; conex(i,ncc); } } if(ncc==1) return true; else return false; }// econex() static void conex(int u,int et) { cc[u]=et; for(int v=1;v<=n;v++) if((a[u][v]==1)&&(cc[v]==0)) conex(v,et); }//conex }//class /* 9 10 7 2 5 1 1 8

1 2 3 7

8 7 9 9

15.3. PROBLEME REZOLVATE 9 4 6 9 6 4 4 1 9 5 9 7 9 3 */

323

15.3.16

Determinarea componentelor biconexe

// Componenta biconexa = componenta conexa maximala fara muchii de rupere import java.io.*; // noduri = 1,...,n class Biconex // liste de adiacenta pentru graf { // vs=varf stiva; m=muchii; ncb=nr componente biconexe // ndr=nr descendenti radacina (in arbore DFS), t=time in parcurgerea DFS static static static static static static static static final int WHITE=0, GRAY=1,BLACK=2; int n,ncb,t,ndr,vs,m=0,root; // root=radacina arborelui DFS int[][] G; // liste de adiacenta int[] grad,low,d; // grad nod, low[], d[u]=moment descoperire nod u int[][] B; // componente biconexe int[] A; // puncte de articulare int[] color; // culoarea nodului int[] fs,ts; // fs=fiu stiva; ts=tata stiva

public static void main(String[] args) throws IOException { init(); root=3; // radacina arborelui (de unde declansez DFS) vs=0; // pozitia varfului stivei unde este deja incarcat un nod (root) fs[vs]=root; // pun in stiva "root" si ts[vs]=0; // tata_root=0 (nu are!) t=0; // initializare time; numerotarea nodurilor in DFS dfs(root,0); // (u,tatau) tatau=0 ==> nu exista tatau if(ncb==1) System.out.println("Graful este Biconex"); else { System.out.println("Graful NU este Biconex"); if(ndr>1) A[root]=1; System.out.print("Puncte de articulare : "); afisv(A);

324

CAPITOLUL 15. METODA BACKTRACKING System.out.print("Numar componente Biconexe : "); System.out.println(ncb); for(int i=1;i<=ncb;i++) { System.out.print("Componenta Biconexa "+i+" : "); afisv(B[i]); }

} }//main() static int minim(int a, int b) { return a<b?a:b; } // minim() static void init() throws IOException { int i,j,k; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("biconex.in"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; d=new int[n+1]; low=new int[n+1]; A=new int[n+1]; fs=new int[m+1]; // vectorii sunt initializati cu zero grad=new int[n+1]; color=new int[n+1]; // 0=WHITE G=new int[n+1][n+1]; B=new int[n+1][n+1]; ts=new int[m+1];

for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; G[i][++grad[i]]=j; G[j][++grad[j]]=i; } }//Init() static void dfs(int u, int tatau) { int fiuu,i; d[u]=++t; color[u]=GRAY; low[u]=d[u]; for(i=1;i<=grad[u];i++) { fiuu=G[u][i]; /* calculeaza d si low */

// fiuu = un descendent al lui u

15.3. PROBLEME REZOLVATE

325

if(fiuu==tatau) continue; // este aceeasi muchie if((color[fiuu]==WHITE)|| // fiuu nedescoperit sau (d[fiuu]<d[u])) // (u,fiuu) este muchie de intoarcere { /* insereaza in stiva muchia (u,fiuu) */ vs++; fs[vs]=fiuu; ts[vs]=u; } if(color[fiuu]==WHITE) /* fiuu nu a mai fost vizitat */ { if(u==root) ndr++; // root=caz special (maresc nrfiiroot) dfs(fiuu,u); // acum este terminat tot subarborele cu radacina fiuu !!! low[u]=minim(low[u],low[fiuu]); if(low[fiuu]>=d[u]) // "=" ==> fiuu intors in u ==> ciclu "agatat" in u !!! // ">" ==> fiuu nu are drum de rezerva !!! { /* u este un punct de articulatie; am identificat o componenta biconexa ce contine muchiile din stiva pana la (u,fiuu) */ if(low[fiuu]!=low[u]) // (u,fiuu) = bridge (pod) System.out.println("Bridge: "+fiuu+" "+u); if(u!=root) A[u]=1; // root = caz special compBiconexa(fiuu,u); } } else // (u,fiuu) = back edge low[u]=minim(low[u],d[fiuu]); } color[u]=BLACK; } // dfs(...) static void compBiconexa(int fiu, int tata) { int tatas,fius; ncb++; do { tatas=ts[vs]; fius=fs[vs]; vs--; B[ncb][tatas]=1; B[ncb][fius]=1;

326

CAPITOLUL 15. METODA BACKTRACKING

} while(!((tata==tatas)&&(fiu==fius))); } // compBiconexa(...) static void afisv(int[] x) // indicii i pentru care x[i]=1; { for(int i=1;i<=n;i++) if(x[i]==1) System.out.print(i+" "); System.out.println(); }// afisv(...) }//class /* 8 9 <-- n m 1 8 1 2 1 3 6 3 4 | \ 2 4 | 5 --3 5 | / 5 7 7 5 6 6 7 */

8 | 1 / 3 \ 4 \ 2 /

Bridge: 8 1 Bridge: 5 3 Graful NU este Biconex Puncte de articulare : 1 3 5 Numar componente Biconexe : 4 Componenta Biconexa 1 : 1 8 Componenta Biconexa 2 : 1 2 3 4 Componenta Biconexa 3 : 5 6 7 Componenta Biconexa 4 : 3 5

15.3.17

Triangulatii - OJI2002 clasa a X-a

O triangulatie a unui poligon convex este o multime format din diagonale a ale poligonului care nu se intersecteaz interiorul poligonului ci numai vrfuri a n n a i care s mpart toat suprafata poligonului triunghiuri. a n Fiind dat un poligon cu n vrfuri notate 1, 2, ..., n s se genereze toate a a triangulatiile distincte ale poligonului. Dou triangulatii sunt distincte dac difer a a a prin cel putin o diagonal. a Datele de intrare: ierul text triang.in se a pe prima linie un singur n s a numr natural reprezentnd valoarea lui n (n 11). a a Datele de ieire: ierul text triang.out se vor scrie: s n s - pe prima linie, numrul de triangulatii distincte; a - pe ecare din urmtoarele linii cte o triangulatie descris prin diagonalele a a a ce o compun. O diagonal va precizat prin dou numere reprezentnd cele dou a a a a a vrfuri care o denesc; cele dou numere ce denesc o diagonal se despart prin a a a cel putin un spatiu, iar ntre perechile de numere ce reprezint diagonalele dintr-o a triangulatie se va lsa de asemenea minimum un spatiu. a Exemplu

15.3. PROBLEME REZOLVATE triang.in 5 triang.out 5 1314 2425 5253 3531 4214

327

Timp maxim de executare: 7 secunde/test pe un calculator la 133 MHz. 3 secunde/test pe un calculator la peste 500 MHz. Rezolvare detaliat a Se genereaza toate combinatiile de diagonale care formeaza o triangulatie. import java.io.*; // merge si n=12 in 3 sec class Triangulatii { static int n; // numar varfuri poligon static int ndt=n-3; // numar diagonale in triangulatie static int nd=n*(n-3)/2; // numarul tuturor diagonalelor static int[] x; static int[] v1,v2; static int nsol=0; static PrintWriter out; public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("triang.in"))); out=new PrintWriter(new BufferedWriter(new FileWriter("triang.out"))); st.nextToken(); n=(int)st.nval; ndt=n-3; nd=n*(n-3)/2; x=new int[ndt+1]; v1=new int[nd+1];

328 v2=new int[nd+1];

CAPITOLUL 15. METODA BACKTRACKING

if(n==3) out.println(0); else { out.println(catalan(n-2)); diagonale(); f(1); } out.close(); t2=System.currentTimeMillis(); System.out.println("nsol = "+nsol+" Timp = "+(t2-t1)); } static void afisd() throws IOException { int i; ++nsol; for(i=1;i<=ndt;i++) out.print(v1[x[i]]+" "+v2[x[i]]+" "); out.println(); } static void diagonale() { int i,j,k=0; i=1; for(j=3;j<=n-1;j++) {v1[++k]=i; v2[k]=j;} for(i=2;i<=n-2;i++) for(j=i+2;j<=n;j++){v1[++k]=i; v2[k]=j;} } static boolean seIntersecteaza(int k, int i) { int j; // i si x[j] sunt diagonalele !!! for(j=1;j<=k-1;j++) if(((v1[x[j]]<v1[i])&&(v1[i]<v2[x[j]])&&(v2[x[j]]<v2[i]) )|| ((v1[i]<v1[x[j]])&&(v1[x[j]]<v2[i])&&(v2[i]<v2[x[j]]))) return true; return false; } static void f(int k) throws IOException {

15.3. PROBLEME REZOLVATE int i; for(i=x[k-1]+1; i<=nd-ndt+k; i++) { if(seIntersecteaza(k,i)) continue; x[k]=i; if(k<ndt) f(k+1); else afisd(); } } static int catalan(int n) { int rez; int i,j; int d; int[] x=new int[n+1]; int[] y=new int[n+1]; for(i=2;i<=n;i++) x[i]=n+i; for(j=2;j<=n;j++) y[j]=j; for(j=2;j<=n;j++) for(i=2;i<=n;i++) { d=cmmdc(y[j],x[i]); y[j]=y[j]/d; x[i]=x[i]/d; if(y[j]==1) break; } rez=1; for(i=2;i<=n;i++) rez*=x[i]; return rez; } static int cmmdc (int a,int b) { int d,i,c,r; if(a>b) {d=a;i=b;} else{d=b;i=a;} while(i!=0) {c=d/i; r=d%i; d=i; i=r;} return d; } }// class

329

330

CAPITOLUL 15. METODA BACKTRACKING

15.3.18

Partitie - ONI2003 clasa a X-a

Se denete o partitie a unui numr natural n ca ind o scriere a lui n sub s a forma: n = n1 + n2 + ... + nk , (k 1) unde n1 , n2 , ..., nk sunt numere naturale care veric urmtoarea relatie: a a n1 n2 ... ni ... nk 1 Cerint a Fiind dat un numr natural n, s se determine cte partitii ale lui se pot a a a scrie, conform cerintelor de mai sus, tiind c oricare numr ni dintr-o partitie s a a trebuie s e un numr impar. a a Datele de intrare Fiierul partitie.in contine pe prima linie numrul n s a Datele de ieire s Fierul partitie.out va contine pe prima linie numrul de partitii ale lui n s a conform cerintelor problemei. Restrictii i precizri s a 1 N 160 Exemplu partitie.in partitie.out 7 5 Explicatii: Cele cinci partitii sunt: 1+1+1+1+1+1+1 1+1+1+1+3 1+1+5 1+3+3 7 Timp maxim de executare: 3 secunde/test Indicatii de rezolvare * Stelian Ciurea Problema se poate rezolva mai multe moduri: n Solutia comisiei se bazeaz pe urmtoarele formule i teoreme de combina a a s toric: a

15.3. PROBLEME REZOLVATE

331

este

numrul de partitii ale unui numr n k prti (nu neaprat distincte) a a n a a P (n, k) = P (n k, 1) + P (n k, 2) + ... + P (n k, k)

cu P (n, 1) = P (n, n) = 1 i P (n, k) = 0 dac n < k. s a numrul de partitii ale unui num n k prti distincte este a a n a P (n, k) = P (n k(k 1)/2, k) numrul de partitii ale unui numr n k prti impare care se pot i repeta a a n a s este egal cu numrul de partitii ale unui numr n k prti distincte. a a n a Problema se poate rezolva i prin backtracking; fr prea mari optimizri se s aa a poate obtine rezultatul mai putin de 3 secunde pentru n < 120. Pentru valori n mai mari ale lui n, se poate lsa programul s ruleze i se retin rezultatele a a s ntr-un vector cu valori initiale. Rezultatul pentru n = 160 are 8 cifre, deci nu este necesar implementarea a operatiilor cu numere mari! Mihai Stroe, GInfo nr. 13/6 - octombrie 2003 Problema se poate rezolva mai multe moduri. n O idee bun de rezolvare a problemei const folosirea metodei programrii a n a dinamice. Se poate construi o matrice A, unde Ai,k reprezint numrul de partitii a a ale numrului i cu numere impare, din care ultimul este cel mult egal cu k. Un a element din A se calculeaz observnd c o partitie a lui i cu numere impare, cel a a a mult egale cu k, este format dintr-un un numr M , mai mic sau egal cu k, i alte a a s numere, mai mici sau egale cu M . De aici rezult relatia: a Ai,k =
M =1,...,k;M i;

AiM,M
M =impar

Initial A0,0 este 1. La implementare, pentru a economisi spatiu, se poate alege o variant care Ai,k s reprezinte numrul de partitii ale lui i cu numere impare, a n a a din care ultimul este cel mult egal cu al k-lea numr impar, adic 2 k 1. a a Dup calcularea elementelor matricei, solutia pentru numul i se gsete a a a s n Ai,i . Aceste valori pot salvate ntrun vector de constante, care este transformat ntr-un nou program, acelai mod ca la problema Circular de la clasa a IX-a. n s Aceast metod conduce la o rezolvare instantanee a testelor date concurs. a a n O alt metod, bazat pe vectorul de constante, ar a a a nsemnat generarea solutiilor folosind metoda backtracking. Un backtracking lsat s se execute a a n timpul concursului ar putut genera solutiile pentru toate valorile lui N , dup a care se putea folosi metoda vectorului de constante, timp ce un backtracking n folosit ca solutie a problemei ar obinut punctajul maxim doar pentru testele mici i medii (pentru valori ale lui N mai mici dect 130). s a Limitele problemei i timpul de execuie de 3 secunde permit rezolvrii prin s a backtracking btinerea unui punctaj multumitor. s

332

CAPITOLUL 15. METODA BACKTRACKING

Analiza complexitii at Pentru o rezolvare care se bazeaz pe metoda vectorului de constante, ordinul a de complexitate al solutiei nale ar fost O(1); solutia const citirea valorii a nn lui N i aarea rezultatului memorat. s s Solutia descris anterior, bazat pe metoda programrii dinamice, are ordinul a a a de complexitate O(n3 ). Invitm cititorul s caute metode mai eciente. a a Ordinul de complexitate al unei solutii care se bazeaz pe metoda backtrack a ing este exponential. Codul surs a

class PartitieNrGenerare // n = suma de numere { static int dim=0,nsol=0; static int n=6; static int[] x=new int[n+1]; public static void main(String[] args) { long t1,t2; t1=System.currentTimeMillis(); f(n,n,1); t2=System.currentTimeMillis(); System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n"); }// main(...) static void f(int val, int maxp, int poz) { if(maxp==1) { nsol++; dim=poz-1; afis2(val,maxp); return; } if(val==0) { nsol++; dim=poz-1; afis1(); return; }

15.3. PROBLEME REZOLVATE

333

int i; int maxok=min(maxp,val); for(i=maxok;i>=1;i--) { x[poz]=i; f(val-i,min(val-i,i),poz+1); } }// f(...) static void afis1() { int i; System.out.print("\n"+nsol+" : "); for(i=1;i<=dim;i++) System.out.print(x[i]+" "); }// afis1() static void afis2(int val,int maxip) { int i; System.out.print("\n"+nsol+" : "); for(i=1;i<=dim;i++) System.out.print(x[i]+" "); for(i=1;i<=val;i++) System.out.print("1 "); }afis2(...) static int min(int a,int b) { return (a<b)?a:b; } }// class class PartitieNrImpare1 // n = suma de numere impare { // nsol = 38328320 timp = 8482 static int dim=0,nsol=0; static int[] x=new int[161]; public static void main(String[] args) { long t1,t2; int n=160; int maxi=((n-1)/2)*2+1; t1=System.currentTimeMillis(); f(n,maxi,1); t2=System.currentTimeMillis(); System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n"); }// main(...)

334

CAPITOLUL 15. METODA BACKTRACKING

static void f(int val, int maxip, int poz) { if(maxip==1) { nsol++; // dim=poz-1; // afis2(val,maxip); return; } if(val==0) { nsol++; // dim=poz-1; // afis1(); return; } int maxi=((val-1)/2)*2+1; int maxiok=min(maxip,maxi); int i; for(i=maxiok;i>=1;i=i-2) { x[poz]=i; f(val-i,min(maxiok,i),poz+1); } }// f(...) static void afis1() { int i; System.out.print("\n"+nsol+" : "); for(i=1;i<=dim;i++) System.out.print(x[i]+" "); }// afis1() static void afis2(int val,int maxip) { int i; System.out.print("\n"+nsol+" : "); for(i=1;i<=dim;i++) System.out.print(x[i]+" "); for(i=1;i<=val;i++) System.out.print("1 "); }// afis2(...) static int max(int a,int b) { return (a>b)?a:b; }

15.3. PROBLEME REZOLVATE static int min(int a,int b) { return (a<b)?a:b; } }// class import java.io.*; class PartitieNrImpare2 // n = suma de numere impare ; { // nsol = 38328320 timp = 4787 static int dim=0,nsol=0; static int[] x=new int[161]; public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis();

335

int n,i; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("partitie.in"))); PrintWriter out=new PrintWriter(new BufferedWriter( new FileWriter("partitie.out"))); st.nextToken(); n=(int)st.nval; int maxi=((n-1)/2)*2+1; f(n,maxi,1); out.print(nsol); out.close(); t2=System.currentTimeMillis(); System.out.println("nsol = "+nsol+" timp= "+(t2-t1)); }// main(...) static void f(int val, int maxip, int poz) { if(maxip==1) { nsol++; dim=poz-1; afis2(val); return; } if(val==0) { nsol++; dim=poz-1;

336 //afis1(); return; }

CAPITOLUL 15. METODA BACKTRACKING

int maxi=((val-1)/2)*2+1; int maxiok=min(maxip,maxi); int i; for(i=maxiok;i>=3;i=i-2) { x[poz]=i; f(val-i,i,poz+1); } nsol++; dim=poz-1; //afis2(val); }// f(...) static void afis1() { int i; System.out.print("\n"+nsol+" : "); for(i=1;i<=dim;i++) System.out.print(x[i]+" "); }// afis1() static void afis2(int val) { int i; System.out.print("\n"+nsol+" : "); for(i=1;i<=dim;i++) System.out.print(x[i]+" "); for(i=1;i<=val;i++) System.out.print("1 "); }// afis2(...) static int max(int a,int b) { return (a>b)?a:b; } static int min(int a,int b) { return (a<b)?a:b; } }// class

15.3.19

Scuta - ONI2003 clasa a X-a

Majoritatea participantilor la ONI2003 au auzit, copilrie, povestea Scutei n a Roii. Pentru cei care o tiu, urmeaz partea a doua; pentru cei care nu o tiu, nu s s a s v faceti griji, cunoaterea ei nu este necesar pentru a rezolva aceast problem. a s a a a Povestea nu spune ce s-a amplat pe drumul pe care Scuta Roie s-a nt s ntors de

15.3. PROBLEME REZOLVATE

337

la bunicut. Veti aa amnunte continuare. a a n Pe drum, ea s-a alnit cu Lupul (fratele lupului care a prsit povestea nt aa prima parte). Acesta dorea s o mnnce, dar a decis s-i acorde o ans de n a a a a s a scpare, provocnd-o la un concurs de cules ciupercute. a a Scuta Roie se a pozitia (1, 1) a unei matrice cu N linii i N coloane, s a n s ecare pozitie a matricei ind amplasate ciupercute. Lupul se a pozitia n a n (1, 1) a unei alte matrice similare. Pe parcursul unui minut, att Scuta, ct i a a s Lupul se deplaseaz a ntr-una din pozitiile vecine (pe linie sau pe coloan) i culeg a s ciupercutele din pozitia respectiv. Dac Scuta Roie ajunge a a s ntr-o pozitie n care nu sunt ciupercute, va pierde jocul. Dac la sfritul unui minut ea are mai a as putine ciupercute dect Lupul, ea va pierde jocul de asemenea. Jocul a ncepe dup a ce amndoi participantii au cules ciupercutele din pozitiile lor initiale (nu conteaz a a cine are mai multe la nceputul jocului, ci doar dup un numr a a ntreg strict pozitiv de minute de la nceput). Dac Scuta Roie pierde jocul, Lupul o va mnca. a s a Inainte de nceperea jocului, Scuta Roie l-a sunat pe Vntor, care i-a s a a promis c va veni a ntr-un sfert de ora (15 minute) pentru a o salva. Deci Scuta Roie va liber s plece dac nu va pierde jocul dup 15 minute. s a a a a Din acest moment, scopul ei este nu numai s rmn viat, ci i s culeag a a a a n a s a a ct mai multe ciupercute, pentru a le duce acas (dup ce va veni, vntorul nu o a a a a a va mai lsa s culeag). a a a Lupul, cunoscut pentru lcomia sa proverbial, va alege la ecare minut mua a tarea cmpul vecin cu cele mai multe ciupercute (matricea sa este dat astfel n a a nct s nu existe mai multe posibiliti de alegere la un moment dat). a at Povestea spune c Scuta Roie a plecat acas cu couletul plin de ciupercute, a s a s folosind indicatiile date de un program scris de un concurent la ONI 2003 (nu vom da detalii suplimentare despre alte aspecte, cum ar cltoria timp, pentru aa n a nu complica inutil enuntul problemei). S fost acest program scris de ctre a a dumneavoastr? Vom vedea... a Cerint a Scrieti un program care s o ajute pe Scuta Roie s rmn joc i s a s a a a a n s a culeag ct mai multe ciupercute la sfritul celor 15 minute! a a as Date de intrare Fiierul scuta.in are urmtoarea structur: s a a N - dimensiunea celor dou matrice a a11 a12 ...a1n -matricea din care culege Scuta Roie s a21 a22 ...a2n ... an1 an2 ...ann b11 b12 ...b1n - matricea din care culege Lupul b21 b22 ...b2n ... bn1 bn2 ...bnn Date de ieire s Fiierul scuta.out are urmtoarea structur: s a a

338

CAPITOLUL 15. METODA BACKTRACKING

N R - numrul total de ciupercute culese a d1 d2 ...d15 - directiile pe care s-a deplasat Scuta Roie, separate prin cte un s a spatiu (directiile pot N, E, S, V indicnd deplasri spre Nord, Est, Sud, Vest; a a pozitia (1, 1) este situat coltul de Nord-Vest al matricei) a n Restrictii 4 N 10; valorile din ambele matrice sunt numere naturale mai mici dect 256; a nici Scuta Roie i nici Lupul nu vor prsi matricele corespunztoare; s s aa a dup ce unul din juctori culege ciupercutele dintr-o pozitie, pozitia a a n respectiv rmn 0 ciupercute; a a a pe testele date, Scuta Roie va avea s ntotdeauna posibilitatea de a rezista 15 minute. Exemplu scuta.in scuta.out 4 137 2234 SSSEEENVVNEENVV 5678 9 10 11 12 13 14 15 16 1234 5678 9 10 11 12 13 14 15 16 Explicatie: Scuta Roie a efectuat aceleai mutri cu cele efectuate de Lup i a avut tot s s a s timpul o ciupercut plus. nal ea a cules toate ciupercutele din matrice. a n In Timp maxim de executare: 1 secund/test a Indicatii de rezolvare * Mihai Stroe, GInfo nr. 13/6 - octombrie 2003 Problema se rezolv folosind metoda backtracking plan. a n Se observ c mutrile Lupului sunt independente de mutrile Scutei, astfel a a a a ele pot determinate imediat dup citirea datelor. a acest punct al rezolvrii, Scuta trebuie s mute la ecare moment, astfel In a a at s rmn joc, iar nal s strng ct mai multe ciupercute. Restrictiile nc a a a a n n a a a a sunt date de enuntul problemei i de numrul de ciupercute culese de Lup, care s a este cunoscut la ecare moment. Rezolvarea prin metoda backtracking ncearc la ecare pas, pe rnd, ecare a a mutare din cele cel mult patru posibile (teoretic; de fapt, cel mult trei mutri sunt a posibile ecare moment, deoarece Scuta nu se va n ntoarce pozitia din care n tocmai a venit).

15.3. PROBLEME REZOLVATE

339

Se urmrete respectarea restrictiilor impuse. momentul gsirii unei solutii, a s In a aceasta este comparat cu solutia optim gsit pn atunci i dac este mai bun a a a a a a s a a este retinut. a a nu a fost gsit o rezolvare polinomial pentru aceast problem (i este Inc a a a a a s improbabil ca o astfel de rezolvare s existe), dar limitele mici i faptul c numrul a s a a de mutri disponibile a ncepe s scad destul de repede, multe cazuri permit un a a n timp de executie foarte bun. Analiza complexitii at Fie M numrul de mutri pe care le are de efectuat Scuta. a a Operatiile de citire i scriere a datelor au ordinul de complexitate O(N 2 ), s respectiv O(M ), deci nu vor luate calcul. Ordinul de complexitate este dat de n rezolvarea prin metoda backtracking. Avnd vedere c la ecare pas Scuta poate alege dintre cel mult 3 mutri a n a a (deoarece nu se poate ntoarce pozitia din care a venit), ordinul de complexitate n ar O(3M ). De fapt, numrul maxim de stri examinate este mult mai mic; de a a exemplu, primele dou mutri ofer dou variante de alegere loc de trei. Alte a a a a n restrictii apar datorit limitrii la o matrice de 10 10 elemente. a a Cum numrul M este cunoscut i mic, s-ar putea considera c ordinul de a s a complexitate este limitat superior, deci constant (constanta introdus ind totui a s destul de mare). Codul surs a

import java.io.*; class Scufita { static int n, maxc=0; static int[][] a,b; static int[] x,y; static char[] tc=new char[16]; static char[] tmax=new char[16]; public static void main(String[] args) throws IOException { int i,j; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("scufita.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("scufita.out"))); st.nextToken(); n=(int)st.nval;

340 a=new b=new x=new y=new int[n+2][n+2]; int[n+2][n+2]; int[17]; int[17];

CAPITOLUL 15. METODA BACKTRACKING

for(i=1;i<=n;i++) for(j=1;j<=n;j++) { st.nextToken(); a[i][j]=(int)st.nval; } for(i=1;i<=n;i++) for(j=1;j<=n;j++) { st.nextToken(); b[i][j]=(int)st.nval; } culegeLupul(1,1,1); f(1,1,1); out.println(maxc); for(i=1;i<=15;i++) out.print(tmax[i]); out.println(); out.close(); }// main() static void f(int i, int j, int k) throws IOException { int aij=a[i][j]; x[k]=x[k-1]+a[i][j]; a[i][j]=0; if(k==16) { if(x[16]>maxc) { maxc=x[16]; for(int ii=1;ii<=15;ii++) tmax[ii]=tc[ii]; } a[i][j]=aij; return; } if((a[i-1][j]>0)&&(a[i-1][j]+x[k]>=y[k+1])) if((a[i+1][j]>0)&&(a[i+1][j]+x[k]>=y[k+1])) if((a[i][j-1]>0)&&(a[i][j-1]+x[k]>=y[k+1])) if((a[i][j+1]>0)&&(a[i][j+1]+x[k]>=y[k+1])) a[i][j]=aij; }//f(...) { { { { tc[k]=N; tc[k]=S; tc[k]=V; tc[k]=E; f(i-1,j,k+1); f(i+1,j,k+1); f(i,j-1,k+1); f(i,j+1,k+1); } } } }

15.3. PROBLEME REZOLVATE

341

static void culegeLupul(int i, int j, int k) { if(k>16) return; y[k]=y[k-1]+b[i][j]; b[i][j]=0; if((b[i-1][j]>b[i+1][j])&&(b[i-1][j]>b[i][j-1])&&(b[i-1][j]>b[i][j+1])) culegeLupul(i-1,j,k+1); else if((b[i+1][j]>b[i][j-1])&&(b[i+1][j]>b[i][j+1])) culegeLupul(i+1,j,k+1); else if(b[i][j-1]>b[i][j+1]) culegeLupul(i,j-1,k+1); else culegeLupul(i,j+1,k+1); }// culegeLupul(...) }// class

342

CAPITOLUL 15. METODA BACKTRACKING

Capitolul 16

Programare dinamic a

16.1

Prezentare general a

Folosirea tehnicii programrii dinamice solicit experient, intuitie i abiliti a a a s at matematice. De foarte multe ori rezolvrile date prin aceast tehnic au ordin de a a a complexitate polinomial. literatura de specialitate exist dou variante de prezentare a acestei In a a tehnici. Prima dintre ele este mai degrab de natur deductiv pe cnd a doua a a a a folosete gndirea inductiv. s a a Prima variant se bazeaz pe conceptul de subproblem. Sunt considerate a a a urmtoarele aspecte care caracterizeaz o rezolvare prin programare dinamic: a a a Problema se poate descompune recursiv mai multe subprobleme care sunt n caracterizate de optime partiale, iar optimul global se obtine prin combinarea acestor optime partiale. Subproblemele respective se suprapun. La un anumit nivel, dou sau mai a multe subprobleme necesit rezolvarea unei aceeai subprobleme. Pentru a a s evita risipa de timp rezultat urma unei implementri recursive, optimele a n a partiale se vor retine treptat, maniera bottom-up, anumite structuri de n n date (tabele). A doua variant de prezentare face apel la conceptele intuitive de sistem, stare a i decizie. O problem este abordabil folosind tehnica programrii dinamice dac s a a a a satisface principiul de optimalitate sub una din formele prezentate continuare. n Fie secventa de stri S0 , S1 , ..., Sn ale sistemului. a 343

344

CAPITOLUL 16. PROGRAMARE DINAMICA Dac d1 , d2 , ..., dn este un ir optim de decizii care duc la trecerea sistemului a s din starea S0 starea Sn , atunci pentru orice i (1 i n) di+1 , di+2 , ..., dn n este un ir optim de decizii care duc la trecerea sistemului din starea Si s starea Sn . Astfel, decizia di depinde de deciziile anterioare di+1 , ..., dn . n Spunem c se aplic metoda a a nainte. Dac d1 , d2 , ..., dn este un ir optim de decizii care duc la trecerea sistemului a s din starea S0 starea Sn , atunci pentru orice i (1 i n) d1 , d2 , ..., di este n un ir optim de decizii care duc la trecerea sistemului din starea S0 starea s n Si . Astfel, decizia di+1 depinde de deciziile anterioare d1 , ..., di . Spunem c a se aplic metoda a napoi. Dac d1 , d2 , ..., dn este un ir optim de decizii care duc la trecerea sistemului a s din starea S0 starea Sn , atunci pentru orice i (1 i n) di+1 , di+2 , ..., dn n este un ir optim de decizii care duc la trecerea sistemului din starea Si s n starea Sn i d1 , d2 , ..., di este un ir optim de decizii care duc la trecerea s s sistemului din starea S0 starea Si . Spunem c se aplic metoda mixt. n a a a

Indiferent de varianta de prezentare, rezolvarea prin programare dinamic a presupune gsirea i rezolvarea unui sistem de recurente. prima variant avem a s In a recurente ntre subprobleme, a doua variant avem recurente irul de decizii. n a n s afara cazurilor care recurentele sunt evidente, este necesar i o justicare In n as sau o demonstratie a faptului c aceste recurente sunt cele corecte. a Deoarece rezolvarea prin recursivitate duce de cele mai multe ori la ordin de complexitate exponential, se folosesc tabele auxiliare pentru retinerea optimelor partiale iar spatiul de solutii se parcurge ordinea cresctoare a dimensiunilor n a subproblemelor. Folosirea acestor tabele d i numele tehnicii respective. as Asemntor cu metoda divide et impera, programarea dinamic rezolv a a a a problemele combinnd solutiile unor subprobleme. Deosebirea const faptul c a a n a divide et impera partitioneaz problema subprobleme independente, le rezolv a n a (de obicei recursiv) i apoi combin solutiile lor pentru a rezolva problema initial, s a a timp ce programarea dinamic se aplic problemelor ale cror subprobleme nu n a a a sunt independente, ele avnd sub-subprobleme comune. aceast situatie, se a In a rezolv ecare sub-subproblem o singur dat i se folosete un tablou pentru a a a as s a memora solutia ei, evitndu-se recalcularea ei de cte ori subproblema reapare. a a Algoritmul pentru rezolvarea unei probleme folosind programarea dinamic a se dezvolta 4 etape: n 1. caracterizarea unei solutii optime (identicarea unei modalitti optime de a rezolvare, care satisface una dintre formele principiului optimalitii), at 2. denirea recursiv a valorii unei solutii optime, a 3. calculul efectiv al valorii unei solutii optime, 4. reconstituirea unei solutii pe baza informatiei calculate.

16.2. PROBLEME REZOLVATE

345

16.2

Probleme rezolvate

16.2.1

Inmultirea optimal a matricelor a

Considerm n matrice A1 , A2 , ..., An , de dimensiuni d0 d1 , d1 d2 , ..., a dn1 dn . Produsul A1 A2 ... An se poate calcula diverse moduri, aplicnd n a asociativitatea operatiei de nmultire a matricelor. Numim nmultire elementar a nmultirea a dou elemente. a functie de modul de parantezare difer numrul de In a a nmultiri elementare necesare pentru calculul produsului A1 A2 ... An . Se cere parantezare optimal a produsului A1 A2 ... An (pentru care a costul, adic numrul total de a a nmultiri elementare, s e minim). a Exemplu: Pentru 3 matrice de dimensiuni (10, 1000), (1000, 10) i (10, 100), produsul s A1 A2 A3 se poate calcula dou moduri: n a 1. (A1 A2 ) A3 necesitnd 1000000+10000=1010000 a nmultiri elementare 2. A1 (A2 A3 ), necesitnd 1000000+1000000=2000000 a nmultiri. Reamintim c numrul de a a nmultiri elementare necesare pentru a nmulti o matrice A, avnd n linii i m coloane, cu o matrice B, avnd m linii i p coloane, a s a s este n m p. Solutie 1. Pentru a calcula A1 A2 ...An , nal trebuie s n a nmultim dou matrice, a deci vom paranteza produsul astfel: (A1 A2 ... Ak ) (Ak+1 ... An ). Aceast observatie se aplic i produselor dintre paranteze. Prin urmare, a a s subproblemele problemei initiale constau determinarea parantezrii opti n a male a produselor de matrice de forma Ai Ai+1 ... Aj , 1 i j n. Observm c subproblemele nu sunt independente. De exemplu, calcularea a a produsului Ai Ai+1 ...Aj i calcularea produsului Ai+1 Ai+2 ...Aj+1 , s au ca subproblem comun calcularea produsului Ai+1 ... Aj . a a 2. Pentru a retine solutiile subproblemelor, vom utiliza o matrice M , cu n linii i n coloane, cu semnicatia: s M [i][j] = numrul minim de a nmultiri elementare necesare pentru a calcula produsul Ai Ai+1 ... Aj , 1 i j n. Evident, numrul minim de a nmultiri necesare pentru a calcula A1 A2 ... An este M [1][n].

346

CAPITOLUL 16. PROGRAMARE DINAMICA

3. Pentru ca parantezarea s e optimal, parantezarea produselor A1 A2 a a ... Ak i Ak+1 ... An trebuie s e de asemenea optimal. Prin urmare s a a elementele matricei M trebuie s satisfac urmatoarea relatie de recurent: a a a M [i][i] = 0, i = 1, 2, ..., n. M [i][j] = min {M [i][k] + M [k + 1][j] + d[i 1] d[k] d[j]}
ik<j

Cum interpretm aceast relatie de recurent? Pentru a determina numrul a a a a minim de nmultiri elementare pentru calculul produsului Ai Ai+1 ...Aj , xm pozitia de parantezare k toate modurile posibile ( a n ntre i i j 1), i s s alegem varianta care ne conduce la minim. Pentru o pozitie k xata, costul parantezrii este egal cu numarul de a nmultiri elementare necesare pentru calculul produsului Ai Ai+1 ...Ak , la care se adaug numrul de a a nmultiri elementare necesare pentru calculul produsului Ak+1 ... Aj i costul s nmultirii celor dou matrice rezultate (di1 dk dj ). a

Observm c numai jumtatea de deasupra diagonalei principale din M este a a a utilizat. Pentru a construi solutia optim este util i retinerea indicelui k, a a as pentru care se obtine minimul. Nu vom considera un alt tablou, ci-l vom retine, pe pozitia simetric fat de diagonala principala (M [j][i]). a a

4. Rezolvarea recursiv a relatiei de recurent este inecient, datorit faptua a a a lui c subproblemele se suprapun, deci o abordare recursiv ar conduce la a a rezolvarea aceleiai subprobleme de mai multe ori. Prin urmare vom rezolva s relatia de recurent mod bottom-up: (determinm parantezarea optimal a n a a a produselor de dou matrice, apoi de 3 matrice, 4 matrice, etc). a import java.io.*; class InmOptimalaMatrice { static int nmax=20; static int m[][]=new int[100][100]; static int p[]=new int[100]; static int n,i,j,k,imin,min,v; public static void paranteze(int i,int j) { int k; if(i<j) { k=m[j][i]; if(i!=k) { System.out.print("("); paranteze(i,k);

16.2. PROBLEME REZOLVATE System.out.print(")"); }//if else paranteze(i,k); System.out.print(" * "); if(k+1!=j) { System.out.print("("); paranteze(k+1,j); System.out.print(")"); }//if else paranteze(k+1,j); }//if else System.out.print("A"+i); }//paranteze

347

public static void main(String[]args) throws IOException { BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); System.out.print("numarul matricelor: "); n=Integer.parseInt(br.readLine()); System.out.println("Dimensiunile matricelor:"); for(i=1;i<=n+1;i++) { System.out.print("p["+i+"]="); p[i]=Integer.parseInt(br.readLine()); } for(i=n;i>=1;i--) for(j=i+1;j<=n;j++) { min=m[i][i]+m[i+1][j]+p[i]*p[i+1]*p[j+1]; imin=i; for(k=i+1;k<=j-1;k++) { v=m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1]; if(min>v) { min=v; imin=k; } } m[i][j]=min; m[j][i]=imin; }//for i,j

348

CAPITOLUL 16. PROGRAMARE DINAMICA

System.out.println("Numarul minim de inmultiri este: "+m[1][n]); System.out.print("Ordinea inmultirilor: "); paranteze(1,n); System.out.println(); }//main }//class /* numarul matricelor: 8 Dimensiunile matricelor: p[1]=2 p[2]=8 p[3]=3 p[4]=2 p[5]=7 p[6]=2 p[7]=5 p[8]=3 p[9]=7 Numarul minim de inmultiri este: 180 Ordinea inmultirilor: ((((A1 * A2) * A3) * (A4 * A5)) * (A6 * A7)) * A8 */

16.2.2

Subir cresctor maximal s a

Fie un ir A = (a1 , a2 , ..., an ). Numim subir al irului A o succesiune de s s s elemente din A, ordinea care acestea apar A: n n n ai1 , ai2 , ..., aik , unde 1 i1 < i2 < ... < ik n. Se cere determinarea unui subir cresctor al irului A, de lungime maxim. s a s a De exemplu, pentru A = (8, 3, 6, 50, 10, 8, 100, 30, 60, 40, 80) o solutie poate (3, 6, 10, 30, 60, 80). Rezolvare s a 1. Fie Ai1 = (ai1 ai2 ... aik ) cel mai lung subir cresctor al irului A. Observm c el coincide cu cel mai lung subir cresctor al irului s a a s a s s (ai1 , ai1 +1 , ..., an ). Evident Ai2 = (ai2 ai3 ... aik ) este cel mai lung subir a cresctor al lui (ai2 , ai2 +1 , ..., an ), etc. Prin urmare, o subproblem a problemei a initiale const determinarea celui mai lung subir cresctor care a n s a ncepe cu ai , i = {1, .., n}.

16.2. PROBLEME REZOLVATE

349

Subproblemele nu sunt independente: pentru a determina cel mai lung subir s cresctor care a ncepe cu ai , este necesar s determinm cele mai lungi subiruri a a s cresctoare care a ncep cu aj , ai aj , j = {i + 1, .., n}. 2. Pentru a retine solutiile subproblemelor vom considera doi vectori l i poz, s ecare cu n componente, avnd semnicatia: a l[i] =lungimea celui mai lung subir cresctor care s a ncepe cu a[i]; poz[i] =pozitia elementului care urmeaz dup a[i] cel mai lung subir a a n s cresctor care a ncepe cu a[i], dac un astfel de element exist, sau 1 dac un a a a astfel de element nu exist. a 3. Relatia de recurent care caracterizeaz substructura optimal a problemei a a a este: l[n] = 1; poz[n] = 1; l[i] = max {1 + l[j]|a[i] a[j]}
j=i+1,n

unde poz[i] = indicele j pentru care se obtine maximul l[i]. Rezolvm relatia de recurent mod bottom-up: a a n int i, j; l[n]=1; poz[n]=-1; for (i=n-1; i>0; i--) for (l[i]=1, poz[i]=-1, j=i+1; j<=n; j++) if (a[i] <= a[j] && l[i]<1+l[j]) { l[i]=1+l[j]; poz[i]=j; } Pentru a determina solutia optim a problemei, determinm valoarea maxim a a a din vectorul l, apoi aam solutia, s ncepnd cu pozitia maximului i utiliznd a s a informatiile memorate vectorul poz: n //determin maximul din vectorul l int max=l[1], pozmax=1; for (int i=2; i<=n; i++) if (max<l[i]) { max=l[i]; pozmax=i; } cout<<"Lungimea celui mai lung subsir crescator: " <<max; cout<<"\nCel mai lung subsir:\n"; for (i=pozmax; i!=-1; i=poz[i]) cout<<a[i]<< ; Secventele de program de mai sus sunt scrise c/C++. n

350

CAPITOLUL 16. PROGRAMARE DINAMICA Programele urmtoare sunt scrise Java: a n

import java.io.*; class SubsirCrescatorNumere { static int n; static int[] a; static int[] lg; static int[] poz; public static void main(String []args) throws IOException { int i,j; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("SubsirCrescatorNumere.in"))); PrintWriter out = new PrintWriter ( new BufferedWriter( new FileWriter("SubsirCrescatorNumere.out"))); st.nextToken();n=(int)st.nval; a=new int[n+1]; lg=new int[n+1]; poz=new int[n+1]; for(i=1;i<=n;i++) { st.nextToken(); a[i]=(int)st.nval; } int max,jmax; lg[n]=1; for(i=n-1;i>=1;i--) { max=0; jmax=0; for(j=i+1;j<=n;j++) if((a[i]<=a[j])&&(max<lg[j])) { max=lg[j]; jmax=j; } if(max!=0) { lg[i]=1+max; poz[i]=jmax; } else lg[i]=1; } max=0; jmax=0; for(j=1;j<=n;j++) if(max<lg[j]){ max=lg[j]; jmax=j; } out.println(max); int k; j=jmax; for(k=1;k<=max;k++) { out.print(a[j]+" "); j=poz[j]; } out.close(); }//main }//class

16.2. PROBLEME REZOLVATE import java.io.*; class SubsirCrescatorLitere {

351

public static void main(String[] args) throws IOException { int n,i,j,jmax,k,lmax=-1,pozmax=-1; int [] l,poz; String a; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("SubsirCrescatorLitere.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("SubsirCrescatorLitere.out"))); st.nextToken(); a=st.sval; n=a.length(); out.println(a+" "+n); l=new int[n];//l[i]=lg.celui mai lung subsir care incepe cu a[i] poz=new int[n];//poz[i]=pozitia elementului care urmeaza dupa a[i] for(i=0;i<n;i++) poz[i]=-1; l[n-1]=1; poz[n-1]=-1; for(i=n-2;i>=0;i--) { jmax=i; for(j=i+1;j<n;j++) if((a.charAt(i)<=a.charAt(j))&&(1+l[j]>1+l[jmax])) jmax=j; l[i]=1+l[jmax]; poz[i]=jmax; if(l[i]>lmax) { lmax=l[i]; pozmax=i; } } out.print("Solutia "); k=pozmax; out.print("("+l[pozmax]+") : "); for(j=1;j<=lmax;j++) { out.print(a.charAt(k)); k=poz[k]; } out.close(); } // main }// class

352

CAPITOLUL 16. PROGRAMARE DINAMICA

16.2.3

Sum maxim triunghi de numere a a n

S considerm un triunghi format din n linii (1 < n 100), ecare linie a a continnd numere a ntregi din domeniul [1, 99], ca exemplul urmtor: n a 7 3 8 2 4 5 7 2 1 4 6 8 0 4 5

Tabelul 16.1: Triunghi de numere Problema const scrierea unui program care s determine cea mai mare a n a sum de numere aate pe un drum a ntre numrul de pe prima linie i un numr a s a de pe ultima linie. Fiecare numr din acest drum este situat sub precedentul, la a stnga sau la dreapta acestuia. (IOI, Suedia 1994) a Rezolvare 1. Vom retine triunghiul ntr-o matrice ptratic T , de ordin n, sub diagonala a a principal. Subproblemele problemei date constau determinarea sumei maxime a n care se poate obtine din numere aate pe un drum ntre numarul T [i][j], pn la un a a numr de pe ultima linie, ecare numr din acest drum ind situat sub precedentul, a a la stnga sau la dreapta sa. Evident, subproblemele nu sunt independente: pentru a a calcula suma maxim a numerelor de pe un drum de la T [i][j] la ultima linie, a trebuie s calculm suma maxim a numerelor de pe un drum de la T [i + 1][j] la a a a ultima linie i suma maxim a numerelor de pe un drum de la T [i + 1][j + 1] la s a ultima linie. 2. Pentru a retine solutiile subproblemelor, vom utiliza o matrice S, ptratic a a de ordin n, cu semnicatia S[i][j] = suma maxim ce se poate obtine pe un drum de la T [i][j] la un a element de pe ultima linie, respectnd conditiile problemei. a Evident, solutia problemei va S[1][1]. 3. Relatia de recurenta care caracterizeaz substructura optimal a problemei a a este: S[n][i] = T [n][i], i = {1, 2, ..., n} S[i][j] = T [i][j] + max{S[i + 1][j], S[i + 1][j + 1]} 4. Rezolvm relatia de recurent mod bottom-up: a a n int i, j; for (i=1; i<=n; i++) S[n][i]=T[n][i]; for (i=n-1; i>0; i--) for (j=1; j<=i; j++)

16.2. PROBLEME REZOLVATE { S[i][j]=T[i][j]+S[i+1][j]; if (S[i+1][j]<S[i+1][j+1]) S[i][j]=T[i][j]+S[i+1][j+1]); }

353

Exercitiu: Aati i drumul triunghi pentru care se obtine solutia optim. s s n a

16.2.4

Subir comun maximal s

Fie X = (x1 , x2 , ..., xn ) i Y = (y1 , y2 , ..., ym ) dou iruri de n, respectiv m s as numere ntregi. Determinati un subir comun de lungime maxim. s a Exemplu Pentru X = (2, 5, 5, 6, 2, 8, 4, 0, 1, 3, 5, 8) i Y = (6, 2, 5, 6, 5, 5, 4, 3, 5, 8) o s solutie posibil este: Z = (2, 5, 5, 4, 3, 5, 8). a Solutie 1. Notm cu Xk = (x1 , x2 , ..., xk ) (prexul lui X de lungime k) i cu Yh = a s (y1 , y2 , ..., yh ) prexul lui Y de lungime h. O subproblem a problemei date const a a determinarea celui mai lung subir comun al lui Xk , Yh . Notam cu LCS(Xk , Yh ) n s lungimea celui mai lung subir comun al lui Xk , Yh . s Utiliznd aceste notatii, problema cere determinarea LCS(Xn , Ym ), precum a i un astfel de subir. s s Observatie 1. Dac Xk = Yh atunci a LCS(Xk , Yh ) = 1 + LCS(Xk1 , Yh1 ). 2. Dac Xk = Y h atunci a LCS(Xk, Y h) = max(LCS(Xk1 , Yh ), LCS(Xk , Yh1 )). Din observatia precedent deducem c subproblemele problemei date nu sunt a a independente i c problema are substructur optimal. s a a a 2. Pentru a retine solutiile subproblemelor vom utiliza o matrice cu n + 1 linii i m + 1 coloane, denumit lcs. Linia i coloana 0 sunt utilizate pentru initializare s a s cu 0, iar elementul lcs[k][h] va lungimea celui mai lung subir comun al irurilor s s Xk si Yh . 3. Vom caracteriza substructura optimal a problemei prin urmtoarea relatie a a de recurent: a lcs[k][0] = lcs[0][h] = 0, k = {1, 2, .., n}, h = {1, 2, .., m} max lcs[k][h 1], lcs[k 1][h], dac x[k] <> y[h] a lcs[k][h] = 1 + lcs[k 1][h 1], dac x[k] = y[h] a

Rezolvm relatia de recurent mod bottom-up: a a n

354

CAPITOLUL 16. PROGRAMARE DINAMICA

for (int k=1; k<=n; k++) for (int h=1; h<=m; h++) if (x[k]==y[h]) lcs[k][h]=1+lcs[k-1][h-1]; else if (lcs[k-1][h]>lcs[k][h-1]) lcs[k][h]=lcs[k-1][h]; else lcs[k][h]=lcs[k][h-1]; Deoarece nu am utilizat o structur de date suplimentar cu ajutorul creia a a a s memorm solutia optim, vom reconstitui solutia optim pe baza rezultatelor a a a a memorate matricea lcs. Prin reconstituire vom obtine solutia ordine invers, n n a din acest motiv vom memora solutia ntr-un vector, pe care vom aa de la l s sfrit ctre as a nceput: cout<<"Lungimea subsirului comun maximal: " <<lcs[n][m]; int d[100]; cout<<"\nCel mai lung subsir comun este: \n"; for (int i=0, k=n, h=m; lcs[k][h]; ) if (x[k]==y[h]) { d[i++]=x[k]; k--; h--; } else if (lcs[k][h]==lcs[k-1][h]) k--; else h--; for (k=i-1;k>=0; k--) cout<< d[k] << ; Secventele de cod de mai sus sunt scrise C/C++. Programul urmtor este n a scris Java i determin toate solutiile. Sunt determinate cea mai mic i cea mai n s a as mare solutie, ordine lexicograc. De asemenea sunt aate matricele auxiliare n a s de lucru pentru a se putea urmrii mai uor modalitatea de determinare recursiv a s a a tuturor solutiilor. import java.io.*; // SubsirComunMaximal class scm { static PrintWriter out; static int [][] a;

16.2. PROBLEME REZOLVATE static char [][] d; static static static static String x,y; char[] z,zmin,zmax; int nsol=0,n,m; final char sus=|, stanga=-, diag=*;

355

public static void main(String[] args) throws IOException { citire(); n=x.length(); m=y.length(); out=new PrintWriter(new BufferedWriter( new FileWriter("scm.out"))); int i,j; matrad(); out.println(a[n][m]); afism(a); afism(d); z=new char[a[n][m]+1]; zmin=new char[z.length]; zmax=new char[z.length]; System.out.println("O solutie oarecare"); osol(n,m);// o solutie System.out.println("\nToate solutiile"); toatesol(n,m,a[n][m]);// toate solutiile out.println(nsol); System.out.print("SOLmin = "); afisv(zmin); System.out.print("SOLmax = "); afisv(zmax); out.close(); } static void citire() throws IOException { BufferedReader br=new BufferedReader(new FileReader("scm.in")); x=br.readLine(); y=br.readLine(); }

356

CAPITOLUL 16. PROGRAMARE DINAMICA

static void matrad() { int i,j; a=new int[n+1][m+1]; d=new char[n+1][m+1]; for(i=1;i<=n;i++) for(j=1;j<=m;j++) if(x.charAt(i-1)==y.charAt(j-1)) // x.charAt(i)==y.charAt(j) ! { a[i][j]=1+a[i-1][j-1]; d[i][j]=diag; } else { a[i][j]=max(a[i-1][j],a[i][j-1]); if(a[i-1][j]>a[i][j-1]) d[i][j]=sus; else d[i][j]=stanga; } } static void osol(int lin, int col) { if((lin==0)||(col==0)) return; if(d[lin][col]==diag) osol(lin-1,col-1); else if(d[lin][col]==sus) osol(lin-1,col); else osol(lin,col-1); if(d[lin][col]==diag) System.out.print(x.charAt(lin-1)); } static void toatesol(int lin, int col,int k) { int i,j; if(k==0) { System.out.print(++nsol+" "); afisv(z); zminmax(); return; } i=lin+1; while(a[i-1][col]==k)//merg in sus

16.2. PROBLEME REZOLVATE {

357

i--; j=col+1; while(a[i][j-1]==k) j--; if( (a[i][j-1]==k-1)&&(a[i-1][j-1]==k-1)&&(a[i-1][j]==k-1)) { z[k]=x.charAt(i-1); toatesol(i-1,j-1,k-1); } }//while } static void zminmax() { if(nsol==1) { copiez(z,zmin); copiez(z,zmax); } else if(compar(z,zmin)<0) copiez(z,zmin); else if(compar(z,zmax)>0) copiez(z,zmax); } static int compar(char[] z1, char[] z2)//-1=<; 0=identice; 1=> { int i=1; while(z1[i]==z2[i]) i++;// z1 si z2 au n componente 1..n if(i>n) return 0; else if(z1[i]<z2[i]) return -1; else return 1; } static void copiez(char[] z1, char[] z2) { int i; for(i=1;i<z1.length;i++) z2[i]=z1[i]; }

358

CAPITOLUL 16. PROGRAMARE DINAMICA

static int max(int a, int b) { if(a>b) return a; else return b; } static void afism(int[][]a)// difera tipul parametrului !!! { int n=a.length; int i,j,m; m=y.length(); System.out.print(" "); for(j=0;j<m;j++) System.out.print(y.charAt(j)+" "); System.out.println(); System.out.print(" "); for(j=0;j<=m;j++) System.out.print(a[0][j]+" "); System.out.println(); for(i=1;i<n;i++) { System.out.print(x.charAt(i-1)+" "); m=a[i].length; for(j=0;j<m;j++) System.out.print(a[i][j]+" "); System.out.println(); } System.out.println("\n"); } static void afism(char[][]d)// difera tipul parametrului !!! { int n=d.length; int i,j,m; m=y.length(); System.out.print(" "); for(j=0;j<m;j++) System.out.print(y.charAt(j)+" "); System.out.println(); System.out.print(" "); for(j=0;j<=m;j++) System.out.print(d[0][j]+" "); System.out.println();

16.2. PROBLEME REZOLVATE for(i=1;i<n;i++) { System.out.print(x.charAt(i-1)+" "); m=d[i].length; for(j=0;j<m;j++) System.out.print(d[i][j]+" "); System.out.println(); } System.out.println("\n"); } static void afisv(char[]v) { int i; for(i=1;i<=v.length-1;i++) System.out.print(v[i]); for(i=1;i<=v.length-1;i++) out.print(v[i]); System.out.println(); out.println(); } } Pe ecran apar urmtoarele rezultate: a 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 3 0 1 1 2 2 2 2 2 2 2 2 2 2 2 0 1 2 2 2 2 2 2 2 2 2 2 2 4 0 1 2 2 3 3 3 3 3 3 3 3 3 6 0 1 2 2 3 3 4 4 4 4 4 4 4 5 0 1 2 2 3 4 4 4 4 4 4 4 4 a 0 1 2 2 3 4 4 5 5 5 5 5 5 c 0 1 2 2 3 4 4 5 5 6 6 6 6 b 0 1 2 2 3 4 4 5 6 6 6 6 6 d 0 1 2 2 3 4 4 5 6 6 7 7 7 f 0 1 2 2 3 4 4 5 6 6 7 7 8 e 0 1 2 2 3 4 4 5 6 6 7 8 8

359

1 2 3 4 5 6 a b c d e f

1 3 2 4 6 5 a c b d f e 1 2 3 4 * | | | * | * * -

360 5 6 a b c d e f | | | | | | | | | | | | | | | | | | | | | | | | * | | | | | | * * | | | | | * | | | * * | | *

CAPITOLUL 16. PROGRAMARE DINAMICA * -

O solutie oarecare 1346acdf Toate solutiile 1 1346acdf 2 1246acdf 3 1345acdf 4 1245acdf 5 1346abdf 6 1246abdf 7 1345abdf 8 1245abdf 9 1346acde 10 1246acde 11 1345acde 12 1245acde 13 1346abde 14 1246abde 15 1345abde 16 1245abde SOLmin = 1245abde SOLmax = 1346acdf

16.2.5

Distanta minim de editare a

Fie d(s1, s2) distanta de editare (denit ca ind numrul minim de operatii a a de tergere, inserare i modicare) dintre irurile de caractere s1 i s2. Atunci: s s s s d(, ) = 0 d(s, ) = d(, s) = |s|, (16.2.1) (16.2.2)

Avnd vedere ultima operatie efectuat asupra primului ir de caractere, a n a s la sfritul acestuia (dar dup modicrile efectuate deja), obtinem: as a a

16.2. PROBLEME REZOLVATE

361

d(s1 + ch1 , s2 ) + 1, inserare ch2 d(s , s + ch ) + 1, tergere ch s 1 2 2 1 d(s1 + ch1 , s2 + ch2 ) = min (16.2.3) a d(s , s ) + 0 dac ch1 = ch2 , nimic! 1 2 1 dac ch1 = ch2 , a nlocuire Folosim o matrice c[0..|s1|][0..|s2|] unde c[i][j] = d(s1 [1..i], s2 [1..j]). Algoritmul pseudocod este: n
def

m[0][0]=0; for(i=1; i<=length(s1); i++) m[i][0]=i; for(j=1; j<=length(s2); j++) m[0][j]=j; for(i=1; i<=length(s1); i++) for(j=1;j<=length(s2); j++) { val=(s1[i]==s2[j]) ? 0 : 1; m[i][j]=min( m[i-1][j-1]+val, m[i-1][j]+1, m[i][j-1]+1 ); } Programul surs: a import java.io.*; class DistEdit { static final char inserez=i, // inserez inaintea pozitiei sterg=s, modific=m, nimic= ; // caracter !!! static final char sus=|, stanga=-, diags=x; static static static static int na,nb; int [][] c; // c[i][j] = cost a1..ai --> b1..bj char [][] op; // op = operatia efectuata char [][] dir; // dir = directii pentru minim !!! // a--> b

static String a,b;

public static void main(String[] args) throws IOException { int i,j,cmin=0;

362

CAPITOLUL 16. PROGRAMARE DINAMICA BufferedReader br=new BufferedReader( new FileReader("distedit.in")); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("distedit.out"))); a=br.readLine(); b=br.readLine(); na=a.length(); nb=b.length(); c=new int[na+1][nb+1]; op=new char[na+1][nb+1]; dir=new char[na+1][nb+1]; System.out.println(a+" --> "+na); System.out.println(b+" --> "+nb); for(i=1;i<=na;i++) { c[i][0]=i; // stergeri din a; b=vid !!! op[i][0]=sterg; // s_i dir[i][0]=sus; } for(j=1;j<=nb;j++) { c[0][j]=j; // inserari in a; op[0][j]=inserez; //t_j dir[0][j]=stanga; } op[0][0]=nimic; dir[0][0]=nimic; for(i=1;i<=na;i++) { for(j=1;j<=nb;j++) { if(a.charAt(i-1)==b.charAt(j-1)) { c[i][j]=c[i-1][j-1]; op[i][j]=nimic; dir[i][j]=diags; }

a=vid !!!

16.2. PROBLEME REZOLVATE else { cmin=min(c[i-1][j-1],c[i-1][j],c[i][j-1]); c[i][j]=1+cmin; if(cmin==c[i][j-1]) // inserez t_j { op[i][j]=inserez; dir[i][j]=stanga; } else if(cmin==c[i-1][j]) // sterg s_i { op[i][j]=sterg; dir[i][j]=sus; } else if(cmin==c[i-1][j-1]) //s_i-->t_j { op[i][j]=modific; dir[i][j]=diags; } }// else }// for j }// for i afismi(c,out); afismc(dir,out); afismc(op,out); afissol(na,nb,out); out.println("\nCOST transformare = "+c[na][nb]); out.close(); System.out.println("COST transformare = "+c[na][nb]); }// main static void afismc(char[][] x, PrintWriter out) { int i,j; out.print(" "); for(j=1;j<=nb;j++) out.print(b.charAt(j-1)); for(i=0;i<=na;i++) { out.println(); if(i>0) out.print(a.charAt(i-1)); else out.print(" ");

363

364

CAPITOLUL 16. PROGRAMARE DINAMICA

for(j=0;j<=nb;j++) out.print(x[i][j]); } out.println("\n"); }// afismc(...) static void afismi(int[][] x, PrintWriter out) { int i,j; out.print(" "); for(j=1;j<=nb;j++) out.print(b.charAt(j-1)); for(i=0;i<=na;i++) { out.println(); if(i>0) out.print(a.charAt(i-1)); else out.print(" "); for(j=0;j<=nb;j++) out.print(x[i][j]); } out.println("\n"); }// afismi(...) static void afissol(int i,int j, PrintWriter out) { if(i==0&&j==0) return; if(dir[i][j]==diags) afissol(i-1,j-1,out); else if(dir[i][j]==stanga) afissol(i,j-1,out); else if(dir[i][j]==sus) afissol(i-1,j,out); if((i>0)&&(j>0)) { if(op[i][j]==sterg) out.println(i+" "+a.charAt(i-1)+" "+op[i][j]); else if(op[i][j]==inserez) out.println(" "+op[i][j]+" "+j+" "+b.charAt(j-1)); else out.println(i+" "+a.charAt(i-1)+" "+op[i][j]+" "+j+" "+b.charAt(j-1)); } else if(i==0) out.println(i+" "+a.charAt(i)+" "+op[i][j]+" "+j+" "+b.charAt(j-1)); else if(j==0) out.println(i+" "+a.charAt(i-1)+" "+op[i][j]+" "+j+" "+b.charAt(j));

16.2. PROBLEME REZOLVATE }//afissol(...)

365

static int min(int a, int b) { return a<b ? a:b; } static int min(int a, int b, int c) { return min(a,min(b,c)); } }// class Rezultate aate ierul de ieire: s n s s altruisti 0123456789 a1012345678 l2101234567 g3211234567 o4322234567 r5433234567 i6544333456 t7654444445 m8765555555 i9876665665 altruisti --------a|x-------l||x------g|||x-----o||||x----r||||x----i|||||xx--x t|||x|||xxm|||||||||x i||||||x-|x altruisti iiiiiiiii as iiiiiiii lss iiiiiii gsssmiiiiii ossssmiiiii rssss iiiii isssssm ii tsss sssm i msssssssssm issssss is

366 1 2 3 4 5 6 7 8 9 a 1 l 2 g m 3 o s r 4 i 5 i 6 i 7 t 8 m s i 9 a l t r u i s t i

CAPITOLUL 16. PROGRAMARE DINAMICA

COST transformare = 5

16.2.6

Problema rucsacului (0 1)

import java.io.*; class Rucsac01 { public static void main(String[] args) throws IOException { int n,m; int i,j; int[] g,v; int[][] c; PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("rucsac.out"))); StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("rucsac.in"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; g=new int[n+1]; v=new int[n+1]; c=new int[n+1][m+1]; for(i=1;i<=n;i++) { st.nextToken(); g[i]=(int)st.nval; } for(i=1;i<=n;i++) { st.nextToken(); v[i]=(int)st.nval; } for(i=1; i<=n; i++) c[i][0]=0;

16.2. PROBLEME REZOLVATE for(j=0; j<=m; j++) c[0][j]=0; for(i=1; i<=n; i++) for(j=1; j<=m; j++) if(g[i]>j) c[i][j]=c[i-1][j]; else c[i][j]=max( c[i-1][j], c[i-1][j-g[i]] + v[i] ); out.println(c[n][m]); out.close(); }// main(...) static int max(int a, int b) { if(a>b) return a; else return b; }// max(...) }

367

16.2.7

Problema schimbului monetar

import java.io.*; class Schimb { public static void main(String[] args) throws IOException { int v,n; int i,j; int[] b, ns; PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("schimb.out"))); StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("schimb.in"))); st.nextToken(); v=(int)st.nval; st.nextToken(); n=(int)st.nval; b=new int[n+1]; ns=new int[v+1]; for(i=1;i<=n;i++) { st.nextToken(); b[i]=(int)st.nval; }

368

CAPITOLUL 16. PROGRAMARE DINAMICA

ns[0]=1; for(i=1; i<=n; i++) for(j=b[i]; j<=v; j++) ns[j]+=ns[j-b[i]]; out.println(ns[v]); out.close(); }// main }// class

16.2.8

Problema traversrii matricei a

Traversarea unei matrice de la Vest la Est; sunt permise deplasri spre vecinii a unei pozitii (chiar i deplasri prin exteriorul matricei). s a import java.io.*; class Traversare { public static void main(String[] args) throws IOException { int m,n; int i,j,imin; int[][] a,c,t; int[] d; int cmin,minc; PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("traversare.out"))); StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("traversare.in"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new c=new t=new d=new int[m][n]; int[m][n]; int[m][n]; int[n];

for(i=0;i<m;i++) for(j=0;j<n;j++) { st.nextToken();

16.2. PROBLEME REZOLVATE a[i][j]=(int)st.nval; } for(i=0;i<m;i++) c[i][0]=a[i][0]; for(j=1; j<n; j++) for(i=0; i<m; i++) { minc=min(c[(i+m-1)%m][j-1], c[i][j-1], c[(i+1)%m][j-1]); c[i][j]=a[i][j]+minc; if(minc==c[(i+m-1)%m][j-1]) t[i][j]=((i+m-1)%m)*n+(j-1); else if(minc==c[i][j-1]) t[i][j]=i*n+(j-1); else t[i][j]=((i+1)%m)*n+(j-1); } imin=0; cmin=c[0][n-1]; for(i=1;i<m;i++) if(c[i][n-1]<cmin) { cmin=c[i][n-1]; imin=i; } out.println(cmin); d[n-1]=imin*n+(n-1); j=n-1; i=imin; while(j>0) { i=t[i][j]/n; j--; d[j]=i*n+j; } for(j=0;j<n;j++) out.println((1+d[j]/n)+" "+(1+d[j]%n)); out.close(); }// main(...) static int min(int a, int b) { if(a<b) return a; else return b; } static int min(int a, int b, int c)

369

370 {

CAPITOLUL 16. PROGRAMARE DINAMICA

return min(min(a,b),c); } }// class /* traversare.in traversare.out 3 4 6 2 1 3 2 2 1 1 3 5 4 1 2 3 4 2 7 3 3 1 4 */

16.2.9

Problema segmentrii vergelei a

Scopul algoritmului este de a realiza n tieturi, dea lungul unei vergele a n locuri pre-specicate, cu efort minim (sau cost). Costul ecrei operatii de tiere a a este proportional cu lungimea vergelei care trebuie tiat. viata real, ne putem a a In a imagina costul ca ind efortul depus pentru a plasa vergeaua (sau un butean!) s n maina de tiat. s a Considerm irul de numere naturale 0 < x1 < x2 < ... < xn < xn+1 care a s n xn+1 reprezint lungimea vergelei iar x1 , x2 , ..., xn reprezint abscisele punctelor a a care se vor realiza tieturile (distantele fat de captul din stnga al vergelei). n a a a a Notm prin c[i][j] (i < j) costul minim necesar realizrii tuturor tieturilor a a a segmentului de vergea [xi ..xj ]. Evident c[i][i + 1] = 0 pentru c nu este necesar nici o tietur. a a a a Pentru j > i s presupunem c realizm prima tietur xk (i < k < j). Din a a a a a n vergeaua [xi ...xj ] obtinem dou bucati mai mici: [xi ...xk ] i [xk ...xj ]. Costul pentru a s tierea vergelei [xi ...xj ] este format din costul transportului acesteia la maina de a s tiat (xj xi ) + costul tierii vergelei [xi ...xk ] (adic c[i][k]) i + costul tierii a a a s a vergelei [xk ...xj ] (adic c[k][j]). a Dar, ce valoare are k? Evident, k trebuie s aib acea valoare care s minia a a mizeze expresia c[i][k] + c[k][j]. Obtinem: c[i][j] = xj xi + min {c[i][k] + c[k][j]}
i<k<j

import java.io.*; class Vergea { static int n,nt=0; static int x[]; static int c[][]; static int t[][];

16.2. PROBLEME REZOLVATE

371

static BufferedReader br; // pentru "stop" (pentru depanare!) public static void main(String[]args) throws IOException { int i,j,h,k,min,kmin; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("vergea.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("vergea.out"))); br=new BufferedReader(new InputStreamReader(System.in)); st.nextToken(); n=(int)st.nval; x=new int[n+2]; c=new int[n+2][n+2]; t=new int[n+2][n+2]; for(i=1;i<=n+1;i++) { st.nextToken(); x[i]=(int)st.nval; } System.out.println("n="+n); System.out.print("x: "); for(i=1;i<=n+1;i++) System.out.print(x[i]+" "); System.out.println(); for(i=0;i<=n;i++) c[i][i+1]=0; j=-1; for(h=2;h<=n+1;h++) // lungimea vargelei { for(i=0;i<=n+1-h;i++) // inceputul vargelei { j=i+h; // sfarsitul vargelei c[i][j]=x[j]-x[i]; min=Integer.MAX_VALUE; kmin=-1; for(k=i+1;k<=j-1;k++) if(c[i][k]+c[k][j]<min) { min=c[i][k]+c[k][j]; kmin=k; } c[i][j]+=min; t[i][j]=kmin; }//for i

372 }// for h

CAPITOLUL 16. PROGRAMARE DINAMICA

out.println(c[0][n+1]); out.close(); afism(c); afism(t); System.out.println("Ordinea taieturilor: \n"); taieturi(0,n+1); System.out.println(); }//main public static void taieturi(int i,int j) throws IOException { if(i>=j-1) return; int k; k=t[i][j]; System.out.println((++nt)+" : "+i+".."+j+" --> "+k); //br.readLine(); // "stop" pentru depanare ! if((i<k)&&(k<j)) { taieturi(i,k); taieturi(k,j); } }//taieturi static void afism(int[][] x) // pentru depanare ! { int i,j; for(i=0;i<=n+1;i++) { for(j=0;j<=n+1;j++) System.out.print(x[i][j]+" "); System.out.println(); } System.out.println(); }// afism(...) }//class /* n=5 x: 2 4 5 8 12 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 8 16 27 38 3 9 19 29 0 4 12 22 0 0 7 17 0 0 0 7 0 0 0 0 0 0 0 0

16.2. PROBLEME REZOLVATE 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 2 0 0 0 0 0 2 3 3 0 0 0 0 3 4 4 4 0 0 0 4 4 4 4 5 0 0

373

Ordinea taieturilor: 1 : 2 : 3 : 4 : 5 : */ 0..6 0..4 0..2 2..4 4..6 --> --> --> --> --> 4 2 1 3 5

16.2.10

Triangularizarea poligoanelor convexe

Considerm un poligon convex cu n vrfuri numerotate cu 1, 2, ..., n ( gur a a n a n = 9) i dorim s obtinem o triangularizare care suma lungimilor diagonalelor s a n trasate s e minim (ca i cum am dori s consumm ct mai putin tu pentru a a s a a a s trasarea acestora!).
1 2 9 8 7 3 4 6 5 3 4 5 5 5 6 4 555 2 1 9 9 8 7 3 6 6 2 11 9 9

8 8

8 7

Evident, orice latur a poligonului face parte dintr-un triunghi al triangulatiei. a Considerm la a nceput latura [1, 9]. S presupunem c a a ntr-o anumit triangulatie a optim latura [1, 9] face parte din triunghiul [1, 5, 9]. Diagonalele triangulatiei a optime vor genera o triangulatie optim a poligoanelor convexe [1, 2, 3, 4, 5] i a s [5, 6, 7, 8, 9]. Au aprut astfel dou subprobleme ale problemei initiale. a a S notm prin p(i, k, j) perimetrul triunghiului [i, k, j] (i < k < j) ii prin a a s c[i][j] costul minim al triagulatiei poligonului convex [i, i + 1, ..., j] (unde i < j). Atunci: c[i][j] = 0, dac j = i + 1 a

374 i s

CAPITOLUL 16. PROGRAMARE DINAMICA

c[i][j] = min {p(i, k, j) + c[i][k] + c[k][j]}, dac i < j n a


ik<j

16.2.11

Algoritmul Roy-Floyd-Warshall

// Lungime drum minim intre oricare doua varfuri in graf orientat ponderat. // OBS: daca avem un drum de lungime minima de la i la j atunci acest drum // va trece numai prin varfuri distincte, iar daca varful k este varf intermediar, // atunci drumul de la i la k si drumul de la k la j sunt si ele minime // (altfel ar exista un drum mai scurt de la i la j); astfel, este indeplinit // "principiul optimalitatii" import java.io.*; class RoyFloydWarshall // O(n^3) { static final int oo=0x7fffffff; static int n,m; static int[][] d; public static void main(String[]args) throws IOException { int i,j,k; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("RoyFloydWarshall.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("RoyFloydWarshall.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; d=new int[n+1][n+1]; for(i=1;i<=n;i++) for(k=1;k<=m;k++) { st.nextToken(); st.nextToken(); st.nextToken(); } for(j=1;j<=n;j++) d[i][j]=oo;

i=(int)st.nval; j=(int)st.nval; d[i][j]=d[j][i]=(int)st.nval;

for(k=1;k<=n;k++) for(i=1;i<=n;i++)

// drumuri intre i si j care trec

16.2. PROBLEME REZOLVATE for(j=1;j<=n;j++) // numai prin nodurile 1, 2, ..., k if((d[i][k]<oo)&&(d[k][j]<oo)) if(d[i][j]>d[i][k]+d[k][j]) d[i][j]=d[i][k]+d[k][j]; for(i=1;i<=n;i++) { for(j=1;j<=n;j++) if(d[i][j]<oo) System.out.print(d[i][j]+" "); else System.out.print("*"+" "); System.out.println(); } out.close(); }//main }//class /* 6 6 1 2 1 3 2 3 4 5 5 6 4 6 */

375

3 1 2 1 3 2

2 3 1 * * *

3 4 2 * * *

1 2 2 * * *

* * * 2 1 2

* * * 1 2 3

* * * 2 3 4

16.2.12

Oracolul decide - ONI2001 cls 10

prof. Doru Popescu Anastasiu, Slatina La un concurs particip N concurenti. Fiecare concurent primete o foaie de a s hrtie pe care va scrie un cuvnt avnd cel mult 100 de caractere (litere mici ale a a a alfabetului englez). Cuvintele vor distincte. Pentru departajare, concurentii apeleaz la un oracol. Acesta produce i el a s un cuvnt. Va ctiga concurentul care a scris cuvntul cel mai apropiat de al as a oracolului. Gradul de apropiere dintre dou cuvinte este lungimea subcuvntului coa a mun de lungime maxim. Prin subcuvnt al unui cuvnt dat se elege un cuvnt a a a nt a care se poate obtine din cuvntul dat, eliminnd 0 sau mai multe litere i pstrnd a a s a a ordinea literelor rmase. a Cerint a Se cunosc cuvntul c0 produs de oracol i cuvintele ci , i = 1, ..., N scrise de a s concurenti. Pentru a ajuta comisia s desemneze ctigtorul, se cere ca pentru a as a ecare i s identicati pozitiile literelor ce trebuie terse din c0 i din ci astfel at a s s nc prin tergere s se obtin unul dintre subcuvintele comune de lungime maxim. s a a a

376

CAPITOLUL 16. PROGRAMARE DINAMICA

Date de intrare Fiier de intrare: ORACOL.IN s Linia 1: N numr natural nenul, reprezentnd numrul concurentilor; a a a Linia 2: c0 cuvntul produs de oracol; a Liniile 3..N+2: cuvnt a pe aceste N linii se a cuvintele scrise de cei a N concurenti, un cuvnt pe o linie; a Date de ieire s Fiier de ieire: ORACOL.OUT s s Liniile 1..2*N: pozitiile literelor ce trebuie terse s pe ecare linie i (i = 1, 3, ..., 2 N 1) se vor scrie numere naturale nenule, separate prin cte a un spatiu, reprezentnd pozitiile de pe care se vor terge litere din cuvntul pro a s a dus de oracol; pe ecare linie j (j = 2, 4, ..., 2 N ) se vor scrie numere naturale nenule, separate prin cte un spatiu, reprezentnd pozitiile de pe care se vor terge a a s litere din cuvntul concurentului cu numrul j/2. a a Restrictii 2 N 100 Dac exist mai multe solutii, ier se va scrie una singur. a a n s a Dac dintr-un cuvnt nu se va tia nici o liter, linia respectiv din ierul a a a a a s de intrare va rmne vid. a a a Exemplu ORACOL.IN ORACOL.OUT poate contine solutia: 3 3 abc 34 abxd aabxyc 145 acb 3 2 Timp maxim de executare/test: 1 secund a Codul surs a Varianta 1: import java.io.*; // subsir comun maximal - problema clasica ... class Oracol // varianta cu mesaje pentru depanare ... { static final char sus=|, stanga=-, diag=*; static int[][] a; static char[][] d; static String x,y; static boolean[] xx=new boolean[101]; static boolean[] yy=new boolean[101];

16.2. PROBLEME REZOLVATE static char[] z; static int m,n,nc;

377

public static void main(String[] args) throws IOException { int i,j,k; BufferedReader br=new BufferedReader(new FileReader("oracol.in")); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("oracol.out"))); nc=Integer.parseInt(br.readLine()); x=br.readLine().replace(" ",""); // elimina spatiile ... de la sfarsit ! m=x.length(); for(k=1;k<=nc;k++) { y=br.readLine().replaceAll(" ",""); // elimina spatiile ... daca sunt! n=y.length(); matrad(); afism(a); afism(d); System.out.print("O solutie oarecare: "); z=new char[a[m][n]+1]; for(i=1;i<=m;i++) xx[i]=false; for(j=1;j<=n;j++) yy[j]=false; osol(m,n); System.out.println("\n"); for(i=1;i<=m;i++) if(!xx[i]) out.print(i+" "); out.println(); for(j=1;j<=n;j++) if(!yy[j]) out.print(j+" "); out.println(); } out.close(); System.out.println("\n"); }// main(...) static void matrad() { int i,j; a=new int[m+1][n+1];

378

CAPITOLUL 16. PROGRAMARE DINAMICA

d=new char[m+1][n+1]; for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(x.charAt(i-1)==y.charAt(j-1)) { a[i][j]=1+a[i-1][j-1]; d[i][j]=diag; } else { a[i][j]=max(a[i-1][j],a[i][j-1]); if(a[i-1][j]>a[i][j-1]) d[i][j]=sus; else d[i][j]=stanga; } }// matrad() static void osol(int lin, int col) { if((lin==0)||(col==0)) return; if(d[lin][col]==diag) osol(lin-1,col-1); else if(d[lin][col]==sus) osol(lin-1,col); else osol(lin,col-1); if(d[lin][col]==diag) { System.out.print(x.charAt(lin-1)); xx[lin]=yy[col]=true; } }// osol(...) static int max(int a, int b) { if(a>b) return a; else return b; }// max(...) static void afism(int[][] a) { int i,j; System.out.print(" "); for(j=0;j<n;j++) System.out.print(y.charAt(j)+" "); System.out.println();

16.2. PROBLEME REZOLVATE System.out.print(" "); for(j=0;j<=n;j++) System.out.print(a[0][j]+" "); System.out.println(); for(i=1;i<=m;i++) { System.out.print(x.charAt(i-1)+" "); for(j=0;j<=n;j++) System.out.print(a[i][j]+" "); System.out.println(); } System.out.println("\n"); }// afism(int[][]...) static void afism(char[][] d) // difera tipul parametrului { int i,j; System.out.print(" "); for(j=0;j<n;j++) System.out.print(y.charAt(j)+" "); System.out.println(); System.out.print(" "); for(j=0;j<=n;j++) System.out.print(d[0][j]+" "); System.out.println(); for(i=1;i<=m;i++) { System.out.print(x.charAt(i-1)+" "); for(j=0;j<=n;j++) System.out.print(d[i][j]+" "); System.out.println(); } System.out.println("\n"); }// afism(char[][]...) }// class Varianta 2: import java.io.*; // problema reala ... class Oracol { static final char sus=|, stanga=-, diag=*; static int[][] a; static char[][] d; static String x,y; static boolean[] xx=new boolean[101];

379

380

CAPITOLUL 16. PROGRAMARE DINAMICA

static boolean[] yy=new boolean[101]; static int m,n,nc; public static void main(String[] args) throws IOException { int i,j,k; BufferedReader br=new BufferedReader(new FileReader("oracol.in")); PrintWriter out=new PrintWriter( new BufferedWriter( new FileWriter("oracol.out"))); nc=Integer.parseInt(br.readLine()); x=br.readLine().replace(" ",""); // elimina spatiile ... de la sfarsit ! m=x.length(); for(k=1;k<=nc;k++) { y=br.readLine().replaceAll(" ",""); // elimina spatiile ... daca sunt! n=y.length(); matrad(); for(i=1;i<=m;i++) xx[i]=false; for(j=1;j<=n;j++) yy[j]=false; osol(m,n); for(i=1;i<=m;i++) if(!xx[i]) out.print(i+" "); out.println(); for(j=1;j<=n;j++) if(!yy[j]) out.print(j+" "); out.println(); } out.close(); }// main(...) static void matrad() { int i,j; a=new int[m+1][n+1]; d=new char[m+1][n+1]; for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(x.charAt(i-1)==y.charAt(j-1)) { a[i][j]=1+a[i-1][j-1]; d[i][j]=diag;

16.2. PROBLEME REZOLVATE } else { a[i][j]=max(a[i-1][j],a[i][j-1]); if(a[i-1][j]>a[i][j-1]) d[i][j]=sus; else d[i][j]=stanga; } }// matrad() static void osol(int lin, int col) { if((lin==0)||(col==0)) return; if(d[lin][col]==diag) osol(lin-1,col-1); else if(d[lin][col]==sus) osol(lin-1,col); else osol(lin,col-1); if(d[lin][col]==diag) xx[lin]=yy[col]=true; }// osol(...) static int max(int a, int b) { if(a>b) return a; else return b; }// max(...) }// class

381

16.2.13

Pavri - ONI2001 clasa a X-a a

prof. Doru Popescu Anastasiu, Slatina Se d un dreptunghi cu lungimea egal cu 2N centimetri i limea egal cu a a s at a 3 centimetri . Cerint a S se determine numrul M al pavrilor distincte cu dale dreptunghiulare a a a care au lungimea egal cu un centimetru i limea egal cu 2 centimetri. a s at a Datele de intrare Fiier de intrare: pavari.in s Linia 1: N - numr natural nenul, reprezentnnd jumtatea lungimii drepa a a tunghiului. Datele de ieire s Fiier de ieire: pavari.out s s Linia 1: M - numr natural nenul, reprezentnd numrul modalitilor de a a a a at pava dreptunghiul. Restrictii i precizri s a 1 N 100

382 Exemplu pavari.in 2

CAPITOLUL 16. PROGRAMARE DINAMICA

pavari.out 11

Timp maxim de executare: 1 secund/test a Codul surs * a

import java.io.*; // x[n]=x[n-1]+2*y[n-1]; x[1]=3; class Pavari // y[n]=y[n-1]+x[n]; y[1]=4; { public static void main(String[] args) throws IOException { int k,kk,n; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("pavari9.in"))); st.nextToken(); n=(int)st.nval; int[] xv,xn,yv,yn; xv=new int[1]; yv=new int[1]; xv[0]=3; yv[0]=4; xn=xv; for(k=2;k<=n;k++) { xn=suma(xv,suma(yv,yv)); yn=suma(yv,xn); xv=xn; yv=yn; } PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("pavari.out"))); for(kk=xn.length-1;kk>=0;kk--) out.print(xn[kk]); out.close(); }// main(...) static int[] suma(int[] x, int[] y) { int nx=x.length,ny=y.length,i,t; int nz; if(nx>ny) nz=nx+1; else nz=ny+1;

16.2. PROBLEME REZOLVATE int[] z=new int[nz]; t=0; for(i=0;i<nz;i++) { z[i]=t; if(i<nx) z[i]+=x[i]; if(i<ny) z[i]+=y[i]; t=z[i]/10; z[i]=z[i]%10; } if(z[nz-1]!=0) return z; else { int[] zz=new int[nz-1]; for(i=0;i<nz-1;i++) zz[i]=z[i]; return zz; } }//suma }

383

16.2.14

Balanta ONI2002 clasa a X-a

Gigel are o balant mai ciudat pe care vrea s o echilibreze. De fapt, a a a aparatul este diferit de orice balant pe care ati vazut-o pn acum. a a a Balanta lui Gigel dispune de dou brate de greutate neglijabil i lungime a a s 15 ecare. Din loc loc, la aceste brate sunt ataate crlige, pe care Gigel poate n s a atrna greuti distincte din colectia sa de G greuti (numere naturale a at at ntre 1 i s 25). Gigel poate atrna oricte greuti de orice crlig, dar trebuie s foloseasc a a at a a a toate greutile de care dispune. at Folosindu-se de experienta participrii la Olimpiada National de Informatic, a a a Gigel a reuit s echilibreze balanta relativ repede, dar acum dorete s tie cte s a s a s n a moduri poate ea echilibrat. a Cerint a Cunoscnd amplasamentul crligelor i setul de greuti pe care Gigel are a a s at l la dispozitie, scrieti un program care calculeaz cte moduri se poate echilibra a n a balanta. Se presupune c este posibil s se echilibreze balanta (va posibil pe toate a a testele date la evaluare). Datele de intrare Fiierul de intrare balanta.in are urmtoarea structur: s a a

384

CAPITOLUL 16. PROGRAMARE DINAMICA

pe prima linie, numrul C de crlige i numrul G de greuti, valori sepa a s a at arate prin spatiu; pe urmtoarea linie, C numere a ntregi, distincte, separate prin spatiu, cu valori cuprinse ntre 15 i 15 inclusiv, reprezentnd amplasamentele crligelor s a a fat de centrul balantei; valoarea absolut a numerelor reprezint distanta fat de a a a a centrul balantei, iar semnul precizeaz bratul balantei la care este ataat crligul, a s - pentru bratul stng i + pentru bratul drept; a s pe urmtoarea linie, G numere naturale distincte, cuprinse a ntre 1 i 25 ins clusiv, reprezentnd valorile greutilor pe care Gigel le va folosi pentru a echilibra a at balanta. Datele de ieire s Fiierul de ieire balanta.out contine o singur linie, pe care se a un numr s s a a a natural M , numrul de variante de plasare a greutilor care duc la echilibrarea a at balantei. Restrictii i precizri s a 2 C 20, 2 G 20; greutile folosite au valori naturale at ntre 1 i 25; s numrul M cerut este a ntre 1 i 100.000.000; s celelalte restrictii (lungimea bratelor balantei etc.) au fost prezentate ante

rior.

balanta se echilibreaz dac suma produselor dintre greuti i coordonatele a a at s unde ele sunt plasate este 0 (suma momentelor greutilor fat de centrul balantei at a este 0). Exemplu balanta.in 24 -2 3 3458 balanta.out 2

Timp maxim de executare: 1 secund/test a Indicatii de rezolvare * Solutia comisiei Problema se rezolva prin metoda programrii dinamice. a Se calculeaz cte moduri se poate scrie ecare sum j, folosind primele i a n a a greuti. Initial i = 0 i suma 0 se poate obtine at s ntr-un singur mod, restul sumelor 0 moduri. n Urmeaza G pai. La ecare astfel de pas i se calculeaz cte moduri putem s a n a obtine ecare sum introducnd o nou greutate - a i-a - toate conguratiile a a a n precedente. Practic, dac suma S s-a obtinut cu primele i1 greuti M moduri, a at n punnd greutatea i pe crligul k se va obtine suma S +(greutate[i]coordonata[k]) a a

16.2. PROBLEME REZOLVATE

385

M moduri (la care, evident, se pot adauga alte moduri de obtinere plasnd n a greutatea i pe un alt crlig i folosind suma respectiv). a s a acest mod s-ar construi o matrice cu G linii i 2 (sumamaxima) + 1 In s coloane, cu elemente numere ntregi pe 32 de biti; de fapt se memoreaza doar ultimele dou linii (ecare linie se obtine din precedenta). Suma maxim este a a 15 25 20 = 7500, deci o linie se incadreaz mai putin de 64K. a n Rezultatul nal se obtine pe ultima linie, coloana asociat sumei 0. n a GInfo 12/6 octombrie 2002 Se observ c suma momentelor greutilor este cuprins a a at a ntre 6000 i 6000 s (dac avem 20 de greuti cu valoarea 20 i acestea sunt amplasate pe cel mai a at s ndeprtat crlig fat de centrul balantei, atunci modulul sumei momentelor fortelor a a a este 152020 = 6000). Ca urmare, putem pstra un ir a ale crui valori ai vor a s a contine numrul posibilitilor ca suma momentelor greutilor s e i. a at at a Indicii irului vor varia s ntre 6000 i 6000. Pentru a s mbunti viteza de a at executie a programului, indicii vor varia ntre 300g i 300g, unde g este numrul s a greutilor. Pot realizate at mbuntiri suplimentare dac se determin distantele a at a a maxime fat de mijlocul balantei ale celor mai a ndeprtate crlige de pe cele dou a a a talere i suma total a greutilor. Dac distantele sunt d1 i d2 , iar suma este s, s a at a s atunci indicii vor varia ntre d1 s i d2 s. s Initial, pe balant nu este agat nici o greutate, aadar suma momentelor a at a s greutilor este 0. Ca urmare, initial valorile ai vor 0 pentru orice indice nenul at i a0 = 1 (exist o posibilitate ca initial suma momentelor greutilor s e 0 i nu s a a a s exist nici o posibilitate ca ea s e diferit de 0). a a a continuare, vom In ncerca s amplasm greutile pe crlige. Fiecare greutate a a at a poate amplasat pe oricare dintre crlige. S presupunem c la un moment a a a a dat exist ai posibiliti de a obtine suma i. Dac vom amplasa o greutate de a at a valoare g pe un crlig aat la distanta d fat de centrul balantei, suma momentelor a a greutilor va crete sau va scdea cu gd ( functie de bratul pe care se a at s a n a crligul). Ca urmare, dup amplasarea noii greuti exist ai posibiliti de a a a at a at obtine suma i+gd. Considerm c irul b va contine valori care reprezint numrul a as a a posibilitilor de a obtine sume ale momentelor fortelor dup amplasarea greutii at a at curente. Inainte de a testa posibilitile de plasare a greutii, irul b va contine a at s doar zerouri. Pentru ecare pereche (i, d), valoarea bi+gd va crete cu ai . Dup s a considerarea tuturor perechilor, vom putea trece la o nou greutate. a Valorile din irul b vor salvate irul a, iar irul b va reinitializat cu s n s s 0. Rezultatul nal va dat de valoarea a0 obtinut dup considerarea tuturor a a greutilor disponibile. at Analiza complexitii at Pentru studiul complexitii vom nota numrul greutilor cu g, iar cel al at a at crligelor cu c. a Citirea datelor de intrare corespunztoare crligelor i greutatilor se reala a s izeaz timp liniar, deci ordinul de complexitate al acestor operatii este O(g), a n respectiv O(c).

386

CAPITOLUL 16. PROGRAMARE DINAMICA

Singura mrime care nu este considerat constant i de care depinde numrul a a as a de posibiliti de a obtine sumele este numrul greutilor. Aadar, vom considera at a at s c ordinul de complexitate al traversrii irului a este O(g). a a s Numrul de traversri este dat tot de numrul greutilor disponibile, deci a a a at vom avea O(g) traversri. a timpul traversrii vom considera toate crligele pentru ecare element al In a a irului. Ca urmare, ordinul de complexitate al operatiilor efectuate asupra unui s element ntr-o parcurgere este O(c). Rezult c ordinul de complexitate al unei a a parcurgeri este O(g)O(c) = O(gc), timp ce ordinul de complexitate al n ntregii operatii care duce la obtinerea rezultatului este O(g)O(gc) = O(g2c). Aarea numrului de posibiliti de a echilibra balanta se realizeaz timp s a at a n constant. concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei In probleme este O(c) + O(g) + O(g 2 c) + O(1) = O(g 2 c). Codul surs a

import java.io.*; class Balanta { static long[] a=new long[15001]; static long[] b=new long[15001]; static int[] carlig=new int[20]; static int[] greutate=new int[20]; static int nrCarlige, nrGreutati;

// // // //

a[i] i = -7500, 7500 sir auxiliar (a+7500)!!! coordonatele carligelor valorile greutatilor

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); citesteDatele(); determinaSolutia(); scrieSolutia(); t2=System.currentTimeMillis(); System.out.println("TIMP = "+(t2-t1)+" milisecunde"); }// main() static void citesteDatele() throws IOException { StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("balanta9.in"))); st.nextToken(); nrCarlige=(int)st.nval;

16.2. PROBLEME REZOLVATE st.nextToken(); nrGreutati=(int)st.nval; for(int i=0;i<nrCarlige;i++) { st.nextToken(); carlig[i]=(int)st.nval; } for(int i=0;i<nrGreutati;i++) { st.nextToken(); greutate[i]=(int)st.nval; } }// citesteDate()

387

static void scrieSolutia() throws IOException { PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("balanta9.out"))); out.print(a[7500+0]); out.close(); }// scrieSolutia() static void determinaSolutia() { int i,j,k; a[7500+0]=1; // initial balanta este echilibrata for(i=0;i<nrGreutati;i++) { for(j=-7500;j<=7500;j++) if(a[7500+j]!=0) for(k=0;k<nrCarlige;k++) b[7500+j+carlig[k]*greutate[i]]+=a[7500+j]; for (j=-7500;j<=7500;j++) { a[7500+j]=b[7500+j]; b[7500+j]=0; } } }// determinaSolutia() }// class

16.2.15

Aliniere ONI2002 clasa a X-a

armat, o companie este alctuit din n soldati. La inspectia de dimineat In a a a a soldatii stau aliniati linie dreapt fata cpitanului. Acesta nu e multumit de n a n a

388

CAPITOLUL 16. PROGRAMARE DINAMICA

ceea ce vede; e drept c soldatii sunt aezati ordinea numerelor de cod 1, 2, ..., n a s n din registru, dar nu ordinea altimii. Cpitanul cere ctorva soldati s ias din n n a a a a rnd, astfel ca cei rmai, fr a-i schimba locurile, doar apropiindu-se unul de a a s aa s altul (pentru a nu rmne spatii mari a a ntre ei) s formeze un ir care ecare a s n soldat vede privind de-a lungul irului, cel putin una din extremiti (stnga sau s at a dreapta). Un soldat vede o extremitate dac a ntre el i captul respectiv nu exist s a a un alt soldat cu altimea mai mare sau egal ca a lui. n a Cerint a Scrieti un program care determin, cunoscnd altimea ecrui soldat, numrul a a n a a minim de soldati care trebuie s prseasc formatia astfel ca irul rmas s a aa a s a a ndeplineasc conditia din enunt. a Datele de intrare Pe prima linie a ierului de intrare aliniere.in este scris numrul n al s a soldatilor din ir, iar pe linia urmtoare un ir de n numere reale, cu maximum 5 s a s zecimale ecare i separate prin spatii. Al k-lea numr de pe aceast linie reprezint s a a a altimea soldatului cu codul k (1 k n). n Datele de ieire s Fiierul aliniere.out va contine pe prima linie numrul soldatilor care tres a buie s prseasc formatia, iar pe linia urmtoare codurile acestora ordine a aa a a n cresctoare, separate dou cte dou printr-un spatiu. Dac exist mai multe a a a a a a solutii posibile, se va scrie una singur. a Restrictii i precizri s a 2 n 1000 altimile sunt numere reale intervalul [0.5; 2.5]. n n Exemplu aliniere.in aliniere.out 8 4 1.86 1.86 1.30621 2 1.4 1 1.97 2.2 1 3 7 8 Explicatie Rmn soldatii cu codurile 2, 4, 5, 6 avnd altimile 1.86, 2, 1.4 i 1. a a a n s Soldatul cu codul 2 vede extremitatea stng. a a Soldatul cu codul 4 vede ambele extremiti. at Soldatii cu codurile 5 i 6 vd extremitatea dreapt. s a a Timp maxim de executare: 1 secund/test a Indicatii de rezolvare Solutia comisiei Problema se rezolv prin metoda programrii dinamice. a a

16.2. PROBLEME REZOLVATE

389

Se calculeaz, pentru ecare element, lungimea celui mai lung subir strict a s cresctor care se termin cu el i lungimea celui mai lung subir strict descresctor a a s s a care ncepe cu el. Solutia const pstrarea a dou astfel de subiruri de soldati (unul cresctor a n a a s a i unul descresctor) pentru DOI soldati de aceeai altime (eventual identici) i s a s n s eliminarea celorlalti. Soldatii din primul subir privesc spre stanga, ceilalti spre s dreapta. Primul subir se termin inainte de a s a ncepe al doilea. Se au in vedere cazurile particulare. Deoarece s-a considerat c o parte din concurenti vor rezolva problema pentru a un singur soldat central (toti ceilalti soldati pstrati avnd a a naltimea mai mic) i a s nu vor observa cazul care se pot pstra doi soldati de aceeai ime, majorin a s nalt tatea testelor se ncadreaz acest caz. a n GInfo 12/6 octombrie 2002 Pentru ecare soldat vom determina cel mai lung subir strict cresctor (din s a punct de vedere al altimii) de soldati care se termin cu el, respectiv cel mai n a lung subir strict descresctor de soldati care urmeaz dup el. s a a a Dup aceast operatie, vom determina soldatul pentru care suma lungima a ilor celor dou iruri este maxim. Chiar dac s-ar prea c acest mod am gsit as a a a a n a solutia problemei, mai exist o posibilitate de a mri numrul soldatilor care rmn a a a a a ir. S considerm soldatul cel mai n s a a nalt irul rmas (cel cruia corespunde n s a a i suma maxim). Acesta poate privi e spre stnga, e spre dreapta irului. Din a a s aceste motive, la stnga sau la dreapta sa poate s se ae un soldat de aceeai a a s altime; unul dintre cei doi va privi spre dreapta, iar cellalt spre stnga. Totui, n a a s nu putem alege orice soldat cu aceeai altime, ci doar unul pentru care lungimea s n irului strict cresctor (dac se a spre stnga) sau a celui strict descresctor s a a a a a (dac se a spre dreapta) este aceeai cu lungimea corespunztoare irului strict a a s a s cresctor, respectiv strict descresctor, corespunztoare celui mai a a a nalt soldat dintre cei rmai ir. a s n s Dup identicarea celor doi soldati de altimi egale (sau demonstrarea faptua n lui c nu exist o pereche de acest gen care s respecte conditiile date) se marcheaz a a a a toti soldatii din cele dou subiruri. Ceilalti soldati vor trebui s prseasc formatia. a s a aa a Analiza complexitii at Citirea datelor de intrare se realizeaz timp liniar, deci ordinul de coma n plexitate al acestei operatii este O(n). Chiar dac exist algoritmi ecienti (care ruleaz n timp liniar-logaritmic) de a a determinare a celui mai lung subir ordonat, timpul de executie admis ne permite s folosirea unui algoritm simplu, cu ordinul de complexitate O(n2 ). Acesta va aplicat de dou ori, dup care se va cuta valoarea maxim a sumei lungimilor a a a a irurilor corespunztoare unui soldat; aadar identicarea soldatului care poate s a s privi ambele directii este o operatie cu ordinul de complexitate O(n2 ) + O(n2 ) + n O(n) = O(n2 ). Urmeaz eventuala identicare a unui alt soldat de aceeai altime care a s n respect conditiile referitioare la lungimile subirurilor. Pentru aceasta se parcurge a s

390

CAPITOLUL 16. PROGRAMARE DINAMICA

irul soldatilor de la soldatul identicat anterior spre extremiti; operatia necesit s at a un timp liniar. Deteminarea celor dou subiruri se realizeaz timp liniar dac, momena s a n a n tul construirii celor dou subiruri, se pstreaz predecesorul, respectiv succesorul a s a a ecrui soldat. timpul parcurgerii subirurilor sunt marcati soldatii care rmn a In s a a formatie. n Pentru scrierea datelor de ieire se parcurge irul marcajelor i sunt identicati s s s soldatii care prsesc formatia. Ordinul de complexitate al acestei operatii este aa O(n). concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei In probleme este O(n) + O(n2 ) + O(n) + O(n) + O(n) = O(n2 ). Codul surs a

import java.io.*; class Aliniere { static final String fisi="aliniere.in"; static float[] inalt; // inaltimile soldatilor static int ns, nsr; // nr soldati, nr soldati ramasi static int[] predCresc; // predecesor in subsirul crescator static int[] lgCresc; // lungimea sirului crescator care se termina cu i static int[] succDesc; // succesor in subsirul descrescator static int[] lgDesc; // lungimea sirului descrescator care urmeaza dupa i static boolean[] ramas; // ramas in sir public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); citescDate(); subsirCresc(); subsirDesc(); alegeSoldati(); scrieRezultate(); t2=System.currentTimeMillis(); System.out.println("TIME = "+(t2-t1)+" millisec "); }// main() static void citescDate() throws IOException { StreamTokenizer st=new StreamTokenizer(

16.2. PROBLEME REZOLVATE

391

new BufferedReader(new FileReader(fisi))); st.nextToken(); ns=(int)st.nval; predCresc=new int[ns]; lgCresc=new int[ns]; succDesc=new int[ns]; lgDesc=new int[ns]; ramas=new boolean[ns]; inalt=new float[ns]; for(int i=0;i<ns;i++) {st.nextToken(); inalt[i]=(float)st.nval;} }//citescDate() static void subsirCresc() { int i,j; lgCresc[0]=1; predCresc[0]=-1; for(i=1;i<ns;i++) { lgCresc[i]=1; // subsirul formar doar din i predCresc[i]=-1; // nu are predecesor for (int j=0;j<i;j++) if(inalt[j]<inalt[i]) if(lgCresc[i]<lgCresc[j]+1) // sir mai lung { lgCresc[i]=lgCresc[j]+1; predCresc[i] = j; } } }//subsirCresc() static void subsirDesc() { int i,j; lgDesc[ns-1]=0; // nu exista nici un soldat mai mic dupa ns-1 succDesc[ns-1]=-1; // ns-1 nu are succesor for(i=ns-2;i>=0;i--) { lgDesc[i]=0; // nu exista nici un soldat mai mic dupa i succDesc[i]=-1; // i nu are succesor for(j=ns-1;j>i;j--) if(inalt[j]<inalt[i]) // soldat mai mic if(lgDesc[i]<lgDesc[j]+1) // sir mai lung

392 {

CAPITOLUL 16. PROGRAMARE DINAMICA

lgDesc[i]=lgDesc[j]+1; succDesc[i]=j; } } }// subsirDesc()

// actualizarea lg subsir // actualizare succesor

static void alegeSoldati() { int i; // este posibil ca in mijloc sa fie doi soldati cu inaltimi egale int im=-1; // indicele soldatului din mijloc int ic, id; // indicii care delimiteaza in interior cele doua subsiruri for(i=0;i<ns;i++) if(lgCresc[i]+lgDesc[i]>nsr) { nsr=lgCresc[i]+lgDesc[i]; im=i; } // in "mijlocul" sirului se pot afla doi soldati cu aceeasi inaltime ic=im; id=im; // caut in stanga un subsir cu aceeasi lungime --> soldat cu aceeasi inaltime for(i=im-1;i>=0;i--) if(lgCresc[ic]==lgCresc[i]) ic=i; // caut in dreapta un subsir cu aceeasi lungime --> soldat cu aceeasi inaltime for(i=im+1;i<ns;i++) if(lgDesc[id]==lgDesc[i]) id=i; if(ic!=id) // in "mijloc" sunt doi soldati cu aceeasi inaltime nsr++; while(id!=-1) // indice descrescator { ramas[id]=true; id=succDesc[id]; } while(ic!=-1) // indice crescator { ramas[ic] = true; ic=predCresc[ic]; } }// alegeSoldati()

16.2. PROBLEME REZOLVATE

393

static void scrieRezultate() throws IOException { PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("aliniere.out"))); out.println(ns- nsr); for(int i=0;i<ns;i++) if(!ramas[i]) out.print((i+1)+" "); out.close(); }// scriuRezultate() }// class

16.2.16

Munte - ONI2003 cls 10

Intr-o zon montan se dorete deschiderea unui lant de telecabine. Statiile a a s de telecabine pot nintate pe oricare din cele N vrfuri ale zonei montane. a Vrfurile sunt date ordine de la stnga la dreapta i numerotate de la 1 la N , a n a s ecare vrf i ind precizat prin coordonata X[i] pe axa OX i prin altimea H[i]. a s n Se vor ninta exact K statii de telecabine. Statia de telecabine i (2 i K) va conectat cu statiile i 1 i i + 1; statia 1 va conectat doar cu statia 2, a s a iar statia K, doar cu statia K 1. Statia 1 va obligatoriu amplasat vrful a n a 1, iar statia K vrful N . n a Se dorete ca lantul de telecabine s asigure legtura s a a ntre vrful 1 i vrful a s a N . Mai mult, se dorete ca lungimea total a cablurilor folosite pentru conectare s a s e minim. Lungimea cablului folosit pentru a conecta dou statii este egal cu a a a a distanta dintre ele. plus, un cablu care unete dou statii consecutive nu poate In s a avea lungimea mai mare dect o lungime xat L. a a O restrictie suplimentareste introdus de formele de relief. Astfel, vrfurile i a a a i j (i < j) nu pot conectate direct dac exist un vrf v (i < v < j) astfel at s a a a nc segmentul de dreapta care ar uni vfurile i i j nu ar trece pe deasupra vrfului a s a v. cazul care cele trei vrfuri sunt coliniare, se consider toate trei ca ind In n a a statii, chiar dac distan c dintre vrfurile i i j este mai mic dect L. a s a a Cerint a Dndu-se amplasarea celor N vrfuri ale lantului muntos, stabiliti o modalitate a de dispunere a celor K statii de telecabine astfel at lungimea total a cablurilor nc a folosite pentru conectare s e minim, cu restrictiile de mai sus. a a Se garanteaz c, pe toate testele date la evaluare, conectarea va posibil. a a a Date de intrare Prima linie a ierului de intrare munte.in contine trei numere s ntregi N , K i L, separate prin spatii, cu semnicatiile de mai sus. Urmtoarele N linii contin s a coordonatele vrfurilor; linia i + 1 contine coordonatele vrfului i, X[i] i H[i], a a s separate printr-un spatiu. Date de ieire s ierul munte.out veti aa: In s s

394

CAPITOLUL 16. PROGRAMARE DINAMICA

- pe prima linie lungimea total minim a cablurilor, rotunjit la cel mai a a a apropiat numar ntreg (pentru orice ntreg Q, Q.5 se rotunjeste la Q + 1); - pe a doua linie K numere distincte ntre 1 i N , ordonate cresctor, numerele s a vrfurilor care se vor a n nin statii de telecabine. Dac exist mai multe variante, a a a aati una oarecare. s Restrictii i precizri s a 2 N 100 2 K 30 i K N s 0 L, X[i], H[i] 100.000 i X[i] < X[i + 1] s Exemplu munte.in munte.out 7 5 11 22 0 16 13567 43 68 74 12 16 13 16 14 16 Explicatii - trasarea unui cablu direct ntre vrfurile 1 i 5 ar contravenit restrictiei a s referitoare la lungimea maxim a unui cablu; plus, s-ar obtinut o solutie cu 2 a n statii de telecabine loc de 3 (deci solutia ar invalid i pentru valori mari ale n as lui L); - pentru a ilustra restrictia introdus de formele de relief, precizm c vrfurile a a a a 1 i 4 nu au putut conectate direct datorit altimii vrfului 3. De asemenea, s a n a vrfurile 5 i 7 nu au putut conectate direct datorit altimii vrfului 6. a s a n a Timp maxim de executare: 1 secund/test. a Indicatii de rezolvare - descriere solutie * Mihai Stroe, GInfo nr. 13/6 - octombrie 2003 Problema se rezolv prin metoda programrii dinamice. a a Practic, problema se poate arti dou subprobleme. Primul pas const mp n a a determinarea perechilor de vrfuri care pot unite printr-un cablu. Acest pas n a se rezolv folosind cunotinte elementare de geometrie plan (ecuatia dreptei, y = a s a a x + b). Se va obtine o matrice OK, unde OKi,j are valoarea 1 dac vrfurile i i a a s j pot unite i 0 caz contrar. Folosind aceast matrice, se vor conecta vrfurile s n a a 1 i N cu un lant de K statii, astfel at oricare dou statii consecutive s aib s nc a a a OK-ul corespunztor egal cu 1. a Aceast subproblema se rezolv prin metoda programrii dinamice dup cum a a a a urmeaz: se construiete o matrice A cu K linii i N coloane, unde Ai,j reprezint a s s a lungimea total minim a unui lant cu i statii care conecteaz vrfurile 1 i j. a a a a s

16.2. PROBLEME REZOLVATE

395

Initial A1,1 = 0, A1,i = + i Ai,1 = + pentru i > 1. s Pentru i cuprins ntre 2 i N , componentele matricei se calculeaz astfel: s a Ai,j = min{Ai1,v + dist(v, j)}, unde v < j i OKv,j = 1 s Concomitent cu calculul elementelor Ai,j se construiete o matrice T , unde s Ti,j reprezint v-ul care minimizeaz expresia de mai sus. Matricea T este folosit a a a pentru reconstituirea solutiei. Lungimea total minim este regasit AK,N . a a a n Subproblemele puteau tratate i simultan, ceea ce complica implementarea. s Analiza complexitii at Operatia de citire a datelor are ordinul de complexitate O(N ). Calculul matricei OK are ordinul de complexitate O(N 3 ). Algoritmul bazat pe metoda programrii dinamice are ordinul de complexitate a O(N 2 K). Reconstituirea solutiei i aarea au ordinul de complexitate O(N ). s s Deoarece K N , ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O(N 3 ). Codul surs a Prima variant: a import java.io.*; // cu mesaje pt depanare dar ... fara traseu class Munte1 { static final int oo=Integer.MAX_VALUE; static int n,m,L; // m=nr statii (in loc de K din enunt) static static static static int[] x,h; double[][] a; int[][] t; boolean[][] ok;

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i; BufferedReader br=new BufferedReader(new FileReader("munte.in")); StreamTokenizer st=new StreamTokenizer(br); st.nextToken(); n=(int)st.nval;

396

CAPITOLUL 16. PROGRAMARE DINAMICA st.nextToken(); m=(int)st.nval; st.nextToken(); L=(int)st.nval; x=new int[n+1]; h=new int[n+1]; ok=new boolean[n+1][n+1]; a=new double[m+1][n+1]; t=new int[m+1][n+1];

// implicit este false

for(i=1;i<=n;i++) { st.nextToken(); x[i]=(int)st.nval; st.nextToken(); h[i]=(int)st.nval; } matriceaOK(); afism(ok); matriceaA(); afisSolutia(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() static void matriceaA() { int i,j,k,kmin; double d,dkj,min; a[1][1]=0; for(i=2;i<=m;i++) a[i][1]=oo; for(j=2;j<=n;j++) a[1][j]=oo; afism(a); for(i=2;i<=m;i++) { for(j=2;j<=n;j++) { min=oo; kmin=-1; for(k=1;k<j;k++) { System.out.println(i+" "+j+" "+k+" "+ok[k][j]);

16.2. PROBLEME REZOLVATE

397

if(ok[k][j]) { dkj=dist(k,j); d=a[i-1][k]+dkj; System.out.println(i+" "+j+" "+k+" dkj="+dkj+" d="+d+" min="+min); if(d<min) { min=d;kmin=k; } } }// for k a[i][j]=min; }// for j System.out.println("Linia: "+i); afism(a); }// for i }// matriceaA() static double dist(int i, int j) { double d; d=(double)(x[i]-x[j])*(x[i]-x[j])+(double)(h[i]-h[j])*(h[i]-h[j]); return Math.sqrt(d); }// dist(...) static void matriceaOK() { int i,j,ij,x1,y1,x2,y2,sp1,sp2; for(i=1;i<=n-1;i++) { x1=x[i]; y1=h[i]; for(j=i+1;j<=n;j++) { x2=x[j]; y2=h[j]; ok[i][j]=ok[j][i]=true; for(ij=i+1;ij<=j-1;ij++) // i .. ij .. j { sp1=(0 -y1)*(x2-x1)-(y2-y1)*(x[ij]-x1); sp2=(h[ij]-y1)*(x2-x1)-(y2-y1)*(x[ij]-x1); if(sp1*sp2<=0) { ok[i][j]=ok[j][i]=false; System.out.println(i+" "+j+" ("+ij+")\t"+sp1+"\t"+sp2); break; } if(!ok[i][j]) break; }//for ij

398

CAPITOLUL 16. PROGRAMARE DINAMICA

if(ok[i][j]) if(dist(i,j)>L+0.0000001) ok[i][j]=ok[j][i]=false; }//for j }// for i }// matriceaOK() static void afisSolutia() throws IOException { int i,j; PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter("munte.out"))); out.println(a[m][n]); out.close(); }//afisSolutia() static void afism(int[][] a) { int i,j; for(i=0;i<a.length;i++) { for(j=0;j<a[i].length;j++) System.out.print(a[i][j]+" "); System.out.println(); } System.out.println(); }// afism(...) static void afism(boolean[][] a) { int i,j; for(i=1;i<a.length;i++) { for(j=1;j<a[i].length;j++) System.out.print(a[i][j]+" "); System.out.println(); } System.out.println(); }// afism(...) static void afism(double[][] a) { int i,j; for(i=1;i<a.length;i++) { for(j=1;j<a[i].length;j++) System.out.print(a[i][j]+" "); System.out.println(); }

16.2. PROBLEME REZOLVATE System.out.println(); }// afism(...) }// class A doua variant: a

399

import java.io.*; // FINAL: fara mesaje si cu traseu ... dar class Munte2 // test 8 : P1(99,59) P2(171,96) P3(239,81) P4(300,78) { // solutia: 1 4 5 ... este gresita (1 4 nu trece de P2 si P3!) static final int oo=Integer.MAX_VALUE; static int n,m,L; // m=nr statii (in loc de K din enunt) static static static static static int[] x,h; double[][] a; int[][] t; boolean[][] ok; int[] statia;

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j; BufferedReader br=new BufferedReader(new FileReader("munte.in")); StreamTokenizer st=new StreamTokenizer(br); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; st.nextToken(); L=(int)st.nval; x=new int[n+1]; h=new int[n+1]; ok=new boolean[n+1][n+1]; a=new double[m+1][n+1]; t=new int[m+1][n+1]; statia=new int[m+1]; for(i=1;i<=n;i++) { st.nextToken(); x[i]=(int)st.nval; st.nextToken(); h[i]=(int)st.nval; } matriceaOK();

400

CAPITOLUL 16. PROGRAMARE DINAMICA matriceaA(); j=n; for(i=m;i>=1;i--) { statia[i]=j; j=t[i][j]; } afisSolutia();

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() static void matriceaA() { int i,j,k,kmin; double d,dkj,min; a[1][1]=0; for(i=2;i<=m;i++) a[i][1]=oo; for(j=2;j<=n;j++) a[1][j]=oo; for(i=2;i<=m;i++) { for(j=2;j<=n;j++) { min=oo; kmin=0; for(k=1;k<j;k++) { if(ok[k][j]) { dkj=dist(k,j); d=a[i-1][k]+dkj; if(d<min) { min=d;kmin=k; } } }// for k a[i][j]=min; t[i][j]=kmin; }// for j }// for i }// matriceaA() static double dist(int i, int j) { double d; d=(double)(x[i]-x[j])*(x[i]-x[j])+(double)(h[i]-h[j])*(h[i]-h[j]); return Math.sqrt(d); }// dist(...)

16.2. PROBLEME REZOLVATE

401

static void matriceaOK() { int i,j,ij,x1,y1,x2,y2; long sp1,sp2; // 100.000*100.000=10.000.000.000 depaseste int !!! for(i=1;i<=n-1;i++) { x1=x[i]; y1=h[i]; for(j=i+1;j<=n;j++) { x2=x[j]; y2=h[j]; ok[i][j]=ok[j][i]=true; for(ij=i+1;ij<=j-1;ij++) // i .. ij .. j { sp1=(0 -y1)*(x2-x1)-(y2-y1)*(x[ij]-x1); sp2=(h[ij]-y1)*(x2-x1)-(y2-y1)*(x[ij]-x1); if(sp1*sp2<=0) { ok[i][j]=ok[j][i]=false; break; } if(!ok[i][j]) break; }//for ij if(ok[i][j]) if(dist(i,j)>L) ok[i][j]=ok[j][i]=false; }//for j }// for i }// matriceaOK() static void afisSolutia() throws IOException { int i; PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter("munte.out"))); out.println((int)(a[m][n]+0.5)); for(i=1;i<=m;i++) out.print(statia[i]+" "); out.println(); out.close(); }//afisSolutia() }// class

16.2.17

Lcusta - OJI2005 clasa a X-a a

Se consider o matrice dreptunghiular cu m linii i n coloane, cu valori a a s naturale. Traversm matricea pornind de la coltul stnga-sus la coltul dreapta-jos. a a

402

CAPITOLUL 16. PROGRAMARE DINAMICA

O traversare const din mai multe deplasri. La ecare deplasare se execut un a a a salt pe orizontal i un pas pe vertical. Un salt as a nseamn c putem trece de la o a a celul la oricare alta aat pe aceeai linie, iar un pas a a s nseamn c putem trece a a de la o celul la celula aat imediat sub ea. Exceptie face ultima deplasare (cea a a care ne am pe ultima linie), cnd vom face doar un salt pentru a ajunge n a a n coltul dreapta-jos, dar nu vom mai face i pasul corespunztor. Astfel traversarea s a va consta din vizitarea a 2m celule. Cerint a Scrieti un program care s determine suma minim care se poate obtine a a pentru o astfel de traversare. Datele de intrare Fiierul de intrare lacusta.in contine pe prima linie dou numere naturale s a separate printr-un spatiu m n, reprezentnd numrul de linii i respectiv numrul a a s a de coloane ale matricei. Pe urmtoarele m linii este descris matricea, cte n a a a numere pe ecare linie, separate prin cte un spatiu. a Datele de ieire s Fiierul de ieire lacusta.out va contine o singur linie pe care va scris s s a a suma minim gsit. a a a Restrictii i precizri s a 1 m, n 100 Valorile elementelor matricei sunt numere ntregi din intervalul [1, 255]. Exemple lacusta.in 45 34579 66344 63396 65382 lacusta.out 28 Explicatie Drumul este: (1, 1) (1, 3) (2, 3) (2, 2) (3, 2) (3, 3) (4, 3) (4, 5)

Timp maxim de executare: 1 secund/test a Indicatii de rezolvare * Ginfo nr. 15/3 martie 2005 Pentru rezolvarea acestei probleme vom utiliza metoda programrii dinamice. a Vom nota prin A matricea dat i vom construi o matrice B ale crei elemente as a bij vor contine sumele minime necesare pentru a ajunge celula (i, j) pornind n din celula (i 1, j). Vom completa initial elementele de pe a doua linie a matricei B. Valoarea b2,1 va deoarece aceast celul nu se poate ajunge. Valorile celorlalte elemente n a a b2i vor calculate pe baza formulei: b2,i = a1,1 + a1,i + a2,i .

16.2. PROBLEME REZOLVATE Pentru celelalte linii, valorile bij vor calculate pe baza formulei: bi,j = ai,j + ai1,j + min(bi1,k ),

403

unde k variaz a ntre 1 i n. Evident, relatia nu este valabil pentru elementul de s a pe coloana k care corespunde minimului, deoarece nu se poate cobor direct, ci trebuie efectuat un salt orizontal. aceast situatie vom alege al doilea minim de In a pe linia anterioar. a nal alegem minimul valorilor de pe ultima linie a matricei B (fr a lua In aa considerare elementul de pe ultima coloan a acestei linii) la care adaugm n a a valoarea amn . Coduri surs * a Prima variant: a import java.io.*; class Lacusta1 { static final int oo=100000; static int m,n; static int[][] a,b; // 0 <= i <= m-1;

0 <= j <= n-1

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[m][n]; b=new int[m][n]; for(i=0;i<m;i++) for(j=0;j<n;j++) {

404

CAPITOLUL 16. PROGRAMARE DINAMICA st.nextToken(); a[i][j]=(int)st.nval; } for(i=0;i<m;i++) for(j=0;j<n;j++) b[i][j]=oo; // prima linie (i=0) din b este oo // a doua linie (i=1) din b for(j=1;j<n;j++) b[1][j]=a[0][0]+a[0][j]+a[1][j]; // urmatoarele linii din b for(i=2;i<m;i++) for(j=0;j<n;j++) b[i][j]=a[i][j]+a[i-1][j]+minLinia(i-1,j); // "obligatoriu" (!) si ultima linie (i=n-1) dar ... fara coborare b[m-1][n-1]=minLinia(m-1,n-1)+a[m-1][n-1];

out.println(b[m-1][n-1]); out.close(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() static int minLinia(int ii, int jj) // min pe linia=ii fara pozitia jj==col { int j,min=oo; for(j=0;j<n;j++) if(j!=jj) if(b[ii][j]<min) min=b[ii][j]; return min; }// minLinia(...) }// class A doua variant: a import java.io.*; // suplimentar ... si traseul ! class Lacusta2 { static final int oo=100000; static int m,n; static int[][] a,b; // 1 <= i <= m; 1 <= j <= n public static void main(String[] args) throws IOException { long t1,t2;

16.2. PROBLEME REZOLVATE t1=System.currentTimeMillis(); int i,j,min,jmin,j0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[m+1][n+1]; b=new int[m+1][n+1]; for(i=1;i<=m;i++) for(j=1;j<=n;j++) { st.nextToken(); a[i][j]=(int)st.nval; } for(i=1;i<=m;i++) for(j=1;j<=n;j++) b[i][j]=oo; // prima linie (i=1) din b este oo // a doua linie (i=2) din b for(j=2;j<=n;j++) b[2][j]=a[1][1]+a[1][j]+a[2][j]; // urmatoarele linii din b for(i=3;i<=m;i++) for(j=1;j<=n;j++) b[i][j]=a[i][j]+a[i-1][j]+minLinia(i-1,j);

405

// "obligatoriu" (!) si ultima linie (i=n) dar ... fara coborare b[m][n]=minLinia(m,n)+a[m][n]; out.println(b[m][n]); out.close(); jmin=-1; // initializare aiurea ! j0=1; // pentru linia 2 System.out.print(1+" "+1+" --> "); for(i=2;i<=m-1;i++) // liniile 2 .. m-1 { min=oo; for(j=1;j<=n;j++) if(j!=j0) if(b[i][j]<min) { min=b[i][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> ");

406

CAPITOLUL 16. PROGRAMARE DINAMICA System.out.print(i+" "+jmin+" --> "); j0=jmin; } j0=n; min=oo; for(j=1;j<n;j++) if(j!=j0) if(b[i][j]<min) { min=b[i][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> "); System.out.print(i+" "+jmin+" --> "); System.out.println(m+" "+n);

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() static int minLinia(int ii, int jj) // min pe linia=ii fara pozitia jj==col { int j,min=oo; for(j=1;j<=n;j++) if(j!=jj) if(b[ii][j]<min) min=b[ii][j]; return min; }// minLinia(...) }// class Varianta 3: import java.io.*; // fara matricea de costuri (economie de "spatiu") class Lacusta3 // traseul este ... pentru depanare { // daca se cere ... se poate inregistra si apoi ... static final int oo=100000; static int m,n; static int[][] a; // 1 <= i <= m; 1 <= j <= n public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j; int minc,minsol,jmin,j0;

16.2. PROBLEME REZOLVATE StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[m+1][n+1]; for(i=1;i<=m;i++) for(j=1;j<=n;j++) { st.nextToken(); a[i][j]=(int)st.nval; } minsol=oo; System.out.print(1+" "+1+" --> ");

407

// a doua linie (i=2) minc=oo; jmin=-1; for(j=2;j<=n;j++) if(a[1][1]+a[1][j]+a[2][j]<minc) {minc=a[1][1]+a[1][j]+a[2][j]; jmin=j;} System.out.print(1+" "+jmin+" --> "); System.out.print(2+" "+jmin+" --> "); minsol=minc; j0=jmin; jmin=-1; // initializare aiurea ! for(i=3;i<=m-1;i++) { minc=oo; for(j=1;j<=n;j++) if(j!=j0) if(a[i-1][j]+a[i][j]<minc) {minc=a[i-1][j]+a[i][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> "); System.out.print(i+" "+jmin+" --> "); minsol+=minc; j0=jmin; } j0=n; minc=oo; for(j=1;j<=n;j++)

408

CAPITOLUL 16. PROGRAMARE DINAMICA if(j!=j0) if(a[m-1][j]+a[m][j]<minc) {minc=a[m-1][j]+a[m][j]; jmin=j;} System.out.print((m-1)+" "+jmin+" --> "); System.out.print(m+" "+jmin+" --> "); minsol+=minc+a[m][n]; System.out.println(m+" "+n); out.println(minsol); out.close();

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() }// class Varianta 4: import java.io.*; // fara matricea de costuri (economie de "spatiu") class Lacusta4 // si ... fara matricea initiala (numai doua linii din ea !) { // calculez pe masura ce citesc cate o linie ! static final int oo=100000; static int m,n; static int[][] a; // 1 <= i <= m; 1 <= j <= n public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j,ii,jj; int minc,minsol,jmin,j0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[2][n+1]; for(i=1;i<=2;i++) // citesc numai primele doua linii for(j=1;j<=n;j++) {

16.2. PROBLEME REZOLVATE st.nextToken(); a[i%2][j]=(int)st.nval; } minsol=oo; System.out.print(1+" "+1+" --> "); // a doua linie (i=2) minc=oo; jmin=-1; for(j=2;j<=n;j++) if(a[1%2][1]+a[1][j]+a[2%2][j]<minc) {minc=a[1%2][1]+a[1%2][j]+a[2%2][j]; jmin=j;} System.out.print(1+" "+jmin+" --> "); System.out.print(2+" "+jmin+" --> "); minsol=minc; j0=jmin;

409

jmin=-1; // initializare aiurea ! for(i=3;i<=m-1;i++) // citesc mai departe cate o linie { for(j=1;j<=n;j++) { st.nextToken(); a[i%2][j]=(int)st.nval; } minc=oo; for(j=1;j<=n;j++) if(j!=j0) if(a[(i-1+2)%2][j]+a[i%2][j]<minc) {minc=a[(i-1+2)%2][j]+a[i%2][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> "); System.out.print(i+" "+jmin+" --> "); minsol+=minc; j0=jmin; } //citesc linia m for(j=1;j<=n;j++) { st.nextToken(); a[m%2][j]=(int)st.nval; } j0=n; minc=oo; for(j=1;j<=n;j++) if(j!=j0) if(a[(m-1+2)%2][j]+a[m%2][j]<minc) {minc=a[(i-1+2)%2][j]+a[i%2][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> "); System.out.print(i+" "+jmin+" --> "); minsol+=minc+a[m%2][n];

410

CAPITOLUL 16. PROGRAMARE DINAMICA System.out.println(m+" "+n); out.println(minsol); out.close();

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() }// class Varianta 5: import java.io.*; // numai o linie din matricea initiala ! class Lacusta5 { static final int oo=100000; static int m,n; static int[] a; // 1 <= j <= n public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j,ii,jj,a11,aa; int minc,minsol,jmin,j0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[n+1]; // citesc numai prima linie for(j=1;j<=n;j++) { st.nextToken(); a[j]=(int)st.nval; } System.out.print(1+" "+1+" --> "); a11=a[1]; // citesc a doua linie st.nextToken(); a[1]=(int)st.nval; minc=oo; jmin=-1;

16.2. PROBLEME REZOLVATE for(j=2;j<=n;j++) { aa=a[j]; st.nextToken(); a[j]=(int)st.nval; if(aa+a[j]<minc) {minc=aa+a[j]; jmin=j;} } System.out.print(1+" "+jmin+" --> "+2+" "+jmin+" --> "); minsol=a11+minc; j0=jmin;

411

// citesc mai departe cate o linie si ... for(i=3;i<=m-1;i++) { minc=oo; jmin=-1; for(j=1;j<=n;j++) { aa=a[j]; st.nextToken(); a[j]=(int)st.nval; if(j!=j0) if(aa+a[j]<minc) {minc=aa+a[j]; jmin=j;} } System.out.print((i-1)+" "+jmin+" --> "+i+" "+jmin+" --> "); minsol+=minc; j0=jmin; } //citesc linia m (primele n-1 componente) minc=oo; jmin=-1; for(j=1;j<=n-1;j++) { aa=a[j]; st.nextToken(); a[j]=(int)st.nval; if(aa+a[j]<minc) {minc=aa+a[j]; jmin=j;} } System.out.print((m-1)+" "+jmin+" --> "+m+" "+jmin+" --> "); minsol+=minc; j0=jmin; // citesc ultimul element st.nextToken(); a[n]=(int)st.nval; minsol+=a[n]; System.out.println(m+" "+n);

412 out.println(minsol); out.close();

CAPITOLUL 16. PROGRAMARE DINAMICA

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() }// class

16.2.18

Avere ONI2005 cls 10

Italag a fost toat viata pasionat de speculatii bursiere reuind s adune o a s a avere considerabil. Fiind un tip original i pasionat de matematic a scris un testaa s a ment inedit. Testamentul contine dou numere naturale: S reprezentnd averea ce a a trebuie artit motenitorilor i N reprezentnd alegerea sa pentru artirea mp a s s a mp averii. Italag decide s-i a s mpart toat averea, iar sumele pe care le acord motenitorilor a a a s s e ordine strict descresctoare. a n a De exemplu dac averea ar 7 uniti monetare, ar putea artit astfel: a at mp a 4 (uniti primului motenitor) 3 (uniti celui de-al doilea), sau at s at 6 (uniti primului motenitor) 1 (unitate celui de-al doilea), sau at s 7 (uniti doar primului motenitor), sau at s 5 (uniti primului motenitor) 2 (uniti celui de-al doilea), sau at s at 4 (uniti primului motenitor) 2 (uniti celui de-al doilea) 1 (unitate celui at s at de-al treilea). Vznd c este foarte greu s verice dac nu cumva a omis vreo variant a a a i a a a de artire, Italag le-a scris ordine lexicograc. Pentru exemplul de mai sus: mp n a 4 2 1; 4 3; 5 2; 6 1; 7. A hotrt ca banii s e distribuiti conform celei de-a N -a posibiliti din aa a at ordinea lexicograc. a Cerint a Scrieti un program care pentru numerele S, N date s calculeze i s aeze a s a s numrul total de posibiliti de artire a averii, precum i modul care se face a at mp s n aceast artire conform cu a N -a posibilitate din ordinea lexicograc. a mp a Datele de intrare Fiierul de intrare avere.in contine o singur linie pe care se a dou numere s a a a naturale separate printr-un singur spatiu: primul numr (S) reprezint suma total a a a cel de-al doilea (N ) reprezint numrul de ordine al pozitiei cutate. a a a Datele de ieire s Fiierul de ieire avere.out va contine dou linii: s s a pe prima linie va aat numrul total de modaliti de artire a averii; s a at mp

16.2. PROBLEME REZOLVATE

413

pe cea de-a doua linie va aat a N -a posibilitate de artire a lui S s a mp conform cerintei ordine lexicograc. Elementele sale vor separate prin cte n a a un spatiu. Restrictii i precizri s a 1 < S < 701 0 < N < numrul total de posibiliti cu suma S a at Se acord punctaj partial pentru ecare test: 5 puncte pentru determinarea a corect a numrului de posibiliti de artire a lui S i 5 puncte pentru detera a at mp s minarea corect a posibilitii N , din ordinea lexicograc. a at a Posibilitile de artire a averii sunt numerotate at mp ncepnd cu 1. a Fie x = (x1 , x2 ..., xm ) i y = (y1 , y2 , ..., yp ) dou iruri. Spunem c x preced s as a a pe y din punct de vedere lexicograc, dac exist k 1, astfel at xi = yi , pentru a a nc orice i = 1, ..., k 1 i xk < yk . s Exemple: 4 2 1 preced secventa 4 3 deoarece (4 = 4, 2 < 3), iar 6 1 preced a a 7 deoarece 6 < 7. Exemple: avere.in 72 avere.in 12 5 avere.out 5 43 avere.out 15 651 avere.out
962056220379782044 175 68 63 58 54 45 40 36 34 32 20 18 17 14 11 9 3 2 1

avere.in
700 912345678912345678

Timp maxim de executie/test: 1 secund pentru Windows i 0.1 secunde a s pentru Linux. Limita total de memorie sub Linux este 3Mb din care 1Mb pentru a stiv. a Indicatii de rezolvare - descriere solutie * stud. Emilian Miron, Universitatea Bucureti s Problema se rezolv prin programare dinamic dup valoarea maxim pe care a a a a poate s o ia primul numr cadrul descompunerii i dup suma total. a a n s a a Obtinem recurenta:

414 c[v][s] =

CAPITOLUL 16. PROGRAMARE DINAMICA

c[v 1][s] //punnd pe prima pozitie 1, 2, ...,v-1 a +c[v 1][s v] //punnd pe prima pozitie v c[0][0] = 1, c[0][s] = 0 pentru s > 0 Numrul total de posibiliti va egal cu c[S][S]. a at Reconstituirea solutiei se face stabilind primul numr ca ind cel mai mic i a astfel at c[i][S] N i c[i 1][S] < N . Procesul continu pentru S = S i i nc s a s N = N c[i 1][S] pn cnd N = 0. a a a Observm c recurenta depinde doar de linia anterioar, aa c ea se poate a a a s a calcula folosind un singur vector. Aceasta ne ajut pentru a respecta limita de a memorie. Astfel calculm toate valorile folosind un singur vector i pstrm la a s a a ecare pas c[i][S]. Reconstructia se face recalculnd valorile la ecare pas pentru a S-ul curent. Solutia descris efectueaz O(S 2 L) operatii, unde L este lungimea solutiei. a a Observnd L = maximO(S 1/2 ) timpul total este O(S 3/2 ). a O solutie backtracking obtine 20 puncte, iar una cu memorie O(N 2 ) 50 puncte. Codul surs a

import java.io.*; class Avere1 { static int S; static long n; static long c[][];

// // // // // //

c[0][0]=1; c[0][s]=0; pentru s=1,2,...,S c[v][s]=c[v-1][s]; pentru v>=1 si s<v c[v][s]=c[v-1][s]+c[v-1][s-v]; pentru v>=1 si s>=v test 4 ??? test 5 = gresit N (>...) in fisier intrare toate celelalte: OK rezultat si timp !!! dar fara economie de memorie !!!!!!!

public static void main(String[] args) throws IOException { long t1,t2; t1=System.nanoTime(); int s,v; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("10-avere.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("avere.out"))); st.nextToken(); S=(int)st.nval; st.nextToken(); n=(int)st.nval; c=new long[S+1][S+1]; for(v=0;v<=S;v++) c[v][0]=(long)1;

16.2. PROBLEME REZOLVATE

415

for(v=1;v<=S;v++) for(s=0;s<=S;s++) if(s<v) c[v][s]=c[v-1][s]; else c[v][s]=c[v-1][s]+c[v-1][s-v]; //afism(); out.println(c[S][S]); while((n>0)&&(S>0)) { v=0; while(c[v][S]<n) v++; out.print(v+" "); n=n-c[v-1][S]; S=S-v; } out.close(); t2=System.nanoTime(); System.out.println("Timp = "+((double)(t2-t1))/1000000000); }// main(...) static void afism() { int i,j; for(i=0;i<=S;i++) { for(j=0;j<=S;j++) System.out.print(c[i][j]+" "); System.out.println(); } }// afism() }// class /* 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 2 1 1 1 0 0 0 0 0 0 1 1 1 2 2 2 2 2 1 1 1 0 0 1 1 1 2 2 3 3 3 3 3 3 2 2 1 1 1 2 2 3 4 4 4 5 5 5 5 1 1 1 2 2 3 4 5 5 6 7 7 8 1 1 1 2 2 3 4 5 6 7 8 9 10 1 1 1 2 2 3 4 5 6 8 9 10 12 1 1 1 2 2 3 4 5 6 8 10 11 13 1 1 1 2 2 3 4 5 6 8 10 12 14 1 1 1 2 2 3 4 5 6 8 10 12 15 Press any key to continue...

416 */

CAPITOLUL 16. PROGRAMARE DINAMICA

16.2.19

Suma - ONI2005 cls 10

Traditia este ca, la ieirea la pensie, pentru ecare zi de activitate slujba s n sultanului, marele vizir s primeasc o prim stabilit de marele sfat al rii. Astfel, a a a a ta vizirul Magir a primit pentru doar 5 zile de activitate prima total de 411 galbeni, a deoarece sfatul rii a hotrt pentru ziua ai o sum de 53 de galbeni, pentru ta aa nt a ziua a doua 200 de galbeni, pentru ziua a treia 12 galbeni, pentru ziua a patra 144 de galbeni, iar pentru ziua a cincea doar 2 galbeni. Vizirul Jibal, celebru pentru contributia adus la rezolvarea conictului din a zon, primete dreptul ca, la ieirea la pensie, s modice sumele stabilite de sfatul a s s a rii, dar nu foarte mult. El poate uni cifrele sumelor stabilite i le poate desprti ta s a apoi, dup dorint, astfel at, suma primit pe ecare zi s nu depeasc 999 de a a nc a a as a galbeni i s primeasc cel putin un galben pentru ecare dintre zilele de activitate. s a a Astfel, dac are doar 5 zile de activitate, pltite cu 23, 417, 205, 5 i respectiv 40 de a a s galbeni, total 680 de galbeni, el poate opta pentru o nou distributie a cifrelor n a numerelor stabilite de marele sfat astfel: pentru prima zi cere 2 galbeni, pentru a doua 3, pentru a treia 417, pentru a patra 205 i pentru a cincea 540 de galbeni, s primind astfel 1167 de galbeni total. n Cerint a Pentru numrul de zile n i cele n sume stabilite de sfatul rii pentru Jibal, a s ta scrieti un program care s determine cea mai mare prim total care se poate a a a obtine prin unirea i desprtirea cifrelor sumelor date. s a Datele de intrare Fiierul de intrare suma.in contine: s pe prima linie un numr natural n reprezentnd numrul de zile de activa a a

itate

pe linia urmtoare, n numere naturale separate prin spatii s1 , s2 , ..., sn a reprezentnd sumele atribuite de sfatul rii. a ta Datele de ieire s Fiierul de ieire suma.out va contine o singur linie pe care va aat un s s a s singur numr natural reprezentnd prima total maxim care se poate obtine. a a a a Restrictii i precizri s a 1 < n < 501 0 < si < 1000, pentru orice 1 i n orice distributie, ecare sum trebuie s e o valoare proprie (s nu In a a a nceap cu 0). a Orice sum dintr-o distributie trebuie s e nenul. a a a Pentru 20% din teste, n 10, pentru 50% din teste n 50. Exemple

16.2. PROBLEME REZOLVATE suma.in 3 58 300 4 suma.out 362 Explicatie Prima maxim (362) se obtine a chiar pentru distributia 58 300 4 Explicatie Prima maxim (1608) se obtine a pentru distributia 2 341 720 5 540

417

suma.in 5 23 417 205 5 40

suma.out 1608

Timp maxim de executie/test: 1 secund pentru Windows i 0.1 secunde a s pentru Linux. Indicatii de rezolvare - descriere solutie Solutia ocial a Solutia I Solutia propus utilizeaz metoda programrii dinamice. Este implementat a a a un algoritm de expandare tip Lee realizat cu o coad alocat dinamic. a a Astfel, ecare cifr contribuie la expandarea solutiilor precedente care au a ans de dezvoltare ulterioar. Vectorul best memoreaz la ecare moment suma s a a a cea mai mare format dintr-un numr dat de termeni. a a Conditia nc-nr<=(n-p^.t-1)*3+2 (unde nc este numrul total de cifre care a se distribuie, nr este numrul de ordine al cifrei curente, n este numrul total a a de termeni i p^.t este numrul de termeni ai solutiei curente) testeaz ca, prin s a a crearea unui nou termen cu ajutorul cifrei curente, s mai existe ansa construirii a s cu cifrele rmase a unei soluti cu n termeni. a Conditia nc-nr>=n-p^.t testeaz ca, prin lipirea cifrei curente la ultimul a termen al solutiei curente, s mai existe ansa construirii cu cifrele rmase a unei a s a solutii cu n termeni. type pnod=^nod; nod=record s:longint; {suma} t,last:word; {nr. de termeni si ultimul termen} next:pnod end; var n,nc,i,k:longint; f:text; best:array[1..1000]of longint; p,u:pnod; c:char; procedure citire; {determina numarul total de cifre} var i,x:longint; begin

418

CAPITOLUL 16. PROGRAMARE DINAMICA

assign(f,suma.in);reset(f); readln(f,n); for i:=1 to n do begin read(f,x); repeat inc(nc);x:=x div 10 until x=0 end; close(f) end; {expandarea corespunzatoare cifrei curente} procedure calc(nr:longint;cif:byte); var c,q:pnod; gata:boolean; begin c:=u;gata:=false; repeat if (cif>0) and (nc-nr<=(n-p^.t-1)*3+2) and (best[p^.t]=p^.s) then begin new(u^.next);u:=u^.next; u^.s:=p^.s+cif; u^.t:=p^.t+1; u^.last:=cif end; if (p^.last<100)and(nc-nr>=n-p^.t) then begin new(u^.next);u:=u^.next; u^.s:=p^.s+p^.last*9+cif; u^.t:=p^.t; u^.last:=p^.last*10+cif; end; if p=c then gata:=true; q:=p;p:=p^.next;dispose(q) until gata; end; {recalcularea valorilor maxime memorate in vectorul best} procedure optim; var i:longint; q:pnod; gata:boolean; begin for i:=1 to n do best[i]:=0; q:=p;gata:=false; repeat if q^.s>best[q^.t] then best[q^.t]:=q^.s; if q=u then gata:=true;

16.2. PROBLEME REZOLVATE q:=q^.next until gata; end; BEGIN citire; {reluarea citirii cifrelor, ignorand spatiile} reset(f); readln(f); repeat read(f,c) until c<> ; new(p); p^.s:=ord(c)-48;p^.t:=1; p^.last:=p^.s; best[1]:=p^.s; u:=p; for i:=2 to nc do begin repeat read(f,c) until c<> ; calc(i,ord(c)-48); optim end; close(f); assign(f,suma.out);rewrite(f);writeln(f,best[n]);close(f) END.

419

Solutia II Problema se rezolv prin metoda programare dinamic. Concatenm nua a a merele i obtinem un ir (s notm a) de cifre (de lungime L maxim N 3). s s a l a Pentru ecare pozitie p (p = 1..L) calculm prima maxim pe care o putem a a obtine desprtind subirul de pn la p inclusiv n sume (n = 1..N ). Obtinem a s a a n relatia: smax[p][n] = min( smax[p-1][n-1]+a[p], // punnd ultima sum format doar din cifra a[i] a a a smax[p-2][n-1]+a[p-1]*10+a[p], // punnd ultima sum din 2 cifre a a smax[p-3][n-1]+a[p-2]*100+a[p-1]*10+a[p] // punnd ultima sum din 3 cifre a a ) Trebuie avute vedere cazurile limit cnd p = 1, p = 2 sau p = 3 i cazurile n a a s care a[p], a[p 1] sau a[p 2] sunt zero, moment care nu putem forma o sum n n a de lungimea respectiv, aa c excludem termenii din expresia de minim. a s a Pentru uurinta implementare stocm smax[p][n] = inf init pentru cazul s n a care subirul de pn la p nu poate artit mod corect n sume, iar n s a a mp n n observnd c recurenta depinde doar de ultimele 3 linii, nu pstrm dect pe a a a a a acestea i linia curent pentru a nu avea probleme cu memoria. s a Obtinem memorie O(N ) i timp de executie O(L N ) = O(N 2 ); s

420 Codul surs a Varianta 1:

CAPITOLUL 16. PROGRAMARE DINAMICA

import java.io.*; // fara economie de spatiu class Suma1 { static int n,nc; // nc=nr cifre din sir static int[] c=new int[1501]; // sirul cifrelor static int[][] s; public static void main (String[] args) throws IOException { int i,j,x; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("suma.in"))); PrintWriter out=new PrintWriter(new BufferedWriter( new FileWriter("suma.out"))); st.nextToken(); n=(int)st.nval; nc=0; j=0; for(i=1;i<=n;i++) { st.nextToken(); x=(int)st.nval; if(x<10) {c[++j]=x; nc+=1;} else if(x<100) {c[++j]=x/10; c[++j]=x%10; nc+=2;} else if(x<1000) {c[++j]=x/100; c[++j]=(x/10)%10; c[++j]=x%10; nc+=3;} else System.out.println("Eroare date !"); } s=new int[nc+1][n+1]; calcul(); afism(); out.print(s[nc][n]); out.close(); }// main(...) static void calcul() { // s[i][j]=max(s[i-1][j-1]+c[i], // xj are 1: cifra c[i] // s[i-2][j-1]+c[i-1]*10+c[i], // xj are 2 cifre: c[i-1]c[i] // s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); // xj are 3 cifre

16.2. PROBLEME REZOLVATE int i,j,smax; s[1][1]=c[1]; s[2][1]=c[1]*10+c[2]; s[3][1]=c[1]*100+c[2]*10+c[3]; if(c[2]!=0) s[2][2]=c[1]+c[2]; if((c[2]!=0)&&(c[3]!=0)) s[3][3]=c[1]+c[2]+c[3]; if(c[3]==0) { if(c[2]!=0) s[3][2]=c[1]+c[2]; } else // c[3]!=0 { if(c[2]==0) s[3][2]=c[1]*10+c[3]; else // c[2]!=0 && c[3]!=0 s[3][2]=max(c[1]+c[2]*10+c[3],c[1]*10+c[2]+c[3]); } for(i=4;i<=nc;i++) for(j=1;j<=n;j++) { smax=0;

421

// i = pozitie cifra in sirul cifrelor // j = pozitie numar in sirul final al numerelor

if(j<=i) { if((c[i]!=0)&&(s[i-1][j-1]!=0)) smax=max(smax,s[i-1][j-1]+c[i]); if((c[i-1]!=0)&&(s[i-2][j-1]!=0)) smax=max(smax,s[i-2][j-1]+c[i-1]*10+c[i]); if((c[i-2]!=0)&&(s[i-3][j-1]!=0)) smax=max(smax,s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); } s[i][j]=smax; }// for }// calcul() static int max(int a, int b) { if(a>b) return a; else return b; }

422 static void afism() { int i,j;

CAPITOLUL 16. PROGRAMARE DINAMICA

System.out.print(" \t"); for(j=1;j<=n;j++) System.out.print(j+"\t"); System.out.println(); for(i=1;i<=nc;i++) { System.out.print(i+" "+c[i]+" :\t"); for(j=1;j<=n;j++) System.out.print(s[i][j]+"\t"); System.out.println(); } }// afism() }// class Varianta 2: import java.io.*; // cu economie de spatiu !!! class Suma2 { static int n,nc; // nc=nr cifre din sir static int[] c=new int[1501]; // sirul cifrelor static int[][] s; public static void main (String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j,x; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("suma.in"))); PrintWriter out=new PrintWriter(new BufferedWriter( new FileWriter("suma.out"))); st.nextToken(); n=(int)st.nval; nc=0; j=0; for(i=1;i<=n;i++) { st.nextToken(); x=(int)st.nval; if(x<10) {c[++j]=x; nc+=1;} else

16.2. PROBLEME REZOLVATE

423

if(x<100) {c[++j]=x/10; c[++j]=x%10; nc+=2;} else if(x<1000) {c[++j]=x/100; c[++j]=(x/10)%10; c[++j]=x%10; nc+=3;} else System.out.println("Eroare date !"); } s=new int[4][n+1]; // cu economie de spatiu !!! calcul(); out.print(s[nc%4][n]); out.close(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main(...) static void calcul() { // s[i][j]=max(s[i-1][j-1]+c[i],// xj are 1: cifra c[i] // s[i-2][j-1]+c[i-1]*10+c[i], // xj are 2 cifre: c[i-1]c[i] // s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); // xj are 3 cifre int i,j,smax; s[1][1]=c[1]; s[2][1]=c[1]*10+c[2]; s[3][1]=c[1]*100+c[2]*10+c[3]; if(c[2]!=0) s[2][2]=c[1]+c[2]; if((c[2]!=0)&&(c[3]!=0)) s[3][3]=c[1]+c[2]+c[3]; if(c[3]==0) { if(c[2]!=0) s[3][2]=c[1]+c[2]; } else // c[3]!=0 { if(c[2]==0) s[3][2]=c[1]*10+c[3]; else // c[2]!=0 && c[3]!=0 s[3][2]=max(c[1]+c[2]*10+c[3],c[1]*10+c[2]+c[3]); } for(i=4;i<=nc;i++) // i = pozitie cifra in sirul cifrelor for(j=1;j<=n;j++) // j = pozitie numar in sirul final al numerelor { smax=0; if(j<=i)

424 {

CAPITOLUL 16. PROGRAMARE DINAMICA

if((c[i]!=0)&&(s[(i-1+4)%4][j-1]!=0)) smax=max(smax,s[(i-1+4)%4][j-1]+c[i]); if((c[i-1]!=0)&&(s[(i-2+4)%4][j-1]!=0)) smax=max(smax,s[(i-2+4)%4][j-1]+c[i-1]*10+c[i]); if((c[i-2]!=0)&&(s[(i-3+4)%4][j-1]!=0)) smax=max(smax,s[(i-3+4)%4][j-1]+c[i-2]*100+c[i-1]*10+c[i]); } s[i%4][j]=smax; } }// calcul() static int max(int a, int b) { if(a>b) return a; else return b; } }// class Varianta 3: import java.io.*; // fara economie de spatiu dar cu afisare o solutie ! class Suma3 { static int n,nc; // nc=nr cifre din sir static int[] c=new int[1501]; // sirul cifrelor static int[][] s; static int[][] p; // predecesori static int[] sol; // o solutie public static void main (String[] args) throws IOException { int i,j,x; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("suma.in"))); PrintWriter out=new PrintWriter(new BufferedWriter( new FileWriter("suma.out"))); st.nextToken(); n=(int)st.nval; nc=0; j=0; for(i=1;i<=n;i++)

16.2. PROBLEME REZOLVATE {

425

st.nextToken(); x=(int)st.nval; if(x<10) {c[++j]=x; nc+=1;} else if(x<100) {c[++j]=x/10; c[++j]=x%10; nc+=2;} else if(x<1000) {c[++j]=x/100; c[++j]=(x/10)%10; c[++j]=x%10; nc+=3;} else System.out.println("Eroare date !"); } s=new int[nc+1][n+1]; p=new int[nc+1][n+1]; calcul(); afism(s); System.out.println(); afism(p); System.out.println(); sol=new int[n+1]; solutia(); afisv(sol); out.print(s[nc][n]); out.close(); }// main(...) static void solutia() { int i,i1,i2,k; i2=nc; for(k=n;k>=1;k--) { i1=p[i2][k]; System.out.print(k+" : "+i1+"->"+i2+" ==> "); for(i=i1;i<=i2;i++) sol[k]=sol[k]*10+c[i]; System.out.println(sol[k]); i2=i1-1; } }// solutia() static void calcul() { // s[i][j]=max(s[i-1][j-1]+c[i],// xj are 1: cifra c[i] // s[i-2][j-1]+c[i-1]*10+c[i], // xj are 2 cifre: c[i-1]c[i] // s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); // xj are 3 cifre int i,j,smax;

426

CAPITOLUL 16. PROGRAMARE DINAMICA s[1][1]=c[1]; s[2][1]=c[1]*10+c[2]; s[3][1]=c[1]*100+c[2]*10+c[3]; p[1][1]=p[2][1]=p[3][1]=1; if(c[2]!=0) s[2][2]=c[1]+c[2]; if((c[2]!=0)&&(c[3]!=0)) s[3][3]=c[1]+c[2]+c[3]; if(c[3]==0) { if(c[2]!=0) s[3][2]=c[1]+c[2]; } else // c[3]!=0 { if(c[2]==0) { s[3][2]=c[1]*10+0+c[3]; p[3][2]=3;} else // c[2]!=0 && c[3]!=0 { s[3][2]=max(c[1]+c[2]*10+c[3],c[1]*10+c[2]+c[3]); if(s[3][2]==c[1]+c[2]*10+c[3]) p[3][2]=2; else p[3][2]=3; } } for(i=4;i<=nc;i++) // i = pozitie cifra in sirul cifrelor for(j=1;j<=n;j++) // j = pozitie numar in sirul final al numerelor { smax=0; if(j<=i) { if((c[i]!=0)&&(s[i-1][j-1]!=0)) { smax=max(smax,s[i-1][j-1]+c[i]); if(smax==s[i-1][j-1]+c[i]) p[i][j]=i; } if((c[i-1]!=0)&&(s[i-2][j-1]!=0)) { smax=max(smax,s[i-2][j-1]+c[i-1]*10+c[i]); if(smax==s[i-2][j-1]+c[i-1]*10+c[i]) p[i][j]=i-1; } if((c[i-2]!=0)&&(s[i-3][j-1]!=0)) { smax=max(smax,s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); if(smax==s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]) p[i][j]=i-2; }

16.2. PROBLEME REZOLVATE }// if s[i][j]=smax; }// for }// calcul() static int max(int a, int b) { if(a>b) return a; else return b; } static void afism(int[][] x) { int i,j; System.out.print(" \t"); for(j=1;j<=n;j++) System.out.print(j+"\t"); System.out.println(); for(i=1;i<=nc;i++) { System.out.print(i+" "+c[i]+" :\t"); for(j=1;j<=n;j++) System.out.print(x[i][j]+"\t"); System.out.println(); } }// afism() static void afisv(int[] sol) { int i,sum=0; System.out.println(); for(i=1;i<=n;i++) { System.out.print(sol[i]+" "); sum+=sol[i]; } System.out.println(" ==> "+sum); }// afisv() }// class

427

428

CAPITOLUL 16. PROGRAMARE DINAMICA

Capitolul 17

Potrivirea irurilor s
Considerm un text (un ir de caractere) t = (t1 , t2 , ..., tn ) i un ablon a s s s (tot un ir de caractere, numit pattern englez) p = (p1 , p2 , ..., pm ). Considerm s n a a m n i dorim s determinm dac textul t contine ablonul p, adic, dac exist s a a a s a a a 0 d n m astfel at td+i = pi pentru orice 1 i m. Problema potrivirii nc irurilor const determinarea tuturor valorilor d (considerate deplasamente) cu s a n proprietatea mentionat. a

17.1

Un algoritm inecient

Pentru ecare pozitie i cuprins a ntre 1 i nm+1 vom verica dac subirul s a s (xi , xi+1 , ..., xi+m1 ) coincide cu y. import java.io.*; class PotrivireSir { static char[] t,p; static int n,m; public static void main(String[] args) throws IOException { int i,j; String s; BufferedReader br=new BufferedReader( new FileReader("potriviresir.in")); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("potriviresir.out"))); 429

430

CAPITOLUL 17. POTRIVIREA SIRURILOR

s=br.readLine(); n=s.length(); t=new char[n+1]; for(i=0;i<n;i++) t[i+1]=s.charAt(i); System.out.print("t : "); afisv(t,1,n); s=br.readLine(); m=s.length(); p=new char[m+1]; for(i=0;i<m;i++) p[i+1]=s.charAt(i); System.out.print("p : "); afisv(p,1,m); System.out.println(); for(i=1;i<=n-m+1;i++) { for(j=1;j<=m;j++) if(p[j]!=t[i+j-1]) break; j--; // ultima pozi\c tie potrivita if(j==m) { afisv(t,1,n); afisv(p,i,i+j-1); System.out.println(); } } out.close(); }//main() static void afisv(char[] x, int i1, int i2) { int i; for(i=1;i<i1;i++) System.out.print(" "); for(i=i1;i<=i2;i++) System.out.print(x[i]); System.out.println(); }// afisv(...) }//class /* x : abababaababaababa y : abaabab abababaababaababa abaabab abababaababaababa abaabab */

17.2. UN ALGORITM EFICIENT - KMP

431

17.2

Un algoritm ecient - KMP

Algoritmul KMP (Knuth-Morris-Pratt) determin potrivirea irurilor folosind a s informatii referitoare la potrivirea subirului cu diferite deplasamente ale sale. s Pentru t: 1 a i s 1 a 2 b 3 a 1 a 3 a 4 b 2 b 4 b 5 a 3 a 5 a a 1 5 a 6 b 4 a 6 b b 2 6 b 7 a 5 b 7 a a 3 7 a 8 a 6 a 8 a a 4 8 a 9 b 7 b 9 b b 5 9 b 10 a 11 b 12 a 13 a 14 b 15 a 16 b 17 a

p: 2 b

exist dou potriviri: a a 10 a a 6 10 a a 1 11 b b 7 11 b b 2 12 a 13 a 14 b 15 a 16 b 17 a

t: p:

t: p:

1 a

2 b

3 a

4 b

12 a a 3

13 a a 4

14 b b 5

15 a a 6

16 b b 7

17 a

Sirul inecient de ncercri este: a 1 a a 2 b b a 3 a a b a 4 b a a b a 5 a b a a b a 6 b a b a a b a 7 a b a b a a b a 8 a b a b a a b a 9 b 10 a 11 b 12 a 13 a 14 b 15 a 16 b 17 a

t: p: p: p: p: p: p: p: p: p: p: p:

b a b a a b a

b a b a a b a

b a b a a b a 1

* b a b a a b 2

b a b a a 3

b a b a 4

b a b 5

b a 6

* b 7

Prima nepotrivire din ecare ncercare este evidentiat prin caracter boldat a iar solutiile sunt marcate cu *. Dorim s avansm cu mai mult de un pas la o nou a a a ncercare, fr s riscm aa a a s pierdem vreo solutie! a

432 1 a a 2 b b 3 a a a 4 b a b 5 a b a a 6 b a a b 7 a b b a

CAPITOLUL 17. POTRIVIREA SIRURILOR 8 a a a 9 b b b 10 a 11 b 12 a 13 a 14 b 15 a 16 b 17 a

t: p: p: p: p: p:

a a

b b

* a

a a

b b

* a

Notm prin t[i..j] secventa de elemente consecutive (ti , ..., tj ) (cuprins a a ntre pozitiile i i j) din irul t = (t1 , t2 , ..., tn ). s s S presupunem c suntem la un pas al vericrii potrivirii cu un deplasament a a a d i prima nepotrivire a aprut pe pozitia i din text i pozitia j + 1 din ablon, s a s s deci t[i j..i 1] = p[1..j] i t[i] = p[j + 1]. s Care este cel mai bun deplasament d pe care trebuie s-l a ncercam?

p:
1 ...

t:
d'

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx

...

j+1-k i-k 1

...

j+1

... ... ...

i-j

...

...

i-1 k

i k+1

... ... m

...

p:

...

Figura 17.1: Deplasare optim a Folosind Figura 17.2, dorim s determinm cel mai mare indice k < j astfel a a at p[1..k] = p[j + 1 k..j]. Cu alte cuvinte, dorim s determinm cel mai nc a a lung sux al secventei p[1..j] iar noul deplasament d trebuie ales astfel at s nc a realizeze acest lucru. Este astfel realizat i potrivirea textului t cu ablonul p, a s s t[i k..i 1] = p[1..k]. Rmne s vericm apoi dac t[i] = p[k + 1]. a a a a a Observm c noul deplasament depinde numai de ablonul p i nu are nici o a a s s legtur cu textul t. a a Algoritmul KMP utilizeaz pentru determinarea celor mai lungi suxe o a functie numit next. a Dat ind irul de caractere p[1..m], functia s next : {1, 2, ..., m} {0, 1, ..., m 1} este denit astfel: a next(j) = max{k/k < j i p[1..k] este sux pentru p[1..j]}. s Cum determinm practic valorile functiei next? a

17.2. UN ALGORITM EFICIENT - KMP Initializm next[1] = 0. a Presupunem c au fost determinate valorile next[1], next[2], ..., next[j]. a Cum determinm next[j + 1]? a
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

433

k+1-k'

p: p:
1 ...

...

...

k+1 j+1 k'+1

... ... ...

... ... ...

j+1-k

... ...

p:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

j+1-k'

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

k'

Figura 17.2: Functia next Ne ajutm de Figura 17.2 pentru a urmri mai uor rationamentul! aceast a a s In a gur next[j] = k i next[k] = k . a s Dac p[j+1] = p[k+1] (folosind notatiile din gur) atunci next[j+1] = k+1. a a Obtinem: dac p[j + 1] = p[next(j) + 1] atunci next[j + 1] = next(j) + 1. a Ce se ampl dac p[j + 1] = p[k + 1]? nt a a Cutm un sux mai mic pentru p[1..j]! Fie acesta p[1..k ]. Dar acest sux a a mai mic este cel mai lung sux pentru p[1..k], cu alte cuvinte k = next(k) = next(next(j)). Astfel, dac p[j + 1] = p[k + 1] atunci next(j + 1) = k + 1. a Obtinem: dac p[j + 1] = p[next(next(j)) + 1] atunci next[j + 1] = next(next(j)) + 1. a Dac nici acum nu avem egalitate de caractere, vom continua acelai rationaa s ment pn cand gsim o egalitate de caractere sau lungimea prexului cutat a a a a este 0. Evident, acest algoritm se termin a ntr-un numr nit de pai pentru c a s a j > k > k > ... 0. Dac ajungem la 0, atunci vom avea next(j + 1) = 0. a Ordinul de complexitate al algoritmului KMP este O(n + m). import java.io.*; class KMP { static int na=0; // nr aparitii static char[] t,p; // t[1..n]=text, p[1..m]=pattern static int[] next;

434

CAPITOLUL 17. POTRIVIREA SIRURILOR

static void readData() throws IOException { String s; char[] sc; int i,n,m; BufferedReader br=new BufferedReader(new FileReader("kmp.in")); s=br.readLine(); sc=s.toCharArray(); n=sc.length; t=new char[n+1]; for(i=1;i<=n;i++) t[i]=sc[i-1]; s=br.readLine(); sc=s.toCharArray(); m=sc.length; p=new char[m+1]; for(i=1;i<=m;i++) p[i]=sc[i-1]; }//readData() static int[] calcNext(char[] p) { int m=p.length-1; int[] next=new int[m+1]; // next[1..m] pentru p[1..m] next[1]=0; // initializare int k=0; // nr caractere potrivite int j=2; while(j<=m) { while(k>0&&p[k+1]!=p[j]) k=next[k]; if(p[k+1]==p[j]) k++; next[j]=k; j++; } return next; }// calcNext() static void kmp(char[] t, char[] p) // t[1...n], p[1..m] { int n=t.length-1, m=p.length-1; next=calcNext(p); int j=0; // nr caractere potrivite deja int i=1; // ultima pozitie a sufixului while(i<=n) // t[1..n] {

17.2. UN ALGORITM EFICIENT - KMP

435

while(j>0&&p[j+1]!=t[i]) j=next[j]; if(p[j+1]==t[i]) j++; if(j==m) { na++; System.out.println("pattern cu deplasarea "+(i-m)+" : "); afissol(t,p,i-m); j=next[j]; } i++; }// while }// kmp static void afissol(char[] t, char[] p, int d) { int i, n=t.length-1, m=p.length-1; for(i=1;i<=n;i++) System.out.print(t[i]); System.out.println(); for(i=1;i<=d;i++) System.out.print(" "); for(i=1;i<=m;i++) System.out.print(p[i]); System.out.println(); }// afissol(...) public static void main(String[] args) throws IOException { readData(); kmp(t,p); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("kmp.out"))); out.println(na); out.close(); }//main() }//class /* pattern apare cu deplasarea 5 : 12312123412123412 1234 pattern apare cu deplasarea 11 : 12312123412123412 1234 */

436

CAPITOLUL 17. POTRIVIREA SIRURILOR

17.3
17.3.1

Probleme rezolvate
Circular - Campion 2003-2004 Runda 6

Autor: prof. Mot Nistor, Colegiul National N.Balcescu - Braila Se spune c irul y1 , y2 , ..., yn este o permutare circular cu p pozitii a irului as a s x1 , x2 , ..., xn dac y1 = xp + 1, y2 = xp + 2, ..., yn = xp + n, unde indicii mai mari a ca n se consider modulo n, adic indicele k, cu k > n se refer la elementul de a a a indice k n. Cerint a Pentru dou iruri date determinati dac al doilea este o permutare circular as a a a primului ir. s Date de intrare Pe prima linie a ierului de intrare circular.in este scris numarul natural s n. Pe liniile urmtoare sunt dou iruri de caractere de lungime n, formate numai a as din litere mari ale alfabetului latin. Date de ieire s Pe prima linie a ierului circular.out se va scrie cel mai mic numr natural p s a pentru care irul de pe linia a treia este o permutare circular cu p pozitii a irului s a s de pe linia a doua, sau numrul 1 dac nu avem o permutare circular. a a a Restrictii i precizri s a 1 n 20000 Exemple circular.in 10 ABCBAABBAB BABABCBAAB circular.out 7

Timp maxim de executie/test: 0.1 secunde Rezolvare (indicatia autorului): O variant cu dou for-uri e foarte uor a a s de scris, dar nu se ncadreaz timp pentru n mare. a n Folosim algoritmului KMP de cutare a unui subir. a s Concatenm primul ir cu el si i cutm prima aparitie a celui de-al a s nsu s a a doilea ir irul nou format. realitate nu e nevoie de concatenarea efectiv a s n s In a irului, doar inem cont c indicii care se refer la irul mai lung trebuie luati s t a a s modulo n. import java.io.*; class Circular { static int n,d=-1; // pozitia de potrivire

17.3. PROBLEME REZOLVATE static char[] x,y; // x[1..n]=text, y[1..m]=pattern static int[] next;

437

static void readData() throws IOException { String s; char[] sc; int i; BufferedReader br=new BufferedReader(new FileReader("circular.in")); n=Integer.parseInt(br.readLine()); // System.out.println("n="+n); x=new char[n+1]; y=new char[n+1]; s=br.readLine(); sc=s.toCharArray(); // System.out.println("x="+s); for(i=1;i<=n;i++) x[i]=sc[i-1]; s=br.readLine(); sc=s.toCharArray(); // System.out.println("y="+s); for(i=1;i<=n;i++) y[i]=sc[i-1]; }//readData() static int[] calcNext(char[] p) { int m=n; int[] next=new int[m+1]; // next[1..m] pentru p[1..m] next[1]=0; // initializare int k=0; // nr caractere potrivite int j=2; while(j<=m) { while(k>0&&p[k+1]!=p[j]) k=next[k]; if(p[k+1]==p[j]) k++; next[j]=k; j++; } return next; }// calcNext() static void kmp(char[] t, char[] p) // t[1...n], p[1..m] { int m=p.length-1; next=calcNext(p); int j=0; // nr caractere potrivite deja int i=1; // ultima pozitie a sufixului

438

CAPITOLUL 17. POTRIVIREA SIRURILOR

while((i<=2*n)&&(d==-1)) // t[1..n] { while(j>0&&p[j+1]!=t[(i>n)?(i-n):i]) j=next[j]; if(p[j+1]==t[(i>n)?(i-n):i]) j++; if(j==m) { d=i-n; break; } i++; }// while }// kmp public static void main(String[] args) throws IOException { readData(); kmp(x,y); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("circular.out"))); out.println(d); out.close(); }//main() }//class /* circular.in circular.out ---------------------20 5 12312123412123412341 12341212341234112312 */

17.3.2

Cifru - ONI2006 baraj

Copiii solarieni se joac adesea trimitndu-i mesaje codicate. Pentru codia a s care ei folosesc un cifru bazat pe o permutare p a literelor alfabetului solarian i s un numr natural d. a Alfabetul solarian contine m litere foarte complicate, aa c noi le vom s a reprezenta prin numere de la 1 la m. Dat ind un mesaj limbaj solarian, reprezentat de noi ca o succesiune n de n numere cuprinse ntre 1 i m, c1 c2 ...cn , codicarea mesajului se realizeaz s a astfel: se nlocuiete ecare liter ci cu p(ci ), apoi irul obtinut p(c1 )p(c2 )...p(cn ) s a s se rotete spre dreapta, fcnd o permutare circular cu d pozitii rezultnd irul s a a a a s p(cnd+1 )...p(cn1 )p(cn )p(c1 )p(c2 )...p(cnd ). De exemplu, pentru mesajul 213321, permutarea p = (312) (adic p(1) = 3, a p(2) = 1, p(3) = 2) i d = 2. Aplicnd permutarea p vom obtine irul 132213, apoi s a s rotind spre dreapta irul cu dou pozitii obtinem codicarea 131322. s a Cerint: a

17.3. PROBLEME REZOLVATE

439

Date ind un mesaj necodicat i codicarea sa, determinati cifrul folosit s (permutarea p i numrul d). s a Date de intrare: Fiierul de intrare cifru.in contine pe prima linie numele naturale n i m, s s separate prin spatiu, reprezentnd lungimea mesajului i respectiv numrul de a s a litere din alfabetul solarian. Pe cea de a doua linie este scris mesajul necodicat ca o succesiune de n numere cuprinse ntre 1 i m separate prin cte un spatiu. s a Pe cea de a treia linie este scris mesajul codicat ca o succesiune de n numere cuprinse ntre 1 i m separate prin cte un spatiu. s a Date de ieire: s Fiierul de ieire cifru.out va contine pe prima linie numrul natural d, s s a reprezentnd numrul de pozitii cu care s-a realizat permutarea circular spre a a a dreapta. Dac pentru d exist mai multe posibilitii se va alege valoarea minim. a a at a Pe urmtoarea linie este descris permutarea p. Mai exact se vor scrie valorile a a p(1), p(2), ..., p(m) separate prin cte un spatiu. a Restrictii: n 100000 m 9999 Mesajul contine ecare numr natural din intervalul [1, m] cel putin o dat. a a Pentru teste cu m 5 se acord 40 de puncte din care 20 pentru teste i cu a s n 2000. Exemplu: cifru.in cifru.out 63 2 213321 312 131322 Timp maxim de executie/test: 0.2 secunde Indicatii de rezolvare: Solutia comisiei Fiecare aparitie a unui simbol din alfabet ntr-un ir se s nlocuiete cu distanta s fat de precedenta aparitie a aceluiai simbol (considernd irul circular, deci pena s a s tru prima aparitie a simbolului se ia distanta fat de ultima aparitie a aceluiai a s simbol). Fcnd aceast recodicare pentru cele dou iruri reducem problema la dea a a as terminarea permutrii circulare care duce primul ir al doilea, care poate a s n rezolvat cu un algoritm de pattern matching, dac concatenm primul ir cu el a a a s si rezultnd o complexitate O(n). nsu a Pentru m mic se pot genera toate permutrile multimii {1, 2, ..., m} fcnd a a a pentru ecare permutare o cutare (cu KMP de exemplu), iar pentru n mic se a poate cuta permutarea pentru ecare d = 0, 1, ..., n. a Codul surs a import java.io.*;

440

CAPITOLUL 17. POTRIVIREA SIRURILOR

class kmp { static int[] t0; // text mesaj necodificat --> spatiu ... de eliberat ! static int[] t1; // text mesaj codificat --> spatiu ... de eliberat ! static int[] d0; // distante ... mesaj necodificat static int[] d1; // distante ... mesaj codificat static int[] t; // text in KMP ... (d0,d0) ... d0 dublat ... spatiu !!! static int[] s; // sablon in KMP ... (d1) static int[] p; // prefix in KMP ... 1,2,...n static int[] ua; // pozitia ultimei aparitii ... 1,2,...,m ... ==> d[] mai rapid ... static int[] perm;// permutarea static int n,m; // ... n=100.000, m=9.999 ... maxim !!! ==> 200K public static void main(String[] args) throws IOException { StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("9-cifru.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("cifru.out"))); int i,j,j0,j1,k,deplasarea=-1; st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; ua=new int[m+1]; t0=new int[n+1]; t1=new int[n+1]; d0=new int[n+1]; d1=new int[n+1]; p=new int[n+1]; for(i=1;i<=n;i++) { st.nextToken(); t0[i]=(int)st.nval; } for(i=1;i<=n;i++) { st.nextToken(); t1[i]=(int)st.nval; } distanta(t0,d0); distanta(t1,d1); //afisv(t0,1,n); afisv(d0,1,n); System.out.println(); //afisv(t1,1,n); afisv(d1,1,n); System.out.println();

17.3. PROBLEME REZOLVATE s=d0; prefix(s,p,n); //afisv(s,1,n); afisv(p,1,n); System.out.println();

441

t=new int[2*n+1]; // ocupa spatiu prea mult; aici standard dar ... for(i=1;i<=n;i++) t[i]=t[n+i]=d1[i]; //afisv(t,1,2*n); deplasarea=kmp(t,2*n,s,n)-1; // d1 dublat si caut d0 ... out.println(deplasarea); System.out.println(deplasarea); // permutarea ... perm=ua; // economie de spatiu ... for(i=1;i<=m;i++) perm[i]=0; k=0; // nr elemente plasate deja in permutare ... j1=0; for(i=1;i<=n;i++) { j1++; j0=n-deplasarea+i; if(j0>n) j0=j0-n; //System.out.println(i+" : "+j0+" "+j1); if(perm[t0[j0]]==0) { perm[t0[j0]]=t1[j1]; k++; } if(k==m) break; } //afisv(perm,1,m); for(i=1;i<=m;i++) out.print(perm[i]+" "); out.close(); }// main static int kmp(int[] t, int n, int[] s, int m)// t1,...,tn si s1,...,sm { int k,i,pozi=-1; k=0; for (i=1;i<=n;i++) { while(k>0&&s[k+1]!=t[i]) k=p[k]; if (s[k+1]==t[i]) k++;

442

CAPITOLUL 17. POTRIVIREA SIRURILOR

if(k==m) { pozi=i-m+1; //System.out.println("incepe pe pozitia "+pozi); break; // numai prima aparitie ... !!! } }// for return pozi; }// kmp(...) static void distanta(int[] t,int[] d) // t=text, d=distante ... { int i,j,k; for(i=1;i<=m;i++) ua[i]=0; for(i=1;i<=n;i++) { if(ua[t[i]]!=0) // stiu pozitia spre stanga a lui t[i] ... { if(ua[t[i]]<i) d[i]=i-ua[t[i]]; // e mai la stanga ... else d[i]=i-ua[t[i]]+n; // e mai la dreapta ... ua[t[i]]=i; // noua pozitie a lui t[i] ... continue; } // nu a aparut inca in 1..i-1 ==> de la n spre stanga k=i; // distanta spre stanga ... pana la n inclusiv ... j=n; // caut in zona n,n-1,n-2,... while(t[i]!=t[j]) { k++; j--; } d[i]=k; ua[t[i]]=i; }// for i }// distanta(...) static void prefix(int[] s,int[] p,int m) // s=sablon, p=prefix, m=dimensiune { int i,k;

17.3. PROBLEME REZOLVATE p[1]=0; for(i=2;i<=m;i++) { k=p[i-1]; while(k>0&&s[k+1]!=s[i]) k=p[k]; if(s[k+1]==s[i]) p[i]=k+1; else p[i]=0; } }// prefix() static void afisv(int[] x, int i1, int i2) { int i; for(i=i1;i<=i2;i++) System.out.print(x[i]+" "); System.out.println(); }// afisv(...) }// class

443

444

CAPITOLUL 17. POTRIVIREA SIRURILOR

Capitolul 18

Geometrie computational a

18.1

Determinarea orientrii a

Considerm trei puncte plan P1 (x1 , y1 ), P2 (x2 , y2 ) i P3 (x3 , y3 ). a n s Panta segmentului P1 P2 : m12 = (y2 y1 )/(x2 x1 ) Panta segmentului P2 P3 : m23 = (y3 y2 )/(x3 x2 )
P3 P3 P2 P2 P1 a) P1 b) P1 c) P2 P3

Orientarea parcurgerii laturilor P1 P2 i P2 P3 ( aceast ordine): s n a sens trigonometric (spre stnga): m12 < m23 , cazul a) gur n a n a sensul acelor de ceas (spre dreapta): m12 > m23 , cazul c) gur n n a vrfuri coliniare: m12 = m23 , cazul b) gur a n a Orientarea depinde de valoarea expresiei o(P1 (x1 , y1 ), P2 (x2 , y2 ), P3 (x3 , y3 )) = (y2 y1 ) (x3 x2 ) (y3 y2 ) (x2 x1 ) 445

446 astfel

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

18.2

< 0 sens trigonometric o(P1 (x1 , y1 ), P2 (x2 , y2 ), P3 (x3 , y3 )) = 0 coliniare > 0 sensul acelor de ceas

Testarea convexitii poligoanelor at

Considerm un poligon cu n vrfuri P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ), n 3. a a Poligonul este convex dac i numai dac perechile de segmente as a (P1 P2 , P2 P3 ), (P2 P3 , P3 P4 ), ..., (Pn2 Pn1 , Pn1 Pn ) i (Pn1 Pn , Pn P1 ) s au aceeai orientare sau sunt colineare. s
P7 P6 P5 P1 P2 P3 P4 P1 P7 P6 P5 P2 P3 P4

a)

b)

18.3

Aria poligoanelor convexe

Aria poligonului convex cu n vrfuri P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ), n 3 a se poate determina cu ajutorul urmtoarei formule: a 1 |x1 y2 + x2 y3 + ... + xn1 yn + xn y1 y1 x2 + y2 x3 + ... + yn1 xn + yn x1 | 2 Expresia de sub modul este pozitiv dac orientarea P1 P2 ...Pn P1 a a este sens trigonometric, este negativ dac orientarea P1 P2 ...Pn P1 n a a este sensul acelor de ceasornic i este nul dac punctele P1 (x1 , y1 ), P2 (x2 , y2 ), n s a a ..., Pn (xn yn ) sunt colineare. Reciproca acestei armatii este deasemenea adevrat a a cazul poligoanelor convexe. n

18.4

Pozitia unui punct fat de un poligon convex a

18.5. POZITIA UNUI PUNCT FATA DE UN POLIGON CONCAV

447

Considerm un poligon convex cu n vrfuri P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ), a a n 3 i un punct P0 (x0 , y0 ). Dorim s determinm dac punctul P0 (x0 , y0 ) este s a a a interiorul poligonului. n Pentru comoditatea prezentrii considerm i punctul Pn+1 (xn+1 , yn+1 ) unde a a s x1 = xn+1 i y1 = yn+1 , adic punctul Pn+1 este de fapt tot punctul P1 . s a Considerm o latur oarecare [Pi Pi+1 ] (1 i n) a poligonului. a a Ecuatia dreptei (Pi Pi+1 ) este (Pi Pi+1 ) : y yi = yi+1 yi (x xi ) xi+1 xi

Aducem la acelai numitor i considerm functia s s a fi (x, y) = (y yi ) (xi+1 xi ) (x xi ) (yi+1 yi ) Dreapta (Pi Pi+1 ) mparte planul dou semiplane. Functia fi (x, y) are valori n a de acelai semn pentru toate punctele din acelai semiplan, valori cu semn contrar s s pentru toate punctele din cellalt semiplan i valoarea 0 pentru doate punctele a s situate pe dreapt. a Pentru a siguri c punctul P0 (x0 , y0 ) se a interiorul poligonului (acesta a a n ind convex) trebuie s vericm dac toate vrfurile poligonului a a a a mpreun cu a punctul P0 (x0 , y0 ) sunt de aceeai parte a dreptei (Pi Pi+1 ), adic toate valorile s a fi (xj , yj ) (1 j n, j = i i j = i + 1) au acelai semn cu fi (x0 , y0 ) (sau s s sunt nule dac acceptm prezenta punctului P0 (x0 , y0 ) pe frontiera poligonului). a a Aceasta este o conditie necesar dar nu i sucient. Vom verica dac pentru a s a a orice latur [Pi Pi+1 ] (1 i n) a poligonului toate celelalte vrfuri sunt a a n acelai semiplan cu P0 (x0 , y0 ) (din cele dou determinate de dreapta suport a s a laturii respective) iar dac se ampl acest lucru atunci putem trage concluzia a nt a c punctul P0 (x0 , y0 ) se a interiorul poligonului convex. a a n O alt modalitate de vericare dac punctul P0 (x0 , y0 ) este interiorul a a n sau pe frontiera poligonului convex P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ) este vericarea urmtoarei relatii: a
n

ariepoligon (P1 P2 ...Pn ) =


k=1

arietriunghi (P0 Pk Pk+1 )

unde punctul P (xn+1 , yn+1 ) este de fapt tot punctul P1 (x1 , y1 ).

18.5

Pozitia unui punct fat de un poligon concav a

Considerm un poligon concav cu n vrfuri P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ), a a n 3 i un punct P0 (x0 , y0 ). Dorim s determinm dac punctul P0 (x0 , y0 ) este s a a a interiorul poligonului. n

448

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

Poligonul concav se descompune poligoane convexe cu ajutorul diagon nalelor interne i se folosete un algoritm pentru poligoane convexe pentru ecare s s poligon convex astfel obtinut. Dac punctul este interiorul unui poligon convex a n obtinut prin partitionarea poligonului concav atunci el se a interiorul acestuia. a n Dac nu se a nici un poligon convex obtinut prin partitionarea poligonului a a n concav atunci el nu se a interiorul acestuia. a n
P7 P6 P5 P1 P2 P3 P4 P1 P2 P3 P4 P7 P6 P5

a)

b)

18.6

aurtoarea convex Infs a a


Impachetarea Jarvis

18.6.1

5 4 3 2 1 1 2 3 4 5 6 7

5 4 3 2 1 1 2 3 4 5 6 7

a)

b)

Toate punctele de pe aurtoarea convex (cazul a) gur): nfs a a n a import java.io.*; // infasuratoare convexa class Jarvis1 // pe frontiera coliniare ==> le iau pe toate ... !!! {

18.6. ASURATOAREA CONVEXA INF static static static static static int int int int int n,npic=0; [] x; [] y; [] p; [] u;

449

// npic=nr puncte pe infasuratoarea convexa

// precedent // urmator

static void afisv(int[] a, int k1, int k2) { int k; for(k=k1;k<=k2;k++) System.out.print(a[k]+" "); System.out.println(); } static int orient(int i1, int i2, int i3) { long s=(y[i1]-y[i2])*(x[i3]-x[i2])-(y[i3]-y[i2])*(x[i1]-x[i2]); if(s<0) return -1; else if(s>0) return 1; else return 0; } static void infasurareJarvis() throws IOException { BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); int i0,i,i1,i2; i0=1; for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i; System.out.println("Stanga_Jos ==> P"+i0+" : "+x[i0]+" "+y[i0]); i1=i0; npic++; System.out.println(npic+" --> "+i1); //br.readLine(); do { i2=i1+1; if(i2>n) i2-=n; for(i=1;i<=n;i++) { //System.out.println("orient("+i1+","+i+","+i2+")="+orient(i1,i,i2)); //br.readLine(); if(orient(i1,i,i2)>0) i2=i; else if(orient(i1,i,i2)==0) // coliniare if( // i intre i1 i2 ==> cel mai apropiat ((x[i]-x[i1])*(x[i]-x[i2])<0)|| ((y[i]-y[i1])*(y[i]-y[i2])<0)

450 ) i2=i;

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

} u[i1]=i2; p[i2]=i1; i1=i2; npic++; System.out.println(npic+" --> "+i1); //br.readLine(); } while(i2!=i0); npic--; // apare de doua ori primul punct ! System.out.print("u : "); afisv(u,1,n); System.out.print("p : "); afisv(p,1,n); }// infasurareJarvis() public static void main(String[] args) throws IOException { int k; StreamTokenizer st= new StreamTokenizer( new BufferedReader(new FileReader("jarvis.in"))); st.nextToken(); n=(int)st.nval; x=new int[n+1]; y=new int[n+1]; p=new int[n+1]; u=new int[n+1]; for(k=1;k<=n;k++) { st.nextToken(); x[k]=(int)st.nval; st.nextToken(); y[k]=(int)st.nval; } infasurareJarvis(); }//main }//class Fr punctele coliniare de pe aurtoarea convex (cazul b) gur): aa nfs a a n a import java.io.*; // infasuratoare convexa class Jarvis2 // pe frontiera coliniare ==> iau numai capetele ... !!! { static int n,npic=0; // npic=nr puncte pe infasuratoarea convexa static int [] x; static int [] y; static int [] p; // precedent static int [] u; // urmator

18.6. ASURATOAREA CONVEXA INF static void afisv(int[] a, int k1, int k2) { int k; for(k=k1;k<=k2;k++) System.out.print(a[k]+" "); System.out.println(); }

451

static int orient(int i1, int i2, int i3) { long s=(y[i1]-y[i2])*(x[i3]-x[i2])-(y[i3]-y[i2])*(x[i1]-x[i2]); if(s<0) return -1; else if(s>0) return 1; else return 0; } static void infasurareJarvis() throws IOException { BufferedReader br=new BufferedReader(new InputStreamReader(System.in)); int i0,i,i1,i2; i0=1; for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i; System.out.println("Stanga_Jos ==> P"+i0+" : "+x[i0]+" "+y[i0]); i1=i0; npic++; System.out.println(npic+" --> "+i1); //br.readLine(); do { i2=i1+1; if(i2>n) i2-=n; for(i=1;i<=n;i++) { //System.out.println("orient("+i1+","+i+","+i2+")="+orient(i1,i,i2)); //br.readLine(); if(orient(i1,i,i2)>0) i2=i; else if(orient(i1,i,i2)==0) // coliniare if( // i2 intre i1 i ==> cel mai departat ((x[i2]-x[i1])*(x[i2]-x[i])<0)|| ((y[i2]-y[i1])*(y[i2]-y[i])<0) ) i2=i; } u[i1]=i2; p[i2]=i1; i1=i2;

452

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

npic++; System.out.println(npic+" --> "+i1); //br.readLine(); } while(i2!=i0); npic--; // apare de doua ori primul punct ! System.out.print("u : "); afisv(u,1,n); System.out.print("p : "); afisv(p,1,n); }// infasurareJarvis() public static void main(String[] args) throws IOException { int k; StreamTokenizer st= new StreamTokenizer( new BufferedReader(new FileReader("jarvis.in"))); st.nextToken(); n=(int)st.nval; x=new int[n+1]; y=new int[n+1]; p=new int[n+1]; u=new int[n+1]; for(k=1;k<=n;k++) { st.nextToken(); x[k]=(int)st.nval; st.nextToken(); y[k]=(int)st.nval; } infasurareJarvis(); }//main }//class

18.6.2

Scanarea Craham

Versiune cu mesaje pentru sortarea punctelor: import java.io.*; // numai pentru sortare si mesaje ... class Graham0 { static int n,npic=0; // npic=nr puncte pe infasuratoarea convexa static int[] x; static int[] y; static int[] o; // o[k] = pozitia lui k inainte de sortare static int[] of; // of[k] = pozitia lui k dupa sortare // pentru depanare ... stop ! ... static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

18.6. ASURATOAREA CONVEXA INF

453

5 4 3 2 1 1 2 3 4 5 6 7

5 4 3 2 1 1 2 3 4 5 6 7

a)

b)

5 4 3 2 1 1 2 3 4 5 6 7

5 4 3 2 1 1 2 3 4 5 6 7

a)

b)

static void afisv(int[] a, int k1, int k2) { int k; for(k=k1;k<=k2;k++) System.out.print(a[k]+" "); System.out.print(" "); } static int orient(int i0,int i1, int i2) { long s=(y[i1]-y[i0])*(x[i2]-x[i0])-(y[i2]-y[i0])*(x[i1]-x[i0]); if(s<0) return -1; else if(s>0) return 1; else return 0; }

454

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

static void qsort(int p, int u) throws IOException { // aleg un punct fix k int k=(p+u)/2; System.out.println("qsort: p="+p+" u="+u+" k="+k+" xk="+x[k]+" yk="+y[k]); System.out.print("x : "); afisv(x,p,u); System.out.println(); System.out.print("y : "); afisv(y,p,u); System.out.println(); int i,j,aux; i=p;j=u; while(i<j) { while( (i<j)&& ( (orient(1,i,k)<0)|| ((orient(1,i,k)==0)&& (((x[i]-x[1])*(x[i]-x[k])<0)||((y[i]-y[1])*(y[i]-y[k])<0))) ) ) { i++; } while( (i<j)&& ( (orient(1,j,k)>0)|| ((orient(1,j,k)==0)&& (((x[j]-x[1])*(x[j]-x[k])>0)||((y[j]-y[1])*(y[j]-y[k])>0))) ) ) { j--; } if(i<j) { if(k==i) k=j; else if(k==j) k=i;// k=fix dar se poate schimba pozitia aux=x[i]; x[i]=x[j]; x[j]=aux; aux=y[i]; y[i]=y[j]; y[j]=aux; aux=o[i]; o[i]=o[j]; o[j]=aux; } }// while System.out.println("Final while ... i="+i+" // i=j si P[i] este pe locul lui !!! j="+j);

18.6. ASURATOAREA CONVEXA INF

455

System.out.print("x : "); afisv(x,p,i-1); afisv(x,i,i); afisv(x,i+1,u); System.out.println(); System.out.print("y : "); afisv(y,p,i-1); afisv(y,i,i); afisv(y,i+1,u); System.out.println(); br.readLine(); if(p<i-1) qsort(p,i-1); if(j+1<u) qsort(j+1,u); }// qSort(...) static void scanareGraham() throws IOException { int i0,i,i1,i2,aux; System.out.print("x : "); afisv(x,1,n); System.out.println(); System.out.print("y : "); afisv(y,1,n); System.out.println(); System.out.println(); i0=1; for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i; System.out.println("Stanga_Jos ==> P"+i0+" : "+x[i0]+" "+y[i0]+"\n"); aux=x[1]; x[1]=x[i0]; x[i0]=aux; aux=y[1]; y[1]=y[i0]; y[i0]=aux; aux=o[1]; o[1]=o[i0]; o[i0]=aux; System.out.print("x : "); afisv(x,1,n); System.out.println(); System.out.print("y : "); afisv(y,1,n); System.out.println(); System.out.print("o : "); afisv(o,1,n); System.out.println(); System.out.println(); qsort(2,n); System.out.println(); System.out.print("x : "); afisv(x,1,n); System.out.println(); System.out.print("y : "); afisv(y,1,n); System.out.println(); System.out.print("o : "); afisv(o,1,n); System.out.println(); System.out.println(); }// scanareGraham() public static void main(String[] args) throws IOException { int k;

456

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA StreamTokenizer st= new StreamTokenizer( new BufferedReader(new FileReader("graham.in"))); st.nextToken(); n=(int)st.nval; x=new int[n+1]; y=new int[n+1]; o=new int[n+1]; of=new int[n+1]; for(k=1;k<=n;k++) { st.nextToken(); x[k]=(int)st.nval; st.nextToken(); y[k]=(int)st.nval; o[k]=k; } scanareGraham();

// ordinea finala (dupa sortare) a punctelor for(k=1;k<=n;k++) of[o[k]]=k; System.out.println(); System.out.print("of : "); afisv(of,1,n); System.out.println(); System.out.println(); }//main }//class Versiune cu toate punctele de pe aurtoare: nfs a import java.io.*; // NU prinde punctele coliniarele pe ultima latura ! class Graham1 // daca schimb ordinea pe "razele" din sortare, atunci ... { // NU prinde punctele coliniarele pe prima latura, asa ca ... static int n; static int np; // np=nr puncte pe infasuratoarea convexa static int[] x; static int[] y; static int[] o; // pozitia inainte de sortare static int[] p; // poligonul infasuratoare convexa static int orient(int i0,int i1, int i2) { long s=(y[i1]-y[i0])*(x[i2]-x[i0])-(y[i2]-y[i0])*(x[i1]-x[i0]); if(s<0) return -1; else if(s>0) return 1; else return 0; } static void qsort(int p, int u)

18.6. ASURATOAREA CONVEXA INF {

457

int i,j,k,aux; // aleg un punct fix k k=(p+u)/2; i=p; j=u; while(i<j) { while( (i<j)&& ( (orient(1,i,k)<0)|| ((orient(1,i,k)==0)&& (((x[i]-x[1])*(x[i]-x[k])<0)||((y[i]-y[1])*(y[i]-y[k])<0))) ) ) i++; while( (i<j)&& ( (orient(1,j,k)>0)|| ((orient(1,j,k)==0)&& (((x[j]-x[1])*(x[j]-x[k])>0)||((y[j]-y[1])*(y[j]-y[k])>0))) ) ) j--; if(i<j) { if(k==i) k=j; else if(k==j) k=i;// k=fix dar se poate schimba pozitia aux=x[i]; x[i]=x[j]; x[j]=aux; aux=y[i]; y[i]=y[j]; y[j]=aux; aux=o[i]; o[i]=o[j]; o[j]=aux; } } if(p<i-1) qsort(p,i-1); if(j+1<u) qsort(j+1,u); }// qSort(...) static void scanareGraham() throws IOException { int i0,i,i1,i2,i3,aux; i0=1; for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i; aux=x[1]; x[1]=x[i0]; x[i0]=aux; aux=y[1]; y[1]=y[i0]; y[i0]=aux; aux=o[1]; o[1]=o[i0]; o[i0]=aux; qsort(2,n);

458

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

i1=1; p[1]=i1; i2=2; p[2]=i2; np=2; i3=3; while(i3<=n) { while(orient(i1,i2,i3)>0) { i2=p[np-1]; i1=p[np-2]; np--; } np++; p[np]=i3; i2=p[np]; i1=p[np-1]; i3++; }// while // plasez si punctele coliniare de pe ultima latura a infasuratorii i=n-1; while(orient(1,p[np],i)==0) p[++np]=i--; // afisez rezultatele System.out.print("punctele initiale: "); for(i=1;i<=np;i++) System.out.print(o[p[i]]+" "); System.out.println(); System.out.print("infasuratoare x: "); for(i=1;i<=np;i++) System.out.print(x[p[i]]+" "); System.out.println(); System.out.print("infasuratoare y: "); for(i=1;i<=np;i++) System.out.print(y[p[i]]+" "); System.out.println(); }// scanareGraham() public static void main(String[] args) throws IOException { int k; StreamTokenizer st= new StreamTokenizer( new BufferedReader(new FileReader("graham1.in"))); st.nextToken(); n=(int)st.nval;

18.6. ASURATOAREA CONVEXA INF x=new y=new o=new p=new int[n+1]; int[n+1]; int[n+1]; int[n+1];

459

for(k=1;k<=n;k++) { st.nextToken(); x[k]=(int)st.nval; st.nextToken(); y[k]=(int)st.nval; o[k]=k; } scanareGraham(); }//main }//class Versiune fr puncte coliniare pe aurtoare: aa nfs a import java.io.*; // aici ... infasuratoarea nu contine puncte coliniare ... class Graham2 // este o eliminare din rezultatul final dar ... { // se pot elimina puncte la sortare si/sau scanare ... static int n; static int np; // np=nr puncte pe infasuratoarea convexa static int[] x; static int[] y; static int[] o; // pozitia inainte de sortare static int[] p; // poligonul infasuratoare convexa static int orient(int i0,int i1, int i2) { long s=(y[i1]-y[i0])*(x[i2]-x[i0])-(y[i2]-y[i0])*(x[i1]-x[i0]); if(s<0) return -1; else if(s>0) return 1; else return 0; } static void qsort(int p, int u)// elimin si punctele coliniare (din interior) { int i,j,k,aux; // aleg un punct fix k k=(p+u)/2; i=p; j=u; while(i<j) {

460 while(

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA (i<j)&& ( (orient(1,i,k)<0)|| ((orient(1,i,k)==0)&& (((x[i]-x[1])*(x[i]-x[k])<0)||((y[i]-y[1])*(y[i]-y[k])<0))) ) ) i++; while( (i<j)&& ( (orient(1,j,k)>0)|| ((orient(1,j,k)==0)&& (((x[j]-x[1])*(x[j]-x[k])>0)||((y[j]-y[1])*(y[j]-y[k])>0))) ) ) j--; if(i<j) { if(k==i) k=j; else if(k==j) k=i;// k=fix dar se poate schimba pozitia aux=x[i]; x[i]=x[j]; x[j]=aux; aux=y[i]; y[i]=y[j]; y[j]=aux; aux=o[i]; o[i]=o[j]; o[j]=aux; }

} if(p<i-1) qsort(p,i-1); if(j+1<u) qsort(j+1,u); }// qSort(...) static void scanareGraham() throws IOException { int i0,i,i1,i2,i3,aux; i0=1; for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i; aux=x[1]; x[1]=x[i0]; x[i0]=aux; aux=y[1]; y[1]=y[i0]; y[i0]=aux; aux=o[1]; o[1]=o[i0]; o[i0]=aux; qsort(2,n); i1=1; p[1]=i1; i2=2; p[2]=i2; np=2; i3=3; while(i3<=n) {

18.6. ASURATOAREA CONVEXA INF while(orient(i1,i2,i3)>0) // elimin i2 { i2=p[np-1]; i1=p[np-2]; np--; } np++; p[np]=i3; i2=p[np]; i1=p[np-1]; i3++; }// while // eliminarea punctelor coliniare de pe infasuratoare p[np+1]=p[1]; for(i=1;i<=np-1;i++) if(orient(p[i],p[i+1],p[i+2])==0) o[p[i+1]]=0;

461

// afisez rezultatele System.out.print("punctele initiale: "); for(i=1;i<=np;i++) if(o[p[i]]!=0) System.out.print(o[p[i]]+" "); System.out.println(); System.out.print("infasuratoare x: "); for(i=1;i<=np;i++) if(o[p[i]]!=0) System.out.print(x[p[i]]+" "); System.out.println(); System.out.print("infasuratoare y: "); for(i=1;i<=np;i++) if(o[p[i]]!=0) System.out.print(y[p[i]]+" "); System.out.println(); }// scanareGraham() public static void main(String[] args) throws IOException { int k; StreamTokenizer st= new StreamTokenizer( new BufferedReader(new FileReader("graham2.in"))); st.nextToken(); n=(int)st.nval; x=new int[n+2]; y=new int[n+2]; o=new int[n+2]; p=new int[n+2]; for(k=1;k<=n;k++) {

462

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA st.nextToken(); x[k]=(int)st.nval; st.nextToken(); y[k]=(int)st.nval; o[k]=k; } scanareGraham();

}//main }//class

18.7

Dreptunghi minim de acoperire a punctelor

Se poate determina dreptunghiul minim de acoperire pentru aurtoarea nfs a convex (gura 18.1) pentru a prelucra mai putine puncte dar nu este obligatorie a aceast strategie. a

M P7 A P1 P2 N
xmin

D P6 P12 P10 P3 P11 P4 P5 C Q


xmax ymin

ymax

P8

P9

Figura 18.1: Dreptunghi minim de acoperire

Putem s presupunem c punctele formeaz un poligon convex. Determinarea a a a dreptunghiului de arie minim care contine interiorul su (inclusiv frontiera) a n a toate punctele date se poate face observnd c o latur a sa contine o latur a a a a a poligonului convex. Pentru ecare latur a poligonului convex se determin drepa a tunghiul minim de acoperire care contine acea latur. Dintre aceste dreptunghiuri a se alege cel cu aria minim. a

18.8. CERC MINIM DE ACOPERIRE A PUNCTELOR

463

18.8

Cerc minim de acoperire a punctelor

Se poate determina cercul minim de acoperire pentru aurtoarea convex nfs a a pentru a prelucra mai putine puncte dar nu este obligatorie aceast strategie. a

Ck = C k-1
P
k x

k+1

Ck

Ck

k+1

a)

b)

Ordonm punctele astfel at pe primele pozitii s e plasate punctele de a nc a extrem (cel mai din stnga, urmat de cel mai din dreapta, urmat de cel mai de a jos, urmat de cel mai de sus; dup acestea urmeaz celelalte puncte a a ntr-o ordine oarecare). Presupunem c punctele, dup ordonare, sunt: P1 (x1 , y1 ), P2 (x2 , y2 ), a a P3 (x3 , y3 ), ..., Pn (xn , yn ). Notm cu Ci (ai , bi ; ri ) cercul de centru (ai , bi ) i raz minim ri care acoper a s a a a punctele P1 , P2 , ..., Pn . Considerm cercul C2 (a2 , b2 ; r2 ) unde a2 = (x1 + x2 )/2, b2 = (y1 + y2 )/2 i a s a r2 = 1 (x2 x1 )2 + (y2 y1 )2 , adic cercul de diametru [P1 P2 ]. 2 S presupunem c am determinat, pas cu pas, cercurile C2 , C3 , ..., Ci i a a s trebuie s determinm cercul Ci+1 . a a Dac punctul Pi+1 se a interiorul cercului Ci atunci cercul Ci+1 este a a n identic cu Ci . Dac punctul Pi+1 nu se a interiorul cercului Ci atunci cercul Ci+1 se dea a n termin relu algoritmul pentru irul de puncte P1 , P2 , ...,Pi , Pi+1 dar impunnd a nd s a conditia ca acest cerc s treac mod obligatoriu prin punctul Pi+1 (xi+1 , yi+1 ). a a n Putem plasa acest punct pe prima pozitie irul punctelor i astfel vom impune n s s la ecare pas ca punctul P1 s e pe cercul care trebuie determinat! a

18.9
18.9.1

Probleme rezolvate
Seceta - ONI2005 clasa a IX-a
lect. Ovidiu Doma s

464

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

Grdinile roditoare ale Brganului sufer anual pierderi imense din a aa a cauza secetei. Cuttorii de ap au gsit n fntni din care doresc s alimenteze n a a a a a a a grdini. Fie Gi , Fi , i = 1, ..., n puncte plan reprezentnd puncte de alimentare a n a ale grdinilor i respectiv punctele care se a fntnile. Pentru ecare punct a s n a a a se dau coordonatele ntregi (x, y) plan. n Pentru a economisi materiale, legtura dintre o grdin i o fntn se reaa a as a a a lizeaz printr-o conduct linie dreapt. Fiecare fntn alimenteaz o singur a a n a a a a a a grdin. Consiliul Judetean Galati pltete investitia cu conditia ca lungimea toa a a s tal a conductelor s e minim. a a a Fiecare unitate de conduct cost 100 lei noi (RON). a a Cerint a S se determine m, costul minim total al conductelor ce leag ecare grdin a a a a cu exact o fntn. a a a Date de intrare Fiierul de intrare seceta.in va contine: s Pe prima linie se a numrul natural n, reprezentnd numrul grdinilor a a a a a i al fntnilor. s a a Pe urmtoarele n linii se a perechi de numere a a ntregi Gx Gy , separate printr-un spatiu, reprezentnd coordonatele punctelor de alimentare ale grdinilor. a a Pe urmtoarele n linii se a perechi de numere a a ntregi Fx Fy , separate printr-un spatiu, reprezentnd coordonatele punctelor fntnilor. a a a Date de ieire s Fiierul de ieire seceta.out va contine: s s m un numr natural reprezentnd partea a a ntreag a costului minim total a al conductelor. Restrictii i precizri s a 1 < n < 13 0 Gx, Gy, F x, F y 200 Nu exist trei puncte coliniare, indiferent dac sunt grdini sau fntni a a a a a Orice linie din ierele de intrare i ieire se termin prin marcajul de sfrit s s s a as de linie. Exemplu seceta.in 3 14 33 47 23 25 31 seceta.out 624 Explicatie Costul minim este [6.24264 * 100]=624 prin legarea perechilor: Gradini Fantani 14 23 33 31 47 25

Timp maxim de executie/test: 1 sec sub Windows i 0.5 sec sub Linux. s

18.9. PROBLEME REZOLVATE Indicatii de rezolvare *

465

Solutia ocial, lect. Ovidiu Doma a s Numrul mic al punctelor permite generarea tuturor posibilitilor de a conecta a at o grdin cu o fntn neconectat la un moment dat. a a a a a a Pentru ecare astfel de combinatie gsit se calculeaz suma distantelor a a a (Gi, F j), linie dreapta, folosind formula distantei dintre dou puncte plan, n a n studiat la geometrie. (d(A(x, y), B(z, t) = (x z)2 + (y t)2 ). a Acest solutie implementat corect asigur 60 70 de puncte. a a a Pentru a obtine punctajul maxim se tine cont de urmtoarele aspecte: a 1. Se construiete prealabil matricea distantelor d(i, j) cu semnicatia s n distantei dintre grdina i i fntna j. Aceasta va reduce timpul de calcul la a s a a variantele cu peste 9 perechi. 2. Pentru a elimina cazuri care nu pot constitui solutii optime se folosete s proprietatea patrulaterului c suma a doua laturi opuse (conditie care asigur a a unicitatea conectrii unei singure fntni la o singur grdin) este mai mic dect a a a a a a a a suma diagonalelor. De aceea nu se vor lua considerare acele segmente care se n intersecteaz. Conditia de intersectie a dou segmente care au capetele punctele a a n de coordonate A(a1, a2), B(b1, b2), C(c1, c2), D(d1, d2) este ca lund segmentul a AB, punctele C i D s se ae de aceeai parte a segmentului AB i respectiv s a s s pentru segmentul CD, punctele A i B s se ae de aceeai parte (se s a s nlocuiete s ecuatia dreptei ce trece prin dou puncte, studiat clasa a 9-a). n a a n Observatie: Pentru cei interesati, problema are solutie i la un nivel superior, s folosind algoritmul de determinare a unui ux maxim de cost minim. Variant cu determinarea intesectiei segmentelor. a import java.io.*; // cu determinarea intesectiei segmentelor class Seceta1 // Java este "mai incet" decat Pascal si C/C++ { // test 9 ==> 2.23 sec static int nv=0; static int n; static int[] xg, yg, xf, yf, t, c; static int[] a; // permutare: a[i]=fantana asociata gradinii i static double costMin=200*1.42*12*100; static double[][] d; static PrintWriter out; static StreamTokenizer st; public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); citire();

466

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA rezolvare(); afisare(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms");

} static void citire() throws IOException { int k; st=new StreamTokenizer(new BufferedReader(new FileReader("seceta.in"))); st.nextToken(); n=(int)st.nval; xg=new int[n+1]; yg=new int[n+1]; xf=new int[n+1]; yf=new int[n+1]; a=new int[n+1]; d=new double[n+1][n+1]; for(k=1;k<=n;k++) { st.nextToken(); st.nextToken(); } for(k=1;k<=n;k++) { st.nextToken(); st.nextToken(); } } static void rezolvare() throws IOException { int i,j; int s; for(i=1;i<=n;i++) // gradina i for(j=1;j<=n;j++) // fantana j { s=(xg[i]-xf[j])*(xg[i]-xf[j])+(yg[i]-yf[j])*(yg[i]-yf[j]); d[i][j]=Math.sqrt(s); } f(1); // generez permutari }

xg[k]=(int)st.nval; yg[k]=(int)st.nval;

xf[k]=(int)st.nval; yf[k]=(int)st.nval;

18.9. PROBLEME REZOLVATE

467

static void f(int k) { boolean ok; int i,j; for(i=1;i<=n;i++) { ok=true; // k=1 ==> nu am in stanga ... for nu se executa ! for(j=1;j<k;j++) if(i==a[j]) {ok=false; break;} if(!ok) continue; for(j=1;j<k;j++) if(seIntersecteaza(xg[k],yg[k],xf[i], yf[i], xg[j],yg[j],xf[a[j]],yf[a[j]])) { ok=false; break; } if(!ok) continue; a[k]=i; if(k<n) f(k+1); else verificCostul(); } } static void verificCostul() { int i; double s=0; for(i=1;i<=n;i++) s=s+d[i][a[i]]; if(s<costMin) costMin=s; } // de ce parte a dreptei [(xa,ya);(xb,yb)] se afla (xp,yp) static int s(int xp,int yp,int xa,int ya,int xb,int yb) { double s=(double)yp*(xb-xa)-xp*(yb-ya)+xa*yb-xb*ya; if(s<-0.001) return -1; // in zona "negativa" else if(s>0.001) return 1; // in zona "pozitiva" else return 0; // pe dreapta suport } // testeaza daca segmentul[P1,P1] se intersecteaza cu [P3,P4] static boolean seIntersecteaza(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4) { double x,y;

468

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA if((x1==x2)&&(x3==x4)) // ambele segmente verticale if(x1!=x3) return false; else if(intre(y1,y3,y4)||intre(y2,y3,y4)) return true; else return false; if((y1==y2)&&(y3==y4)) // ambele segmente orizontale if(y1!=y3) return false; else if(intre(x1,x3,x4)||intre(x2,x3,x4)) return true; else return false; if((y2-y1)*(x4-x3)==(y4-y3)*(x2-x1)) // au aceeasi panta (oblica) if((x2-x1)*(y3-y1)==(y2-y1)*(x3-x1)) // au aceeasi dreapta suport if(intre(x1,x3,x4)||intre(x2,x3,x4)) return true; else return false; else return false;// nu au aceeasi dreapta suport else // nu au aceeasi panta (macar unul este oblic) { x=(double)((x4-x3)*(x2-x1)*(y3-y1)x3*(y4-y3)*(x2-x1)+ x1*(y2-y1)*(x4-x3))/ ((y2-y1)*(x4-x3)-(y4-y3)*(x2-x1)); if(x2!=x1) y=y1+(y2-y1)*(x-x1)/(x2-x1); else y=y3+(y4-y3)*(x-x3)/(x4-x3); if(intre(x,x1,x2)&&intre(y,y1,y2)&&intre(x,x3,x4)&&intre(y,y3,y4)) return true; else return false; }

} static boolean intre(int c, int a, int b) // c este in [a,b] ? { int aux; if(a>b) {aux=a; a=b; b=aux;} if((a<=c)&&(c<=b)) return true; else return false; } static boolean intre(double c, int a, int b) // c este in [a,b] ? { int aux; if(a>b) {aux=a; a=b; b=aux;} if((a<=c)&&(c<=b)) return true; else return false; } static void afisare() throws IOException { int k;

18.9. PROBLEME REZOLVATE

469

out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out"))); out.println((int)(costMin*100)); out.close(); } } Variant cu cu determinarea pozitiei punctelor in semiplane i mesaje pentru a s depanare. import java.io.*; // cu determinarea pozitiei punctelor in semiplane class Seceta2 // cu mesaje pentru depanare ! { static int nv=0; static int n; static int[] xg, yg, xf, yf, t, c; static int[] a; // permutare: a[i]=fantana asociata gradinii i static double costMin=200*1.42*12*100; static double[][] d; static PrintWriter out; static StreamTokenizer st; public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); citire(); rezolvare(); afisare(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } static void citire() throws IOException { int k; st=new StreamTokenizer(new BufferedReader(new FileReader("seceta.in"))); st.nextToken(); n=(int)st.nval; xg=new int[n+1]; yg=new int[n+1]; xf=new int[n+1]; yf=new int[n+1]; a=new int[n+1]; d=new double[n+1][n+1]; for(k=1;k<=n;k++)

470 {

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

st.nextToken(); xg[k]=(int)st.nval; st.nextToken(); yg[k]=(int)st.nval; } for(k=1;k<=n;k++) { st.nextToken(); xf[k]=(int)st.nval; st.nextToken(); yf[k]=(int)st.nval; } } static void rezolvare() throws IOException { int i,j; int s; for(i=1;i<=n;i++) // gradina i for(j=1;j<=n;j++) // fantana j { s=(xg[i]-xf[j])*(xg[i]-xf[j])+(yg[i]-yf[j])*(yg[i]-yf[j]); d[i][j]=Math.sqrt(s); } f(1); // generez permutari } static void f(int k) { boolean ok; int i,j; for(i=1;i<=n;i++) { ok=true; // k=1 ==> nu am in stanga ... for nu se executa ! for(j=1;j<k;j++) if(i==a[j]) {ok=false; break;} if(!ok) continue; for(j=1;j<k;j++) if((s(xg[k],yg[k],xg[j],yg[j],xf[a[j]],yf[a[j]])* s(xf[i],yf[i],xg[j],yg[j],xf[a[j]],yf[a[j]])<0)&& (s(xg[j], yg[j], xg[k],yg[k],xf[i],yf[i])* s(xf[a[j]],yf[a[j]],xg[k],yg[k],xf[i],yf[i])<0)) { afisv(k-1);// pe pozitia k(gradina) vreau sa pun i(fantana) System.out.print(i+" ");// pe pozitia j(gradina) e pus a[j](fantana) System.out.print(k+""+i+" "+j+""+a[j]); System.out.print(" ("+xg[k]+","+yg[k]+") "+" ("+xf[i]+","+yf[i]+") ");

18.9. PROBLEME REZOLVATE System.out.println(" ok=false; break; } if(!ok) continue; a[k]=i; if(k<n) f(k+1); else verificCostul(); } } static void verificCostul() { int i; double s=0; for(i=1;i<=n;i++) s=s+d[i][a[i]]; if(s<costMin) costMin=s; afisv(n); System.out.println(" "+s+" "+costMin+" "+(++nv)); } static void afisv(int nn) { int i; for(i=1;i<=nn;i++) System.out.print(a[i]); } // de ce parte a dreptei [(xa,ya);(xb,yb)] se afla (xp,yp) static int s(int xp,int yp,int xa,int ya,int xb,int yb) { double s=(double)yp*(xb-xa)-xp*(yb-ya)+xa*yb-xb*ya; if(s<-0.001) return -1; // in zona "negativa" else if(s>0.001) return 1; // in zona "pozitiva" else return 0; // pe dreapta suport } ("+xg[j]+","+yg[j]+") "+"

471 ("+xf[a[j]]+","+yf[a[j]]+") ");

static void afisare() throws IOException { int k; out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out"))); out.println((int)(costMin*100)); out.close(); } }

472

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

Variant cu cu determinarea pozitiei punctelor in semiplane, fr mesaje a aa pentru depanare. import java.io.*; // cu determinarea pozitiei punctelor in semiplane class Seceta3 // Java este "mai incet" decat Pascal si C/C++ { // test 9 ==> 2.18 sec static int n; static int[] xg, yg, xf, yf, t, c; static int[] a; // permutare: a[i]=fantana asociata gradinii i static double costMin=200*1.42*12*100; static double[][] d; static PrintWriter out; static StreamTokenizer st; public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); citire(); rezolvare(); afisare(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); } static void citire() throws IOException { int k; st=new StreamTokenizer(new BufferedReader( new FileReader("seceta.in"))); st.nextToken(); n=(int)st.nval; xg=new int[n+1]; yg=new int[n+1]; xf=new int[n+1]; yf=new int[n+1]; a=new int[n+1]; d=new double[n+1][n+1]; for(k=1;k<=n;k++) { st.nextToken(); xg[k]=(int)st.nval; st.nextToken(); yg[k]=(int)st.nval; } for(k=1;k<=n;k++)

18.9. PROBLEME REZOLVATE { st.nextToken(); xf[k]=(int)st.nval; st.nextToken(); yf[k]=(int)st.nval; } } static void rezolvare() throws IOException { int i,j; int s; for(i=1;i<=n;i++) // gradina i for(j=1;j<=n;j++) // fantana j { s=(xg[i]-xf[j])*(xg[i]-xf[j])+(yg[i]-yf[j])*(yg[i]-yf[j]); d[i][j]=Math.sqrt(s); } f(1); // generez permutari }

473

static void f(int k) { boolean ok; int i,j; for(i=1;i<=n;i++) { ok=true; // k=1 ==> nu am in stanga ... for nu se executa ! for(j=1;j<k;j++) if(i==a[j]) {ok=false; break;} if(!ok) continue; for(j=1;j<k;j++) if((s(xg[k], yg[k], xg[j],yg[j],xf[a[j]],yf[a[j]])* s(xf[i], yf[i], xg[j],yg[j],xf[a[j]],yf[a[j]])<0)&& (s(xg[j], yg[j], xg[k],yg[k],xf[i], yf[i])* s(xf[a[j]],yf[a[j]],xg[k],yg[k],xf[i], yf[i])<0)) { ok=false; break; } if(!ok) continue; a[k]=i; if(k<n) f(k+1); else verificCostul(); } } static void verificCostul()

474 {

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

int i; double s=0; for(i=1;i<=n;i++) s=s+d[i][a[i]]; if(s<costMin) costMin=s; } //de ce parte a dreptei [(xa,ya);(xb,yb)] se afla (xp,yp) static int s(int xp,int yp,int xa,int ya,int xb,int yb) { double s=(double)yp*(xb-xa)-xp*(yb-ya)+xa*yb-xb*ya; if(s<-0.001) return -1; // in zona "negativa" else if(s>0.001) return 1; // in zona "pozitiva" else return 0; // pe dreapta suport } static void afisare() throws IOException { int k; out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out"))); out.println((int)(costMin*100)); out.close(); } } Varianta 4: import java.io.*; // gresit (!) dar ... obtine 100p ... !!! class Seceta4 // test 9 : 2.18 sec --> 0.04 sec { static int n; static int[] xg, yg, xf, yf, t, c; static int[] a; // permutare: a[i]=fantana asociata gradinii i static double costMin=200*1.42*12*100; static double[][] d; static boolean[] epus=new boolean[13]; static PrintWriter out; static StreamTokenizer st; public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis();

18.9. PROBLEME REZOLVATE citire(); rezolvare(); afisare(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); }// main(...)

475

static void citire() throws IOException { int k; st=new StreamTokenizer(new BufferedReader(new FileReader("seceta.in"))); st.nextToken(); n=(int)st.nval; xg=new int[n+1]; yg=new int[n+1]; xf=new int[n+1]; yf=new int[n+1]; a=new int[n+1]; d=new double[n+1][n+1]; for(k=1;k<=n;k++) { st.nextToken(); st.nextToken(); } for(k=1;k<=n;k++) { st.nextToken(); st.nextToken(); } }// citire(...)

xg[k]=(int)st.nval; yg[k]=(int)st.nval;

xf[k]=(int)st.nval; yf[k]=(int)st.nval;

static void rezolvare() throws IOException { int i,j; int s; for(i=1;i<=n;i++) // gradina i for(j=1;j<=n;j++) // fantana j { s=(xg[i]-xf[j])*(xg[i]-xf[j])+(yg[i]-yf[j])*(yg[i]-yf[j]); d[i][j]=Math.sqrt(s); } f(1); // generez permutari

476 }// rezolvare(...)

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

static void f(int k) { int i,j; boolean seIntersecteaza; for(i=1;i<=n;i++) { if(epus[i]) continue; seIntersecteaza=false; for(j=1;j<=k-1;j++) if(d[k][i]+d[j][a[j]]>d[j][i]+d[k][a[j]]) { seIntersecteaza=true; break; } if(seIntersecteaza) continue; a[k]=i; epus[i]=true; if(k<n) f(k+1); else verificCostul(); epus[i]=false; }// for i }// f(...) static void verificCostul() { int i; double s=0; for(i=1;i<=n;i++) s=s+d[i][a[i]]; if(s<costMin) costMin=s; }// verificCostul(...) static void afisare() throws IOException { int k; out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out"))); out.println((int)(costMin*100)); out.close(); }// afisare(...) }// class

18.9. PROBLEME REZOLVATE

477

18.9.2

Antena - ONI2005 clasa a X-a

prof. Osman Ay, Liceul International de Informatic Bucureti a s Delta Dunrii exist o zon slbatic, rupt de bucuriile i necazurile In a a a a a a s civilizatiei moderne. aceast zon exist doar n case, pozitiile acestora ind specicate prin In a a a coordonatele carteziene de pe hart. a Postul de radio al ONI 2005 dorete s emit pentru toti locuitorii din zon s a a a i, prin urmare, va trebui s instaleze o anten de emisie special pentru aceasta. s a a a O anten emite unde radio a ntr-o zon circular. Centrul zonei coincide cu a a punctul care este pozitionat antena. Raza zonei este denumit puterea antenei. n a a Cu ct puterea antenei este mai mare, cu att antena este mai scump. a a a Prin urmare trebuie selectat o pozitie optim de amplasare a antenei, astfel a a at ecare cas s se ae interiorul sau pe frontiera zonei circulare care nc a a n n emite antena, iar puterea antenei s e minim. a a Cerint a Scrieti un program care s determine o pozitie optim de amplasare a antenei, a a precum i puterea minim a acesteia. s a Datele de intrare Fiierul de intrare antena.in contine pe prima linie un numr natural n, s a reprezentnd numrul de case din zon. Pe urmtoarele n linii se a pozitiile a a a a a caselor. Mai exact, pe linia i + 1 se a dou numere a a ntregi separate printr-un spatiu x y, ce reprezint abscisa i respectiv ordonata casei i. Nu exist dou case a s a a aceeai locatie. n s Datele de ieire s Fiierul de ieire antena.out contine pe prima linie dou numere reale seps s a arate printr-un spatiu x y reprezentnd abscisa i ordonata pozitiei optime de am s plasare a antenei. Pe cea de a doua linie se va scrie un numr real reprezentnd puterea antenei. a a Restrictii i precizri s a 2 < N < 15001 15000 < x, y < 15001 Numerele reale din ierul de ieire trebuie scrise cu trei zecimale cu rotuns s

jire.

La evaluare, se veric dac diferenta dintre solutia aat i cea corect a a s as a ( valoare absolut) este < 0.01. n a Exemplu

478 antena.in 7 50 26 45 22 02 36 52

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA antena.out 3.250 2.875 3.366 Explicatie Antena va plasat punctul a n de coordonate (3.250, 2.825) iar puterea antenei este 3.366

Timp maxim de executie/test: 0.3 secunde pentru Windows i 0.1 se s cunde pentru Linux. import java.io.*; // practic, trebuie sa determinam cele trei puncte class Antena // prin care trece cercul care le acopera pe toate!!! { static int n; static int[] x,y; static double x0, y0, r0; public static void main(String[] args) throws IOException { int k; long t1,t2; t1=System.nanoTime(); StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("antena.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("antena.out")));

18.9. PROBLEME REZOLVATE st.nextToken(); n=(int)st.nval; x=new int[n+1]; y=new int[n+1]; for(k=1;k<=n;k++) { st.nextToken(); x[k]=(int)st.nval; st.nextToken(); y[k]=(int)st.nval; }

479

if(n>3) { puncteExtreme(); cercDeDiametru(x[1],y[1],x[2],y[2]); for(k=3;k<=n;k++) if(!esteInCerc(k)) cercPrin(x[k],y[k],k-1); // trece prin Pk si acopera 1,2,...,k-1 } else cercCircumscris(x[1],y[1],x[2],y[2],x[3],y[3]); // scriere cu 3 zecimale rotunjite out.print( (double)((int)((x0+0.0005)*1000))/1000+" "); out.println((double)((int)((y0+0.0005)*1000))/1000); out.println((double)((int)((r0+0.0005)*1000))/1000); out.close(); t2=System.nanoTime(); System.out.println("Timp = "+((double)(t2-t1))/1000000000); }// main(...) // trece prin (xx,yy) si acopera punctele 1,2,...,k static void cercPrin(int xx, int yy, int k) { int j; cercDeDiametru(x[1],y[1],xx,yy); // trece prin P1 si (xx,yy) for(j=2;j<=k;j++) if(!esteInCerc(j)) cercPrin(xx,yy,x[j],y[j],j-1); // ... acopera 1,2,...,j-1 }// cercPrin(...) // trece prin (xx,yy) si (xxx,yyy) si acopera 1,2,3,...,j static void cercPrin(int xx,int yy,int xxx,int yyy,int j) { int i;

480

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

cercDeDiametru(xx,yy,xxx,yyy); for(i=1;i<=j;i++) // acopera 1,2,...,j if(!esteInCerc(i)) cercCircumscris(xx,yy,xxx,yyy,x[i],y[i]); }// cercPrin(...) static boolean esteInCerc(int k) { if(d(x[k],y[k],x0,y0)<r0+0.0001) return true; else return false; } static void puncteExtreme() { int k,aux,min,max,kmin,kmax; // caut cel mai din stanga punct (si mai jos) si-l pun pe pozitia 1 // (caut incepand cu pozitia 1) kmin=1; min=x[1]; for(k=2;k<=n;k++) if((x[k]<min)||(x[k]==min)&&(y[k]<y[kmin])) {min=x[k]; kmin=k;} if(kmin!=1) swap(1,kmin); // caut cel mai din dreapta (si mai sus) punct si-l pun pe pozitia 2 // (caut incepand cu pozitia 2) kmax=2; max=x[2]; for(k=3;k<=n;k++) if((x[k]>max)||(x[k]==max)&&(y[k]>y[kmax])) {max=x[k]; kmax=k;} if(kmax!=2) swap(2,kmax); // caut cel mai de jos (si mai la dreapta) punct si-l pun pe pozitia 3 // (caut incepand cu pozitia 3) kmin=3; min=y[3]; for(k=4;k<=n;k++) if((y[k]<min)||(y[k]==min)&&(x[k]>x[kmin])) {min=y[k]; kmin=k;} if(kmin!=3) swap(3,kmin); // caut cel mai de sus (si mai la stanga) punct si-l pun pe pozitia 4 // (caut incepand cu pozitia 4) kmax=4; max=y[4]; for(k=5;k<=n;k++) if((y[k]>max)||(y[k]==max)&&(x[k]<x[kmax])) {max=y[k]; kmax=k;} if(kmax!=4) swap(4,kmax); if(d(x[1],y[1],x[2],y[2])<d(x[3],y[3],x[4],y[4])) // puncte mai departate

18.9. PROBLEME REZOLVATE { swap(1,3); swap(2,4); } }// puncteExtreme()

481

static void cercCircumscris(int x1,int y1,int x2,int y2,int x3,int y3) { // consider ca punctele nu sunt coliniare ! // (x-x0)^2+(y-y0)^2=r^2 ecuatia cercului verificata de punctele P1,P2,P3 // 3 ecuatii si 3 necunoscute: x0, y0, r double a12, a13, b12, b13, c12, c13; // int ==> eroare !!! a12=2*(x1-x2); b12=2*(y1-y2); c12=x1*x1+y1*y1-x2*x2-y2*y2; a13=2*(x1-x3); b13=2*(y1-y3); c13=x1*x1+y1*y1-x3*x3-y3*y3; // sistemul devine: a12*x0+b12*y0=c12; // a13*x0+b13*y0=c13; if(a12*b13-a13*b12!=0) { x0=(c12*b13-c13*b12)/(a12*b13-a13*b12); y0=(a12*c13-a13*c12)/(a12*b13-a13*b12); r0=Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0)); } else // consider cercul de diametru [(minx,maxx),(miny,maxy)] { // punctele sunt coliniare ! x0=(max(x1,x2,x3)+min(x1,x2,x3))/2; y0=(max(y1,y2,y3)+min(y1,y2,y3))/2; r0=d(x0,y0,x1,y1)/2; } }// cercCircumscris(...) static void cercDeDiametru(int x1,int y1,int x2,int y2) { x0=((double)x1+x2)/2; y0=((double)y1+y2)/2; r0=d(x1,y1,x2,y2)/2; }// cercDeDiametru(...) static int min(int a,int b) { if(a<b) return a; else return b; } static int max(int a,int b) { if(a>b) return a; else return b; } static int min(int a,int b,int c) { return min(min(a,b),min(a,c)); } static int max(int a,int b,int c) { return max(min(a,b),max(a,c)); }

482

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

static double d(int x1, int y1, int x2, int y2) { double dx,dy; dx=x2-x1; dy=y2-y1; return Math.sqrt(dx*dx+dy*dy); } static double d(double x1, double y1, double x2, double y2) { double dx,dy; dx=x2-x1; dy=y2-y1; return Math.sqrt(dx*dx+dy*dy); } //interschimb punctele i si j static void swap(int i, int j) { int aux; aux=x[i]; x[i]=x[j]; x[j]=aux; aux=y[i]; y[i]=y[j]; y[j]=aux; }// swap(...) }// class

18.9.3

Moia lui Pcal - OJI2004 clasa a XI-a s a a

Pcal a primit, aa cum era a a s nvoiala, un petec de teren de pe moia boierului. s Terenul este mprejmuit complet cu segmente drepte de gard ce se sprijin la a ambele capete de cte un par zdravn. La o nou prinsoare, Pcal iese iar a a a a a n ctig i primete dreptul s strmute nite pari, unul cte unul, cum i-o voia, as s s a a s a astfel at s-i extind suprafata de teren. Dar nc a s a nvoiala prevede c ecare par a poate mutat orice directie, dar nu pe o distant mai mare dect o valoare n a a dat (scris pe ecare par) i ecare segment de gard, ind cam uubred, poate a a s s rotit i prelungit de la un singur capt, cellalt rmnnd nemicat. s a a a a a s Cunoscnd pozitiile initiale ale parilor i valoarea s nscris pe ecare par, se a cere suprafata maxim cu care poate s-i extind Pcal proprietatea. Se tie c a as a a a s a parii sunt dati ntr-o ordine oarecare, pozitiile lor initiale sunt date prin numere ntregi de cel mult 3 cifre, distantele pe care ecare par poate deplasat sunt numere naturale strict pozitive i gura format de terenul initial este un poligon s a neconcav. Date de intrare

18.9. PROBLEME REZOLVATE

483

Fiierul MOSIA.IN contine n + 1 linii cu urmtoarele valori: s a n - numrul de pari a x1 y1 d1 - coordonatele initiale i distanta pe care poate mutat parul 1 s x2 y2 d2 - coordonatele initiale i distanta pe care poate mutat parul 2 s ... xn yn dn - coordonatele initiale i distanta pe care poate mutat parul n s Date de ieire s ierul MOSIA.OUT se scrie un numr real cu 4 zecimale ce reprezint In s a a suprafata maxim cu care se poate mri moia. a a s Restrictii i observatii: s 3 < N 200 numr natural a 1000 < xi , yi < 1000 numere ntregi 0 < di 20 numere ntregi poligonul neconcav se denete ca un poligon convex cu unele vrfuri coliniare s a pozitiile parilor sunt date ntr-o ordine oarecare poligonul obtinut dup mutarea parilor poate concav a pozitiile nale ale parilor nu sunt mod obligatoriu numere naturale n Exemplu Pentru ierul de intrare s 4 -3 0 2 3 0 3 0 6 2 0 -6 6 se va scrie ierul de ieire valoarea 30.0000 n s s Explicatie: prin mutarea parilor 1 i 2 cu cte 2 i respectiv 3 uniti, se s a s at obtine un teren avnd suprafata cu 30 de uniti mai mare dect terenul initial. a at a Timp limit de executare: 1 sec./test a

18.9.4

Partitie - ONI2006 baraj

Ionic a primit de ziua lui de la tatl su un joc format din piese de form a a a a triunghiular de dimensiuni diferite i o suprafatu a magnetic pe care acestea pot a s a aezate. s Pe suprafata magnetic este desenat un triunghi dreptunghic cu lungimile a catetelor a, respectiv b i un sistem de coordonate xOy cu originea unghiul s n drept al triunghiului, semiaxa [Ox pe cateta de lungime a, respectiv semiaxa [Oy pe cateta de lungime b. La un moment dat Ionic aeaz pe tabla magnetic n piese, pentru care se a s a a cunosc coordonatele vrfurilor lor. Tatl lui Ionic vrea s verice dac pe tabl a a a a a a piesele realizeaz o partitie a triunghiului dreptunghic desenat, adic dac sunt a a a ndeplinite conditiile: nu exist piese suprapuse; a

484

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA piesele acoper toat portiunea desenat ( form de triunghi dreptunghic); a a a n a nu exist portiuni din piese afara triunghiului desenat. a n

Cerint a Se cere s se verice dac piesele plasate pe tabla magnetic formeaz o a a a a partitie a triunghiului desenat pe tabla magnetic. a Date de intrare Fiierul de intrare part.in contine pe prima linie un numr natural k, reprezens a tnd numrul de seturi de date din ier. Urmeaz k grupe de linii, cte o grup a a s a a a pentru ecare set de date. Grupa de linii corespunztoare unui set este format a a dintr-o linie cu numerele a, b, n separate ntre ele prin cte un spatiu i n linii cu a s cte ase numere a s ntregi separate prin spatii reprezentnd coordonatele vrfurilor a a (abscis ordonat) celor n piese, cte o pies pe o linie. a a a a Date de ieire s ierul part.out se vor scrie k linii, cte o linie pentru ecare set de date. In s a Pe linia i (i = 1, 2, ..., k) se va scrie 1 dac triunghiurile din setul de date i formeaz a a o partitie a triunghiului desenat pe tabla magnetic sau 0 caz contrar. a n Restrictii i precizri s a 1 n 150 1 k 10 a, b sunt numere ntregi din intervalul [0, 31000] Coordonatele vrfurilor pieselor sunt numere ntregi din intervalul [0, 31000]. Exemplu part.in 2 20 10 4 0 5 0 10 10 5 0 0 10 5 0 5 0 0 10 0 10 5 10 0 20 0 10 5 20 10 2 0 0 0 10 10 5 0 0 20 0 20 10 part.out 1 0

Timp maxim de executie: 0.3 secunde/test Prelucrare Java dup rezolvarea C a autorului problemei n a n import java.io.*; class part { static final int ON_EDGE=0; static final int INSIDE=1;

18.9. PROBLEME REZOLVATE


y 10 T1 5 T2 T3 a) 10 T4 20 x b) 10 5 T1 T2 20 x 10 y

485

Figura 18.2: a) pentru setul 1 de date i b) pentru setul 2 de date s

static final int OUTSIDE=2; static final int N_MAX=512; static int N, A, B; static int[][] X=new int[N_MAX][3]; static int[][] Y=new int[N_MAX][3]; static int sgn(int x) { return x>0 ? 1 : (x<0 ? -1 : 0); } static int point_sign (int x1, int y1, int x2, int y2, int _x, int _y) { int a, b, c; a=y2-y1; b=x1-x2; c=y1*x2-x1*y2; return sgn(a*_x+b*_y+c); } static int point_inside (int n, int x, int y) { int i; int[] sgn=new int[3]; for(i=0;i<3;i++) sgn[i]=point_sign(X[n][i],Y[n][i],X[n][(i+1)%3],Y[n][(i+1)%3],x,y); if(sgn[0]*sgn[1]<0 || sgn[0]*sgn[2]<0 || sgn[1]*sgn[2]<0) return OUTSIDE; if(sgn[0]==0 || sgn[1]==0 || sgn[2]==0) return ON_EDGE; return INSIDE; }

486

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

static boolean segment_intersect(int x1,int y1,int x2,int y2, int x3,int y3,int x4,int y4) { int a1,b1,c1,a2,b2,c2; a1=y2-y1; b1=x1-x2; c1=y1*x2-x1*y2; a2=y4-y3; b2=x3-x4; c2=y3*x4-x3*y4; return sgn(a1*x3+b1*y3+c1)*sgn(a1*x4+b1*y4+c1)<0 && sgn(a2*x1+b2*y1+c2)*sgn(a2*x2+b2*y2+c2)<0; } static boolean triangle_intersect (int n1, int n2) { int i,j,x,t1=0,t2=0; for(i=0;i<3;i++) { if((x=point_inside(n2,X[n1][i],Y[n1][i]))==ON_EDGE) t1++; if(x==INSIDE) return true; if((x=point_inside(n1,X[n2][i],Y[n2][i]))==ON_EDGE) t2++; if(x==INSIDE) return true; } if(t1==3 || t2==3) return true; for(i=0;i<3;i++) for(j=0;j<3;j++) if(segment_intersect( X[n1][i],Y[n1][i],X[n1][(i+1)%3],Y[n1][(i+1)%3], X[n2][j],Y[n2][j],X[n2][(j+1)%3],Y[n2][(j+1)%3] )) { return true; } return false; } static int solve() { int i,j,area=0; for(i=0;i<N;i++) { for(j=0;j<3;j++) if(point_inside(N,X[i][j],Y[i][j])==OUTSIDE) return 0; area+=Math.abs((X[i][1]*Y[i][2]-X[i][2]*Y[i][1])(X[i][0]*Y[i][2]-X[i][2]*Y[i][0])+ (X[i][0]*Y[i][1]-X[i][1]*Y[i][0])); } if(area!=A*B) return 0;

18.9. PROBLEME REZOLVATE

487

for(i=0;i<N;i++) for(j=i+1;j<N;j++) if(triangle_intersect(i,j)) return 0; return 1; } public static void main(String[] args) throws IOException { int tests, i, j; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("part.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("part.out"))); st.nextToken(); tests=(int)st.nval; for(; tests-->0;) { st.nextToken(); A=(int)st.nval; st.nextToken(); B=(int)st.nval; st.nextToken(); N=(int)st.nval; for(i=0;i<N;i++) for(j=0;j<3;j++) { st.nextToken(); X[i][j]=(int)st.nval; st.nextToken(); Y[i][j]=(int)st.nval; } X[N][0]=0; Y[N][0]=0; X[N][1]=A; Y[N][1]=0; X[N][2]=0; Y[N][2]=B; out.println(solve()); } out.close(); }// main(...) }// class

18.9.5

Triunghi - ONI2007 cls 9

comuna Triunghi din Romnia sunt n rani codicati prin numerele In a ta

488

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

1, 2, ..., n. Dup anul 1990 a a nceput retrocedarea suprafetelor de pmnt detinute a a nainte de colectivizare. Fiecare ran are un document prin care dovedete c este ta s a proprietar pe o singur suprafat de teren de form triunghiular. Din pcate, doca a a a a umentele dau btaie de cap primarului (care se ocup de retrocedarea suprafetelor a a de pmnt), pentru c sunt portiuni din suprafetele de pmnt care se regsesc pe a a a a a a mai multe documente. aceast comun exist o fntn cu ap, ind posibil ca ea s e revendiIn a a a a a a a a cat de mai multi rani. O suprafat de pmnt este dat prin coordonatele celor a ta a a a a trei colturi, iar fntna este considerat punctiform i dat prin coordonatele a a a a s a punctului. Cerint a S se scrie un program care s determine: a a a) Codurile ranilor care au documente cu suprafete de pmnt ce contin ta a a n interior sau pe frontier fntna. a a a b) Codul ranului ce detine un document cu suprafata de teren, care include ta toate celelalte suprafete. Date de intrare Fiierul de intrare triunghi.in are pe prima linie numrul n de rani, pe s a ta urmtoarele n linii cte 6 valori numere a a ntregi separate prin cte un spatiu, fora n matul: x1 y1 x2 y2 x3 y3, ce reprezint coordonatele celor trei colturi ale suprafetei a triunghiulare detinute de un ran (x1, x2, x3 abscise, iar y1, y2, y3 ordonate). Pe ta linia i + 1 se a coordonatele colturilor suprafetei de teren triunghiulare detinute a de ranul i, i = 1, 2, ..., n. Ultima linie a ierului (linia n + 2) va contine coordota s natele fntnii formatul x y, cu un spatiu a a n ntre ele (x abscis, iar y ordonat). a a Date de ieire s Fiierul de ieire triunghi.out va contine pe prima linie rspunsul de la s s a punctul a), adic: numrul de rani care a a ta ndeplinesc conditia din cerint i apoi a s codurile lor ( ordine cresctoare), cu un spatiu n a ntre ele. Dac nu exist rani a a ta cu conditia din cerint, pe prima linie se va scrie cifra 0. Pe linia a doua se va scrie a rspunsul de la punctul b), adic: codul ranului cu proprietatea cerut, sau cifra a a ta a 0, dac nu exist un astfel de ran. a a ta Restrictii i precizri s a 2 n 65 coordonatele colturilor suprafetelor de pmnt i ale fntnii sunt numere a a s a a ntregi din intervalul [3000, 3000] cele trei colturi ale ecrei suprafete de pmnt sunt distincte i necoliniare a a a s nu exist doi rani care s detin aceeai suprafat de pmnt a ta a a s a a a nu se acord punctaje partiale. a Exemplu

18.9. PROBLEME REZOLVATE secv.in 3 10 0 0 10 10 10 0 100 100 0 -100 0 0 0 10 0 0 10 10 5 secv.out 212 2

489

Explicatie: La punctul a), sunt doi rani care detin suprafete de pmnt ce au interior ta a a n sau pe frontier fntna, cu codurile 1 i 2. a a a s La punctul b), ranul cu codul 2 detine o suprafat de teren care include, ta a suprafetele de pmnt detinute de ceilalti rani (cu codurile 1 i 3). a a ta s Timp maxim de executie/test: 0.1 secunde Indicatii de rezolvare - descriere solutie Descrierea solutiei (Prof. Doru Popescu Anastasiu) Notm cu T1 , T2 , ..., Tn triunghiurile corespunztoare suprafetelor i cu I a a s punctul unde se gsete fntna. a s a a Ti = Ai Bi Ci , i = 1, 2, ..., n. a) nr = 0 Pentru i = 1, ..., n vericm dac I este interior sau pe frontiera lui Ti , caz a a n armativ nr = nr + 1 i sol[nr] = i. Aam nr i vectorul sol. s s s Pentru a verica dac I este interior sau pe frontiera unui triunghi Ti este a sucient s vericm dac: a a a aria(Ai Bi Ci ) = aria(IAi Bi ) + aria(IAi Ci ) + aria(IBi Ci ) O alt variant ar s folosim pozitia unui punct fat de o dreapt. a a a a a b) Dac exist un asemenea triunghi atunci el este de arie maxim. Astfel dea a a terminm triunghiul p de arie maxim. Pentru acest triunghi vericm dac toate a a a a celelalte n 1 triunghiuri sunt interioare sau pe frontiera lui Tp (adic dac au a a toate vrfurile interiorul sau pe frontiera lui Tp ). caz armativ se aeaz p, a n In s a altfel 0. Codul surs a import java.io.*; class Pereti {

490 static static static static static static static static static

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA int n,x0,y0; int smax=-1,imax=-1,nr=0; int[] x1=new int[66]; int[] y1=new int[66]; int[] x2=new int[66]; int[] y2=new int[66]; int[] x3=new int[66]; int[] y3=new int[66]; int[] sol=new int[66];

public static void main(String[] args) throws IOException { int i,j,s,s1,s2,s3; boolean ok; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("14-triunghi.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("triunghi.out"))); st.nextToken(); n=(int)st.nval; for(i=1;i<=n;i++) { st.nextToken(); x1[i]=(int)st.nval; st.nextToken(); y1[i]=(int)st.nval; st.nextToken(); x2[i]=(int)st.nval; st.nextToken(); y2[i]=(int)st.nval; st.nextToken(); x3[i]=(int)st.nval; st.nextToken(); y3[i]=(int)st.nval; } st.nextToken(); x0=(int)st.nval; st.nextToken(); y0=(int)st.nval; for(i=1;i<=n;i++) { s=aria(x1[i],y1[i],x2[i],y2[i],x3[i],y3[i]); if(s>smax) {smax=s; imax=i;} s1=aria(x1[i],y1[i],x2[i],y2[i],x0,y0); s2=aria(x2[i],y2[i],x3[i],y3[i],x0,y0); s3=aria(x1[i],y1[i],x3[i],y3[i],x0,y0); if(s==s1+s2+s3) {nr++; sol[nr]=i;} //System.out.println("i = "+i+" --> "+s+" "+s1+" "+s2+" "+s3); } if(nr>0) { out.print(nr+" ");

18.9. PROBLEME REZOLVATE

491

for(i=1;i<=nr;i++) if(i!=nr) out.print(sol[i]+" "); else out.println(sol[i]); } else out.println(0); //System.out.println("imax = "+imax); ok=true; for(i=1;i<=n;i++) { if(i==imax) continue; s1=aria(x1[imax],y1[imax],x2[imax],y2[imax],x1[i],y1[i]); s2=aria(x2[imax],y2[imax],x3[imax],y3[imax],x1[i],y1[i]); s3=aria(x1[imax],y1[imax],x3[imax],y3[imax],x1[i],y1[i]); if(smax!=s1+s2+s3) { ok=false; break; } s1=aria(x1[imax],y1[imax],x2[imax],y2[imax],x2[i],y2[i]); s2=aria(x2[imax],y2[imax],x3[imax],y3[imax],x2[i],y2[i]); s3=aria(x1[imax],y1[imax],x3[imax],y3[imax],x2[i],y2[i]); if(smax!=s1+s2+s3) { ok=false; break; } s1=aria(x1[imax],y1[imax],x2[imax],y2[imax],x3[i],y3[i]); s2=aria(x2[imax],y2[imax],x3[imax],y3[imax],x3[i],y3[i]); s3=aria(x1[imax],y1[imax],x3[imax],y3[imax],x3[i],y3[i]); if(smax!=s1+s2+s3) { ok=false; break; } } if(ok) out.println(imax); else out.println(0); out.close(); }// main(...) static int aria(int x1, int y1, int x2, int y2, int x3, int y3) // dubla ... { int s=x1*y2+x2*y3+x3*y1-y1*x2-y2*x3-y3*x1; if(s<0) s=-s; return s; } }// class

492

CAPITOLUL 18. GEOMETRIE COMPUTATIONALA

Capitolul 19

Teoria jocurilor

19.1

Jocul NIM

19.1.1

Prezentare general a

19.1.2

Exemple

493

494

CAPITOLUL 19. TEORIA JOCURILOR

Capitolul 20

Alti algoritmi

20.1

Secvent de sum maxim a a a

20.1.1

Prezentare general a

20.1.2

Exemple

20.2

Algoritmul Belmann-Ford

Pentru grafuri cu costuri negative (dar fara cicluri negative!) se poate folosi algoritmul Bellman-Ford.

20.2.1

Algoritmul Belmann-Ford pentru grafuri neorientate

// drum scurt in graf neorientat cu costuri negative (dar fara ciclu negativ!) // Algoritm: 1. init // 2. repeta de n-1 ori // pentru fiecare arc (u,v) // relax(u,v) // 3. OK=true 495

496 // // // //

CAPITOLUL 20. ALTI ALGORITMI 4. pentru fiecare muchie (u,v) daca d[v]>d[u]+w[u][v] OK=false 5. return OK

import java.io.*; class BellmanFord { static final int oo=0x7fffffff; // infinit static int n,m; // varfuri, muchii static int[][] w; static int[] d; static int[] p; // matricea costurilor // d[u]=dist(nods,u) // p[u]=predecesorul nodului u

public static void main(String[] args) throws IOException { int i,j,k; int nods=1, cost; // nod sursa, costul arcului int u,v; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("BellmanFordNeorientat.in"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; w=new int[n+1][n+1]; d=new int[n+1]; p=new int[n+1]; for(i=1;i<=n;i++) for(j=1;j<=n;j++) w[i][j]=oo;// initializare ! for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost=(int)st.nval; w[i][j]=cost; w[j][i]=cost; // numai pentru graf neorientat } init(nods);

20.2. ALGORITMUL BELMANN-FORD

497

for(k=1;k<=n-1;k++) for(u=1;u<n;u++) for(v=u+1;v<=n;v++) if(w[u][v]<oo) relax(u,v);

// // // //

de n-1 ori !!! vectorii muchiilor erau mai buni ! lista de adiacenta era mai buna ! (u,v)=muchie si u<v

boolean cicluNegativ=false; for(u=1;u<n;u++) for(v=u+1;v<=n;v++) if(w[u][v]<oo) // (u,v)=muchie if(d[u]<oo) // atentie !!! oo+ceva=??? if(d[v]>d[u]+w[u][v]) { cicluNegativ=true; break; } if(!cicluNegativ) for(k=1;k<=n;k++) { System.out.print(nods+"-->"+k+" dist="+d[k]+" drum: "); drum(k); System.out.println(); } }//main static void init(int s) { int u; for(u=1;u<=n;u++) { d[u]=oo; d[s]=0; }// init(...)

p[u]=-1;

static void relax(int u,int v) // (u,v)=arc(u-->v) { if(d[u]<oo) // oo+ceva ==> ??? if(d[u]+w[u][v]<d[v]) { d[v]=d[u]+w[u][v]; p[v]=u; } }

498

CAPITOLUL 20. ALTI ALGORITMI

static void drum(int k) // s --> ... --> k { if(p[k]!=-1) drum(p[k]); System.out.print(k+" "); } static void afisv(int[] x) { int i; for(i=1;i<=n;i++) System.out.print(x[i]+" "); System.out.println(); } }//class /* 6 7 1 2 1 3 2 3 3 4 4 5 5 6 4 6 */

-3 1 2 1 1 -3 2

1-->1 1-->2 1-->3 1-->4 1-->5 1-->6

dist=0 drum: 1 dist=-3 drum: 1 2 dist=-1 drum: 1 2 3 dist=0 drum: 1 2 3 4 dist=1 drum: 1 2 3 4 5 dist=-2 drum: 1 2 3 4 5 6

20.2.2

Alg Belmann-Ford pentru grafuri orientate

// drumuri scurte in graf orientat cu costuri negative (dar fara ciclu negativ!) // Dijkstra nu functioneaza daca apar costuri negative ! // Algoritm: 1. init // 2. repeta de n-1 ori // pentru fiecare arc (u,v) // relax(u,v) // 3. OK=true // 4. pentru fiecare arc (u,v) // daca d[v]>d[u]+w[u][v] // OK=false // 5. return OK import java.io.*; class BellmanFord { static final int oo=0x7fffffff; static int n,m; // varfuri, muchii

20.2. ALGORITMUL BELMANN-FORD static int[][] w; // matricea costurilor static int[] d; // d[u]=dist(nods,u) static int[] p; // p[u]=predecesorul nodului u

499

public static void main(String[] args) throws IOException { int i,j,k; int nods=1, cost; // nod sursa, costul arcului int u,v; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("BellmanFordNeorientat.in"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; w=new int[n+1][n+1]; d=new int[n+1]; p=new int[n+1]; for(i=1;i<=n;i++) for(j=1;j<=n;j++) w[i][j]=oo; // initializare ! for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost=(int)st.nval; w[i][j]=cost; } init(nods); for(k=1;k<=n-1;k++) for(u=1;u<=n;u++) for(v=1;v<=n;v++) if(w[u][v]<oo) relax(u,v);

// // // //

de n-1 ori !!! vectorii arcelor erau mai buni ! lista de adiacenta era mai buna ! (u,v)=arc

boolean cicluNegativ=false; for(u=1;u<=n;u++) for(v=1;v<=n;v++) if(w[u][v]<oo) // (u,v)=arc if(d[u]<oo) // atentie !!! oo+ceva=??? if(d[v]>d[u]+w[u][v]) { cicluNegativ=true; break; } System.out.println(cicluNegativ);

500

CAPITOLUL 20. ALTI ALGORITMI

if(!cicluNegativ) for(k=1;k<=n;k++) { System.out.print(nods+"-->"+k+" dist="+d[k]+" drum: "); if(d[k]<oo) drum(k); else System.out.print("Nu exista drum!"); System.out.println(); } }//main static void init(int s) { int u; for(u=1;u<=n;u++) { d[u]=oo; p[u]=-1; } d[s]=0; }// init() static void relax(int u,int v) // (u,v)=arc(u-->v) { if(d[u]<oo) // oo+ceva ==> ??? if(d[u]+w[u][v]<d[v]) { d[v]=d[u]+w[u][v]; p[v]=u; } }// relax(...) static void drum(int k) // s --> ... --> k { if(p[k]!=-1) drum(p[k]); System.out.print(k+" "); }// drum(...) static void afisv(int[] x) { int i; for(i=1;i<=n;i++) System.out.print(x[i]+" "); System.out.println(); } }//class /* 6 8 1 2 -3 1 3 1

false 1-->1 dist=0 drum: 1 1-->2 dist=-4 drum: 1 3 2

20.2. ALGORITMUL BELMANN-FORD 2 3 3 4 5 4 5 6 4 6 3 2 */ 6 1 1 -3 2 -5 1-->3 1-->4 1-->5 1-->6 dist=1 drum: 1 3 dist=2 drum: 1 3 4 dist=2147483647 drum: Nu exista drum! dist=4 drum: 1 3 4 6

501

20.2.3
// // // // // // // //

Alg Belmann-Ford pentru grafuri orientate aciclice

Cele mai scurte drumuri in digraf (graf orientat ACICLIC) Dijkstra nu functioneaza daca apar costuri negative ! Algoritm: 1. sortare topologica O(n+m) 2. init(G,w,s) 3. pentru toate nodurile u in ordine topologica pentru toate nodurile v adiacente lui u relax(u,v) OBS: O(n+m)

import java.io.*; class BellmanFordDAG { static final int oo=0x7fffffff; static final int WHITE=0, BLACK=1; // color[u]=BLACK ==> u in lista static int n,m,t,pozl; // varfuri, muchii, time, pozitie in lista static int[] color; // culoare static int[] lista; // lista static int[] gi; // grad interior static int[][] w; // matricea costurilor static int[] d; // d[u]=dist(nods,u) static int[] p; // p[u]=predecesorul nodului u public static void main(String[] args) throws IOException { int i,j,k; int nods=1, cost; // nod sursa, costul arcului int u,v; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("BellmanFordDAG.in"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; w=new int[n+1][n+1]; color=new int[n+1];

502 lista=new int[n+1]; gi=new int[n+1]; d=new int[n+1]; p=new int[n+1];

CAPITOLUL 20. ALTI ALGORITMI

for(i=1;i<=n;i++) for(j=1;j<=n;j++) w[i][j]=oo; // initializare ! for(k=1;k<=m;k++) { st.nextToken(); i=(int)st.nval; st.nextToken(); j=(int)st.nval; st.nextToken(); cost=(int)st.nval; w[i][j]=cost; gi[j]++; } topoSort(); System.out.print("Lista init(nods); for(i=1;i<=n;i++) { u=lista[i]; for(v=1;v<=n;v++) if(w[u][v]<oo) relax(u,v); }

: "); afisv(lista);

// lista de adiacenta era mai buna !

System.out.print("Distante : "); afisv(d); for(k=1;k<=n;k++) { if(d[k]<oo) System.out.print(k+" : "+d[k]+" ... "); else System.out.print(k+": oo ... "); drum(k); System.out.println(); } }//main static void init(int s) { int u;

20.2. ALGORITMUL BELMANN-FORD for(u=1;u<=n;u++) { d[u]=oo; p[u]=-1; } d[s]=0; }// init(...) static void relax(int u,int v) // (u,v)=arc(u-->v) { if(d[u]<oo) // oo+ceva ==> ??? if(d[u]+w[u][v]<d[v]) { d[v]=d[u]+w[u][v]; p[v]=u; } }// relax(...) static void drum(int k) // s --> ... --> k { if(p[k]!=-1) drum(p[k]); if(d[k]<oo) System.out.print(k+" "); }

503

static void topoSort() { int u,i,k,pozl; for(i=1;i<=n;i++) // oricum era initializat implicit, dar ... !!! color[i]=WHITE; pozl=1; for(k=1;k<=n;k++) // pun cate un nod in lista { u=nodgi0(); color[u]=BLACK; micsorezGrade(u); lista[pozl++]=u; } }// topoSort() static int nodgi0() // nod cu gradul interior zero { int v,nod=-1; for(v=1;v<=n;v++) // coada cu prioritati (heap) este mai buna !!! if(color[v]==WHITE) if(gi[v]==0) {nod=v; break;} return nod; }// nodgi0()

504

CAPITOLUL 20. ALTI ALGORITMI

static void micsorezGrade(int u) { int v; for(v=1;v<=n;v++) // lista de adiacenta este mai buna !!! if(color[v]==WHITE) if(w[u][v]<oo) gi[v]--; }// micsorezGrade(...) static void afisv(int[] x) { int i; for(i=1;i<=n;i++) if(x[i]<oo) System.out.print(x[i]+" "); else System.out.print("oo "); System.out.println(); }// afisv(...) }//class /* 6 7 1 2 1 3 3 4 5 4 5 6 4 6 3 2 */

-3 1 1 1 -3 2 -5

Lista : 1 3 2 5 4 6 Distante : 0 -4 1 2 oo 4 1 : 0 ... 1 2 : -4 ... 1 3 2 3 : 1 ... 1 3 4 : 2 ... 1 3 4 5: oo ... 6 : 4 ... 1 3 4 6

Bibliograe
[1] Aho, A.; Hopcroft, J.; Ullman, J.D.; Data strutures and algorithms, Addison Wesley, 1983 [2] Aho, A.; Hopcroft, J.; Ullman, J.D.; The Random Access Machine, 1974 [3] Andonie R., Grbacea I.; Algoritmi fundamentali, o perspectiv C++, Ed. a a Libris, 1995 [4] Apostol C., Roca I. Gh., Roca V., Ghilic-Micu B., Introducere progras s n mare. Teorie i aplicatii, Editura ... Bucureti, 1993 s s [5] Atanasiu, A.; Concursuri de informatic. Editura Petrion, 1995 a [6] Atanasiu, A.; Ordinul de complexitate al unui algoritm. Gazeta de Informatic a nr.1/1993 [7] - Bell D., Perr M.: Java for Students, Second Edition, Prentice Hall, 1999 [8] Calude C.; Teoria algoritmilor, Ed. Universitii Bucureti, 1987 at s [9] Cerchez, E.; Informatic - Culegere de probleme pentru liceu, Ed. Polirom, a Iai, 2002 s [10] Cerchez, E., Serban, M.; Informatic - manual pentru clasa a X-a., Ed. a Polirom, Iai, 2000 s [11] Cori, R.; Lvy, J.J.; Algorithmes et Programmation, Polycopi, version 1.6; e e http://w3.edu.polytechnique.fr/informatique/ [12] Cormen, T.H., Leiserson C.E., Rivest, R.L.; Introducere Algoritmi, Ed n Agora, 2000 [13] Cormen, T.H., Leiserson C.E., Rivest, R.L.; Pseudo-Code Language, 1994 [14] Cristea, V.; Giumale, C.; Kalisz, E.; Paunoiu, Al.; Limbajul C standard, Ed. Teora, Bucureti, 1992 s [15] Erickson J.; Combinatorial Algorithms; http://www.uiuc.edu/~jeffe/ 505

506 [16] Flanagan, D.; Java in a Nutshell, OReilly, 1997. [17] Flanagan, D.; Java examples in a Nutshell, OReilly, 1997.

BIBLIOGRAFIE

[18] Giumale, C.; Introducere Analiza Algoritmilor, Ed.Polirom, 2004 n [19] Giumale C., Negreanu L., Clinoiu S.; Proiectarea i analiza algoritmilor. a s Algoritmi de sortare, Ed. All, 1997 [20] Gosling, J.; Joy, B.; Steele, G.; The Java Language Specication, Addison Wesley, 1996. [21] Knuth, D.E.; Arta programrii calculatoarelor, vol. 1: Algoritmi fundamentali, a Ed. Teora, 1999. [22] Knuth, D.E.; Arta programarii calculatoarelor, vol. 2: Algoritmi seminumerici, Ed. Teora, 2000. [23] Knuth, D.E.; Arta programarii calculatoarelor, vol. 3: Sortare i cutare, Ed. s a Teora, 2001. [24] Lambert,K. A., Osborne,M.; Java. A Framework for Programming and Problem Solving, PWS Publishing, 1999 [25] Livovschi, L.; Georgescu H.; Analiza i sinteza algoritmilor. Ed. Enciclopedic, s a Bucureti, 1986. s [26] Niemeyer, P.; Peck J.; Exploring Java, OReilly, 1997. [27] Odgescu, I.; Smeureanu, I.; Stefnescu, I.; Programarea avansat a calculaa a a toarelor personale, Ed. Militar, Bucureti 1993 a s [28] Odgescu, I.; Metode i tehnici de programare, Ed. Computer Lobris Agora, a s Cluj, 1998 [29] Popescu Anastasiu, D.; Puncte de articulatie i punti grafuri, Gazeta de s n Informatic nr. 5/1993 a [30] Rotariu E.; Limbajul Java, Computer Press Agora, Tg. Mures, 1996 [31] Tomescu, I.; Probleme de combinatoric i teoria grafurilor, Editura Didactic as a i Pedagogic, Bucureti, 1981 s a s [32] Tomescu, I.; Leu, A.; Matematic aplicat tehnica de calcul, Editura Dia a n dactic i Pedagogic, Bucureti, 1982 as a s [33] Vaduva, C.M.; Programarea in JAVA. Microinformatica, 1999 [34] iinescu, R.;; Viinescu, V.; Programare dinamic - teorie i aplicatii; GInfo nr. a s 15/4 2005

BIBLIOGRAFIE

507

[35] Vlada, M.; Conceptul de algoritm - abordare modern, GInfo, 13/2,3 2003 a [36] Vlada, M.; Grafuri neorientate i aplicatii. Gazeta de Informatic, 1993 s a [37] Weis, M.A.; Data structures and Algorithm Analysis, Ed. The Benjamin/Cummings Publishing Company. Inc., Redwoods City, California, 1995. [38] Winston, P.H., Narasimhan, S.: On to JAVA, Addison-Wesley, 1996 [39] Wirth N., Algorithms + Data Structures = Programs, Prentice Hall, Inc 1976 [40] *** - Gazeta de Informatic, Editura Libris, 1991-2005 a