Sunteți pe pagina 1din 43

Algoritmul lui Euclid

Cel mai mare divizor comun dintre doua numere naturale a si b este cel mai mare numar natural
pozitiv d care divide ambele numere.

Cerinta
Dandu-se doua numere naturale a si b sa se calculeze cel mai mare divizor comun al lor.

Date de intrare
Fisierul de intrare euclid2.in contine pe prima linie doua numere naturale a si b.

Date de iesire
In fisierul de iesire euclid2.out se va afisa o singura linie ce contine rezultatul cerut.

Restrictii
 2 ≤ a, b ≤ 2 * 109
Exemplu
euclid2.in euclid2.out
12 42 6

Timp executie pe test 0.1 sec Limita de memorie 512 kbytes

Indicatii de rezolvare
Cel mai mare divizor comun dintre doua numere a si b se poate calcula iterativ, printr-o simpla
parcurgere a tuturor numerelor de la 2 la minim(a, b). Aceasta rezolvare obtine 40 de puncte. Pentru
a imbunatati timpul de rulare putem folosi algoritmul lui Euclid.
O solutie de 100 de puncte, pe ideea din articolul de mai sus, o gasiti ca o a doua rezolvare.

1. #include <stdio.h>   
2.   
3. #define minim(a, b) ((a < b) ? a : b)   
4.   
5. int A, B;   
6.   
7. int main(void)   
8. {   
9.     int i;   
10.   
11.     freopen("euclid2.in", "r", stdin);   
12.     freopen("euclid2.out", "w", stdout);   
13.        
14.     scanf("%d %d", &A, &B);   
15.     for (i = minim(A, B); i; --i)   
16.         if (A % i == 0 && B % i == 0)   
17.         {   
18.             printf("%d\n", i);   
19.             break;   
20.         }   
21.   
22.     return 0;   
23. }  
1. #include <stdio.h>   
2.   
3. int A, B;   
4.   
5. int gcd(int a, int b)   
6. {   
7.     if (!b) return a;   
8.     return gcd(b, a % b);   
9. }   
10.   
11. int main(void)   
12. {   
13.     freopen("euclid2.in", "r", stdin);   
14.     freopen("euclid2.out", "w", stdout);   
15.   
16.     scanf("%d %d", &A, &B);   
17.     printf("%d\n", gcd(A, B));   
18.   
19.     return 0;   
20. }  

Cel mai lung subsir comun


Fie v un vector cu N elemente. Se numeste subsir de lungime K al vectorului v un nou vector v' =
(vi1, vi2, ... viK), cu i1 < i2 < ... < iK. De exemplu, vectorul v = (5 7 8 9 1 6) contine ca subsir
sirurile (5 8 6) sau (7 8 1), dar nu contine subsirul (1 5). Se dau doi vectori A si B cu elemente
numere naturale nenule.

Cerinta
Sa se determine subsirul de lungime maxima care apare atat in A cat si in B.

Date de intrare
Fisierul de intrare cmlsc.in contine pe prima linie M si N, numarul de elemente pentru vectorul A,
respectiv pentru B. A doua linie contine M numere naturale, elementele vectorului A. A treia linie
contine descrierea vectorului B sub acelasi format.

Date de iesire
Fisierul de iesire cmlsc.out va contine pe prima linie MAX, lungimea maxima a unui subsir comun. A
doua linie va contine MAX numere ce reprezinta un subsir comun pentru A si B. Daca exista mai multe
solutii se poate afisa oricare.

Restrictii
 1 ≤ M, N ≤ 1024

 Numerele din cei doi vectori nu depasesc 256


Exemplu
cmlsc.in cmlsc.out
5 3 2
1 7 3 9 8 7 8
7 5 8

Timp executie pe test 0.1 sec Limita de memorie 6144 kbytes

Indicatii de rezolvare
Problema celui mai lung subsir comun pentru doua siruri A si B se poate rezolva in timp exponential
prin metoda backtracking. Sursa unei astfel de abordari se gaseste ca prima rezolvare si obtine 30 de
puncte. Rezolvarea optima are complexitatea O(M * N), daca se utilizeaza programarea dinamica.
Algoritmul din spatele unei astfel de rezolvari este foarte bine explicat in cartea Introducere in
algoritmi, Thomas Cormen, editura Agora, Cluj-Napoca.
Sursa de 100 de puncte ce foloseste programarea dinamica se gaseste ca o a doua rezolvare.

1. #include <stdio.h>   
2.   
3. #define NMax 260   
4.   
5. int M, N, A[NMax], B[NMax], sir[NMax], bst, sol[NMax];   
6.   
7. int subsir(int nr) // daca sir[1..nr] este subsir pentru B[1..N]   
8. {   
9.     int i, j = 1;   
10.   
11.     for (i = 1; i <= nr && j <= N; i++)   
12.         for (; j <= N && B[j] != sir[i]; ++j);   
13.     return j <= N;   
14. }   
15.   
16. void back(int nivel, int len)   
17. {   
18.     int i;   
19.   
20.     if (nivel == M+1)   
21.     {   
22.         if (subsir(len)) // daca sir este subsir si pentru B   
23.             if (len > bst)   
24.             {   
25.                 bst = len;   
26.                 for (i = 1; i <= bst; i++)   
27.                     sol[i] = sir[i];   
28.             }   
29.            
30.         return ;   
31.     }   
32.   
33.     // nu folosim A[nivel]   
34.     back(nivel+1, len);   
35.     // folosim A[nivel] in solutie   
36.     sir[len+1] = A[nivel]; back(nivel+1, len+1);   
37. }   
38.   
39. int main(void)   
40. {   
41.     int i;   
42.        
43.     freopen("cmlsc.in", "r", stdin);   
44.     freopen("cmlsc.out", "w", stdout);   
45.   
46.     scanf("%d %d", &M, &N);   
47.     for (i = 1; i <= M; i++)   
48.         scanf("%d", &A[i]);   
49.     for (i = 1; i <= N; i++)   
50.         scanf("%d", &B[i]);   
51.   
52.     back(1, 0);   
53.   
54.     printf("%d\n", bst);   
55.     for (i = 1; i < bst; i++)   
56.         printf("%d ", sol[i]);           
57.     printf("%d\n", sol[bst]);   
58.   
59.     return 0;   
60. }  

1. #include <stdio.h>   
2.   
3. #define maxim(a, b) ((a > b) ? a : b)   
4. #define FOR(i, a, b) for (i = a; i <= b; ++i)   
5. #define NMax 1024   
6.   
7. int M, N, A[NMax], B[NMax], D[NMax][NMax], sir[NMax], bst;   
8.   
9. int main(void)   
10. {   
11.     int i, j;   
12.        
13.     freopen("cmlsc.in", "r", stdin);   
14.     freopen("cmlsc.out", "w", stdout);   
15.   
16.     scanf("%d %d", &M, &N);   
17.     FOR (i, 1, M)   
18.         scanf("%d", &A[i]);   
19.     FOR (i, 1, N)   
20.         scanf("%d", &B[i]);   
21.   
22.     FOR (i, 1, M)   
23.         FOR (j, 1, N)   
24.             if (A[i] == B[j])   
25.                 D[i][j] = 1 + D[i-1][j-1];   
26.             else  
27.                 D[i][j] = maxim(D[i-1][j], D[i][j-1]);   
28.   
29.     for (i = M, j = N; i; )   
30.         if (A[i] == B[j])   
31.             sir[++bst] = A[i], --i, --j;   
32.         else if (D[i-1][j] < D[i][j-1])   
33.             --j;   
34.         else  
35.             --i;   
36.   
37.     printf("%d\n", bst);   
38.     for (i = bst; i; --i)   
39.         printf("%d ", sir[i]);   
40.   
41.     return 0;   
42. }  

Algoritmul lui Euclid extins


Cerinta
Se dau T ecuatii de forma a * x + b * y = c, cu coeficientii a, b si c. Pentru fiecare dintre aceste
ecuatii se cere aflarea unei perechi de numere intregi x y care sa satisfaca ecuatia, in cazul in care o
astfel de pereche exista.

Date de intrare
Fisierul de intrare euclid3.in contine pe prima linie numarul T de teste. Urmatoarele T linii contin
fiecare cate 3 numere intregi a, b, c.

Date de iesire
Fisierul de iesire euclid3.out va contine T linii. Pe linia i se va afla o pereche de numere intregi x y
care respecta ecuatia cu numarul i sau 0 0 in cazul in care ecuatia respectiva nu are solutie.

Restrictii
 1 ≤ T ≤ 100

 -1 000 000 000 ≤ a ≤ b ≤ 1 000 000 000

 -2 000 000 000 ≤ c ≤ 2 000 000 000, c diferit de 0.

 In cazul in care exista mai multe solutii pentru o ecuatie, se poate afisa oricare solutie pentru
care necunoscutele nu depasesc in valoare absoluta 2 000 000 000. Pentru toate ecuatiile

pentru care exista solutie, va exista si o solutie cu ambele necunoscute aflate in acest interval.
Exemplu
euclid3.in euclid3.out
3 33 -43
24 15 147 33 -43
24 16 104 0 0
2 4 5

Timp executie pe test 0.1 sec Limita de memorie 512 kbytes

Indicatii de rezolvare
Ecuatiile pot fi rezolvate cu ajutorul algoritmului lui Euclid extins, prezentat in articolul urmator. Astfel
se poate determina perechea (x y) care satisface relatia a * x + b * y = d, unde d este cmmmdc(a,
b). In cazul in care c nu se divide cu d ecuatia nu poate fi rezolvata in multimea numerelor intregi, in
caz contrar se inmulteste intreaga ecuatie cu c / d.

O solutie de 100 de puncte, pe ideea de mai sus, o gasiti in continuare.

Ca o completare: Pentru fiecare ecuatie exista o infinitate de solutii. Avand dat a * x + b * y = c,


se observa ca toate solutiile de forma (x + k * b/d, y - k * a/d), cu k intreg respecta ecuatia.

Ecuatia devine a * (x + k * b/d) + b * (y - k * a/d) = c. Desfacand parantezele: a * x + k *


a * b / d + b * y - k * a * b / d = a * x + b * y. Solutiile noi sunt la randul lor intregi, a si b
fiind divizibile cu d.

Algoritmul lui Euclid


O prezentare a variantei extinse a algoritmului lui Euclid, care rezolva ecuatie de forma A * X + B *
Y = D, unde D este cel mai mare divizor comun al lui A si B. De asemenea este prezentata o aplicatie
"interesanta": impartirea modulara.

Probabil ca multi stiti algoritmul lui Euclid de prin clasa a 5-a, cand invatati la matematica
divizibilitate. Varianta simplista a algoritmului lui Euclid este cunoscuta de multa lume, dar fara prea
multe explicatii despre functionarea lui.

Euclid simplu
In cuvinte, algoritmul pur si simplu imparte deimpartitul la rest pana cand impartitorul este 0, apoi
returneaza deimpartitul. Poate fi usor implementat iterativ in C. Probabil ca aceasta forma este si cea
mai rapida, si este de preferat cand nu e necesar Euclid extins.

neformatatcopiaza in clipboardprint?
1. int euclid(int a, int b)   
2.   
3.    int c;   
4.    while (b) {   
5.        c = a % b;   
6.        a = b;   
7.        b = c;   
8.    }   
9.    return a;   

Pentru a fi inteles mai usor si eventual extins, este mai bine sa il punem sub forma recursiva

neformatatcopiaza in clipboardprint?
1.  void euclid(int a, int b, int *d)   
2. {   
3.     if (b == 0) {   
4.         *d = a;   
5.     } else  
6.         euclid(b, a % b, d);   
7. }  

Sa vedem cum functioneaza algoritmul lui Euclid. Se observa ca daca a<b, atunci euclid(b, a % b)
este de fapt euclid(b, a).

Vom demonstra ca cmmdc(a, b) = cmmdc(b, a % b). Notam cmmdc(a, b) cu d. Scriem a%b drept a
- b * c, unde c este parte intreaga din a / b. Cum a si b sunt divizibile cu b, atunci orice combinatie
liniara a lor este divizibila cu d, inclusiv a - b * c = a b$}.

Asta insa nu este de ajuns, putem aveam Z > d, Z divizibil cu d, care sa fie
{$cmmdc(b, a b). Insa atunci ar rezulta similar ca a e divizibil cu Z, deci Z = d = cmmdc(a, b),
Incalcand Z > d.

Astfel, algoritmul lucreaza reducand problema la numere din ce in ce mai mici, pana cand a % b = 0.
Ca sa finalizam recurenta, daca a este divizibil cu b, atunci este evident ca cmmdc(a, b) este b. In cod
este un pic mai "ciudat", prindem acest caz doar dupa inca un apel recurent, cand b = 0. De fapt cred
ca cmmdc este definit pe numere strict pozitive, dar in informatica putem ocoli un pic matematica.

Euclid extins
Acest algoritm poate fi extins, in sensul gasirii x si y astfel incat a * x + b * y = d. In acest articol
vom incerca sa deducem modul de calculare al lui x si y. Cei grabiti sau certati cu matematica pot sari
direct la codul sursa, dar vor avea probleme in a tine minte algoritmul pe viitor.

Vom extinde procedura recursiva de calculare a cmmdc pentru e include si x si y. Calculam x si y


incepand de la "capatul recurentei". Daca b = 0, atunci a * 1 + b * 0 = a(cmmdc) evident, asa ca
initial x = 1 si y = 0. Incercam sa calculam x, y in functie de x0, y0 obtinuti pentru b, a % b. Noi stim
urmatoarele:

 b * x0 + (a % b) * y0 = d

 a * x + b * y = d

Trebuie sa aflam o solutie pentru x si y. Vom nota ca mai sus parte intreaga din a / b cu c.

 b * x0 + (a - b * c) * y0 = a * x + b * y

 b * (x0 - c * y0 - y) = a * (x - y0)

O solutie este acum evidenta (Una, sunt o infinitate de perechi x, y)

 x0 - c * y0 - y = 0, De unde rezulta y = x0 - c * y0

 x - y0 = 0, De unde rezulta x = y0

Acum nu mai pare asa de "magic", nu?

Sursa modificata pentru a calcula si x si y nu este mult mai complexa. Acum intelegeti de ce am trimis
d ca pointer mai sus, si de ce am folosit varianta recursiva a algoritmului lui euclid. Implementat
iterativ, este nevoie de un vector care sa tina toate valorile c (a / b) obtinute pe parcurs.

Nota pentru pascalisti: in C nu exista div, impartirea intre int-uri este automat si impartire intreaga
(div din pascal).

1.  void euclid(int a, int b, int *d, int *x, int *y)   
2. {   
3.     if (b == 0) {   
4.         *d = a;   
5.         *x = 1;   
6.         *y = 0;   
7.     } else {   
8.         int x0, y0;   
9.         euclid(b, a % b, d, &x0, &y0);   
10.         *x = y0;   
11.         *y = x0 - (a / b) * y0;   
12.     }   
13. }  
Impartire modulara
Acum pentru o aplicatie interesanta, impartirea modulara. Nu voi intra in detalii, pe scurt aritmetica
modulara este aritmetica in care toate valorile se iau modulo un anumit numar n. Spre exemplu, in
modulo 7, 3 = 10. Similar, rezultatele tuturor operatiilor se iau in modul: 4 + 5 = 2, 3 * 5 = 1,
etc. Cum putem defini insa impartirea in modulo? Evident, 6 / 2 este tot 3, dar 3 / 4 cu cat este
egal? De obicei impartirea este definita ca operatia inversa a inmultirii, similar cum scaderea este
operatia inversa a adunarii. Astfel, daca a / b = c atunci b * c = a. Prin algoritmul lui Euclid putem
afla x si y astfel incat n * x + b * y = cmmdc(n, b), unde n este modulul. Totul e modulo n, asa ca
putem ignora x * n. Atunci c = y * a / cmmdc(n, b). Daca a nu este divizibil cu cmmdc(n, b),
atunci c nu exista. Intradevar, nu exista c pentru care 3 * c = 4 modulo 6.

1. #include <stdio.h>   
2. #include <assert.h>   
3.   
4. inline int gcd( int A, int B, int &X, int &Y )   
5. {   
6.     if (B == 0)   
7.     {   
8.         X = 1;   
9.         Y = 0;   
10.         return A;   
11.     }   
12.   
13.     int X0, Y0, D;   
14.     D = gcd( B, A % B, X0, Y0 );   
15.        
16.     X = Y0;   
17.     Y = X0 - (A / B) * Y0;   
18.     return D;   
19. }   
20.   
21. int main()   
22. {   
23.     freopen("euclid3.in", "rt", stdin);   
24.     freopen("euclid3.out", "wt", stdout);   
25.   
26.     int T;   
27.     for (scanf("%d", &T), assert( T <= 100 ); T; T--)   
28.     {   
29.         int A, B, C;   
30.         scanf("%d %d %d", &A, &B, &C);   
31.   
32.         assert( -1000000000 <= A && A <= 1000000000 );   
33.         assert( -1000000000 <= B && B <= 1000000000 );   
34.         assert( -2000000000 <= C && C <= 2000000000 && C != 0 );   
35.         int D, X, Y;   
36.         D = gcd( A, B, X, Y );   
37.            
38.         if (C % D)   
39.             printf("0 0\n");   
40.         else  
41.             printf("%d %d\n", X * (C / D), Y * (C / D));   
42.     }   
43.   
44.     return 0;}  
Floyd-Warshall/Roy-Floyd
Se da un graf orientat cu N noduri, memorat prin matricea ponderilor.

Cerinta
Sa se determine pentru orice pereche de noduri x si y lungimea minima a drumului de la nodul x la
nodul y si sa se afiseze matricea drumurilor minime. Prin lungimea unui drum intelegem suma
costurilor arcelor care-l alcatuiesc.

Date de intrare
Fisierul de intrare royfloyd.in contine pe prima linie N, numarul de noduri al grafului, iar urmatoarele
N linii contin cate N valori reprezentand matricea ponderilor (al j-lea numar de pe linia i+1 reprezinta
costul muchiei de la i la j din graf, sau 0, in cazul in care intre cele doua noduri nu exista muchie).

Date de iesire
In fisierul de iesire royfloyd.out se vor afisa N linii a cate N valori, reprezentand matricea drumurilor
minime.

Restrictii
 1 ≤ N ≤ 100

 Numerele din fisierul de intrare nu vor depasi valoarea 1 000.

 Daca nu exista muchie intre o pereche de noduri x si y, distanta de la nodul x la nodul y din

fisierul de intrare va fi 0.
 Daca dupa aplicarea algoritmului nu se gaseste un drum intre o pereche de noduri x si y, se

va considera ca distanta intre cele doua noduri este 0.


 Nu exista drum intre un nod la el insusi ( ai,i este 0 pentru orice i cuprins intre 1 si N).
Exemplu
royfloyd.in royfloyd.out
5 0 3 4 5 3
0 3 9 8 3 5 0 1 4 2
5 0 1 4 2 6 6 0 4 5
6 6 0 4 5 2 5 2 0 5
2 9 2 0 7 4 7 3 2 0
7 9 3 2 0

Timp executie pe test 0.1 sec Limita de memorie 640 kbytes

Indicatii de rezolvare
Algoritmul are complexitatea O(N^3) si este explicat in cartea Introducere in algoritmi, Thomas
Cormen, editura Agora, Cluj-Napoca. Sursa de 100 de puncte se gaseste implementata mai jos .
1. #include <stdio.h>     
2. int n, a[105][105];  
3. void citire()   
4. {   
5.     freopen("royfloyd.in","r",stdin);   
6.     freopen("royfloyd.out","w",stdout);   
7.   
8.     int i, j;   
9.     scanf("%d",&n);   
10.     for (i = 1; i <= n; i++)   
11.         for (j = 1; j <= n; j++) scanf("%d",&a[i][j]);   
12. }   
13.   
14. void roy_floyd()   
15. {   
16.     int i, j, k;   
17.     for (k = 1; k <= n; k++)   
18.         for (i = 1; i <= n; i++)   
19.             for (j = 1; j <= n; j++)   
20.                 if (a[i][k] && a[k][j] && (a[i][j] > a[i][k] + a[k][j] || !a[i][j]) && 
i != j) a[i][j] = a[i][k] + a[k][j];   
21. }   
22.   
23. void afis()   
24. {   
25.     int i, j;   
26.     for (i = 1; i <= n; i++)    
27.     {   
28.         for (j = 1; j <= n; j++) printf("%d ",a[i][j]);   
29.         printf("\n");   
30.     }   
31. }   
32.   
33.   
34. int main()   
35. {   
36.     citire();   
37.     roy_floyd();   
38.     afis();   
39.     return 0;   
40. }  
Sortare Topologica
O sortare topologica a varfurilor unui graf orientat aciclic este o operatie de ordonare liniara a
varfurilor, astfel incat, daca exista un arc ( i, j ), atunci i apare inaintea lui j in aceasta ordonare.

Date de intrare
In fisierul de intrare sortaret.in vom avea pe prima linie doua numere intregi N si M. Pe fiecare dintre
urmatoarele M linii se vor afla cate doua numere intregi, separate intre ele printr-un spatiu, X si Y, cu
semnificatia ca exista arc de la nodul X catre nodul Y.

Date de iesire
Fisierul de iesire sortaret.out va contine pe o singura linie N numere separate intre ele prin spatii, care
reprezinta sortarea topologica a nodurilor grafului dat.

Restrictii
 1 ≤ N ≤ 50000

 1 ≤ M ≤ 100000
Exemplu
sortaret.in sortaret.out
9 8 1 2 3 4 6 7 8 5 9
1 2
1 3
3 4
3 5
5 9
4 6
4 7
4 8

Timp executie pe test 0.3 sec Limita de memorie 16384 kbytes

Indicatii de rezolvare
 O scurta prezentare a acestui subiect gasiti in manualele de specialitate

 Algoritmul de Sortare Topologica il gasiti foarte bine explicat si in cartea Introducere in

algoritmi, Thomas Cormen, editura Agora, Cluj-Napoca.


 Ideea din spatele rezolvarii consta intr-o parcurgere in adancime pentru a calcula timpii de
terminare pentru fiecare varf v. Pe masura ce fiecare varf este terminat, este inserat in capul

unei liste simplu inlantuite. Parcurgerea listei va constitui solutia.


Acest algoritm are o complexitate de O( N + M ) deoarece cautarea in adancime necesita un

timp O( N + M ) iar inserarea fiecaruia din cele |N| varfuri in capul liste simplu inlantuite

necesita timp O( 1 ).

Rezolvarea in manualul de clasa a 11-a.


Potrivirea sirurilor
Se dau doua siruri A si B formate din litere mici si mari ale alfabetului englez si din cifre. Se cere
gasirea tuturor aparitiilor sirului A ca subsecventa a sirului B.

Date de intrare
Fisierul de intrare strmatch.in contine 2 linii cu sirurile A, respectiv B.

Date de iesire
In fisierul de iesire strmatch.out se va afla pe prima linie numarul N de aparitii a sirului A in sirul B.
Pe urmatoarea linie se vor afla N numere care reprezinta pozitiile in care sirul A se potriveste peste
sirul B, afisate in ordine crescatoare. Pentru a evita fisierele de output foarte mari, in cazul in care N
este mai mare decat 1000 se vor afisa doar primele 1000 de pozitii. Sirurile sunt indexate de la 0.

Restrictii
 Lungimea sirurilor A si B se afla in intervalul [1, 2 000 000]
Exemplu
strmatch.in strmatch.out
ABA 2
CABBCABABAB 5 7

Explicatie
Cele doua aparitii sunt:

 CABBCABABAB

 CABBCABABAB

Timp executie pe test 0.3 sec Limita de memorie 16384 kbytes

Indicatii de rezolvare
Problema se poate rezolva in complexitate O(|A| * |B|), incercand pe rand toate pozitile i in care
sirul A se poate potrivi peste sirul B si comparand sirul A cu subsecventa de pe pozitia i din sirul B in
complexitate O(|A|). Aceasta rezolvare obtine 40 de puncte.

Complexitatea optima pentru aceasta problema este O(|A| + |B|) si problema se poate rezolva cu
ajutorul algoritmului KMP prezentat in articolul de mai jos sau cu ajutorul algoritmului Rabin-Karp
prezentat dupa algoritmul KMP.

Dupa aceste doua articole, veti gasi rezolvarile bazate pe fiecare din cei doi algoritmi, fiecare
implementare obtinand 100 de puncte.

Desi atat KMP cat si Rabin-Karp au complexitate liniara, in practica, KMP este mai rapid.

Automate finite si KMP


In acest articol vom aborda cele mai comune probleme legate de pattern matching si vom oferi
suportul teoretic necesar intelegerii algoritmului Knuth-Morris-Pratt, pornind de la potrivirea standard
cu automate finite si rafinand-o treptat pana la un algoritm de complexitate O(n + m). Toate acestea
intr-o maniera usor de inteles ;)

Automate finite
Ce sunt automatele finite ?
Un automat finit este definit ca un cvintuplu <Q, q0, A, Σ, δ> unde Q este o multime finita de stari Q
= {q0, q1, ... qn}, q0 apartine Q (q0 = stare initiala), A inclus in Q (A = multimea starilor de
acceptare), Σ este un alfabet, iar functia δ : Q x Σ -> Q este functia de tranzitie a automatului.

Aceasta este definitia matematica si foarte abstractizata a automatelor. Pentru a le intelege mai usor,
sa luam un exemplu concret

 Q = {q0, q1, q2, q3}

 A = {q3}

 Σ = {a, b}

 δ=
δ a b
0  1 2
1 3 1
2 3 0 
3 3 3

Ce inseamna asta? Sa spunem ca automatul primeste un string s = bbaba


Initial ne aflam in q0. Pentru fiecare element al stringului si facem tranzitia δ(qk, si).

Pornim din k = 0. Vom avea :

 k = 0; δ(0, b) = 2;

 k = 2; δ(2, b) = 0;

 k = 0; δ(0, a) = 1;

 k = 1; δ(1, b) = 1;

 k = 2; δ(1, a) = 3;

Daca ultima stare obtinuta qk apartine A, atunci spunem ca automatul accepta stringul. Altfel spus,
daca avem stringul s, lungime(s) = n, automatul accepta stringul daca si numai daca δ( ...
δ( δ(0, s1), s2 ) ..., sn ) apartine A.

Stringurile 'aa', 'aaaaaaa', 'aabababab', 'aaaba', 'ba', 'aba' sunt acceptate de automat, dar 'abbbbbb',
'bba' nu.

La ce folosesc ?
1. Inteligenta artificiala (prima si cea mai involuata stare a inteligentei artificiale)
2. Aplicatii teoretice si probleme de matematica :)

3. Pattern matching

Se dau stringurile M si N. Se cere sa gasim toate aparitiile lui N in M.


Vom numi Mi prefixul lui M de lungime i. Presupunand ca avem construit automatul care accepta
stringul N, vom cauta toate prefixele lui M acceptate de automat, deci toate numerele 1 ≤ i ≤
lungime(M) cu proprietatea ca automatul accepta stringul Mi.

Algoritm_potrivire_cu_automat_finit    
1. n = lungime(N)   
2. q = 0;   
3. pt i <- 1, n   
4.     q = d(q, M[i])   
5.     daca q apartine A   
6.         scrie "potrivire la pozitia " i - n + 1  

 Complexitate : O(n)

Sa vedem cum se construieste automatul de potrivire pentru un string N. Fie m = lungime(M).


Construim un automat cu m + 1 stari {q0, q1, ... qm}, A = {qm} . Faptul ca ne aflam in starea x
inseamna ca au fost acceptate primele x caractere din sirul de intrare.
Din fiecare stare qx apartine Q si pt fiecare c apartine S construim δ(x, c) = y cu proprietatea ca My
este cel mai lung prefix al lui M care este sufix al lui Mxc (prefixul de lungime x al lui M, concatenat cu
caracterul c).

Algoritm_constructie_automat_finit
1. m <- lungime(M)   
2. pt q <- 0, m   
3.     pt c apartine S   
4.         gaseste M[i] = cel mai lung prefix al lui M cu M[i] sufix al lui M[q]c   
5.             d(q, c) = i  

 Complexitate : linia 4 are complexitatea O(m2) (implementata in maniera bruta) si se executa

de (m + 1) * |Σ| ori => complexitate totala O(m3 * |Σ|)

Practic, algoritmul calculeaza pentru toate 0 ≤ i ≤ m, c apartine S cat de mult putem lua de la
sfarsitul lui Mic astfel incat acesta sa fie un "inceput" de N.

Acesta se poate rafina, eliminand operatii redundante, dupa cum vom vedea in cele ce urmeaza.

Algoritmul KMP
Gaseste toate aparitiile unui string N in M in timp O(n + m), unde n = lungime(N), m = lungime(M).
O parte esentiala a sa este functia prefix π : {1..n} -> {0..n-1} unde πi = cel mai lung prefix al lui
M care este sufix al lui Mi. Evident, Mπi (prefixul de lungime πi al lui M) este prefix al lui Mi, deci πi < i.

Algoritm_calcul_functie_prefix
1. n <- lungime(N)   
2. k <- 0   
3. pi[1] <- 0   
4. pt i <- 2, n   
5.     cat timp (k > 0) si (N[k + 1] != N[i])   
6.         k <- pi[k]   
7.     daca N[k + 1] = N[i]   
8.         k <- k + 1   
9.     pi[i] <- k  

Analiza complexitatii :

 la fiecare pas (i = 2, n) k se incrementeaza cel mult o data, deci pe parcursul algoritmului

k se va incrementa de cel mult n - 1 ori (linia 8)

 in linia 5, k se decrementeaza cel mult pana devine 0, deci se va decrementa de cel

mult n - 1 ori pe parcursul algoritmului

 ⇒ Complexitate : O(n)

Algoritmul este similar cu constructia automatului de acceptare. Din fiecare stare i in care s-a
acceptat Ni, vedem cat de mult putem lua de la sfarsitul lui Ni astfel incat sufixul respectiv sa fie prefix
pentru N. De remarcat ca in cazul in care starea candidata k nu este buna, nu mergem in k - 1, ci in
πk. Aceasta este de fapt "magia" care ofera complexitate liniara.

Algoritmul de potrivire este similar celui al calculului functiei prefix, numai ca aici la fiecare pas i
cautam cel mai lung prefix al lui N care este sufix al lui Mi.

Algoritm_potrivire_KMP
1. m <- lungime(M), n <- lungime(N)   
2. q <- 0   
3. pt i <- 1, m   
4.     cat timp (q > 0) si (N[q + 1] != M[i])   
5.         q <- pi[q]   
6.     daca N[q + 1] = M[i]   
7.         q <- q + 1   
8.     daca q = n   
9.         scrie "potrivire la pozitia " i - n + 1  
Analog Algoritm_Calcul_Functie_Prefix, complexitatea algoritmului efectiv de potrivire este O(m).
Astfel rezulta complexitatea liniara a algoritmului KMP O(n + m)

1. #include <stdio.h>   
2. #define minim(a, b) ((a < b) ? a : b)   
3. #define NMax 2000005    
4. int M, N;   
5. char A[NMax], B[NMax];   
6. int pi[NMax], pos[1024];   
7.   
8. void make_prefix(void)   
9. {    int i, q = 0;   
10.     for (i = 2, pi[1] = 0; i <= M; ++i)   
11.     {   
12.         while (q && A[q+1] != A[i])   
13.             q = pi[q];   
14.         if (A[q+1] == A[i])   
15.             ++q;   
16.         pi[i] = q;   
17.     }   
18. }   
19.   
20. int main(void)   
21. {   
22.     int i, q = 0, n = 0;     
23.     freopen("strmatch.in", "r", stdin);   
24.     freopen("strmatch.out", "w", stdout);   
25.     fgets(A, sizeof(A), stdin);   
26.     fgets(B, sizeof(B), stdin);   
27.     for (; (A[M] >= 'A' && A[M] <= 'Z') || (A[M] >= 'a' && A[M] <= 'z')    
28.             || (A[M] >= '0' && A[M] <= '9'); ++M);   
29.     for (; (B[N] >= 'A' && B[N] <= 'Z') || (B[N] >= 'a' && B[N] <= 'z')   
30.             || (B[N] >= '0' && B[N] <= '9'); ++N);   
31.     for (i = M; i; --i) A[i] = A[i-1]; A[0] = ' ';   
32.     for (i = N; i; --i) B[i] = B[i-1]; B[0] = ' ';      
33.     make_prefix();     
34.     for (i = 1; i <= N; ++i)   
35.     {          
36.         while (q && A[q+1] != B[i])   
37.             q = pi[q];   
38.         if (A[q+1] == B[i])   
39.             ++q;   
40.         if (q == M)   
41.         {   
42.             q = pi[M];   
43.             ++n;   
44.             if (n <= 1000)   
45.                 pos[n] = i-M;      
46.         }      
47.     }          
48.     printf("%d\n", n);   
49.     for (i = 1; i <= minim(n, 1000); ++i)   
50.         printf("%d ", pos[i]);   
51.     printf("\n");              
52.     return 0;   
53. }  
1. #include <stdio.h>   
2. #include <string.h>   
3.   
4. #define MAXN 2000001   
5. #define P 73   
6. #define MOD1 100007   
7. #define MOD2 100021   
8.   
9. char A[MAXN], B[MAXN];   
10. int NA, NB;    
11. int hashA1, hashA2, P1, P2;   
12. char match[MAXN];   
13.   
14. int main()   
15. {   
16.     freopen("strmatch.in", "rt", stdin);   
17.     freopen("strmatch.out", "wt", stdout);   
18.        
19.     scanf("%s %s", A, B);   
20.     NA = strlen(A);   
21.     NB = strlen(B);   
22.   
23.     P1 = P2 = 1;   
24.     hashA1 = hashA2 = 0;   
25.     for (int i = 0; i < NA; i++)   
26.     {   
27.         hashA1 = (hashA1 * P + A[i]) % MOD1;   
28.         hashA2 = (hashA2 * P + A[i]) % MOD2;   
29.   
30.         if (i != 0)   
31.             P1 = (P1 * P) % MOD1,   
32.             P2 = (P2 * P) % MOD2;   
33.     }   
34.     if (NA > NB)   
35.     {   
36.         printf("0\n");   
37.         return 0;   
38.     }   
39.   
40.     int hash1 = 0, hash2 = 0;   
41.     for (int i = 0; i < NA; i++)   
42.         hash1 = (hash1 * P + B[i]) % MOD1,   
43.         hash2 = (hash2 * P + B[i]) % MOD2;   
44.   
45.     int Nr = 0;   
46.     if (hash1 == hashA1 && hash2 == hashA2)   
47.         match[0] = 1, Nr++;   
48.   
49.     for (int i = NA; i < NB; i++)   
50.     {   
51.         hash1 = ((hash1 - (B[i - NA] * P1) % MOD1 + MOD1) * P + B[i]) % MOD1;   
52.         hash2 = ((hash2 - (B[i - NA] * P2) % MOD2 + MOD2) * P + B[i]) % MOD2;   
53.   
54.         if (hash1 == hashA1 && hash2 == hashA2)   
55.             match[ i - NA + 1 ] = 1, Nr++;   
56.     }   
57.     printf("%d\n", Nr);   
58.   
59.     Nr = 0;   
60.     for (int i = 0; i < NB && Nr < 1000; i++)   
61.         if (match[i])   
62.             Nr++,   
63.             printf("%d ", i);   
64.     printf("\n");   
65.        
66.     return 0;   
67. }  
Arbori de intervale
Fie un vector A cu N elemente naturale. Asupra lui se vor face M operatii, codificate astfel in fisierul de
intrare:
• 0 a b - Sa se determine maximul din intervalul [a,b] (maximul dintre valorile Ai cu a ≤ i ≤ b).
• 1 a b - Valoarea elementului de pe pozita a va deveni b.

Date de intrare
Pe prima linie a fisierului de intrare se afla N si M. Pe urmatoarea linie se gasesc cele N elemente ale
vectorului, iar urmatoarele M linii descriu operatia care trebuie efectuata.

Date de iesire
Pentru fiecare operatie de tip 0, se va afisa pe cate o linie maximul pentru intervalul cerut (in ordinea
ceruta in fisierul de intrare).

Restrictii
 1 ≤ M, N ≤ 100000

 0 ≤ Ai ≤ 109 pentru 1 ≤ i ≤ N

 Pentru operatia de tip 0: 1 ≤ a ≤ b ≤ N 

 Pentru operatia de tip 1: 1 ≤ a ≤ N si 1 ≤ b ≤ 109 


Exemplu
arbint.in arbint.out
5 5 5
4 3 5 6 1 3
0 1 3 4
1 3 2
0 2 3
1 4 2
0 1 5

Timp executie pe test 0.4 sec Limita de memorie 16384 kbytes

Indicatii pentru rezolvare


O rezolvare brute ar obtine in jur de 30-40 puncte si o poti gasi imediat. Solutia optima pentru
rezolvarea problemei are complexitatea O(MlogN) si se poate realiza prin intermediul arborilor de
intervale, teoria fiind prezentata in continuare. O solutie de 100 puncte pe ideea prezentata in articol
gasesti ca ultim punct al articolului de la acest capitol.

1. #include <stdio.h>   
2. #include <fstream>   
3. using namespace std;   
4.   
5. #define in "arbint.in"   
6. #define out "arbint.out"   
7. #define dim 100001   
8.   
9. int N, M;   
10. int C[dim];   
11. int maxim;   
12.   
13. void Maxim(int st, int dr);   
14.   
15. int main()   
16. {   
17.     int X, A, B;   
18.     freopen(in,"r",stdin);   
19.     freopen(out,"w",stdout);   
20.        
21.     scanf("%d%d", &N, &M);   
22.     for ( int i = 1; i <= N; i++ )   
23.     {   
24.         scanf("%d", &C[i]);   
25.     }      
26.        
27.     for ( int i = 1; i <= M; i++ )   
28.     {   
29.         scanf("%d%d%d", &X, &A, &B);   
30.         if ( X == 0 )    
31.         {   
32.              maxim = -1;   
33.              Maxim(A,B);   
34.                 
35.              printf("%d\n", maxim);   
36.         }   
37.         else  
38.         {   
39.             C[A] = B;   
40.         }   
41.     }   
42. }   
43.   
44. void Maxim(int a, int b)   
45. {   
46.      for ( int i = a; i <= b; i++ )   
47.          if ( maxim < C[i] ) maxim = C[i];   
48. }  

Arbori de intervale si aplicatii in geometria


computationala
Problema 1
Se considera N (N ≤ 50 000) segmente in plan, dispuse paralel cu axele OX si OY. Sa se determine
care este numarul total de intersectii dintre segmente.

In fisierul segment.in se gaseste pe prima linie numarul N de segmente, iar pe fiecare dintre
urmatoarele N linii cate patru numere naturale mai mici decat 50 000, reprezentand coordonatele
carteziene ale extremitatilor fiecarui segment.
Rezultatul se va scrie in segment.out.
Timp de executie: 1 secunda/test
Exemplu:
segment.in segment.out Figura
5 4
2 9 13 9
4 6 12 6
1 2 6 2
5 0 5 8
7 5 7 11

Algoritmi de "baleiere" (line sweeping)


Folosind cunostinte generale de geometrie analitica se poate obtine un algoritm O(N2) dar acesta nu se
va incadra in limita de timp.

Pentru rezolvarea acestei probleme vom folosi o tehnica cunoscuta sub numele de "baleiere"
(sweeping) care este comuna multor algoritmi de geometrie computationala. In baleiere, o dreapta de
baleiere verticala, imaginara, traverseaza multimea obiectelor geometrice, de Baleierea ofera o
metoda pentru ordonarea obicei de la stanga la dreapta. obiectelor geometrice, plasandu-le intr-o
structura de date, pentru obtinerea relatiilor dintre ele.

Algoritmii de baleiere gestioneaza doua multimi de date:

1. Starea liniei de baleiere da relatia dintre obiectele intersectate de linia de baleiere


2. Lista punct-eveniment este o secventa de coordonate x, ordonate de la stanga la dreapta de

obicei, care definesc pozitiile de oprire ale dreptei de baleiere; fiecare astfel de pozitie de oprire

se numeste punct eveniment; numai in punctele eveniment se intalnesc modificari ale starii

liniei de baleiere; pentru unii algoritmi, lista punct-eveniment este determinata dinamic in

timpul executiei algoritmului.

Pentru a rezolva problema, vom deplasa o dreapta de baleiere verticala, imaginara de la stanga la
dreapta. Lista punct-eveniment va contine capetele segmentelor orizontale, ce fel de tip sunt (cap
stanga sau cap dreapta) si segmentele verticale. Pe masura ce ne deplasam de la stanga la dreapta
vom efectua urmatoarele operatii:

 cand intalnim un capat stang, inseram capatul in starile dreptei de baleiere;

 cand intalnim un capat drept, stergem capatul din starile dreptei de baleiere;

 cand intalnim un segment vertical, numarul de intersectii ale acestui segment cu alte

segmente orizontale va fi dat de numarul capetelor de intervale care se afla in starile dreptei de
baleiere cuprinse intre coordonatele y ale segmentului vertical.

Astfel, starile dreptei de baleiere sunt o structura de date pentru care avem nevoie de urmatoarele
operatii:

 INSEREAZA : insereaza capatul y

 STERGE : sterge capatul y

 INTEROGARE : intoarce numarul de capete cuprinse in intervalul [y1, y2]

Fie MAXC valoarea maxima a coordonatelor capetelor de segmente. Folosind un vector pentru a
implementa aceste operatiile descrise mai sus vom obtine o complexitate O(1) pentru primele doua
operatii si O(MAXC) va fi pentru cea de-a treia. Astfel, complexitatea O(N*MAXC) in cazul cel mai
defavorabil. Putem comprima spatiul [0...MAXC] observand ca doar maxim N din valori din acest
interval conteaza, si anume capetele segmentelor orizontale, astfel reducand a treia operatie la O(N),
dar algoritmul va avea complexitatea O(N2), ceea ce nu aduce nici o imbunatatire fata de algoritmul
trivial.

Aceasta situatie ne indeamna sa cautam o structura de date mai eficienta. O prima varianta ar fi
impartirea vectorului in bucati de sqrt(N) reducand complexitatea totala la O(N*√N). In continuare
vom prezenta o structura de date care ofera o complexitate logaritmica pentru operatiile descrise mai
sus.

Arbori de intervale
Un arbore de intervale este un arbore binar in care fiecare nod poate avea asociata o structura
auxiliara(anumite informatii). Dandu-se doua numere intregi st si dr, cu st<dr, atunci arborele de
intervale T(st,dr) se construieste recursiv astfel:

 consideram radacina nod avand asociat intervalul [st,dr]


 daca st<dr atunci vom avea asociat subarborele stang T(st,mij), respectiv subarborele drept

T(mij+1,dr), unde mij este mijlocul intervalului [st,dr]

Intervalul [st,dr] asociat unui nod se numeste interval standard. Frunzele arborelui sunt considerate
intervale elementare, ele avand lungimea 1.

Proprietate:
Un arbore de intervale este un arbore binar echilibrat(diferenta absoluta intre adancimea subarborelui
stang si cea a subarborelui drept este cel mult 1). Astfel, adancimea unui arbore de intervale care
contine N intervale este [log2N]+1.

Operatii efectuate asupra unui arbore de intervale:


Actualizare unui interval intr-un arbore de intervale
Vom prezenta pseudocodul unei proceduri recursive care insereaza un interval [a,b] intr-un arbore de
intervale T(st,dr) cu radacina in nodul nod. Cea mai eficienta metoda de stocare in memorie a unui
arbore de intervale este sub forma unui vector folosind aceeasi codificare a nodurilor ca si la heap-
uri :

1. procedura ACTUALIZARE(nod, st, dr, a, b)   
2.    daca (a<=st) si (dr<=b) atunci   
3.       modifica structura auxiliara din nod    
4.    altfel    
5.       mij = (st+dr)/2   
6.       daca (a <= mij) atunci ACTUALIZARE(2*nod, st, mij, a, b)   
7.       daca (b > mij) atunci ACTUALIZARE(2*nod+1, mij+1, dr, a, b)   
8.       actualizeaza structura auxiliara din nodul nod  

Interogarea unui interval intr-un arbore de intervale


Vom prezenta pseudocodul unei proceduri recursive care returneaza informatiile asociate unui interval
[a,b] intr-un arbore de intervale T(st,dr) cu radacina in nodul nod.

1. procedura INTEROGARE(nod, st, dr, a, b)   
2.    daca (a<=st) si (dr<=b) atunci   
3.       returneaza structura auxiliara din nod    
4.    altfel    
5.       mij = (st+dr)/2   
6.       daca (a <= mij) atunci INTEROGARE(2*nod, st, mij, a, b)   
7.       daca (b > mij) atunci INTEROGARE(2*nod+1, mij+1, dr, a, b)   
8.       returneaza structura auxiliara din fiul stang combinata cu cea din fiul drept  

Vom demonstra in continuare ca operatiile prezentate mai sus au complexitatea O(log2 N) pentru un
arbore de N intervale. Este posibil ca intr-un nod sa aiba loc apel atat in fiul stang cat si in cel drept.
Acest lucru produce un cost aditional doar prima data cand are loc. Dupa prima "rupere in doua",
oricare astfel de "rupere" nu va aduce cost aditional, deoarece unul din fii va fi mereu inclus complet
in intervalul [a,b]. Cum inaltimea arborelui, pentru N intervale, este [log2N]+1, complexitatea
operatiilor va fi tot O(log2 N).

Pentru a retine in memorie un arbore de intervale pentru N valori, vom aveam de nevoie de
N+N/2+N/4+N/8...=2*N-1 locatii de memorie (sunt 2*N-1 noduri). Deoarece arborele nu este complet,
trebuie verificat de fiecare data daca fii unui nod exista in arbore (aceasta verificare a fost omisa in
pseudocodul de mai sus), altfel s-ar incerca accesarea de valori din vector care nu exista. Daca
memorie disponibila in timpul concursului este suficienta, se poate declara vectorul care retine
arborele de intervale de lungime 2K astfel incat 2K≥2*N-1, simuland astfel un arbore complet si nefiind
necesare verificarile mentionate mai sus.

Pentru a rezolva in continuare problema, vom folosi un arbore de intervale pentru a simula in timp
logaritmic operatiile facute inainte pe un vector obisnuit. Astfel, in fiecare nod din arborele din
intervale vom retine cate capete exista in acel interval. Primele doua operatii vor fi implementate
folosind procedura ACTUALIZARE de mai sus pentru intervalul [y,y] in arborele T(0, MAXC) si
adunand 1, respectiv scazand 1 la fiecare nod actualizat. Cea de-a treia operatie poate fi realizata
folosind procedura INTEROGARE pe intervalul [y1,y2]. Astfel complexitatea se reduce la O(N*log2
MAXC)Folosind  aceeasi tehnica de "comprimare" a coordonatelor se poate obtine o complexitate
O(N*log2 N).

In figura urmatoare este descrisa structura arborelui de intervale, dupa actualizarea pentru intervalele
[2,2], [6,6] si [9,9]. Sunt marcate intervalele care conduc la obtinerea numarului de segmente
intersectate de primul segment vertical, obtinute in urma interogarii pe intervalul [0,8].
Problema 2 (Preluata de la IOI 1998, Ziua 2)
Se considera N≤50 000 dreptunghiuri in plan, fiecare avand laturile paralele cu axele OX, OY.
Lungimea marginilor (contururilor) reuniunii tuturor dreptunghiurilor se va numi "perimetru". Sa se
calculeze perimetrul celor N dreptunghiuri.

In fisierul drept.in se gaseste pe prima linie numarul N de dreptunghiuri, iar pe fiecare din
urmatoarele N linii cate patru numere naturale mai mici ca 50 000, reprezentand coordonatele
carteziene ale coltului stanga sus, respectiv dreapta jos ale fiecarui dreptunghi. Rezultatul se va scrie
in fisierul drept.out.

Timp maxim de executie: 1 secunda / test

Exemplu:
drept.in drept.out Figura
3 44
3 8 8 3
6 10 12 6
12 4 15 1

Folosind un rationament asemanator ca si la prima problema, constatam necesitatea unui algoritm de


baleiere. Vom descompune problema in doua parti: prima data calculam perimetrul folosind doar
laturile stanga si dreapta ale dreptunghiurilor pentru a calcula perimetrul pe OY; apoi putem sa rotim
dreptunghiurile si folosind aceeasi procedura pt a calcula perimetrul pe OX, folosind doar laturile sus si
jos ale dreptunghiurilor.

Fiecare latura (stanga sau dreapta) va fi un punct eveniment. Sortam laturile crescator dupa
coordonata x si parcurgem de la stanga la dreapta. Cand intalnim o latura stanga adaugam in starile
dreptei de baleiere, intervalul de unitati ocupat de latura pe OY, iar cand intalnim o latura dreapta,
eliminam din starile dreptei de baleiere intervalul de unitati ocupat de latura. Intre oricare doua
puncte eveniment consecutive are loc o modificare a perimetrului total pe OY. Astfel, dupa fiecare
actualizare a starilor dreptei de baleiere se va retine care este numarul de unitati adaugate pana in
prezent (nu se tine cont daca o unitate este adaugata de mai multe ori). Vom aduna la perimetrul pe
OY total de fiecare data, diferenta absoluta intre numarul de unitati adaugate pana in prezent si
valoarea imediat anterioara (dinaintea actualizarii). Asadar, este necesara o structura de date care se
efectueze urmatoarele operatii in timp eficient (preferabil logaritmic):

 MARCHEAZA : adauga intervalul [a, b]

 DEMARCHEAZA : elimina intervalul [a, b]

 INTEROGARE : returneaza numarul total de coordonate adaugate pana in prezent

Putem folosi un arbore de intervale pentru a obtine o complexitate O(log2 MAXC) sau O(log2 N)
pentru primele doua operatii si O(1) pentru ce-a de treia. In fiecare nod din arbore retinem de cate ori
a fost marcat intervalul respectiv si cate unitati din intervalul respectiv au fost marcate. Primele doua
operatii pot fi implementate folosind procedura ACTUALIZARE iar a treia operatie va returna valoarea
numarul de unitati care au fost marcate din radacina arborelui de intervale.

In figura urmatoare este descrisa structura arborelui de intervale, dupa adaugarea intervalelor [3,8]
si [6,10] (un interval [y1,y2] reprezinta intervalul de unitati [y1, y2-1] din arbore).

Problema 3
Se dau N≤100 000 puncte in plan de coordonate numere naturale mai mici ca 2 000 000 000. Sa se
raspunda la M≤1 000 000 intrebari de forma "cate puncte din cele N exista in dreptunghiul cu coltul
stanga sus in (x1,y1) si coltul dreapta jos in (x2,y2)?".

In fisierul puncte.in se vor gasi pe prima linie numerele N si M. Pe urmatoarele N linii se vor gasi
coordonatele punctelor. Pe urmatoarele M linii se vor gasi cate patru numere naturale reprezentand
coordonatele colturilor dreptunghiurilor.

In fisierul puncte.out se vor gasi M numere naturale reprezentand raspunsurile la intrebari.

Timp maxim de executie: 1 secunda / test

Exemplu:

puncte.in puncte.out Figura


6 1 2
2 8
5 3
6 5
8 7
8 1
10 10
4 8 9 4

Problema determinarii numarului de puncte din interiorul unui dreptunghi este o particularizare
bidimensionala pentru problema numita in literatura de specialitate "Orthogonal Range Search".
Varianta aleasa pentru acest caz consta intr-un arbore de intervale, care permite determinarea
numarului de puncte din interiorul unui dreptunghi cu o complexitate O(log22 N).

Astfel, se sorteaza coordonatele x ale punctelor si se construieste un arbore de intervale. Primul nivel
al arborelui va contine toate coordonatele x. Cei doi fii ai lui vor contine prima jumatate, respectiv a
doua jumatate a punctelor (sortate dupa coordonata x) si asa mai departe. Pentru fiecare interval de
coordonate x, se mentin sortate coordonatele y corespunzatoare punctelor care au coordonatele x in
intervalul respectiv. Astfel, memoria folosita este O(N*log2 N). Pentru a construi efectiv se foloseste o
abordare asemanatoare algoritmului de sortare prin interclasare: in fiecare nod, pentru a obtine lista
de coordonate y ordonate, se interclaseaza listelele celor doi fii (deja ordonate). Cand se ajunge intr-o
frunza, lista nodului este formata dintr-un singur punct.

Pentru fiecare dreptunghi, se executa cautarea in arborele de intervale pentru segmentul [x1, x2]
(deoarece se folosesc doar cele N coordonate x ale punctelor, se vor potrivi capetele acestui interval
folosind cautarea binara la cea mai apropiata coordonata x de fiecare capat). Pentru fiecare interval
din arbore atins, se executa doua cautari binare printre coordonatele y corespunzatoare coordonatelor
x din acel interval, pentru a determina cate puncte din intervalul respectiv se afla in intervalul [y1,
y2] (adica in interiorul dreptunghiului).

Complexitatea O(log22 N) poate fi redusa, folosind o metoda descoperita independent de Willard si


Lueker in anul 1978. Se observa ca pentru fiecare coordonata y dintr-un nod, daca presupunem ca se
efectueaza o cautare binara, atunci putem determina in timpul interclasarii pozitia din fiecare fiu pe
care o va returna cautarea binara pentru aceeasi valoare. Este evident ca pentru o valoare y dintr-un
nod care a provenit dintr-un fiu in timpul interclasarii, exista o unica valoare maxima y' din celalalt fiu
astfel incat y'≤y. Asadar, tinem pentru fiecare valoare y dintr-un nod doi indici care identifica pozitiile
corespunzatoare efectuarii unei cautari binare in fii pentru valoarea y. Astfel, in timpul cautarii, in loc
sa se efectueze cautari binare, se pot parcurge acesti indici, care ofera in O(1) pozitia in lista cautata,
reducand complexitatea totala la O(log2 N).
Probleme propuse
1) Se considera N≤50 000 dreptunghiuri in plan, fiecare avand laturile paralele cu axele OX, OY. Sa se
calculeze aria ocupata de reuniunea celor N dreptunghiuri.

In fisierul drept2.in se gaseste pe prima linie numarul N de dreptunghiuri, iar pe fiecare din
urmatoarele N linii cate patru numere naturale mai mici ca 50 000, reprezentand coordonatele
carteziene ale coltului stanga sus, respectiv dreapta jos ale fiecarui dreptunghi. Rezultatul se va scrie
in fisierul drept2.out.

Timp maxim de executie: 1 secunda / test

2) Se considera N≤50 000 puncte in plan de coordonate numere naturale mai mici ca 50 000. Sa se
determine unde se poate aseza un dreptunghi de lungime DX si latime DY astfel incat numarul de
puncte incluse in dreptunghi sa fie maxim.

In fisierul puncte.in se gasesc pe prima linie numerele N, DX si DY. Pe urmatoarele N linii se gasesc
coordonatele punctelor.

In fisierul puncte.out se vor gasi cinci numere naturale reprezentand coordonatele colturilor stanga
sus si dreapta jos ale asezarii dreptunghiului si numarul maxim de puncte din dreptunghi.

Timp maxim de executie: 1 secunda / test

3) Se considera N≤100 000 puncte in plan de coordonate numere naturale mai mici ca 100 000. Sa se
determine unde se pot aseza doua dreptunghiuri de lungime DX si latime DY, fara sa se intersecteze,
astfel incat numarul de puncte incluse in cele doua dreptunghiuri sa fie maxim.

In fisierul puncte2.in se gasesc pe prima linie numerele N, DX si DY. Pe urmatoarele N linii se gasesc
coordonatele punctelor.

In fisierul puncte2.out se vor gasi 9 numere naturale reprezentand coordonatele colturilor stanga sus
si dreapta jos ale asezarii primului dreptunghi, respectiv a celui de-al doilea, cat si numarul maxim de
puncte incluse in ambele dreptunghiuri.

Timp maxim de executie: 1 secunda / test

4) Se considera N≤250 000 dreptunghiuri in plan, fiecare avand laturile paralele cu axele OX/OY, care
nu se intersecteaza si nu se ating, dar pot fi incluse unul in altul. Se numeste "inchisoare" un
dreptunghi inconjurat de alte dreptunghiuri. Sa se determine numarul maxim de dreptunghiuri de care
poate fi inconjurata o "inchisoare" si cate astfel de "inchisori" maxime exista.

In fisierul inchis.in se gaseste pe prima linie numarul N de dreptunghiuri, iar pe fiecare din
urmatoarele N linii cate patru numere naturale mai mici ca 1 000 000 000, reprezentand coordonatele
carteziene ale coltului stanga sus, respectiv dreapta jos ale fiecarui dreptunghi.

In fisierul inchis.out se gasesc doua numere naturale: numarul maxim de dreptunghiuri de care
poate fi inconjurata o "inchisoare" si cate astfel de "inchisori" maxime exista.

Timp maxim de executie: 1 secunda / test


1. #include <stdio.h>   
2. #include <fstream>   
3. #include <assert.h>     
4. using namespace std;     
5. #define in "arbint.in"   
6. #define out "arbint.out"   
7. #define dim 100001     
8. int N, M;   
9. int MaxArb[4*dim+66];   
10. int start, finish, Val, Pos, maxim;    
11. inline int Maxim(int a, int b) {   
12.        if ( a > b ) return a;   
13.        return b;   }       
14. void Update(int,int,int);   
15. void Query(int,int,int);     
16. int main()   
17. {   
18.     int X, A, B;   
19.     freopen(in,"r",stdin);   
20.     freopen(out,"w",stdout);   
21.        
22.     scanf("%d%d", &N, &M);   
23.     for ( int i = 1; i <= N; i++ )   
24.     {   
25.         scanf("%d", &X);   
26.         Pos = i, Val = X;   
27.         Update(1,1,N);   
28.     }         
29.     for ( int i = 1; i <= M; i++ )   
30.     {   
31.         scanf("%d%d%d", &X, &A, &B);   
32.         if ( X == 0 )    
33.         {   
34.              maxim = -1;   
35.              start = A, finish = B;   
36.              Query(1,1,N);            
37.              printf("%d\n", maxim);   
38.         }   
39.         else  
40.         {   
41.             Pos = A, Val = B;   
42.             Update(1,1,N);   
43.         }   
44.     }   
45. }    
46. void Update(int nod, int left, int right)   
47. {   
48.      if ( left == right )   
49.      {   
50.           MaxArb[nod] = Val;   
51.           return;   
52.      }           
53.      int div = (left+right)/2;   
54.      if ( Pos <= div ) Update(2*nod,left,div);   
55.      else              Update(2*nod+1,div+1,right);   
56.         
57.      MaxArb[nod] = Maxim( MaxArb[2*nod], MaxArb[2*nod+1] );   
58. }     
59. void Query(int nod, int left, int right)   
60. {   
61.      if ( start <= left && right <= finish )   
62.      {   
63.           if ( maxim < MaxArb[nod] ) maxim = MaxArb[nod];   
64.           return;   
65.      }      
66.      int div = (left+right)/2;   
67.      if ( start <= div ) Query(2*nod,left,div);   
68.      if ( div < finish ) Query(2*nod+1,div+1,right);   
69. }  
Algoritmul lui Dijkstra
Se da un graf orientat cu N noduri si M arce.

Cerinta
Sa se determine lungimea minima a drumului de la nodul 1 la fiecare din nodurile 2, 3, ..., N-1, N si sa
se afiseze aceste lungimi. Lungimea unui drum este data de suma lungimilor arcelor care constituie
drumul.

Date de intrare
Fisierul de intrare dijkstra.in contine pe prima linie numerele N si M, separate printr-un spatiu, cu
semnificatia din enunt. Urmatoarele M linii contin, fiecare, cate 3 numere naturale separate printr-un
spatiu A B C semnificand existenta unui arc de lungime C de la nodul A la nodul B.

Date de iesire
In fisierul de iesire dijkstra.out veti afisa pe prima linie N-1 numere naturale separate printr-un
spatiu. Al i-lea numar va reprezenta lungimea unui drum minim de la nodul 1 la nodul i+1.

Restrictii
 1 ≤ N ≤ 50 000

 1 ≤ M ≤ 250 000

 Lungimile arcelor sunt numere naturale cel mult egale cu 1 000.

 Pot exista arce de lungime 0

 Nu exista un arc de la un nod la acelasi nod.


 Daca nu exista drum de la nodul 1 la un nod i, se considera ca lungimea minima este 0.

 Arcele date in fisierul de intrare nu se repeta.


Exemplu
dijkstra.in dijkstra.out
5 6 1 3 2 5
1 2 1
1 4 2
4 3 4
2 3 2
4 5 3
3 5 6

Timp executie pe test 0.5 sec Limita de memorie 8192 kbytes

Indicatii de rezolvare
O rezolvare de complexitate O(N2) obtine 40 de puncte si se poate gasi imediat in articol.
O rezolvare in O(Mlog2N) folosind un heap obtine 100 de puncte si se poate gasi ca o a doua
rezolvared. O implementare de aceeasi complexitate foloseste in loc de heap structura de date numita
SET, care este de fapt un arbore binar echilibrat si permite interogarea costului minim si modificarea
costurilor in timp logaritmic. Aceasta abordare se gaseste ca o a treia rezolvare si are avantajul ca
necesita un cod mult mai scurt. Totusi, ea este mai inceata decat solutia cu heap-uri si, in consecinta,
obtine doar 80 de puncte.
1. #include <cstdio>   
2.   
3. const int maxn = 50001;   
4. const int inf = 1 << 30;   
5.   
6. FILE *in = fopen("dijkstra.in","r"), *out = fopen("dijkstra.out","w");   
7.   
8. struct graf   
9. {   
10.     int nod, cost;   
11.     graf *next;   
12. };   
13.   
14. int n, m;   
15. graf *a[maxn];   
16. int d[maxn], q[maxn];   
17.   
18. void add(int where, int what, int cost)   
19. {   
20.     graf *q = new graf;   
21.     q->nod = what;   
22.     q->cost = cost;   
23.     q->next = a[where];   
24.     a[where] = q;   
25. }   
26.   
27. void read()   
28. {   
29.     fscanf(in, "%d %d", &n, &m);   
30.   
31.     int x, y, z;   
32.     for ( int i = 1; i <= m; ++i )   
33.     {   
34.         fscanf(in, "%d %d %d", &x, &y, &z);   
35.         add(x, y, z);   
36.     }   
37. }   
38.   
39. void dijkstra()   
40. {   
41.     for ( int i = 2; i <= n; ++i )   
42.         d[i] = inf;   
43.   
44.     int min, pmin = 0;   
45.     for ( int i = 1; i <= n; ++i )   
46.     {   
47.         min = inf;   
48.   
49.         for ( int j = 1; j <= n; ++j )   
50.             if ( d[j] < min && !q[j] )   
51.                 min = d[j], pmin = j;   
52.   
53.         q[pmin] = 1;   
54.   
55.         graf *t = a[pmin];   
56.   
57.         while ( t )   
58.         {   
59.             if ( d[ t->nod ] > d[pmin] + t->cost )   
60.                 d[ t->nod ] = d[pmin] + t->cost;   
61.   
62.             t = t->next;   
63.         }   
64.     }   
65. }   
66.   
67. int main()   
68. {   
69.     read();   
70.     dijkstra();   
71.   
72.     for ( int i = 2; i <= n; ++i )   
73.         fprintf(out, "%d ", d[i] == inf ? 0 : d[i]);   
74.     fprintf(out, "\n");   
75.   
76.     return 0;   
77. }  

1. #include <cstdio>   
2.   
3. const int maxn = 50001;   
4. const int inf = 1 << 30;   
5.   
6. FILE *in = fopen("dijkstra.in","r"), *out = fopen("dijkstra.out","w");   
7.   
8. struct graf   
9. {   
10.     int nod, cost;   
11.     graf *next;   
12. };   
13.   
14. int n, m;   
15. graf *a[maxn];   
16. int d[maxn], h[maxn], poz[maxn], k;   
17.   
18. void add(int where, int what, int cost)   
19. {   
20.     graf *q = new graf;   
21.     q->nod = what;   
22.     q->cost = cost;   
23.     q->next = a[where];   
24.     a[where] = q;   
25. }   
26.   
27. void read()   
28. {   
29.     fscanf(in, "%d %d", &n, &m);   
30.   
31.     int x, y, z;   
32.     for ( int i = 1; i <= m; ++i )   
33.     {   
34.         fscanf(in, "%d %d %d", &x, &y, &z);   
35.         add(x, y, z);   
36.     }   
37. }   
38.   
39. void swap(int x, int y)   
40. {   
41.     int t = h[x];   
42.     h[x] = h[y];   
43.     h[y] = t;   
44. }   
45.   
46. void upheap(int what)   
47. {   
48.     int tata;   
49.     while ( what > 1 )   
50.     {   
51.         tata = what >> 1;   
52.   
53.         if ( d[ h[tata] ] > d[ h[what] ] )   
54.         {   
55.             poz[ h[what] ] = tata;   
56.             poz[ h[tata] ] = what;   
57.   
58.             swap(tata, what);   
59.   
60.             what = tata;   
61.         }   
62.         else  
63.             what = 1;   
64.     }   
65. }   
66.   
67. void downheap(int what)   
68. {   
69.     int f;   
70.     while ( what <= k )   
71.     {   
72.         f = what;   
73.         if ( (what<<1) <= k )   
74.         {   
75.             f = what << 1;   
76.             if ( f + 1 <= k )   
77.                 if ( d[ h[f + 1] ] < d[ h[f] ] )   
78.                     ++f;   
79.         }   
80.         else  
81.             return;   
82.   
83.         if ( d[ h[what] ] > d[ h[f] ] )   
84.         {   
85.             poz[ h[what] ] = f;   
86.             poz[ h[f] ] = what;   
87.   
88.             swap(what, f);   
89.   
90.             what = f;   
91.         }   
92.         else  
93.             return;   
94.     }   
95. }   
96.   
97. void dijkstra_heap()   
98. {   
99.     for ( int i = 2; i <= n; ++i )   
100.         d[i] = inf, poz[i] = -1;   
101.     poz[1] = 1;   
102.   
103.     h[++k] = 1;   
104.   
105.     while ( k )   
106.     {   
107.         int min = h[1];   
108.         swap(1, k);   
109.         poz[ h[1] ] = 1;   
110.         --k;   
111.   
112.         downheap(1);   
113.   
114.         graf *q = a[min];   
115.   
116.         while ( q )   
117.         {   
118.             if ( d[q->nod] > d[min] + q->cost )   
119.             {   
120.                 d[q->nod] = d[min] + q->cost;   
121.   
122.                 if ( poz[q->nod] != -1 )   
123.                     upheap( poz[q->nod] );   
124.                 else  
125.                 {   
126.                     h[++k] = q->nod;   
127.                     poz[ h[k] ] = k;   
128.                     upheap( k );   
129.                 }   
130.             }   
131.             q = q->next;   
132.         }   
133.     }   
134. }   
135.   
136. int main()   
137. {   
138.     read();   
139.     dijkstra_heap();   
140.   
141.     for ( int i = 2; i <= n; ++i )   
142.         fprintf(out, "%d ", d[i] == inf ? 0 : d[i]);   
143.     fprintf(out, "\n");   
144.   
145.     return 0;   
146. }  

1. #include <cstdio>   
2. #include <cstring>   
3. #include <set>   
4. #include <vector>   
5. using namespace std;   
6.   
7. #define pb push_back   
8. #define mp make_pair   
9. #define MAXN 50100   
10. #define INF 1000000000   
11.   
12. int N, M, d[MAXN]; vector<int> G[MAXN], C[MAXN];   
13. set< pair<int, int> > T;   
14.   
15. void solve(void)   
16. {   
17.     int i, j, k, val, x;   
18.        
19.     for(i = 2; i <= N; i++) d[i] = INF;   
20.     T.insert( mp(0, 1) );   
21.   
22.     while( T.size() > 0 )   
23.     {   
24.         val = (*T.begin()).first, x = (*T.begin()).second;   
25.         T.erase(*T.begin());   
26.         for(i = 0; i < G[x].size(); i++)   
27.          if(d[ G[x][i] ] > val + C[x][i] )   
28.             d[ G[x][i] ] = val + C[x][i], T.insert(mp(d[G[x][i]],G[x][i]));   
29.     }   
30. }   
31.   
32. int main(void)   
33. {   
34.     freopen("dijkstra.in", "rt", stdin);   
35.     freopen("dijkstra.out", "wt", stdout);   
36.   
37.     int i, a, b, c;   
38.   
39.     scanf("%d %d\n", &N, &M);   
40.   
41.     for(i = 1; i <= M; i++)   
42.         scanf("%d %d %d\n", &a, &b, &c), G[a].pb(b), C[a].pb(c);   
43.   
44.     solve();   
45.   
46.     for(i = 2; i <= N; i++)   
47.         printf("%d ", d[i] == INF ? 0 : d[i]);   
48.     return 0;   
49. }   
Ciurul lui Eratosthenes
Se de un numar natural N.

Cerinta
Sa se determine numarul numerelor prime mai mici sau egale cu N si sa se afiseze aceste numere. In
caz ca numarul de numere depaseste 1000, se vor afisa doar cele mai mari 1000 de numere.

Date de intrare
Fisierul de intrare ciur.in contine o singura linie pe care se afla numarul N.

Date de iesire
In fisierul de iesire ciur.out se va scrie pe prima linie numarul de numere prime mai mici sau egale
cu N. A doua linie va contine numerele prime aflate. In caz ca numarul lor depaseste 1000, a doua linie
va contine doar cele mai mari 1000 de numere. Numerele de pe cea de a doua linie trebuiesc afisate in
ordine crescatoare.

Restrictii
 2 ≤ N ≤ 2 000 000
Exemplu
ciur.in ciur.out
10 4
2 3 5 7

Timp executie pe test 0.4 sec Limita de memorie 3072 kbytes

Indicatii de rezolvare
O rezolvare imediata ar fi iterarea tuturor numerelor de la 2 la N si testarea primalitatii acestora.
Aceasta solutie obtine 30 de puncte si se gaseste in continuare ca prima rezolvare. Rezolvarea de 100
de puncte se bazeaza pe folosirea Ciurului lui Erathostenes. Sursa se gaseste ca a doua rezolvare.

1. #include <stdio.h>   
2.   
3. int N, n, sol[1024], cnt;   
4. char prim[2000005];   
5.   
6. int is_prim(int X)   
7. {   
8.     int i;   
9.        
10.     for (i = 2; i * i <= X; ++i)   
11.         if (X % i == 0)   
12.             return 0;   
13.     return 1;   
14. }   
15.   
16. int main(void)   
17. {   
18.     int i;   
19.        
20.     freopen("ciur.in", "r", stdin);   
21.     freopen("ciur.out", "w", stdout);   
22.   
23.     scanf("%d", &N);   
24.     for (i = 2; i <= N; ++i)   
25.         if (is_prim(i))   
26.             ++n,   
27.             prim[i] = 1;        
28.     printf("%d\n", n);   
29.     for (i = N; i > 1; --i)   
30.         if (prim[i])   
31.         {   
32.             sol[++cnt] = i;   
33.             if (cnt == 1000)   
34.                 break;     
35.         }   
36.        
37.     for (i = cnt; i; --i)   
38.         printf("%d ", sol[i]);   
39.     printf("\n");   
40.     return 0;   
41. }  

1. #include <stdio.h>   
2.   
3. int N, n, sol[1024], cnt;   
4. char prim[2000005];   
5.   
6. int main(void)   
7. {   
8.     int i, j;   
9.     freopen("ciur.in", "r", stdin);   
10.     freopen("ciur.out", "w", stdout);   
11.   
12.     scanf("%d", &N);   
13.     for (i = 2; i <= N; ++i)   
14.         prim[i] = 1;   
15.     for (i = 2; i <= N; ++i)   
16.         if (prim[i])   
17.         {   
18.             ++n;   
19.             for (j = i+i; j <= N; j += i)   
20.                 prim[j] = 0;   
21.         }   
22.        
23.     printf("%d\n", n);   
24.     for (i = N; i > 1; --i)   
25.         if (prim[i])   
26.         {   
27.             sol[++cnt] = i;   
28.             if (cnt == 1000)   
29.                 break;   
30.         }   
31.   
32.     for (i = cnt; i; --i)   
33.         printf("%d ", sol[i]);   
34.     printf("\n");   
35.        
36.     return 0;   
37. }  

Generare de permutari
Sa se genereze toate permutarile multimii {1, 2, ...N}, in ordine lexicografica.

Date de intrare
In fisierul de intrare permutari.in se gaseste pe prima linie numarul natural N.

Date de iesire
In fisierul de iesire permutari.out se vor afisa permutarile multimii, fiecare pe cate o linie.

Restrictii
 1 ≤ N ≤ 8
Exemplu
permutari.in permutari.out
3 1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1

Timp executie pe test 0.2 sec Limita de memorie 16384 kbytes

Indicatii de rezolvare
Problema este o aplicatie clasica a metodei backtracking. Pentru mai multe informatii consultati
manualele de specialitate O solutie de 100 de puncte poate fi gasita in continuare

1. var s:array[1..10] of longint;   
2.     n,k:longint;   
3.   
4. procedure back(k:longint);   
5. var i,j:longint;   
6.     ok:boolean;   
7. begin  
8. if k=n then  
9.         begin  
10.         for i:=1 to n do  
11.                 write(s[i],' ');   
12.         writeln;   
13.         end  
14. else  
15.         begin  
16.         for i:=1 to n do  
17.                 begin  
18.                 ok:=true;   
19.                 for j:=1 to k do  
20.                         if s[j]=i then  
21.                                 begin  
22.                                 ok:=false;   
23.                                 break;   
24.                                 end;   
25.                 if ok then  
26.                         begin  
27.                         s[k+1]:=i;   
28.                         back(k+1);   
29.                         end;   
30.                 end;   
31.         end;   
32. end;   
33.   
34. begin  
35. assign(input,'permutari.in');reset(input);   
36. assign(output,'permutari.out');rewrite(output);   
37. readln(n);   
38. back(0);   
39. close(input);close(output);   
40. end.  

Evaluarea unei expresii


Se da un sir de caractere ce reprezinta o expresie aritmetica.
Cerinta
Afisati rezultatul obtinut prin evaluarea expresiei.

Date de intrare
Fisierul de intrare evaluare.in va contine pe prima linie un sir de caractere compus din cifre ( '0' -
'9' ), operatorii '+', '-', '*', '/' si paranteze( '(', ')' ).

Date de iesire
In fisierul de iesire evaluare.out se va scrie un singur numar intreg care reprezinta valoarea obtinuta
in urma evaluarii expresiei.

Restrictii si precizari
 1 ≤ lungimea sirului ≤ 100 000

 Operatorii '+','-','*' au semnificatia cunoscuta de la matematca, iar operatorul '/'

reprezinta catul impartirii intregi a doua numere


 Ordinea efectuarii operatiilor este cea normala

 Se garanteaza ca atat rezultatul final, cat si orice rezultat intermediar nu va depasi in modul
1 000 000 000
Exemplu
evaluare.in evaluare.out
(1+1)*3+10/2 11

Timp executie pe test 0.1 sec Limita de memorie 16384 kbytes

Indicatii de rezolvare
Probabil cea mai cunoscuta metoda de a evalua o expresie algebrica este scrierea ei in forma
poloneza, urmata de evaluarea propriu-zisa, despre care puteti gasi mai multe informatii in manualul
de clasa a 11-a. Problema se poate rezolva si folosind arbori.
De asemenea, o a treia metoda este explicata pe larg in sursa urmatoare de 100 puncte.

1. #include <cstdio>   
2. #include <cstring>   
3.   
4. char s[100010];   
5. char *p=s;   
6. char operand[4][4] = {"+-", "*/", "^", ""};   
7. const long hmax = 2;   
8.   
9. long operatie(long x, long y, char c) {   
10.     switch ( c ) {   
11.         case '+': return x+y;   
12.         case '-': return x-y;   
13.         case '*': return x*y;   
14.         case '/': return x/y;   
15.     }   
16.     return 0;   
17. }   
18.   
19. long eval(long);   
20.   
21. long element() {   
22.     long r=0;   
23.     if ( *p == '(' ) {   
24.         ++p; r = eval(0); ++p;   
25.     } else {   
26.         while ( strchr("0123456789", *p) )   
27.             r = r*10 + *(++p-1) - '0';   
28.     }   
29.     return r;   
30. }   
31.   
32. long eval(long h) {   
33.     long r = (h==hmax)?element():eval(h+1);   
34.     while ( strchr(operand[h], *p) )    
35.         r = operatie(r, eval(h+1), *(++p-1));   
36.     return r;   
37. }   
38.   
39.   
40. int main() {   
41.     fgets(s, 100010, fopen("evaluare.in", "r"));   
42.     fprintf(fopen("evaluare.out", "w"), "%ld\n", eval(0));   
43.     return 0;   
44. }  

Ridicare la putere in timp logaritmic


Dandu-se doua numere naturale N si P, se cere sa se calculeze restul impartirii lui NP la 1999999973.
Date de intrare
Fisierul de intrare lgput.in va contine 2 numere N si P.

Date de iesire
In fisierul de iesire lgput.out va fi scris un singur numar reprezentand restul impartirii lui NP la
1999999973.

Restrictii
 2 ≤ N, P ≤ 232
Exemplu
lgput.in lgput.out
2 4 16

Timp executie pe test 0.1 sec Limita de memorie 16384 kbytes

Indicatii de rezolvare
O solutie directa este cea prin inmultiri repetate. Aceasta solutie are complexitatea O(P) si obtine 30
de puncte.
O alta solutie este cea de a ridica la putere in timp logaritmic si are o complexitatea O(log2P).
Algoritmul se poate aplica si la matrici si polinoame.
Sursa de 100 de puncte se gaseste implementata mai jos.

1. #include <stdio.h>   
2. #include <string.h>   
3.   
4. const int n_max = 10001; // Definim numarul maxim de cifre al numerelor   
5. const int m = 1999999973;   
6.   
7. int main()   
8. {   
9.     unsigned int i, n, p;   
10.     long long a, sol = 1;   
11.   
12.     freopen("lgput.in","r",stdin);   
13.     freopen("lgput.out","w",stdout);   
14.     scanf("%d %d", &n, &p);   
15.     a = n;   
16.     for (i = 0; (1<<i) <= p; ++ i)  // Luam toti biti lui p la rand   
17.     {   
18.         if ( ((1<<i) & p) > 0) // Daca bitul i din p este 1 atunci adaugam n^(2^i) la s
olutie    
19.             sol= (sol * a) % m;   
20.        
21.             a=(a * a) % m; // Inmultim a cu a ca sa obtinem n^(2^(i+1))   
22.     }   
23.     printf("%lld\n", sol); // Afisam solutia   
24. }  

Parcurgere DFS - componente conexe


Se da un graf neorientat cu N noduri si M muchii.
Cerinta
Sa se determine numarul componentelor conexe ale grafului.

Date de intrare
Fisierul de intrare dfs.in contine pe prima linie numerele N si M cu semnificatia din enunt, iar pe
urmatoarele M linii se gasesc cate doua numere X si Y cu semnificatia: exista muchie de la nodul X la
nodul Y.

Date de iesire
In fisierul de iesire dfs.out se va afisa numarul de componente conexe ale grafului.

Restrictii
 1 ≤ N ≤ 100 000

 0 ≤ M ≤ minim(200 000, N*(N+1)/2)

 Pentru 50% dintre teste 1 ≤ N ≤ 1 000


Exemplu
dfs.in dfs.out
6 3 3
1 2
1 4
3 5

Timp executie pe test 0.2 sec Limita de memorie 16384 kbytes

Indicatii de rezolvare
Problema se rezolva prin parcurgeri DFS din fiecare nod nemarcat, si marcarea nodurilor in aceste
parcurgeri. O rezolvare care retine graful prin matricea de adiacenta va obtine doar 50 de puncte.
Sursa bazata pe aceasta idee se gaseste mai jos, ca prima implementare a metodei. O rezolvare care
foloseste liste de adiacenta pentru retinerea grafului obtine 100 de puncte. Sursa ce obtine punctajul
maxim e implementata ca o a doua rezolvare.

1. #include <stdio.h>   
2.   
3. int a[1005][1005], n, m, viz[1005], cnt;   
4.   
5. void citire()   
6. {   
7.     freopen("dfs.in","r",stdin);   
8.     freopen("dfs.out","w",stdout);   
9.     scanf("%d %d",&n,&m);   
10.     int i, x, y;   
11.     for (i = 1; i <= m; i++)   
12.     {   
13.         scanf("%d %d",&x,&y);   
14.         a[x][y] = a[y][x] = 1;   
15.     }   
16. }   
17.   
18. void DFS(int nod)   
19. {   
20.     int i;   
21.     viz[nod] = 1;   
22.     for (i = 1; i <= n; i++) if (!viz[i] && a[nod][i]) DFS(i);   
23. }      
24.   
25. int main()   
26. {   
27.     citire();   
28.     int i;   
29.     for (i = 1; i <= n; i++) if (!viz[i]) { cnt++; DFS(i);}   
30.     printf("%d\n",cnt);   
31.     return 0;   
32. }  

1. #include <stdio.h>   
2.   
3. int n, m, viz[100005], cnt;   
4.   
5. typedef struct nod   
6. {   
7.     int x;   
8.     nod *a;   
9. } *pNod;   
10. pNod v[100005];   
11.   
12. void citire()   
13. {   
14.     freopen("dfs.in","r",stdin);   
15.     freopen("dfs.out","w",stdout);   
16.     scanf("%d %d",&n,&m);   
17.     int i, x, y;   
18.     pNod p;   
19.        
20.     for (i = 1; i <= m; i++)   
21.     {   
22.         scanf("%d %d",&x,&y);   
23.         p = new nod;   
24.         p -> x = x;   
25.         p -> a = v[y];   
26.         v[y] = p;   
27.         p = new nod;   
28.         p -> x = y;   
29.         p -> a = v[x];   
30.         v[x] = p;   
31.     }   
32. }   
33.   
34. void DFS(int nod)   
35. {   
36.     pNod p;   
37.     viz[nod] = 1;   
38.     for (p = v[nod]; p != NULL; p = p -> a) if (!viz[p -> x]) DFS(p -> x);   
39. }      
40.   
41. int main()   
42. {   
43.     citire();   
44.     int i;   
45.     for (i = 1; i <= n; i++) if (!viz[i]) { cnt++; DFS(i);}   
46.     printf("%d\n",cnt);   
47.     return 0;   
48. }  

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