Bursele Agora! Etapa FINALĂPrima ediție a concursului de programare organizat de Gazeta de Informatică s-a încheiat. Primii zece în clasamentul după cele șase runde desfășurate prin e-mail au participat la faza finală care a avut loc pe data de 17 iunie 2000 la Liceul de Informatică din Cluj. Cei zece participanți calificați la faza finală au fost:
Singurul care nu a putut să se prezinte la faza finală a fost Cristian Cadar, așadar am avut doar nouă participanți. Până la urmă bursa de 50$/lună oferită de Computer Press Agora a fost câștigată de Mihai Stroe. El a fost singurul care a reușit să obțină punctajul maxim la toate cele trei probleme propuse spre rezolvare. Pe locul al doilea s-a clasat Mihai Pătrașcu care a câștigat o bursă de 30$/lună. Toți ceilalți participanți au primit premii oferite de editurile Computer Press Agora și Computer Libris Agora. În continuare vă prezentăm enunțurile celor trei probleme împreună cu rezolvările propuse de câștigătorul concursului.
CR7P1: Sens unic Într-un oraș primarul dorește să introducă sensuri unice pe cât mai multe străzi. Există o singură restricție, și anume din fiecare punct al orașului trebuie să se poată ajunge în orice alt punct, respectând sensurile de circulație. Planul orașului este dat sub forma unui graf neorientat, conex, vârfurile reprezentând intersecțiile, iar muchiile reprezentând străzile.
Cerință Ajutați primarul să stabilească străzile cu circulație în sens unic, precum și sensul de circulație. Rezultatul va fi un graf orientat având în locul fiecărei muchii din graful inițial un arc sau eventual o pereche de arce în sensuri opuse.
Date de intrare Datele de intrare se vor citi din fișierul STRAZI.IN având următorul format: - Pe prima linie se află două numere întregi, N și M, reprezentând numărul de vârfuri, respectiv numărul de muchii; cele două numere sunt separate printr-un spațiu; - Pe fiecare din următoarele M linii se găsesc câte două numere, reprezentând extremitățile uneia dintre cele M muchii.
Date de ieșire Rezultatul se va scrie în fișierul STRAZI.OUT astfel: pe fiecare linie se va scrie câte o pereche de numere, reprezentând vârful inițial, respectiv cel final, al câte unui arc din graful rezultat.
Restricții - N Ł 500 - M Ł 3000 - fiecare muchie apare o singură dată - vârfurile sunt numerotate de la 1 la N - în graful dat nu există bucle - ordinea listării arcelor nu are importanță - dacă există mai multe soluții optime, se va da doar una singură
Exemplu STRAZI.IN STRAZI.OUT 8 10 1 2 1 2 2 1 2 3 2 3 3 5 3 2 3 6 3 4 3 4 4 5 5 4 4 6 6 4 4 7 4 7 7 8 4 8 5 3 8 7 6 3 8 4
Soluție Realizăm o parcurgere DF a grafului; pentru fiecare nod calculăm nivelul său. Muchiile arborelui DF vor deveni arce de la părinte spre fiu. Pentru fiecare nod, calculăm înălțimea minimă la care putem ajunge din noduri din subarborii ai căror rădăcini sunt fiii nodului respectiv. Dacă nu putem ajunge mai sus (pe un nivel mai mic decât al nodului curent), vom duce un arc de la nodul respectiv în sus, și anume arcul care ajunge pe un nivel cât mai mic; dacă nu este necesar, din nodul respectiv nu mai ducem nici un arc. Complexitatea algoritmului este O(m - log m); este posibilă și obținerea unei complexități O(m) dacă se folosesc liste de muchii pentru fiecare nod. Structura aleasă aduce avantaje deoarece poate fi vizualizată ușor, permițând o depanare eficientă, iar limitele problemei permit rezolvarea în complexitatea respectivă.
Listing CR7P1.PAS Program Sens_Unic; var i,j,k,l,m,n:Longint; fi,fo:Text; mm:array[1..6001,1..2] of Integer; sel:array[1..6000] of Integer; st,h,luate,nivel,t,thigh,p:array[1..501] of Integer;
procedure readdata; begin Assign(fi,'strazi.in'); Assign(fo,'strazi.out'); Reset(fi); Readln(fi,n,m); for i:=1 to m do begin Readln(fi,mm[i,1],mm[i,2]); mm[m+i,1]:=mm[i,2]; mm[m+i,2]:=mm[i,1] end; Close(fi) end;
procedure Sort(l,r:Integer); var i,j,x,y:Integer; begin i:=l; j:=r; x:=mm[(l+r) div 2,1]; repeat while mm[i,1]<x do i:=i+1; while x<mm[j,1] do j:=j-1; if i<=j then begin y:=mm[i,1]; mm[i,1]:=mm[j,1]; mm[j,1]:=y; y:=mm[i,2]; mm[i,2]:=mm[j,2]; mm[j,2]:=y; i:=i+1; j:=j-1 end until i>j; if l<j then Sort(l,j); if i<r then Sort(i,r) end;
procedure rec(k,niv:Integer); var i,x:Integer; begin if luate[k]=1 then Exit; luate[k]:=1; nivel[k]:=niv; h[k]:=niv; { folosim mai intai muchiile catre noduri care nu apartin inca arborelui } for i:=st[k] to st[k+1]-1 do begin x:=mm[i,2]; if luate[x]=0 then begin t[x]:=k; rec(x,niv+1); if h[x]<h[k] then h[k]:=h[x] end end; { Mai avem nevoie de o muchie 'in sus'? } if h[k]<nivel[k] then p[k]:=1 else thigh[k]:=t[k]; { Actualizam valoarea h[k] folosind si } { muchiile 'in sus' din k, pentru a } { calcula corect h pentru parintele } { lui k. Daca avem nevoie de o muchie } { 'in sus', o alegem pe cea dorita } for i:=st[k] to st[k+1]-1 do begin x:=mm[i,2]; if nivel[x]<nivel[k] then begin if x<>t[k] then if h[k]>nivel[x] then begin h[k]:=nivel[x]; if p[k]=0 then thigh[k]:=x end end end end; procedure solve; begin m:=2*m; sort(1,m); for i:=1 to n do begin st[i]:=1; while mm[st[i],1]<>i do Inc(st[i]) end; st[n+1]:=m+1; { am construit structura de date: - mm - lista de muchii - st - pozitia in lista de la care incep vecinii fiecarui nod } h[1]:=1; nivel[1]:=1; rec(1,1); { afisare } Rewrite(fo); for i:=1 to n do if t[i]<>0 then Writeln(fo,t[i],' ',i); for i:=1 to n do if thigh[i]>0 then Writeln(fo,i,' ',thigh[i]); Close(fo) end;
Begin readdata; solve End.
CR7P2: Munte Linia orizontului dintr-un peisaj se poate desena pe un caroiaj, cu ajutorul caracterelor '/' și '\', aceasta conturându-se într-un "zig-zag" obișnuit. O linie a orizontului este corectă dacă este desenată cu același număr de caractere '/', respectiv '\' și fiecare coloană a caroiajului conține un singur caracter. De asemenea, această linie trebuie să pornească dintr-o celulă a caroiajului aflată la nivelul mării și se termină tot într-o celulă aflată la nivelul mării. Pe parcursul trasării, linia nu coboară niciodată sub nivelul mării. Linia orizontului începe cu caracterul '/' și se termină cu caracterul '\'. După caracterul '/' poate fi plasat pe coloana următoare caracterul '/' pe linia superioară sau caracterul '\' pe aceeași linie. Analog, după caracterul '\' poate fi plasat pe coloana următoare caracterul '\' pe linia inferioară sau caracterul '/' pe aceeași linie. Definim vârful muntelui ca fiind format din două caractere '/\' situate pe aceeași linie și pe coloane alăturate.
Cerință Scrieți un program care calculează numărul de posibilități de a trasa linii ale orizontului cu un număr dat de perechi de caractere '/' și '\', astfel încât să existe exact k vârfuri.
Date de intrare Datele de intrare se vor citi din fișierul MUNTE.IN care va conține numerele n (numărul de caractere '/') și k (numărul de vârfuri) separate printr-un singur spațiu.
Date de ieșire Rezultatul se va scrie în fișierul MUNTE.OUT care va conține numărul de posibilități de a trasa linii ale orizontului (având k vârfuri) folosind n perechi de caractere '/' și '\'.
Restricții - 2 Ł n Ł 20 - 1 Ł k Ł n
Exemple MUNTE.IN MUNTE.OUT 2 2 1
MUNTE.IN MUNTE.OUT 3 2 3
MUNTE.IN MUNTE.OUT 4 1 1
MUNTE.IN MUNTE.OUT 4 3 6
Soluție Problema se rezolvă prin metoda programării dinamice. Se construiește matricea X unde X[i, j, h, l] reprezintă numărul de munți construibili cu i caractere, având j vârfuri, cu ultimul caracter situat la înălțimea h; l indică tipul ultimului caracter (1 pentru '/', 2 pentru '\'). Relațiile de recurență sunt: X[i, j, h, '/'] = X[i - 1, j, h - 1, '/'] + X[i - 1, j, h, '\'] X[i, j, h, '\'] = X[i - 1, j - 1, h, '/'] + X[i - 1, j, h + 1, '\'] Implementare: se folosesc matricele A și B, care reprezintă, pe rând, X[i] și X[i + 1]. Matricele au ca elemente numere mari; in practică se folosesc pointeri către astfel de numere. Maniera de lucru cu aceste numere face programul mai clar, mai ușor de depanat și elimină problemele legate de limitele memoriei (numerele sunt alocate în heap).
Listing CR7P2.PAS Program Munte; type nr=record nc:Integer; a:array[1..100] of Byte end; numar=^nr; var i,j,k,l,m,n,h:Longint; fi,fo:Text; a,b:array[0..21,0..21,1..2] of numar; x:numar;
procedure readdata; begin Assign(fi,'munte.in'); Assign(fo,'munte.out'); Reset(fi); Rewrite(fo); Readln(fi,n,k); Close(fi) end;
procedure init(var a:numar); begin if a=nil then New(a); Fillchar(a^,Sizeof(a^),0); a^.nc:=1 end;
procedure copy(a,b:numar); begin init(a); a^:=b^ end;
procedure add(a,b:numar); var i:Integer; begin if b^.nc>a^.nc then a^.nc:=b^.nc; for i:=1 to a^.nc do a^.a[i]:=a^.a[i]+b^.a[i]; for i:=1 to a^.nc do begin a^.a[i+1]:=a^.a[i+1]+a^.a[i] div 10; a^.a[i]:=a^.a[i] mod 10 end; if a^.a[a^.nc+1]<>0 then Inc(a^.nc) end;
procedure afis; begin init(x); copy(x,a[k,1,2]); Rewrite(fo); for i:=x^.nc downto 1 do Write(fo,x^.a[i]); Writeln(fo); Close(fo) end; procedure solve; begin for i:=0 to n do for j:=0 to n+1 do for l:=1 to 2 do
begin init(a[i,j,l]); init(b[i,j,l]) end; a[0,1,1]^.nc:=1; a[0,1,1]^.a[1]:=1; for i:=2 to n*2 do
begin for j:=0 to i div 2 do for h:=1 to n do begin init(b[j,h,1]); init(b[j,h,2]); copy(b[j,h,1],a[j,h,2]); if h>1 then add(b[j,h,1],a[j,h-1,1]); copy(b[j,h,2],a[j,h+1,2]); if j>0 then add(b[j,h,2],a[j-1,h,1]) end; for j:=0 to k do for h:=1 to n+1 do for l:=1 to 2 do a[j,h,l]^:=b[j,h,l]^ end; afis end;
Begin readdata; solve End.
CR7P3: Mașina O mașină trebuie condusă printr-un labirint din poziția de start până la poziția finală, ambele cunoscute, știind că viteza ei poate fi cel mult K și că după fiecare pas, viteza ei poate să se modifice cu 1. Mașina, într-o unitate de timp poate să se deplaseze doar pe orizontală sau verticală, iar atunci când se deplasează cu viteza V, parcurge o distanță egală cu V. În poziția de start mașina are viteza 0, iar în poziția de sosire trebuie să aibe viteza 1. Mașina poate să treacă de mai multe ori prin același punct.
Cerință Scrieți un program care determină timpul minim necesar deplasării mașinii din punctul de start în poziția de sosire.
Date de intrare Pe prima linie a fișierului de intrare AUTO.IN sunt scrise dimensiunile labirintului N (număr de linii) și M (număr de coloane), precum și viteza maximă a mașinii (K), separate prin câte un spațiu. Fiecare din următoarele N linii conține fiecare exact M caractere, astfel încât spațiul reprezintă coridor, iar caracterul '*' reprezintă zid. Poziția de start este marcată cu litera 'S', cea finală cu litera 'F'.
Date de ieșire În fișierul de ieșire AUTO.OUT se va scrie un singur număr, reprezentând timpul minim necesar pentru deplasarea mașinii din punctul de start în poziția finală. În cazul în care mașina nu poate să ajungă la destinație, în fișier se va scrie -1.
Restricții - 1 Ł N, M Ł 100 - 1 Ł K Ł 10
Exemplu AUTO.IN AUTO.OUT 5 10 3 7 ********** *S * ****** *** F *** ****
Soluție Problema se rezolvă printr-o parcurgere în lățime. O situație în care se poate afla mașina este dată de (i, j, v), care reprezintă coordonatele și viteza. Se introduce situația inițială într-o coadă, apoi se introduc situațiile adiacente ei ș.a.m.d. Două situații se consideră adiacente dacă mașina poate trece la un pas din una în cealaltă (viteza diferă cu maxim 1, se încadrează în limite, pozițiile sunt pe aceeași orizontală/verticală și nu sunt despărțite de ziduri). O situație introdusă în coadă se marchează în X pentru a nu fi introdusă din nou. X[v, i] este mulțimea valorilor pentru j care corespunde situațiilor (i, j, v) în care se poate afla mașina. Implementarea nu folosește coada, ci vectorul situațiilor accesibile la momentul T, din care se generează cel corespunzător momentului T+1. Soluția este afișată la prima trecere prin situația finală; dacă aceasta nu se realizează și nu mai apar situații noi, se va afișa -1. Programul poate fi optimizat, în special la testarea adiacenței a două situații însă, în forma actuală, este foarte ușor de depanat.
Listing CR7P3.PAS Program Masina; type ar=array[1..10000,1..3] of Integer; var time,nr1,nr2,i,j,k,l,m,n,x1,y1,x2,y2: Longint; fi,fo:Text; s:array[1..102] of string[102]; a,b:^ar; x:array[0..10,1..102] of set of 1..102;
procedure readdata; begin Assign(fi,'auto.in'); Assign(fo,'auto.out'); Reset(fi); Readln(fi,m,n,k); for i:=1 to m do Readln(fi,s[i+1]); Close(fi); for i:=1 to n+2 do s[1]:=s[1]+'*'; s[m+2]:=s[1]; for i:=2 to m+1 do begin s[i]:='*'+s[i]; while Length(s[i])<n+1 do s[i]:=s[i]+' '; s[i]:=s[i]+'*' end end;
procedure findx1y1; begin for x1:=1 to m do for y1:=1 to n do if s[x1,y1]='S' then Exit end;
procedure findx2y2; begin for x2:=1 to m do for y2:=1 to n do if s[x2,y2]='F' then Exit end;
procedure expand; var ii,jj:Integer; begin if (a^[i,1]=x2) and (a^[i,2]=y2) and (a^[i,3]=1) then begin Rewrite(fo); Writeln(fo,time); Close(fo); Halt end; for j:=a^[i,3]-1 to a^[i,3]+1 do if (j>0) and (j<=k) then
begin if a^[i,1]+j<m then if not (a^[i,2] in x[j,a^[i,1]+j]) then begin l:=0; for ii:=1 to j do if s[a^[i,1]+ii,a^[i,2]]='*' then l:=1; if l=0 then begin Inc(nr2); b^[nr2,1]:=a^[i,1]+j; b^[nr2,2]:=a^[i,2]; b^[nr2,3]:=j; x[j,b^[nr2,1]]:=x[j,b^[nr2,1]]+ [b^[nr2,2]] end end; if a^[i,1]-j>0 then if not (a^[i,2] in x[j,a^[i,1]-j]) then
begin l:=0; for ii:=1 to j do if s[a^[i,1]-ii,a^[i,2]]='*' then l:=1; if l=0 then begin Inc(nr2); b^[nr2,1]:=a^[i,1]-j; b^[nr2,2]:=a^[i,2]; b^[nr2,3]:=j; x[j,b^[nr2,1]]:=x[j,b^[nr2,1]]+ [b^[nr2,2]] end end; if a^[i,2]+j<n then if not (a^[i,2]+j in x[j,a^[i,1]]) then begin l:=0; for ii:=1 to j do if s[a^[i,1],a^[i,2]+ii]='*' then l:=1; if l=0 then begin Inc(nr2); b^[nr2,1]:=a^[i,1]; b^[nr2,2]:=a^[i,2]+j; b^[nr2,3]:=j; x[j,b^[nr2,1]]:=x[j,b^[nr2,1]]+ [b^[nr2,2]] end end; if a^[i,2]-j>0 then if not (a^[i,2]-j in x[j,a^[i,1]]) then begin l:=0; for ii:=1 to j do if s[a^[i,1],a^[i,2]-ii]='*' then l:=1; if l=0 then
begin Inc(nr2); b^[nr2,1]:=a^[i,1]; b^[nr2,2]:=a^[i,2]-j; b^[nr2,3]:=j; x[j,b^[nr2,1]]:=x[j,b^[nr2,1]]+ [b^[nr2,2]]
end end end end;
procedure solve; begin Inc(m,2); Inc(n,2); New(a); New(b); Fillchar(a^,Sizeof(a^),0); Fillchar(b^,Sizeof(b^),0); findx1y1; findx2y2; a^[1,1]:=x1; a^[1,2]:=y1; a^[1,3]:=0; nr1:=1; nr2:=0; x[0,x1]:=x[0,x1]+[y1]; time:=0; while nr1>0 do begin for i:=1 to nr1 do expand; Inc(time); nr1:=nr2; nr2:=0; a^:=b^; Fillchar(b^,Sizeof(b^),0) end; Rewrite(fo); Writeln(fo,-1); Close(fo) end;
Begin readdata; solve End. [cuprins] |