Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pa:tutoriale:dei [2026/02/25 18:35] (current)
darius.neatu created
Line 1: Line 1:
 +====== Tutorial: Divide et Impera ======
 +
 +
 +===== Obiective laborator =====
 +
 +  * Înțelegerea conceptului teoretic din spatele descompunerii unei probleme
 +  * Rezolvarea de probleme abordabile folosind conceptul ​ de Divide et Impera
 +
 +
 +===== Precizări inițiale =====
 +<​note>​
 +Toate exemplele de cod se găsesc pe pagina [[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​demo/​lab01|pa-lab::​demo/​lab01]].
 +
 +Exemplele de cod apar încorporate și în textul laboratorului pentru a facilita parcurgerea cursivă a acestuia. ATENȚIE! Varianta actualizată a acestor exemple se găsește întotdeauna pe GitHub.
 +</​note>​
 +
 +  * Toate bucățile de cod prezentate în partea introductivă a laboratorului (înainte de exerciții) au fost testate. Cu toate acestea, este posibil ca din cauza mai multor factori (formatare, caractere invizibile puse de browser etc.) un simplu copy-paste să nu fie de ajuns pentru a compila codul.
 +  * Vă rugam să compilați **DOAR** codul de pe GitHub. Pentru raportarea problemelor,​ contactați unul dintre maintaineri. ​
 +  * Pentru orice problemă legată de conținutul acestei pagini, vă rugam să dați e-mail unuia dintre responsabili.
 +
 +===== Importanţă – aplicaţii practice =====
 +
 +Paradigma Divide et Impera stă la baza construirii de algoritmi eficienți pentru diverse probleme:
 +  * Sortări (ex: MergeSort [[http://​www.sorting-algorithms.com/​merge-sort|[1]]],​ QuickSort [[http://​www.sorting-algorithms.com/​quick-sort|[2]]])
 +  * Înmulțirea numerelor mari (ex: Karatsuba [[http://​en.wikipedia.org/​wiki/​Karatsuba_algorithm|[3]]])
 +  * Analiza sintactică (ex: parsere top-down [[http://​en.wikipedia.org/​wiki/​Top-down_parser|[4]]])
 +  * Calcularea transformatei Fourier discretă (ex: FFT [[http://​en.wikipedia.org/​wiki/​Fast_Fourier_transform|[5]]])
 +
 +Un alt domeniu de utilizare a tehnicii divide et impera este programarea paralelă pe mai multe procesoare, sub-problemele fiind executate pe mașini diferite.
 +
 +===== Prezentarea generală a problemei =====
 +
 +O descriere a tehnicii D&I: “Divide and Conquer algorithms break the problem into several sub-problems that are similar to the original problem but smaller in size, solve the sub-problems recursively,​ and then combine these solutions to create a solution to the original problem.”
 +
 +Deci un algoritm D&I **împarte problema** în mai multe subprobleme similare cu problema inițială şi de dimensiuni mai mici, **rezolvă subproblemele** recursiv şi apoi **combină soluțiile** obţinute pentru a obține soluția problemei inițiale.
 +
 +Sunt trei pași pentru aplicarea algoritmului D&I:
 +
 +  * **Divide**: împarte problema în una sau mai multe //probleme similare de dimensiuni mai mici//.
 +  * **Impera** (stăpânește):​ rezolvă subproblemele recursiv; dacă dimensiunea sub-problemelor este mică, se rezolvă iterativ.
 +  * **Combină**:​ combină soluțiile subproblemelor pentru a obține soluția problemei inițiale.
 +
 +Complexitatea algoritmilor D&I se calculează după formula:
 +
 +$T(n) = D(n) + S(n) + C(n)$,
 +
 +unde $D(n)$, $S(n)$ şi $C(n)$ reprezintă complexitățile celor 3 pași descriși mai sus: divide, stăpânește,​ respectiv combină.
 +
 +===== Probleme clasice =====
 +
 +==== MergeSort ====
 +=== Enunț ===
 +Sortarea prin interclasare (MergeSort [[http://​www.sorting-algorithms.com/​merge-sort|[1]]]) este un algoritm de sortare de vectori ce folosește paradigma D&I:
 +
 +  * **Divide**: împarte vectorul inițial în doi sub-vectori de dimensiune n/2.
 +  * **Stăpânește**:​ sortează cei doi sub-vectori recursiv folosind sortarea prin interclasare;​ recursivitatea se oprește când dimensiunea unui sub-vector este 1 (deja sortat).
 +  * **Combina**:​ Interclasează cei doi sub-vectori sortați pentru a obține vectorul inițial sortat.
 +
 +=== Pseudocod ===
 +Mai jos găsiți algoritmul MergeSort scris în pseudocod.
 +
 +<spoiler Pseudocod>​
 +<code cpp>
 +MergeSort(v,​ start, end) // v – vector, start – limită inferioră, end – limită superioară
 + if (start == end) return; // condiția de oprire
 + mid = (start + end) / 2; // etapa divide
 + MergeSort(v,​ start, mid); // etapa stăpânește
 + MergeSort(v,​ mid + 1, end);
 + Merge(v, start, end); // etapa combină
 + 
 +Merge(v, start, end) // interclasare sub-vectori
 + // indecsi
 +        mid = (start + end) / 2;
 + i = start;
 + j = mid + 1;
 + k = 1;
 +
 +        tmp = buffer temporar in care incap (end - start + 1) numere
 + while (i <= mid && j <= end) 
 + if (v[i] <= v[j]) tmp[k++] = v[i++];
 +              else tmp[k++] = v[j++];
 +
 + while (i <= mid) 
 + tmp[k++] = v[i++];
 +
 + while (j <= end) 
 + tmp[k++] = v[j++];
 +
 + copy(v[start..end],​ tmp[1..k-1]);​
 +</​code>​
 +</​spoiler>​
 +
 +<spoiler Implementare in C++>
 +Mai jos puteti găsi o implementare în C++.
 +
 +<code cpp>
 +#include <​bits/​stdc++.h>​
 +using namespace std;
 +
 +vector<​int>​ v;
 +
 +void merge_halves(int left, int right) {
 + int mid = (left + right) / 2;
 + vector<​int>​ aux;
 + int i = left, j = mid + 1;
 +
 +        while (i <= mid && j <= right) {
 + if (v[i] <= v[j]) {
 + aux.push_back(v[i]);​
 + i++;
 + } else {
 + aux.push_back(v[j]);​
 + j++;
 + }
 + }
 +
 +        while (i <= mid) {
 + aux.push_back(v[i]);​
 + i++;
 + }
 +
 +        while (j <= right) {
 + aux.push_back(v[j]);​
 + j++;
 + }
 +
 + for (int k = left; k <= right; k++) {
 + v[k] = aux[k - left];
 + }
 +}
 +
 +
 +void merge_sort(int left, int right) {
 + if (left >= right) return ;
 + int mid = (left + right) / 2;
 +
 +        merge_sort(left,​ mid);
 + merge_sort(mid + 1, right);
 + merge_halves(left,​ right);
 +}
 +
 +int main() {
 + random_device rd;
 + for (int i = 0; i < 10; i++) {
 + v.push_back(rd() % 100);
 + }
 +
 + cout << "​Vectorul initial: ";
 + for (int i = 0; i < v.size(); i++) {
 + cout << v[i] << " ";
 + }
 + cout << "​\n";​
 +
 + merge_sort(0,​ v.size() - 1);
 +
 + cout << "​Vectorul sortat: ";
 + for (int i = 0; i < v.size(); i++) {
 + cout << v[i] << " ";
 + }
 + cout << "​\n";​
 +
 + return 0;
 +}
 +
 +</​code>​
 +</​spoiler>​
 +
 +== Complexitate ===
 +Complexitatea algoritmului este dată de formula: $T(n) = D(n) + S(n) + C(n)$, unde $D(n)=O(1)$,​ $S(n) = 2 * T(n / 2)$ și $C(n) = O(n)$, rezulta $T(n) = 2 * T(n / 2) + O(n)$.
 +
 +Folosind teorema Master [[http://​people.csail.mit.edu/​thies/​6.046-web/​master.pdf|[8]]] găsim complexitatea algoritmului:​ $T(n) = O(n * log(n))$.
 +
 +
 +  * **complexitate temporală** : $T=O(n * log(n))$
 +    * Ce înseamnă această metrică? Măsoara efectiv **timpul de execuție** al algoritmului (nu include citiri, afișări etc).
 +  * **complexitate spatiala** : $S=O(n)$
 +    * Ce înseamnă această metrica? Măsoara efectiv **memoria suplimentară** folosită de algoritm (în acest caz ne referim strict la buffer-ul temporar).
 +
 +<​note>​
 +Rețineți cele două convenții despre complexități de mai sus. Le vom folosi pentru restul semestrului.
 +</​note>​
 +==== Binary Search ====
 +=== Enunț ===
 +Se dă un **vector sortat crescător** ($v[1]$, $v[2]$, ..., $v[n]$) ce conține valori reale distincte și o valoare x. 
 +
 +Să se găsească la ce **poziție** apare x în vectorul dat.
 +
 +=== Rezolvare ===
 +BinarySearch (Căutare Binară), se rezolva cu un algoritm D&I:
 +
 +  * **Divide**: împărțim vectorul în doi sub-vectori de dimensiune n/2.
 +  * **Stăpânește**:​ aplicăm algoritmul de căutare binară pe sub-vectorul care conține valoarea căutată.
 +  * **Combină**:​ soluția sub-problemei devine soluția problemei inițiale, motiv pentru care nu mai este nevoie de etapa de combinare.
 +
 +=== Pseudocod ===
 +<code cpp>
 +BinarySearch(v,​ left, right, x) {             // functia returneaza pozitia x pe care se afla numarul x (oricare pozitie) sa 
 +       // vom cauta cat timp intervalul de cautare nu a fost inca epuizat (are cel putin un element)
 +       while (left <= right) {
 +            mid = (left + right) / 2          // mijlocul intervalului de cautare
 +             
 +            if (x == v[mid]) return mid;      // elementul cautat este cel din mijloc
 +            if (x < v[mid]) ​ right = mid - 1; // elementul cautat este mai mic decat cel din mijloc, ne mutam in prima jumatate
 +            if (x > v[mid]) ​ left  = mid + 1; // elementul cautat este mai mare decat cel din mijloc, ne mutam in a doua jumatate
 +        }
 +        ​
 +        return -1;                            // in acest punct ajungem daca si numai daca x nu a fost gasit
 +}
 +</​code>​
 +
 +=== Complexitate ===
 +  * **complexitate temporală** : $T = O(log (n))$
 +    * se deduce din recurența $T(n)=T(n/​2) + O(1)$  ​
 +  * **complexitate spațială** : $S=O(1)$
 +    * nu avem structuri de date complexe auxiliare
 +    * atragem atenția că acest algoritm se poate implementa și **recursiv**,​ caz în care complexitatea spațiala devine $O(log(n))$,​ întrucât salvăm pe stivă $O(log(n))$ parametri (întregi, referințe)
 +
 +==== Turnurile din Hanoi ====
 +=== Enunț ===
 +Se consideră 3 tije $S$ (**sursa**),​ $D$ (**destinatie**),​ $aux$ (**auxiliar**) şi n discuri de dimensiuni distincte ($1, 2, ..., n$ - ordinea crescătoare a dimensiunilor) situate inițial toate pe tija $S$ în ordinea $1,2,...,n$ (de la vârf către baza). ​
 +
 +Singura operație care se poate efectua este de a selecta un disc ce se află în vârful unei tije şi plasarea lui în vârful altei tije astfel încât să fie așezat deasupra unui disc de dimensiune mai mare decât a sa. 
 +
 +Să se găsească un algoritm prin care se mută toate discurile de pe tija $S$ pe tija $D$.(problema turnurilor din Hanoi).
 +
 +=== Soluție ===
 +Pentru rezolvarea problemei folosim următoarea strategie [[http://​www.mathcs.org/​java/​programs/​Hanoi/​index.html|[9]]]:​
 +
 +  * mutăm primele $n-1$ discuri de pe tija S pe tija aux folosindu-ne de tija D.
 +  * mutăm discul n pe tija D.
 +  * mutăm apoi cele $n-1$ discuri de pe tija aux pe tija D folosindu-ne de tija S.
 +
 +Ideea din spate este că avem mereu o singura sursă si o singură destinație să atingem un scop. Întotdeauna a 3-a tija va fi considerată auxiliară și poate fi folosită pentru a atinge scopul propus.
 +
 +=== Algoritm ===
 +<code cpp>
 +// muta n discuri de pe tija S pe tija D folosind tija aux
 +Hanoi(n, S, D, aux)  {
 +    if (n >= 1) {
 +        Hanoi(n - 1, S, aux, D);   // mut n-1 discuri de pe sursa (S) pe auxiliar (aux)
 +                                   // in aceasta subproblema sursa este S, destinatia este aux, intermediarul este D
 +                                   
 +        Muta_disc(S,​ D);           // acum pot muta direct discul n de pe sursa (S) pe destinatie (D)
 +        ​
 +        Hanoi(n - 1, aux, D, S);   // mut n-1 discuri de pe sursa (aux, aici sunt ele momentan) pe destinatie (D - scop final)
 +                                   // in aceasta subproblema,​ S este auxiliar, intrucat este tija libera
 +    }
 +}
 +</​code>​
 +
 +
 +=== Complexitate ===
 +  * **complexitate temporală** : $T(n) = O(2^n)$
 +    * se deduce din recurența $T(n)=2*T(n - 1) + O(1)$  ​
 +  * **complexitate spațială** : $S(n) = O(n)$
 +    * la un moment dat, nivelul maxim de recursivitate este n
 +
 +
 +==== ZParcurgere ====
 +=== Enunț ===
 +Gigel are o tablă pătratică de dimensiuni $2^n * 2^n$. Ar vrea să scrie pe pătrățelele tablei numere naturale cuprinse între $1$ si $2^n * 2^n$ conform unei parcurgeri mai deosebite pe care o numește **Z-parcurgere**. ​
 +
 +O Z-parcurgere vizitează recursiv cele patru cadrane ale tablei în ordinea: **stânga-sus**,​ **dreapta-sus**,​ **stânga-jos**,​ **dreapta-jos**.
 +
 +La un moment dat, Gigel ar vrea să știe ce **număr de ordine** trebuie să scrie conform Z-parcurgerii pe anumite pătrațele date prin coordonatele lor $( x, y )$. Gigel incepe umplerea tablei **întotdeauna** din colțul din stânga-sus.
 + 
 +<spoiler Exemplu 1>
 +$n = 1$ si $(x, y) = (2, 2)$
 +
 +Răspuns: $4$
 +
 +Explicație:​ Matricea arată ca în exemplul următor.
 +|1|2|
 +|3|4|
 +</​spoiler>​
 +
 +
 +<spoiler Exemplu 2>
 +$n = 2$ si $(x, y) = (3, 3)$
 +
 +Răspuns: $13$
 +
 +Explicație:​ Matricea arată ca în exemplul următor.
 +|1|2|5|6|
 +|3|4|7|8|
 +|9|10|13|14|
 +|11|12|15|16|
 +</​spoiler>​
 +
 +=== Soluție ===
 +Analizând modul în care se **completează** tabloul/​matricea din enunț, observăm că la fiecare etapa împărțim matricea (**problema**) în 4 submatrici (**4 subprobleme**). De asemenea, șirul de numere pe care dorim să il punem în matrice se împarte în 4 secvente, fiecare corespunzând unei submatrici.
 +
 +Observăm astfel că problema suportă **descompunerea** în **subprobleme disjuncte** și cu **structura similara**, ceea ce ne face să ne gândim la o soluție cu Divide et Impera.
 + 
 +
 +=== Complexitate ===
 +  * **complexitate temporală** : $T = O(n)$
 +    * $\log_{4} (2^n) = \frac{1}{2} \log _{2} (2^n) =  \frac{1}{2} n $  ​
 +  * **complexitate spatială** : $S=O(n)$
 +    * stocăm parametri pentru recursivitate ​
 +    * solutia se poate implementa si iterativ, caz in care $S = O(1)$; deoarece dimensiunile spațiului de căutare sunt $2^n * 2^n$, n este foarte mic ($n <= 15$), de aceea o solutie iterativa nu aduce nici un câștig **efectiv** în această situație.
 +
 +===== Concluzii =====
 +
 +Divide et impera este o tehnică folosită pentru a realiza soluții pentru o anumita clasă de probleme: acestea contin **subprobleme disjuncte** și cu **structură similară**. În cadrul acestei tehnici se disting trei etape: divide, stăpânește și combină.
 +
 +Mai multe exemple de algoritmi care folosesc tehnica divide et impera puteți găsi la [[http://​www.cs.berkeley.edu/​~vazirani/​algorithms/​chap2.pdf|[11]]].
 +
 +===== Exerciții =====
 +<​note>​
 +Scheletul de laborator se găsește pe pagina [[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​skel/​lab01|pa-lab::​skel/​lab01]].
 +</​note>​
 +
 +=== Count occurrences ===
 +Se dă un șir sortat **v** cu **n** elemente. Găsiți numărul de elemente egale cu **x** din șir.
 +
 +<spoiler Exemplu 1>
 +$n = 6$ si $x = 10$
 +|i|1|2|3|4|5|6|
 +|v|1|2|4|10|10|20|
 +
 +Răspuns: $2$
 +
 +Explicație:​ 10 apare de 2 ori in sir.
 +
 +</​spoiler>​
 +
 +Task-uri:
 +   * Aceasta problemă este deja rezolvată. Pentru a vă acomoda cu scheletul, va trebui sa faceți câțiva pași:
 +     * Rulați comanda $./​check.sh$ si cititi cum se foloseste checker-ul.
 +     * Rulați comanda necesara pentru a rula task-ul 1. Sursa nu implementeaza corect algoritmul si returneaza valori default. Din acest motiv primiti mesajul **WRONG ANSWER**.
 +     * Copiati următoarea sursă în folderul corespunzător. Rulați comanda anterioară. Observați mesajele afișate când ați rezolvat corect un task.
 +<spoiler Solutie C++>
 +Sursa **main.cpp** asociata cu task 1 este mai jos.
 +<code cpp>
 +#include <​bits/​stdc++.h>​
 +using namespace std;
 +
 +class Task {
 +public:
 +    void solve() {
 +        read_input();​
 +        print_output(get_result());​
 +    }
 +
 +private:
 +    int n, x;
 +    vector<​int>​ v;
 +
 +    void read_input() {
 +        ifstream fin("​in"​);​
 +        fin >> n;
 +        for (int i = 0, e; i < n; i++) {
 +            fin >> e;
 +            v.push_back(e);​
 +        }
 +        fin >> x;
 +        fin.close();​
 +    }
 +
 +    int find_first() {
 +        int left = 0, right = n - 1, mid, res = -1;
 +        while (left <= right) {
 +            mid = (left + right) / 2;
 +            if (v[mid] >= x) {
 +                res = mid;
 +                right = mid - 1;
 +            } else {
 +                left = mid + 1;
 +            }
 +        }
 +        return (res != -1 && v[res] == x) ? res : -1;
 +    }
 +
 +    int find_last() {
 +        int left = 0, right = n - 1, mid, res = -1;
 +        while (left <= right) {
 +            mid = (left + right) / 2;
 +            if (v[mid] <= x) {
 +                res = mid;
 +                left = mid + 1;
 +            } else {
 +                right = mid - 1;
 +            }
 +        }
 +        return (res != -1 && v[res] == x) ? res : -1;
 +    }
 +
 +    int get_result() {
 +        int first = find_first();​
 +        int last = find_last();​
 +        if (first == -1 || last == -1) {
 +            return 0;
 +        }
 +        return last - first + 1;
 +    }
 +
 +    void print_output(int result) {
 +        ofstream fout("​out"​);​
 +        fout << result;
 +        fout.close();​
 +    }
 +};
 +
 +// [ATENTIE] NU modifica functia main!
 +int main() {
 +    // * se aloca un obiect Task pe heap
 +    // (se presupune ca e prea mare pentru a fi alocat pe stiva)
 +    // * se apeleaza metoda solve()
 +    // (citire, rezolvare, printare)
 +    // * se distruge obiectul si se elibereaza memoria
 +    auto* task = new (std::​nothrow) Task{}; // hint: cppreference/​nothrow
 +    if (!task) {
 +        std::cerr << "new failed\n";​
 +        return -1;
 +    }
 +    task->​solve();​
 +    delete task;
 +    return 0;
 +}
 +</​code>​
 +</​spoiler>​
 +
 +<spoiler Solutie Java>
 +Sursa **Main.java** asociata cu task 1 este mai jos.
 +<code cpp>
 +import java.io.File;​
 +import java.io.IOException;​
 +import java.io.PrintWriter;​
 +import java.util.Scanner;​
 +
 +public class Main {
 + static class Task {
 + public final static String INPUT_FILE = "​in";​
 + public final static String OUTPUT_FILE = "​out";​
 +
 + int n;
 + int[] v;
 + int x;
 +
 + private void readInput() {
 + try {
 + Scanner sc = new Scanner(new File(INPUT_FILE));​
 + n = sc.nextInt();​
 + v = new int[n];
 + for (int i = 0; i < n; i++) {
 + v[i] = sc.nextInt();​
 + }
 + x = sc.nextInt();​
 + sc.close();​
 + } catch (IOException e) {
 + throw new RuntimeException(e);​
 + }
 + }
 +
 + private void writeOutput(int count) {
 + try {
 + PrintWriter pw = new PrintWriter(new File(OUTPUT_FILE));​
 + pw.printf("​%d\n",​ count);
 + pw.close();​
 + } catch (IOException e) {
 + throw new RuntimeException(e);​
 + }
 + }
 +
 + private int findFirst() {
 + int left = 0, right = n - 1, mid, res = -1;
 + while (left <= right) {
 + mid = (left + right) / 2;
 + if (v[mid] >= x) {
 + res = mid;
 + right = mid - 1;
 + } else {
 + left = mid + 1;
 + }
 + }
 + return (res != -1 && v[res] == x) ? res : -1;
 + }
 +
 + private int findLast() {
 + int left = 0, right = n - 1, mid, res = -1;
 + while (left <= right) {
 + mid = (left + right) / 2;
 + if (v[mid] <= x) {
 + res = mid;
 + left = mid + 1;
 + } else {
 + right = mid - 1;
 + }
 + }
 + return (res != -1 && v[res] == x) ? res : -1;
 + }
 +
 + private int getAnswer() {
 + int first = findFirst();​
 + int last = findLast();
 + if (first == -1 || last == -1) {
 + return 0;
 + }
 + return last - first + 1;
 + }
 +
 + public void solve() {
 + readInput();​
 + writeOutput(getAnswer());​
 + }
 + }
 +
 + public static void main(String[] args) {
 + new Task().solve();​
 + }
 +}
 +
 +</​code>​
 +</​spoiler>​
 +
 +   * Înțelegeți soluția oferită împreună cu asistentul vostru.
 +   * Care este complexitatea soluției (timp + spațiu)? De ce?
 +
 +=== SQRT ===
 +Se dă un număr real **n**. Scrieți un algoritm de complexitate **O(log n)** care să calculeze $sqrt(n)$ cu o precizie de $0.001$. ​
 +
 +<spoiler Exemplu 1>
 +$ n = 0.25 $
 +
 +Răspuns: orice valoare intre $0.499$ si $0.501$ (inclusiv)
 +</​spoiler>​
 +
 +<​note>​
 +Pentru a putea trece testele, trebuie sa afișați rezultatul cu **cel puțin ** 4 zecimale.
 +
 +</​note>​
 +
 +=== ZParcurgere ===
 +Rezolvați problema ZParcurgere folosind scheletul pus la dispoziție. Enunțul și explicațiile le găsiți în partea de seminar.
 +
 +=== Exponențiere logaritmică ===
 +Se dau două numere naturale **base** și  **exponent**. Scrieți un algoritm de complexitate $O(log (exponent))$ care să calculeze ${base} ^ {exponent} \ \% \  MOD $. 
 +
 +<​note>​
 +Întrucât expresia $ {base} ^ {exponent} $ este foarte mare, dorim să aflăm doar **restul** împărțirii lui la un număr **MOD**.
 +
 +
 +Proprietăți matematice necesare:
 +    * $(a + b)   \ \% \   MOD = ((a   \ \% \   MOD) + (b   \ \% \   ​MOD)) ​  \ \% \   MOD $ 
 +    * $(a \ * b)   \ \% \   MOD = ((a   \ \% \   MOD) \ * (b   \ \% \   ​MOD)) ​  \ \% \   MOD $ 
 +  ​
 +
 +Atenție la înmulțire! Rezultatul **temporar** poate provoca un overflow.
 +
 +Soluții:
 +  * **C++**: $a * b$ => $1LL * a * b$
 +  * **Java**: $a * b$ => $1L * a * b$
 +</​note>​
 +
 +<spoiler Exemplu 1>
 +$ base  = 2 $,  $ exponent = 10$, $MOD = 5$
 +
 +Răspuns: $4$
 +
 +Explicatie: $2^{10} \ \% \ 5 = 4$
 +</​spoiler>​
 +
 +
 +==== Bonus ====
 +
 +<spoiler Inversiuni>​
 +Se da un sir $S$ de n numere intregi. Sa se detemine cate inversiuni sunt in sirul dat. Numim inversiune o pereche de indici $1 < = i < j < = n$ astfel incat $S[i] > S[j]$
 +
 +<note tip>
 + ​Exemplu:​ in sirul ''​{0 1 9 4 5 7 6 8 2}''​ sunt 12 inversiuni.
 +</​note>​
 +
 +<​note>​
 +Aceasta problema nu are schelet, dar poate fi testată pe infoarena, la problema [[https://​www.infoarena.ro/​problema/​inv | Inv]].
 +</​note>​
 +
 +</​spoiler>​
 +
 +<spoiler ClassicTask>​
 +Testați soluția voastră de la **Exponentiere logaritmica** pe infoarena, la problema [[https://​infoarena.ro/​problema/​classictask | ClassicTask ]] (trebuie să modificați numele fișierelor).
 +
 +Identificați problema și modificați sursa astfel încât să luați punctaj maxim.
 +</​spoiler> ​
 +==== Extra ====
 +<spoiler Statistici de ordine>
 +Se dă un vector de numere întregi neordonate. Scriind o funcție de partitionare,​ folosiți **Divide et Impera** pentru
 +    * a determina a k-lea element ca mărime din vector
 +    * a sorta vectorii prin QuickSort
 +
 +Puteti testa problema partition [[https://​leetcode.com/​problems/​kth-largest-element-in-an-array/​description/​|aici]]. Problema [[https://​en.wikipedia.org/​wiki/​Quicksort|QuickSort]] (chiar si MergeSort) poate fi testata [[https://​infoarena.ro/​problema/​algsort|aici]].
 +
 +
 +<note tip>
 + ​Exemplu:​ pentru vectorul ''​{0 1 2 4 5 7 6 8 9}'',​ al 3-lea element ca ordine este 2, iar vectorul sortat este {0 1 2 4 5 6 7 8 9}
 +</​note>​
 +</​spoiler>​
 +
 +<spoiler Missing number>
 +Se dau $n - 1$ numere naturale distincte intre $0$ si $n - 1$. Scriind o functie de partitionare,​ determinati numarul lipsa.
 +
 +<note tip>
 + ​Exemplu:​ pentru $n = 9$ si vectorul ${0 1 9 4 5 7 6 8 2}$, numarul lipsa este $3$.
 +</​note>​
 +
 +</​spoiler>​
 +
 +
 +
 +<spoiler Fractal>
 +Puteți rezolva această problemă pe [[https://​infoarena.ro/​problema/​fractal| infoarena]].
 +</​spoiler>​
 +
 +<spoiler Hanoi>
 +Puteți rezolva această problemă pe [[https://​www.infoarena.ro/​problema/​hanoi| infoarena]].
 +</​spoiler>​
 +
 +<spoiler Secventa descrescatoare>​
 +Dându-se N numere întregi sub forma unei secvenţe de numere strict crescătoare,​ care se continuă cu o secvenţă de întregi strict descrescătoare,​ se doreşte determinarea punctului din întregul şir înaintea căruia toate elementele sunt strict crescătoare,​ şi dupa care, toate elementele sunt strict descrescătoare. Considerăm evident faptul că acest punct nu există dacă cele N numere sunt dispuse într-un şir fie doar strict crescător, fie doar strict descrescător.
 +
 +</​spoiler>​
 +
 +<spoiler K closest points to origin>
 +Puteți rezolva această problemă pe [[https://​leetcode.com/​problems/​k-closest-points-to-origin/​ | leetcode]]
 +</​spoiler>​
 +
 +<spoiler Merge k Sorted Lists>
 +Puteți rezolva această problemă pe [[https://​leetcode.com/​problems/​merge-k-sorted-lists/​ | leetcode]]
 +</​spoiler>​
 +
 +
 +
 +===== Referințe =====
 +
 +[0] Chapter **Divide-and-Conquer**,​ "​Introduction to Algorithms",​ Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein
 +
  
pa/tutoriale/dei.txt · Last modified: 2026/02/25 18:35 by darius.neatu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0