This shows you the differences between two versions of the page.
pa:laboratoare:laborator-04 [2018/04/02 14:03] darius.neatu |
pa:laboratoare:laborator-04 [2024/04/16 17:38] (current) radu.nichita |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laborator 4: Programare Dinamică (continuare) ====== | + | ====== Laborator 04: Programare Dinamică (2/2) ====== |
- | Responsabili: | + | |
- | * [[visanr95@gmail.com|Radu Vișan]] | + | |
- | * [[neatudarius@gmail.com|Darius Neațu]] | + | |
- | * [[cristb@gmail.com|Cristian Banu]] | + | |
- | * [[razvan.ch95@gmail.com|Răzvan Chițu]] | + | |
===== Obiective laborator ===== | ===== Obiective laborator ===== | ||
- | * Ințelegerea noțiunilor de bază despre programare dinamică | + | * Înțelegerea noțiunilor de bază despre programarea dinamică. |
- | * Insușirea abilităților de implementare a algoritmilor bazați programare dinamică. | + | * Însușirea abilităților de implementare a algoritmilor bazați pe programarea dinamică. |
- | ===== Precizari initiale ===== | + | ===== Precizări inițiale ===== |
<note> | <note> | ||
- | Toate exemplele de cod se gasesc in {{pa:new_pa:demo-lab04.zip}}. | + | Toate exemplele de cod se găsesc pe pagina [[https://github.com/acs-pa/pa-lab/tree/main/demo/lab04|pa-lab::demo/lab04]]. |
- | Acestea apar incorporate si in textul laboratorului pentru a facilita parcurgerea cursiva a laboratorului. | + | 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> | </note> | ||
- | * Toate bucatile de cod prezentate in partea introductiva a laboratorului (inainte de exercitii) 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 sa nu fie de ajuns pentru a compila codul. | + | * 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. |
- | * Va rugam sa incercati si codul din arhiva ** demo-lab04.zip**, inainte de a raporta ca ceva nu merge. :D | + | * Vă rugăm să compilați **DOAR** codul de pe GitHub. Pentru raportarea problemelor, contactați unul dintre maintaineri. |
- | * Pentru orice problema legata de continutul acestei pagini, va rugam sa dati email unuia dintre responsabili. | + | * Pentru orice problemă legată de conținutul acestei pagini, vă rugam să dați e-mail unuia dintre responsabili. |
===== Ce este DP? ===== | ===== Ce este DP? ===== | ||
- | Similar cu greedy, tehnica de programare dinamica este folosită pentru rezolvarea **problemelor de optimizare**. | + | Similar cu greedy, tehnica de programare dinamică este folosită pentru rezolvarea **problemelor de optimizare**. |
- | In continuare vom folosi acronimul **DP (dynamic programming)**. | + | În continuare vom folosi acronimul **DP (dynamic programming)**. |
- | De asemenea, DP se poate folosi si pentru probleme in care nu cautam un optim, cum ar fi **problemele de numarare**. | + | De asemenea, DP se poate folosi și pentru probleme în care nu căutam un optim, cum ar fi **problemele de numărare**. |
- | Pentru restul notiunilor prezentate pana acum despre DP, va rugam sa consulati pagina laboratorului 3. | + | Pentru noțiunile prezentate până acum despre DP, vă rugăm să consultați pagina laboratorului 3. |
===== Exemple clasice ===== | ===== Exemple clasice ===== | ||
- | Programarea Dinamică este cea mai flexibilă tehnica din programare. Cel mai ușor mod de a o înțelege presupune parcurgerea cât mai multor exemple. | + | Programarea Dinamică este cea mai flexibilă tehnică a programării. Cel mai ușor mod de a o înțelege presupune parcurgerea cât mai multor exemple. |
- | Propunem cateva categorii de recurente, pe care le vom grupa astfel: | + | Propunem câteva categorii de recurențe pe care le vom grupa astfel: |
- | * recurente de tip **SSM** (Subsecventa de Suma Maxima) | + | * recurențe de tip **SSM** (Subsecvență de Sumă Maximă) |
- | * recurente de tip **RUCSAC** | + | * recurențe de tip **RUCSAC** |
- | * recurente de tip **PODM** (Parantezare Optima de Matrici) | + | * recurențe de tip **PODM** (Parantezare Optimă de Matrice) |
- | * recurente de tip **numărat** | + | * recurențe de tip **numărat** |
- | * recurente pe **grafuri** | + | * recurențe pe **grafuri** |
<note> | <note> | ||
- | Pentru o problema data, este **posibil** sa gasim **mai multe recurente corecte **(mai multe solutii posibile). Evident, criteriul de alegere intre acestea va fi cel bazat pe complexitate. | + | Pentru o problemă dată, este **posibil** să găsim **mai multe recurențe corecte** (mai multe soluții posibile). Evident, criteriul de alegere între acestea va fi cel bazat pe complexitate. |
</note> | </note> | ||
===== Categoria 3: PODM ===== | ===== Categoria 3: PODM ===== | ||
- | Aceste recurente au o oarecare asemanare cu problema PODM (enunt + solutie). | + | Aceste recurențe au o oarecare asemănare cu problema PODM (enunț + soluție). |
- | ATENTIE! Acest tip de recurente poate fi mai greu (decat celelalte). Puteti consulta ** acasa **materialele puse la dispozitie pentru intelege mai bine aceasta categorie. | + | **ATENȚIE!** Acest tip de recurențe poate fi mai greu (decât celelalte). Puteți consulta **acasă** materialele puse la dispoziție pentru a înțelege mai bine această categorie. |
Caracteristici: | Caracteristici: | ||
- | * Acest tip de problema presupune ca o putem formula ca pe o problema de tip ** subinterval $[i, j]$**. | + | |
- | * Daca dorim sa gasim optimul pentru acest interval, va trebuie sa luam in calcul toate combinatiile de 2 subproblemele care ar fi putut genera solutie pentru probleme **$[i, j]$**. | + | * Acest tip de problemă presupune că o putem formula ca pe o problemă de tip **subinterval $[i, j]$**. |
- | * Se considera fiecare divizare in 2 subprobleme, data de intermediarul k | + | * Dacă dorim să găsim optimul pentru acest interval, va trebui să luăm în calcul toate combinațiile de 2 subprobleme care ar putea genera o soluție pentru problemele **$[i, j]$**. |
- | * **$[i, k]$** si **$[k + 1, j]$ ** sunt cele 2 subprobleme pentru care cunoastem solutiile | + | * Se consideră fiecare divizare în 2 subprobleme, dată de intermediarul k, astfel: |
- | * atunci o solutie pentru **$[i,j]$** se poate obtine imbinandu-le pe cele doua | + | * Fie **$[i, k]$** și **$[k + 1, j]$ ** cele 2 subprobleme pentru care cunoaștem soluțiile, atunci o soluție pentru **$[i,j]$** se poate obține îmbinându-le pe cele două |
- | * ca sa gasim solutia cea mai buna | + | * pentru a gasi soluția cea mai bună: |
* vom itera prin toate valorile k posibile | * vom itera prin toate valorile k posibile | ||
- | * vom alege pe cea care maximizeaza solutia problemei **$[i,j]$** | + | * o vom alege pe cea care maximizează soluția problemei **$[i,j]$** |
- | * Calculul se face de la intervale mici (probleme usoare - **$[i,i]$** sau **$[i, i+1]$**) spre probleme generale (dimensiune generala - ** $[i, j]$ **). In final se ajunge si la dimensiunile initiale (**$[1, n]$**). | + | * Calculul se face de la intervale mici (probleme ușoare - **$[i,i]$** sau **$[i, i+1]$**) spre probleme generale (dimensiune generală - ** $[i, j]$ **). În final, se ajunge și la dimensiunile inițiale (**$[1, n]$**). |
- | * Privind imaginea de ansamblu, adica cum se completeaza matricea, obervam ca matricea dp se completeaza **diagonala cu diagonala**. | + | * Privind imaginea de ansamblu, adică modul în care se completează matricea dp, observăm că aceasta se completează **diagonală cu diagonală**. |
==== Exemple clasice ==== | ==== Exemple clasice ==== | ||
=== PODM === | === PODM === | ||
- | == Enunt == | + | == Enunț == |
- | Fie un produs matricial $M = M_1 M_2 ... M_n$. Putem pune paranteze in mai multe moduri si vom obtine acelasi rezultat (inmultire asociativa), dar este posibil sa obtinem numar diferit de ** inmultiri scalare **. | + | Fie un produs matriceal $M = M_1 M_2 ... M_n$. Putem pune paranteze în mai multe moduri și vom obține același rezultat (înmulțire asociativă), dar este posibil să obținem un număr diferit de **înmulțiri scalare**. |
- | Matricea $M_i$ are (prin conventie), dimensiunile $d_{i-1} d_{i}$. | + | Matricea $M_i$ are (prin convenție), dimensiunile $d_{i-1} d_{i}$. |
- | == Cerinta == | + | == Cerință == |
- | Se cere sa se gaseasca o ** parantezare optima de matrice ** (PODM), adica sa se gaseasca o parantezare care sa minimizeze numarul de inmultiri scalare. | + | Se cere să se găsească o **parantezare optimă de matrice ** (PODM), adică să se găsească o parantezare care să minimizeze numărul de înmulțiri scalare. |
== Exemple == | == Exemple == | ||
Line 81: | Line 75: | ||
|d|2|3|4|5|| | |d|2|3|4|5|| | ||
- | Raspuns: ** 64 ** (inmultiri scalare) | + | Răspuns: ** 64 ** (înmulțiri scalare) |
- | Explicatie: Avem 3 matrici: | + | Explicație: Avem 3 matrice: |
* A de dimensiuni (2, 3) | * A de dimensiuni (2, 3) | ||
* B de (3, 4) | * B de (3, 4) | ||
* C de (4, 5) | * C de (4, 5) | ||
- | In functie de ordinea efectuarii inmultirilor matriciale , numarul total de inmultiri scalare poate sa fie foarte diferit: | + | În funcție de ordinea efectuării înmulțirilor matriceale, numărul total de înmulțiri scalare poate să fie foarte diferit: |
- | * $(AB)C$ => $24 + 40 = 64$ de inmultiri | + | * $(AB)C$ => $24 + 40 = 64$ de înmulțiri |
- | * explicatie: $X = (AB)$ genereaza $2 * 3 * 4 = 24$ inmultiri, $(XC)$ genereaza $2 * 4 * 5 = 40$ de inmultiri | + | * explicație: $X = (AB)$ generează $2 * 3 * 4 = 24$ înmulțiri, $(XC)$ generează $2 * 4 * 5 = 40$ de înmulțiri |
- | * $A(BC)$ => $60 + 30 = 90$ de inmultiri | + | * $A(BC)$ => $60 + 30 = 90$ de înmulțiri |
- | * explicatie: $X =(BC)$ genereaza $3 * 4 * 5 = 60$ inmultiri, $(AX)$ genereaza $2 * 3 * 5 = 30$ de inmultiri | + | * explicație: $X =(BC)$ generează $3 * 4 * 5 = 60$ înmulțiri, $(AX)$ generează $2 * 3 * 5 = 30$ de înmulțiri |
- | Rezultatul optim se obtine pentru cea de a treia parantezare: $(AB)C$. | + | Rezultatul optim se obține pentru prima parantezare: $(AB)C$. |
</spoiler> | </spoiler> | ||
Line 104: | Line 98: | ||
|d|2|3|4|2|3| | |d|2|3|4|2|3| | ||
- | Raspuns: ** 48 ** (inmultiri scalare) | + | Răspuns: ** 48 ** (înmulțiri scalare) |
- | Explicatie: Avem 4 matrici: | + | Explicație: Avem 4 matrice: |
* A de dimensiuni (2, 3) | * A de dimensiuni (2, 3) | ||
* B de (3, 4) | * B de (3, 4) | ||
Line 112: | Line 106: | ||
* D de (2, 3) | * D de (2, 3) | ||
- | In functie de ordinea efectuarii inmultirilor matriciale, numarul total de inmultiri scalare poate sa fie foarte diferit: | + | În funcție de ordinea efectuării înmulțirilor matriceale, numărul total de înmulțiri scalare poate să fie foarte diferit: |
- | * $(AB)C)D$ => $24 + 16 + 12 = 52$ inmultiri | + | * $(AB)C)D$ => $24 + 16 + 12 = 52$ înmulțiri |
- | * explicatie: $X = (AB)$ genereaza $2 * 3 *4 = 24$ inmultiri scalare, $Y = (XC)$ genereaza $2 * 4 * 2 = 16$ inmultiri scalare, $Z = YD$ genereaza $2 * 2 *3 = 12$ inmultiri scalare | + | * explicație: $X = (AB)$ generează $2 * 3 *4 = 24$ înmulțiri scalare, $Y = (XC)$ generează $2 * 4 * 2 = 16$ înmulțiri scalare, $Z = YD$ generează $2 * 2 *3 = 12$ înmulțiri scalare |
- | * $(A(BC))D$ => $24 + 12 + 12 = 48$ inmultiri | + | * $(A(BC))D$ => $24 + 12 + 12 = 48$ înmulțiri |
- | * explicatie: $X = (BC)$ genereaza $3 * 4 * 2 = 24$ inmultiri scalare, $Y = (AX)$ genereaza $ 2 * 3 * 2= 12$ inmultiri scalare, $Z = YD$ genereaza 2 * 2 * 3$ = 12$ inmultiri scalare | + | * explicație: $X = (BC)$ generează $3 * 4 * 2 = 24$ înmulțiri scalare, $Y = (AX)$ generează $ 2 * 3 * 2= 12$ înmulțiri scalare, $Z = YD$ generează 2 * 2 * 3$ = 12$ înmulțiri scalare |
- | * $(AB)(CD)$ => $ = $ inmultiri | + | * $(AB)(CD)$ => $ = $ inmulțiri |
- | * explicatie: $X = (AB)$ genereaza $2 * 3 * 4 = 24$ inmultiri scalare, $Y = (CD)$ genereaza $4 * 2 * 3 = $24 inmultiri scalare, $Z = XY$ genereaza $2 * 4 * 3 = 24$ inmultiri scalare | + | * explicație: $X = (AB)$ generează $2 * 3 * 4 = 24$ înmulțiri scalare, $Y = (CD)$ generează $4 * 2 * 3 = 24$ înmulțiri scalare, $Z = XY$ generează $2 * 4 * 3 = 24$ înmulțiri scalare |
- | * $A((BC)D)$ => $24 + 18 + 27 = 69$ inmultiri | + | * $A((BC)D)$ => $24 + 18 + 27 = 69$ înmulțiri |
- | * explicatie: $X = (BC)$ genereaza $3 * 4 * 2 = 24$ inmultiri scalare, $Y = (XD)$ genereaza $3 * 2 * 3 = 18$ inmultiri scalare, $Z = AY$ genereaza $3 * 3 * 3 = 27$ inmultiri scalare | + | * explicație: $X = (BC)$ generează $3 * 4 * 2 = 24$ înmulțiri scalare, $Y = (XD)$ generează $3 * 2 * 3 = 18$ înmulțiri scalare, $Z = AY$ generează $3 * 3 * 3 = 27$ înmulțiri scalare |
- | * $A(B(CD))$ => $24 + 36 + 18 = 78$ inmultiri | + | * $A(B(CD))$ => $24 + 36 + 18 = 78$ înmulțiri |
- | * explicatie: $X = (CD)$ genereaza $4 * 2 * 3 = 24$ inmultiri scalare, $Y = (BX)$ genereaza $3 * 4 * 3 =36 $ inmultiri scalare, $Z = AY$ genereaza $2 * 3 * 3 = 18$ inmultiri scalare | + | * explicație: $X = (CD)$ generează $4 * 2 * 3 = 24$ înmulțiri scalare, $Y = (BX)$ generează $3 * 4 * 3 = 36$ înmulțiri scalare, $Z = AY$ generează $2 * 3 * 3 = 18$ înmulțiri scalare |
- | Rezultatul optim se obtine pentru cea de a treia parantezare: $((A(BC))D)$. | + | Rezultatul optim se obține pentru cea de a treia parantezare: $((A(BC))D)$. |
</spoiler> | </spoiler> | ||
Line 133: | Line 127: | ||
|d|13|5|89|3|34| | |d|13|5|89|3|34| | ||
- | Raspuns: ** 2856 ** (inmultiri scalare) | + | Răspuns: ** 2856 ** (înmulțiri scalare) |
- | Explicatie: Avem 4 matrici: | + | Explicație: Avem 4 matrice: |
* A de dimensiuni (13, 5) | * A de dimensiuni (13, 5) | ||
* B de (5, 89) | * B de (5, 89) | ||
Line 141: | Line 135: | ||
* D de (3, 34) | * D de (3, 34) | ||
- | In functie de ordinea efectuarii inmultirilor matriciale , numarul total de inmultiri scalare poate sa fie foarte diferit: | + | În funcție de ordinea efectuării înmulțirilor matriciale, numărul total de înmulțiri scalare poate să fie foarte diferit: |
- | * $((AB)C)D$ => 10582 inmultiri | + | * $((AB)C)D$ => 10582 înmulțiri |
- | * $(AB)(CD)$ => 54201 inmultiri | + | * $(AB)(CD)$ => 54201 înmulțiri |
- | * $(A(BC))D$ => 2856 inmultiri | + | * $(A(BC))D$ => 2856 înmulțiri |
- | * $A((BC)D)$ => 4055 inmultiri | + | * $A((BC)D)$ => 4055 înmulțiri |
* ... | * ... | ||
- | Rezultatul optim se obtine pentru cea de a treia parantezare: $(A(BC))D$. | + | |
+ | Rezultatul optim se obține pentru cea de a treia parantezare: $(A(BC))D$. | ||
</spoiler> | </spoiler> | ||
== TIPAR == | == TIPAR == | ||
- | A fost descris in detaliu mai sus (cand s-a vorbit de categorie). | + | A fost descris în detaliu mai sus (când s-a vorbit de categorie). |
- | == Numire recurenta === | + | == Numire recurență === |
- | $dp[i][j]$ = **numarul minim de inmultiri scalare** cu care se poate obtine produsul $M_i * M_{i+1} * ... *{M_j}$ | + | $dp[i][j]$ = **numărul minim de înmulțiri scalare** cu care se poate obține produsul $M_i * M_{i+1} * ... *{M_j}$ |
+ | |||
+ | Răspunsul la problemă este **dp[1][n]** . | ||
- | == Gasire recurenta == | + | == Găsire recurență == |
- | * **Cazul de baza** : | + | * **Cazul de bază** : |
* $dp[i][i] = 0 $ | * $dp[i][i] = 0 $ | ||
- | * NU avem nici un efort daca nu avem ce inmulti. | + | * NU avem niciun efort dacă nu avem ce înmulți. |
* $dp[i][i+1] = d_{i-1} d_{i} d_{i+1}$ | * $dp[i][i+1] = d_{i-1} d_{i} d_{i+1}$ | ||
- | * Daca avem doua matrice, putem doar sa le inmultim. Nu are sens sa folosim paranteze. | + | * Dacă avem două matrice, putem doar să le înmulțim. Nu are sens să folosim paranteze. |
- | * Daca inmultim 2 matrice de dimensiuni $d_{i-1} * d_{i}$ si $d_{i} * d_{i + 1}$, avem costul $d_{i-1} d_{i} d_{i-1}$ | + | * Daca înmulțim 2 matrice de dimensiuni $d_{i-1} * d_{i}$ și $d_{i} * d_{i + 1}$, avem costul $d_{i-1} d_{i} d_{i+1}$ |
* **Cazul general**: $dp[i][j] = min(dp[i][k] + dp[k+1][j] + d_{i-1} d_{k} d_{j})$, unde $k = i : j - 1$ | * **Cazul general**: $dp[i][j] = min(dp[i][k] + dp[k+1][j] + d_{i-1} d_{k} d_{j})$, unde $k = i : j - 1$ | ||
- | * daca avem de efectuat sirul de inmultiri $M_i ... M_j$, atunci putem pune paranteze oriunde si sa facem inmultirile astfel $(M_i ... M_k) (M_{k+1} ... M_{j})$ | + | * dacă avem de efectuat șirul de înmulțiri $M_i ... M_j$, atunci putem pune paranteze oriunde și să facem înmulțirile astfel $(M_i ... M_k) (M_{k+1} ... M_{j})$ |
* costul minim pentru $(M_i ... M_k)$ este $dp[i][k]$ | * costul minim pentru $(M_i ... M_k)$ este $dp[i][k]$ | ||
* costul minim pentru $(M_{k+1} ... M_j)$ este $dp[k + 1][j]$ | * costul minim pentru $(M_{k+1} ... M_j)$ este $dp[k + 1][j]$ | ||
- | * vom avea in final de inmultit 2 matrice de dimensiune $d_{i-1} * d_{k}$ si $d_{k} * d_{j}$, operatie care are costul $d_{i-1}d_{k}d_{j}$ | + | * vom avea, în final, de înmulțit 2 matrice de dimensiune $d_{i-1} * d_{k}$ si $d_{k} * d_{j}$, operație care are costul $d_{i-1}d_{k}d_{j}$ |
- | * insumam cele 3 costuri intermediare | + | * însumăm cele 3 costuri intermediare |
== Implementare == | == Implementare == | ||
- | Puteti rezolva si testa problema PODM pe infoarena [[https://infoarena.ro/problema/podm|aici]]. | + | Puteți rezolva și testa problema PODM pe infoarena [[https://infoarena.ro/problema/podm|aici]]. |
- | Un exemplu de implementare in C++ se gaseste mai jos. | + | Un exemplu de implementare în C++ se găsește mai jos. |
<spoiler Implementare C++> | <spoiler Implementare C++> | ||
<code cpp> | <code cpp> | ||
- | // kInf este valoarea maxima - "infinitul" nostru | + | // INF este valoarea maximă - "infinitul" nostru |
- | const unsigned long long kInf = std::numeric_limits<unsigned long long>::max(); | + | const auto INF = std::numeric_limits<unsigned long long>::max(); |
// T = O(n ^ 3) | // T = O(n ^ 3) | ||
- | // S = O(n ^ 2) - stocam n x n intregi in tabloul dp | + | // S = O(n ^ 2) - stocăm n x n întregi în tabloul dp |
unsigned long long solve_podm(int n, const vector<int> &d) { | unsigned long long solve_podm(int n, const vector<int> &d) { | ||
- | // dp[i][j] = numarul MINIM inmultiri scalare cu codare poate fi calculat produsul | + | // dp[i][j] = numărul MINIM înmulțiri scalare cu codare, poate fi calculat produsul |
- | // matricial M_i * M_i+1 * ... * M_j | + | // matriceal M_i * M_i+1 * ... * M_j |
- | vector<vector<unsigned long long>> dp(n + 1, vector<unsigned long long> (n + 1, kInf)); | + | vector<vector<unsigned long long>> dp(n + 1, vector<unsigned long long> (n + 1, INF)); |
- | // Cazul de baza 1: nu am ce inmulti | + | // Cazul de bază 1: nu am ce înmulți |
for (int i = 1; i <= n; ++i) { | for (int i = 1; i <= n; ++i) { | ||
- | dp[i][i] = 0ULL; // 0 pe unsigned long long (voi folosi mai incolo si 1ULL) | + | dp[i][i] = 0ULL; // 0 pe unsigned long long (voi folosi mai încolo și 1ULL) |
} | } | ||
- | // Cazul de baza 2: matrice d[i - 1] x d[i] inmultia cu matrice d[i] x d[i + 1] | + | // Cazul de bază 2: matrice d[i - 1] x d[i] înmulțită cu matrice d[i] x d[i + 1] |
- | // (matrice pe pozitii consecutive) | + | // (matrice pe poziții consecutive) |
for (int i = 1; i < n; ++i) { | for (int i = 1; i < n; ++i) { | ||
dp[i][i + 1] = 1ULL * d[i - 1] * d[i] * d[i + 1]; | dp[i][i + 1] = 1ULL * d[i - 1] * d[i] * d[i + 1]; | ||
Line 200: | Line 197: | ||
// Cazul general: | // Cazul general: | ||
// dp[i][j] = min(dp[i][k] + dp[k + 1][j] + d[i - 1] * d[k] * d[j]), k = i : j - 1 | // dp[i][j] = min(dp[i][k] + dp[k + 1][j] + d[i - 1] * d[k] * d[j]), k = i : j - 1 | ||
- | for (int len = 2; len <= n; ++len) { // fixam lungimea intervalului (2, 3, 4, ...) | + | for (int len = 2; len <= n; ++len) { // fixăm lungimea intervalului (2, 3, 4, ...) |
- | for (int i = 1; i + len - 1 <= n; ++i) { // fixam capatul din stanga: i | + | for (int i = 1; i + len - 1 <= n; ++i) { // fixăm capătul din stânga: i |
- | int j = i + len - 1; // capatul din dreapta se deduce: j | + | int j = i + len - 1; // capătul din dreapta se deduce: j |
| | ||
- | // Iteram prin indicii dintre capete, spargand sirul de inmultiri in doua (paranteze). | + | // Iterăm prin indicii dintre capete, spărgând șirul de înmulțiri in două (paranteze). |
for (int k = i; k < j; ++k) { | for (int k = i; k < j; ++k) { | ||
// M_i * ... M_j = (M_i * .. * M_k) * (M_k+1 *... * M_j) | // M_i * ... M_j = (M_i * .. * M_k) * (M_k+1 *... * M_j) | ||
unsigned long long new_sol = dp[i][k] + dp[k + 1][j] + 1ULL * d[i - 1] * d[k] * d[j]; | unsigned long long new_sol = dp[i][k] + dp[k + 1][j] + 1ULL * d[i - 1] * d[k] * d[j]; | ||
| | ||
- | // actualizam solutia daca este mai buna | + | // actualizăm soluția dacă este mai bună |
dp[i][j] = min(dp[i][j], new_sol); | dp[i][j] = min(dp[i][j], new_sol); | ||
} | } | ||
Line 215: | Line 212: | ||
} | } | ||
- | // Rezultatul se afla in dp[1][n]: Numarul MINIM de inmultiri scalare | + | // Rezultatul se află în dp[1][n]: Numărul MINIM de inmultiri scalare |
- | // pe care trebuie sa le facem pentru a obtine produsul M_1 * ... * M_n | + | // pe care trebuie să le facem pentru a obține produsul M_1 * ... * M_n |
return dp[1][n]; | return dp[1][n]; | ||
Line 222: | Line 219: | ||
</code> | </code> | ||
<note> | <note> | ||
- | Sursa a fost scrisa pentru a fi testata pe infoarena. In cazul problemei [[https://infoarena.ro/problema/podm | PODM]], deoarece avem o suma de foarte multe produse, rezultatul este foarte mare. Pe infoarena se cerea ca rezultatul sa fie afisat asa cum e, garantandu-se ca incape pe 64 biti. | + | Sursa a fost scrisă pentru a fi testată pe infoarena. În cazul problemei [[https://infoarena.ro/problema/podm | PODM]], deoarece avem o sumă de foarte multe produse, rezultatul este foarte mare. Pe infoarena se cerea ca rezultatul să fie afișat asa cum e, garantându-se că încape pe 64 biți. |
</note> | </note> | ||
Line 228: | Line 225: | ||
</spoiler> | </spoiler> | ||
<note> | <note> | ||
- | Reamintim ca prin inmultirea/adunarea a doua variabile de tipul **int**, rezultatul poate sa nu incapa pe 32 biti. De aceea, in solutia prezentata, s-a facut cast pe 64biti. | + | Reamintim că prin înmulțirea/adunarea a două variabile de tipul **int**, rezultatul poate să nu încapă pe 32 biți. De aceea, în soluția prezentată, s-a făcut cast pe 64 biți. |
</note> | </note> | ||
<note> | <note> | ||
- | ATENTIE! La PA, in general, vom folosi conventia $ expresie \ \% \ kMod $, care va fi detaliata in capitolul urmatorul din acest laborator. | + | **ATENȚIE!** La PA, în general, vom folosi convenția $ expresie \ \% \ MOD $, care va fi detaliată în capitolul următor din acest laborator. |
</note> | </note> | ||
== Complexitate == | == Complexitate == | ||
- | Intrucat solutia presupune fixarea capetelor unui subinterval (i, j), apoi alegerea unui intermediar (k), complexitatea este data de aceste 3 cicluri. | + | Întrucat soluția presupune fixarea capetelor unui subinterval (i, j), apoi alegerea unui intermediar (k), complexitatea este dată de aceste 3 cicluri. |
- | * **complexitate temporala**: $T(n) = O(n^3)$ | + | * **complexitate temporală**: $T(n) = O(n^3)$ |
- | * **complexitate spatiala**: $S(n) = O(n^2)$ | + | * **complexitate spațială**: $S(n) = O(n^2)$ |
- | ===== Categoria 4: NUMARAT ===== | + | ===== Categoria 4: NUMĂRAT ===== |
- | Aceste recurente au o oarecare asemanare: | + | Aceste recurențe au o oarecare asemănare: |
- | * toate numara lucruri! :p | + | * toate numară lucruri! :p |
- | * interesante sunt cazurile cand numarul cautat este foarte mare (altfel am putea apela la alte metode - ex. generarea tuturor candidatilor posibili cu backtracking) | + | * interesante sunt cazurile când numărul căutat este foarte mare (altfel am putea apela la alte metode - ex. generarea tuturor candidaților posibili cu backtracking) |
- | * in acest caz, deoarece numarul poate sa nu incapa pe un tip reprezentabil standard (ex. int pe 32/64 de biti), se cere (de obicei) restul impartirii numarului cautat la un numar **MOD** (vom folosi in continuare aceasta notatie). | + | * în acest caz, deoarece numărul poate să nu încapă pe un tip reprezentabil standard (ex. int pe 32/64 de biți), se cere (de obicei) restul împarțirii numărului căutat la un număr **MOD** (vom folosi în continuare această notație). |
==== Sfaturi / Reguli ==== | ==== Sfaturi / Reguli ==== | ||
- | * cand cautati o recurenta pentru o problema de numarare trebuie sa aveti grija la doua aspecte: | + | * când căutați o recurență pentru o problema de numărare trebuie să aveți grijă la două aspecte: |
- | * 1) sa **NU** numarati acelasi obiect de doua ori. | + | * 1) să **NU** numărați același obiect de două ori. |
- | * 2) sa numarati toate obiectele in cauza. | + | * 2) să numărați toate obiectele în cauză. |
- | * de multe ori o problema de numarare implica o partitionare a **tuturor** posibilelor solutii dupa un anumit criteriu (relevant). Gasirea criteriului este partea esentiala pentru gasirea recurentei. | + | * de multe ori, o problemă de numărare implică o partiționare a **tuturor** posibilelor soluții după un anumit criteriu (relevant). Găsirea criteriului este partea esențială pentru aflarea recurenței. |
==== Regulile de lucru cu clase de resturi ==== | ==== Regulile de lucru cu clase de resturi ==== | ||
- | Reamintim cateva proprietati matematice pe care ar trebui sa le aveti in vedere atunci in implementati pentru obtine corect resturile pentru anumite expresii. (corect poate sa insemne, de exemplu, sa evitati overflow :D - lucru neintuitiv cateodata). | + | Reamintim câteva proprietăți matematice pe care ar trebui să le aveți în vedere atunci când implementați pentru a obține corect resturile anumitor expresii. (corect poate să însemne, de exemplu, să evitați overflow :D - lucru neintuitiv câteodată). |
- | * proprietati de de baza | + | * proprietăți de bază: |
* $(a + b) \ \% \ MOD = ((a \ \% \ MOD) + (b \ \% \ MOD)) \ \% \ MOD $ | * $(a + b) \ \% \ MOD = ((a \ \% \ MOD) + (b \ \% \ MOD)) \ \% \ MOD $ | ||
* $(a \ * b) \ \% \ MOD = ((a \ \% \ MOD) \ * (b \ \% \ MOD)) \ \% \ MOD $ | * $(a \ * b) \ \% \ MOD = ((a \ \% \ MOD) \ * (b \ \% \ MOD)) \ \% \ MOD $ | ||
- | * $(a - b) \ \% \ MOD = ((a \ \% \ MOD) - (b \ \% \ MOD) + MOD) \ \% \ MOD $ (restul nu poate fi ceva negativ; in C++ **%** nu functioneaza pe numere negative) | + | * $(a - b) \ \% \ MOD = ((a \ \% \ MOD) - (b \ \% \ MOD) + MOD) \ \% \ MOD $ (restul nu poate fi ceva negativ; în C++ **%** nu funcționează pe numere negative) |
* invers modular | * invers modular | ||
* $ \frac{a}{b} \ \% \ MOD = ((a \ \% \ MOD) * (b ^ {MOD-2} \ \% \ MOD)) \ \% \ MOD)$ | * $ \frac{a}{b} \ \% \ MOD = ((a \ \% \ MOD) * (b ^ {MOD-2} \ \% \ MOD)) \ \% \ MOD)$ | ||
- | * **DACA** MOD este prim; **DACA** a si b nu sunt multipli ai lui MOD | + | * **DACĂ** MOD este prim; **DACĂ** a și b nu sunt multipli ai lui MOD |
- | <spoiler Explicatii invers modular> | + | <spoiler Explicații invers modular> |
- | * ** definitie **: **b** este inversul modular a lui **a** in raport cu **MOD** daca $ a * b = 1 (modulo \ MOD)$ | + | * ** definiție **: **b** este inversul modular al lui **a** în raport cu **MOD** dacă $ a * b = 1 (modulo \ MOD)$ |
* ** utilizare **: $ \frac{a}{b} \ \% \ MOD = ((a \ \% \ MOD) * (invers(b) \ \% \ MOD)) \ \% \ MOD $ | * ** utilizare **: $ \frac{a}{b} \ \% \ MOD = ((a \ \% \ MOD) * (invers(b) \ \% \ MOD)) \ \% \ MOD $ | ||
- | * ** calculare **: deoarece la PA aceasta discutie are sens doar in contextul posibilitatii implementarii unei recurente DP in care folosim resturile doar pentru a evita overflow/imposibilitatea de a retine rezultatul pe tipurile standard de tip int (adica nu ne intereseaza sa dam o metoda generala pentru invers modular), vom simplifica problema - **MOD este prim!!!** | + | * ** calculare **: deoarece la PA această discuție are sens doar în contextul posibilității implementării unei recurențe DP în care folosim resturile doar pentru a evita overflow/imposibilitatea de a reține rezultatul pe tipurile standard de tip int (adică nu ne interesează să dăm o metoda generală pentru invers modular), vom simplifica problema - **MOD este prim!!!** |
- | * ** Mica teorema a lui Fermat**: Daca p este un numar prim si a este un număr intreg care nu este multiplu al lui p, atunci $a^{p-1} = 1 (modulo \ p)$. | + | * ** Mica teoremă a lui Fermat**: Dacă p este un număr prim și a este un număr întreg care nu este multiplu al lui p, atunci $a^{p-1} = 1 (modulo \ p)$. |
- | * din definitia inversului modular, reiese ca ** a ** si **b** nu sunt multipli ai lui **MOD** | + | * din definiția inversului modular, reiese că ** a ** și **b** nu sunt multipli ai lui **MOD** |
- | * introducand notatiile noastre in teorema si prelucrand obtinem | + | * introducând notațiile noastre în teoremă și prelucrând obținem |
* $a ^ {MOD - 1} = 1 (modulo \ MOD) <=> a * (a ^{MOD-2}) = 1 (modulo \ MOD)$ | * $a ^ {MOD - 1} = 1 (modulo \ MOD) <=> a * (a ^{MOD-2}) = 1 (modulo \ MOD)$ | ||
- | * deci inversul modular al lui a (in aceste conditii specifice) este $b = a ^ {MOD -2 }$ | + | * deci, inversul modular al lui a (în aceste condiții specifice) este $b = a ^ {MOD -2 }$ |
Line 276: | Line 273: | ||
<note> | <note> | ||
- | Reamintim ca prin inmultirea/adunarea a doua variabile de tipul int, rezultatul poate sa nu incapa pe 32 biti. E posibil sa trebuiasca sa combinam regulile cu resturi cu urmatoarele: | + | Reamintim că prin înmulțirea/adunarea a două variabile de tipul int, rezultatul poate să nu încapă pe 32 biți. E posibil să trebuiască să combinăm regulile de la resturi cu următoarele: |
* C++ | * C++ | ||
- | * **1LL / 1ULL** - constanta 1 pe 64 biti cu semn / fara semn | + | * **1LL / 1ULL** - constanta 1 pe 64 biti cu semn / făra semn |
- | * **1LL * a * b** - am grija ca rezultatul sa nu dea overflow si sa se stocheze direct pe 64biti (cu semn) | + | * **1LL * a * b** - am grijă ca rezultatul să nu dea overflow și să se stocheze direct pe 64 biți (cu semn) |
* Java | * Java | ||
- | * **1L** - constanta 1 pe 64biti cu semn (in Java nu exista unsigned types) | + | * **1L** - constanta 1 pe 64 biți cu semn (în Java nu există unsigned types) |
- | * **1L * a * b** - am grija ca rezultatul sa nu dea overflow si sa se stocheze direct pe 64biti (cu semn) | + | * **1L * a * b** - am grijă ca rezultatul să nu dea overflow și să se stocheze direct pe 64 biți (cu semn) |
</note> | </note> | ||
==== Gardurile lui Gigel ==== | ==== Gardurile lui Gigel ==== | ||
- | === Enunt === | + | === Enunț === |
- | Gigel trece de la furat obiecte cu un rucsac la numarat garduri (fiecare are micile lui placeri :D). El doreste sa construiasca un gard folosind in mod repetat **un singur tip de piesa**. | + | Gigel trece de la furat obiecte cu un rucsac la numărat garduri (fiecare are micile lui plăceri :D). El dorește să construiască un gard folosind în mod repetat **un singur tip de piesă**. |
- | O piesa are dimensiunile ** 4 x 1 ** (o unitate = 1m). Din motive irelevante pentru aceasta problema, orice gard construit trebuie sa aiba **inaltime 4m** in orice punct. | + | O piesă are dimensiunile ** 4 x 1 ** (o unitate = 1m). Din motive irelevante pentru această problema, orice gard construit trebuie să aibă **înălțimea 4m** în orice punct. |
- | O piesa poate fi pusa in pozitie **orizontala** sau in pozitie ** verticala**. | + | O piesă poate fi pusă în poziție **orizontală** sau în poziție **verticală**. |
- | === Cerinta === | + | === Cerință === |
- | Gigel se intreaba **cate garduri de lungime n** si inaltime 4 exista? Deoarece celalalt prenume al lui este Bulănel, el intuieste ca acest numar este foarte mare, de aceea va cere ** restul impartirii** acestui numar la **1009**. | + | Gigel se întreabă **câte garduri de lungime n și înălțime 4** există? Deoarece celălalt prenume al lui este Bulănel, el intuiește că acest număr este foarte mare, de aceea va cere ** restul împărțirii** acestui numar la **1009**. |
<spoiler Exemplu 123> | <spoiler Exemplu 123> | ||
Line 301: | Line 298: | ||
- | Raspuns: ** 1 ** (un singur gard) | + | Răspuns: **1** (un singur gard) |
- | Explicatie: Se poate forma un singur gard in fiecare caz, dupa cum este ilustrat si in figura **Garduri_123**. | + | Explicație: Se poate forma un singur gard în fiecare caz, după cum este ilustrat și în figura **Garduri_123**. |
</spoiler> | </spoiler> | ||
Line 312: | Line 309: | ||
$n = 4$ | $n = 4$ | ||
- | Raspuns: ** 2 ** | + | Răspuns: **2** |
- | Explicatie: Se pot forma 2 garduri, in functie de cum asezam piesele, dupa cum este ilustrat si in figura **Garduri_4**. | + | Explicație: Se pot forma 2 garduri, în funcție de cum așezăm piesele, după cum este ilustrat și în figura **Garduri_4**. |
- | Observam ca de fiecare daca cand punem o piesa in pozitie orizontala, de fapt suntem obligati sa punem 4 piese, una peste alta! | + | Observăm că de fiecare dată când punem o piesă în poziție orizontală, de fapt suntem obligați să punem 4 piese, una peste alta! |
</spoiler> | </spoiler> | ||
Line 324: | Line 321: | ||
$n = 5$ | $n = 5$ | ||
- | Raspuns: ** 3 ** | + | Răspuns: **3** |
- | Explicatie: Se pot forma 3 garduri, in functie de cum asezam piesele, dupa cum este ilustrat si in figura **Garduri_5**. | + | Explicație: Se pot forma 3 garduri, în funcție de cum așezăm piesele, după cum este ilustrat și în figura **Garduri_5**. |
- | * daca dorim ca acest gard sa se termine cu 4 piese in pozitie **orizontala** (una peste alta - marcat cu rosu), atunci la stanga mai ramane de completat **un subgard de lungime 1**, in toate modurile posibile | + | * dacă dorim ca acest gard să se termine cu 4 piese în poziție **orizontală** (una peste alta - marcat cu roșu), atunci la stânga mai ramane de completat **un subgard de lungime 1**, în toate modurile posibile |
- | * daca dorim ca acest gard sa se termine cu o piesa in pozitie **verticala** (marcat cu rosu), atunci la stanga mai ramane de completat **un subgard de lungime 4**, in toate modurile posibile | + | * dacă dorim ca acest gard să se termine cu o piesă în poziție **verticală** (marcat cu roșu), atunci la stânga mai rămâne de completat **un subgard de lungime 4**, în toate modurile posibile |
</spoiler> | </spoiler> | ||
Line 337: | Line 334: | ||
$n = 6$ | $n = 6$ | ||
- | Raspuns: ** 4 ** | + | Răspuns: **4** |
- | Explicatie: Se pot forma 4 garduri, in functie de cum asezam piesele, dupa cum este ilustrat si in figura **Garduri_6**. | + | Explicație: Se pot forma 4 garduri, în funcție de cum așezăm piesele, după cum este ilustrat și în figura **Garduri_6**. |
- | * daca dorim ca acest gard sa se termine cu o piesa in pozitie **verticala** (marcat cu rosu), atunci la stanga mai ramane de completat **un subgard de lungime 5**, in toate modurile posibile | + | * dacă dorim ca acest gard să se termine cu o piesă în poziție **verticală** (marcat cu roșu), atunci la stânga mai rămâne de completat **un subgard de lungime 5**, în toate modurile posibile |
- | * daca dorim ca acest gard sa se termine cu 4 piese in pozitie **orizontala** (una peste alta - marcat cu rosu), atunci la stanga mai ramane de completat **un subgard de lungime 2**, in toate modurile posibile | + | * dacă dorim ca acest gard să se termine cu 4 piese în poziție **orizontală** (una peste alta - marcat cu roșu), atunci la stânga mai ramane de completat **un subgard de lungime 2**, în toate modurile posibile |
</spoiler> | </spoiler> | ||
- | === Recurenta === | + | === Recurență === |
- | == Numire recurenta == | + | == Numire recurență == |
- | $dp[i] $ = numarul de garduri de lungime i si inaltime 4 (nimic special - exact ceeea ce se cere in enunt) | + | $dp[i] $ = numărul de garduri de lungime i și înălțime 4 (nimic special - exact ceea ce se cere în enunț) |
- | Raspunsul la problema este $dp[n]$. | + | Răspunsul la problemă este $dp[n]$. |
- | == Gasire recurenta == | + | == Găsire recurență == |
- | * **Caz de baza** | + | * **Caz de bază** |
* $dp[1] = dp[2] = dp[3] = 1$; $dp[4]$ = 2 | * $dp[1] = dp[2] = dp[3] = 1$; $dp[4]$ = 2 | ||
* ** Caz general ** | * ** Caz general ** | ||
- | * atunci dorim sa formam un gard de lungime i ($ i >= 5 $) am vazut ca putem alege cum sa punem ultima/ultimele piese | + | * atunci când dorim să formăm un gard de lungime i ($i >= 5$) am văzut că putem alege cum să punem ultima/ultimele piese |
- | * **DACA** alegem ca ultima piesa sa fie pusa in pozitie verticala, atunci la stanga mai ramane de completat **un subgard de lungime $i-1$** | + | * **DACĂ** alegem ca ultima piesă să fie pusă în poziție verticală, atunci la stânga mai rămâne de completat **un subgard de lungime $i-1$** |
- | * numarul de moduri in care putem face acest subgard este $dp[i-1]$ | + | * numărul de moduri în care putem face acest subgard este $dp[i-1]$ |
- | * **DACA** alegem ca ultima piesa sa fie in pozitie orizontala (de fapt punem 4 piese in pozitie orizontala), atunci la stanga mai ramane de completat **un subgard de lungime $i-4$** | + | * **DACĂ** alegem ca ultima piesă să fie în poziție orizontală (de fapt, punem 4 piese în poziție orizontală), atunci la stânga mai rămâne de completat **un subgard de lungime $i-4$** |
- | * numarul de moduri in care putem face acest subgard este $dp[i-4]$ | + | * numărul de moduri în care putem face acest subgard este $dp[i-4]$ |
* $dp[i] = (dp[i-1] + dp[i-4]) \ \% \ MOD$ | * $dp[i] = (dp[i-1] + dp[i-4]) \ \% \ MOD$ | ||
* | * | ||
- | <note> Asa cum am zis in sectiunea de [[http://ocw.cs.pub.ro/courses/pa/laboratoare/laborator-04?&#sfaturireguli|sfaturi si reguli]] vrem sa facem o **partionare** dupa un anumit **criteriu**, in cazul problemei de fata criteriul de partionare este daca gardul se termina cu o scandura verticala sau orizontala. | + | <note> Așa cum am zis în secțiunea de [[http://ocw.cs.pub.ro/courses/pa/laboratoare/laborator-04?&#sfaturireguli|sfaturi și reguli]] vrem să facem o **parționare** după un anumit **criteriu**: în cazul problemei de față, criteriul de parționare este dacă gardul se termină cu o scândură verticală sau orizontală. |
- | De asemenea tot in sectiunea [[http://ocw.cs.pub.ro/courses/pa/laboratoare/laborator-04?&#sfaturireguli|sfaturi si reguli]] am precizat ca nu vrem **sa numaram un obiect** (un mod de a construi gardul)** de doua ori**. Recurenta noastra (dp[i] = dp[i-1] + dp[i-4]) nu ia un obiect de doua ori pentru ca orice solutie care vine din dp[i-4] e diferita de alta care vine din dp[i-1] pentru ca difera in cel putin ultima scandura asezata) </note> | + | De asemenea, tot în secțiunea [[http://ocw.cs.pub.ro/courses/pa/laboratoare/laborator-04?&#sfaturireguli|sfaturi și reguli]] am precizat că nu vrem **să număram un obiect** (un mod de a construi gardul) **de două ori**. Recurența noastră (dp[i] = dp[i-1] + dp[i-4]) nu ia un obiect de două ori pentru că orice soluție care vine din dp[i-4] e diferită de alta care vine din dp[i-1] pentru că diferă în cel puțin ultima scândură așezată) </note> |
- | == Implementare recurenta == | + | == Implementare recurență == |
- | Aici puteti vedea un exemplu simplu de implementare in C++. | + | Aici puteți vedea un exemplu simplu de implementare în C++. |
- | <spoiler Implementare in C++> | + | <spoiler Implementare în C++> |
<code cpp> | <code cpp> | ||
#define MOD 1009 | #define MOD 1009 | ||
int gardurile_lui_Gigel(int n) { | int gardurile_lui_Gigel(int n) { | ||
- | // cazurile de baza | + | // cazurile de bază |
if (n <= 3) return 1; | if (n <= 3) return 1; | ||
if (n == 4) return 2; | if (n == 4) return 2; | ||
- | vector<int> dp(n + 1); // pastrez indexarea de la 1 ca in explicatii | + | vector<int> dp(n + 1); // păstrez indexarea de la 1 ca în explicații |
- | // cazurile de baza | + | // cazurile de bază |
dp[1] = dp[2] = dp[3] = 1; | dp[1] = dp[2] = dp[3] = 1; | ||
dp[4] = 2; | dp[4] = 2; | ||
Line 395: | Line 392: | ||
</code> | </code> | ||
- | Mentionez ca am folosit expresia $dp[i] = (dp[i - 1] + dp[i - 4]) \ \% \ MOD$ in loc de $dp[i] = ((dp[i - 1] \ \% \ MOD) + (dp[i - 4] \ \% \ MOD)) \ \% \ MOD$, deoarece [e valorile anterior calcule in dp a fost deja aplicata operatia $%$. | + | Menționez că am folosit expresia $dp[i] = (dp[i - 1] + dp[i - 4]) \ \% \ MOD$ în loc de $dp[i] = ((dp[i - 1] \ \% \ MOD) + (dp[i - 4] \ \% \ MOD)) \ \% \ MOD$, deoarece, pe valorile anterior calculate în dp, a fost deja aplicată operația $%$. |
- | Am plecat cu numerele $1, 1, 1, 2$ si la fiecare pas rezultatul stocat este $\ \% \ MOD$, deci tot ce este stocat **deja** in dp este un rest in raport cu MOD. NU mai era nevoie deci sa aplica **%** si pe termenii din paranteza. | + | Am plecat cu numerele $1, 1, 1, 2$ și, la fiecare pas, rezultatul stocat este $\ \% \ MOD$, deci, tot ce este stocat **deja** în dp este un rest în raport cu MOD. NU mai era nevoie, deci, să aplicăm **%** și pe termenii din paranteză. |
</spoiler> | </spoiler> | ||
== Complexitate == | == Complexitate == | ||
- | * **complexitate temporala**: $T = O(n)$ | + | * **complexitate temporală**: $T = O(n)$ |
- | * explicatie: avem o singura parcurgere in care construim tabloul dp | + | * explicație: avem o singură parcurgere în care construim tabloul dp |
- | * se poate obtine $T=O(log n)$ folosind exponentiere pe matrice! | + | * se poate obține $T=O(log n)$ folosind exponențiere pe matrice! |
- | * **complexitate spatiala**: $S = O(n)$ | + | * **complexitate spațială**: $S = O(n)$ |
- | * explicatie: stocam tabloul dp | + | * explicație: stocăm tabloul dp |
- | * se poate ontine $S = O(1)$ folosind exponentiere pe matrice! | + | * se poate obține $S = O(1)$ folosind exponențiere pe matrice! |
- | ===== Tehnici folosite in DP ===== | + | ===== Tehnici folosite în DP ===== |
- | De multe ori este nevoie sa folosim cateva tehnici pentru a obtine performanta maxima cu recurenta gasita. | + | De multe ori, este nevoie să folosim câteva tehnici pentru a obține performanța maximă cu recurența găsită. |
- | In laboratorul 3 se mentiona tehnica de memoizare (in prima parte a laboratorului). In acesta ne vom rezuma la cum putem folosi cunostintele de lucru matricial pentru a favoriza implementarea unor anumite tipuri de recurente. | + | În prima parte a laboratorului 3 se menționa tehnica de memoizare. În acesta, ne vom rezuma la cum putem folosi cunoștințele de lucru matriceal pentru a favoriza implementarea unor anumite tipuri de recurențe. |
- | ==== Exponentiere pe matrice pentru recurente liniare ==== | + | ==== Exponențiere pe matrice pentru recurențe liniare ==== |
- | === Recurente liniare=== | + | === Recurențe liniare === |
- | O recurenta liniara in contextul laboratorului de DP este de forma: | + | O recurență liniară, în contextul laboratorului de DP, este de forma: |
* $dp[i] = \sum_{k=1}^{KMAX} c_k * dp[i - k]$ | * $dp[i] = \sum_{k=1}^{KMAX} c_k * dp[i - k]$ | ||
- | * pentru **KMAX o constanta** | + | * pentru **KMAX o constantă** |
- | * de obicei, KMAX este foarte mica comparativ cu dimensinea n a problemei | + | * de obicei, KMAX este foarte mică comparativ cu dimensiunea n a problemei |
* $c_k$ constante reale (unele pot fi nule) | * $c_k$ constante reale (unele pot fi nule) | ||
- | O astfel de recurenta ar insemna ca pentru a calcula ** costul problemei i **, imbinam costurile problemelor $i - 1, i-2, ...., i-k$, fiecare contribuind cu un anumit coeficient $c_{1}, c_{2}, ..., c_{k}$. | + | O astfel de recurență ar însemna că, pentru a calcula **costul problemei i**, îmbinăm costurile problemelor $i - 1, i - 2, ...., i - k$, fiecare contribuind cu un anumit coeficient $c_{1}, c_{2}, ..., c_{k}$. |
- | <spoiler Complexitate recurente liniara> | + | <spoiler Complexitate recurențe liniară> |
- | Presupunand ca nu mai exista alte specificatii ale problemei si ca avand cele KMAX cazuri de baza (primele KMAX valori ar trebui stiute/deduse prin alte reguli), atunci un algoritm poate implementa recurenta de mai sus folosind 2 cicluri de tip for (for i = 1 : n, for k = 1 : KMAX ...). | + | Presupunând că nu mai există alte specificații ale problemei și că, având cele KMAX cazuri de bază, (primele KMAX valori ar trebui știute/deduse prin alte reguli), atunci un algoritm poate implementa recurența de mai sus folosind 2 cicluri de tip: for (for i = 1 : n, for k = 1 : KMAX ...). |
- | * **complexitatea temporala** : $ T =O(n * KMAX) = O(n) $ | + | * **complexitatea temporală** : $T = O(n * KMAX) = O(n)$ |
- | * reamintim ca acea valoarea KMAX este o constanta foarte mica in compartie cu n (ex. KMAX < 100) | + | * reamintim că acea valoare KMAX este o constantă foarte mică în comparație cu n (ex. KMAX < 100) |
- | * **complexitatea spatiala** : $ S = O(n) $ | + | * **complexitatea spațială** : $S = O(n)$ |
- | * am presupus ca avem nevoie sa retinem doar tabloul dp | + | * am presupus că avem nevoie să reținem doar tabloul dp |
</spoiler> | </spoiler> | ||
- | === Exponentiere pe matrice === | + | === Exponențiere pe matrice === |
- | Facem urmatoarele notatii: | + | Facem următoarele notații: |
* $S_i$ = starea la pasul i | * $S_i$ = starea la pasul i | ||
* $S_i = (dp[i - k + 1], dp[i - k + 2], ..., dp[i - 1], dp[i])$ | * $S_i = (dp[i - k + 1], dp[i - k + 2], ..., dp[i - 1], dp[i])$ | ||
- | * $S_k$ = starea initiala (in care cunoaste cele k cazuri de baza) | + | * $S_k$ = starea inițială (în care cunoaște cele k cazuri de bază) |
* $S_k = (dp[1], dp[2], ..., dp[k-1], dp[k])$ | * $S_k = (dp[1], dp[2], ..., dp[k-1], dp[k])$ | ||
- | * $C$ = matrice ce coeficienti constanti | + | * $C$ = matrice de coeficienți constanți |
* are dimensiune $KMAX * KMAX$ | * are dimensiune $KMAX * KMAX$ | ||
- | * putem pune constante in clar | + | * putem pune constante în clar |
- | * putem pune constantele $c_k$ care tin de problema curenta | + | * putem pune constantele $c_k$ care țin de problema curentă |
Line 448: | Line 445: | ||
== Algoritm naiv == | == Algoritm naiv == | ||
Putem formula problema astfel: | Putem formula problema astfel: | ||
- | * $S_k$ = este starea initiala | + | * $S_k$ = este starea inițială |
- | * pentru a obtine starea urmatoare, aplicam algoritmul urmator | + | * pentru a obține starea următoare, aplicăm algoritmul următor |
* $S_i = S_{i-1}C$ | * $S_i = S_{i-1}C$ | ||
| | ||
== Determinare C == | == Determinare C == | ||
- | Pentru a determina elementele matricei C, trebuie sa ne uitam la inmultirea matriceala de mai sus si sa alegem elementele lui C astfel incat prin inmultirealui $S_{i-1}$ cu $C$ sa obtinem elementele din $S_i$. | + | Pentru a determina elementele matricei C, trebuie să ne uităm la înmulțirea matriceală de mai sus și să alegem elementele lui C astfel încât prin înmulțirea lui $S_{i-1}$ cu $C$ să obținem elementele din $S_i$. |
\begin{gather} | \begin{gather} | ||
Line 472: | Line 469: | ||
- | <spoiler Explicatie determinare C> | + | <spoiler Explicație determinare C> |
- | * ultima coloana contine toti coeficientii $c_k$ intrucat $dp[i] = \sum_{k=1}^{KMAX} c_k * dp[i - k]$ | + | * ultima coloană conține toți coeficienții $c_k$ întrucât $dp[i] = \sum_{k=1}^{KMAX} c_k * dp[i - k]$ |
- | * celelalte coloane contin doar cate o valoare nenula | + | * celelalte coloane conțin doar câte o valoare nenulă |
- | * pe coloana j vom avea valoarea 1 pe linia $j+1$ ($j = 1 : KMAX - 1$) | + | * pe coloana j vom avea valoarea 1 pe linia $j + 1$ ($j = 1 : KMAX - 1$) |
- | * cum obtinem, de exemplu, $dp[i - 1]$? | + | * cum obținem, de exemplu, $dp[i - 1]$? |
- | * pai avem $dp[i-1]$ chiar si in starea $S_{i-1}$, deci trebuie sa il copiam in starea $S_i$ | + | * păi, avem $dp[i-1]$ chiar și în starea $S_{i-1}$, deci trebuie să îl copiem în starea $S_i$ |
- | * copierea se realizeaza prin inmultirea cu 1 | + | * copierea se realizează prin inmulțirea cu 1 |
- | * daca $dp[i-1]$ era pe ultima pozitiei (pozitia k) in starea $S_{i-1}$, in noua stare $S_i$ este pe penultima pozitie (pozitia $k-1$) | + | * dacă $dp[i-1]$ era pe ultima poziție (poziția k) în starea $S_{i-1}$, în noua stare $S_i$ este pe penultima poziție (poziția $k-1$) |
- | * deci s-a deplasat la stanga cu o pozitie! | + | * deci s-a deplasat la stânga cu o poziție! |
- | * in noua stare, noua pozitie este deplasata cu o unitate la stanga fata de starea precedenta | + | * în noua stare, noua poziție este deplasată cu o unitate la stânga față de starea precedentă |
- | * de aceea pe coloana $j$, vrem sa avem elementul 1 pe linia $j + 1$ ($j = 1 : KMAX - 1$) | + | * de aceea, pe coloana $j$, vrem să avem elementul 1 pe linia $j + 1$ ($j = 1 : KMAX - 1$) |
- | * cand inmultim $S_{i-1}$ cu coloana $C_j$ **dorim sa** | + | * când înmulțim $S_{i-1}$ cu coloana $C_j$ **dorim să** |
- | * ce copiam? | + | * ce copiem? |
- | * valoarea $dp[i - KMAX + j]$ din $S_{i-1}$ in $S_{i}$ | + | * valoarea $dp[i - KMAX + j]$ din $S_{i-1}$ în $S_{i}$ |
- | * adica sa copiam a j-a valoare de pe linie | + | * adică să copiem a j-a valoare de pe linie |
- | * unde copiam? | + | * unde copiem? |
- | * de pe pozitia $j + 1$ pe pozitia $j$ | + | * de pe poziția $j + 1$ pe poziția $j$ |
</spoiler> | </spoiler> | ||
- | == Exponentiere logaritmica pe matrice == | + | == Exponențiere logaritmică pe matrice == |
- | Algoritmul naiv de mai sus are dezavantajul ca are tot o complexitate temporala $O(n)$. | + | Algoritmul naiv de mai sus are dezavantajul că are tot o complexitate temporală $O(n)$. |
- | Sa executam cativa pasi de inductie pentru a vedea cum este determinat $S_i$. | + | Să executăm câțiva pași de inducție pentru a vedea cum este determinat $S_i$. |
$$S_i = S_{i-1}C$$ | $$S_i = S_{i-1}C$$ | ||
$$S_i = S_{i-2}C^2$$ | $$S_i = S_{i-2}C^2$$ | ||
Line 502: | Line 499: | ||
- | In laboratorul 2 (Divide et Impera) am invatat ca putem calcula $ x ^ n $ in timp logaritmic. Deoarece si inmultirea matricilor este asociativa, putem calcula $C ^ n$ in timp logaritmic. | + | În laboratorul 2 (Divide et Impera) am învățat că putem calcula $x ^ n$ în timp logaritmic. Deoarece și înmulțirea matricilor este asociativă, putem calcula $C ^ n$ in timp logaritmic. |
- | Obtinem astfel o solutie cu urmatoarele complexitati: | + | Obținem astfel o soluție cu următoarele complexități: |
- | * ** complexitate temporala **: $T = O(KMAX^3 * log(n))$ | + | * ** complexitate temporală **: $T = O(KMAX^3 * log(n))$ |
- | * explicatie | + | * explicație |
- | * facem doar $O(log n)$ pasi, dar un pas implica inmultire de matrice | + | * facem doar $O(log n)$ pași, dar un pas implică înmulțire de matrice |
- | * o inmultire de matrice patratica de dimensiune KMAX are $KMAX^3$ operatii | + | * o înmulțire de matrice patrătică de dimensiune KMAX are $KMAX^3$ operații |
- | * aceasta metoda este eficienta cand $KMAX << n$ (KMAX este mult mai mic decat n) | + | * această metodă este eficientă când $KMAX << n$ (KMAX este mult mai mic decât n) |
- | * ** complexitatea spatiala **: $S = O(KMAX^3)$ | + | * ** complexitatea spațială **: $S = O(KMAX^2)$ |
- | * explicatie | + | **Observație!** În ultimele calcule nu am șters constanta KMAX, întrucât apare la puterea a 2-a! $KMAX = 1000$ implică $KMAX^2 = 10^6$, valoare care nu mai poate fi ignorată în practică ($KMAX^2$ poate fi comparabil cu n). |
- | * este nevoie sa stocam cateva matrici | + | |
- | Observatie! In ultimele calcule nu am sters contanta KMAX, intrucat apare la puterea a 3-a! $KMAX = 100$ implica $KMAX^3 = 10^6$, valoare care nu mai poate fi ignorata in practica ($KMAX^3$ poate fi comparabil cu n). | + | |
=== Gardurile lui Gigel (optimizare) === | === Gardurile lui Gigel (optimizare) === | ||
- | Dupa cum am vazut mai sus, in problema cu garduri data de Gigel solutia este o recurenta liniara: | + | După cum am văzut mai sus, în problema cu garduri dată de Gigel, soluția este o recurență liniară: |
* $dp[1] = dp[2] = dp[3] = 1$; $d[4]=2$; | * $dp[1] = dp[2] = dp[3] = 1$; $d[4]=2$; | ||
* $dp[i] = dp[i - 1] + dp[i - 4]$, pentru $i > 4$ | * $dp[i] = dp[i - 1] + dp[i - 4]$, pentru $i > 4$ | ||
- | == Exponentiere rapida :p == | + | == Exponențiere rapidă == |
* $ k = 4 $ | * $ k = 4 $ | ||
- | * $S_4 = (dp[1], dp[2], dp[3], dp[4]) = (1, 1, 1, 4)$ | + | * $S_4 = (dp[1], dp[2], dp[3], dp[4]) = (1, 1, 1, 2)$ |
* $S_i = (dp[i-3], dp[i-2], dp[i-1], dp[i])$ | * $S_i = (dp[i-3], dp[i-2], dp[i-1], dp[i])$ | ||
- | * Raspunsul se afla efectuand operatia $S_n = S_4 * C^{n - 4}$, unde C are urmatorul continut: | + | * Răspunsul se află efectuând operația $S_n = S_4 * C^{n - 4}$, unde C are următorul conținut: |
\begin{gather} | \begin{gather} | ||
C = \begin{bmatrix} | C = \begin{bmatrix} | ||
Line 534: | Line 529: | ||
\end{gather} | \end{gather} | ||
- | <spoiler Implementare in C++> | + | <spoiler Implementare în C++> |
- | Mai jos se afla o implementare simplista in C++ care cuprinde toate etapele pe care trebuie sa le realizati in cod, dupa ce stiti cum arata recurenta sub forma matriceala. | + | Mai jos se află o implementare simplistă în C++ care cuprinde toate etapele pe care trebuie să le realizați în cod, după ce știți cum arată recurența sub forma matriceală. |
<code cpp> | <code cpp> | ||
Line 549: | Line 544: | ||
for (int i = 0; i < KMAX; ++i) { | for (int i = 0; i < KMAX; ++i) { | ||
for (int j = 0; j < KMAX; ++j) { | for (int j = 0; j < KMAX; ++j) { | ||
- | unsigned long long sum = 0; // presupun ca suma incape pe 64 de biti | + | unsigned long long sum = 0; // presupun că suma încape pe 64 de biți |
for (int k = 0; k < KMAX; ++k) { | for (int k = 0; k < KMAX; ++k) { | ||
Line 576: | Line 571: | ||
if (p % 2 == 0) { | if (p % 2 == 0) { | ||
multiply_matrix(C, C, C); // C = C*C | multiply_matrix(C, C, C); // C = C*C | ||
- | p /= 2; // ramane de calculat C^(p/2) | + | p /= 2; // rămâne de calculat C^(p/2) |
} else { | } else { | ||
// reduc la cazul anterior: | // reduc la cazul anterior: | ||
multiply_matrix(tmp, C, tmp); // tmp = tmp*C | multiply_matrix(tmp, C, tmp); // tmp = tmp*C | ||
- | --p; // ramane de calculat C^(p-1) | + | --p; // rămâne de calculat C^(p-1) |
} | } | ||
} | } | ||
- | // avem o parte din rezultat in C si o parte in tmp | + | // avem o parte din rezultat în C și o parte în tmp |
multiply_matrix(C, tmp, R); // rezultat = tmp * C | multiply_matrix(C, tmp, R); // rezultat = tmp * C | ||
} | } | ||
int garduri_rapide(int n) { | int garduri_rapide(int n) { | ||
- | // cazurile de baza | + | // cazurile de bază |
if (n <= 3) return 1; | if (n <= 3) return 1; | ||
if (n == 4) return 2; | if (n == 4) return 2; | ||
Line 598: | Line 593: | ||
{0, 1, 0, 0}, | {0, 1, 0, 0}, | ||
{0, 0, 1, 1}}; | {0, 0, 1, 1}}; | ||
- | // vreau sa aplic formula S_n = S_4 * C^(n-4) | + | // vreau să aplic formula S_n = S_4 * C^(n-4) |
// C = C^(n-4) | // C = C^(n-4) | ||
power_matrix(C, n - 4, C); | power_matrix(C, n - 4, C); | ||
- | // sol = S_4 * C = dp[n] (se afla pe ultima pozitie din S_n, | + | // sol = S_4 * C = dp[n] (se află pe ultima poziție din S_n, |
- | // deci voi folosi ultima coloana din C) | + | // deci voi folosi ultima coloană din C) |
int sol = 1 * C[0][3] + 1 * C[1][3] + 1 * C[2][3] + 2 * C[3][3]; | int sol = 1 * C[0][3] + 1 * C[1][3] + 1 * C[2][3] + 2 * C[3][3]; | ||
return sol % MOD; | return sol % MOD; | ||
Line 612: | Line 607: | ||
<note> | <note> | ||
- | Remarcati faptul ca in functia de inmultire se foloseste o matrice temporara $tmp$. Motivul este ca vrem sa apelam functia $multiply(C, C, C)$, unde C joaca atat rol de intrare cat si de iesire. Daca am pune rezultatele direct in C, atunci am strica inputul inainte sa obtinem rezultatul. | + | Remarcați faptul că în funcția de înmulțire se folosește o matrice temporară $tmp$. Motivul este că vrem să apelăm funcția $multiply(C, C, C)$, unde C joacă atât rol de intrare cât și de ieșire. Dacă am pune rezultatele direct in C, atunci am strica inputul înainte să obținem rezultatul. |
- | Putem spune ca acea functie este **matrix_multiply_safe**, in sensul ca pentru orice A,B,C care respecta dimensiunile impuse, functia va calcula corect produsul. | + | Putem spune că acea funcție este **matrix_multiply_safe**, în sensul că pentru orice A,B,C care respectă dimensiunile impuse, funcția va calcula corect produsul. |
</note> | </note> | ||
</spoiler> | </spoiler> | ||
- | <spoiler Comparatie solutii (studiu de caz pentru curiosi)> | + | <spoiler Comparație solutii (studiu de caz pentru curioși)> |
- | In arhiva ** demo-lab04.zip ** gasiti o sursa completa in care se realizeaza: | + | Pe git găsiți o sursă completă în care se realizează: |
- | * o verificare a faptului ca cele 2 implementari (** gardurile_lui_Gigel** si **garduri_rapide**) produc aceleasi rezultate | + | * o verificare a faptului că cele 2 implementări (** gardurile_lui_Gigel** și **garduri_rapide**) produc aceleași rezultate |
- | * un benchmark in care cele 2 implementari sunt comparate | + | * un benchmark în care cele 2 implementări sunt comparate |
- | * pe sistem uzual (laptop) s-au obtinut urmatoarele rezulate: | + | * pe un sistem uzual (laptop) s-au obținut următoarele rezulate: |
<code bash> | <code bash> | ||
- | test case: varianta simpla | + | test case: varianta simplă |
n = 100000000 sol = 119; time = 0.984545 s | n = 100000000 sol = 119; time = 0.984545 s | ||
- | test case: varianta rapida | + | test case: varianta rapidă |
n = 100000000 sol = 119; time = 0.000021 s | n = 100000000 sol = 119; time = 0.000021 s | ||
- | test case: varianta simpla | + | test case: varianta simplă |
n = 1000000000 sol = 812; time = 9.662377 s | n = 1000000000 sol = 812; time = 9.662377 s | ||
- | test case: varianta rapida | + | test case: varianta rapidă |
n = 1000000000 sol = 812; time = 0.000022 s | n = 1000000000 sol = 812; time = 0.000022 s | ||
</code> | </code> | ||
- | * se observa clar diferenta intre cele 2 solutii (am confirmat ceea ce spunea si teoria: $O(n) $ vs $O(log(n))$); aceasta tehnica imbunatateste drastic o solutie gasita relativ usor. | + | * se observă clar diferența între cele 2 soluții (am confirmat ceea ce spunea și teoria: $O(n) $ vs $O(log(n))$); această tehnică îmbunătățește drastic o soluție gasită relativ usor. |
</spoiler> | </spoiler> | ||
- | ===== Exercitii ===== | + | ===== Exerciții ===== |
<note> | <note> | ||
- | In acest laborator vom folosi scheletul de laborator din arhiva {{pa:new_pa:skel-lab04.zip}}. | + | Scheletul de laborator se găsește pe pagina [[https://github.com/acs-pa/pa-lab/tree/main/skel/lab04|pa-lab::skel/lab04]]. |
</note> | </note> | ||
- | |||
=== DP or math? === | === DP or math? === | ||
- | Fie un sir de **numere naturale strict pozitive**. Cate **subsiruri** (submultimi nevide) au suma numerelor **para**? | + | Fie un șir de **numere naturale strict pozitive**. Câte **subșiruri** (submulțimi nevide) au suma numerelor **pară**? |
<note> | <note> | ||
- | **subsir** (**subsequence** in engleza) pentru un vector ** v **inseamna un alt vector $u = [v[i_1], v[i_2],..., v[i_k]]]$ unde $i_1 < i_2 < ... < i_k$. | + | **subșir** (**subsequence** în engleză) pentru un vector **v** înseamnă un alt vector $u = [v[i_1], v[i_2],..., v[i_k]]]$ unde $i_1 < i_2 < ... < i_k$. |
</note> | </note> | ||
Task-uri: | Task-uri: | ||
- | * Se cere o **solutie folosind DP**. | + | * Se cere o **soluție folosind DP**. |
- | * Inspectand recurenta gasita la punctul precedent, incercati sa o inlocuiti cu o **formula matematica**. | + | * Inspectând recurența gasită la punctul precedent, încercați să o înlocuiți cu o **formulă matematică**. |
- | * Care este **complexitatea** pentru fiecare solutie (timp + spatiu)? Care este mai buna? De ce? :D | + | * Care este **complexitatea** pentru fiecare soluție (timp + spațiu)? Care este mai bună? De ce? :D |
- | Deoarece rezultatul poate fi prea mare, se cere **restul impartirii** lui la $1000000007$ ($10^9 + 7$). | + | Deoarece rezultatul poate fi prea mare, se cere **restul împărțirii** lui la $1000000007$ ($10^9 + 7$). |
- | Pentru punctaj maxim pentru aceasta problema, este necesar sa rezolvati toate subpunctele (ex. nu folositi direct formula, gasiti mai intai recurenta DP). Trebuie sa implementati **cel putin** solutia cu DP. | + | Pentru punctaj maxim pentru această problemă, este necesar să rezolvați toate subpunctele (ex. nu folosiți direct formula, găsiți mai întâi recurența DP). Trebuie să implementați **cel puțin** soluția cu DP. |
Line 668: | Line 662: | ||
|v|2|6|4| | |v|2|6|4| | ||
- | Raspuns: $7$ | + | Răspuns: $7$ |
- | Explicatie: Toate subsirule posibile sunt | + | Explicație: Toate subșirurile posibile sunt |
* $[2]$ | * $[2]$ | ||
* $[2, 6]$ | * $[2, 6]$ | ||
Line 678: | Line 672: | ||
* $[6, 4]$ | * $[6, 4]$ | ||
* $[4]$ | * $[4]$ | ||
- | Toate subsirurile de mai sus au suma para. | + | Toate subșirurile de mai sus au suma pară. |
</spoiler> | </spoiler> | ||
Line 687: | Line 681: | ||
|v|2|1|3| | |v|2|1|3| | ||
- | Raspuns: $3$ | + | Răspuns: $3$ |
- | Explicatie: Toate subsirule posibile sunt | + | Explicație: Toate subșirurile posibile sunt |
* $[2]$ | * $[2]$ | ||
* $[2, 1]$ | * $[2, 1]$ | ||
Line 698: | Line 692: | ||
* $[3]$ | * $[3]$ | ||
- | Subsirurile cu suma para sunt: $[2]$, $[2, 1, 3]$, $[1, 3]$. | + | Subșirurile cu sumă pară sunt: $[2]$, $[2, 1, 3]$, $[1, 3]$. |
</spoiler> | </spoiler> | ||
Line 708: | Line 702: | ||
|v|3|2|1| | |v|3|2|1| | ||
- | Raspuns: $3$ | + | Răspuns: $3$ |
- | Explicatie: Toate subsirule posibile sunt | + | Explicație: Toate subșirurile posibile sunt |
* $[3]$ | * $[3]$ | ||
* $[3, 2]$ | * $[3, 2]$ | ||
Line 719: | Line 713: | ||
* $[1]$ | * $[1]$ | ||
- | Subsirurile cu suma para sunt: $[3, 2, 1]$, $[3, 1]$, $[2]$. | + | Subșirurile cu sumă pară sunt: $[3, 2, 1]$, $[3, 1]$, $[2]$. |
</spoiler> | </spoiler> | ||
<note> | <note> | ||
- | Morala: exista probleme pentru care gasim o solutie cu DP, dar pentru care poate exista si alte solutii mai bune (am ignorat citirea). | + | Morala: există probleme pentru care găsim o soluție cu DP, dar pentru care pot exista și alte soluții mai bune (am ignorat citirea/afișarea). |
- | In problemele de numarat, exista o **sansa** buna sa putem gasi (si) o formula matematica, care poate fi implementata intr-un mod mai eficient decat o recurenta DP. | + | În problemele de numărat, există o **șansă** bună să putem găsi (și) o formulă matematică, ce poate fi implementată într-un mod mai eficient decât o recurență DP. |
</note> | </note> | ||
<spoiler Hint> | <spoiler Hint> | ||
- | Dar cate subsiruri au suma **impara**? | + | Câte subșiruri au suma **impară**? |
</spoiler> | </spoiler> | ||
- | <spoiler Solutie> | + | <hidden> |
- | Problema este preluata de [[https://infoarena.ro/problema/azerah|aici]]. Solutia se gaseste [[https://www.infoarena.ro/onis-2015/solutii-runda-1#azerah|aici]]. | + | <spoiler Soluție> |
+ | Problema este preluată de [[https://infoarena.ro/problema/azerah|aici]]. Soluția se găsește [[https://www.infoarena.ro/onis-2015/solutii-runda-1#azerah|aici]]. | ||
</spoiler> | </spoiler> | ||
+ | </hidden> | ||
- | === Expresie booleana === | + | === Expresie booleană === |
- | Se da o expresie booleana corecta cu n termeni. Fiecare din termeni poate fi unul din stringurile **true**, **false**, **and**, **or**, **xor**. | + | Se dă o expresie booleană corectă cu n termeni. Fiecare din termeni poate fi unul din stringurile **true**, **false**, **and**, **or**, **xor**. |
- | Numarati modurile in care se pot aseza paranteze astfel incat rezultatul sa fie **true**. Se respecta regulile de la logica (tabelele de adevar pentru operatiile **and**, **or**, **xor**). | + | Numărați modurile în care se pot așeza paranteze astfel încât rezultatul să fie **true**. Se respectă regulile de la logică (tabelele de adevăr pentru operațiile **and**, **or**, **xor**). |
- | Deoarece rezultatul poate fi prea mare, se cere **restul impartirii** lui la $1000000007$ ($10^9 + 7$). | + | Deoarece rezultatul poate fi prea mare, se cere **restul împărțirii** lui la $1000000007$ ($10^9 + 7$). |
<note> | <note> | ||
- | In schelet vom codifica cu valori de tip char cele 5 stringuri: | + | În schelet vom codifica cu valori de tip char cele 5 stringuri: |
* **false**: 'F' | * **false**: 'F' | ||
* **true**: 'T' | * **true**: 'T' | ||
Line 752: | Line 748: | ||
* **xor**: '^' | * **xor**: '^' | ||
- | Functia pe care va trebui sa o implementati voi va folosi variabilele **n** (numarul de termeni) si ** expr** (vectorul cu termenii expresiei). | + | Funcția pe care va trebui să o implementați voi va folosi variabilele **n** (numărul de termeni) și **expr** (vectorul cu termenii expresiei). |
</note> | </note> | ||
<spoiler Exemplu 1> | <spoiler Exemplu 1> | ||
- | $n = 5$ si $expr = ['T', '&', 'F', '^', 'T']$ (expr = [** true and false xor true**]) | + | $n = 5$ și $expr = ['T', '&', 'F', '^', 'T']$ (expr = [** true and false xor true**]) |
- | Raspuns: $2$ | + | Răspuns: $2$ |
- | Explicatie: Exista 2 moduri corecte de a paranteza expresia astfel incat sa obtinem rezultatul **true** (1). | + | Explicație: Există 2 moduri corecte de a paranteza expresia astfel încât să obținem rezultatul **true** (1). |
* $ T&(F^T) $ | * $ T&(F^T) $ | ||
* $ (T&F)^T $ | * $ (T&F)^T $ | ||
Line 767: | Line 763: | ||
<spoiler Hint> | <spoiler Hint> | ||
- | Complexitate temporală dorita este $O(n ^ 3)$. | + | Complexitate temporală dorită este $O(n ^ 3)$. |
| | ||
- | Optional, se pot defini functii ajutatoare precum **is_operand**, **is_operator**, **evaluate**. | + | Opțional, se pot defini funcții ajutătoare precum **is_operand**, **is_operator**, **evaluate**. |
</spoiler> | </spoiler> | ||
<note tip> | <note tip> | ||
- | Pentru rezolvarea celor doua probleme ganditi-va la ce scrie in sectiunea [[http://ocw.cs.pub.ro/courses/pa/laboratoare/laborator-04?&#sfaturireguli| Sfaturi / Reguli]]. Pentru fiecare dintre cele doua probleme facem o **partitionare dupa un anumit criteriu**. | + | Pentru rezolvarea celor două probleme gândiți-vă la ce scrise în secțiunea [[http://ocw.cs.pub.ro/courses/pa/laboratoare/laborator-04?&#sfaturireguli| Sfaturi / Reguli]]. Pentru fiecare dintre cele două probleme facem o **partiționare după un anumit criteriu**. |
- | Pentru problema** DP or math?** partitionam toate subsirurile dupa critieriul **paritatii sumei subsirului** (cate sunt pare/impare).\\ | + | Pentru problema **DP or math?** partiționăm toate subșirurile după critieriul **parității sumei subșirului** (câte sunt pare/impare).\\ |
- | Pentru problema **expresie booleana** partitionam **toate parantezarile posibile dupa rezultatul lor** (cate dau true/false). | + | Pentru problema **expresie booleană** partiționăm **toate parantezările posibile după rezultatul lor** (câte dau true/false). |
</note> | </note> | ||
=== Bonus === | === Bonus === | ||
- | Asistentul va alege una dintre problemele din sectiunea Extra. | + | Asistentul va alege una dintre problemele din secțiunea Extra. |
<spoiler Hint> | <spoiler Hint> | ||
- | Recomandam sa ** NU** fie una din cele 3 probleme de la Test PA 2017. Recomandam sa le incercati dupa ce recapitulati acasa DP1 si DP2, pentru a verifica daca cunostintele acumulate sunt la nivelul asteptat. | + | Recomandăm să **NU** fie una dintre cele 3 probleme de la Test PA 2017. Recomandăm să le incercați după ce recapitulați acasă DP1 și DP2, pentru a verifica dacă cunoștințele acumulate sunt la nivelul așteptat. |
</spoiler> | </spoiler> | ||
=== Extra === | === Extra === | ||
- | <spoiler Extraterestrii> | + | <spoiler Extratereștrii> |
- | Rezolvati problema [[https://www.hackerrank.com/contests/test-practic-pa-2017-v1-plumbus/challenges/test-1-extraterestrii | + | Rezolvați problema [[https://www.hackerrank.com/contests/test-practic-pa-2017-v1-plumbus/challenges/test-1-extraterestrii |
- | | extraterestrii]] de la Test PA 2017. | + | | extratereștrii]] de la Test PA 2017. |
</spoiler> | </spoiler> | ||
- | <spoiler Secvente> | + | <spoiler Secvențe> |
- | Rezolvati problema [[https://www.hackerrank.com/contests/test-practic-pa-2017-v1-plumbus/challenges/test-1-secvente | + | Rezolvați problema [[https://www.hackerrank.com/contests/test-practic-pa-2017-v1-plumbus/challenges/test-1-secvente |
- | | Secvente]] de la Test PA 2017. | + | | Secvențe]] de la Test PA 2017. |
</spoiler> | </spoiler> | ||
Line 801: | Line 797: | ||
<spoiler PA Country> | <spoiler PA Country> | ||
- | Rezolvati problema [[https://www.hackerrank.com/contests/test-practic-pa-2017-v2-meeseeks/challenges/test-2-pa-country-medie | + | Rezolvați problema [[https://www.hackerrank.com/contests/test-practic-pa-2017-v2-meeseeks/challenges/test-2-pa-country-medie |
| PA Country]] de la Test PA 2017. | | PA Country]] de la Test PA 2017. | ||
</spoiler> | </spoiler> | ||
Line 809: | Line 805: | ||
<spoiler iepuri> | <spoiler iepuri> | ||
- | Rezolvati pe infoarena problema [[http://infoarena.ro/problema/iepuri| iepuri]]. | + | Rezolvați pe infoarena problema [[http://infoarena.ro/problema/iepuri| iepuri]]. |
- | Hint: Exponentiere logaritmica pe matrice | + | Hint: Exponențiere logaritmică pe matrice |
- | Solutie: | + | Soluție: |
* $dp[0] = X; dp[1] = Y; dp[0] = Z; $ | * $dp[0] = X; dp[1] = Y; dp[0] = Z; $ | ||
* $dp[i] = (A * dp[i-1] + B * dp[i-2] + C * dp[i-3]) \ \% \ 666013$ | * $dp[i] = (A * dp[i-1] + B * dp[i-2] + C * dp[i-3]) \ \% \ 666013$ | ||
- | Pentru punctaj maxim, pentru fiecare test se foloseste ecuatia matriceala atasata. | + | Pentru punctaj maxim, pentru fiecare test se folosește ecuația matriceală atașată. |
Complexitate: $O(T * log(n))$. | Complexitate: $O(T * log(n))$. | ||
Line 824: | Line 820: | ||
<spoiler Minimum Path Sum> | <spoiler Minimum Path Sum> | ||
- | Rezolvati pe leetcode problema [[https://leetcode.com/problems/minimum-path-sum/description/#| Minimum Path Sum]]. | + | Rezolvați pe leetcode problema [[https://leetcode.com/problems/minimum-path-sum/description/#| Minimum Path Sum]]. |
</spoiler> | </spoiler> | ||
- | <spoiler Lacusta> | + | <spoiler Lăcusta> |
- | Rezolvati pe infoarena problema [[http://infoarena.ro/problema/Lacusta| Lacusta]]. | + | Rezolvați pe infoarena problema [[http://infoarena.ro/problema/Lacusta| Lăcusta]]. |
</spoiler> | </spoiler> | ||
<spoiler Suma4> | <spoiler Suma4> | ||
- | Rezolvati pe infoarena problema [[http://infoarena.ro/problema/Suma4|Suma4]]. | + | Rezolvați pe infoarena problema [[http://infoarena.ro/problema/Suma4|Suma4]]. |
</spoiler> | </spoiler> | ||
- | <spoiler Subsir> | + | <spoiler Subșir> |
- | Rezolvati pe infoarena problema [[https://www.infoarena.ro/problema/subsir|subsir]]. | + | Rezolvați pe infoarena problema [[https://www.infoarena.ro/problema/subsir|subșir]]. |
</spoiler> | </spoiler> | ||
- | <spoiler 2sah> | + | <spoiler 2șah> |
- | Rezolvati pe infoarena problema [[https://infoarena.ro/problema/2sah | 2sah]]. | + | Rezolvați pe infoarena problema [[https://infoarena.ro/problema/2sah | 2șah]]. |
- | Hint: Exponentiere logaritmica pe matrice | + | Hint: Exponențiere logaritmică pe matrice |
- | O descrie detaliata se afla in [[http://olimpiada.info/oji2015/index.php?cid=arhiva | arhiva OJI 2015]]. | + | O descriere detaliată se află în [[http://olimpiada.info/oji2015/index.php?cid=arhiva | arhiva OJI 2015]]. |
</spoiler> | </spoiler> | ||
- | ===== Referințe ===== | ||
- | [0] Capitolul **Dynamic Programming** din **Introductions to Algorithms** de către T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein | + | <spoiler DP problems> |
+ | Articolul de pe [[https://leetcode.com/discuss/general-discussion/458695/Dynamic-Programming-Patterns| leetcode]] conține o listă cu diverse tipuri de probleme de programare dinamică, din toate categoriile discutate la PA. | ||
+ | </spoiler> | ||
+ | ===== Referințe ===== | ||
+ | |||
+ | [0] Chapter **Dynamic Programming**, “Introduction to Algorithms”, Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein | ||
+ | |||
[1] [[http://infoarena.ro/problema/podm]] | [1] [[http://infoarena.ro/problema/podm]] | ||