Sunteți pe pagina 1din 48

Ministerul Educaţiei Tineretului şi Sportului

al Republici Moldova

Olimpiada Republicană la Informatică,

Ediţia 2005

Chişinău, 2005
Dragi elevi,
Stimaţi profesori,

În luna martie a anului curent Guvernul Republicii Moldova a aprobat Strategia Naţională de
edificare a societăţii informaţionale „Moldova electronica”.
Societatea informaţională este o formă nouă, mult mai perfectă, a civilizaţiei umane, în care
accesul egal şi universal la informaţie, în corelaţie cu o infrastructură informaţională şi de
comunicaţii dezvoltată, contribuie la o dezvoltare social-economică durabilă, reducerea gradului de
sărăcie, îmbunătăţirea calităţii vieţii, la integrarea în Uniunea Europeană. Practica internaţională
demonstrează impactul pozitiv al infrastructurii informaţionale şi de comunicaţii asupra dezvoltării
societăţii contemporane, care constă în diversificarea posibilităţilor de acces la informaţie şi la
resursele informaţionale publice în toate domeniile de activitate umană: guvernare electronică,
economia electronică, comerţul electronic, învăţământul electronic, cultura electronică, medicina
electronică etc., precum şi în creşterea nivelului de ocupaţie a populaţiei prin crearea a noi locuri de
muncă.
Conform Strategiei Naţionale de edificare a societăţii informaţionale „Moldova electronica”,
document de o importanţă primordială pentru ţara noastră, se preconizează ca pînă în anul 2015
numărul de abonaţi la telefonia fixă şi mobilă va creşte cel puţin de două ori. Pînă la sfîrşitul anului
2006 rata de penetrare a telefoniei fixe va atinge valoarea de 25,0, iar în anii 2005−2007 în zonele
rurale vor fi construite circa 151 de mii de linii telefonice. În scopul asigurării accesului populaţiei
la întreaga gamă de servicii oferite de dezvoltarea tehnologiilor informaţionale, se preconizează
creşterea numărului de calculatoare personale şi al utilizatorilor Internet cu o rată de cel puţin 15%
anual.
Un rol important în edificarea societăţii informaţionale revine educaţiei, în special,
învăţămîntului preuniversitar. Anume în şcoală se formează cultura informaţională şi gîndirea
algoritmică, anume în cabinetele şcolare de informatică învăţăm a face primii paşi în lumea
miraculoasă a algebrei booleene şi sistemelor de numeraţie, limbajelor algoritmice şi tehnicilor de
programare. Succesul acestor paşi depinde în egală măsură atît de dragostea noastră faţă de
informatică, cît şi de perseverenţa cu care învăţăm materiile incluse în programele şcolare.
Desigur, toţi participanţii la Olimpiada Republicană la Informatică au demonstrat de
nenumărate ori aceste calităţi, confirmînd prin rezultatele obţinute la concursurile şcolare, raionale,
naţionale şi internaţionale că informatica în ţara noastră este un domeniu de perspectivă, care ne
permite să ne realizăm aspiraţiile şi visurile noastre. Sînt sigur, că problemele propuse de profesori,
rezolvările originale elaborate de elevi, schimbul de idei şi de opinii au extins şi au consolidat
spaţiul informaţional şcolar, au contribuit la perfecţionarea metodelor de predare-învăţare a
informaticii.
Dorim tuturor participanţilor la Olimpiadă noi succese şi sperăm şi în continuare la o
conlucrare eficientă în domeniul edificării societăţii informaţionale.

În numele Consiliului Olimpic la Informatică,

Anatol Gremalschi,
doctor habilitat,
profesor universitar

2
Instrucţiune

I. Fişiere şi directoare
I.1. După terminarea lucrului, participantul va transmite oficialului numai fişierele sursă ale
programelor, câte unul pentru fiecare problemă.

I.2. Fişierele sursă vor avea acelaşi nume ca şi fişierele de intrare/ieşire. De exemplu în cazul
fişierelor de intrare/ieşire numite SORTARE.IN şi SORTARE.OUT, fişierul sursă se va numi
SORTARE.PAS, SORTARE.C, sau SORTARE.CPP.

I.3. Fişierele sursă se vor afla într-un director cu numele format din primele 7 litere ale
numelui de familie, plus prima literă a numelui. De exemplu pentru elevul Tudor Mărgineanu,
directorul se va numi MARGINET. Pentru elevul Tudor Roşu, directorul se va numi ROSUT.
Denumirile de directoare sînt indicate în fişa de înregistrare.

II. Intrări şi ieşiri


II.1. Programul va citi şi va scrie toate datele din fişierele text indicate, cu excepţia cazurilor
în care se indică altfel în enunţul problemei.

II.2. Fişierele de intrare/ieşire vor fi citite/scrise în catalogul curent, evitându-se introducerea


căilor absolute sau relative de acces. De exemplu, în cazul limbajului Pascal şi a fişierului
SORTARE.IN se va utiliza apelul assign(f,’sortare.in’). Pentru un program în limbajul
C se va utiliza apelul f=fopen(”sortare.in”,”rt”).

II.3. Programul nu va scrie nimic pe ecran, nu va citi nimic de la tastatura, nu va lucra cu alte
fişiere, cu excepţia cazurilor în care se indică altfel în enunţul problemei.

III. Limbaje de programare


III.1. Programul va utiliza numai mijloacele standard ale limbajului respectiv, evitându-se
includerea codului Asamblor, codului de maşină, apelurilor de întreruperi, scrierea în porturi,
schimbarea timpului curent, scrierea directă în memorie, apelul altor programe, funcţii ale
sistemului de operare şi altor posibilităţi nestandard.

III.2. Programul Pascal va utiliza numai unit-ul system care este încărcat de compilator automat.
Includerea altor unit-uri prin utilizarea clausei uses este interzisă, cu excepţia cazurilor special
prevăzute în enunţul problemei.

III.3. Soluţiile competitorilor nu vor include nici un fel de directive de compilare care alterează
opţiunile prestabilite de compilare.

III.4. Competitorilor le vor fi puse la dispoziţie fişierele C_PAS.BAT, C_CPP.BAT care vor
compila soluţiile lor cu exact aceleaşi opţiuni ca şi în cadrul evaluării. Mediile integrate vor fi
configurate pentru a reflecta cât mai precis opţiunile de compilare din cadrul evaluării.

IV. Alte restricţii


IV.1. Competitorul va lucra numai în mediul DOS oferit de organizatori, utilizând programele DN,
TP, TC. Utilizarea altor programe, inclusiv Windows este interzisă.

IV.2. În cazul încălcării cerinţelor de mai sus, participantul poate fi sancţionat cu anularea
rezultatului pentru problema în cauză, pentru ziua respectivă, sau cu înlăturarea de la competiţie.

3
Descrierea problemelor − Ziua 1

Clasele 7 - 9

Numărul de Denumirea Denumirea


Denumire Denumirea
puncte alocat fişierului de fişierului de
a problemei fişierului sursă
problemei intrare ieşire
CALCULE.PAS
Calcule 100 CALCULE.C CALCULE.IN CALCULE.OUT
CALCULE.CPP

ECHIPE.PAS
Echipe 100 ECHIPE.C ECHIPE.IN ECHIPE.OUT
ECHIPE.CPP

PUNCTE.PAS
Puncte 100 PUNCTE.C PUNCTE.IN PUNCTE.OUT
PUNCTE.CPP

Total 300 - - -

4
Calcule

Unitatea gramaticală <Expresie> este definită cu ajutorul următoarelor formule


metalingvistice:
<Cifră>::= 0⏐1⏐2⏐3⏐4⏐5⏐6⏐7⏐8⏐9
<Număr>::= <Cifră>⏐<Număr><Cifră>
<Semn>::= +⏐-
<Expresie>::= <Număr>⏐<Număr><Semn><Expresie>
Elaboraţi un program care evaluează astfel de expresii.
Date de intrare.
Fişierul text CALCULE.IN conţine pe o singură linie un şir de caractere − expresia supusă
evaluării.
Date de ieşire.
Fişierul text CALCULE.OUT va conţine pe o singură linie un număr întreg − rezultatul evaluării
expresiei din fişierul de intrare.
Exemplu.

CALCULE.IN CALCULE.OUT
23+01-333+000 -309

Restricţii. Un număr poate conţine cel mult cinci cifre. O expresie poate include cel mult 250
de caractere. Timpul de execuţie nu va depăşi o secundă. Fişierul sursă va avea denumirea
CALCULE.PAS, CALCULE.C sau CALCULE.CPP.

5
Rezolvare

Valoarea expresiei poate fi calculată printr-o singură parcurgere a şirului de caractere, citit din
fişierul de intrare. În procesul parcurgerii, unităţile gramaticale <Număr> din componenţa şirului
de caractere, sînt transformate în numere întregi. Pentru a evita erorile de depăşre, se va folosi tipul
de date longint.

Program Calcule;
{ Clasele 7-9 }
var S : string;
n : longint;
Intrare, Iesire : text;

procedure Citire;
begin
assign(Intrare, 'CALCULE.IN');
reset (Intrare);
readln(Intrare, S);
close(Intrare);
end; { Calcule }

procedure Scrie;
begin
assign(Iesire, 'CALCULE.OUT');
rewrite(Iesire);
writeln(Iesire, n);
close(Iesire);
end; { Scrie }

function Valoare(Numar : string) : longint;


{ Transforma sirul de caractere intr-un numar intreg }
var m : longint;
code : integer;
begin
val(Numar, m, code);
Valoare:=m;
end; { Valoare }

procedure Evaluare;
var Numar : string;
i : integer;
Semn : char;
begin
Numar:='';
i:=1;
while (S[i] in ['0'..'9']) and (i<= length(S)) do
begin
Numar:=Numar+S[i];
i:=i+1;
end; { while }
n:=Valoare(Numar);
while i<=length(S) do
begin
Semn:=S[i];
i:=i+1;
Numar:='';
while (S[i] in ['0'..'9']) and (i<= length(S)) do
begin
Numar:=Numar+S[i];
i:=i+1;
end;

6
case Semn of
'+': n:=n+Valoare(Numar);
'-': n:=n-Valoare(Numar);
end; { case }
end; { while }
end; { Evaluare }

begin
Citire;
Evaluare;
Scrie;
end.

7
Echipe

O companie a hotărît să formeze o echipă care trebuie să incudă exact trei persoane. La
concursul de selecţie a echipei s-au înscris n participanţi. Selecţia echipei se efectuează în felul
următor:
1. Iniţial participanţii la concurs sînt împărţiţi în două subgrupe. În acest scop, participanţii sînt
numerotaţi prin 1, 2, ..., n. În una din subgrupe se includ participanţii cu numere pare, iar în
cealaltă − cei cu numere impare.
2. În continuare se analizează fiecare din subgrupele obţinute. Sînt posibile următoarele cazuri:
a) dacă subgrupa curentă conţine exact trei persoane, ele vor forma echipa respectivă;
b) dacă subgrupa curentă conţine una sau două persoane, ea este exclusă din studiu;
c) dacă subgrupa curentă conţine mai mult de trei persoane, ele din nou vor fi numerotate
prin 1, 2, 3 ş.a.m.d. şi împărţite în două subgrupe: cei cu numerele pare vor fi încluse
într-o subgrupă, iar cei cu numerele impare − în alta.
3. Procesul de divizare în subgrupe se termină atunci cînd a fost găsită o echipă sau cînd toate
subgrupele obţinute conţin mai puţin de trei persoane.

Elaboraţi un program care calculează numărul variantelor posibile de selecţie a echipei. Dacă
echipa respectivă nu poate fi selectată prin metoda descrisă mai sus, prin definiţie, numărul
variantelor posibile este egal cu zero.
Date de intrare. Fişierul text ECHIPE.IN conţine pe o singură linie numărul natural n.
Date de ieşire. Fişierul text ECHIPE.OUT va conţine pe o singură linie numărul variantelor
posibile de selecţie a echipei.
Exemplu.
ECHIPE.IN ECHIPE.OUT
11 3

Restricţii. 2 ≤ n ≤ 10 6 . Timpul de execuţie nu va depăşi o secundă. Fişierul sursă va avea


denumirea ECHIPE.PAS, ECHIPE.C sau ECHIPE.CPP.

8
Rezolvare

Prin V (n) vom nota funcţia ce reprezintă numărul variantelor posibile de selectare a echipei
dintr-o grupă formată din n participanţi. Prin calcule directe ne putem convinge că V (1) = 0 ,
V (2) = 0 şi V (3) = 1 . Evident, pentru orice n, n > 3 , sînt posibile următoarele cazuri:
1. Numărul n este par. În rezultatul divizării grupei curente de participanţi vom obţine două
subgrupe cu cîte (n div 2) participanţi. Evident, în acest caz:

V ( n) = V ( n div 2) + V ( n div 2) = 2V ( n div 2) .


2. Numărul n este impar. În rezultatul divizării grupei curente de participanţi vom obţine două
subgrupe. Una din ele va include (n div 2) participanţi, iar cealaltă − ( n div 2) + 1 participanţi.
Evident, în acest caz:
V (n) = V ( n div 2) + V (( n div 2) + 1) .
Prin urmare, obţinem următoarea definiţie recursivă a funcţiei V(n):

⎧0, daca n < 3;



V (n) = ⎨2V (n div 2), daca n ≥ 3 si n este par;
⎪V (n div 2) + V ((n div 2) + 1), daca n ≥ 3 si n este impar.

Algoritmul recursiv de calcul a numărului de variante posibile de selecţie a echipei este
realizat în programul ce urmeză.
Program Echipe;
var
n : longint; {numarul de participanti}
Variante : longint;
Intrare, Iesire:text;
procedure Citire;
begin
assign(Intrare,'ECHIPE.IN');
reset(Intrare);
readln(Intrare, n);
close(Intrare);
end; { Citire }
procedure Scrie;
begin
assign(Iesire, 'ECHIPE.OUT');
rewrite(Iesire);
writeln(Iesire, Variante);
close(Iesire);
end; { Scrie }

function V(n : longint) : longint;


begin
if n<3 then V:=0 else
if n=3 then V:=1 else
if odd(n) then V:=V(n div 2)+V((n div 2)+1)
else V:=2*V(n div 2)
end; { Var }
begin
Citire;
Variante:=V(n);
Scrie;
end.

9
Pentru a estima complexitatea temporală a programului Echipe, vom examina cel mai
nefavorabil caz, în care toate apelurile recursive ale funcţiei V(n) se realiza numai prin execuţia
instrucţiunii de atribuire V:=V(n div 2)+V((n div 2)+1).
Evident, în astfel de cazuri, numărul maximal de apeluri recursive Q ale funcţiei V(n)
satisface inegalitatea:
Q ≤ 1 + 21 + 2 2 + K + 2 k ,

unde k se determină din relaţia 2 k ≤ n ≤ 2 k +1 .


Prin urmare, k ≤ log 2 n . Întrucît 1 + 2 2 + 2 2 + ... + 2 k < 2 ⋅ 2 k , numărul maximal de apeluri
recursive Q < 2n . Conform restricţiilor problemei, n ≤ 10 6 , deci Q < 2 ⋅ 10 6 , mărime comparabilă
cu capacitatea de prelucrare a calculatoarelor din laboratorul de informatică.

10
Puncte

Se consideră punctele P1, P2, P3, ..., Pn pe un plan cartezian. Fiecare punct Pi, i = 1, 2 , ..., n ,
este definit prin coordonatele sale întregi (xi, yi).
Elaboraţi un program care construieşte un dreptunghi de arie minimă cu laturile paralele cu
axele de coordonate, ce conţine toate punctele P1, P2, P3, ..., Pn. Evident, orice punct de pe latura
dreptunghiului aparţine acestuia.
Date de intrare. Fişierul text PUNCTE.IN conţine pe prima linie numărul natural n. Fiecare
din următoarele n linii ale fişierului de intrare conţine cîte două numere întregi separate prin spaţiu.
Linia i + 1 a fişierului de intrare conţine coordonatele xi, yi ale punctului Pi.
Date de ieşire. Fişierul text PUNCTE.OUT va conţine două linii. Pe prima linie se scriu
coordonatele întregi (a, b) ale colţului stînga-jos, iar pe linia a doua − coordonatele întregi (c, d) ale
colţului dreapta-sus ale dreptunghiului construit. Numerele respective vor fi separate prin spaţiu.
Exemplu.
PUNCTE.IN PUNCTE.OUT
4 1 2
2 6 4 7
1 2
3 7
4 5

Restricţii. 2 ≤ n ≤ 1000 ; 0 ≤ xi , y i ≤1 000 . Timpul de execuţie nu va depăşi o secundă.


Fişierul sursă va avea denumirea PUNCTE.PAS, PUNCTE.C sau PUNCTE.CPP.

11
Rezolvare

Presupunem că a fost construit un dreptunghi cu laturile paralele cu axele de coordonate, ce


conţine toate punctele P1, P2, P3, ..., Pn. Acest dreptunghi va fi de arie minimă numai atunci, cînd pe
fiecare latură se va afla cel puţin cîte un punct din mulţimea {P1, P2, P3, ..., Pn}. În caz contrar, aria
dreptunghiului în studiu ar putea fi micşorată, deplasînd latura ce nu conţine nici un punct în
interiorul dreptunghiului (Fig. 1).

Fig. 1

Prin urmare, coordonatele vîrfurilor dreptunghiului se determină conform relaţiilor:

a = min( x1 , x 2 , ..., xi , ..., x n ) ; b = min( y1 , y 2 , ..., y i , ..., y n )


c = max( x1 , x 2 , ..., xi , ..., x n ) ; d = max( y1 , y 2 , ..., yi , ..., y n ) .

Program Puncte;
{ Clasele 7-9 }
const
nmax=1000; { numarul maximal de puncte }
ValoareaMaxima=1000;
ValoareaMinima=0;
var n : integer;
a, b, c, d : integer;
X, Y : array[1..nmax] of integer;

procedure Citire;
var Intrare : text;
i : integer;
begin
assign(Intrare, 'PUNCTE.IN');
reset(Intrare);
readln(Intrare, n);
for i:=1 to n do
readln(Intrare, X[i], Y[i]);
close(Intrare);
end; { Citire }

12
procedure Scrie;
var Iesire : text;
begin
assign(Iesire, 'PUNCTE.OUT');
rewrite(Iesire);
writeln(Iesire, a, ‘ ‘, b);
writeln(Iesire, c, ‘ ‘, d);
close(Iesire);
end; { Scrie }

procedure Dreptunghiuri;
var i : integer;
begin
{ calculam minimul din X[i] }
a:=ValoareaMaxima;
for i:=1 to n do
if X[i]<a then a:=X[i];
{ calculam minimumul din Y[i] }
b:=ValoareaMaxima;
for i:=1 to n do
if Y[i]<b then b:=Y[i];
{ calculam maximumul din X[i] }
c:=ValoareaMinima;
for i:=1 to n do
if X[i]>c then c:=X[i];
{ calculam maximumul din Y[i] }
d:=ValoareaMinima;
for i:=1 to n do
if Y[i]>d then d:=Y[i];
end; { Dreptunghiuri }

begin
Citire;
Dreptunghiuri;
Scrie;
end.

13
Descrierea problemelor − Ziua 1

Clasele 10 - 12

Numărul de Denumirea Denumirea


Denumirea Denumirea
puncte alocat fişierului de fişierului de
problemei fişierului sursă
problemei intrare ieşire
CALCULE.PAS
Calcule 100 CALCULE.C CALCULE.IN CALCULE.OUT
CALCULE.CPP

ECHIPE.PAS
Echipe 100 ECHIPE.C ECHIPE.IN ECHIPE.OUT
ECHIPE.CPP

PUNCTE.PAS
Puncte 100 PUNCTE.C PUNCTE.IN PUNCTE.OUT
PUNCTE.CPP

Total 300 - - -

14
Calcule

Unitatea gramaticală <Expresie> este definită cu ajutorul următoarelor formule


metalingvistice:
<Cifră>::= 0⏐1⏐2⏐3⏐4⏐5⏐6⏐7⏐8⏐9
<Întreg fără semn>::= <Cifră>⏐<Întreg fără semn><Cifră>
<Real fără semn>::=< Întreg fără semn >.< Întreg fără semn >
<Semn>::= +⏐-
<Expresie>::= <Real fără semn>⏐<Real fără semn><Semn><Expresie>
Elaboraţi un program care evaluează astfel de expresii.
Date de intrare.
Fişierul text CALCULE.IN conţine pe o singură linie un şir de caractere − expresia supusă
evaluării.
Date de ieşire.
Fişierul text CALCULE.OUT va conţine pe o singură linie un număr real scris fără specificatori
de format − rezultatul evaluării expresiei din fişierul de intrare.
Exemplu.

CALCULE.IN CALCULE.OUT
2.3+0.1-33.3+0.00 -3.0900000000E+01

Restricţii. Întregul fără semn poate conţine cel mult cinci cifre. O expresie poate include cel
mult 250 de caractere. Timpul de execuţie nu va depăşi o secundă. Fişierul sursă va avea denumirea
CALCULE.PAS, CALCULE.C sau CALCULE.CPP.

15
Rezolvare

Valoarea expresiei poate fi calculată printr-o singură parcurgere a şirului de caractere, citit din
fişierul de intrare. În procesul parcurgerii, unităţile gramaticale <Real fără semn> din componenţa
şirului de caractere, sînt transformate în numere reale.

Program Calcule;
{ Clasele 10-12 }
var S : string;
x : real;
Intrare, Iesire : text;

procedure Citire;
begin
assign(Intrare, 'CALCULE.IN');
reset (Intrare);
readln(Intrare, S);
close(Intrare);
end; { Calcule }

procedure Scrie;
begin
assign(Iesire, 'CALCULE.OUT');
rewrite(Iesire);
writeln(Iesire, x);
close(Iesire);
end; { Scrie }

function Valoare(Numar : string) : real;


{ Transforma sirul de caractere intr-un numar real }
var y : real;
code : integer;
begin
val(Numar, y, code);
Valoare:=y;
end; { Valoare }

procedure Evaluare;
var Numar : string;
i : integer;
Semn : char;
begin
Numar:='';
i:=1;
while (S[i] in ['0'..'9', '.']) and (i<= length(S)) do
begin
Numar:=Numar+S[i];
i:=i+1;
end; { while }
x:=Valoare(Numar);
while i<=length(S) do
begin
Semn:=S[i];
i:=i+1;
Numar:='';
while (S[i] in ['0'..'9', '.']) and (i<= length(S)) do
begin
Numar:=Numar+S[i];
i:=i+1;
end;
case Semn of

16
'+': x:=x+Valoare(Numar);
'-': x:=x-Valoare(Numar);
end; { case }
end; { while }
end; { Evaluare }

begin
Citire;
Evaluare;
Scrie;
end.

17
Echipe

O companie a hotărît să formeze o echipă care trebuie să incudă exact trei persoane. La
concursul de selecţie a echipei s-au înscris n participanţi. Selecţia echipei se efectuează în felul
următor:
4. Iniţial participanţii la concurs sînt împărţiţi în două subgrupe. În acest scop, participanţii sînt
numerotaţi prin 1, 2, ..., n. În una din subgrupe se includ participanţii cu numere pare, iar în
cealaltă − cei cu numere impare.
5. În continuare se analizează fiecare din subgrupele obţinute. Sînt posibile următoarele cazuri:
d) dacă subgrupa curentă conţine exact trei persoane, ele vor forma echipa respectivă;
e) dacă subgrupa curentă conţine una sau două persoane, ea este exclusă din studiu;
f) dacă subgrupa curentă conţine mai mult de trei persoane, ele din nou vor fi numerotate
prin 1, 2, 3 ş.a.m.d. şi împărţite în două subgrupe: cei cu numerele pare vor fi încluse
într-o subgrupă, iar cei cu numerele impare − în alta.
6. Procesul de divizare în subgrupe se termină atunci cînd a fost găsită o echipă sau cînd toate
subgrupele obţinute conţin mai puţin de trei persoane.

Elaboraţi un program care calculează numărul variantelor posibile de selecţie a echipei. Dacă
echipa respectivă nu poate fi selectată prin metoda descrisă mai sus, prin definiţie, numărul
variantelor posibile este egal cu zero.
Date de intrare. Fişierul text ECHIPE.IN conţine pe o singură linie numărul natural n.
Date de ieşire. Fişierul text ECHIPE.OUT va conţine pe o singură linie numărul variantelor
posibile de selecţie a echipei.
Exemplu.
ECHIPE.IN ECHIPE.OUT
11 3

Restricţii. 2 ≤ n ≤ 10 6 . Timpul de execuţie nu va depăşi o secundă. Fişierul sursă va avea


denumirea ECHIPE.PAS, ECHIPE.C sau ECHIPE.CPP.

18
Rezolvare
Prin V (n) vom nota funcţia ce reprezintă numărul variantelor posibile de selectare a echipei
dintr-o grupă formată din n participanţi. Prin calcule directe ne putem convinge că V (1) = 0 ,
V (2) = 0 şi V (3) = 1 . Evident, pentru orice n, n > 3 , sînt posibile următoarele cazuri:
1. Numărul n este par. În rezultatul divizării grupei curente de participanţi vom obţine două
subgrupe cu cîte (n div 2) participanţi. Evident, în acest caz:

V ( n) = V ( n div 2) + V ( n div 2) = 2V ( n div 2) .


2. Numărul n este impar. În rezultatul divizării grupei curente de participanţi vom obţine două
subgrupe. Una din ele va include (n div 2) participanţi, iar cealaltă − ( n div 2) + 1 participanţi.
Evident, în acest caz:
V (n) = V ( n div 2) + V (( n div 2) + 1) .
Prin urmare, obţinem următoarea definiţie recursivă a funcţiei V(n):

⎧0, daca n < 3;



V (n) = ⎨2V (n div 2), daca n ≥ 3 si n este par;
⎪V (n div 2) + V ((n div 2) + 1), daca n ≥ 3 si n este impar.

Algoritmul recursiv de calcul a numărului de variante posibile de selecţie a echipei este
realizat în programul ce urmeză.
Program Echipe;
var
n : longint; {numarul de participanti}
Variante : longint;
Intrare, Iesire:text;

procedure Citire;
begin
assign(Intrare,'ECHIPE.IN');
reset(Intrare);
readln(Intrare, n);
close(Intrare);
end; { Citire }

procedure Scrie;
begin
assign(Iesire, 'ECHIPE.OUT');
rewrite(Iesire);
writeln(Iesire, Variante);
close(Iesire);
end; { Scrie }
function V(n : longint) : longint;
begin
if n<3 then V:=0 else
if n=3 then V:=1 else
if odd(n) then V:=V(n div 2)+V((n div 2)+1)
else V:=2*V(n div 2)
end; { Var }

begin
Citire;
Variante:=V(n);
Scrie;
end.

19
Pentru a estima complexitatea temporală a programului Echipe, vom examina cel mai
nefavorabil caz, în care toate apelurile recursive ale funcţiei V(n) se realiza numai prin execuţia
instrucţiunii de atribuire V:=V(n div 2)+V((n div 2)+1).
Evident, în astfel de cazuri, numărul maximal de apeluri recursive Q ale funcţiei V(n)
satisface inegalitatea:
Q ≤ 1 + 21 + 2 2 + K + 2 k ,

unde k se determină din relaţia 2 k ≤ n ≤ 2 k +1 .


Prin urmare, k ≤ log 2 n . Întrucît 1 + 2 2 + 2 2 + ... + 2 k < 2 ⋅ 2 k , numărul maximal de apeluri
recursive Q < 2n . Conform restricţiilor problemei, n ≤ 10 6 , deci Q < 2 ⋅ 10 6 , mărime comparabilă
cu capacitatea de prelucrare a calculatoarelor din laboratorul de informatică.

20
Puncte

Se consideră punctele P1, P2, P3, ..., Pn pe un plan cartezian. Fiecare punct Pi, i = 1, 2 , ..., n ,
este definit prin coordonatele sale reale (xi, yi).
Elaboraţi un program care construieşte un dreptunghi de arie minimă cu laturile paralele cu
axele de coordonate, ce conţine toate punctele P1, P2, P3, ..., Pn. Evident, orice punct de pe latura
dreptunghiului aparţine acestuia.
Date de intrare. Fişierul text PUNCTE.IN conţine pe prima linie numărul natural n. Fiecare
din următoarele n linii ale fişierului de intrare conţine cîte două numere reale separate prin spaţiu.
Linia i + 1 a fişierului de intrare conţine coordonatele xi, yi ale punctului Pi.
Date de ieşire. Fişierul text PUNCTE.OUT va conţine două linii. Pe prima linie se scriu
coordonatele (a, b) ale colţului stînga-jos, iar pe linia a doua − coordonatele (c, d) ale colţului
dreapta-sus ale dreptunghiului construit. Numerele respective vor fi scrise fără specificatori de
format şi separate prin spaţiu.
Exemplu.
PUNCTE.IN PUNCTE.OUT
4 1.0000000000E+00 2.0000000000E+00
2.0 6.0 4.0000000000E+00 7.0000000000E+00
1.0 2.0
3.0 7.0
4.0 5.0

Restricţii. 2 ≤ n ≤ 10 000 ; 0 ≤ xi , y i ≤ 70 000 . Timpul de execuţie nu va depăşi o secundă.


Fişierul sursă va avea denumirea PUNCTE.PAS, PUNCTE.C sau PUNCTE.CPP.

21
Rezolvare

Presupunem că a fost construit un dreptunghi cu laturile paralele cu axele de coordonate, ce


conţine toate punctele P1, P2, P3, ..., Pn. Acest dreptunghi va fi de arie minimă numai atunci, cînd pe
fiecare latură se va afla cel puţin cîte un punct din mulţimea {P1, P2, P3, ..., Pn}. În caz contrar, aria
dreptunghiului în studiu ar putea fi micşorată, deplasînd latura ce nu conţine nici un punct în
interiorul dreptunghiului (Fig. 1).

Fig. 1

Prin urmare, coordonatele vîrfurilor dreptunghiului se determină conform relaţiilor:

a = min( x1 , x 2 , ..., xi , ..., x n ) ; b = min( y1 , y 2 , ..., y i , ..., y n )


c = max( x1 , x 2 , ..., xi , ..., x n ) ; d = max( y1 , y 2 , ..., yi , ..., y n ) .
Întrucît volumul de memorie statică, alocat de mediul de programare unui program Pascal, nu
este suficient pentru memorarea coordonatele punctelor P1, P2, P3, ..., Pn, în programul ce urmează
tablourile X şi Y sînt declarate ca varibile dinamice.

Program Puncte;
{ Clasele 10-12 }
const
nmax=10000; { numarul maximal de puncte }
ValoareaMaxima=70000;
ValoareaMinima=0;
type Tablou = array[1..nmax] of real;
var n : integer;
a, b, c, d : real;
X, Y :^Tablou;

procedure Citire;
var Intrare : text;
i : integer;
begin
assign(Intrare, 'PUNCTE.IN');
reset(Intrare);

22
readln(Intrare, n);
for i:=1 to n do
readln(Intrare, X^[i], Y^[i]);
close(Intrare);
end; { Citire }

procedure Scrie;
var Iesire : text;
begin
assign(Iesire, 'PUNCTE.OUT');
rewrite(Iesire);
writeln(Iesire, a,' ', b);
writeln(Iesire, c,' ', d);
close(Iesire);
end; { Scrie }

procedure Dreptunghiuri;
var i : integer;
begin
{ calculam minimul din X[i] }
a:=ValoareaMaxima;
for i:=1 to n do
if X^[i]<a then a:=X^[i];
{ calculam minimumul din Y[i] }
b:=ValoareaMaxima;
for i:=1 to n do
if Y^[i]<b then b:=Y^[i];
{ calculam maximumul din X[i] }
c:=ValoareaMinima;
for i:=1 to n do
if X^[i]>c then c:=X^[i];
{ calculam maximumul din Y[i] }
d:=ValoareaMinima;
for i:=1 to n do
if Y^[i]>d then d:=Y^[i];
end; { Dreptunghiuri }

begin
new(X); new(Y);
Citire;
Dreptunghiuri;
Scrie;
dispose(X); dispose(Y);
end.

23
Descrierea problemelor − Ziua 2

Clasele 7 - 9

Numărul de Denumirea Denumirea


Denumirea Denumirea
puncte alocat fişierului de fişierului de
problemei fişierului sursă
problemei intrare ieşire
PASARI.PAS
Păsări
100 PASARI.C PASARI.IN PASARI.OUT
călătoare
PASARI.CPP

MODEME.PAS
Modeme 100 MODEME.C MODEME.IN MODEME.OUT
MODEME.CPP

CANGUR.PAS
Cangurul 100 CANGUR.C CANGUR.IN CANGUR.OUT
CANGUR.CPP

Total 300 - - -

24
Păsări călătoare

O colonie de păsări călătoare este formată din n cuiburi, notate prin C1, C2, ..., Ci, ..., Cn.
Fiecare cuib Ci este definit prin coordonatele întregi carteziene xi, yi şi numărul de păsări mi ce
locuiesc în el.
Pentru a migra spre sud, într-o zi de toamnă, păsările se adună într-un singur stol,
coordonatele întregi carteziene ale căruia sînt notate prin xs, ys. Evident, pentru a ajunge din cuibul
Ci în punctul de formare a stolului, fiecare pasăre din acest cuib trebuie să parcurgă în zbor distanţa
dintre punctele (xi, yi) şi (xs, ys):

d i = ( xi − x s ) 2 + ( y i − y s ) 2 .

Pentru a economisi puterile înainte de o călătorie foarte lungă, instinctiv, păsările aleg
coordonatele xs, ys ale punctului de formare a stolului în aşa mod, încît suma S a distanţelor parcurse
în zbor de toate păsările coloniei să fie minimă.
Elaboraţi un program care calculează suma minimă S.
Date de intrare. Fişierul text PASARI.IN conţine pe prima linie numărul natural n. Fiecare
din următoarele n linii ale fişierului de intrare conţine cîte trei numere întregi separate prin spaţiu.
Linia i + 1 a fişierului de intrare conţine numerele xi, yi, mi.
Date de ieşire. Fişierul text PASARI.OUT va conţine pe o singură linie numărul real S scris
fără specificatori de format.
Exemplu.
PASARI.IN PASARI.OUT
4 1.1313708499E+01
1 1 1
5 1 1
5 5 1
1 5 1

Restricţii. 2 ≤ n ≤ 50 ; 1 ≤ xi , y i ≤ 100 ; 1 ≤ mi ≤ 10 . Timpul de execuţie nu va depăşi o


secundă. Fişierul sursă va avea denumirea PASARI.PAS, PASARI.C sau PASARI.CPP.

25
Rezolvare

Conform restricţiilor problemei, toate punctele ce simbolizează cuiburile C1, C2, ..., Ci, ..., Cn
aparţin unui pătrat cu laturile paralele axelor de coordonate şi vîrfurile (1, 1), (10, 1), (10, 10), (1,
10). Evident, punctul de formare a stolului (xs, ys) aparţine, de asemenea, aceluiaşi pătrat (Fig. 1).

y
10 Cuib

* Stol
...
*
...

2
1
0 1 2 ... ... 10 x

Fig. 1

Pentru a determina suma minimă S vom folosi metoda trierii, calculînd consecutiv pentru
fiecare punct cu coordonatele întregi (xs, ys), 1 ≤ x s ≤ 10 şi 1 ≤ y s ≤ 10 , suma distanţelor
n
S = ∑ mi d i ,
i =1

scriind în fişierul de ieşire doar valoarea minimă a acesteia.


În programul ce urmează suma distanţelor S se calculează cu ajutorul funcţiei Suma, iar suma
minimă − cu ajutorul procedurii SumaMinima. Pentru a evita erorile de depăşire în cazul calculării
funcţiei standard QRT, în antetul funcţiei Suma argumentele xs şi ys sînt declarate ca marimi reale.

Program Pasari;
{ Clasele 7-9 }
const Dimensiuni=100;
nmax=50;
var n : integer;
X, Y, M : array[1..nmax] of integer;
S : real;
Intrare, Iesire : text;

procedure Citire;
{ Citirea datelor din fisierul de intrare }
var i : integer;
begin
assign(Intrare, 'PASARI.IN');
reset(Intrare);
readln(Intrare, n);
for i:=1 to n do readln(Intrare, X[i], Y[i], M[i]);
close(Intrare);
end; { Citire }

26
procedure Scrie;
{ Scrierea datelor in fisierul de iesire }
begin
assign(Iesire, 'PASARI.OUT');
rewrite(Iesire);
writeln(Iesire, S);
close(Iesire);
end; { Scrie }

function Suma(xs, ys : real) : real;


var i : integer;
r : real;
begin
r:=0;
for i:=1 to n do
r:=r+M[i]*SQRT(SQR(X[i]-xs)+SQR(Y[i]-ys));
Suma:=r;
end; { Suma }

procedure SumaMinima;
var xs, ys : integer;
r : real;
begin
S:=1.7E38; { valoarea maximal admisibila a sumei distantelor }
for xs:=1 to Dimensiuni do
for ys:=1 to Dimensiuni do
begin
r:=Suma(xs, ys);
if r<S then S:=r;
end;
end; { SumaMinima }

begin
Citire;
SumaMinima;
Scrie;
end.

Din analiza textului procedurii SumaMinima se observă că funcţia Suma va fi apelată de


100 × 100 = 10 4 ori. La rîndul său, în procesul execuţiei subprogramului Suma, funcţia standard
SQRT va fi apelată de cel mult 50 de ori, iar funcţia standard SQR − de cel mult 100 de ori. Evident,
numărul de apeluri ale funcţiilor standard SQRT şi SQR este de ordinul 106, iar numărul necesar de
operaţii elementare este comparabil cu capacitatea de prelucrare a calculatoarelor personale. Prin
urmare, timpul de execuţie al programului PASARI nu va depăşi o secundă.

27
Modeme

O companie care produce modeme pentru conectarea calculatoarelor la reţea a elaborat o


metodă originală de testare a acestora. Pentru a fi testate, modemele trebuie grupate în perechi. În
acest scop, mai întîi modemele sînt numerotate prin 1, 2, 3, ..., n. În continuare, se formează astfel
de perechi de modeme {i, j} , pentru care suma i + j este un număr prim. Perechile {i, j} şi { j , i}
sînt identice. Unul şi acelaşi modem poate fi inclus numai într-o singură pereche.
Elaboraţi un program care calculează numărul maxim de astfel de perechi.
De exemplu, pentru n = 7 , numărul maxim de astfel de perechi este 3, iar perechile respective
pot fi {1, 6} , {2, 5} şi {4, 7} .

Date de intrare. Fişierul text MODEME.IN conţine pe o singură linie numărul


natural n.
Date de ieşire. Fiecare linie a fişierului text MODEME.OUT va conţine cîte două numere
naturale separate prin spaţiu − o pereche de modeme ce satisface condiţiile problemei. Ordinea în
care perechile respective apar în fişierul de ieşire este arbitrară.
Exemplu.
MODEME.IN MODEME.OUT
7
1 6
2 5
4 7

Restricţii. 2 ≤ n ≤ 500 000 . Timpul de execuţie nu va depăşi o secundă. Fişierul sursă va avea
denumirea MODEME.PAS, MODEME.C sau MODEME.CPP.

28
Rezolvare

Mai întîi se găseşte un număr prim p mai mare decît numărul n. Evident, perechile { p − n, n}
şi { p − n + 1, n − 1} sînt perechi care au suma respectivă egală cu p şi satisfac condiţiilor problemei.
De asemenea, toate numerele de la p − n pînă la n tot vor forma astfel de perechi.
În continuare, soluţionam aceiaşi problemă pentru o nouă valoare a lui n, şi anume, pentru
p − n − 1 . Acest proces se va repeta atît timp, cît valoarea curentă a lui n este mai mare ca unu. În
rezultat obţinem mulţimea care este formată din toate perechile posibile ce corespund condiţiilor din
enunţul problemei.

Program Modeme;
var n : longint;
Intrare, Iesire : text;

procedure Citire;
begin
assign(Intrare, 'MODEME.IN');
reset(Intrare);
readln(Intrare, n);
close(Intrare);
end; { Citire }

function NumarPrim(k : longint) : boolean;


{ Returneaza true daca k este numar prim }
var i, j : longint;
begin
j:=0;
for i:=1 to k do
if (k mod i)=0 then j:=j+1;
if j=2 then NumarPrim:=true else NumarPrim:=false;
end; { NumarPrim }

Procedure Perechi;
var i, j, p : longint;
begin
while n>1 do
begin
p:=n+1;
while not NumarPrim(p) do p:=p+1;
i:=p-n;
j:=n;
n:=i-1;
while i<j do
begin
writeln(Iesire,i,' ',j);
i:=i+1;
j:=j-1
end;
end;
end; { Perechi }

begin
Citire;
assign(Iesire,'MODEME.OUT');
rewrite(Iesire);
Perechi;
close(Iesire);
end.

29
Menţionăm, că funcţia NumarPrim realizează metoda “forţei brute”, calculînd prin triere
numărul de divizori ai argumentului k. Prin măsurări directe ne putem convinge că pentru
n = 500 000 timpul de execuţie poate depăşi valoarea de o secundă. Pentru a evita astfel de situaţii,
vom utiliza un algoritm mai “inteligent”, şi anume:

function NumarPrim(k:longint): boolean;


{ Returneaza true daca k este numar prim }
var i, j : longint;
t : boolean;
begin
t:=true;
j:=2;
while t and (j*j<=k) do
begin
if (k mod j)=0 then t:=false;
j:=j+1
end;
NumarPrim:=t
end; { NumarPrim }

30
Cangurul

În poziţia iniţială, cangurul se afla la o distanţă de n salturi de un izvor punctiform. Cangurul


poate face salturi doar pe semidreapta care trece prin poziţia iniţială şi are originea în punctul ce
simbolizează izvorul (Fig. 1).

n ... 4 3 2 1

- cangurul - izvorul

- pozitia initiala a cangurului

Fig. 1

Întrucît cangurul nu întotdeauna ţine minte unde se află izvorul, înaintea fiecărui salt el decide
în care direcţie va face saltul respectiv: înainte sau înapoi. Este cunoscut faptul că după k salturi
cangurul a ajuns la izvor.
Elaboraţi un program, care calculează numărul de variante posibile de deplasare a cangurului
din poziţia iniţială la izvor.
Date de intrare.
Fişierul text CANGUR.IN conţine pe o singură linie numerele naturale n şi k, separate prin
spaţiu.
Date de ieşire.
Fişierul text CANGUR.OUT va conţine pe o singură linie un număr întreg − numărul de variante
posibile de deplasare a cangurului din poziţia iniţială la izvor.
Exemplu.
CANGUR.IN CANGUR.OUT
2 4 2

Restricţii. 1 ≤ n ≤ k ≤ 37 . Timpul de execuţie nu va depăşi o secundă. Fişierul sursă va avea


denumirea CANGUR.PAS, CANGUR.C sau CANGUR.CPP.

31
Rezolvare

Introducem în studiu tabloul A. Elementul A[i] al acestui tablou reprezintă numărul de


variante posibile de deplasare a cangurului din poziţia aflată la distanţa i la izvor prin t salturi
(Fig. 2). Întrucît din poziţia iniţială cangurul se poate deplasa în direcţia opusă izvorului cu cel mult
k salturi, tabloul A trebuie să aibă cel puţin n+k componente. Evident, pentru i=1 şi t=1, avem
A[1]=1, A[2]=0, ..., A[n+k]=0.

A[i]
A[i+1] A[i-1]
t
i-1 i i+1

B[i]

t+1
i-1 i i+1

- cangurul - izvorul

Fig. 2

Într-un mod similar, introducem în studiu tabloul B. Elementul B[i] al acestui tablou
reprezintă numărul de variante posibile de deplasare a cangurului din poziţia aflată la distanţa i la
izvor prin t+1 salturi (Fig. 2). Cunoscînd elementele tabloului A, putem calcula elementele
tabloului B:
B[i]:=A[i-1]+A[i+1], i=1, 2, 3, ..., n+k.
Pentru a evita verificările în cazurile în care i=1 sau i=n+k, prin definiţie stabilim A[0]=0,
A[n+k+1]=0 şi B[0]=0, B[n+k+1]=0.
Evident, pentru a afla numărul de variante posibile de deplasare a cangurului la izvor prin t+2
salturi, stabilim A:=B şi calculăm din nou elementele tabloului B. Prin urmare, calculînd elementele
tablourilor A şi B pentru t=2, 3, ..., k, vom afla şi valoarea A[n], care reprezintă soluţia problemei.

Program Cangurul;
const nmax=37;
kmax=37;
var
n, k : integer;
A, B : array [0..nmax+kmax+1] of longint;
Intrare, Iesire : text;

procedure Citire;
begin
assign(Intrare, 'CANGUR.IN');
reset(Intrare);
readln(Intrare, n, k);
close(Intrare);
end; { Citire }

32
procedure Scrie;
begin
assign(Iesire, 'CANGUR.OUT');
rewrite(Iesire);
writeln(Iesire, A[n]);
close(Iesire);
end; { Citire }

procedure Salturi;
var i, t : integer;
begin
{ zerografiem tablourile A si B }
for i:=0 to n+k+1 do
A[i]:=0;
B:=A;
{ calcule recurente }
A[1]:=1;
for t:=2 to k do
begin
for i:=1 to n+k do
B[i]:=A[i-1]+A[i+1];
A:=B;
end; { for }
end; { Salturi }

begin
Citire;
Salturi;
Scrie;
end.

Complexitatea temporală a procedurii Salturi este O(nk+k2). Evident, pentru restricţiile


1 ≤ n ≤ k ≤ 37 , timpul de calcul va fi mai mic de o secundă.

33
Descrierea problemelor − Ziua 2

Clasele 10 - 12

Numărul de Denumirea Denumirea


Denumirea Denumirea
puncte alocat fişierului de fişierului de
problemei fişierului sursă
problemei intrare ieşire
BILIARD.PAS
Biliard 100 BILIARD.C BILIARD.IN BILIARD.OUT
BILIARD.CPP

GENE.PAS
Gene 100 GENE.C GENE.IN GENE.OUT
GENE.CPP

CANGUR.PAS
Cangurul 100 CANGUR.C CANGUR.IN CANGUR.OUT
CANGUR.CPP

Total 300 - - -

34
Biliard

În cazul Biliardului Matematic masa de joc este simbiolizată printr-un dreptunghi cu


dimensiunile a × b , cu laturile paralele cu axele de coordonate şi originea sistemului de coordonate
în colţul stînga-jos (Fig. 1).

Y
- bila
- tacul
- gaura cu pungă
- traiectoria bilei

X
0 a

Fig. 1

Pe masa de joc se află o bilă punctiformă cu coordonatele (xb, yb). În colţul stînga-jos masa de
biliard are o gaură punctiformă, prin care, în cazul unei lovituri reuşite cu tacul, bila cade într-o
pungă. Se consideră că forţa de frecare este egală cu zero, iar loviturile bilei de marginile mesei se
produc conform legii “unghiul de cădere este egal cu unghiul de reflexie”. În Biliardul Matematic
se consideră reuşită doar acea lovitură cu tacul, după care, înainte de a cădea în pungă, bila se
loveşte de marginile mesei exact de k ori, k ≥ 1 . Conform regulilor jocului, colţurile mesei nu
aparţin nici la una din margini. Lovitura cu tacul se defineşte prin coordonatele (xi, yi) ale punctului
de pe marginea mesei, în care bila va lovi pentru prima oară.
Elaboraţi un program, care, cunoscînd coordonatele (xb, yb) şi numărul de lovituri k ale bilei
de marginile mesei, calculează toate variantele posibile de lovituri reuşite cu tacul.
Date de intrare. Fişierul text BILIARD.IN conţine pe prima linie numerele reale a, b,
separate prin spaţiu. Linia a doua a fişierului de intrare conţine numerele reale xb, yb, separate prin
spaţiu. Linia a treia a fişierului de intrare conţine numărul întreg k.
Date de ieşire. Fişierul text BILIARD.OUT va conţine pe prima linie un număr întreg n −
numărul variantele posibile de lovituri reuşite cu tacul. Fiecare din următoarele n linii ale fişierului
de ieşire va conţine cîte două numere reale scrise fără specificatori de format şi separate prin spaţiu.
Linia i + 1 a fişierului de ieşire va conţine coordonatele xi, yi ce definesc o lovitură reuşită cu tacul.
Exemplu.
BILIARD.IN BILIARD.OUT
3.0 2.0 3
1.5 1.5 0.0000000000E+00 1.2000000000E+00
2 1.0909090909E+00 0.0000000000E+00
2.4000000000E+00 2.0000000000E+00

Restricţii. 1 ≤ k ≤ 20 ; 0 ≤ a, b ≤ 100 . Timpul de execuţie nu va depăşi o secundă. Fişierul


sursă va avea denumirea BILIARD.PAS, BILIARD.C sau BILIARD.CPP.

35
Rezolvare

Pentru a studia mişcarea bilei, introducem în studiu mese virtuale de joc. Prima masă virtuală
se obţine prin reflexia în “oglindă” a mesei de biliard, oglinda respectivă fiind formată de marginea
de care bila s-a lovit pentru prima oară (Fig. 2).

Fig. 2

A doua masă virtuală se obţine prin reflexia în “oglindă” a primei mese virtuale, “oglinda”
respectivă fiind formată de marginea virtuală de care bila s-a lovit a doua oară. Evident, într-un mod
similar, pot fi obţinute toate cele k mese virtuale de joc.
Întrucît loviturile bilei de marginile mesei se produc conform legii “unghiul de cădere este
egal cu unghiul de reflexie”, traectoria bilei pe mesele virtuale reprezintă o linie dreaptă, care
reuneşte punctul cu coordonatele (xb, yb) cu imaginea în “oglindă” a găurii punctiforme.
Prin urmare, pentru a determina toate variantele posibile de lovituri reuşite cu tacul, divizăm
planul cartezian în dreptunghiuri de dimensiunile a × b şi plasăm în punctele cu coordonatele
( 2ia , 2 jb ) , − k ≤ i, j ≤ k , imagini ale găurii punctiforme. În continuare, reunim prin linii drepte
punctul (xb, yb) cu fiecare din punctele ( 2ia , 2 jb ) şi selectăm segmentele care intersectează exact k
laturi ale dreptunghiurilor respective. Evident, nu vor fi examinate dreptele care trec prin vîrfuri de
dreptunghiuri.

Program Biliard;
const kmax=25;
var a, b, { dimensiunile mesei }
x, y : real; { coordonatele bilei }
k, { numarul cerut de lovituri de marginile mesei }
m : integer; { numarul de lovituri reusite cu tacul }
L : array [1..kmax, 1..2] of real; { loviturile reusite cu tacul}
Intrare, Iesire : text;

procedure Citeste;
begin
assign(Intrare,'BILIARD.IN');
reset(Intrare);
readln(Intrare, a, b);
readln(Intrare, x, y);
readln(Intrare, k);
close(Intrare);

36
end; {Citeste }

procedure Scrie;
var i : integer;
begin
assign(Iesire,'BILIARD.OUT');
rewrite(Iesire);
writeln(Iesire, m);
for i:=1 to m do
writeln(Iesire, L[i, 1],' ',L[i, 2]);
close(Iesire);
end; { Scrie }

procedure LovituriReusite;
var i, j : integer;
c, d : real; { coordonatele gaurilor virtuale }
dir : integer;
t, u : real;
trece : boolean;
begin
m:=0; { numarul curent de lovituri reusite cu tacul }
for i:=-k to k do
for j:=-k to k do
begin
c:=2*i*a; d:=2*j*b;
if trunc(abs(c-x)/a)+trunc(abs(d-y)/b)=k then
begin
{ verifica sa nu treaca prin nici un alt virf }
if c>x then dir:=-1 else dir:=1;
t:=c+dir*a;
trece:=false;
while dir*t<dir*x do
begin
u:=y+(d-y)*(t-x)/(c-x);
u:=u/b;
if (abs(u-round(u))<0.00001) then trece:=true;
t:=t+dir*a;
end; { while }
if not(trece) then
begin
m:=m+1;

if (((b-y)*(c-x)-(a-x)*(d-y))*((b-y)*(0-x)-(a-x)*(b-y))>=0) and
(((b-y)*(c-x)-(0-x)*(d-y))*((b-y)*(a-x)-(0-x)*(b-y))>0) then
begin L[m, 1]:=x+(c-x)*(b-y)/(d-y); L[m, 2]:=b; end;

if (((0-y)*(c-x)-(a-x)*(d-y))*((0-y)*(a-x)-(a-x)*(b-y))>=0) and
(((b-y)*(c-x)-(a-x)*(d-y))*((b-y)*(a-x)-(a-x)*(0-y))>0) then
begin L[m, 1]:=a; L[m, 2]:=y+(d-y)*(a-x)/(c-x); end;

if (((0-y)*(c-x)-(0-x)*(d-y))*((0-y)*(a-x)-(0-x)*(0-y))>=0) and
(((0-y)*(c-x)-(a-x)*(d-y))*((0-y)*(0-x)-(a-x)*(0-y))>0) then
begin L[m, 1]:=x+(c-x)*(0-y)/(d-y); L[m, 2]:=0; end;

if (((0-y)*(c-x)-(0-x)*(d-y))*((0-y)*(0-x)-(0-x)*(b-y))>=0) and
(((b-y)*(c-x)-(0-x)*(d-y))*((b-y)*(0-x)-(0-x)*(0-y))>0) then
begin L[m, 1]:=0; L[m, 2]:=y+(d-y)*(0-x)/(c-x); end;

if abs(L[m, 1])+abs(L[m, 2])<0.000001 then m:=m-1;


end; { then }
end; { then }
end;
end; { LovituriReusite }

37
begin
Citeste;
LovituriReusite;
Scrie;
end.

Numărul de execuţii ale instrucţiunii compuse begin ... end din componenţa ciclurilor
imbricate for ale procedurii LovituriReusite este 4k 2 . Evident, pentru k ≤ 20 , timpul cerut de
această procedură va fi cu mult mai mic decît o secundă.

38
Gene
Se consideră o mulţime de gene G = {g1, g2, ..., gi, ..., gn}. În scopuri didactice, fiecare genă gi
din mulţimea G este reprezentată printr-un şir de caractere, format din literele mici ale alfabetului
latin. Toate şirurile de caractere din mulţimea G au aceiaşi lungime.
Prin definiţie, gena gk este rezultatul încrucişării genelor gi, gj (acest fapt se notează prin
gi • gj → gk), dacă şirul de caractere gk poate fi obţinut din şirurile de caractere gi, gj prin înlocuirea
unui prefix din şirul gi (gj) cu un prefix de aceeaşi lungime din şirul gj (gi).
De exemplu, abcde • xyztv → xycde, întrucît şirul xycde poate fi obţinut din şirul
abcde prin înlocuirea prefixului ab cu prefixul xy din şirul xyztv.
Într-un mod similar, abcde • xyztv → abztv, întrucît şirul abztv poate fi obţinut din
şirul xyztv prin înlocuirea prefixului xy cu prefixul ab din şirul abcde.
Numim protopereche a mulţimii G orice pereche de gene {gi, gj}, gi ∈ G, gj ∈ G, pentru care
mulţimea G poate fi ordonată în forma:
(gi, gj, gm, ..., gk, ..., gq)
în aşa mod, încît fiecare genă gm, ..., gk, ..., gq este rezultatul încrucişării a două din genele
anterioare.
De exemplu, genele abcabc şi xymnpr sînt o protopereche a mulţimii:
G = {abcnpr, abmabc, xymnpr, aymabc, aycabc, xymabc, abcabc},
întrucît mulţimea în studiu poate fi ordonată în forma:
(abcabc, xymnpr, xymabc, abcnpr, abmabc, aymabc, aycabc),
unde
abcabc • xymnpr → xymabc;
abcabc • xymnpr → abcnpr;
xymabc • abcabc → abmabc;
xymabc • abcabc → aymabc;
aymabc • abcabc → aycabc.
Elaboraţi un program care calculează toate protoperechile posibile ale mulţimii G.
Date de intrare. Fişierul text GENE.IN conţine pe prima linie numărul natural n. Fiecare din
următoarele n linii ale fişierului de intrare conţine cîte un şir de caractere. Linia i + 1 a fişierului de
intrare conţine şirul de caractere ce reprezintă gena gi.
Date de ieşire. Fişierul text GENE.OUT va conţine protoperechile găsite, scrise în ordine
arbitrară. Fiecare protopereche {gi, gj} va ocupa două linii vecine: una din ele va conţine şirul de
caractere ce reprezintă gena gi, iar cealaltă − şirul de caractere ce reprezintă gena gj.
Exemplu.
GENE.IN GENE.OUT
7 abcabc
abcnpr xymnpr
abmabc abcnpr
xymnpr xymabc
aymabc
aycabc
xymabc
abcabc

39
Restricţii. 2 ≤ n ≤ 100 . Mulţimea G conţine cel puţin o protopereche. Lungimea oricărui şir
de caractere din fişierul de intrare este mai mică sau egală cu 100. Timpul de execuţie nu va depăşi
2 secunde. Fişierul sursă va avea denumirea GENE.PAS, GENE.C sau GENE.CPP.

Rezolvare

Introducem în studiu funcţiile MaxPref( x, y ) şi MaxSuf( x, y ) , valorile cărora reprezintă


lungimile celui mai lung prefix, respectiv, sufix comun ale genelor de lungime egală x şi y.
De exemplu,
MaxPref( ' xymnpr' , ' xymabc' ) = 3 ; MaxSuf (' abcabc' , ' xymabc' ) = 3 .
Afirmaţia 1. Gena z poate fi rezulatul încrucişării genelor x şi y, daca şi numai daca
max(MaxPre f( x, z ), MaxPref ( y, z )) + max(MaxSuf ( x, z ), MaxSuf ( y, z )) ≥ length ( z ) .

Demonstraţie. Fie
p = max(MaxPre f ( x, z ), MaxPref ( y, z )) ; t = max(MaxSuf ( x, z ), MaxSuf ( y, z )) .
Evident, p = MaxPref( x, z ) sau p = MaxPref( y, z ) . Fără a restrînge generalitea, putem
presupune că p = MaxPref ( x, z ) .
Într-un mod similar, t = MaxSuf ( x, z) sau t = MaxSuf ( y, z ) . Dacă t = MaxSuf ( x, z ) , atunci
concludem că x = z , fiindcă genele respective au un prefix şi sufix comun de lungime mai mare sau
egală cu length ( z ) . Dacă t = MaxSuf( y, z ) , atunci, luînd prefixul de lungime p din x şi sufixul de
lungime length ( z ) - p din y, prin încrucişare, obţinem gena z.
Reciproc, daca x şi y pot produce prin încrucişare gena z, atunci există un p, un prefix din x de
lungime p şi un sufix din y de lungime length ( z ) - p , care formează gena z (sau un prefix de lugime
p din y şi un sufix din x de lungime length ( z ) - p , dar acest caz se examinează în acelaşi mod).
Deci, MaxPref ( x, z ) ≥ p şi MaxSuf ( y,z ) ≥ length ( z ) - p , de unde rezultă inegalitatea din
enunţul afirmaţiei.
Fie S o mulţime nevidă de gene. Introducem în studiu funcţiile MaxPrefGen ( S, z ) şi
MaxSufGen( S, z ) , valorile cărora reprezintă lugimile celui mai lung prefix comun, respective sufix
comun, al genei z şi al unei gene din mulţimea S:
MaxPrefGen( S , z ) = max(MaxPref(xi , z )) ; MaxSufGen(S , z ) = max(MaxSuf(xi , z )) .
xi ∈S xi ∈S

Afirmaţia 2. Gena z este rezultatul încrucişării a două gene din muţimea S dacă şi numai dacă
MaxPrefGen ( S , z ) + MaxSufGen( S , z ) ≥ length( z ) .

Demonstraţie. Presupunem că inegalitatea din enunţul afirmaţiei se respectă. Evident, în acest


caz există genele xi şi xj, pentru care
MaxPref( xi , z ) + MaxSuf( x j , z ) ≥ length( z ) .

Prin urmare, gena z este rezultatul încrucişării genelor xi şi xj.


Reciproc, daca gena z este rezultatul încrucişării genelor xi şi xj prin preluarea prefixului de la
xi şi sufixului de la xj, atunci

40
MaxPref ( xi , z ) + MaxPref( x j , z ) ≥ z
sau
max(MaxPref(xi , z)) + max(MaxSuf(xi , z)) ≥ length(z ) ,
xi ∈S xi ∈S

ceea ce trebuia de demonstrat.


Folosind funcţiile MaxPrefGen ( S, z ) şi MaxSufGen( S, z ) , rezolvarea problemei devine
intuitivă. Începem cu mulţimea iniţială S ce conţine două gene din G, pe care dorim să le testăm
dacă sînt o protopereche. Calculăm pentru toate celelalte gene z din G funcţiile MaxPrefGen ( S , z )
şi MaxSufGen( S , z ) . Dacă, conform formulelor de mai sus, găsim o genă z care poate obţinută prin
încrucişare din genele din S, o includem în S şi recalculăm funcţiile respective. Procesul continuă
pînă nu se mai poate găsi nici o genă din mulţimea iniţială care să fie rezultatul încrucişării a doua
gene din S, sau pînă cînd S va conţine toate genele din G.
Afirmaţia 3. Perechea iniţială este o protopereche dacă şi numai dacă după aplicarea
procedurii de mai sus, se obţine mulţimea G.
Demonstraţie. Dacă {x, y} este o protopereche, atunci exista o ordonare a cuvintelor din G,
care încep cu x şi y, astfel încît fiecare genă din şir, mai puţin primele două, sînt obţinute printr-o
încrucişare a două gene anterioare. Vom nota această ordonare prin x1, x2, x3 , ..., xn.
Menţionăm, că dacă S poate genera gena z, atunci şi S U V poate genera aceiaşi genă z. Prin
urmare, conform procedurii de mai sus, gena x3 va fi neapărat inclusă în mulţimea S (posibil şi mai
tîrziu, în cazul în care x1 şi x2 pot genera prin încrucişare mai multe gene din S). În baza aceluiaşi
raţionament, la un anumit pas (nu neapărat al doilea), gena x4 va fi inclusă in mulţimea S, deoarece
{x1, x2, x3} poate produce gena x4. Urmînd raţionamentul, observăm, că procesul de calcul se va
termina doar atunci, cînd în S vor fi incluse toate genele din G.
Reciproc, dacă după efectuarea procedurii de mai sus se obţine mulţimea G, este evident că
perechea de pornire este o protopereche. Mai mult, ordinea în care sînt incluse genele din G
corespunde proprietăţilor din enunţul problemei.
Evident, pentru a găsi toate protoperechile, trebuie să verificăm cu ajutorul procedurii descrise
mai sus toate perechile posibile, formate din genele mulţimii G.
Pentru a reduce volumul calculelor, vom memora valorile MaxPrefGen ( S , z ) şi
MaxsufGen( S , z ) într-un vector. Imediat cum am selectat o genă nouă z, care reprezintă rezultatul
încrucişării a doua gene din S, recalculăm elementele vectorului respectiv:
MaxPrefGen ( S U {z}, t ) = max(MaxPre fGen( S , t ), MaxPref( z , t )) ;
MaxSufGen( S U {z}, t ) = max(MaxSuf Gen( S , t ), MaxSuf( z , t )).
Astfel, avînd valorile precedente şi cunoscînd valorile MaxPref( x, t ) şi MaxSuf( x, t ) , putem
actualiza elementele vectorului în studiu în timp O(n). Evident, valorile funcţiilor MaxPref şi
MaxSuf pentru toate perechile de şiruri pot fi calculate înainte de a începe testarea propriu-zisă a
perechilor de gene din mulţimea G. Aceasta operaţie necisită O(N + n2) operaţii, unde N este
dimensiunea datelor de intrare. Întrucît toate şirurile de caractere din fişierul de intrare au aceiaşi
lungime, N = n ⋅ length( x ) , unde x este orice şir de caractere ce reprezintntă o genă.
Pentru aceasta problemă, putem folosi tehnica naivă, care pentru fiecare pereche găseşte cel
mai mic indice al unui caracter diferit în cele două cuvinte, avînd complexitatea O(n 2 length( x)) .
Pentru MaxSuf( x, y ) se procedeazăîn acelaşi mod.
Aşa cum este prezentat mai sus, algoritmul propus are complexitatea O(n 4 + n 2 length( x )) .
Pentru n = 100 , complexitatea O(n4) depăşeşte puterea de calcul a calculatoarelor din laboratorul de
informatică. Pentru a micşora numărul necesar de operaţii, vom lua în considerare faptul că toate
genele obţinute prin încrucişare vor avea pe poziţia i una din cele doua litere de pe pozitia i a

41
genelor iniţiale. Astfel, în loc să testăm n2 perechi, vom testăm cel mult doar n perechi. Evident, în
acest caz complexitatea algoritmului va fi O(n 3 + n 2 length( x )) .

Program Gene;
{ Clasele 10-12 }
type Gena = string[100];
var S : array [1..100] of Gena;
n, i, j : integer;
x, y, s1, s2 : Gena;
MaxPrefVal, MaxSufVal : array[1..100, 1..100] of byte;
MaxPrefGen, MaxSufGen : array[1..101] of byte;
B : array[1..101] of boolean;
Intrare, Iesire : Text;

procedure Citire;
var i : integer;
begin
assign(Intrare, 'GENE.IN');
reset(Intrare);
readln(Intrare, n);
for i:=1 to n do
readln(Intrare, S[i]);
close(Intrare);
end; { Citire }

function MaxPref(var x, y : Gena) : integer;


{ Calculeaza functia MaxPref }
var I : integer;
begin
i:=0;
while i<length(x) do
begin
if x[i+1]=y[i+1] then inc(i) else break;
end; {while}
MaxPref:=i;
end; { MaxPref }

function MaxSuf(var x, y : Gena) : integer;


{ Calculeaza functia MaxSuf }
var i : integer;
begin
i:=0;
while i<length(x) do
begin
if x[length(x)-i]=y[length(y)-i] then inc(i) else break;
end; { while }
MaxSuf:=i;
end; { MaxSuf }

function Verifica : boolean;


{ Verifica daca pe fiecare pozitie sunt cel mult 2 litere distincte }
var i, j : integer;

42
begin
s1:=s[1]; s2:=s1;
for i:=2 to n do
for j:=1 to length(s[1]) do
begin
if (s[i][j]<>s1[j])and(s[i][j]<>s2[j])
then { am gasit o litera noua pe pozitia j }
if s2[j]=s1[j] then s2[j]:=s[i][j] else
begin
{ am gasit deja o a treia litera pe pozitia j}
Verifica:=false;
exit;
end; { else }
end; { do }
Verifica:=true;
end; { Verifica }

procedure Precalculare;
{ Calculeaza functiile MaxPref }
var i, j : integer;
begin
for i:=1 to n-1 do
for j:=i+1 to n do
begin
MaxPrefVal[i,j]:=MaxPref(s[i],s[j]);
MaxPrefVal[j,i]:=MaxPrefVal[i,j];
MaxSufVal[i,j]:=MaxSuf(s[i],s[j]);
MaxSufVal[j,i]:=MaxSufVal[i,j];
end;
end; { Precalculare }

function Max(a, b : byte) : byte;


begin
if a<b then Max:=b else Max:=a;
end; { Max }

function Protopereche(i, j : integer) : boolean;


var k, t, q : integer;
begin
{ zerografiem vectorul B }
fillchar(B, sizeof(B), 0);
{ include primele elemente }
B[i]:=true;
B[j]:=true;
{ calculeaza MaxPrefGen si MaxSufGen }
for k:=1 to n do
begin
if B[k] then continue; {nu calcula pentru cele deja incluse}
MaxPrefGen[k]:=max(MaxPrefVal[i, k], MaxPrefVal[j, k]);
MaxSufGen[k]:=max( MaxSufVal[i, k], MaxSufVal[j, k]);
end;
for k:=1 to n-2 do
begin
{ cauta un element care poate fi generat din cele deja incluse}

43
t:=1;
while (t<=n) and ((B[t])
or (MaxPrefGen[t] + MaxSufGen[t]<length(s[1]))) do inc(t);
if (t>n) then break; { nu am gasit }
{ daca am gasit include pe S[t] }
B[t]:=true;
{ Recalculeaza vectorii MaxPrefGen si MaxSufGen }
for q:=1 to n do
begin
if B[q] then continue;
MaxPrefGen[q]:=max(MaxPrefGen[q], MaxPrefVal[t,q]);
MaxSufGen[q]:=max(MaxSufGen[q], MaxSufVal[t,q]);
end;
end;
{ verifica daca am atins toate cuvintele }
for k:=1 to n do
if not(B[k]) then
begin
Protopereche:=false;
exit;
end;
Protopereche:=true;
end; { Protopereche }

function Conjugat(x : gena) : gena;


var r : Gena; { rezultatul }
i : integer;
begin
r:=x;
for i:=1 to length(x) do
if x[i]=s1[i] then r[i]:=s2[i] else r[i]:=s1[i];
Conjugat:=r;
end; { Conjugat }

function Cauta(x : gena) : integer;


{ Returneaza indexul lui x in multime, sau 0, daca x nu e in multime }
var i : integer;
begin
for i:=1 to n do
if x=s[i] then
begin
cauta:=i;
exit;
end;
Cauta:=i;
end; { Cauta }

begin
{ programul principal }
Citire;
if not(Verifica) then halt(0);
Precalculare;
assign(Iesire,'GENE.OUT');
rewrite(Iesire);

44
for i:=1 to n-1 do
begin
x:= s[i];
y:=conjugat(x);
j:=Cauta(y);
if j<i then continue;
if (Protopereche(i, j)) then
begin
writeln(Iesire, x);
writeln(Iesire, y);
end;
end;
close(Iesire);
end.

45
Cangurul

În poziţia iniţială, cangurul se afla la o distanţă de n salturi de un izvor punctiform. Cangurul


poate face salturi doar pe semidreapta care trece prin poziţia iniţială şi are originea în punctul ce
simbolizează izvorul (Fig. 1).

n ... 4 3 2 1

- cangurul - izvorul

- pozitia initiala a cangurului

Fig. 1

Întrucît cangurul nu întotdeauna ţine minte unde se află izvorul, înaintea fiecărui salt el decide
în care direcţie va face saltul respectiv: înainte sau înapoi. Este cunoscut faptul că după k salturi
cangurul a ajuns la izvor.
Elaboraţi un program, care calculează numărul de variante posibile de deplasare a cangurului
din poziţia iniţială la izvor.
Date de intrare.
Fişierul text CANGUR.IN conţine pe o singură linie numerele naturale n şi k, separate prin
spaţiu.
Date de ieşire.
Fişierul text CANGUR.OUT va conţine pe o singură linie un număr întreg − numărul de variante
posibile de deplasare a cangurului din poziţia iniţială la izvor.
Exemplu.
CANGUR.IN CANGUR.OUT
2 4 2

Restricţii. 1 ≤ n ≤ k ≤ 37 . Timpul de execuţie nu va depăşi o secundă. Fişierul sursă va avea


denumirea CANGUR.PAS, CANGUR.C sau CANGUR.CPP.

46
Rezolvare

Introducem în studiu tabloul A. Elementul A[i] al acestui tablou reprezintă numărul de


variante posibile de deplasare a cangurului din poziţia aflată la distanţa i la izvor prin t salturi (Fig.
2). Întrucît din poziţia iniţială cangurul se poate deplasa în direcţia opusă izvorului cu cel mult k
salturi, tabloul A trebuie să aibă cel puţin n+k componente. Evident, pentru i=1 şi t=1, avem
A[1]=1, A[2]=0, ..., A[n+k]=0.

A[i]
A[i+1] A[i-1]
t
i-1 i i+1

B[i]

t+1
i-1 i i+1

- cangurul - izvorul

Fig. 2

Într-un mod similar, introducem în studiu tabloul B. Elementul B[i] al acestui tablou
reprezintă numărul de variante posibile de deplasare a cangurului din poziţia aflată la distanţa i la
izvor prin t+1 salturi (Fig. 2). Cunoscînd elementele tabloului A, putem calcula elementele
tabloului B:
B[i]:=A[i-1]+A[i+1], i=1, 2, 3, ..., n+k.
Pentru a evita verificările în cazurile în care i=1 sau i=n+k, prin definiţie stabilim A[0]=0,
A[n+k+1]=0 şi B[0]=0, B[n+k+1]=0.
Evident, pentru a afla numărul de variante posibile de deplasare a cangurului la izvor prin t+2
salturi, stabilim A:=B şi calculăm din nou elementele tabloului B. Prin urmare, calculînd elementele
tablourilor A şi B pentru t=2, 3, ..., k, vom afla şi valoarea A[n], care reprezintă soluţia problemei.

Program Cangurul;
const nmax=37;
kmax=37;
var
n, k : integer;
A, B : array [0..nmax+kmax+1] of longint;
Intrare, Iesire : text;

procedure Citire;
begin
assign(Intrare, 'CANGUR.IN');
reset(Intrare);
readln(Intrare, n, k);
close(Intrare);
end; { Citire }

47
procedure Scrie;
begin
assign(Iesire, 'CANGUR.OUT');
rewrite(Iesire);
writeln(Iesire, A[n]);
close(Iesire);
end; { Citire }

procedure Salturi;
var i, t : integer;
begin
{ zerografiem tablourile A si B }
for i:=0 to n+k+1 do
A[i]:=0;
B:=A;
{ calcule recurente }
A[1]:=1;
for t:=2 to k do
begin
for i:=1 to n+k do
B[i]:=A[i-1]+A[i+1];
A:=B;
end; { for }
end; { Salturi }

begin
Citire;
Salturi;
Scrie;
end.

Complexitatea temporală a procedurii Salturi este O(nk+k2). Evident, pentru restricţiile


1 ≤ n ≤ k ≤ 37 , timpul de calcul va fi mai mic de o secundă.

48