This shows you the differences between two versions of the page.
pa:laboratoare:laborator-04 [2021/03/08 23:31] miruna_elena.banu [Sfaturi / Reguli] |
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: | + | |
- | * [[neatudarius@gmail.com|Darius-Florentin Neațu (2017-2021)]] | + | |
- | * [[radunichita99@gmail.com | Radu Nichita (2021)]] | + | |
- | * [[cristianolaru99@gmail.com | Cristian Olaru (2021)]] | + | |
- | * [[mirunaelena.banu@gmail.com | Miruna-Elena Banu (2021)]] | + | |
- | * [[maraioana9967@gmail.com | Mara-Ioana Nicolae (2021)]] | + | |
- | * [[stefanpopa2209@gmail.com | Ștefan Popa (2018-2020)]] | + | |
- | + | ||
- | Autori: | + | |
- | * [[neatudarius@gmail.com|Darius-Fforentin Neațu (2018)]] | + | |
- | * [[visanr95@gmail.com|Radu Vișan (2018)]] | + | |
- | * [[cristb@gmail.com|Cristian Banu (2018)]] | + | |
- | * [[razvan.ch95@gmail.com|Răzvan Chițu (2018)]] | + | |
===== Obiective laborator ===== | ===== Obiective laborator ===== | ||
Line 102: | Line 88: | ||
* explicație: $X =(BC)$ generează $3 * 4 * 5 = 60$ înmulțiri, $(AX)$ generează $2 * 3 * 5 = 30$ de înmulțiri | * explicație: $X =(BC)$ generează $3 * 4 * 5 = 60$ înmulțiri, $(AX)$ generează $2 * 3 * 5 = 30$ de înmulțiri | ||
- | Rezultatul optim se obține pentru cea de a treia parantezare: $(AB)C$. | + | Rezultatul optim se obține pentru prima parantezare: $(AB)C$. |
</spoiler> | </spoiler> | ||
Line 165: | Line 151: | ||
== Numire recurență === | == Numire recurență === | ||
$dp[i][j]$ = **numărul minim de înmulțiri scalare** cu care se poate obține 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]** . | ||
== Găsire recurență == | == Găsire recurență == | ||
Line 186: | Line 174: | ||
<spoiler Implementare C++> | <spoiler Implementare C++> | ||
<code cpp> | <code cpp> | ||
- | // kInf este valoarea maximă - "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) | ||
Line 194: | Line 182: | ||
// dp[i][j] = numărul MINIM înmulțiri scalare cu codare, poate fi calculat produsul | // dp[i][j] = numărul MINIM înmulțiri scalare cu codare, poate fi calculat produsul | ||
// matriceal 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 bază 1: nu am ce înmulți | // Cazul de bază 1: nu am ce înmulți | ||
Line 242: | Line 230: | ||
<note> | <note> | ||
- | **ATENȚIE!** La PA, în general, vom folosi convenția $ expresie \ \% \ kMod $, care va fi detaliată în capitolul următor 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> | ||
Line 263: | Line 251: | ||
* 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. | * 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 285: | 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 310: | 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 321: | 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 333: | 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 346: | 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 404: | 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 pe valorile anterior calculate 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 457: | 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 481: | 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 511: | 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 == | + | == 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 543: | 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 558: | 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 585: | 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 607: | 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 621: | 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> | ||
Scheletul de laborator se găsește pe pagina [[https://github.com/acs-pa/pa-lab/tree/main/skel/lab04|pa-lab::skel/lab04]]. | 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 676: | Line 662: | ||
|v|2|6|4| | |v|2|6|4| | ||
- | Raspuns: $7$ | + | Răspuns: $7$ |
- | Explicatie: Toate subsirurile posibile sunt | + | Explicație: Toate subșirurile posibile sunt |
* $[2]$ | * $[2]$ | ||
* $[2, 6]$ | * $[2, 6]$ | ||
Line 686: | 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 695: | Line 681: | ||
|v|2|1|3| | |v|2|1|3| | ||
- | Raspuns: $3$ | + | Răspuns: $3$ |
- | Explicatie: Toate subsirurile posibile sunt | + | Explicație: Toate subșirurile posibile sunt |
* $[2]$ | * $[2]$ | ||
* $[2, 1]$ | * $[2, 1]$ | ||
Line 706: | 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 716: | Line 702: | ||
|v|3|2|1| | |v|3|2|1| | ||
- | Raspuns: $3$ | + | Răspuns: $3$ |
- | Explicatie: Toate subsirurile posibile sunt | + | Explicație: Toate subșirurile posibile sunt |
* $[3]$ | * $[3]$ | ||
* $[3, 2]$ | * $[3, 2]$ | ||
Line 727: | 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> | ||
Line 734: | Line 720: | ||
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). | 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> | ||
- | Cate subsiruri au suma **impara**? | + | Câte subșiruri au suma **impară**? |
</spoiler> | </spoiler> | ||
<hidden> | <hidden> | ||
- | <spoiler Solutie> | + | <spoiler Soluție> |
- | Problema este preluata de [[https://infoarena.ro/problema/azerah|aici]]. Solutia se gaseste [[https://www.infoarena.ro/onis-2015/solutii-runda-1#azerah|aici]]. | + | 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> | </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 762: | 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 777: | 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 811: | 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 819: | 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 834: | 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]] | ||