Sunteți pe pagina 1din 180

Culegere de probleme de informatic

- contine rezolvari si implementarile lor in limbajul Pascal Bogdan Batog Ctlin Drul

Aceasta este versiunea electronica a cartii "Tehnici de programare - Laborator clasa a X-a". Cartea contine probleme de informatica si solutiile lor impreuna cu implementari in limbajul Pascal. Se adreseaza tuturor elevilor, dar si celor cu un interes mai special pentru informatica. Astfel, sunt tratate cateva subiecte mai avansate si sunt prezentate si probleme intalnite la diferite concursuri.

Cuprins
Prefa
Capitolul 1

Backtracking
Capitolul 2

Recursivitate
Capitolul 3

Backtracking recursiv
Capitolul 4

Analiza timpului de calcul necesar algoritmilor


Capitolul 5

Divide et Impera
Capitolul 6

Structuri de date
Capitolul 7

Tehnica Greedy
Capitolul 8

Programare dinamic

Capitolul 9

Branch and Bound


Capitolul 10

Teoria grafurilor Bibliografie

Culegere de probleme de informatic


Bogdan Batog Ctlin Drul

Prefata
la varianta electronica

O scurta istorie a acestei carti ar fi cam asa. In vara anului 1998, domnul profesor Tudor Sorin ne-a propus sa scriem un manual de laborator pentru cursul de informatica din clasa a X-a. La vremea respectiva, eram amandoi in vacanta de vara dinaintea clasei a XII-a. Pentru ca ni s-a parut un proiect accesibil, si probabil pentru ca eram dornici sa ne afirmam, ne-am apucat cu entuziasm sa scriem. Scopul acestei carti a fost initial sa fie o culegere de probleme, care sa-i ajute atat pe elevi cat si pe profesori, ca sursa de exercitii suplimentare. Acesta a si devenit in mare continutul cartii. Contine probleme structurate in functie de tehnica de programare folosita in solutiile lor. Fiecare problema este urmata de solutie si de implementarea acesteia in limbajul Pascal. Ca o parenteza, limbajul acesta ne este drag amandorura, pentru ca ne aduce aminte cu un pic de nostalgie de olimpiadele de informatica la care am luat parte in liceu. In plus, il consideram si un limbaj foarte potrivit pentru invatarea programarii si a algoritmicii. La acest proiect initial al cartii, noi am adaugat cateva subiecte avansate, pentru elevii care au un interes mai special in algoritmica sau pentru cei care se pregatesc pentru olimpiadele de informatica. Culegerea a aparut in doua editii tiparite care intre timp s-au epuizat. Recent, ne-am hotarat sa continuam experimentul inceput de domnul profesor Tudor Sorin, si ii spun experiment, pentru ca dumnealui a dat dovada de multa deschidere incredintand acest proiect unor liceeni. Asadar, ne-am hotarat sa facem publica pe Internet aceasta carte cu speranta ca va fi de ajutor elevilor pasionati de algoritmi si programare. De aceea, va rugam sa ne trimteti prin e-mail comentariile voastre. 3 martie 2002 Bogdan Batog, bbbb@ss.pub.ro Catalin Drula, catalin.drula@utoronto.ca

Multumiri

Multumirile noastre merg in primul rand catre domnul profesor Tudor Sorin pentru ca ne-a dat sansa sa scriem aceasta carte. Si as adauga eu (Catalin): pentru ca a scris cartile dupa care am invatat primul limbaj de programare si primii algoritmi.

Celelalte multumiri sunt cele din prefata originala a manualului pe care le reproducem aici: domnilor profesori Cristian Francu si Catalin Francu (pentru pregatirea efectuata in particular), Mihai Boicu, Dan Grigoriu, Daniela Oprescu si Rodica Pintea.

Capitolul 1 Backtracking

Problema 1
Enun. Avem la dispoziie 6 culori: alb, galben, rou, verde, albastru i negru. S se precizeze toate drapelele tricolore care se pot proiecta, tiind c trebuie respectate urmtoarele reguli:
q

orice drapel are culoarea din mijloc galben sau verde; cele trei culori de pe drapel sunt distincte.

Exemple: "alb galben rou", "rou verde galben" Rezolvare. Folosim o stiv cu 3 nivele, reprezentnd cele trei culori de pe drapel i codificm culorile prin numere: 1 2 3 4 5 6 alb galben rou verde albastru negru

Folosim un vector auxiliar fol de tip boolean: fol[i]= TRUE, dac culoarea i a fost folosit n drapel deja; FALSE, dac nu a fost folosit.

Condiiile care trebuie ndeplinite pentru aezarea unei anumite culori c pe un nivel al stivei sunt:
q

fol[c]=FALSE, adic s nu fi folosit deja culoarea respectiv n drapel; dac nivelul stivei este 2 (adic culoarea din mijloc), atunci culoarea

trebuie s fie 2 sau 4 (galben sau verde).

program Drapel; const Culoare:array [1..6] of string[10]=('alb','galben', 'rosu','verde', 'albastru','negru'); var ST Fol k, i :array [1..3] of integer; :array [1..6] of boolean; :integer;

procedure Printsol; var i:integer; begin for i:=1 to 3 do write(Culoare[ST[i]],' '); writeln end; function Valid:boolean; begin if Fol[ST[k]] then Valid:=false else if k<>2 then Valid:=true else Valid:=(ST[k] in [2,4]) end; begin for i:=1 to 6 do Fol[i]:=false; k:=1; ST[k]:=0; while k>0 do begin repeat ST[k]:=ST[k]+1 until (ST[k]>6) or Valid; if ST[k]<=6 then if k=3 then Printsol else begin Fol[ST[k]]:=true; k:=k+1; ST[k]:=0 end else begin

k:=k-1; if k>0 then Fol[ST[k]]:=false end end end.

Problema 2
Enun. S se descompun un numr natural N, n toate modurile posibile, ca sum de P numere naturale (PN). Rezolvare. Folosim o stiv cu P nivele, unde fiecare nivel ia valoarea unui termen din sum. Variabila S reine suma primelor k nivele pentru a putea testa validitatea elementului aezat pe poziia curent fr a mai face o parcurgere suplimentar a stivei. Condiia de validitate devine: S+ST[k]N. Pentru a evita afiarea descompunerilor identice, cu ordinea termenilor schimbat, form ordinea cresctoare (nu strict cresctoare!) a termenilor din sum, prin iniializarea unui nivel din stiv cu valoarea nivelului anterior.

program Descompuneri; var n, p, k, S, i ST :integer; :array [0..1000] of integer;

Procedure Printsol; var i:integer; begin for i:=1 to p do write(ST[i],' '); writeln end; begin write('N= '); readln(n); write('P= '); readln(p); S:=0; k:=1; fillchar(ST,sizeof(ST),0); while k>0 do begin ST[k]:=ST[k]+1;

if S+ST[k]<=N then if (k=p) and (S+ST[k]=N) then Printsol else begin S:=S+ST[k]; k:=k+1; ST[k]:=ST[k-1]-1 end else begin k:=k-1; S:=S-ST[k] end end end.

Problema 3
Enun. Dintr-un grup de N persoane, dintre care P femei, trebuie format o delegaie de C persoane, dintre care L femei. S se precizeze toate delegaiile care se pot forma. Rezolvare. Vom nota femeile cu numerele de la 1 la P, i brbaii de la P+1 la N. Pentru rezolvarea problemei folosim o stiv cu C nivele, unde nivelele 1..L conin indicii femeilor din delegaia curent, i nivelele L+1..C indicii brbailor. st[i] poate lua valori ntre st[i-1] i MAX, unde: MAX=p, pentru i<=l; MAX=n, pentru i>l. st[l] poate lua valori ntre p i n. Motivul pentru care st[i] se iniializeaz cu st[i-1] este evitarea duplicrii delegaiilor, prin forarea ordinii strict cresctoare n fiecare delegaie. Limita pn la care se pot majora elementele de pe un nivel este P pentru nivelele 1..L (femeile) i N pentru nivelele L+1..C. De aceea, la urcarea i coborrea pe nivelul L, variabila MAX devine N, respectiv P.

st[l] nu se iniializeaz cu st[l-1], ci cu P, pentru c este primul dintre brbai i brbaii au indicii ntre P+1 i N.

program Delegatie; var st MAX, k, N, P, C, L :array [1..100] of integer; :integer;

procedure Printsol; var i:integer; begin for i:=1 to C do write(st[i],' '); writeln end; begin write('N, P, C, L: '); readln(N,P,C,L); k:=1; st[1]:=0; MAX:=P; while k>0 do begin st[k]:=st[k]+1; if st[k]<=MAX then if k=C then Printsol else begin k:=k+1; if k<>L+1 then st[k]:=st[k-1] else begin st[k]:=P; MAX:=N end end else begin k:=k-1; if k=l then MAX:=P end end end.

Problema 4
Enun. O persoan are la dispoziie un rucsac cu o capacitate de G uniti de greutate i intenioneaz s efectueze un transport n urma cruia s obin un ctig. Persoanei i se pun la dispoziie N obiecte. Pentru fiecare obiect se cunoate greutatea (mai mic dect capacitatea rucsacului) i ctigul obinut n urma transportului su. Ce obiecte trebuie s aleag persoana pentru a-i maximiza ctigul i care este acesta? Se va avea n vedere i faptul ca pentru acelai ctig persoana s transporte o greutate mai mic. Datele de intrare se vor citi din fiierul input.txt. Pe primele dou linii ale fiierului sunt N i G, numrul de obiecte i capacitatea rucsacului. Pe fiecare din urmtoarele N linii se afl dou numere ntregi reprezentnd greutatea i ctigul asociat obiectului respectiv. Rezolvare. n vectorii gr i c avem greutatea i ctigul obinut n urma transportrii fiecrui obiect. CG este ctigul obinut n urma transportrii obiectelor aezate n stiv pn la nivelul curent i S, greutatea acestor obiecte. Condiiile care trebuie ndeplinite pentru aezarea unui obiect i pe nivelul k al stivei sunt:

1. Pus[i]=FALSE, adic s nu fi aezat deja obiectul respectiv n stiv i 2. S+gr[ST[k]]<=G, adic greutatea obiectelor s fie mai mic sau cel mult egal cu greutatea
maxim care poate fi transportat cu rucsacul. n variabilele Cmax, Gmax i STmax (ctigul, greutatea i obiectele) reinem pe timpul execuiei programului cea mai bun soluie gsit. Not: Rezolvarea optim a acestei probleme poate fi gsit n manualul Tehnici de programare de Tudor Sorin la capitolul Programare dinamic.

program Rucsac; var i,k,n,G,CG,S,Cmax,Gmax,NOb :integer; c,gr,ST,STmax :array [1..100] of integer; Pus :array [1..100] of boolean; procedure Citire; var f :text; i :integer; begin assign(f,'input.txt'); reset(f); readln(f,N);

readln(f,G); for i:=1 to n do readln(f,gr[i],c[i]); close(f) end; function Valid:boolean; begin if Pus[ST[k]] then Valid:=false else if S+gr[ST[k]]<=G then begin S:=S+gr[ST[k]]; CG:=CG+c[ST[k]]; Valid:=true end else Valid:=false end; begin Citire; k:=1; for i:=1 to n do Pus[i]:=false; ST[k]:=0; CG:=0; S:=0; Cmax:=0; while k>0 do begin repeat ST[k]:=ST[k]+1 until (ST[k]>n) or Valid; if ST[k]<=n then begin if CG>Cmax then begin Cmax:=CG; NOb:=k; for i:=1 to k do STmax[i]:=ST[i]; Gmax:=S; end; Pus[ST[k]]:=true; k:=k+1; ST[k]:=0 end else begin k:=k-1; if k>0 then

begin S:=S-gr[ST[k]]; CG:=CG-c[ST[k]]; Pus[ST[k]]:=false end end end; writeln('Cistigul maxim: ',Cmax); writeln('Greutate transportata: ',Gmax); write('Obiectele transportate: '); for i:=1 to NOb do write(STmax[i],' '); writeln end.

Problema 5
Enun. Fiind dat un numr natural N, se cere s se afieze toate descom-punerile sale ca sum de numere prime. Rezolvare. Pentru eficien, vom calcula mai nti toate numerele prime mai mici ca N; acestea vor fi memorate n vectorul Numar. Rutina de backtracking este similar cu cea din problema 2, cu excepia faptului c termenii pot lua valori din Numar[1..P] i c descompunerea nu trebuie s aib un numr fix de termeni.

Program SumaPrime; var ST, Numar n, K, P, S, i :array[0..1000] of integer; :integer;

Function Prim( X : integer):boolean; var i:integer; begin Prim:=true; i:=2; while i*i<=X do begin if X mod i=0 then begin Prim:=false; i:=X

end; i:=i+1 end end; procedure Printsol; var i:integer; begin for i:=1 to k do write(Numar[ST[i]],' '); writeln end; begin fillchar(ST,sizeof(ST),0); fillchar(Numar,sizeof(Numar),0); write('N='); readln(N); P:=0; for i:=2 to N do if Prim(i) then begin inc(P); Numar[P]:=i end; k:=1; S:=0; while k>0 do begin ST[k]:=ST[k]+1; if (ST[k]<=P) and (S+Numar[St[k]]<=N) then if S+Numar[St[k]]=N then Printsol else begin S:=S+Numar[ST[k]]; k:=k+1; ST[k]:=ST[k-1]-1 end else begin k:=k-1; S:=S-Numar[St[k]] end end end.

Problema 6

Enun. ("Attila i regele") Un cal i un rege se afl pe o tabl de ah. Unele cmpuri ale tablei sunt "arse", poziiile lor fiind cunoscute. Calul nu poate clca pe cmpuri "arse", iar orice micare a calului face ca respectivul cmp s devin "ars". S se afle dac exist o succesiune de mutri permise (cu restriciile de mai sus), prin care calul s poat ajunge la rege i s revin la poziia iniial. Poziia iniial a calului, precum i poziia regelui sunt considerate "nearse". Rezolvare. Vom folosi o stiv tripl cu elemente de tip pozitie: l,c - linia i coloana poziiei curente, mut - mutarea fcut din aceast poziie. Vectorul Mutari (constant) conine cele 8 mutri pe care le poate efectua un cal (modificrile coordonatelor calului prin mutarea respectiv; vezi i problema 4). Pentru nceput vom citi datele de intrare cu procedura Citire. Fiierul de intrare input.txt trebuie s aib forma: N // pe prima linie, n - numrul de linii (coloane) al tablei lCal cCal // pe urmtoarele dou linii, poziia iniial a lRege cRege // calului i a regelui a11 a12 a13 .. a1n // pe urmtoarele n linii, tabla de ah .... // aij = 0, dac ptrelul respectiv e ars an1 an2 an3 .. ann // = 1, dac nu este ars

Vom face o "bordare" a tablei de ah cu dou rnduri de ptrele "arse". De exemplu dac tabla este 3x3 i notm cu 0 ptrelele arse i cu 1 pe cele nearse: 0000000 0000000 0011100 0011100 0011100 0000000 0000000 Aceast bordare este util pentru c nu trebuie s mai verificm dac coordonatele rezultate n urma efecturii unei mutri sunt pe tabla de ah.

Astfel, presupunnd c l i c sunt coordonatele rezultate n urma unei mutri, condiia de validitate a acelei mutri va fi: if a[l,c]=1 then ... , n loc de, if (l>=1) and (l<=n) and (c>=1) and (c<=n) and (a[l,c]=1) then Condiia de validitate a unei mutri este ca poziia rezultat n urma mutrii s fie pe un ptrat "nears" i diferit de poziia iniial a calului dac acesta nu a ajuns nc la rege. Dac poziia rezultat n urma mutrii este poziia iniial a calului i acesta a ajuns la rege atunci programul se termin cu afiarea soluiei (variabila Terminat devine TRUE). Dac mutarea este corect, se "arde" cmpul respectiv i se verific dac poziia rezultat n urma mutrii este poziia regelui pentru da valoarea TRUE variabilei Rege (care este TRUE dac s-a ajuns la rege i FALSE, altfel). La trecerea pe un nivel superior al stivei, se iniializeaz acest nivel cu poziia curent. La trecerea pe un nivel inferior al stivei, se marcheaz poziia respectiv ca "nears" i, dac poziia de pe care se coboar n stiv era poziia regelui, variabila Rege devine FALSE.

program attila; const Mutari:array [1..8,1..2] of integer= ((-2,1),(-1,2),(1,2),(2,1),(2,-1),(1,-2),(-1,-2),(-2,-1)); type pozitie=record l,c,mut:integer; end; var N, lCal, cCal, lRege, cRege, k, nlin, ncol, i T :array [-1..22,-1..22] of integer; ST :array [1..400] of pozitie; Rege, Terminat :boolean; procedure citire; var f:text; i,j:integer; begin assign(f,'input.txt'); reset(f); readln(f,N); readln(f,lCal,cCal); readln(f,lRege,cRege); for i:=1 to n do begin for j:=1 to n do :integer;

read(f,T[i,j]); readln(f); end; close(f); end; procedure Printsol; var i:integer; begin for i:=1 to k do writeln(ST[i].l,' ',ST[i].c); writeln(lCal,' ',cCal) end; function Valid:boolean; begin nlin:=ST[k].l+Mutari[ST[k].mut,1]; ncol:=ST[k].c+Mutari[ST[k].mut,2]; if (nlin=lCal) and (ncol=cCal) then if Rege then begin Terminat:=true; Valid:=true; end else Valid:=false else if T[nlin,ncol]=1 then Valid:=true else Valid:=false end; begin Citire; for i:=-1 to N+2 do begin T[-1,i]:=0; T[0,i]:=0; T[n+1,i]:=0; T[n+2,i]:=0; T[i,-1]:=0; T[i,0]:=0; T[i,n+1]:=0; T[i,n+2]:=0 end; k:=1; ST[1].mut:=0; ST[1].l:=lcal; ST[1].c:=ccal; Rege:=false; Terminat:=false; while (k>0) and (not Terminat) do begin repeat ST[k].mut:=ST[k].mut+1 until (ST[k].mut>8) or Valid; if ST[k].mut<=8 then

if Terminat then Printsol else begin k:=k+1; ST[k].mut:=0; ST[k].l:=nlin; ST[k].c:=ncol; T[nlin,ncol]:=0; if (nlin=lRege) and (ncol=cRege) then Rege:=true end else begin T[ST[k].l,ST[k].c]:=1; if (ST[k].l=lRege) and (ST[k].c=cRege) then Rege:=false; k:=k-1 end end; if k=0 then writeln('Nu exista solutie.') end.

Problema 7
Enun. (Proprieti numerice) Numrul 123456789 are cteva caracteristici amuzante. Astfel, nmulit cu 2,4,7 sau 8 d ca rezultat tot un numr de nou cifre distincte (n afar de 0): 123456789 2 = 246913578 ; 123456789 4 = 493827156 123456789 7 = 864197523 ; 123456789 8 = 987654312. Aceast proprietate nu funcioneaz pentru numerele 3,6 sau 9. Exist totui multe numere de nou cifre distincte (fr 0) care nmulite cu 3 au aceeai proprietate. S se listeze toate aceste numere n care ultima cifr este 9. Concurs Tuymaada 1995 Rezolvare. n stiv se vor reine cifrele numrului cutat. Pentru c ultima cifr este ntotdeauna 9, stiva va avea numai 8 poziii. Condiia de "valid" este ca cifra selectat s nu fi fost folosit anterior (de fapt se genereaz toate permutrile cifrelor 1..8). Datorit faptului c la fiecare nivel n stiv se alege cea mai mic cifr nefolosit care nu a fost nc ncercat, numerele vor fi generate n ordine cresctoare. Astfel, dac nmulind cu 3 numrul obinut la pasul curent se va obine un numr pe 10 cifre vom ti c i urmtoarele numere vor da un produs pe 10 cifre. Rezult c procesul se oprete cnd ntlnete numrul 341256789 (cel mai mic numr cu cifrele distincte, mai mare dect 333333333).

program Numeric; var ST,U K,i,f e t s :array[0..10] of integer; :integer; :double; :set of char; :string;

begin k:=1; fillchar(ST,sizeof(ST),0); fillchar(U,sizeof(U),0); ST[9]:=9; U[9]:=1; while k>0 do if k=9 then begin e:=0; { se valideaz o posibil soluie } for f:=1 to 9 do e:=e*10+ST[f]; e:=e*3; { se nmulete numrul cu 3 } str(e:0:0,s); { i se verific dac cifrele } t:=[]; { rezultatului sunt distincte i:=0; if length(s)=10 then exit; for f:=1 to length(s) do if not (s[f] in t) then begin i:=i+1; t:=t+[s[f]] end; if i=9 then begin for f:=1 to 9 do write(ST[f]); writeln end; k:=k-1 end else begin U[ST[k]]:=0; repeat ST[k]:=ST[k]+1 until (U[ST[k]]=0) or (ST[k]>8); if ST[k]>8 then begin ST[k]:=0; k:=k-1

end else begin U[ST[k]]:=1; k:=k+1 end end end.

Problema 8
Enun. S se dispun pe cele 12 muchii ale unui cub toate numerele de la 1 la 12, astfel nct suma numerelor aflate pe muchiile unei fee s fie aceeai pentru toate feele. Considerm muchiile numerotate ca n figur:

Ieirea va fi ntr-un fiier text avnd numele solcub.txt care va conine cte o soluie pe fiecare linie sub forma: 1 m2 m3 m4 m5 m6 m7 m8 m9 m10 m11 m12 ... 1 m2 m3 m4 m5 m6 m7 m8 m9 m10 m11 m12

unde mi este valoarea din mulimea {2,...,12} plasat pe muchia i, i=2..12, iar muchia 1 conine valoarea 1 n toate soluiile. O soluie corect este cea de mai jos, ilustrat i n figur:

1 12 9 6 8 5 7 4 11 3 2 10 Baraj Sibiu, 1996 Rezolvare. Notm cu F1, ... ,F6 suma numerelor de pe muchiile feelor cubului. F1 = F2 = ... = F6 = F F1+F2+F3+F4+F5+F6 = 2(m1+m2+...+m12), pentru c fiecare muchie apare n dou fee ale cubului. Deci, 6*F=2*(m1+m2+...+m12), dar m1+m2+...+m12 = 1+2++12 =78 => 6*F=2*78=156 => F=26. Deci suma muchiilor de pe fiecare fa trebuie s fie 26. Observm c este suficient s folosim doar 6 nivele ale stivei, celelalte valori deducndu-se din acestea 6. Astfel, m1=1 ntotdeauna. Dup ce aezm m2 i m3, m8 se deduce ca fiind 26-m1-m2-m3. Analog, dup ce aezm m4 i m5 se deduce m9=26-m4-m5. La fel, se deduc m10=26-m2-m4, m11=26-m5-m7-m3 i m12=26m9-m10-m11. Deci numai 6 muchii variaz independent una de celelalte: m2,...,m7. Vectorul auxiliar Pus este folosit pentru a ti ce numere au fost deja aezate n stiv. n concurs, la aceast problem a fost impus un timp de execuie de maxim 10 secunde. Programul face aranjamente de 11 luate cte 6, timpul de calcul fiind O( ) 55000, adic sub o secund. Dac se utiliza soluia banal, adic permutri de 11, timpul de calcul era O(11!) 39 milioane i timpul de execuie era de aproximativ 30 de secunde.

program cub_magic; var ST Pus k, i :array [2..12] of integer; :array [1..26] of boolean; :integer;

function Valid:boolean; begin if Pus[ST[k]] then Valid:=false else case k of 2:begin Pus[ST[2]]:=true; Valid:=true end; 3:begin Pus[ST[3]]:=true; ST[8]:=26-ST[2]-ST[3]-1; if not Pus[ST[8]] then begin Pus[ST[8]]:=true; Valid:=true end else begin Pus[ST[3]]:=false; Valid:=false end end; 4:begin Pus[ST[4]]:=true; Valid:=true end; 5:begin Pus[ST[5]]:=true; ST[9]:=26-ST[5]-ST[4]-1; if not Pus[ST[9]] then begin Pus[ST[9]]:=true; Valid:=true end else begin Pus[ST[5]]:=false; Valid:=false end end; 6:begin Pus[ST[6]]:=true;

ST[10]:=26-ST[4]-ST[6]-ST[2]; if (ST[10]>0) and (not Pus[ST[10]]) then begin Pus[ST[10]]:=true; Valid:=true end else begin Pus[ST[6]]:=false; Valid:=false end; end; 7:begin Pus[ST[7]]:=true; ST[11]:=26-ST[5]-ST[7]-ST[3]; ST[12]:=26-ST[6]-ST[7]-ST[8]; if (ST[11]>0) and (ST[12]>0) and (ST[11]<>ST[12]) and (not Pus[ST[11]]) and (not Pus[ST[12]]) and (ST[9]+ST[10]+ST[11]+ST[12]=26) then begin Pus[ST[7]]:=false; Valid:=true end else begin Pus[ST[7]]:=false; Valid:=false end; end; end; end; procedure Printsol; begin write('1 '); for i:=2 to 12 do write(ST[i],' '); writeln end;

begin k:=2; ST[k]:=0; Pus[1]:=true; for i:=2 to 26 do Pus[i]:=false; while k>1 do begin repeat ST[k]:=ST[k]+1

until (ST[k]>12) or Valid; if ST[k]<=12 then if k=7 then Printsol else begin k:=k+1; ST[k]:=0 end else begin k:=k-1; case k of 2:Pus[ST[2]]:=false; 3:begin Pus[ST[3]]:=false; Pus[ST[8]]:=false end; 4:Pus[ST[4]]:=false; 5:begin Pus[ST[5]]:=false; Pus[ST[9]]:=false end; 6:begin Pus[ST[6]]:=false; Pus[ST[10]]:=false end end end end end.

Problema 9
Enun. Fie Am,n o matrice binar. Se cunosc coordonatele (i,j) ale unui element, i aparinnd 1...m, j aparinnd 1...n, care are valoarea 1. S se gseasc toate ieirile din matrice mergnd numai pe elementele cu valoarea 1. Rezolvare. Vom proceda ca la problema 4: reinem un vector cu deplasrile relative ale micrilor posibile; se subnelege c dintr-o csu se poate merge doar n cele patru vecine cu ea: (i-1,j) (i+1,j) (i,j+1) (i,j-1) Condiia pentru o soluie o reprezint verificarea atingerii marginilor matricei; deci avem patru cazuri, pentru fiecare din cele patru margini: i=1 sau i=M sau j=1 sau j=N.

O mutare este considerat valid dac:


q

este n matrice; poziia respectiv este marcat cu 1.

Poziiile deja parcurse sunt marcate cu 2 pentru a nu fi considerate nc o dat n soluie.

Program Matrice; const Muta : array[0..4,1..2] of integer= ( (0,0), (-1,0), (1,0), (0,1), (0,-1) );

var N,M,k,i,j,oi,oj A ST :integer; :array[0..100,0..100] of integer; :array[0..1000] of integer;

Procedure Sol; var x,y,f :integer; begin x:=oi; y:=oj; for f:=1 to k do begin write('(',x,',',y,') '); x:=x+Muta[ST[f],1]; y:=y+Muta[ST[f],2] end; writeln; dec(k) end; Function Valid:boolean; var pi,pj :integer; begin pi:=i+Muta[ST[k],1]; pj:=j+Muta[ST[k],2]; Valid:=(pi>0) and (pj>0) and (pi<=M) and (pj<=N) and (A[pi,pj]=1) end; begin write('M,N:'); readln(M,N); for i:=1 to M do

for j:=1 to N do begin write('A[',i,',',j,']='); readln(A[i,j]) end; write('i,j:'); readln(i,j); oj:=j; oi:=i; A[i,j]:=-1; fillchar(ST,sizeof(ST),0); k:=1; while k>0 do begin if (i=1) or (j=1) or (i=M) or (j=N) then Sol; if A[i,j]=2 then A[i,j]:=1; i:=i-Muta[ST[k],1]; j:=j-Muta[ST[k],2]; repeat inc(ST[k]) until (ST[k]>4) or valid; if ST[k]=5 then begin ST[k]:=0; dec(k) end else begin A[i,j]:=2; i:=i+Muta[ST[k],1]; j:=j+Muta[ST[k],2]; inc(k) end end end.

Problema 10
Enun. Se d un numr natural par N. S se afieze toate irurile de N paranteze care se nchid corect. Rezolvare. Un ir de N paranteze care se nchid corect, este un ir n care fiecrei paranteze deschise i corespunde o parantez nchis la o poziie ulterioar n ir. Exemple de iruri corecte: (())() ; ((())) ; ()()() Exemple de iruri incorecte: ())()) ; (((()) ; )()()(

Deducem din propoziia de mai sus o prim condiie necesar pentru ca un ir de paranteze s se nchid corect i anume c nu trebuie s deschidem mai mult de N/2 paranteze. Dup cum se vede ns din aceste exemple (incorecte), aceast condiie nu este suficient: (()))( ; )))((( ; )()()( . A doua condiie este s nu nchidem mai multe paranteze dect am deschis. Pentru formalizarea condiiilor notm cu Nd(k) i Ni(k) numrul de paranteze deschise, respectiv nchise, pn la poziia k a irului, inclusiv. Cele dou condiii sunt:

1. Nd(k) N/2, 1 k n 2. Nd(k) Ni(k), 1 k n


Pentru implementare folosim o stiv cu N nivele: st[i] = 1, dac paranteza de pe poziia i este deschis 2, dac paranteza de pe poziia i este nchis. n variabilele Nd i Ni avem numrul de paranteze de fiecare fel aezate n ir pn la poziia curent, k. Pentru a aeza o parantez deschis pe poziia curent trebuie s fi deschis mai puin de N/2 paranteze, adic Nd<N/2. Pentru a aeza o parantez nchis pe poziia curent trebuie s mai avem o parantez pe care s o nchidem, adic Nd>Ni. La urcarea i coborrea n stiv se modific Nd sau Ni n funcie de tipul parantezei de pe nivelul respectiv.

program Paranteze; var ST k, N, Nd, Ni :array [0..100] of integer; :integer;

procedure Printsol; var i:integer; begin for i:=1 to N do if ST[i]=1 then write('(') else write(')'); writeln end;

function valid:boolean; begin if ST[k]=1 then if Nd<N div 2 then valid:=true else valid:=false else if Nd>Ni then valid:=true else valid:=false end; begin write('N= '); readln(N); Nd:=0; Ni:=0; k:=1; ST[k]:=0; while k>0 do begin repeat ST[k]:=ST[k]+1 until (ST[k]>2) or valid; if ST[k]<=2 then if k=N then Printsol else begin if ST[k]=1 then Nd:=Nd+1 else Ni:=Ni+1; k:=k+1; ST[k]:=0 end else begin k:=k-1; if ST[k]=1 then Nd:=Nd-1 else Ni:=Ni-1 end end end.

Problema 11
Enun. Se consider o mulime de N elemente i un numr natural K nenul. S se calculeze cte submulimi cu k elemente satisfac pe rnd condiiile de mai jos i s se afieze aceste submulimi:

a. conin p obiecte date; b. nu conin nici unul din q obiecte date c. conin exact un obiect dat, dar nu conin un altul d. conin exact un obiect din p obiecte date e. conin cel puin un obiect din p obiecte date f. conin r obiecte din p obiecte date, dar nu conin alte q obiecte date.
Rezolvare. Pentru calcularea i generarea soluiilor se pot folosi doar combinrile. Pentru simplitatea programului se poate considera, fr a restrnge generalitatea, c cele p, respectiv q obiecte date au numerele de ordine 1,2,3, .. ,p, respectiv p+1,p+2, .. , p+q (n cazul c) avem p=q=1). Fiecrui nivel al stivei i este asociat o mulime de elemente care pot ocupa acel loc n soluie. Pentru a generaliza, vom considera c fiecare mulime este de forma {Li,Li+1,Li+2,...,LS}. Anume: pentru fiecare nivel i al stivei vom reine doi vectori Li[i] i Ls[i] care indic limita inferioar, respectiv superioar a elementului ce poate fi ales pe nivelul i (deci pentru Li[i]=2 i Ls[i]=5, pe nivelul i vom putea avea doar elementele 2,3,4,5 ). Acum s vedem cu ce valori vom iniializa vectorii Li i Ls pentru fiecare caz: a) practic trebuie s alegem ntotdeauna elementele 1,2,3, .. ,p i apoi nc oricare k-p din cele rmase (p+1,p+2, .. , N). Pentru a fora alegerea primelor p elemente vom considera Li[i]=Ls [i]=i. Deci vectorii vor arta astfel: I Li Ls 1 1 1 2 2 2 3 3 3 p p p p+1 p+1 N p+2 p+1 N N p+1 N

b) aici considerm p=0: deci obiectele care nu trebuie selectate vor fi 1,2,3 ..., q. Avem Li[i]=Q+1 i Ls[i]=N pentru oricare i. c) p=1 i q=1 Li[1]=1 i Ls[1]=1 obiectul 1 trebuie ales mereu; Li[i]=3 i Ls[i]=N, pentru i>1 cel cu numrul 2 trebuie evitat.

d) Li[1]=1 i Ls[1]=p alegem un obiect ntre 1 i p; Li[i]=p+1 i Ls[i]=N, pentru i>1 i restul ntre p+1 i N. e) Li[1]=1 i Ls[1]=p un obiect ntre 1 i p; Li[i]=1 i Ls[i]=N, pentru i>1 restul pot lua orice valori. f) Li[i]=1 i Ls[i]=p, pentru 1iR pe primele r poziii alegem obiecte din primele p; Li[i]=p+q+1 i Ls[i]=N, pentru R<iK restul trebuie s nu fie printre cele p sau q. Deci vom scrie o singur rutin backtracking, care, n funcie de iniializrile vectorilor Li i Ls, va furniza rspunsul la oricare din cele ase cerine. Programul va mai folosi i vectorul u care va indica dac elementul i a fost sau nu utilizat n soluie pn la pasul curent. Pentru c variabila k face parte din datele de intrare, nivelul curent n stiv va fi memorat n variabila i.

program submultimi; var ST, Li, Ls, u N, P, Q, R, K, i, f sol ch :array[0..1000] of integer; :integer; :longint; :char;

begin write('N,K,P,Q:'); readln(N,K,P,Q); write('subpunctul (A,B,C,D,E sau F):'); readln(ch); Case UpCase(ch) of 'A':begin for i:= 1 to P do begin Li[i]:=i; Ls[i]:=i for i:=P+1 to K do begin Li[i]:=P+1; Ls[i]:=N end; 'B': for i:= 1 to K do begin Li[i]:=Q+1; Ls[i]:=N 'C':begin for i:= 2 to K do begin Li[i]:=3; Ls[i]:=N Li[1]:=1; Ls[1]:=1 end; 'D':begin

end; end

end;

end;

for i:= 2 to K do begin Li[i]:=P+1; Ls[i]:=N end; Li[1]:=1; Ls[1]:=p end; 'E':begin for i:= 2 to K do begin Li[i]:=1; Ls[i]:=N end; Li[1]:=1; Ls[1]:=p end; 'F':begin write('R='); readln(R); for i:= 1 to R do begin Li[i]:=i; Ls[i]:=P end; for i:=R+1 to K do begin Li[i]:=P+Q+1;Ls[i]:=N end end end; i:=1; fillchar(ST,sizeof(ST),0); fillchar(u,sizeof(u),0); ST[1]:=Li[1]-1; sol:=0; while i>0 do if i=k+1 then begin sol:=sol+1; for f:=1 to K do write(ST[f],' '); writeln; i:=i-1 end else begin U[ST[i]]:=0; repeat ST[i]:=ST[i]+1 until (ST[i]>LS[i]) or (U[ST[i]]=0); if ST[i]>LS[i] then begin ST[i]:=0; i:=i-1 end else begin U[ST[i]]:=1; i:=i+1; if ST[i-1]<Li[i] then ST[i]:=Li[i]-1 else ST[i]:=ST[i-1]-1 end end;

writeln('Numar solutii= ',sol) end.

Problema 12
Enun. S se genereze toate permutrile de N cu proprietatea c oricare ar fi 2 i N, exist 1 j i astfel nct |V(i)-V(j)|=1. Exemplu: pentru N=4, permutrile cu proprietatea de mai sus sunt: 2134, 2314, 3214, 3241, 3421, 4321, 1234 Rezolvare. Pentru generarea permutrilor folosim o stiv cu N nivele i un vector auxiliar: Pus[i]=TRUE, dac numrul i a fost aezat deja n permutare; FALSE, altfel. Condiiile care trebuie ndeplinite pentru aezarea unui numr c pe nivelul k al stivei sunt:
q

Pus[c]=FALSE, adic s nu fi aezat deja numrul c n permutare; cel puin unul din numerele aezate pe poziiile 1..k-1 ,respectiv st[1]..st[k-1], s aib diferena absolut fa de c egal cu 1.

program Permutari; var ST k, i, N Pus :array [1..20] of integer; :integer; :array [1..20] of boolean;

function Valid:boolean; var tmp:boolean; begin if k=1 then Valid:=true else begin tmp:=false; if not Pus[ST[k]] then for i:=1 to k-1 do if abs(ST[k]-ST[i])=1 then tmp:=true;

Valid:=tmp end end; procedure Printsol; var i:integer; begin for i:=1 to N do write(st[i],' '); writeln end; begin write('N= '); readln(N); for i:=1 to N do Pus[i]:=false; k:=1; ST[k]:=0; while k>0 do begin repeat ST[k]:=ST[k]+1 until (ST[k]>N) or Valid; if ST[k]<=N then if k=N then Printsol else begin Pus[ST[k]]:=true; k:=k+1; ST[k]:=0 end else begin k:=k-1; Pus[ST[k]]:=false end end end.

Problema 13
Enun. Se dau N puncte albe i N puncte negre n plan, de coordonate ntregi. Fiecare punct alb se unete cu cte un punct negru, astfel nct din fiecare punct, fie el alb sau negru, pleac exact un segment. S se determine o astfel de configuraie de segmente nct oricare dou segmente s nu se intersecteze. Se citesc 2N perechi de coordonate corespunznd punctelor. Rezolvare. Vom reine n vectorii X i Y coordonatele punctelor, aeznd pe primele N poziii punctele

albe i pe poziiile N+1, ... ,2N punctele negre. Soluia problemei este de fapt o permutare. Aceasta va fi generat n vectorul ST: ST[i] reprezint punctul negru cu care va fi conectat punctul alb i. Funcia Intersect ntoarce o valoare boolean care indic dac segmentul determinat de punctul alb i i punctul negru ST[i] se intersecteaz cu cel determinat de j i respectiv ST[j]. Condiia de validitate pentru un segment este, evident, ca acesta s nu se intersecteze cu nici unul din cele construite anterior.

program Puncte; var N,i,k X,Y ST,u :integer; :array[1..1000] of real; :array[0..1000] of integer;

function Intersect(i,j:integer):boolean; var a1,a2,b1,b2:real; begin if x[i]=x[st[i]] then a1:=9e10 else a1:=(y[st[i]]-y[i])/(x[st[i]]-x[i]); if x[j]=x[st[j]] then a2:=9e10 else a2:=(y[st[j]]-y[j])/(x[st[j]]-x[j]); b1:=y[i]-a1*x[i]; b2:=y[j]-a2*x[j]; Intersect:= ((x[i]*a2+b2-y[i])*(x[st[i]]*a2+b2-y[st[i]])<=0) and ((x[j]*a1+b1-y[j])*(x[st[j]]*a1+b1-y[st[j]])<=0) end; function Valid:boolean; var i:integer; r:boolean; begin r:=(U[ST[k]]=0); i:=1; while r and (i<k) do begin r:=r and (not InterSect(k,i)); i:=i+1 end; valid:=r end;

procedure Printsol; var i:integer; begin for i:=1 to N do write(ST[i],' '); writeln; k:=0; end; begin write('N='); readln(N); writeln('punctele albe:'); for i:=1 to N do begin write(i:3,':X,Y='); readln(X[i],Y[i]); end; writeln('punctele negre:'); for i:=N+1 to 2*N do begin write(i:3,':X,Y='); readln(X[i],Y[i]); end; k:=1; fillchar(ST,sizeof(ST),0); fillchar(u,sizeof(u),0); ST[1]:=N; while k>0 do begin repeat St[k]:=St[k]+1 until (ST[k]>2*N) or valid; if ST[k]<=2*n then if k=N then Printsol else begin U[ST[k]]:=1; k:=k+1; ST[k]:=N end else begin k:=k-1; U[ST[k]]:=0 end end end.

Problema 14
Enun: Se consider n puncte n plan, de coordonate reale, (X1,Y1), (X2,Y2), (X3,Y3), ... , (Xn,Yn). Elaborai un program care selecteaz din aceste puncte vrfurile unui ptrat, ce conine numrul maximal de puncte din cele date. Afiai coordonatele punctelor selectate i numrul de puncte incluse n ptratul respectiv. Not: Punctele care aparin laturilor ptratului se consider incluse n ptrat (inclusiv colurile ptratului). Datele de intrare se vor citi din fiierul input.txt, astfel:
q

pe prima linie n, numrul de puncte pe urmtoarele n linii coordonatele punctelor.

Rezolvare. Coordonatele punctelor se vor citi n vectorul Pct. Stiva are 4 nivele reprezentnd indicii celor 4 puncte care formeaz ptratul. Dac se gasete un astfel de ptrat, se numr cte puncte sunt incluse n acest ptrat. Se pstreaz cea mai bun soluie n vectorul MaxP. Condiia pentru ca 3 puncte s poat face parte dintr-un ptrat este s formeze un triunghi dreptunghic isoscel. Acest lucru se verific prin relaia ntre distanele dintre puncte (d12,d23,d13). Dou dintre acestea trebuie s fie egale i a treia egal cu una din primele dou nmulit cu .

Atenie! n geometria analitic aplicat, testul de egalitate a dou variabile reale a i b nu trebuie s fie "dac a=b", ci "dac abs(a-b)<1e-5", adic dac diferen absolut dintre a i b este mai mic dect -5 10 . Acest test evit erorile de aproximaie (n programul nostru acestea pot aprea, de exemplu, la operaia mai sus. ). Din acest motiv, am folosit funcia boolean Egal care face testul de egalitate prezentat

Dac cele 3 puncte formeaz un triunghi dreptunghic isoscel se pstreaz n variabila colt indicele vrfului triunghiului (unghiul drept) i n vectorul P (pe primele 3 poziii) indicii celor 3 puncte n ordinea n care apar n ptrat. Astfel, P[2] este vrful triunghiului dreptunghic isoscel i P[1], P[3] celelalte dou vrfuri. Pe nivelul 4, condiia de validitate este ca punctul aezat s aib distanele la P[1] i P[3], egale cu

distanele de la P[2] la aceste dou puncte. Funcia Dist(p1,p2) ntoarce distana ntre Pct[p1] i Pct[p2]. Funcia boolean Inclus(i) ntoarce TRUE dac punctul Pct[i] este inclus n ptratul P[1]P[2]P[3] P[4] i FALSE, altfel. Pentru a fi inclus n ptrat, punctul Pct[i] trebuie s se afle ntre dreptele suport ale segmentelor P[1]P[2] i P[4]P[3], i de asemenea ntre dreptele suport ale lui P[2]P[3] i P[1]P [4]. Pentru a se afla ntre dou drepte paralele, de acelai sens, un punct trebuie s se afle n semiplane de semne opuse fa de acele drepte.

program puncte; type punct=record x,y:real end; var n, k, colt, max, i Pct ST P,maxp :integer; :array [1..20] of punct; :array [1..4] of integer; :array [1..4] of punct;

procedure Citire; var i :integer; f :text; begin assign(f,'input.txt'); reset(f); readln(f, n); for i:=1 to n do readln(f, Pct[i].x, Pct[i].y); close(f) end; function Egal(val1,val2:real):boolean; begin Egal:=(abs(val1-val2)<=1e-5) end; function Dist(p1,p2:integer):real; begin Dist:=sqrt(sqr(pct[p1].x-pct[p2].x)+sqr(pct[p1].y pct[p2].y)) end; function Valid:boolean; var d12,d23,d13,d1,d2,d3:real; begin if k<=2 then valid:=true

else if k=3 then begin d12:=Dist(ST[1],ST[2]); d23:=Dist(ST[2],ST[3]); d13:=Dist(ST[1],ST[3]); if Egal(d12,d23) then if Egal(d13,d12*sqrt(2)) then begin colt:=2; P[1]:=Pct[ST[1]];P[2]:=Pct[ST[2]]; P[3]:=Pct[ST[3]]; Valid:=true end else Valid:=false else if Egal(d12,d13) then if Egal(d23,d12*sqrt(2)) then begin colt:=1; P[1]:=Pct[ST[2]]; P[2]:=Pct[ST[1]]; P[3]:=Pct[ST[3]]; Valid:=true end else Valid:=false else if Egal(d13,d23) and Egal(d12,d23*sqrt(2)) then begin colt:=3; P[1]:=Pct[ST[1]]; P[2]:=Pct[ST[3]]; P[3]:=Pct[ST[2]]; Valid:=true end else Valid:=false end else begin case colt of 1:begin d1:=Dist(ST[2],ST[4]); d2:=Dist(ST[3],ST[4]); d3:=Dist(ST[1],ST[2]) end; 2:begin d1:=Dist(ST[1],ST[4]); d2:=Dist(ST[3],ST[4]); d3:=Dist(ST[1],ST[2]) end; 3:begin d1:=Dist(ST[2],ST[4]); d2:=Dist(ST[1],ST[4]); d3:=Dist(ST[3],ST[2]) end;

end; if Egal(d1,d3) and Egal(d2,d3) then begin Valid:=true; P[4]:=Pct[ST[4]] end else Valid:=false end end; function Inclus(i:integer):boolean; var tmp:boolean; begin tmp:=false; if ((Pct[i].x-P[3].x)*(P[2].y-P[3].y)-(Pct[i].y- P[3].y)* (P[2].x-P[3].x))* ((Pct[i].x-P[4].x)*(P[1].y-P[4].y)-(Pct[i].y-P[4].y)*(P[1].x-P[4].x))<=0 then if ((Pct[i].x-P[3].x)*(P[4].y-P[3].y)-(Pct[i].y-P[3].y)*(P[4].x-P[3].x))* ((Pct[i].x-P[2].x)*(P[1].y-P[2].y)-(Pct[i].y-P[2].y)*(P[1].x-P[2].x))<=0 then tmp:=true inclus:=tmp end; procedure Numara; var c,i:integer; begin c:=0; for i:=1 to n do if Inclus(i) then c:=c+1; if c>Max then begin Max:=c; for i:=1 to 4 do Maxp[i]:=P[i] end end; begin Citire; k:=1; ST[k]:=0; Max:=0; while k>0 do begin repeat ST[k]:=ST[k]+1 until (ST[k]>n) or Valid; if ST[k]<=n then if k=4 then Numara

else begin k:=k+1; ST[k]:=ST[k-1] end else k:=k-1 end; if Max>0 then for i:=1 to 4 do writeln(Maxp[i].x:0:3,' ',Maxp[i].y:0:3); writeln(Max) end.

Problema 15
Enun. Fiind dat un numr natural N i un vector V cu N componente ntregi, se cer urmtoarele:
q

s se determine toate subirurile cresctoare de lungime [N/5]; s se calculeze p(1)+p(2)+...+p(k), unde p(k) reprezint numrul subirurilor cresctoare de lungime k.

Vom scrie o singur rutin de backtracking, care va genera subirurile cresctoare de lungime L. Pentru a afia soluiile doar pentru L=[N/5] folosim variabila boolean scrie. Punctul doi se realizeaz prin nsumarea numrului de soluii generate de aceeai procedur, cu L lund valori ntre 1 i k. n stiv vom reine indicele din vectorul iniial al elementului aezat pe poziia i n subir. n procedura de backtracking, condiia de validitate pentru a pune un element pe poziia i este ca acesta s se afle n irul iniial dup elementul anterior lui din subir (ST[i]>ST[i-1]) i valoarea sa efectiv s fie mai mare dect a elementului selectat anterior: V[ST[i]] V[ST[i-1]]. S-a considerat "mai mare sau egal" pentru c n cerin se cer subirurile cresctoare i nu strictcresctoare.

program Subsiruri; var ST,V N,k,i sol scrie :array[0..1000] of integer; :integer; :longint; :boolean;

procedure Printsol(l:integer);

var i:integer; begin if scrie then begin for i:=1 to l do write(V[ST[i]],' '); writeln end; sol:=sol+1 end; procedure Bkt(l:integer); var f,i:integer; begin i:=1; ST[1]:=0; while i>0 do begin repeat ST[i]:=ST[i]+1 until ((ST[i]>ST[i-1]) and (V[ST[i]]>=V[ST[i-1]])) or (ST[i]>N); if ST[i]<=N then if i=l then Printsol(l) else begin i:=i+1; ST[i]:=0 end else i:=i-1 end end; begin write('N,k:'); readln(N,k); for i:=1 to N do begin write('V[',i,']='); readln(V[i]) end; scrie:=true; writeln('Subsirurile crescatoare de lungime ',N div 5,' sunt: '); Bkt(N div 5); sol:=0; scrie:=false; for i:=1 to k do Bkt(i); writeln('Numarul subsirurilor crescatore de lungime <= ',K,' este: ',sol) end.

[ Cuprins ]

[ Capitolul 2]

Capitolul 2 Recursivitate

Problema 1
Enun. Calculai recursiv suma a n numere naturale citite. Rezolvare. Funcia recursiv Suma calculeaz suma celor n numere. Funcia primete ca parametru k, numrul de numere care au mai rmas de citit. Cnd k=0 se iese din funcie. n caz contrar, se citete de la tastatur A, un numr din ir i se adun la sum.

program sum; var n :integer; function Suma(k:integer):integer; var A:integer; begin if k>0 then begin write('A[',n-k+1,']= '); readln(A); Suma:=A+Suma(k-1) end else Suma:=0; end; begin write('n= '); readln(n); writeln(Suma(n)); end.

Problema 2
Enun. Fiind dat o mulime cu n elemente, numrul total de partiii n k clase (k submulimi) este dat de numerele lui Stirling de spea a doua S(n,k). Acestea se definesc recursiv astfel: S(n,1)=...=S(n,n)=1 S(n+1,k)=S(n,k-1)+kS(n,k) S se calculeze numrul partiiilor unei mulimi cu n elemente. Rezolvare. Trebuie precizat c numrul partiiilor unei mulimi este egal cu suma numrului de partiii n k clase, dnd lui k valori ntre 1 i numrul de elemente din mulime. Funcia recursiv este ntocmai transcrierea formulei recurente.

program Partitii; var N,k R :integer; :longint;

function Part(n,k:integer):longint; begin if (k=1) or (k=n) then Part:=1 else Part:=Part(n-1,k-1) + k*Part(n-1,k) end; begin write('N='); readln(N); R:=0; for k:=1 to N do R:=R+Part(N,k); writeln(R) end.

Problema 3

Enun. Un numr natural n, se poate descompune ca sum unic de numere naturale. De exemplu, pentru numrul 4 se scrie descompunerea 2+1+1 (secven descresctoare), nu i 1+2+1. Prin P(n, m) notm numrul de mpriri ale lui n ca sum (unic) de m numere. Exemplu: P(4,2)=2 (4=3+1, 4=2+2). Numerele P(n,m) verific relaia de recuren: P(n,1)+P(n,2)++P(n,k)=P(n+k,k); P(n,1)=P(n,n)=1 S se calculeze numrul total de descompuneri ale numrului natural n. Rezolvare.

program Suma; var N,k R :integer; :longint;

function Min(m1,m2:integer):integer; begin if m1<m2 then Min:=m1 else Min:=m2 end; function P(n,k:integer):longint; var i:integer; r:longint; begin r:=0; if (k=n) or (k=1) then R:=1 else for i:=1 to Min(k,N-k) do R:=R+P(n-k,i); P:=R end; begin write('N=');

readln(N); R:=0; for k:=1 to N do R:=R+P(N,k); writeln(R) end.

Problema 4

Enun. Se citesc n i k (numere naturale n>k). Calculai recursiv . Este eficient?

utiliznd formula de recuren:

Rezolvare. Dup cum se vede, programul reprezint ntocmai implementarea formulei de recuren la care s-au adugat dou ramuri: =n i =0, dac k>n.

Pentru a funciona corect, orice program (recursiv) bazat pe o relaie recurent trebuie s porneasc de la cteva rezultate cunoscute pentru valori mici ale datelor de intrare. Apoi prin aplicarea succesiv a formulei de recuren, orice instan a intrrii trebuie redus la unul din rezultatele cunoscute. Astfel, orice pereche (n,k) trece n (n-1,k) i (n-1,k-1); este evident c n final perechea (n,k) se va ncadra ntr-una din cele dou ramuri de iniializare ale formulei de recuren (fie n<k, fie k=1). Acum s rspundem la ntrebarea dac programul este eficient. Succesiunii apelurilor recursive i se poate asocia un arbore binar n care fiecare nod este etichetat cu perechea (n,k) corespunztoare. Este necesar s construim doar dou nivele ale arborelui pentru a observa c etichetele nodurilor nu sunt distincte (perechea (n-2,k-1) apare de dou ori). Deci pentru a calcula de dou ori . Dac mai construim un nivel din arbore vom observa c programul va calcula este calculat de este

ase ori. Acest coeficient va crete exponenial cu numrul de nivele. Pe cazul general, calculat de

Deci programul nostru n nici un caz nu este eficient: pentru o pereche (n,k) de pe parcursul calculului, el va efectua de un numr de ori exponenial aceeai secven de operaii.

program Combinari_recursiv; var N,k :integer;

function Comb(n,k:integer):longint; begin if k=1 then Comb:=n else if k>n then Comb:=0 else Comb:=Comb(n-1,k)+Comb(n-1,k-1) end; begin write('N,K='); readln(N,K); writeln(Comb(N,K)) end.

Problema 5
Enun. Scriei un program iterativ care rezolv problema anterioar utiliznd aceeai formul. Rezolvare. Programul de mai jos rezolv neajunsurile celui dinaintea sa, i anume evit calcularea de mai multe ori a acelorai rezultate. Aceasta se face folosind matricea Comb[n,k] n care se va reine valoarea .

Modificarea esenial fa de programul precedent const n faptul c apelurile recursive sunt nlocuite cu citiri n matrice. Astfel se nlocuiete o operaie consumatoare de timp (numrul de calcule era exponenial) cu o operaie elementar (acces la matrice).

Ca i la varianta recursiv, aici combinrile se calculeaz pe baza aceluiai arbore, care acum este parcurs de "jos n sus". Anume, se pleac de la valorile cunoscute Comb[i,1]; apoi se aplic relaia de recuren i se obin valorile pentru Comb[i,2]; cunoscnd valorile pentru un anumit N, cele pentru N+1 se obin prin simpla aplicare a formulei de recuren.

program Combinari_iterativ; var N,K,i,j Comb :integer; :array[0..50,0..50] of longint;

begin write('N,K='); readln(N,K); for i:=0 to N do Comb[i,0]:=1; for i:=1 to N do for j:=1 to k do Comb[i,j]:=Comb[i-1,j-1]+Comb[i-1,j]; writeln(Comb[N,k]) end.

Problema 6
Enun. Gsii o formul de recuren care s rezolve eficient problema 4 (recursiv). Rezolvare.

Program Combinari_recursiv2; var N,k :integer;

Function Comb(n,k:integer):longint; begin if k=1 then Comb:=N else Comb:=(n-k+1)*Comb(n,k-1) div k end; begin write('N,k='); readln(N,k);

writeln(Comb(n,k)) end.

Calculul celui mai mare divizor comun a dou numere naturale

Algoritmul lui Euclid


Definiie: Cel mai mare divizor comun a dou numere ntregi u i v, notat cmmdc(u,v), este cel mai mare ntreg care l divide att pe u, ct i pe v. Algoritmul se bazeaz pe urmtoarea formul recursiv pentru cmmdc:

S demonstrm acum corectitudinea ecuaiei cmmdc(u,v)=cmmdc(v, u mod v). Considerm c u=q v+r (unde q este ntreg). Atunci, r=u-q v i cum r=u mod v => u mod v=u-q v. Ecuaia devine: cmmdc(u,v)=cmmdc(v, u-q*v). Dac x este un divizor comun al lui u i v, atunci x/(u-q v). Deci orice divizor comun al lui u i v este i divizor al lui u-q v. Analog, orice divizor comun al lui v i u-q*v, este divizor al lui u. Rezult c cele dou perechi de numere (u,v) i (v,u-q*v) au aceiai divizori comuni, deci implicit i acelai cmmdc. Varianta iterativ a algoritmului se scrie astfel: while v<>0 do

r:=u mod v (u mod v = restul impartirii lui u la v) u:=v v:=r (la sfritul execuiei programului, valoarea cmmdc se va afla n u)

Vom prezenta acum implementrile celor dou variante ale algoritmului.

program euclid_iterativ; var u,v,r:integer; begin write('u, v: '); readln(u,v); while v<>0 do begin r:=u mod v; u:=v; v:=r end; writeln(u) end.

program euclid_recursiv; var u,v:integer; function cmmdc(u,v:integer):integer; begin if v=0 then cmmdc:=u else cmmdc:=cmmdc(v,u mod v); end; begin write('u, v: '); readln(u,v);

writeln(cmmdc(u,v)); end.

Algoritmul binar
Vom prezenta acum un alt algoritm pentru aflarea cmmdc descoperit de Josef Stein n 1961. Algoritmul binar se bazeaz pe urmtoarele patru afirmaii despre dou ntregi pozitive u i v:

a. Dac u i v sunt pare, atunci cmmdc(u,v)=2cmmdc(u/2,v/2). b. Dac u este par i v este impar, atunci cmmdc(u,v)=cmmdc(u/2,v). c. cmmdc(u,v)=cmmdc(u-v,v). d. Dac u i v sunt impare, atunci u-v este par, i |u-v|<max(u,v).
Lsm demonstraia acestor propoziii n seama cititorului. Vom schia algoritmul iterativ n pseudocod: k:=0; while u-par i v-par do u:=u/2; v:=v/2; k:=k+1 dup acest prim pas al algoritmului, cmmdc=2k*cmmdc(u,v), conform afirmaiei a) i cel puin unul dintre u i v este impar. if v-par then interschimba(u,v) dac unul dintre numere este par, atunci acesta trebuie s fie u. repeat

while u-par do u=u/2 n urma acestui pas valoarea cmmdc se pstreaz pentru c, conform afirmaiei b), dac u-par i v-impar, cmmdc(u,v)=cmmdc(u/2,v). if v>u then interschimba(u,v); u trebuie s fie ntotdeauna cel mai mare dintre cele dou numere. u:=u-v conform afirmaiei c), cmmdc(u,v)=cmmdc(u-v,v). until u=0; writeln(2k*v) Vom prezenta acum programele care implementeaz algoritmul binar, n cele dou variante, iterativ i recursiv. Not: O implementare mai eficient, ar fi folosit n locul operaiei x mod 2, operaia x and 1 i n locul operaiei x div 2, x shr 1. Aceste perechi de operaii sunt echivalente, dar operaiile binare "and" i "shr" sunt mult mai rapide.

program binar_iterativ; var u,v,k,r,i:integer; procedure intersch(var u,v:integer); var aux:integer; begin aux:=u; u:=v; v:=aux end; begin write('u, v: ');

readln(u,v); k:=0; while (u mod 2=0) and (v mod 2=0) do begin v:=v div 2; u:=u div 2; k:=k+1; end; if v mod 2=0 then intersch(u,v); repeat while u mod 2=0 do u:=u div 2; if v>u then intersch(u,v); u:=u-v until u=0; for i:=1 to k do v:=v*2; writeln(v) end.

program binar_recursiv; var u,v,k,r,i:integer; procedure intersch(var u,v:integer); var aux:integer; begin aux:=u; u:=v; v:=aux end; function cmmdc(u,v:integer):integer; begin if u=0 then cmmdc:=v else if u mod 2=0 then cmmdc:=cmmdc(u div 2,v) else if v>u then cmmdc:=cmmdc(v-u,u) else cmmdc:=cmmdc(u-v,v) end; begin write('u, v: '); readln(u,v); k:=0; while (u mod 2=0) and (v mod 2=0) do begin v:=v div 2;

u:=u div 2; k:=k+1; end; if v mod 2=0 then intersch(u,v); r:=1; for i:=1 to k do r:=r*2; writeln(cmmdc(u,v)*r) end.

[ Capitolul 1]

[ Cuprins ]

[ Capitolul 3]

Capitolul 3 Backtracking recursiv

Problema 1
Enun. Avem la dispoziie 6 culori: alb, galben, rou, verde, albastru i negru. S se precizeze toate drapelele tricolore care se pot proiecta, tiind c trebuie respectate urmtoarele reguli:
r

orice drapel are culoarea din mijloc galben sau verde; cele trei culori de pe drapel sunt distincte.

Exemple: "alb galben rou", "rou verde galben" Rezolvare. Procedura Back primete ca parametru k, nivelul curent al stivei (poziia n drapel). Dac se depete nivelul 3, avem o soluie i aceasta este tiprit. Se ncearc aezarea unei culori pe nivelul curent, dac este valid. Funcia boolean Valid primete ca parametrii nivelul curent al stivei (pentru c acesta nu mai este inut ntr-o variabil global) i culoarea pentru care se face testul de validitate, i. n cazul n care culoarea este "valid", se apeleaz recursiv procedura Back pentru nivelul urmtor. nainte i dup apelare se seteaz corespunztor Fol [i].

program drapel; const Culoare:array [1..6] of string[10]=('alb','galben', 'rosu', 'verde','albastru','negru'); var ST:array [1..3] of integer; Fol:array [1..6] of boolean; k, i:integer; procedure Printsol; var i:integer; begin for i:=1 to 3 do write(Culoare[ST[i]],' ');

writeln; end; function Valid(niv,val:integer):boolean; begin if Fol[val] then Valid:=false else if niv<>2 then Valid:=true else if (val=2) or (val=4) then Valid:=true else Valid:=false; end; procedure Back(k:integer); var i:integer; begin if k>3 then Printsol else for i:=1 to 6 do if Valid(k,i) then begin ST[k]:=i; Fol[i]:=true; Back(k+1); Fol[i]:=false; end; end; begin for i:=1 to 6 do Fol[i]:=false; Back(1); end.

Problema 2
Enun. S se descompun un numr natural N, n toate modurile posibile, ca sum de P numere naturale (PN). Rezolvare. Procedura recursiv rezolv problema pentru o sum s, reinnd termenii n vectorul solutie ncepnd de pe pozitia k. Se consider pe rnd toi termenii ntre 1 i S, care se scad din suma iniial, i apoi se apeleaz recursiv pentru a rezolva aceeai problem, dar de dimensiuni mai mici.

program Descompuneri; var N, P, K, S, i ST :integer; :array[1..1000] of integer;

Procedure Back(k,s:integer); var i:integer; begin if (S=0) and (k=p+1) then begin for i:=1 to k-1 do write(ST[i],' '); writeln end else for i:=1 to S do begin ST[k]:=i; Back(k+1,s-i) end end; begin write('N='); readln(N); write('P='); readln(P); Back(1,N) end.

Problema 3
Enun. Dintr-un grup de N persoane, dintre care P femei, trebuie format o delegaie de C persoane, dintre care L femei. S se precizeze toate delegaiile care se pot forma. Rezolvare. Variabilele Min i Max, sunt limitele ntre care poate lua valori nivelul curent al stivei (explicaia se gsete n Cap. 1, Pr. 3). La trecerea pe nivelul c+1, am ajuns la o soluie, care se tiprete.

program Delegatie; var ST:array [0..100] of integer; k, c, n, p, l:integer;

procedure Printsol; var i:integer; begin for i:=1 to c do write(ST[i],' '); writeln; end; procedure Back(k:integer); var i,Min,Max:integer; begin if k>c then Printsol else begin if k<>l+1 then Min:=ST[k-1]+1 else Min:=p+1; if k<=l then Max:=p else Max:=n; for i:=Min to Max do begin ST[k]:=i; Back(k+1); end; end; end; begin write('n, p, c, l: '); readln(n,p,c,l); ST[0]:=1; Back(1); end.

Problema 4
Enun. Se d un numr natural par N. S se afieze toate irurile de N paranteze care se nchid corect. Rezolvare. Procedura recursiv Back are trei parametri:
q

k, nivelul curent al stivei. Nd i Ni, numrul de paranteze deschise, respectiv nchise, pn la poziia curent.

program Paranteze; var ST:array [1..20] of integer; N:integer; procedure Printsol; var i:integer; begin for i:=1 to N do if ST[i]=1 then write('(') else write(')'); writeln; end; procedure Back(k,Nd,Ni:integer); begin if k>N then Printsol else begin if Nd<N div 2 then begin ST[k]:=1; Back(k+1,Nd+1,Ni); end; if Ni<Nd then begin ST[k]:=2; Back(k+1,Nd,Ni+1); end end end; begin write('N= '); readln(N); Back(1,0,0); end.

Problema 5

Enun. Fiind dat un numr natural N, se cere s se afieze toate descompunerile sale ca sum de numere prime. Rezolvare.

program Prime; var ST, Numar N, P, i :array[0..1000] of integer; :integer;

function Prim( X : integer):boolean; var f:integer; begin Prim:=true; f:=2; while f*f<=X do begin if X mod f=0 then begin Prim:=false; exit end; f:=f+1; end; end; procedure Back(k,s:integer); var i:integer; begin if S=0 then begin for i:=1 to k-1 do write(ST[i],' '); writeln end else begin i:=1; while (Numar[i]<=s) and (i<=p) do begin ST[k]:=Numar[i]; Back(k+1,s-Numar[i]); i:=i+1; end end end; begin write('N='); readln(N);

P:=0; for i:=2 to N do if Prim(i) then begin P:=P+1; Numar[P]:=i end; Back(1,N) end.

Problema 6
Enun. S se genereze toate permutrile de N cu proprietatea c oricare ar fi 2 i N, exist 1 j i astfel nct |V(i)-V(j)|=1. Rezolvare.

program Permutari; var ST n, i Pus :array [1..20] of integer; :integer; :array [1..20] of boolean;

function Valid(k,v:integer):boolean; var tmp:boolean; i:integer; begin if k=1 then Valid:=true else begin tmp:=false; for i:=1 to k-1 do if abs(V-ST[i])=1 then tmp:=true; Valid:=tmp end end; procedure Printsol; var i:integer; begin for i:=1 to n do write(ST[i],' '); writeln end;

procedure Back(k:integer); var i:integer; begin if k>n then Printsol else for i:=1 to n do if not Pus[i] and Valid(k,i) then begin ST[k]:=i; Pus[i]:=true; Back(k+1); Pus[i]:=false; end; end; begin write('N= '); readln(n); for i:=1 to n do Pus[i]:=false; Back(1); end.

Problema 7
Enun. Fie Am,n o matrice binar. Se cunosc coordonatele (i,j) ale unui element, i aparinnd 1...m, j aparinnd 1...n, care are valoarea 1. S se gseasc toate ieirile din matrice mergnd numai pe elementele cu valoarea 1. Rezolvare.

program Matrice; const Muta : array[0..4,1..2] of integer= ( (0,0), (-1,0), (1,0), (0,1), (0,-1) ); :integer; :array[0..100,0..100] of integer; :array[0..1000] of integer;

var N,M,i,j,oi,oj A ST procedure Sol(k:integer); var x,y,f :integer;

begin x:=oi; y:=oj; for f:=1 to k do begin write('(',x,',',y,') '); x:=x+Muta[ST[f],1]; y:=y+Muta[ST[f],2] end; writeln end; function Valid(k,pi,pj:integer):boolean; var i,j :integer; begin i:=pi+Muta[ST[k],1]; j:=pj+Muta[ST[k],2]; Valid:=(i>0) and (j>0) and (i<=M) and (j<=N) and (A[i,j]=1) end; procedure Back(k,pi,pj:integer); var i:integer; begin A[pi,pj]:=2; if (pi=1) or (pj=1) or (pi=M) or (pj=N) then Sol(k); for i:=1 to 4 do begin ST[k]:=i; if Valid(k,pi,pj) then Back(k+1,pi+Muta[i,1],pj+Muta[i,2]) end; A[pi,pj]:=1 end; begin write('M,N:'); readln(M,N); for i:=1 to M do for j:=1 to N do begin write('A[',i,',',j,']='); readln(A[i,j]) end; write('i,j:'); readln(i,j); oj:=j; oi:=i; A[i,j]:=1;

Back(1,i,j) end.

Problema 8
Enun. Se consider o mulime de N elemente i un numr natural K nenul. S se calculeze cte submulimi cu k elemente satisfac pe rnd condiiile de mai jos i s se afieze aceste submulimi:

a. conin p obiecte date; b. nu conin nici unul din q obiecte date c. conin exact un obiect dat, dar nu conin un altul d. conin exact un obiect din p obiecte date e. conin cel puin un obiect din p obiecte date f. conin r obiecte din p obiecte date, dar nu conin alte q obiecte date.
Rezolvare.

program Submultimi; var ST, Li, Ls, u N, P, Q, R, K, i sol ch :array[0..1000] of integer; :integer; :longint; :char;

procedure Back(l:integer); var i,b:integer; begin if st[l-1]<Li[l] then b:=Li[l] else b:=st[l-1]; if l=k+1 then begin sol:=sol+1; for i:=1 to K do write(ST[i],' ');

writeln end else for i:=b to Ls[l] do if U[i]=0 then begin ST[l]:=i; U[i]:=1; Back(l+1); U[i]:=0 end end; begin write('N,K,P,Q:'); readln(N,K,P,Q); write('subpunctul (A,B,C,D,E sau F):'); readln(ch); Case UpCase(ch) of 'A':begin for i:=1 to P do begin Li[i]:=i; Ls[i]:=i end; for i:=P+1 to K do begin Li[i]:=P+1; Ls[i]:=N end end; 'B': for i:=1 to K do begin Li[i]:=Q+1; Ls[i]:=N end; 'C':begin for i:=2 to K do begin Li[i]:=3; Ls[i]:=N end; Li[1]:=1; Ls[1]:=1 end; 'D':begin for i:=2 to K do begin Li[i]:=P+1; Ls[i]:=N end; Li[1]:=1; Ls[1]:=p end; 'E':begin for i:=2 to K do

begin Li[i]:=1; Ls[i]:=N end; Li[1]:=1; Ls[1]:=p end; 'F':begin for i:=1 to R do begin Li[i]:=i; Ls[i]:=P end; for i:=R+1 to K do begin Li[i]:=P+Q+1;Ls[i]:=N end end end; Back(1); writeln('Numar solutii= ',sol) end.

[ Capitolul 2]

[ Cuprins ]

[ Capitolul 4]

Capitolul 4 Analiza timpului de calcul necesar algoritmilor

Cutare binar
Definim problema cutrii: Se d un vector A[1..n] i o valoare v de acelai tip cu elementele din A. S se afieze p astfel nct v=A[p] sau 0 dac nu exist un element cu valoarea v n A. Cel mai simplu algoritm care rezolv aceast problem este cutarea liniar, care testeaz elementele vectorului unul dup altul, ncepnd cu primul, n cutarea lui v. Iat implementarea acestui algoritm:

Gasit_v:=False; p:=0; while not Gasit_v do begin p:=p+1; if v=A[p] then Gasit_v:=True; end; if Gasit_v then writeln(p) else writeln('0'); S analizm complexitatea acestui algoritm pe cazul cel mai defavorabil, acela n care v nu se gsete n A. n acest caz se va face o parcurgere complet a lui A. Considernd ca operaie de baz testul v=A[p], numrul de astfel de operaii de baz efectuate va fi egal cu numrul de elemente al lui A, adic n. Deci complexitatea algoritmului pe cazul cel mai defavorabil este O(n). Un alt algoritm, mai eficient, este cutarea binar. Acest algoritm are ns neajunsul c necesit ca vectorul A s fie sortat cresctor. Iat procedura recursiv care implementeaz algoritmul:

procedure caut(i,j:integer); begin m=(i+j) div 2; if v=A[m] then writeln('Valoarea gasita la pozitia: ',m) else if i<j then if v<A[m] then caut(i,m-1) else caut(m+1,j) end;

Procedura primete ca parametri i i j, limitele poriunii din vector n care cutm. Iniial procedura este apelat cu i=1 i j=n. m primete valoarea (i+j) div 2, adic indicele elementului din mijlocul lui A[i..j]. Dac elementul din mijlocul acestei poriuni, A[m], este chiar valoarea cutat, atunci programul se termin cu afiarea indicelui acestui element. Dac v<A[m] nseamn c poziia lui v, dac se gsete n vector, este n stnga lui m, pentru c vectorul este sortat cresctor. n acest caz procedura se apeleaz recursiv cu noua poriune de vector (i..m-1) ca parametru. Analog, dac v>A[m], procedura se apeleaz recursiv pentru (m+1..j). Dac se ajunge la i=j, adic o poriune de vector de un element, i acest element este diferit de v, nseamn c v nu se gsete n A i apelarea recursiv nceteaz datorit nendeplinirii condiiei i<j. Exemplu:

S analizm complexitatea acestui algoritm pe cel mai defavorabil caz, care este ca i la cutarea liniar acela n care valoarea cutat nu se gsete n vector. Considerm ca operaie elementar , o execuie a procedurii caut. Pentru a determina complexitatea algoritmului, trebuie s vedem de cte ori se execut procedura caut. Procedura se va apela recursiv pn cnd se va ajunge la o poriune de vector de un element, adic pn cnd i=j. Iniial procedura este apelat cu i=1 i j=n, mrimea poriunii de vector fiind , j-i+1=n. La fiecare apel recursiv, poriunea de vector se njumtete. La ultimul apel, i=j, deci mrimea poriunii va fi i-j+1=1. Problema este deci, de cte ori se poate mpri un numr natural n la 2, pn ce ctul mpririi va fi 1. Presupunem c n=2k. La fiecare mprire a lui n la 2 exponentul acestuia scade cu o unitate, pn cnd ctul mpririi va fi 1, adic n=21. Deci n se poate mpri la 2 de k ori. Se demonstreaz uor 1 c acelai lucru este valabil i dac 2k<n<2k+1. Dac n=2k sau 2k<n<2k+ , atunci k=[log2n]. Deci numrul maxim de execuii ale procedurii caut i implicit, complexitatea algoritmului, este O(log2n). Logaritmul este o funcie care crete mult mai ncet dect funciile polinomiale. De exemplu, pentru un vector cu n=100000 de elemente, n cazul n care valoarea cutat nu se gsete n vector, cutarea liniar va face n=100000 de operaii elementare, n timp ce cutarea binar va face numai log2n=log2100000=16 operaii. Dac n=21000, care are aproximativ 300 de cifre, log2n=1000. n concluzie, cutarea binar este mult mai eficient dect cea liniar, dar necesit ca vectorul s fie sortat, iar sortarea nu poate fi fcut (prin metodele care folosesc comparaii) ntr-o complexitate mai bun de O(n lg n).

Sortare prin numrare


Este demonstrat c metodele de sortare care folosesc ca operaie de baz pentru determinarea ordinii elementelor comparaia (ex. quicksort, heapsort, sortarea prin interclasare, sortarea prin inserie) nu pot avea o complexitate, pe cel mai defavorabil caz, mai bun de O(n lg n). Exist totui metode de sortare O(n) care nu folosesc comparaia elementelor ca operaie de baz, o astfel de metod fiind sortarea prin numrare. Ne propunem s sortm cresctor vectorul V, care are n elemente naturale. Definim M ca fiind valoarea maxim din V. Folosim de asemenea, un vector auxiliar A[1..L], unde L M. Se iniializeaz A cu 0 i apoi se face o parcurgere a vectorului V incrementndu-se la fiecare pas A[V [i]], adica elementul din A corespunztor valorii elementului curent din V. n urma acestei parcurgeri A[i] va fi numrul de elemente cu valoarea i din V (respectiv 0, dac nu exist nici un element cu valoarea i n V).

Se parcurg apoi vectorii A i V n paralel, ncepnd cu poziia 1. Cnd se ntlnete un element A[i] >0, se d valoarea i urmtoarelor A[i] poziii din V. Exemplu: n=8 V=<7,5,4,5,1,2,4,5> M=7 Prima parcurgere: i=1, incrementm A[V[1]]=A[7] A=<0,0,0,0,0,0,1> i=2, incrementm A[V[2]]=A[5] A=<0,0,0,0,1,0,1> i=3, incrementm A[V[3]]=A[4] A=<0,0,0,1,1,0,1> i=4, incrementm A[V[4]]=A[5] A=<0,0,0,1,2,0,1> Procedeul continu pn la i=8. n final, A=<1,1,0,2,3,0,1>. Parcurgem apoi A i V n paralel, cu i indicele de parcurgere al lui A i j indicele de parcurgere al lui V: i=1 j=1 A[i]=A[1]=1 V[1]:=1 i=2 j=2 A[i]=A[2]=1 V[2]:=2 i=3 j=3 A[i]=A[3]=0 i=4 j=3 A[i]=A[4]=2 V[3]:=4, V[4]:=4 i=5 j=5 A[i]=A[5]=3 V[5]:=5, V[6]:=5, V[7]:=5 i=6 j=8 A[i]=A[6]=0 i=7 j=8 A[i]=A[7]=1 V[8]:=7 Rezult vectorul V sortat: V=<1,2,4,4,5,5,5,7>

Programul const din dou parcurgeri (nu considerm i parcurgerea pentru iniializarea vectorului A): una de lungime n i una de lungime M, deci complexitatea algoritmului este O(max(n,M)). Spaiul de memorie folosit pentru vectorii A i V este O(M+n). Se observ c algoritmul nu este practic dect pentru valori mari ale lui n i valori relativ mici ale lui M. Altfel, preferm un algoritm clasic, ca heapsort sau quicksort, a crui complexitate este O(n lg n) i spaiu de memorie O(n). n plus, sortarea prin numrare are neajunsul c nu este aplicabil dect pe vectori cu elemente naturale (sau eventual ntregi).

program Sortare_prin_numarare; const L=200; var V A M, n, i, j, k :array [1..100] of integer; :array [1..L] of integer; :integer;

procedure Citire; var i:integer; begin write('n= '); readln(n); for i:=1 to n do begin write('V[',i,']= '); readln(V[i]) end end; begin Citire; FillChar(A,2*L,0); M:=1; for i:=1 to n do begin if V[i]>V[M] then M:=i; A[V[i]]:=A[V[i]]+1; end; M:=V[M]; j:=0; for i:=1 to M do if A[i]>0 then for k:=1 to A[i] do begin

j:=j+1; V[j]:=i; end; for i:=1 to n do write(V[i],' '); writeln; end.

Ciurul lui Eratostene


Ciurul lui Eratostene este o metod de determinare a numerelor prime mai mici dect n. Pentru a determina dac un numr i este prim sau nu, se testeaz divizibilitatea acestuia cu numerele prime deja determinate (inute n vectorul Prime), n ordine. Testarea se oprete dac i se divide cu unul din numere, sau dac ctul mpririi lui i la numrul prim respectiv, Prime[j], este mai mic sau egal dect acest numr prim, caz n care i este prim i este adugat n vectorul Prime. Demonstraia corectitudinii algoritmului:

i:Prime[j]=Q (rest R) i=Prime[j] Q+R. Dac Q Prime[j] Prime[j]>

Dac un numr i nu are nici un divizor pn la

, atunci i este prim.

Demonstraia se face prin reducere la absurd: dac i nu este prim, atunci are cel puin 2 divizori: Contradicie Complexitatea acestei metode este aproximativ O(n lg n).

program Eratostene; var Prime n, NrP, i, j, C, R Prim, Term begin :array [1..13000] of integer; :integer; :boolean;

write('n= '); readln(n); Prime[1]:=2; Prime[2]:=3; NrP:=2; for i:=4 to n do begin j:=0; Prim:=true; Term:=false; while Prim and not Term do begin j:=j+1; C:=i div Prime[j]; R:=i mod Prime[j]; if R=0 then Prim:=false; if C<=Prime[j] then Term:=true; end; if Prim then begin NrP:=NrP+1; Prime[NrP]:=i; end; end; writeln('Numarul de nr. prime mai mici decit ',n,': ',NrP); for i:=1 to NrP do write(Prime[i],' '); writeln end.

Maxim i minim simultan Problema determinrii maximului i minimului dintr-un vector este, n mod clar, O(n). Considerm comparaia ca operaie de baz i facem dou parcurgeri ale vectorului, una pentru determinarea maximului i una pentru determinarea minimului. n total vom avea 2(n-1)=2n-2 comparaii, deci complexitatea O(n). Problema poate fi rezolvat ns, doar cu 3(n/2)-2 comparaii. Se face o singur parcurgere a vectorului pentru determinarea simultan a maximului i a minimului. La fiecare pas se compar o pereche de elemente din vector; apoi cel mai mic dintre ele este comparat cu minimul i cellalt cu maximul. Se fac astfel 3 comparaii la fiecare pas i n total avem n/2 pai (pentru c la fiecare pas procesm o pereche de elemente i n total sunt n elemente). Complexitatea ambelor variante este O(n), dar cea de-a doua este mai eficient, fcnd acelai lucru cu mai puine comparaii.

program MinMax; var A Min, Max, Mic, Mare, i, n procedure Citire; var i:integer; begin write('n= '); readln(n); for i:=1 to n do begin write('A[',i,']= '); readln(A[i]) end end; begin Citire; Min:=1; Max:=1; for i:=1 to (n-1) div 2 do begin if A[2*i]<A[2*i+1] then begin Mic:=2*i; Mare:=2*i+1; end else begin Mic:=2*i+1; Mare:=2*i; end; if A[Mic]<A[Min] then Min:=Mic; if A[Max]<A[Mare] then Max:=Mare; end; if n mod 2=0 then begin if A[n]<A[Min] then Min:=n; if A[Max]<A[n] then Max:=n end; writeln('Minimul: ',A[Min]); writeln('Maximul: ',A[Max]) end. :array [1..100] of integer; :integer;

Problema 1 Enun. Se dau n ntregi: a1, a2,, an. S se afieze valoarea expresiei:

Rezolvare. O prim metod const n a calcula valoarea tuturor termenilor (produselor) i a face suma lor. S analizm complexitatea acestei metode. Considerm ca operaie de baz calcularea valorii unui termen (produs) i adunarea lui la suma total. Aceast operaie va fi efectuat de un numr de ori egal cu numrul de termeni al sumei. S calculm acum numrul de termeni al expresiei. Sunt

termeni n expresie, ceea ce nseamn, avnd n termeni cu k factori. n total avem vedere operaia de baz aleas, c complexitatea acestei metode este O(2n), deci o complexitate exponenial. Considerm polinomul:

Coeficienii acestui polinom sunt exact termenii expresiei noastre, plus 1 (coeficientul lui polinom se poate scrie sub form de produs ca:

). Acest

Suma coeficienilor acestui polinom este exact valoarea expresiei din problema noastr (minus 1, coeficientul lui egal cu ). Suma coeficienilor unui polinom este P(1). Rezult c valoarea expresiei este -1

Considernd ca operaie de baz nmulirea, complexitatea acestei metode este O(n) pentru c produsul are n factori, deci se efectueaz n nmuliri. Prezentm acum programele care implementeaz cele dou metode de rezolvare. Primul program (metoda exponenial) folosete o rutin de generare a combinrilor (backtracking) pentru calcularea celor 2n termeni. Cel de-al doilea program este pur i simplu implementarea formulei de mai sus.

Invitm cititorul s ruleze cele dou programe, cu diferite valori ale lui n, pentru a vedea diferena la timpul de execuie. De exemplu, pentru n=20, primul program va efectua 2n=220=1048576 operaii de baz, timpul de execuie fiind de 20 de secunde pe un Pentium la 90MHz, n timp ce al doilea va efectua numai n=20 operaii, timpul de execuie fiind insesizabil.

program Expresie_exponential; var a, ST n, nrfact Suma :array [0..100] of integer; :integer; :longint;

procedure Citire; var i:integer; begin write('n= '); readln(n); for i:=1 to n do begin write('a[',i,']= '); readln(a[i]); end; end; procedure Aduna; var Termen :longint; i :integer; begin Termen:=1; for i:=1 to nrfact do Termen:=Termen*a[ST[i]]; Suma:=Suma+Termen end; procedure Back(k:integer); var i:integer; begin if k>nrfact then Aduna else for i:=ST[k-1]+1 to n do begin ST[k]:=i; Back(k+1) end; end;

begin Citire; Suma:=0; ST[0]:=0; for nrfact:=1 to n do Back(1); writeln(Suma); end.

program Expresie_polinomial; var a n, i Suma :array [1..100] of integer; :integer; :longint;

procedure Citire; var i:integer; begin write('n= '); readln(n); for i:=1 to n do begin write('a[',i,']= '); readln(a[i]); end; end; begin Citire; Suma:=1; for i:=1 to n do Suma:=Suma*(a[i]+1); writeln(Suma-1); end.

Problema 2 Enun. Se dau suma X i n tipuri de monede avnd valori de a1, a2,, an lei. Se cere numrul modalitilor distincte de plat a sumei X utiliznd aceste monede.

Exemplu: Pentru X=4, n=3, a1=1, a2=2, a3=4 sunt 4 modaliti distincte de plat:

1. 4=1+1+1+1 (cu 4 monede de 1 leu) 2. 4=1+1+2 (cu 2 monede de 1 leu i una de 2 lei) 3. 4=2+2 (cu 2 monede de 2 lei) 4. 4=4 (cu 1 moneda de 4 lei)
Metoda I (exponenial) Aceast metod folosete tehnica backtracking pentru rezolvare. Rutina recursiv Back primete trei parametri: k nivelul curent al stivei, S suma elementelor aezate pn la nivelul curent n stiv i lim valoarea nivelului k-1 din stiv. Programul nu folosete o stiv explicit pentru c nu este nevoie i de generarea propriu-zis a modalitilor de plat, ci numai de numrarea lor. Avem nevoie de variabila S pentru a ti cnd avem o modalitate de plat valid, respectiv S=X i pentru a opri recurena dac S>X. Nivelul curent din stiv poate lua valori numai ntre valoarea nivelului anterior i n, pentru a evita duplicarea modalitilor de plat, prin forarea ordinii cresctoare n stiv. Aceast valoare a nivelului anterior este transmis prin variabila lim, tocmai pentru a evita folosirea unui vector-stiv. Complexitatea acestei metode, fiind vorba de folosirea tehnicii backtracking, este exponenial (nu vom prezenta demonstraia aici ntruct este foarte complex).

program Suma_exponential; var a X, NrMod, n procedure Citire; var i:integer; begin write('X= '); readln(X); write('n= '); readln(n); for i:=1 to n do begin :array [1..100] of integer; :integer;

write('a[',i,']= '); readln(a[i]) end end; procedure Back(k,S,lim:integer); var i:integer; begin if S=X then NrMod:=NrMod+1 else if S<X then for i:=lim to n do Back(k+1,S+a[i],i); end; begin Citire; NrMod:=0; Back(1,0,1); writeln(NrMod); end.

Metoda II (pseudo-polinomial) Cea de-a doua metod de rezolvare utilizeaz urmtoarea formul de recuren:

, unde M(S,i) este numrul de modaliti de plat ale sumei S folosind numai primele i tipuri de monede. Exemplu: Dac S=7, i=4 i ai=3, trebuie s calculm M(7,4) avnd deja calculate M(x,3), pentru x de la 1 la X. Conform formulei, M(7,4)=M(7,3)+M(4,3)+M(1,3). Primul termen este numrul de modaliti n care poate fi obinut aceeai sum S=7, folosind doar primele 3 tipuri de monede; prezena lui n sum este justificat prin nefolosirea niciunei monede de tipul i, adic de 3 lei. Dac folosim o moned de tipul i, adic de 3 lei, se adaug la sum numrul de modaliti n care poate fi obinut suma S-3=7-3=4, cu primele 3 tipuri de monede. Dac folosim dou monede de tipul i, se adun numrul de modaliti n care poate fi obinut suma S-6=1, cu primele 3 tipuri de monede, adic M

(1,3). Folosind aceast formul trebuie s obinem M(X,n), adic numrul de modaliti de plat ale sumei X, cu toate cele n tipuri de monede. Pentru aceasta, folosim doi vectori, M i Mant, unde primul reprezint M(x,i) i al doilea M(x,i-1), pentru x de la 1 la X. Calculm succesiv valoarea lui M din Mant prin formula prezentat, pn la obinerea lui M(X,n). Complexitatea acestei metode este O(n X2). Aceasta este o aa numit complexitate pseudopolinomial, pentru c nu depinde numai de n. Dac valorile monedelor erau numere reale, metoda aceasta nu poate fi aplicat, problema fiind NPcomplet, adic face parte dintr-o clas de probleme pentru care nu se cunoate o rezolvare polinomial.

program Suma_pseudopolinomial; type Vector=array [0..1000] of longint; var a X, S, n, i, k M, Mant :array [1..100] of integer; :integer; :Vector;

procedure Citire; var i:integer; begin write('X= '); readln(X); write('n= '); readln(n); for i:=1 to n do begin write('a[',i,']= '); readln(a[i]) end end; begin Citire; FillChar(Mant,2*(X+1),0); FillChar(M,2*(X+1),0); M[0]:=1; Mant[0]:=1; for i:=1 to n do begin

for S:=1 to X do for k:=1 to S div a[i] do M[S]:=M[S]+Mant[S-k*a[i]]; Mant:=M; end; writeln(M[X]) end.

Problema 3 Enun. Dndu-se a i x1, x2,, xn naturale nenule, s se afieze ultima cifr a numrului . Metoda I Cea mai simpl metod de rezolvare ar fi s-l nmulim pur i simplu pe a cu sine nsui de x1 xn

ori. Este limpede ns c numrul rezultat ar fi imens chiar i pentru valori mici ale lui a, n i x1,, xn. 1024 , numr care are De exemplu, pentru a=n=10 i x1= x2== xn=2, va trebui s calculm 10 1024 de cifre. Este clar c aceast metod nu este practic. Observm c este suficient s pstrm numai ultima cifr a numrului la fiecare nmulire, ba mai mult c este suficient s nmulim numai ultima cifr a lui a cu ea nsi de x1 xn ori i s pstrm de

fiecare dat numai ultima cifr a produsului. Dac considerm ca operaie de baz nmulirea, aceast metod are complexitatea O(x1 xn).

program Cifra_1; var a, n, C O, i :integer; :longint;

procedure Citire; var i, x :integer; begin write('a= '); readln(a);

a:=a mod 10; write('n= '); readln(n); O:=1; for i:=1 to n do begin write('x[',i,']= '); readln(x); O:=O*x; end; end; begin Citire; C:=1; for i:=1 to O do C:=C*a mod 10; writeln(C); end.

Metoda II Cea de-a doua metod pornete de la observaia c cifra n care se termin puterile unui numr se repet cu o anumit perioad. De exemplu, orice putere a unui numr terminat n cifra 0 se va termina cu cifra 0. La fel i pentru numerele terminate n 1, 5 sau 6. S vedem ce se ntmpl pentru 7: 7, 49, ..63, ..21, ..7, ..49, ..63, i aa mai departe. Deci perioada lui 7 este 7, 9, 3, 1. Toate cifrele au perioade de lungime maxim 4, dup cum se poate vedea n tabelul de mai jos:

K 0 1 2 3 4

K2 0 1 4 9 6

K3 0 1 8 7 4

K4 0 1 6 1 6

K5 0 1 2 3 4

5 6 7 8 9

5 6 9 4 1

5 6 3 2 9

5 6 1 6 1

5 6 7 8 9

Este suficient s reinem pentru fiecare cifr perioada (ca n tabelul de mai sus) i s determinm forma lui x1 xn: 4K, 4K+1, 4K+2 sau 4K+3, pentru a ti cifra n care se termin puterea respectiv

a lui a. Complexitatea acestei metode este O(n), considernd ca operaie de baz nmulirea.

program Cifra_2; const Perioada:array [0..9,0..3] of integer= ((0,0,0,0), (1,1,1,1), (6,2,4,8), (1,3,9,7), (6,4,6,4), (5,5,5,5), (6,6,6,6), (1,7,9,3), (6,8,4,2), (1,9,1,9)); var a O :integer; :longint;

procedure Citire; var i, x, n :integer; begin write('a= '); readln(a); a:=a mod 10; write('n= '); readln(n);

O:=1; for i:=1 to n do begin write('x[',i,']= '); readln(x); O:=O*x; end; end; begin Citire; writeln(Perioada[a,O mod 4]); end.

Probleme propuse: 1. Se d o secven de numere x1, x2,, xn. S se determine dac exist un numr care apare de mai multe ori n aceast secven. Complexitate cerut: O(n lg n). Indicaie: Sortai vectorul printr-o metod de sortare n lg n i apoi comparai elementele de pe poziii consecutive.

2. (Problema evalurii unui polinom ntr-un punct) Se dau n coeficieni a0, a1,, an i un numr real x. Calculai valoarea lui P(x)=anxn+ an-1xn-1++ a1x+ a0. Ce complexitate are algoritmul banal ? Descriei un algoritm O(n) care folosete metoda lui Horner de rescriere a unui polinom: P(x)=(((anx+an-1)x+an-2)x++ a1)x+ a0. 3. Estimai timpul de calcul al urmtorului algoritm (sortarea prin selecie), pe cazul cel mai favorabil i pe cel mai defavorabil: Se caut cel mai mic element din vectorul care trebuie sortat, A, i se pune pe prima poziie a unui alt vector B. Apoi se caut cel mai mic element dintre cele rmase n A i se pune pe poziia a doua n B. Procedeul continu pentru restul elementelor. 4. Estimai timpul de calcul al sortrii cu bule (bubble-sort) pe cazurile favorabil i defavorabil. Ce algoritm este de preferat, sortarea prin selecie sau sortarea cu bule ?

Indicaie: Comparai timpii de calcul pe cazul defavorabil i favorabil. 5. Se dau doi vectori sortai cresctor X[1..n] i Y[1..n] i 1 i 2n. S se gseasc elementul din X sau Y, care are proprietatea c este mai mare dect exact i-1 elemente din cele 2n coninute de cei doi vectori. Complexitate cerut: O(lg n) 6. Gsii un algoritm O(n lg n) care, dat fiind o mulime M cu n numere reale i un alt numr real x, determin dac exist sau nu 2 elemente n M, a cror sum este x.

[ Capitolul 3]

[ Cuprins ]

[ Capitolul 5]

Capitolul 5 Divide et Impera

Problema 1
Enun. Un arbore cartezian al unui vector este un arbore binar definit recursiv astfel:
q

rdcina arborelui este elementul cel mai mic din vector; subarborele stng este arborele cartezian al subvectorului stng (fa de poziia elementului din rdcin); subarborele drept este arborele cartezian al subvectorului drept.

Exemplu: Pentru vectorul

I V[I]

1 9

2 8

3 23

4 10

5 16

6 3

7 12

8 4

9 7

Se pune n rdcin elementul cel mai mic din vector, anume 3. Subarborele stng este la rndul lui un arbore cartezian al subvectorului V[1..5], iar cel drept al lui V[7..9]. Ca rdcin a subarborelui stng (adic fiu stnga al rdcinii), alegem elementul minim din V[1..5], adic V[2]=8. Procedeul continu recursiv pentru restul vectorului. Iat arborele rezultat:

Se d un vector cu n elemente. S se afieze muchiile arborelui su cartezian. Rezolvare. Pentru construirea arborelui folosim o procedur recursiv divide(i,j,Tata). Tata este nodul al crui subarbore este arborele cartezian al lui V[i..j]. Se determin Min, indicele elementului minim din V[i..j], i se apeleaz recursiv procedura pentru V[i..Min-1] i V[Min +1..j]. Exemplu: n=9 V=(9,8,23,10,16,3,12,4,7) Iniial procedura se apeleaz cu i=1 i j=n=9. Minimul este gsit pe poziia Min=6. Se apeleaz recursiv pentru V[i..Min-1], adic V[1..5], i pentru V[Min+1..j], adic V[7..9], cu parametrul Tata=Min=6. Analizm doar primul apel. i=1, j=5, Tata=6. Se gsete Min=2. Se afieaz muchia (V[Tata],V [Min]), adic muchia (3,8). Apelm recursiv pentru V[1..1] i V[3..5]. Not: O rezolvare O(n) a acestei probleme poate fi gsit n cartea Psihologia concursurilor de informatic de Ctlin Frncu.

program Arbore_cartezian; var V n M :array [1..100] of integer; :integer; :array [1..101,1..2] of integer;

procedure Citire; var i:integer; begin write('n= '); readln(n); for i:=1 to n do begin write('V[',i,']= '); readln(V[i]) end; end; procedure Divide(i, j, Tata :integer); var k,Min:integer; begin if i<=j then begin

Min:=i; for k:=i+1 to j do if V[k]0 then writeln(V[Min],' ',V[Tata]); Divide(i,Min-1,Min); Divide(Min+1,j,Min); end; end; begin Citire; writeln('Muchiile arborelui sunt:'); Divide(1,n,0); end.

Problema 2
Enun. Se dau un ptrat i un cerc. Se cere s se calculeze aria lor comun cu precizie de o zecimal. Coordonatele se citesc de la tastatur i sunt numere reale. Aria se va afia pe ecran. Prof. dr. Adrian Atanasiu, Paco 1998, enun reformulat Rezolvare. Distingem trei poziii relative ale ptratului fa de cerc:
q

este inclus n cerc; este exterior cercului; are o suprafa n comun cu cercul.

Primele dou cazuri se pot rezolva banal; pentru cazul al treilea vom urmri s reducem problema tot la una din situaiile 1 sau 2. Aplicnd strategia general Divide et Impera, vom calcula aria comun dintre ptrat i cerc, mprind ptratul n alte patru mai mici, pentru care vom rezolva aceeai problem. Deoarece ntregul este egal cu suma prilor sale aria comun dintre ptrat i cerc va fi egal cu suma ariilor comune dintre ptratele n care a fost mprit i acelai cerc. Totui, analiza matematic ne spune c aceast metod nu va calcula aria exact, dect dup un numr infinit de apeluri recursive. Evident, acest lucru nu este posibil. Deci va trebui s ne mulumim cu o aproximare a rspunsului cutat: aceasta se face prin renunarea la o nou divizare a ptratului n cazul n care aria acestuia este mai mic dect o constant. (n program 1e-3=0.001).

Program Cerc_si_Patrat; var cx,cy,r,x1,y1,x2,y2 :double;

Function Inside(var x,y:double):byte; begin if (sqr(x-cx)+sqr(y-cy)<=r*r) then Inside:=1 else Inside:=0 end; Function Arie(var x1,y1,x2,y2:double):double; Forward;

Function Process(var x1,y1,x2,y2:double):double; var t:byte; r:double; begin r:=abs((x1-x2)*(y1-y2)); t:=Inside(x1,y1)+Inside(x2,y1)+ Inside(x2,y2)+Inside(x1,y2); if (t in [1..3]) and (r>1e-3) then r:=Arie(x1,y1,x2,y2) else if t<>4 then r:=0; Process:=r end; Function Arie(var x1,y1,x2,y2:double):double; var mx,my :double; begin mx:=(x1+x2)/2; my:=(y1+y2)/2; Arie:=Process(x1,y1,mx,my)+Process(mx,y1,x2,my)+ Process(mx,my,x2,y2)+Process(x1,my,mx,y2) end; begin write('Cerc (x,y,r):'); readln(cx,cy,r); write('Patrat (x1,y1,x2,y2):'); readln(x1,y1,x2,y2); writeln(Arie(x1,y1,x2,y2):0:4) end.

Problema 3
Enun. Se d o tabl de dimensiuni 2nx2n. Pe aceast tabl exist o gaur la poziia (Lg,Cg). Pentru acoperirea acestei table avem la dispoziie piese de forma:

Aceste piese pot fi rotite cu 90, 180 sau 270 . Se cere s se afieze o acoperire complet a tablei (cu excepia gurii). Pe fiecare linie se vor afia 6 valori separate prin spaii:

l1 c1 l2 c2 l3 c3, reprezentnd coordonatele ptratelor din care este format fiecare pies aezat pe tabl. Olimpiada de Informatic Bucureti Faza pe sector 1995 Rezolvare. Procedura recursiv Acopera primete ca parametrii:
q

o bucat de tabl prin (L1,C1) coordonatele ptratului din colul stnga-sus de pe tabl, i (L2,C2) coordonatele ptratului din colul dreapta-jos. coordonatele gurii de pe aceast bucat de tabl, (Lg,Cg).

Urmnd strategia general Divide et Impera, la fiecare nivel descompunem problema curent n 4 subprobleme, dup care apelm procedura recursiv pentru fiecare din ele. mprim tabla n 4 buci egale. Una dintre aceste 4 buci are deja o gaur n ea. n celelalte trei buci facem cte o gaur prin aezarea unei piese la mbinarea bucilor, ca n figur. Dac se ajunge la o bucat 2x2 se oprete apelarea recursiv; se aeaz o pies pe cele 3 ptrate libere (un ptrat fiind gaur) i se revine din apel. Exemplu: N=3 Lg=3 Cg=6

Notm cele 4 sferturi ale ptratului cu cifre:

n sfertul 3 avem deja gaura iniial. Pentru a face o gaur i n celelalte trei sferturi aezm o pies la mbinarea lor, ca mai jos:

Afim coordonatele piesei pe care am aezat-o i apelm recursiv pentru cele 4 subprobleme:

program Luri; var Lg,Cg,n,i L :integer; :longint;

procedure Citire; begin write('n= '); readln(n);

write('Lg, Cg: '); readln(Lg,Cg); end; procedure Acopera(L1,C1,L2,C2:longint;Lg,Cg:integer); var Ml,Mc:longint; begin Ml:=(L1+L2) div 2; Mc:=(C1+C2) div 2; if (Lg>Ml) and (Cg>Mc) then begin writeln(Ml+1,' ',Mc,' ',Ml,' ',Mc,' ',Ml,' ',Mc+1); if L2-L1>1 then begin Acopera(L1,C1,Ml,Mc,Ml,Mc); Acopera(L1,Mc+1,Ml,C2,Ml,Mc+1); Acopera(Ml+1,C1,L2,Mc,Ml+1,Mc); Acopera(Ml+1,Mc+1,L2,C2,Lg,Cg) end; end else if (Lg>Ml) and (Cg<=Mc) then begin writeln(Ml,' ',Mc,' ',Ml,' ',Mc+1,' ',Ml+1,' ',Mc+1); if L2-L1>1 then begin Acopera(L1,C1,Ml,Mc,Ml,Mc); Acopera(L1,Mc+1,Ml,C2,Ml,Mc+1); Acopera(Ml+1,C1,L2,Mc,Lg,Cg); Acopera(Ml+1,Mc+1,L2,C2,Ml+1,Mc+1) end end else if (Lg<=Ml) and (Cg>Mc) then begin writeln(Ml,' ',Mc,' ',Ml+1,' ',Mc,' ',Ml+1,' ',Mc+1); if L2-L1>1 then begin Acopera(L1,C1,Ml,Mc,Ml,Mc); Acopera(L1,Mc+1,Ml,C2,Lg,Cg); Acopera(Ml+1,C1,L2,Mc,Ml+1,Mc); Acopera(Ml+1,Mc+1,L2,C2,Ml+1,Mc+1) end end else begin writeln(Ml+1,' ',Mc,' ',Ml+1,' ',Mc+1,' ',Ml,' ',Mc+1); if L2-L1>1 then begin

Acopera(L1,C1,Ml,Mc,Lg,Cg); Acopera(L1,Mc+1,Ml,C2,Ml,Mc+1); Acopera(Ml+1,C1,L2,Mc,Ml+1,Mc); Acopera(Ml+1,Mc+1,L2,C2,Ml+1,Mc+1) end end; end; begin Citire; L:=1; for i:=1 to n do L:=L*2; Acopera(1,1,L,L,Lg,Cg); end.

Problema 4
Enun. Pentru codificarea unei imagini de pe ecranul calculatorului, imagine presupus ptratic de dimensiuni 2nx2n, se poate folosi aa-numitul cod Oliver & Wiseman, care i ataeaz un arbore n felul urmtor (imaginea fiind construit folosind 16 culori numerotate de la 0 la 15): dac toi pixelii au aceeai culoare, atunci arborele conine un singur nod (nodul rdcin) care are asociat culoarea pixelilor respectivi; n caz contrar, imaginea se mparte n patru pri egale, de cte 2n-1x2n-1 pixeli, iar arborii corespunztori acestor patru imagini vor fi subarbori ai nodului rdcin. Nodului rdcin i se va asocia valoarea -1 (fr semnificaie de culoare), iar cele patru pri vor fi parcurse n sensul: 1234 Procedeul se repet apoi separat pentru fiecare parte, pn se obin buci de aceeai culoare. S considerm acum o imagine dat ntr-un fiier sub form de matrice, fiecare element reprezentnd culoarea pixelului de pe linia i coloana corespunztoare. Imaginea este de dimensiuni 2nx2n, n fiind dat n prima linie din fiier (imagine.in). Se cere s se construiasc arborele corespunztor imaginii date; afiarea arborelui se va face ntr-un fiier text arbore.out sub forma dat n exemplul de mai jos: Fiier de intrare:2 7271 7737 1477 7977 Fiier de ieire:

-1

-1 7 2 7 7 7 -1 7 1 3 7 -1 1 4 7 9 7
prof. Clara Ionescu Baraj, august 1994, enun modificat

Rezolvare. Aplicarea principiului Divide et Impera este deja descris chiar n enun. Dificultatea poate consta n implementarea acestui tip de afiare. Trebuie folosit o stiv, n program este chiar irul s, pentru a memora cte rnduri de linii verticale trebuie afiate, la momentul curent al recursivitii.

Program Imagine; var fil n,i,j,k s a :text; :integer; :string; :array[0..201,0..201] of byte;

Procedure Print(x1,y1,x2,y2:integer);

var f,mx,my:integer; begin f:=0; for i:=y1 to y2 do begin for j:=x1 to x2 do if a[i,j]<>a[y1,x1] then begin f:=1; break end; if f=1 then break; end; if f=0 then writeln(fil,a[y1,x1]) else begin writeln(fil,' -1'); mx:=(x1+x2) div 2; my:=(y1+y2) div 2; write(fil,s,'+'); s:=s+'- '; Print(x1,y1,mx,my); write(fil,s,'+'); s:=s+'- '; Print(mx+1,y1,x2,my); write(fil,s,'+'); s:=s+'- '; Print(x1,my+1,mx,y2); write(fil,s,'L '); s:=s+' '; Print(mx+1,my+1,x2,y2) end; dec(byte(s[0]),3) end; begin assign(fil,'imagine.in'); reset(fil); readln(fil,n); n:=1 shl n; for i:=1 to n do begin for j:=1 to n do read(fil,a[i,j]); readln(fil) end; close(fil); assign(fil,'arbore.out'); rewrite(fil); Print(1,1,n,n); close(fil) end.

[ Capitolul 4]

[ Cuprins ]

[ Capitolul 6]

Capitolul 6 Structuri de date

Algoritmul lui Tarjan


Se d un pointer ctre captul unei liste simplu nlnuite. Se cere s se spun dac aceast list cicleaz, folosindu-se spaiu de memorie O(1). O list simplu nlnuit cicleaz dac unul din elemente pointeaz ctre un element anterior lui din list. O list care nu cicleaz se va termina cu elementul NIL.

O rezolvare simpl ar fi s marcm elementele parcurse din list. Dac ntlnim un element marcat nseamn c lista cicleaz. Dezavantajul acestei metode este c necesit spaiu de memorie O(n), unde n este lungimea listei. Prezentm un algoritm descoperit de R.E. Tarjan care necesit memorie O(1). Acest algoritm folosete pentru parcurgerea listei doi pointeri: P1 i P2. Cei doi pointeri arat iniial ctre primul element din list. P2 se mic de dou ori mai repede dect P1. Adic la fiecare pas, P1 se mut la elementul urmtor din list n timp ce P2 se mut cu dou elemente. Algoritmul se termin dac P2 ajunge la sfritul listei (la elementul NIL), caz n care lista nu cicleaz, sau dac P2 l ajunge din spate pe P1, caz n care lista are un ciclu. Timpul de calcul este O(n).

program Tarjan; type nod=^nod; var L, P1, P2 :nod;

begin { Initializeaza lista } P1:=L; P2:=L^; while (P2<>P1) and (P2<>nil) do begin P2:=P2^; if (P2<>nil) and (P2<>P1) then P2:=P2^; if (P2<>P1) then P1:=P1^; end; if P2=nil then writeln('Lista nu cicleaza') else writeln('Lista cicleaza'); end.

n continuare vom prezenta o implementare bazat pe obiecte a principalelor structuri de date studiate n manual.

Lista dublu nlnuit

Lista proiectat n programul de mai jos este conceput s foloseasc, pentru informaia util din nod, orice structur creat de utilizator. Anume, programatorul i va putea construi propriul tip de date, cu oricte cmpuri i de orice dimensiuni, i va putea folosi obiectul nostru pentru a gestiona o list cu nregistrri de acest tip de date. Singura restricie este ca primele dou cmpuri din informaia din nod s fie cei doi pointeri pentru referina la nodul urmtor respectiv, anterior. Obiectul lista implementeaz toate operaiile elementare definite pentru aceast structur de date: inserie, cutare, tergere, parcurgere (numele metodelor sunt mai mult dect asemntoare). Datorit generalizrii sunt necesare cteva intervenii ale utilizatorului: n primul rnd, pentru a putea folosi obiectul, trebuie apelat constructorul Dim ce primete ca parametru lungimea n bytes a informaiei din nod (aceasta include i cei doi pointeri). Pentru folosirea metodei Cauta este necesar precizarea n

prealabil a unei funcii pentru compararea a dou noduri, aceasta realizndu-se printr-un apel al metodei SetFunc. Evident utilizatorul poate crea mai multe astfel de funcii, care s compare nregistrrile dup diferite cmpuri i s le foloseasc alternativ, apelnd SetFunc la fiecare schimbare a funciei de comparare. De asemenea, pentru parcurgerea listei este necesar precizarea unei proceduri care va fi apelat primind ca parametru, pe rnd, fiecare nregistrare din list.

Unit ListaDef; interface type ComparFunc ProcessProc Lista = Function (var = Procedure (var = Object prim,urm ds comp Constructor Procedure Procedure Procedure Procedure Function Procedure end; a,b):boolean; v); :pointer; :integer; :ComparFunc; Dim(v:integer); SetFunc(v:pointer); Adauga(var data); Parcurge(v:pointer); ParcurgeInv(v:pointer); Cauta(var data) :pointer; Sterge(v:pointer);

implementation type leg = record next, prev : pointer; end; Constructor Lista.Dim(v:integer); begin ds:=v; prim:=nil; comp:=nil; urm:=nil end; Procedure Lista.SetFunc(v:pointer); begin

comp:=ComparFunc(v) end; Procedure Lista.Adauga(var data); var t :pointer; begin getmem(t,ds); Move(data,t^,ds); leg(t^).next:=nil; leg(t^).prev:=urm; if urm<>nil then leg(urm^).next:=t else prim:=t; urm:=t end; Function Lista.Cauta(var data):pointer; var t:pointer; begin if @comp=nil then begin Cauta:=nil; exit end; t:=prim; while (t<>nil) and (not Comp(t^,data)) do t:=pointer(t^); Cauta:=t end; Procedure Lista.Parcurge(v:pointer); var t:pointer; begin t:=prim; while t<>nil do begin ProcessProc(v)(t^); t:=pointer(t^) end end; Procedure Lista.ParcurgeInv(v:pointer); var t:pointer; begin t:=urm; while t<>nil do begin ProcessProc(v)(t^); t:=leg(t^).prev end end; Procedure Lista.Sterge(v:pointer); begin

if leg(v^).next<>nil then leg(leg(v^).next^).prev:=leg(v^).prev else urm :=leg(v^).prev; if leg(v^).prev<>nil then leg(leg(v^).prev^).next:=leg(v^).next else prim:=leg(v^).next; dispose(v) end; end.

S dm un exemplu: crearea i actualizarea unei agende telefonice. nti vom defini structura de date necesar.

uses ListaDef; type ref Telefon =^telefon; =record next,prev Nume numar end; :Lista; :Telefon;

:ref; :string[20]; :longint;

var Agenda Tmp

Pentru crearea listei este necesar apelul constructorului Dim i apoi inserarea succesiv n list a nregistrrilor:

Begin Agenda.Dim(sizeof(Telefon)); repeat write('Nume:'); readln(tmp.nume); write('Numar:'); readln(tmp.numar); Agenda.Adauga(tmp) until tmp.nume=''; end.

Acum s vedem cum se realizeaz implementarea unei parcurgeri a listei. Utilizatorul i va defini o procedur ce se va ocupa cu procesarea fiecrui element din list (n cazul nostru va face doar o afiare) i apoi o va transmite metodei Parcurge:

Procedure Print(v:telefon); begin writeln(v.Nume:25,' ',v.numar:10) end; begin { . . . } Agenda.Parcurge(@Print); { . . . } end.

S presupunem c unul dintre cunoscuii notri tocmai i-a schimbat numrul de telefon. Vom fi nevoii s cutm ntregistrarea corespunztoare n list i s modificm unul dintre cmpuri. Deoarece cutarea se face dup numele respectivului, funcia construit de utilizator pentru a compara dou nregistrri va verifica doar dac cele dou nume conincid:

Var p

:ref;

Function ComparNume(a,b:telefon):boolean; begin ComparNume:=(a.nume=b.nume) end; begin { . . . } Agenda.SetFunc(@ComparNume); tmp.nume:='Cunoscut'; { aici pui numele pe care-l caui p:=Agenda.Cauta(tmp); p^.Numar:=89 { i aici noul numr { . . . } end.

} }

Un mare avantaj pe care-l prezint aceast implementare este independena de structura folosit

pentru informaia dintr-o nregistrare. Astfel, dup ce aceast aplicaie a fost n totalitate scris, dac apare nevoia modificrii structurii telefon (de exemplu adugarea unui cmp pentru memorarea adresei), aceasta se poate rezolva fr a fi nevoie de alte modificri n surs: se adaug pur i simplu n definiie noul cmp i se scrie codul necesar pentru procesarea lui.

Matricea rar

O matrice rar este o matrice, n general de dimensiuni mari, n care majoritatea elementelor au valoarea 0. Deoarece informaia util propriu-zis este de dimensiuni mult mai mici dect ale matricei, de cele mai multe ori este ineficient s folosim alocarea static (se consum prea mult memorie). Obiectul MatriceRara prezentat mai jos, realizeaz o implementare bazat pe liste simplu nlnuite: pentru fiecare rnd al matricei se ine o list a valorilor nenule pe care le conine. Exemplu: matricea

0 0 0 0 2

1 0 0 0 0

0 0 0 4 0

9 0 0 0 0

0 0 0 0 7

0 0 5 0 0

0 0 0 4 0

6 0 0 4 0

va fi reinut astfel (prima valoare din fiecare pereche reprezint coloana pe care se afl valoarea i cea de-a doua valoarea propriu-zis) :

1: 2: 3: 4: 5:

(2,1),(4,9),(8,6) (6,5) (3,4),(7,4),(8,4) (1,2),(5,7)

Cele dou metode care implementeaz operaiile fundamentale pentru matrice (Citeste i Scrie)

ofer utilizatorului posibilitatea de a accesa matricea ca i cum ar fi fost alocat static. Metoda Scrie are n sarcin actualizarea listelor, realiznd: inserarea unui nou element n list pe poziia corespunztoare (dac elementul se gsete deja n list este actualizat), tergerea din list a elementelor care primesc valoarea zero.

unit MatrDef; interface type ref nod = ^nod; = record c info next end;

: integer; : real; : ref

MatriceRara = Object l : array[1..10000] of ref; Constructor Init; Function Citeste(x,y:integer):real; Procedure Scrie (x,y:integer;v:real); end; implementation Constructor MatriceRara.Init; begin fillchar(l,sizeof(l),0) end; Function MatriceRara.Citeste(x,y:integer):real; var t:ref; begin t:=l[x]; while (t<>nil) and (t^.c>nil) and (t^.c=y) then Citeste:=t^.info else Citeste:=0 end; Procedure MatriceRara.Scrie (x,y:integer;v:real); var t,p,d :ref; begin t:=L[x]; p:=nil; while (t<>nil) and (t^.c>nil) and (t^.c=y) then

if v<>0 then t^.info:=v else begin if p=nil then L[x]:=t^.next else p^.next:=t^.next; dispose(t) end else begin if v=0 then exit; new(d); d^.next:=t; d^.c:=y; d^.info:=v; if p=nil then L[x]:=d else p^.next:=d end end; end.

Probleme propuse:
1. S se determine, folosind obiectul MatriceRara, componentele conexe ale unui graf dat prin matricea de adiacen. 2. S se realizeze obiectele Stiva i Coada prin motenirea obiectului Lista.

[ Capitolul 5]

[ Cuprins ]

[ Capitolul 7]

Capitolul 7 Tehnica Greedy

Problema 1
Enun. ntr-un depozit al monetriei statului sosesc n saci cu monezi. eful depozitului cunoate numrul de monezi din fiecare sac i ar vrea s modifice coninutul sacilor, prin mutri de monezi dintrun sac n altul, astfel nct n final, n fiecare sac s fie acelai numr de monezi. Ajutai eful depozitului s obin acelai numr de monezi n fiecare sac, prin efectuarea unui numr minim de mutri. Date de intrare: n fiierul text MONEZI.IN se va scrie pe prima linie un numr ntreg n (2 n 2000), reprezentnd numrul de saci. Pe urmtoarele n linii sunt scrise numere ntregi, reprezentnd numerele de monezi din fiecare sac (numrul total de monezi din toi sacii 1.000.000.000). Date de ieire: Pe fiecare linie a fiierului text MONEZI.OUT se vor scrie triplete de numere ntregi a b c unde:
q

a reprezint numrul de ordine al sacului din care se mut monezi; b reprezint numrul de ordine al sacului n care se mut monezi; c reprezint numrul de monezi care se mut din sacul a n sacul b.

Observaie: n cazul n care problema nu are soluie, se va scrie n fiier cuvntul: NU. Exemplu: MONEZI.IN MONEZI.OUT

3 35 48 37

2 215 233

Timp maxim de execuie: 8 secunde. Olimpiada Naional de Informatic 1998 Clasa a X-a Rezolvare. Dac suma numrului de monezi din toi sacii nu este divizibil cu numrul de saci, atunci problema nu are soluie. Altfel, calculm media numrului de monezi din saci n variabila Med, adic numrul de monezi pe care va trebui s l aib fiecare sac n final. Pentru a reine numrul de monezi din fiecare sac, folosim o list dublu nlnuit, unde fiecare nod conine, pe lng pointerii necesari, doi ntregi, reprezentnd indicele sacului n ordinea iniial i numrul de monezi din sac. Iat algoritmul folosit pentru rezolvare:
r

se elimin din list sacii care au deja Med monezi apoi, atta timp ct lista nu este vid, se mut attea monezi din sacul cel mai plin n cel mai gol, ct este nevoie pentru a rmne Med monede n acesta din urm se afieaz mutarea fcut se elimin din list sacul n care am pus monede i, dac n sacul din care am pus monede au rmas Med monede, se elimin i acesta

Trebuie precizat c algoritmul este euristic, adic soluia dat de el nu este ntotdeauna cea optim. Pentru implementare folosim obiectul Lista definit n capitolul Structuri de date.

{$F+} (* Aceast directiv de compilare este necesar pentru transmiterea ca parameteri a adreselor de proceduri *)

program Suma; uses ListaDef; type ref=^Nod; Nod=record next, prev:ref; i, mon: integer end; :Lista; :text; :integer; :ref;

var L outp Med Min, Max

procedure Citire; var tmp: Nod; i, n, suma :integer; f:text; begin L.Dim(sizeof(Nod)); assign(f,'monezi.in'); reset(f); readln(f,n); suma:=0; for i:=1 to n do begin tmp.i:=i; readln(f,tmp.mon); suma:=suma+tmp.mon; L.Adauga(tmp) end; if suma mod n<>0 then begin writeln(outp,'NU'); close(outp); halt(1) end else Med:=suma div n; close(f); end; procedure Del(var N:nod); begin if N.mon=Med then L.Sterge(@N);

end; procedure MinMax(var N:nod); begin if N.monMax^.mon then Max:=@N; end; begin assign(outp,'monezi.out'); rewrite(outp); Citire; L.Parcurge(@Del); while L.prim<>nil do begin new(Max);new(Min); Max^.mon:=0; Min^.mon:=maxint; L.Parcurge(@MinMax); writeln(outp,Max^.i,' ',Min^.i,' ',Med-Min^.mon); Max^.mon:=Max^.mon-(Med-Min^.mon); L.Sterge(Min); if Max^.mon=Med then L.Sterge(Max); end; close(outp) end.

Problema 2
Enun. Pentru plata unei sume S avem la dispoziie n tipuri de monede, printre care i moneda cu valoarea 1. S se gseasc o modalitate de plat a sumei cu un numr minim de monede. Rezolvare. Algoritmul folosit este foarte simplu. Se ia moneda cu valoarea cea mai mare i se folosete de cte ori este posibil. Apoi moneda cu valoarea imediat urmtoare i aa mai departe. Exemplu: S=13 n=3 M1=7 M2=3 M3=1 Se folosete moneda M1 de S div M1=13 div 7=1 ori. Apoi M2 de S div M2=6 div 2=3 ori. Am gsit o modalitate de plat cu 4 monede: S=13=7+2+2+2. Algoritmul folosit nu conduce tot timpul la soluia optim, dar este foarte eficient. Putem gsi uor un

contraexemplu: S=17 n=3 M1=7 M2=5 M3=1 , soluia algoritmului va avea 5 monede: S=17=7+7+1 +1+1, n timp ce soluia optim are numai 3 monede: S=17=7+5+5. Programul const dintr-o sortare i o parcurgere a vectorului de monede. Complexitatea algoritmului este O(n lg n+n)=O(n lg n).

program Plata_sumei; var M, F S, n, i :array [1..100] of integer; :integer;

procedure Citire; var i:integer; begin write('S= '); readln(S); write('n= '); readln(n); for i:=1 to n do begin write('M[',i,']= '); readln(M[i]) end end; procedure QuickSort(li, ls: integer); var i, j, x, y: integer; begin i := li; j := ls; x := M[(li+ls) div 2]; repeat while M[i] > x do i := i + 1; while x > M[j] do j := j - 1; if i <= j then begin y := M[i]; M[i] := M[j]; M[j] := y; i := i + 1; j := j - 1; end; until i > j; if li < j then QuickSort(li, j); if i < ls then QuickSort(i, ls); end; begin

Citire; QuickSort(1,n); FillChar(F,2*n,0); i:=0; while S>0 do begin i:=i+1; F[i]:=S div M[i]; S:=S-M[i]*F[i] end; writeln('Am folosit: '); for i:=1 to n do writeln(F[i],' monede de ',M[i],' lei'); end.

Problema 3
Enun. S se umple perfect o tabl de dimensiuni nxn (n 6) cu piese de urmtoarele forme:

Este obligatoriu s se foloseasc cel puin o pies din fiecare tip. Prof. Gheorghe Petrov Universitatea Timioara Concursul Naional de Informatic Lugoj 1998 Rezolvare. Vom face nti observaia c nxn trebuie s fie multiplu de 4 pentru c toate cele 7 piese sunt formate din cte 4 ptrele. Rezult c pentru a putea face acoperirea n trebuie s fie par. Iat o aezare a pieselor prin care putem umple perfect un ptrat de 6x6, folosind cel puin o pies de fiecare tip:

Se aeaz acest ptrat 6x6 n colul din stnga-sus al tablei. Apoi, ne rmn pe tabl neumplute dou dreptunghiuri de dimensiuni 6x(n-6), respectiv (n-6)xn, dup cum se vede n figur:

Aceste dou dreptunghiuri, avnd amndou dimensiunile pare, pot fi umplute numai cu ptrate (piesa 3).

program TETRIS; const Colt:array [1..6,1..6] of integer= ((2,2,4,4,4,7), (2,1,1,4,4,7), (2,6,1,4,4,7),

(6,6,1,2,4,7), (6,5,5,2,3,3), (5,5,2,2,3,3));

var n, i, j T

:integer; :array [1..100,1..100] of integer;

begin write('n= '); readln(n); for i:=1 to 6 do for j:=1 to 6 do T[i,j]:=Colt[i,j]; for i:=7 to n do for j:=1 to n do begin T[i,j]:=3; T[j,i]:=3; end; for i:=1 to n do begin for j:=1 to n do write(T[i,j],' '); writeln end end.

Problema 4
Enun. Se dau n i k, naturale, k n. S se construiasc un tablou nxn care ndeplinete simultan condiiile:

1. conine toate numerele de la 1 la n2 o singur dat; 2. pe fiecare linie numerele sunt aezate n ordine cresctoare, de la stnga la dreapta; 3. suma elementelor de pe coloana k s fie minim.
Rezolvare. Fiecare din elementele de pe coloana k trebuie s fie mai

mare dect cele k-1 elemente de pe linia sa. Rezult, c suma minim a elementelor de pe coloana k

este ordine.

. Numerele de la 1 la nk se aeaz n A[1..n,1..k] , n

program Tablou; var T n, k, i, j :array [1..100,1..100] of integer; :integer;

begin write('n, k: '); readln(n,k); for i:=1 to n do for j:=1 to k do T[i,j]:=(i-1)*k+j; for i:=1 to n do for j:=k+1 to n do T[i,j]:=(i-1)*k+j-k+T[n,k]; for i:=1 to n do begin for j:=1 to n do write(T[i,j],' '); writeln end end.

Problema 5
Enun. Se dau n puncte n plan. S se construiasc, dac este posibil, un poligon convex care s aib ca vrfuri aceste puncte. Rezolvare. Pentru ca un segment (Pi,Pj) s fie latur a unui poligon convex trebuie ca toate celelalte puncte s se gseasc ntr-unul din semiplanele determinate de dreapta-suport a segmentului.

Folosim un vector boolean Puse, unde Puse[i] este True dac am ales deja vrful I, i False, altfel. La fiecare pas al algoritmului alegem un vrf al poligonului j, dac:
q

Puse[j]=False, adic acest vrf nu trebuie s fi fost deja ales Latura(P[i-1],j])=True, unde Latura(i,j) este procedura care verific dac toate celelalte puncte sunt ntr-unul din semiplane cu ajutorul ecuaiei dreptei PiPj i P[i-1] este ultimul punct ales.

Dac nu se gsete un astfel de punct, rezult c nu se poate forma un poligon convex cu punctele date. Not: Acest algoritm are complexitatea O(n3). O metod mai eficient de rezolvare ar folosi un algoritm de determinare a nfurtorii convexe, avnd complexitatea O(n lg n).

program Poligon; type Punct=record X,Y:real end; var V n, i, j Puse P Convex :array [1..100] of Punct; :integer; :set of 1..100; :array [1..100] of integer; :boolean;

procedure Citire; var f:text; i:integer; begin assign(f,'input.txt'); reset(f); readln(f,n);

for i:=1 to n do readln(f,V[i].X,V[i].Y); close(f); end; function Sgn(P1,P2,P3:Punct):integer; var s:real; begin s:=(P3.X-P1.X)*(P2.Y-P1.Y)-(P3.Y-P1.Y)*(P2.X-P1.X); if s<0 then Sgn:=-1 else if s>0 then Sgn:=1 else Sgn:=0; end; function Latura(p1,p2:integer):boolean; var s,i:integer; bun,pr:boolean; begin pr:=true; i:=0; bun:=true; while (i>p1) and (i<>p2) then if pr then begin s:=Sgn(V[p1],V[p2],V[i]); pr:=false end else if Sgn(V[p1],V[p2],V[i])<>s then bun:=false; end; Latura:=bun; end; begin Citire; P[1]:=1; Puse:=[1]; Convex:=true; i:=1; while (in) and not Convex do begin j:=j+1; if not (j in Puse) and Latura(P[i-1],j) then begin Convex:=true; Puse:=Puse+[j]; P[i]:=j;

end; end; end; if Convex then begin write('Poligonul:'); for i:=1 to n do write(' ',P[i]); writeln; end else writeln('Nu se poate forma un poligon convex.'); end.

Problema 6
Enun. Se d un polinom P(X) cu coeficieni ntregi. S se afle toate rdcinile raionale ale polinomului. Rezolvare. Rezolvarea se bazeaz pe urmtoarea teorem:

Fie f=a0+a1X++anXn un polinom de gradul n (n 1) cu coeficieni ntregi. Dac numere prime ntre ele) este o rdcin raional a lui f atunci:

(p, q

1. p divide termenul liber a0; 2. q divide coeficientul termenului de grad maxim an.
Demonstraia acestei teoreme poate fi gsit n manualul de Algebr, clasa a X-a. S observm c cele dou condiii de mai sus, sunt necesare, dar nu neaprat i suficiente pentru ca r s fie rdcin a polinomului. De aceea, dup determinarea numerelor raionale care satisfac aceste dou condiii trebuie s i verificm dac chiar sunt rdcini ale polinomului. Aceast verificare

se face n procedura Rdcin(p,q) care practic calculeaz

program Polinom; var a p, q, n :array [0..100] of integer; :integer;

procedure Citire; var i:integer; begin write('n= '); readln(n); for i:=0 to n do begin write('a',i,'= '); readln(a[i]); end; end; function cmmdc(u,v:integer):integer; begin if v=0 then cmmdc:=u else cmmdc:=cmmdc(v,u mod v); end; function Radacina(p,q:integer):boolean; var suma, X :real; i:integer; begin X:=1; suma:=a[0]; for i:=1 to n do begin X:=X*(p/q); suma:=suma+a[i]*X; end; if abs(suma)<1e-5 then Radacina:=true else Radacina:=false; end; begin Citire; p:=1; while p*pa[n] do begin repeat q:=q+1; until a[n] mod q=0;

if (cmmdc(p,q)=1) and Radacina(p,q) then writeln(p,'/',q); end; end; end.

[ Capitolul 6]

[ Cuprins ]

[ Capitolul 8]

Capitolul 8 Programare dinamic

Problema 1
Enun. Cte cuvinte de lungime N se pot forma cu litere din alfabetul {a,b,c,d} astfel nct a i b s nu se afle pe poziii alturate ? Ioan Tomescu Probleme de combinatoric i teoria grafurilor Rezolvare. S considerm c am construit deja un cuvnt de lungime N-1, care respect condiiile problemei. Un cuvnt valid de lungime N se poate obine adugnd la sfritul celui pe care-l avem deja:
q

litera a,c sau d, dac cuvntul se termin cu a litera b,c sau d, dac cuvntul se termin cu b orice liter dac cuvntul se termin cu c sau d

Se observ c numrul de cuvinte obinute prin adugarea unei litere variaz n funcie de litera adugat dar i de ultima liter a cuvntului de la care se pornete. Vom nota cu A[i] numrul de cuvinte de lungime i care se termin n a sau b, i cu C[i] numrul de cuvinte care se termin n c sau d. Deducem urmtoarele relaii de recuren: A[N] = A[N-1] + 2 * C[N-1] C[N] = 2 * A[N-1] + 2 * C[N-1] De aici pn la implementare mai e doar un pas

Program Cuvinte; var N,i A,C begin write('N='); readln(N); A[1]:=2; C[1]:=2; :integer; :array[0..100] of comp;

for i:=2 to N do begin C[i]:=2*C[i-1]+2*A[i-1]; A[i]:=A[i-1] +2*C[i-1] end; writeln(A[N]+C[N]:20:0) end.

Problema 2
Enun. Titlul de "Campioan Absolut" al unei discipline sportive este disputat de dou echipe cu for de joc egal. Pentru desemnarea campioanei, federaia de specialitate a hotrt organizarea unui turneu n care ntre cele dou echipe s aib loc mai multe partide i s fie declarat campioan, echipa care ctig prima, n partide. Analitii sportivi sunt interesai n estimarea ansei pe care o are una dintre echipe la un moment dat oarecare al turneului, de a deveni campioan. Echipele sunt desemnate cu numerele 1 i 2. Orice partid se termin cu victoria uneia dintre echipe. n orice partid, cele dou echipe au anse egale de ctig indiferent de rezultatele din partidele anterioare. tiindu-se situaia turneului la un moment dat, i anume numarul i de partide ctigate de prima echip i numrul j de partide ctigate de a doua echip, s se calculeze probabilitatea ca echipa 1 s ctige turneul i s devin campioan. Fiierul de intrare conine pe fiecare linie cte un set de date sub form de triplete "n i j", unde n reprezint numrul de partide ce trebuie ctigate pentru a deveni campioan (n45), i numrul de partide ctigate de echipa 1, iar j numrul de partide ctigate de echipa 2. Pentru fiecare set de date trebuie afiat probabilitatea ca echipa 1 s devin campioan, cu patru zecimale exacte. Exemplu: Daca fiierul de intrare are urmtorul coninut: 3 3 5 2 3 1 3 1 1 3 3 0

Ieirea trebuie s arate ca mai jos: 1.0000 0.0000 0.5000 0.7500

Rezolvare. Pentru a ctiga, echipa 1 are nevoie de n-i victorii din cele n-i-j meciuri rmase. Secvenei de ni-j meciuri i vom asocia un arbore binar strict, astfel: fiecare nod va reprezenta numrul de meciuri necesare fiecrei echipe pentru a ctiga turneul. Un nod (i,j) va avea ca descendeni nodul (i-1,j), dac echipa unu ctig meciul, mai fiindu-i necesare i-1 victorii, i nodul (i,j-1) dac echipa unu pierde. tiind c echipele au anse egale rezult c n oricare fiu se poate ajunge cu o probabilitate de 0,5. Deci probabilitatea ca echipa unu s ctige turneul din nodul (i,j) va fi egal cu suma probabilitilor din nodurile fii nmulit cu 0,5.

Program Probabil; var n,i,j,x,y p fil begin for i:=1 to 45 do p[0,i]:=1; for i:=1 to 45 do p[i,0]:=0; for i:=1 to 45 do for j:=1 to 45 do p[i,j]:=(p[i-1,j]+p[i,j-1])/2; assign(fil,'campion.in'); reset(fil); while not eof(fil) do begin readln(fil,n,i,j); writeln(P[n-i,n-j]:4:4) end; close(Fil) end. :integer; :array[0..45,0..45] of real; :text;

Problema 3
Enun. Mai multor elevi li se cere s pun ntr-o anumit ordine un numr de n<200 cuvinte formate numai din litere mici; ordinea exprim faptul c aceste cuvinte se succed dup o anumit logic. Exemplu: platon kant marx stalin havel Logica succesiunii const n faptul c fiecare cuvnt poate fi definit folosind numai cuvintele anterioare . Fiecare elev i transmite profesorului succesiunea de cuvinte care i se pare logic. Sarcina profesorului const n a acorda fiecrui elev una din notele 1,2,3..,n corespunztoare numrului de cuvinte aezate ntr-o succesiune corect.

Pentru exemplul 1 avem urmtoarele note : marx stalin kant platon havel 3 havel marx stalin kant platon 2 havel stalin marx kant platon 1 Fiierul de intrare platon.in va avea urmtoarea form:
q

Pe prima linie apar cuvintele n succesiunea lor corect, desprite ntre ele prin cel puin un blank; Pe urmtoarele linii apar soluiile propuse de elevi.

Programul va afia pe ecran notele acordate de profesor. Rezolvare. Numrul de cuvinte aezate ntr-o succesiune corect este echivalent cu numrul maxim de cuvinte care pot fi extrase din ir, pstrnd relaia de ordine ntre ele, i care se gsesc n aceeai succesiune i n irul corect. Adic un subir cresctor maximal, ntocmai problema studiat n manual. Pentru a reduce problema exact la forma studiat, vom numerota cuvintele n ordinea corect cu numerele de la 1 la N. Deci fiecrui cuvnt i este asociat n mod unic un numr. Pentru fiecare ir de examinat vom construi irul corespunztor al numerelor asociate cuvintelor; acesta va fi reinut n vectorul A. Lungimea subirului cresctor maximal din acest ir, reprezint ntocmai nota acordat de profesor. Exemplu:

platon kant marx stalin havel 1 2 3 4 5

Pentru a evalua succesiunea havel marx stalin kant platon, i vom asocia irul 5,3,4,2,1. Subirul cresctor maximal care se poate forma este 3,4; deci nota corespunztoare va fi 2.

Program Filozofi; var fil Nume A,V N,i,j c tmp :text; :array[1..200] of string[20]; :array[0..200] of integer; :integer; :char; :string;

Function GetWord:string; var r:string; begin r:=''; read(fil,c); while c=' ' do read(fil,c); while (c<>' ') and (not eoln(fil)) do begin r:=r+c; read(fil,c) end; if c<>' ' then r:=r+c; GetWord:=r end; begin assign(fil,'platon.in'); reset(fil); N:=0; while not eoln(fil) do begin inc(N); Nume[N]:=GetWord end; readln(fil); while not eof(fil) do begin for i:=1 to N do begin tmp:=GetWord; j:=1; while tmp<>Nume[j] do inc(j); A[i]:=j end; readln(fil); for i:=1 to N do V[i]:=1; for i:=2 to N do for j:=1 to i-1 do if (A[j]<A[i]) and (V[j]+1>V[i]) then V[i]:=V[j]+1; j:=1; for i:=1 to N do if V[i]>j then j:=V[i]; writeln(j) end; close(fil) end.

Problema 4

Enun. Un subir al unui ir X1,X2,,Xn este un ir care se obine tergnd zero sau mai multe elemente din irul iniial. Elementele care se terg nu trebuie s fie neaprat pe poziii consecutive n ir. De exemplu: 2,3,2,1 este un subir al irului 2,4,3,1,2,1 el obinndu-se prin tergerea lui 4 i a primei apariii a lui 1 n irul iniial. Dndu-se dou iruri X1,X2,,Xn i Y1,Y2,,Ym un subir comun a celor dou iruri este un ir care este subir i pentru primul ir i pentru al doilea. Problema const n a gsi un subir de lungime maxim a dou iruri date. Rezolvare. S presupunem c tim deja cel mai lung subir comun al celor dou iruri de lungime N i respectiv M. S studiem ce se ntmpl cu acesta n cazul n care se mai adaug elemente celor dou iruri. n cel mai simplu caz, dac se adaug acelai element la sfritul ambelor iruri, este evident c lungimea subirului comun va crete cu 1 (elementul adugat fcnd parte din cele dou iruri va aprea i n subir comun). De exemplu, dac avem irurile 4,7,9,3 i 1,4,2,9,5,6 cel mai lung subir comun va fi 4,9. Dac adugm un 8 n finalul ambelor iruri, noua soluie va fi 4,9,8. n cazul n care se adaug un singur element la sfritul unuia din iruri se poate ntmpla, sau nu, ca lungimea subirului comun s creasc cu 1: Exemple: 1) 1,2,3,7 i 1,2,4 cu subirul comun 1,2 Dac adugm un 3 la irul al doilea noul subir comun va deveni 1,2,3 2) 1,7,2 i 1,4,2 cu subirul comun 1,2 Orice element am aduga, la oricare ir, subirul comun va pstra aceeai lungime. ntotdeauna, situaia din exemplul 1 se poate reduce la cazul n care am adugat acelai element la ambele iruri: elementele situate dup ultima valoare din subirul comun pot fi ignorate (n exemplu doar 7 este n aceas situaie), obinnd astfel dou iruri ce au acelai ultim element. Dac notm cu A[i,j] lungimea celui mai lung subir comun care se poate forma folosind doar primele i elemente ale primului ir i primele j elemente ale celui de-al doilea ir, obinem urmtoarea relaie de recuren:

A[i,j] = =

1 + A[i-1,j-1] Max ( A[i-1,j], A[i,j-1] )

dac X[i]=Y[j], altfel

Program SubsirComun; var X,Y A N,M,i,j,k :array[1..100] of integer; :array[0..100,0..100] of integer; :integer;

Function Max(m1,m2:integer):integer; begin if m1>m2 then Max:=m1 else Max:=m2 end; Procedure Print(i,j:integer); begin while X[i]<>Y[j] do if A[i-1,j]>A[i,j-1] then dec(i) else dec(j); if A[i-1,j-1]>0 then Print(i-1,j-1); write(X[i],' ') end; begin write('N='); readln(N); for i:=1 to N do begin write('X[',i,']='); readln(X[i]) end; write('M='); readln(M); for i:=1 to M do begin write('Y[',i,']='); readln(Y[i]) end; fillchar(A,sizeof(A),0); for i:=1 to N do for j:=1 to M do if X[i]=Y[j] then A[i,j]:=A[i-1,j-1]+1 else A[i,j]:=Max(A[i-1,j],A[i,j-1]); writeln(A[N,M]); Print(N,M); writeln end.

Problema 5
Enun. Se consider mulimea format din numerele naturale mai mici sau egale dect N (N 39). O astfel de mulime se poate partiiona n dou submulimi care au aceeai sum. De exemplu, dac N=3, mulimea {1,2,3} se poate mpri n {1,2} i {3}. Se cere s se calculeze numrul de astfel de partiionri tiind c nu are importan ordinea mulimilor dintr-o soluie ({1,2} i {3} reprezint aceeai soluie ca {3} i {1,2}). Fiierul de intrare input.txt conine pe prima linie numrul N.

Ieirea se va face n output.txt i va consta n numrul de partiionri. Exemplu: Pentru N=7, sunt patru soluii: {1,6,7} i {2,3,4,5} {2,5,7} i {1,3,4,6} {3,4,7} i {1,2,5,6} {1,2,4,7} i {3,5,6} USACO, Spring Open 1998 Rezolvare. Problema este analog cu problema rucsacului. Vom ine un vector S[i] de dimensiune egal cu suma maxim care se poate obine ntr-o submulime adic jumtate din suma primelor N numere naturale, deci N*(N-1)/4. S[i] va reprezenta numrul de moduri n care se poate obine suma i (trebuie remarcat c n situaia n care suma primelor N numere este impar, este imposibil s formm dou submulimi cu sume egale, deci numrul de soluii va fi 0). Vectorul S este iniializat cu 0, doar S[0] va primi valoarea 1. Vectorul va fi completat progresiv, la fiecare etap considerndu-se un nou numr, i care va spori numrul de soluii obinute folosindu-le doar pe primele i-1. De exemplu, dac pn la etapa curent avem S[5]=3 i S[9]=8, iar i=4 (am gsit deja 3 moduri de a forma suma 5 i 8 de a forma suma 9) atunci putem s adugm numrul 4 la oricare din cele 3 soluii gsite pentru suma 5, i vom obine nc 3 soluii cu suma 9. Deci numrul de modaliti de a forma suma 9 va crete exact cu numrul de sume gsite pentru 5: S[9]=S[9]+S[5].

Program Submultimi; var fil N,i,j,k S begin fillchar(S,sizeof(S),0); assign(fil,'INPUT.TXT'); reset(fil); readln(fil,N); close(fil); assign(fil,'OUTPUT.TXT'); :text; :integer; :array[0..1000] of comp;

rewrite(fil); k:=(N*N+N) div 2; if odd(k) then begin writeln(fil,0); close(fil); halt end; S[0]:=1; for i:=1 to N do for j:=k-i downto 0 do if S[j]<>0 then S[j+i]:=S[j+i]+S[j]; writeln(fil,S[k div 2]/2:0:0); close(fil) end.

Problema 6
Enun. Pentru o mulime dat de K numere prime S={p1,p2,... pK}, s considerm mulimea tuturor numerelor ale cror factori primi formeaz o submulime a lui S. Ea conine de exemplu printre altele numerele p1, p1p2, p1p1, p1p2p3 (cnd K3). Aceasta este mulimea "numerelor subjugate" pentru mulimea de intrare S. Vom considera aceast mulime ordonat crescator: {H1,H2, .. }. Atenie: prin definiie 1 este declarat un numr nesubjugat. Problema cere s se afle al N-lea numr subjugat HN pentru o mulime dat S. Fiierul de intrare (humble.in) conine dou linii: K N p1 p2 .. pK Limite: 1 K 100, 1 N10.000. Ieirea se va face pe ecran i va consta ntr-un singur numr, HN. Exemplu: Pentru intrarea:

4 19 2 3 5 7 Ieirea este:27 USA Computing Olympiad, Aprilie 1997 Rezolvare. Vom genera n ordine primele N numere subjugate. Acestea vor fi reinute n vectorul V. S presupunem c cunoatem deja primele i numere subjugate i ne intereseaz s-l aflm pe cel cu numrul de ordine i+1. Pornind de la cele pe care le cunoatem, putem s nmulim fiecare din cele i numere subjugate cu fiecare din cele k numere prime. Astfel vom obine nc i k numere subjugate, i n mod sigur cel de-al i+1 lea (pe care-l cutm) se va gsi printre ele. Deci dintre toate numerele generate n ultima etap l vom pstra pe cel mai mic dintre ele care este mai mare dect V[i] (al i-lea numr subjugat). Pentru a optimiza acest proces de calculare a urmtorului numr subjugat, vom ine seama de urmtoarea observaie: tim c numerele din V sunt sortate i, deci, dac le vom nmuli pe toate cu acelai numr prim, pj vom obine, tot un ir de valori sortate cresctor; dintre acestea cel mult unul poate candida la poziia V[i+1], i anume cel mai mic, mai mare dect V[i]. Deci vom mai folosi nc un vector pi[j] care va memora indicele celui mai mic numr subjugat, care nmulit cu pj va da o valoare mai mare dect V[i]. Evident, acest vector va trebui actualizat la fiecare selecie a unui nou numr subjugat.

Program Numere_subjugate; var fil N,K,i,j,f,min p V pi begin assign(fil,'HUMBLE.IN'); reset(fil); readln(fil,K,N); for i:=1 to K do read(fil,P[i]); close(fil); v[0]:=1; for i:=1 to K do pi[i]:=0; for i:=1 to N do begin min:=maxlongint; for j:=1 to K do if V[pi[j]]*P[j] < min then Min:=V[pi[j]]*P[j]; V[i]:=min; for j:=1 to K do while V[pi[j]]*P[j]<=V[i] do inc(pi[j]) end; :text; :longint; :array[1..100] of integer; :array[0..10000] of longint; :array[1..100] of longint;

writeln(V[N]) end.

Problema 7
Enun. Se construiete un limbaj nou, avnd urmatoarele elemente:
q q

un sistem format din n stri, n 200. un alfabet, format din caractere litere mici din alfabetul englez; o stare iniial; o mulime de stri finale; o funcie care descrie evoluia strilor n funcie de caracterele introduse n alfabet.

Un ir de caractere formeaz un cuvnt, al limbajului descris mai sus, dac pornind de la starea iniial, urmrind funcia de evoluie se ajunge la o stare final. Cerine: Datele de intrare se citesc din: - fiierul diction.txt avnd urmtoarea structur: n c c c ... c r 1 2 3 i k k k ... kl 1 2 3 s ... s 1,1 1,i1 s ... s 2,1 2,i2 s ... s n*r,1 n*r,in*r numrul de stri; reprezentnd caracterele alfabetului, desprite prin spaiu; numrul de ordine al strii iniiale; 1 i n numrul de ordine al strilor finale, numere naturale desprite prin spaiu; 0 < l n; pe urmtoarele n*r linii se descrie funcia de evoluie astfel: - pe primlele n linii pentru c evoluia prin cele n stri, amd. 1 - "0" desemneaz mulimea vid

- fiierul cuvinte.txt conine pe m linii cuvinte:cuvnt_1cuvnt_2....cuvnt_m n fiierul cuvinte.txt se vor introduce cuvinte formate doar din caracterele permise (cele introduse n diction.txt). Datele de ieire se scriu n fiierul limbaj.txt, care are structura: - Pe m linii se scrie YES sau NO, dup cum cuvntul de pe linia corespunztoare este sau nu cuvnt al limbajului realizat. Exemplu:

diction.txt: 4 a b 1 3 4 2 3 4 0 4 3 1 3 2 4 0 1

cuvinte.txt abb aabaa aaab bbb abaa

Fiierul de ieire este limbaj.txt:

YES YES NO YES YES

Timp maxim de execuie: 10 sec/test, pentru 586/133MHz.

Prof. Maria i Adrian Ni. Prof. Aniko Sos Liceul Teoretic "Emanuil Gojdu" Oradea Rezolvare. Deci: funcia primete ca parametri starea curent i un caracter, iar rezultatul este tot o stare care poate fi aleas arbitrar din lista corespunztoare parametrilor primii (citim n r linii cu stri). Funcia va fi memorat n matricea A: A[i,j]^[k] va fi starea cu indicele k din lista corespunztoare rezultatelor funciei pentru parametrii i i j (i este stare, j este caracter). A[i,j]^[0] va fi egal cu numrul de rezultate posibile ale funciei pentru parametrii i,j (corespunde valorilor i , i , , i din schema de intrare). Strile finale vor 1 2 n*r fi memorate n vectorul sf. Pentru a testa un cuvnt vom folosi doi vectori auxiliari, s1 i s2. Acetia vor fi completai ntr-un numr de etape egal cu lungimea cuvntului. Dup i etape, s [j] va fi egal cu 1 dac, pornind din starea iniial, exist un 1,2 drum de lungime i care s aduc sistemul n starea j. Deci, la etapa 0 vom avea doar s1[stare iniial] =1, celelalte valori din vector fiind iniializate cu 0; la etapa 1 se parcurge vectorul s1 i pentru fiecare valoare de 1 ntlnit, se completeaz n s2 toate strile n care funcia poate ajunge avnd ca parametri : starea corespunztoare valorii 1, i caracterul din cuvnt egal cu numrul etapei, adic 1. n final s2 este copiat n s1

pentru a putea efectua aceleai operaii i la etapa urmtoare. Dup ce s-a parcurs ntreg cuvntul, rspunsul l gsim prin simpla verificare a strilor finale n vectorul s1: dac cel puin uneia din strile finale i corespunde n s1 o valoare 1 atunci, cuvntul face parte din limbaj.

Program Limbaj; type vec=array[0..200] of byte; var fil,fo N,i,j,si,nf,r,f s c A sf,tmp,s1,s2 begin assign(fil,'DICTION.TXT'); reset(fil); readln(fil,N); while not seekeoln(fil) do begin read(fil,c); while c=' ' do read(fil,c); s:=s+c end; readln(fil,si); nf:=0; r:=length(s); while not seekeoln(fil) do begin inc(nf); read(fil,sf[nf]) end; readln(fil); fillchar(A,sizeof(A),0); for i:=1 to r do for j:=1 to n do begin f:=0; while not eoln(fil) do begin inc(f); read(fil,tmp[f]) end; getmem(A[j,s[i]],f+1); move(tmp,A[j,s[i]]^,f+1); if (f=1) and (tmp[1]=0) then A[j,s[i]]^[0]:=0 :text; :integer; :string; :char; :array[1..200,'a'..'z'] of ^vec; :vec;

else a[j,s[i]]^[0]:=f; readln(fil) end; close(fil); assign(fil,'CUVINTE.TXT'); reset(fil); assign(fo, 'LIMBAJ.TXT'); rewrite(fo); while not seekeof(fil) do begin readln(fil,s); fillchar(s1,sizeof(s1),0); s1[si]:=1; for i:=1 to length(s) do begin fillchar(s2,sizeof(s2),0); for j:=1 to N do if (s1[j]=1) and (A[j,s[i]]<>nil) then for f:=1 to A[j,s[i]]^[0] do s2[A[j,s[i]]^[f]]:=1; s1:=s2 end; f:=0; for i:=1 to nf do if s2[sf[i]]=1 then f:=1; if f=1 then writeln(fo,'YES') else writeln(fo,'NO') end; close(fil); close(fo) end.

Problema 8
Enun. Se consider o mulime ordonat de iruri de N bii. Biii, evident, pot lua valoarea 1 sau 0. Mulimea conine toate irurile posibile de N bii care au cel mult L bii de 1. Vi se cere s tiprii al C-ulea ir de bii din aceast mulime, n ordinea dat de numerele n baza 10 ce corespund irurilor de bii. Intrare: Fiierul input.txt conine trei numere ntregi separate printr-un spaiu: N, L i C. Ieire: Fiierul output.txt va conine o singur linie, cu un singur numr, anume al C-ulea ir de bii din mulime. Exemplu: input.txt:

5 3 19 output.txt: 10100 USACO, Spring Open 1998 Rezolvare. Vom calcula o matrice M cu urmtoarea semnificaie: M[i,j] este egal cu numrul de iruri de i bii, care au cel mult j bii de 1. Presupunnd c cunoatem toate valorile acestei matrici pentru indici mai mici dect i i j, dorim s calculm M[i,j]. Un ir de i bii evident va fi obinut prin adugarea unui bit la un ir de i-1 bii. Dac bitul adugat este 1 rezult c n irul de i-1 pot fi maxim j-1 bii de 1, deoarece n total trebuie s avem maxim j bii de 1. n schimb dac adugm un bit 0, n irul de lungime i-1, se pot afla cel mult j bii de 1. Deci rezult formula de recuren: M[i,j]=M[i-1,j-1]+M[i-1,j]. Biii din irul cutat vor fi dedui, ncepnd cu cel mai semnificativ, folosind urmtorul raionament: dac pe poziia curent am aeza un bit de 1, atunci ar mai rmne N-1 bii de aflat dintre care L-1 bii 1, iar noul C, actualizat pentru restul irului de bii, ar avea valoarea C-M[N-1,L] (aceasta deoarece noul nostru ir va conine doar L-1 bii de 1, deci toate irurile cu L bii de 1 trebuie eliminate). Deci valoarea din C va evolua pe parcursul determinrii biilor, indicnd n fiecare moment, al ctelea ir trebuie gsit folosind doar N bii din care i doar L de 1. Deoarece C trebuie s fie n permanen mai mare dect zero i mai mic dect M[Ni,Li] rezult i c de fiecare dat cnd C-M[N-1,L] este mai mare dect zero va trebui s aezm un bit 1, n caz contrar valoarea din C va depi numrul maxim de iruri care se pot crea n condiiile rezultate (adic: punnd un bit 0, C va rmne constant depaind astfel valoarea M[Ni-1,Li-1]).

Program Bizzi; var M N,L,i,j C fil :array[0..32,0..33] of comp; :longint; :comp; :text;

begin assign(fil,'INPUT.TXT'); reset(fil); read(fil,N,L,C); close(fil); fillchar(M,sizeof(M),0); for i:=0 to N do begin M[i,0]:=1; M[0,i]:=1 end; for i:=1 to N do begin for j:=1 to i do M[i,j]:=M[i-1,j]+M[i-1,j-1]; for j:=i+1 to N do M[i,j]:=M[i,i] end;

assign(fil,'OUTPUT.TXT'); rewrite(fil); j:=L; for i:=N downto 1 do if C>M[i-1,j] then begin write(fil,1); C:=C-M[i-1,j]; dec(j) end else write(fil,0); writeln(fil); close(fil) end.

[ Capitolul 7]

[ Cuprins ]

[ Capitolul 9]

Capitolul 9 Branch and Bound

Problema 1
Enun. Se consider un ptrat cu NxN csue. Fiecare csu conine un numr ntre 1 i N*N-2. Dou csue sunt ocupate cu numrul 0. Fiecare numr natural, diferit de 0, apare o singur dat n cadrul ptratului. tiind c 0 i poate schimba poziia cu orice numr aflat deasupra, la dreapta, la stnga sau jos, n raport cu poziia n care se afl numrul 0, se cere s se precizeze irul de mutri prin care se poate ajunge de la o configuraie iniial la o configuraie final. Se cere de asemenea ca acest ir s fie optim, n sensul c trebuie s se ajung la configuraia final ntr-un numr minim de mutri. Rezolvare. Relum aici problema ptratului, discutat i n manual, pentru a familiariza cititorul cu o nou modalitate de implementare a tehnicii Branch and Bound, modalitate ce va fi folosit i n celelalte rezolvri. Evident algoritmul rmne acelai care este cunoscut cititorului din parcurgerea manualului. Cele cteva modificri n implementare vizeaz, n special, eficientizarea cutrii n spaiul soluiilor, reducerea timpului de calcul, dar i scurtarea codului. Dup cum se cunoate, secvena de cod care se execut cel mai frecvent const n extragerea din lista configuraiilor a aceleia care minimizeaz efortul parcurs i cel estimat (suma g+h) i expandarea acestei configuraii. Deci operaiile cele mai frecvente din algoritm sunt cele care manipuleaz o list. Deoarece pentru date de intrare mai mari, timpul de calcul crete considerabil, aceast list a configuraiilor devine i ea foarte mare, ajungnd s conin chiar zeci de mii de noduri. Dat fiind faptul c operaiile cu pointeri sunt destul de lente, iar lista fiind att de mare, rezult c cea mai mare parte din timpul de calcul este consumat de operaiile de parcurgere, inserare i tergere din list. Va trebui s gsim o cale de reducere a numrului de astfel de operaii dar i a timpului consumat de ele. O prim idee simpl ar fi s folosim o singur list, care s conin i nodurile expandate i cele neexpandate, i pentru a le distinge s folosim un nou cmp care s indice tipul nodului. n acest mod am elimina toate operaiile de tergere din lista open (a nodurilor neexpandate) i de inserare n lista close. Aceast secven s-ar reduce doar la schimbarea unei valori din false n true. Dar aceast schimbare nu aduce o mbuntire substanial pentru c prezint un dezavantaj esenial: verificarea existenei unui nod n list, ca i operaia de extragere a minimului din lista open va consuma un timp de calcul sporit pentru c acum este necesar parcurgerea ntregii liste, deci i a nodurilor deja expandate. Vom ncerca s nlturm i acest neajuns. Tehnica ce urmeaz a fi prezentat pornete de la o idee

de bun-sim, i anume: dac trebuie s cutm un nod ntr-o list foarte mare, de ce s nu mprim lista cea mare, dup o regul bine stabilit, n multe liste mai mici? Astfel n loc s cutm ntr-o list cu 20.000 de noduri vom cuta ntr-una cu 10 de noduri. Aceast tehnic se numete hash table sau tabele de dispersie1, iar regula bine stabilit este de fapt o funcie numit hash, care nu este nici injectiv, nici surjectiv, i de care depinde n mare msur reuita acestei metode. S analizm aceast funcie: rolul ei este de a asocia n mod determinist fiecrui nod, pe baza informaiilor care-l individualizeaz, o valoare ntreag cuprins ntre 0 i hn - o constant care reprezinz numrul listelor mici (numite buckets) care vor fi folosite. Pentru a fi eficient o astfel de funcie trebuie s asigure o distribuie ct mai uniform a nodurilor n liste, i deci s minimizeze timpul unei cutri. n general, datorit diversitii problemelor, nu exist o formul standard pentru aceast funcie, dar, innd cont de cteva observaii, se poate construi destul de uor o funcie hash bun: n primul rnd trebuie calculat, folosind o metod arbitrar, o valoare ct mai mare (ct ncape n reprezentarea intern) care s depind de configuraia respectiv; rezultatul funciei hash va fi egal cu aceast valoare modulo hn. hn trebuie s fie un numr prim pentru a obine rezultate ct mai bune. Acum s particularizm: funcia hash folosit aici, asociaz fiecrei csue din ptrat un numr prim care este memorat in matricea np (pot fi folosite i numere generate aleator cu condiia ca n cazul extrem valoarea calculat s nu depeasc reprezentarea); apoi se nsumeaz ptratele produselor dintre valoarea aflat pe acea csu (ntre 0 i N*N-2) i numrul prim corespunztor; n final se calculeaz modulo hn. Valoarea aleas pentru hn este 4999 care este un numr prim i uor de reinut. Cum se efectueaz operaiile cu lista? Se reine un vector de pointeri v[i] care indic primul element din lista i. Pentru inserarea unui nod se va calcula k=Hash(nod) i nodul respectiv se va insera n lista v[k]. Cutarea unui nod se va desfura n mod analog: se calculeaz din nou k i se parcurge lista v[k], care, n general, nu trebuie s aib mai mult de zece elemente. Totui a rmas o operaie care este la fel de consumatoare de timp ca i nainte, anume extragerea minimului care necesit parcurgerea tuturor elementelor din toate listele. Aceasta poate fi optimizat destul de simplu: vom mai reine un vector de pointeri M[i] care va indica nodul neexpandat din lista i cu suma g+h minim. Aceasta implic, evident, nite operaii n plus: pentru a pstra proprietatea de minim a nodului referit de M la fiecare inserare se va verifica daca nodul curent are valoarea g+h mai mic dect cea aflat la M[k], iar la fiecare extragere de minim va trebui parcurs lista v[k] pentru a selecta noul minim ce trebuie referit de M. Aceste operaii sunt implementate n procedurile AddNode i, respectiv, GetMin. Deci extragerea minimului se va face ntr-un timp constant indiferent de numrul de noduri (este necesar doar o parcurgere a vectorului M). Cutarea unui nod va necesita parcurgerea unei singure liste v[I]. Trebuie observat c timpul de calcul necesar ambelor operaii depinde de hn: prima necesit exact hn pai, iar cea de-a doua, un numr de pai egal, pe medie, cu raportul dintre numrul

total de noduri generate i hn. Este clar c alegerea lui hn determin eficiena programului: dac numrul de noduri obinute din expandarea unei configuraii este mic, atunci ntr-un timp constant se vor efectua mai multe extrageri de minim i ar fi recomandat ca hn s primeasc o valoare mai mic (pentru a reduce timpul consumat de cutarea minimului); n caz contrar, se vor efectua multe operaii de cutare i deci hn va lua o valoare mai mare pentru a minimiza lungimea listelor v[i].

Program Patrat; type ref nod =^nod; =record a n,p g,h e end;

:array[1..6,1..6] of byte; :ref; :integer; :boolean;

const np:array[1..36] of longint= (2,3,5,7,11,13,17,19,23,29,31,37,41,43, 47,53,59,61,67,71,73,79,83,89,97,101,103, 107,109,113,127,131,137,139,149,151); hashno=4999; var fil i,j,n Ci,Cf,minh v,M :text; :integer; :ref; :array[0..4998] of ref;

Function Hash(p:ref):integer; var i,j:integer; t :longint; begin t:=0; for i:=1 to n do for j:=1 to n do t:=t+sqr(p^.a[i,j]*np[37-(i-1)*n-j]); Hash:=t mod hashno end; Procedure H(p:ref); var i,j,x,y:integer; begin p^.h:=0;

for i:=1 to n do for j:=1 to n do if p^.a[i,j]<>Cf^.a[i,j] then for x:=1 to n do for y:=1 to n do if p^.a[i,j]=Cf^.a[x,y] then begin inc(p^.h,abs(i-x)+abs(j-y)); x:=n; y:=n end end; Procedure AddNode(p:ref); var t,t2:integer; z :ref; begin p^.e:=false; t:=Hash(p); if v[t]<>nil then z:=v[t] else z:=nil; v[t]:=p; v[t]^.n:=z; if M[t]=nil then M[t]:=p else if p^.g+p^.h<M[t]^.g+M[t]^.h then M[t]:=p end; Function GetMin:ref; var f,i:integer; t :ref; begin GetMin:=nil; i:=-1; for f:=0 to hashno-1 do if M[f]<>nil then if i=-1 then i:=f else if M[f]^.g+M[f]^.h < M[i]^.g+M[i]^.h then i:=f; GetMin:=M[i]; m[i]:=nil; t:=v[i]; while t<>nil do begin if (not t^.e) then if m[i]=nil then m[i]:=t else if M[i]^.g+M[i]^.h>t^.g+t^.h then M[i]:=t; t:=t^.n

end end; Function Equal(p1,p2:ref):boolean; var i,j:integer; begin Equal:=true; for i:=1 to n do for j:=1 to n do if p1^.a[i,j]<>p2^.a[i,j] then Equal:=false end; Procedure PrintSol(f:ref); var i,j:integer; begin if f^.p<>nil then PrintSol(f^.p); for i:=1 to n do begin for j:=1 to n do write(f^.a[i,j],' '); writeln end; readln end;

Procedure Expand(minh:ref); var i,j,x,y,q :integer; t,f :ref; begin minh^.e:=true; for i:=1 to n do for j:=1 to n do if minh^.a[i,j]=0 then for x:=-1 to 1 do for y:=-1 to 1 do if (x=0) xor (y=0) then if (i+x in [1..n]) and (j+y in [1..n]) then begin new(t); t^:=minh^; inc(t^.g); t^.p:=minh; t^.n:=nil; t^.e:=false; q:=t^.a[i,j]; t^.a[i,j]:=t^.a[i+x,j+y]; t^.a[i+x,j+y]:=q; H(t);

q:=Hash(t); f:=v[q]; while (f<>nil) do begin if Equal(f,t) then break; f:=f^.n end; if f=nil then AddNode(t) else if f^.g>t^.g then begin f^.p:=minh; f^.g:=t^.g; if not f^.e then if f^.g+f^.h < m[q]^.g+m[q]^.h then m[q]:=f end else dispose(t) end end;

begin assign(fil,'PATRAT.IN'); reset(fil); readln(fil,n); new(Ci); new(Cf); for i:=1 to n do begin for j:=1 to n do read(fil,Ci^.a[i,j]); readln(fil); end; for i:=1 to n do begin for j:=1 to n do read(fil,Cf^.a[i,j]); readln(fil) end; close(fil); for i:=0 to hashno-1 do begin v[i]:=nil; M[i]:=nil end; H(Ci); Ci^.g:=0; Ci^.p:=nil; Ci^.e:=false;

AddNode(Ci); minh:=GetMin; while minh^.h>0 do begin Expand(minh); minh:=GetMin end; writeln(minh^.g,' mutari.'); PrintSol(minh) end.

Problema 2
Enun. Se d un tablou MxN (1 M+N 15) cu elemente numere ntregi. Se numete "operaie" n acest tablou nmulirea unei linii sau coloane cu -1. S se determine cea mai mic secven de operaii care aduce tabloul la o form n care toate sumele de linii sau coloane dau numere nenegative. Intrare: Fiierul de intrare, numit "semne.in" este de forma: m n a11 a12 ... a1n a21 a22 ... a2n .............. am1 am2 ... amn Ieire: Rezultatele vor fi n fiierul "semne.out", sub forma: k - numrul minim de operaii x x2 ... xk - unde xi este de forma lt sau ct, lt (ct) reprezentnd 1 schimbarea semnului pe linia (coloana) t. Linia a doua reprezint secvena de operaii care conduc la rezultat.

Atenie: se vor scrie nti operaiile pe linii, ordonate cresctor, urmate de operaiile pe coloane. Dac sunt mai multe soluii, se va lista una din ele. Observaie: pentru k=0, a doua linie este goal. Exemplu:Pentru intrarea 5 3 4 -2 2 3 -1 15 -22 0 -3 4 1 -3 5 -3 2 ieirea este: 2 c2 l3 Timp de execuie: 10 secunde/test Semne prof. dr. univ. Adrian Atanasiu "Marele premiu PACO", Bucureti, 20-21 iunie 1997, clasa X

Rezolvare. Deoarece rezolvarea se bazeaz pe aceeai linie cu precedenta, vom evidenia doar cteva aspecte care in strict de particularitatea problemei. Fiindc reconstituirea soluiei va necesita precizarea operaiilor efectuate suntem nevoii s adugm informaiei din nod nc dou cmpuri ml i mc care vor preciza ce operaie a fost fcut pe linie sau coloan pentru a ajunge n configuraia curent. Dup cum se tie, pentru a asigura optimalitatea soluiei, funcia euristic trebuie n mod obligatoriu s fie optimist adic s prezic un efort mai mic dect cel real. n cazul de fa construirea unei funcii care s fie optimist indiferent de configuraie este mai dificil i ar necesita un timp de calcul considerabil. Problema const n faptul c o singur operaie poate schimba semnul mai multor sume, i deci o configuraie care are majoritatea sumelor negative poate s conduc la soluie ntr-un numr de pai mai mic dect cel al liniilor i coloanelor cu sum mai mic dect zero. De aceea a fost aleas o metod mai puin determinist, dar care d rezultate bune n practic: se aproximeaz numrul de operaii necesare cu jumtate din numrul liniilor i coloanelor cu sum negativ.

Program Semne; type ref rec =^rec; =record a n,p ml,mc e g,h end;

:array[1..15,1..15] of integer; :ref; :byte; :boolean; :integer

var fil n,m,i,j gn,nn l,o t,b,sp const hn p :text; :integer; :integer; :array[-1..4999] of ref; :ref;

=4999; :array[0..14] of integer= (2,3,5,7,11,13,17,19,23,29,31,37,41,47,51);

function Sgn(i:integer):integer; begin if i>0 then sgn:=1 else sgn:=-1 end; Function Hash(t:ref):integer; var i,j,f,r:integer; begin r:=0; for i:=1 to m do for j:=1 to n do r:=r+sgn(t^.a[i,j])*sqr(p[((i-1)*j) mod 15]); Hash:=abs(r) mod hn end; Procedure H(t:ref); var i,j:integer; s :longint; begin t^.h:=0; for i:=1 to m do begin

s:=0; for j:=1 to n do s:=s+t^.a[i,j]; if s < 0 then inc(t^.h) end; for i:=1 to n do begin s:=0; for j:=1 to m do s:=s+t^.a[j,i]; if s<0 then inc(t^.h) end; if t^.h<>0 then inc(t^.h); t^.h:=round(t^.h*0.5) end; Procedure Add(p:ref); var k:integer; begin inc(nn); k:=hash(p); if l[k]=nil then inc(gn); p^.n:=l[k]; l[k]:=p; if o[k]=nil then o[k]:=p else if o[k]^.g+o[k]^.h>p^.g+p^.h then o[k]:=p; end; Function Getmin:ref; var f,I :integer; r :ref; begin r:=nil; i:=-1; for f:=0 to hn do if o[f]<>nil then begin if i=-1 then i:=f else if o[f]^.g+o[f]^.h < o[i]^.g+o[i]^.h then i:=f; end; GetMin:=o[i]; o[i]^.e:=true; r:=l[i]; o[i]:=nil; while (r>>nil) do begin if r^.e=false then begin

if o[i]=nil then o[i]:=r else if o[i]^.g+o[i]^.h>r^.g+r^.h then o[i]:=r end; r:=r^.n end; end; Function Egal(p1,p2:ref):boolean; var i,j :integer; begin Egal:=true; for i:=1 to m do for j:=1 to n do if p1^.a[i,j]<>p2^.a[i,j] then Egal:=false end; Procedure Expand(b:ref); var i,j,k:integer; t,q :ref; begin for i:=1 to m do begin new(t); t^:=b^; t^.e:=false; inc(t^.g); for j:=1 to n do t^.a[i,j]:=-t^.a[i,j]; H(t); t^.p:=b; t^.n:=nil; t^.ml:=i; t^.mc:=0; k:=Hash(t); q:=l[k]; while q<>nil do begin if egal(t,q) then break; q:=q^.n end; if q<>nil then if q^.g>t^.g then q^:=t^ else dispose(t) else Add(t) end; for j:=1 to n do begin

new(t); t^:=b^; t^.e:=false; inc(t^.g); for i:=1 to m do t^.a[i,j]:=-t^.a[i,j]; H(t); t^.p:=b; t^.n:=nil; t^.ml:=0; t^.mc:=j; k:=Hash(t); q:=l[k]; while q<>nil do begin if egal(t,q) then break; q:=q^.n end; if q<>nil then if q^.g>t^.g then q^:=t^ else dispose(t) else Add(t) end end; Procedure Sol(b:ref); var i,j,f :integer; q :ref; l,c :array[0..15] of byte; begin assign(fil,'SEMNE.OUT'); rewrite(fil); for i:=0 to 15 do begin l[i]:=0; c[i]:=0 end; q:=b; while q<>nil do begin l[q^.ml]:=1-l[q^.ml]; c[q^.mc]:=1-c[q^.mc]; q:=q^.p end; f:=0; for i:=1 to 15 do f:=f+l[i]+c[i]; writeln(fil,f); for i:=1 to 15 do if l[i]=1 then write(fil,'L',i,' '); for i:=1 to 15 do if c[i]=1 then write(fil,'C',i,' '); close(fil) end; begin for i:=-1 to hn do begin l[i]:=nil; o[i]:=nil end;

assign(fil,'SEMNE.IN'); reset(fil); readln(fil,m,n); if (m+n>15) or (m+n<1) then begin writeln('date invalide.'); halt end; new(t); t^.n:=nil; t^.p:=nil; t^.e:=false; t^.ml:=0; t^.mc:=0; t^.g:=0; for i:=1 to m do begin for j:=1 to n do read(fil,t^.a[i,j]); readln(fil) end; close(fil); H(t); Add(t); repeat b:=getmin; if b^.h<>0 then expand(b) until b^.h=0; sol(b) end.

Problema 3

Enun. Fie urmtoarea tabl de joc:

constnd din dou ptrate de latur N=4, care au un ptrel comun, D4, n figura de mai sus. Pe tabl sunt aezate piese notate cu 'X' i 'O', aezate simetric fa de D4. Jocul const n a schimba ntre ele locurile ocupate de piesele 'O i 'X', ntr-un numr minim de mutri. O pies poate fi mutat ntr-o ptric liber nvecinat ei, sau prin sritura peste o alt pies alturat dac se ajunge ntr-o ptric liber. Pentru configuraia de mai sus n ptrica D4 se poate ajunge din: D3, D2, D5, D6, C4, B4,E4, F4. Nu sunt permise mutri pe oblic. Fiierul de intrare joc.in conine poziia pieselor notate cu 'X' (lucru suficient, avnd n vedere aezarea simetric a celor notate cu 'O' fa de D4). Se cere schimbarea ntre ele a poziiilor pieselor 'X' cu 'O' ntr-un numr ct mai mic de mutri. Prin mutare se nelege deplasarea unei piese o singur dat ntr-o alt ptric. Mutrile se vor vizualiza pe ecran, simulnd jocul. n acelai timp se vor contoriza mutrile.

Exemplu: (pentru figura dat) fiierul joc.in: D3 D2 E4 E3 E2 F4 F3 F2 Timp de executie 20 sec/test pentru 586 la 133MHz prof. Maria i Adrian NI Liceul "Emanuil Gojdu" Oradea

Rezolvare. Funcia euristic se bazeaz tot pe distana Manhattan, cu cteva modificri necesare, ca i la problema anterioar, pentru a mpiedica funcia s devin pesimist, adic s supraestimeze numrul de mutri necesare, fapt ce ar conduce la obinerea unor rezultate neoptime. Pentru a eficientiza procesul de estimare, vom folosi o tabel precalculat nm cu semnificaia: nm[p,I,j] este egal distana Manhattan de la poziia i,j pe care se afl o pies de tipul p (0 sau 1 - X sau O) pn la cea mai apropiat csu destinaie pentru piesele de tipul p. Deoarece aceste distane nu depind de configuraia pieselor, matricea va fi de fapt o constant (iniializat n procedura Citire). O alt matrice constant folosit este valid care indic dac poziia i,j aparine sau nu tablei de joc.

Program XsiO; uses crt; const hn=2999; ps:string=' XO'; type tabla =array[1..7,1..7] of byte;

ref nod

=^nod; =record a n,t g,h e end;

:tabla; :ref; :byte; :boolean;

var v,min valid nm i,j n,final z hv :array[0..4999] of ref; :array[-1..9,-1..9] of boolean; :array[0..3,1..7,1..7] of byte; :longint; :nod; :ref; :array[1..7,1..7] of longint;

Procedure Citire; var fil :text; c1,c2 :char; i,j,x,y,f,t :integer; begin assign(fil,'JOC.IN'); reset(Fil); while not eof(fil) do begin readln(fil,c1,j); i:=byte(Upcase(c1))-64; n.a[i,j]:=1; final.a[i,j]:=2; n.a[8-i,8-j]:=2; final.a[8-i,8-j]:=1 end; close(fil); for f:=1 to 2 do for i:=1 to 7 do for j:=1 to 7 do if valid[i,j] then begin nm[f,i,j]:=255; for x:=1 to 7 do for y:=1 to 7 do if valid[x,y] then if (abs(x-i)+abs(y-j) < nm[f,i,j]) and (f+final.a[x,y]=3) then nm[f,i,j]:=abs(x-i)+abs(y-j); if nm[f,i,j]<>0 then inc(nm[f,i,j],(abs(i-4)+abs(j-4)) div 2);

nm[f,i,j]:=round(nm[f,i,j]*0.8) end end; Function Hash(p:ref):integer; var i,j,k,c,t:longint; begin t:=0; for i:=1 to 7 do for j:=1 to 7 do if valid[i,j] then begin c:=1; for k:=1 to p^.a[i,j] do c:=c*hv[i,j]; t:=t+c end; Hash:=t mod hn end; Procedure H(p:ref); var i,j:integer; begin p^.h:=0; for i:=1 to 7 do for j:=1 to 7 do if valid[i,j] then p^.h:=p^.h+nm[3-p^.a[i,j],i,j] end; Procedure AdNod(p:ref); var i:integer; t:ref; begin i:=Hash(p); if Min[i]=nil then Min[i]:=p; if Min[i]^.g+Min[i]^.h>p^.g+p^.h then Min[i]:=p; p^.n:=v[i]; v[i]:=p end; Function GetMin:ref; var i,r:integer; k:ref; begin r:=0; while Min[r]=nil do inc(r); for i:=r+1 to hn-1 do if (Min[i]<>nil) and (Min[i]^.g+Min[i]^.h < Min[r]^.g+Min[r]^.h) then r:=i; Min[r]^.e:=true; GetMin:=Min[r];

Min[r]:=nil; k:=v[r]; while k<>nil do begin if not k^.e then begin if Min[r]=nil then Min[r]:=k; if k^.g+k^.h < Min[r]^.g+Min[r]^.h then Min[r]:=k end; k:=k^.n end end; Function Egal(p1,p2:ref):boolean; var i,j:integer; begin Egal:=false; for i:=1 to 7 do for j:=1 to 7 do if valid[i,j] then if p1^.a[i,j]<>p2^.a[i,j] then exit; Egal:=true end; Procedure Expand(p:ref); var i,j,di,dj,k :integer; w,q,f :ref; Procedure Proces(w:ref); begin w^.e:=false; w^.t:=p; inc(w^.g); H(w); k:=Hash(w); q:=V[k]; f:=nil; while q<>nil do begin if q^.h=w^.h then if Egal(q,w) then begin f:=q; break end; q:=q^.n end; if f=nil then AdNod(w) else begin if f^.g>w^.g then begin f^.g:=w^.g; f^.t:=p; f^.e:=false; if Min[k]=nil then Min[k]:=f; if Min[k]^.g+Min[k]^.h>f^.g+f^.h then Min[k]:=f end; Dispose(w)

end end; begin for i:=1 to 7 do for j:=1 to 7 do if p^.a[i,j]<>0 then for di:=-1 to 1 do for dj:=-1 to 1 do if (di=0) xor (dj=0) then begin if Valid[i+di,j+dj] and (p^.a[i+di,j+dj]=0) then begin new(w); w^:=p^; w^.a[i,j]:=0; w^.a[i+di,j+dj]:=p^.a[i,j]; Proces(w) end; if Valid[i+2*di,j+2*dj] and (p^.a[i+di,j+dj]<>0) and (p^.a[i+2*di,j+2*dj]=0) then begin new(w); w^:=p^; w^.a[i,j]:=0; w^.a[i+2*di,j+2*dj]:=p^.a[i,j]; Proces(w) end end end; Procedure Print(p:ref); begin if p^.t<>nil then Print(p^.t); for i:=1 to 7 do for j:=1 to 7 do begin gotoxy(3+4*i,2+2*(8-j)); if p^.a[i,j]<>0 then write(ps[1+p^.a[i,j]]) else write(' ') end; gotoxy(1,24); write('Numr Mutri:',p^.g); readln end; Procedure Sol(p:ref); var i,j,ti,tj:integer; fil:text; begin ClrScr; for i:=1 to 7 do for j:=1 to 7 do if Valid[i,j] then begin

ti:=3+4*i; tj:=2+2*(8-j); gotoxy(ti-1,tj-1); gotoxy(ti-1,tj+1); gotoxy(ti-2,tj); gotoxy(ti+2,tj); end; Print(p) end; begin

write('---'); write('---'); write('|'); write('|')

randomize; for i:=1 to 7 do for j:=1 to 7 do hv[i,j]:=1+Random(200)+Random(200); fillchar(v,sizeof(v),0); fillchar(min,sizeof(min),0); fillchar(n.a,sizeof(tabla),0); fillchar(final.a,sizeof(tabla),0); fillchar(valid,sizeof(valid),0); for i:=4 to 7 do for j:=1 to 4 do begin valid[i,j]:=true; valid[8-i,8-j]:=true end; Citire; n.t:=nil; n.g:=0; n.e:=false; H(@n); AdNod(@n); repeat z:=GetMin; if z^.h<>0 then Expand(z) until z^.h=0; Sol(z) end.

Problema 4
Enun. Fie dou inele n care sunt aezate echidistant 13 bilue, ca n figur. Inelele au dou bilue n comun, i deci n total vor fi 24 de bilue din care 11 negre, 11 albe i dou gri. Fiecare inel se poate roti n sens orar cel puin o poziie i cel mult 12, antrennd cu el cele 13 bilue din interiorul su.

Problema const n a aduce o configuraie iniial la configuraia din figur ntr-un numr minim de rotiri. Datele de intrare se citesc din fiierul bilute.in i conin dou linii, pe fiecare aflndu-se un ir de 13 caractere ce codific aezarea biluelor astel: A,N i G semnific o bilu de culoare alb, neagr, respectiv gri. Primul caracter din ir corespunde ntotdeauna biluei comune din partea superioar a figurii, celelalte urmnd parcurgerea inelului n sens orar. Primul inel va fi cel ce conine n figur biluele albe. Deci configuraia din figur va fi reprezentat astfel: GAAAAAAAAGAAA GNNNGNNNNNNNN Ieirea se va face pe ecran, tiprind configuraia biluelor dup fiecare rotire.

Problem dat la selecia lotului pentru Balcaniad, Sibiu 1996 enun reformulat

Rezolvare. Programul de mai jos este o implementare simpl a tehnicii Branch and Bound, fr nici o optimizare suplimentar. Absena tabelelor de dispersie nu este motivat de structura diferit a informaiei din noduri: chiar dac nu mai avem de-a face cu numere, o funcie hash eficient poate fi construit uor: o idee ar fi s unim cele dou iruri obinnd 26 de elemente, fiecare din ele putnd lua trei valori; apoi putem interpreta acest nou ir ca pe un numr n baza 3 pe care-l vom impri modulo la hn. Este bine de tiut c memoria disponibil pe heap, cea n care se aloc nodurile, poate fi aflat folosind funcia MemAvail. Aceasta poate servi la oprirea programului, n cazul n care nu mai este suficient memorie liber, i tiprirea unui mesaj corespunztor. Program Bilute; type sir ref nod =string[13]; =^nod; =record s1,s2 n,t g,h e end;

:sir; :ref; :integer; :boolean;

var b,i,k cc min :ref; :sir; :integer;

Procedure H(p:ref); var s,f:integer; begin s:=0; for f:=1 to 13 do begin if (p^.s1[f]='N') and (f in [2..9,11..13]) then s:=s+2; if (p^.s2[f]='A') and (f in [2..4,6..13]) then s:=s+2; end; if p^.s1[1]<>'G' then s:=s+1; if p^.s2[5]<>'G' then s:=s+1; p^.h:=s end; Procedure Citire; var fil :text; begin new(B);

b^.t:=nil; b^.n:=nil; b^.g:=0; b^.e:=false; assign(fil,'BILUTE.IN'); reset(fil); readln(fil,b^.s1); readln(fil,b^.s2); close(fil); h(b) end; Procedure Shift(var s:sir;p:integer); var f,n :integer; begin n:=length(s); for f:=1 to p do s:=s[n]+Copy(s,1,n-1) end; Procedure Sol(p:ref); begin if p^.t<>nil then Sol(p^.t); writeln(p^.s1,' ',p^.s2) end; Procedure Expand(p:ref); var z,t,q,cut :ref; f,i :integer; begin t:=b; while t^.n<>nil do t:=t^.n; p^.e:=true; for f:=1 to 12 do for i:=1 to 2 do begin new(z); z^.e:=false; z^.t:=p; z^.n:=nil; z^.g:=p^.g+1; z^.s1:=p^.s1; z^.s2:=p^.s2; if i=1 then begin shift(z^.s1,f); z^.s2[5]:=z^.s1[10]; z^.s2[1]:=z^.s1[1]

end else begin shift(z^.s2,f); z^.s1[10]:=z^.s2[5]; z^.s1[1]:=z^.s2[1] end; h(z); q:=b; cut:=nil; while q^.n<>nil do begin if (q^.s1=z^.s1) and (q^.s2=z^.s2) and (q^.g>z^.g) then begin q^.t:=p; cut:=q end; q:=q^.n end; if cut=nil then begin t^.n:=z; cut:=t^.n; t:=t^.n end else dispose(z); if cut^.h=0 then begin Sol(cut); readln; halt end end end; begin Citire; repeat min:=maxint; i:=b; k:=b; while i^.n<>nil do begin if (i^.h+i^.g<min) and (not i^.e) then begin min:=i^.h+i^.g; k:=i end; i:=i^.n end; expand(k) until memavail<500; writeln(Nu exista solutie) end.

Problem propus
Enun. Pe o tabl de ah nxn se gsesc p cai i k obstacole amplasate ntr-o configuraie iniial.

Micarea calului este cea cunoscut de la ah. Calul poate sri un obstacol sau un alt cal, dar nu poate staiona pe o poziie ocupat de un cal sau de un obstacol. Problema cere ca dvs. s determinai, dac exist, un set de micri ale cailor care permit ajungerea ntr-o configuraie final cerut. Exemplu: Configuraie iniial (n=3, p=2, k=1)

Configuraia final:

Observaii: 1. Configuraia iniial se citete din fiierul text in.txt. Prima linie a fiierului conine numrul n. Fiecare linie, din urmtoarele n, conine n caractere (O pentru obstacol, C pentru cal, S pentru csu neocupat) reprezentnd configuraia iniial. Urmtoarele n linii rein configuraia final. Pentru exemplul anterior avem: 3 COC SSS SSS SOS SSS CSC 2. Programul va scrie soluia, dac exist, n fiierul out.txt. Primele n linii vor fi date de configuraia iniial, urmeaz o linie vid, urmeaz n linii ale primei configuraii intermediare, o linie

vid, , n linii ale configuraiei finale. 3. Valorile p i k se deduc din configuraia iniial 5. Dac nu exist soluie, prima linie a fiierului va conine NU 6. Timp de execuie: 1 minut. Micarea cailor prof. Tudor Sorin, Concurs cl. XI-XII, Lugoj 1998

[ Capitolul 8 ]

[ Cuprins ]

[ Capitolul 10 ]

Capitolul 10 Teoria grafurilor

Reele de transport
Aa cum putem abstractiza harta strzilor folosind un graf orientat pentru a gsi cel mai scurt drum ntre dou noduri, putem de asemenea s interpretm un graf orientat ca pe o reea de transport i s-l folosim pentru a rspunde unor ntrebri despre transportul de material. Imaginai-v traseul materialului printr-un sistem de la o surs, unde este produs, la o destinaie, unde este consumat. Sursa produce materialul ntr-un anumit ritm i destinaia l consum n acelai ritm. Transportul de material n oricare punct din sistem este, intuitiv, ritmul n care materialul se mic. Reelele de transport pot fi folosite ca model teoretic pentru reelele de conducte, pri ale liniilor de asamblare, curentul care circul prin reelele electrice, informaia care circul prin reelele de comunicaie. Fiecare arc dintr-o reea poate fi imaginat ca o conduct prin care circul material. Fiecare conduct are o capacitate specificat, care reprezint ritmul maxim la care materialul poate circula prin conduct, ca de exemplu 2000 metri cubi de lichid pe or printr-o conduct, sau curent electric de 20 amperi care poate trece printr-un conductor. O reea de transport G=(V,E), cu sursa s i destinaia t, este un graf orientat n care fiecare muchie (u,v) E are o capacitate pozitiv c(u,v) 0. Dac (u,v) E, vom considera c(u,v)=0. Nodurile dintr-o reea, n afar de s i t, sunt doar punctele de intersecie ale conductelor: materialul trece prin ele fr a fi colectat. Adic ritmul n care materialul intr ntr-un nod trebuie s fie egal cu ritmul n care materialul iese din nod. Aceasta este proprietatea de conservare a fluxului i este aceeai ca i legea lui Kirchhoff pentru cazul n care materialul este curentul electric (flux este echivalent cu ritm, semnificnd o anumit cantitate ntr-o anumit perioad de timp). Un flux n G este o funcie f:VxVR care are urmtoarele proprieti:
q

0 f c , adic fluxul transportat pe orice arc trebuie s fie nenegativ i subcapacitar; i,j i,j

jV fj,i = jV fi,j , iV-{s,t}, adic conservarea fluxului.

Dac f este un flux n reeaua G=(V,E), atunci se numete valoarea fluxului f numrul: v(f) =

f - f jV j,t jV t,j

V(f) poate fi interpretat ca fiind fluxul net care ajunge n ieirea reelei sau fluxul net care intr n reea prin s.

Problema fluxului maxim


Se d reeaua G=(V,E) cu sursa s i destinaia t, i funcia capacitar c. Se cere s se determine un flux de valoare maxim.

Fie P un drum oarecare n graful suport al lui G (acelai graf n care se ignor sensurile arcelor), i e=vivj o muchie a lui P; dac e corespunde arcului vivj al lui G, e se numete arc direct al drumului P; dac e corespunde arcului vjvi al lui G, atunci e se numete arc invers. De exemplu, n graful de mai sus, s considerm c drumul P trece prin nodurile 1,2,4,5,6,7. Fiecare din muchiile (1,2), (2,4), (4,5) i (6,7) sunt arce directe ale drumului P pentru c au acelai sens de parcurgere ca i cel din reea; n schimb muchia (5,6) a drumului P este un arc invers.

Fie P un drum i vivj o muche din P. Se numete capacitatea rezidual a lui vivj numrul: r(ij) = ci,j fi, dac vivj este arc direct n P j fj,i dac vivj este arc invers n P n exemplul de mai sus, primul numr de pe arc reprezint capacitatea arcului, iar cel de-al doilea, fluxul. Astfel, capacitatea rezidual a arcului (4,5), r(4,5) va fi egal cu c4,5 f4,5 = 8 3 = 5. Pentru arcul (5,6) vom avea: r(5,6) = f6,5 = 4. Capacitatea rezidual a drumului P se noteaz cu r(P) i este egal cu minimul dintre capacitile reziduale ale muchiilor din P: r(P) = min
eP

r(e)

Fie P={4,5,6}. Cum r(4,5)=5 i r(5,6)=4 rezult c r(P)=4. Se numete drum de cretere a fluxului f n reeaua G, un drum de la s la t care are capacitatea rezidual mai mare ca zero. n exemplul nostru, s lum drumul 1,2,4,3,6,7 i s vedem dac este un drum de cretere: avem r (1,2)=8, r(2,4)=1, r(4,3)=5, r(3,6)=3 i r(6,7)=6, deci capacitatea rezidual a drumului este 1; rezult c drumul nostru este un drum de cretere. Teorema 1: Un flux f este de valoare maxim ntr-o reea G, dac i numai dac, nu exist drumuri de cretere a fluxului f n reeaua G. Teorema 2: Dac toate capacitile sunt ntregi, atunci exist un flux de valoare maxim cu toate componentele ntregi. Aceste dou teoreme stau la baza algoritmului Ford-Fulkerson pentru determinarea unui flux de valoare maxim. Primul pas al algoritmului const n determinarea unui flux iniial admisibil, adic fixarea valorilor lui f pentru fiecare arc astfel nct s se respecte proprietile fluxului (cele din definiie). Un astfel de flux admisibil este i cel care are flux 0 pe fiecare arc. Se verific uor c cele dou proprieti sunt respectate: fluxul este subcapacitar pe orice arc, iar conservarea fluxului este evident: n fiecare nod intr 0 uniti i ies de asemenea 0.

La pasul doi se va determina un drum de cretere P. Acesta se poate gsi printr-o simpl parcurgere n adncime, mergnd ntotdeauna doar pe arcele care au capacitatea rezidual mai mare ca zero. 1 Pornind de la fluxul anterior i folosind drumul de cretere gsit se va obine un nou flux, f de valoare mai mare dect f, astfel: fie r(P) capacitatea rezidual a drumului gsit; pentru fiecare arc direct din P se va mri fluxul cu r(P), iar pentru arcele inverse fluxul va fi sczut tot cu r(P). S dm un exemplu: pentru drumul de cretere 1,2,4,5,6,7, avem r(P)=1; deci noile valori ale fluxului vor fi: f1,2=3+1=4, f2,4=3+1=4, f4,5=3+1=4, f5,6=4-1=3 i f6,7=0+1=1. Faptul c r(P) este egal cu minimul dintre capacitile reziduale ale arcelor din P, ne asigur c pentru orice e=(u,v) P, avem r(e) r(P), adic pentru arcele directe se mai poate aduga r(P) flux fr a depi capacitatea arcului, iar pentru arcele inverse se poate scdea r(P) fr a obine un 1 flux negativ. Deci noul flux f va respecta prima proprietate a fluxului; s vedem dac va respecta i conservarea fluxului.

Deoarece arcele pot fi de dou tipuri, vom ntlni patru cazuri, ca n figur (sensul de parcurgere este de la stnga la dreapta). S le analizm pe rnd: a) ambele arce sunt arce directe, deci fluxul va crete pe ambele cu aceeai cantitate r(P); dac egalitatea din legea conservrii fluxului era verificat, va fi de asemenea verificat i dup modificarea fluxului: practic vom aduga aceeai cantitate ambilor termeni; b) primul arc este invers, iar cel de-al doilea este direct, deci pe primul arc fluxul va scdea, iar pe al doilea va crete; deoarece ambele arce ies din nod, iar fluxul de pe unul crete, iar de pe cellalt scade cu aceeai cantitate, rezult c suma a tot ce iese din nod va rmne constant i deci fluxul se va conserva;

c) analog cu b); d) ambele arce sunt inverse; de aceast dat se va scdea aceeai cantitate din ambii termeni ai egalitii conservrii fluxului, deci egalitatea se va pstra. Acest pas se va repeta atta timp ct exista un drum de cretere; n final, conform teoremei 1, fluxul gsit va fi maxim. Finalitatea algoritmului este asigurat de faptul c la fiecare iteraie valoarea fluxului crete cu o cantitate mai mare dect zero; cum valoarea fluxului maxim este finit rezult c este necesar un numr finit de iteraii ale pasului 2. Programul prezentat mai jos urmrete ntocmai algoritmul descris. Parcurgerea n adncime este realizat recursiv n procedura FindFlow; aceeai procedur se ocup de modificarea fluxului dup ce a fost gsit un drum de cretere, operaie care se face la ntoarcerea din recursie. n final valoarea fluxului se calculeaz prin nsumarea fluxului de pe arcele care pornesc din surs; fluxul pe fiecare arc se va gsi n matricea f. Datele de intrare se citesc din fiierul flux.in, i au formatul: n m numrul de noduri i, respectiv, de muchii i j c capacitatea arcului de la i la j ; 1 1 1 1 I,j i j c capacitatea arcului de la i la j . m m I,j m m

Se consider c sursa reelei este nodul 1, iar destinaia este nodul N.

Program Ford_Fulkerson_Flux; type matr var c,f n,i,m,j,t fil found seen :^matr; :longint; :text; :boolean; :set of byte; =array[1..100,1..100] of longint;

Function FindFlow(i,min:longint):longint; var p,tmp:longint; begin seen:=seen+[i]; FindFlow:=min; if i=n then found:=true else for p:=1 to n do if (not (p in seen)) and (not found) then begin if (c^[i,p]-f^[i,p])>0 then begin if c^[i,p]-f^[i,p]<min then min:=c^[i,p]-f^[i,p]; tmp:=FindFlow(p,min); if found then begin f^[i,p]:=f^[i,p]+tmp; FindFlow:=tmp end end; if (f^[p,i]>0) and (not found) then begin if f^[p,i]<min then min:=f^[p,i]; tmp:=FindFlow(p,min); if found then begin f^[p,i]:=f^[p,i]-tmp; FindFlow:=tmp end end end end; begin new(c); fillchar(c^,sizeof(c^),0); new(f); fillchar(f^,sizeof(f^),0); assign(fil,'flux.in'); reset(fil); readln(fil,n,m); for t:=1 to m do readln(fil,i,j,c^[i,j]); close(fil); repeat found:=false; seen:=[]; FindFlow(1,maxlongint) until not found; j:=0; for i:=1 to n do if c^[1,i]<>0 then j:=j+f^[1,i]; writeln('Flux maxim:',j) end.

S cercetm complexitatea algoritmului descris: fiecare iteraie a pasului doi necesit O(M) operaii (M=|E|, numrul de muchii din reea); n schimb, numrul de iteraii nu poate fi precizat cu exactitate, dar trebuie considerat c, pentru cazul cel mai defavorabil cnd la fiecare iteraie fluxul crete cu o unitate, acesta este limitat de valoarea fluxului maxim U. Obinem complexitatea algoritmului O(M U). Aceasta reprezint un dezavantaj, deoarece valoarea fluxului poate fi considerat infinit n raport cu dimensiunea problemei N (chiar pentru N=5, putem avea U=100.000.000, astfel s-ar obine un timp de calcul inadmisibil).

De exemplu, n reeaua de mai sus, considerm c M are o valoare foarte mare. Deoarece algoritmul nu alege drumurile de cretere n mod preferenial, se poate ntmpla ca drumurile alese s fie alternativ 1,2,3,4 i, respectiv, 1,3,2,4, fiecare dintre ele avnd capacitatea rezidual 1. Deci la fiecare iteraie valoarea fluxului va crete cu 1, fiind necesare n total 2M iteraii. Acest dezavantaj poate fi remediat prin mbuntirea adus algoritmului Ford-Fulkerson de ctre Edmonds i Karp. Aceasta const n simpla nlocuire a parcurgerii n adncime cu o parcurgere n lime, asigurnd astfel c la fiecare pas se alege drumul de cretere care are cel mai mic numr de muchii. Este demonstrat c, folosind aceast mbuntire, numrul de iteraii al pasului doi nu poate fi mai mare dect M N/2. Astfel se obine o complexitate de O(N M2). Prezentm mai jos i o implementare care se bazeaz pe aceast modificare. Pentru parcurgerea n lime este folosit o coad; aceasta este memorat n vectorul Q, iar i1 i i2 vor indica primul, respectiv ultimul element din coad. Vectorul T reine tatl nodului de pe poziia i din coad, iar MM [i] este egal cu capacitatea rezidual a drumului de la surs la nodul i.

Program Flux_Edmonds_Karp; type

matr var fil c,f n,m,i,j,k,i1,i2 Q,T MM seen flux

=array[1..100,1..100] of longint;

:text; :^matr; :integer; :array[1..300] of integer; :array[1..100] of longint; :set of byte; :longint;

Function Min(m1,m2:longint):longint; begin if m1<m2 then min:=m1 else min:=m2 end; begin assign(fil,'flux.in'); reset(fil); readln(fil,n,m); new(c); fillchar(c^,sizeof(c^),0); new(f); fillchar(f^,sizeof(f^),0); for k:=1 to m do readln(fil,i,j,c^[i,j]); close(fil); flux:=0; k:=0; repeat seen:=[1]; i1:=1; i2:=1; T[1]:=0; MM[1]:=maxlongint; while (not (n in seen)) and (i1<=i2) do begin j:=Q[i1]; inc(i1); for i:=1 to n do if not (i in seen) then begin if (c^[j,i]-f^[j,i]>0) then begin inc(i2); Q[i2]:=i; T[i]:=j; MM[i]:=Min(MM[j],c^[j,i]-f^[j,i]); seen:=seen+[i] end; if (f^[i,j]>0) then begin inc(i2);

Q[1]:=1;

Q[i2]:=i; T[i]:=-j; MM[i]:=Min(MM[j],f^[i,j]); seen:=seen+[i] end end end; if n in seen then begin flux:=flux+MM[n]; i:=n; while i<>1 do begin j:=abs(T[i]); if T[i]>0 then inc(f^[j,i],MM[n]) else dec(f^[i,j],MM[n]); i:=j end end; inc(k) until not (n in seen); writeln('Flux maxim:',flux) end.

Cititorul este invitat s testeze ambele variante pentru date de intrare mari (N=100, M>3000, i capaciti ct mai apropiate de maxlongint) i s compare timpii de execuie.

Problem propus

Se consider un graf orientat cu N noduri. Se dau gradele interioare i gradele exterioare pentru fiecare nod. S se determine o dispunere a arcelor n graf, astfel nct nodurile s aib gradele date.

Indicaie:

Se va construi o reea cu 2*N+2 noduri, astfel: fiecare din cele N noduri va fi reprezentat de dou ori prin nodul i i nodul i; de la fiecare nod i se va duce un arc de capacitate 1 la fiecare nod j, cu j i. Deoarece din fiecare nod trebuie s plece de(i) muchii (gradul exterior al nodului) fiecare nod i va avea un arc de la surs cu capacitatea de(i), iar fiecare nod i va avea un arc spre destinaie de capacitate di(i) (gradul interior). Dac n aceast reea se poate determina un flux maxim de valoare m=de(1)+de(2)+ ++de(n)=di(1)+di(2)+ ++di(n), atunci din fiecare nod i vor pleca exact de(i) arce, iar n fiecare nod i vor intra exact di(i) arce (dac ntr-un nod i intr k uniti de flux, avnd la ieire doar arce de capacitate 1, se va distribui pe k arce distincte).

Cuplaj maxim ntr-un graf bipartit


Un graf G(V,M) este bipartit dac mulimea vrfurilor sale V poate fi partiionat n dou submulimi V1 i V2, astfel nct oricare muchie din M are un capt n V1 i cellalt capt n V2. Iat un exemplu de graf bipartit:

Un cuplaj ntr-un graf G(V,M) este o submulime C E, astfel nct oricare dou muchii din C sunt neadiacente (nu au un capt comun). Un vrf v V este cuplat dac este incident la una din muchiile cuplajului; altfel, v este necuplat. Exemple de cuplaje pe graf general i bipartit (muchiile cuplajului sunt desenate cu linia ngroat):

Un cuplaj maxim ntr-un graf G(V,M) este un cuplaj de cardinalitate maxim, adic un cuplaj C, astfel nct pentru orice cuplaj C1, C C1 , unde C este numrul de muchii al cuplajului C.

Avem graful G=(V,M) i cuplajul C n acest graf. Un lan alternant n G este un lan ale crui muchii sunt alternativ n C i n M-C. Teorema lui Berge: Un cuplaj C este maxim dac i numai dac nu exist un lan alternant ntre oricare dou vrfuri necuplate.

Dac avem un cuplaj C ntr-un graf G i un lan alternant P ntre dou vrfuri necuplate: P={n1,c1,n2, c2,...,n}, unde nk

C i ck

C, atunci, prin scoaterea muchiilor ck din cuplaj i introducerea n

cuplaj a muchiilor nk se obine un cuplaj C1 cu o muchie n plus fa de C. Din acest motiv un lan alternant ntre dou vrfuri necuplate se mai numete i drum de cretere relativ la C.

Algoritmul nostru pentru determinarea cuplajului maxim ntr-un graf bipartit se bazeaz pe gsirea drumurilor de cretere i creterea cuplajului pe aceste drumuri. Cnd nu mai exist un astfel de drum cuplajul este maxim conform teoremei lui Berge. Pentru eficiena implementrii vom gsi iniial un cuplaj oarecare printr-o metod Greedy pe care l vom mbunti ulterior pn cnd nu mai exist drumuri de cretere, adic cuplajul este maxim. Exemplu: n graful din figur, determinm iniial printr-o metod Greedy oarecare un cuplaj cu 2 muchii:

Gsim drumul de cretere 5-3-6-2-4-1 i incrementm cuplajul pe acest drum obinnd un cuplaj maxim:

Determinarea unui drum de cretere se face printr-o cutare n lime sau n adncime. Pornim cutarea de la un nod necuplat i mergem alternativ pe muchii din cuplaj i din afara lui pn gsim un alt nod necuplat. n cazul n care graful nu este conex, trebuie repetat operaia de cretere a cuplajului pentru fiecare component conex n parte. Numai cnd nu se mai poate crete cuplajul n nici o component conex, avem un cuplaj maxim. Detalii de implementare a algoritmului: Graful va fi memorat sub form de list de noduri cu ajutorul variabilelor Graf, n, i NrV. n este numrul de noduri al grafului. Nodul i are NrV[i] vecini memorai n Graf[i]. Nodurile 1..k formeaz prima partiie, adic partea stng a grafului, i nodurile k+1..n partea dreapt. Prima operaie a programului este citirea grafului prin procedura CitireGraf. Urmeaz determinarea componentelor conexe printr-o parcurgere n adncime a fiecrei componente conexe. Procedura care face aceast parcurgere n adncime este DFS1. Vectorul Comp reine componenta conex din care face parte fiecare nod. C este numrul de componente conexe al grafului. Procedura Greedy determin un cuplaj iniial oarecare. Apoi, atta timp ct cuplajul nu este maxim, se ncearc gsirea unui drum de cretere n fiecare component. Procedura care caut acest drum de cretere este DFS2. Nodul de plecare pentru procedur trebuie s fie un nod necuplat. Dac nu se gsete un nod necuplat sau un drum de cretere n componenta conex curent, atunci cuplajul asociat acestei componente este maxim. Vectorul Max este de tip boolean; Max[i] reine dac cuplajul asociat componentei conexe i este maxim sau nu. Dac toate cuplajele componentelor sunt maxime variabila Maxim devine TRUE i programul se termin cu afiarea cuplajului.

Vectorul Cuplat reine cuplajul; astfel, Cuplaj[i] este nodul cu care este cuplat nodul i. Numrul de muchii din cuplaj este reinut n variabila M. S analizm complexitatea acestui algoritm. Notm cu m, numrul de muchii al grafului i cu n, numrul de noduri. O parcurgere n adncime ntr-un graf este O(m+n). Chiar dac se face o parcurgere n adncime pentru fiecare component conex, complexitatea adunat a parcurgerilor este tot O(m+n). S vedem acum complexitatea fiecrei operaii efectuate de program:
q

operaia de citirea a grafului este O(m) determinarea componentelor conexe este O(m+n), fiind vorba de o parcurgere n adncime a fiecrei componente procedura Greedy este O(m) fiecare cutare a unui drum de cretere este O(m+n). Dac notm cu M, numrul de muchii al cuplajului maxim i cu M1, numrul de muchii al cuplajului iniial, operaia de cutare a unui drum de cretere se va efectua de M-M1 ori.

Adunnd, rezult o complexitate total O((M-M1)(m+n)+(m+n)+m+m). Termenul dominant este (M-M1) (m+n). Dup cum observm timpul de calcul al algoritmului depinde de ct de bun este cuplajul iniial. n cazul cel mai defavorabil, cuplajul iniial va avea 0 muchii i cel final, n, caz n care timpul de calcul va fi O(n (m+n)). n practic ns, dac se folosete un algoritm greedy bun, cuplajul determinat de acesta este foarte aproape de cuplajul maxim. De exemplu, cu 2-3 muchii mai puin dect cuplajul maxim, la un graf cu 10000 de noduri. Timpul de calcul n acest caz se apropie de O(m+n).

program Cuplaj_Maxim; type Graf=array [1..100,1..100] of integer; var G n, k, C, M, i, j NrV, Comp, Cuplat Parcurs Primul, GasitDrum Gasit, Maxim Max procedure CitireGraf; :Graf; :integer; :array [1..100] of integer; :array [1..100] of boolean; :boolean; :boolean; :array [1..100] of boolean;

var F :text; nrvec, i, j :integer; begin assign(F,'input.txt'); reset(F); readln(F,n); readln(F,k); for i:=1 to n do begin read(F,nrvec); NrV[i]:=nrvec; for j:=1 to nrvec do read(F,G[i,j]); readln(F); end; close(F); end; procedure DFS1(Nod:integer); var i :integer; begin Comp[Nod]:=C; for i:=1 to NrV[Nod] do if Comp[G[Nod,i]]=0 then DFS1(G[Nod,i]); end; procedure DFS2(Nod,Tip:integer); var Vec, i :integer; begin Parcurs[Nod]:=True; if Primul then begin Primul:=false; GasitDrum:=false; end else if Cuplat[Nod]=0 then begin GasitDrum:=true; M:=M+1 end; i:=0; while (i<NrV[Nod]) and not GasitDrum do begin i:=i+1; Vec:=G[Nod,i]; if Tip=0 then if not Parcurs[Vec] and (Cuplat[Vec]<>Nod) then

DFS2(Vec,1); if Tip=1 then if not Parcurs[Vec] and (Cuplat[Vec]=Nod) then DFS2(Vec,0); if GasitDrum and (Tip=0) then begin Cuplat[Nod]:=Vec; Cuplat[Vec]:=Nod; end; end; end; procedure Greedy; var i, j :integer; Cup :boolean; begin for i:=1 to k do begin j:=0; Cup:=false; while (j<NrV[i]) and not Cup do begin j:=j+1; if Cuplat[G[i,j]]=0 then begin Cup:=true; M:=M+1; Cuplat[i]:=G[i,j]; Cuplat[G[i,j]]:=i end end end end; begin CitireGraf; FillChar(Comp,2*n,0); FillChar(Cuplat,2*n,0); FillChar(Max,2*n,0); M:=0; i:=1; C:=0; while i<=n do begin C:=C+1; DFS1(i); if NrV[i]=0 then Max[C]:=true;

while (i<=n) and (Comp[i]>0) do i:=i+1; end; Greedy; while not Maxim do begin Maxim:=true; for i:=1 to C do if not Max[i] then begin Maxim:=false; j:=0; Gasit:=false; while (j<n) and not Gasit do begin j:=j+1; if (Comp[j]=i) and (Cuplat[j]=0) then begin Gasit:=true; Primul:=true; FillChar(Parcurs,2*n,0); DFS2(j,0); if not GasitDrum then Max[i]:=true; end; end; if not Gasit then Max[i]:=true; end; end; writeln('Numarul de muchii din cuplaj este ',M); writeln('Muchiile din cuplaj sunt: '); for i:=1 to k do if Cuplat[i]>0 then writeln(i,' ',Cuplat[i]) end.

Aplicaie
Enun. Un numr de c critici literari i-au spus, fiecare, prerea n chestiunea: "Care din cele r romane cu succes de public sunt, de fapt, capodopere?". Opiunile lor au strnit discuii aprinse n masele largi de cititori. Momentul este speculat de cele p=c div 2 posturi de televiziune, care decid s organizeze, simultan i separat, talk-show-uri avnd ca invitai cte doi critici.

Televiziunile pltesc criticilor exclusivitatea - deci nici un critic nu poate aprea la dou posturi diferite -, dar cu sume att de mari, nct niciuna nu-i permite s "dein", dac poate, dect exact doi critici. Cum ns sponsorii i clienii la spaiile publicitare ale televiziunilor cer insistent ca nainte i dup reclamele lor, n emisiuni s nu mai existe certuri sau momente tensionate, fiecare post de televiziune e obligat s invite critici cu preri apropiate, dar nu identice, asupra romanelor-capodoper. Condiia este ca pentru oricare doi critici invitai la acelai post, opiunile s difere pentru exact un roman. Dndu-se c, r i lista de capodopere propuse de fiecare critic s se determine numrul maxim de emisiuni care se pot realiza i criticii care apar n aceste emisiuni. Intrare: Fiierul CRITICI.IN coninnd:
q

pe prima linie, numrul de critici c i numrul de romane r, separate prin spaiu; pe linia numrul i+1, cu i=1..c, lista capodoperelor propuse de criticul i, sub forma unui ir de cel mult r numere naturale diferite ntre ele, cuprinse ntre 1 i r i separate prin spaiu.

Ieire: Fiierul CRITICI.OUT, coninnd:


q

pe prima linie, numrul t de posturi de televiziune care pot realiza talk-show-uri n soluia descris de fiier; pe fiecare linie din urmtoarele t, numerele de ordine ale celor doi critici invitai de cte un post de televiziune dintre cele ce pot organiza talk-show n soluia dat.

Exemplu: CRITICI.IN 4 1 3 4 2 4 1 4 1 1

CRITICI.OUT 2

1 4 2 3 Olimpiada Naional de Informatic 1998 Clasa a XI-a Rezolvare. Construim un graf G cu c noduri n care exist muchie ntre nodurile i i j dac criticul i poate realiza o emisiune mpreun cu criticul j. Condiia pentru ca doi critici s poat realiza o emisiune mpreun este ca opiunile lor s difere exact asupra unui roman. Rezult c unul dintre critici trebuie s aib cu un roman n plus n lista de capodopere fa de cellalt i restul listei s fie comun. Exemplu: criticul 1: 3 4 8 7 2 criticul 2: 9 4 8 3 2 7 Opiunile lor difer numai asupra romanului 9. mprim cei t critici n dou mulimi, M1 i M2, prima coninnd criticii ale cror liste de capodopere au un numr par de romane i cea de-a doua pe cei cu numr impar. Doi critici din aceeai mulime nu pot face o emisiune mpreun pentru c diferena dintre numrul de capodopere din listele lor este par i dup cum am stabilit mai sus aceast diferen trebuie s fie 1. Rezult c ntre dou noduri din M1 sau dou noduri din M2 nu poate exista muchie, deci graful este bipartit. Acum, este clar c problema se reduce la problema cuplajului bipartit maxim. Avem un graf bipartit G cu c noduri. Trebuie s alegem un numr maxim de muchii (emisiuni) din acest graf, iar aceste muchii nu trebuie s aib vreun capt comun (s nu existe un critic care apare n dou emisiuni).

[ Capitolul 9]

[ Cuprins ]

Bibliografie

1. Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest. Introduction to Algorithms. The


MIT Press, 1990.

2. Donald. E. Knuth. Seminumerical Algorithms, volume 2 of The Art of Computer Programming.


Addison-Wesley. Third edition, 1998.

3. K. Thulasiraman, M.N.S. Swamy. Graphs: Theory and Algorithms. John Wiley & Sons, Inc.,
1992.

4. Tudor Sorin. Tehnici de programare. L&S Infomat, 1996.

S-ar putea să vă placă și