Differences

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

Link to this comparison view

pa:laboratoare:laborator-04 [2019/03/20 10:05]
gabriel.bercaru [Exemple clasice]
pa:laboratoare:laborator-04 [2026/03/27 02:05] (current)
radu.nichita
Line 1: Line 1:
-====== Laborator 4: Programare Dinamică (continuare) ​====== +====== Laborator 04: Backtracking ​====== 
-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ă +  * Întelegerea ​noțiunilor de bază despre ​backtracking;​ 
-  * Insușirea abilităților de implementare a algoritmilor bazați ​programare dinamică.+  * Însușirea abilităților de implementare a algoritmilor bazați ​pe backtracking;​ 
 +  * Rezolvarea unor probleme NP-complete în timp exponențial.
  
-===== 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/​algorithms/​lab04|pa-lab::algorithms/lab04]].
  
-Acestea ​apar incorporate si in textul laboratorului pentru a facilita parcurgerea ​cursiva ​laboratorului.+Exemplele de cod apar încorporate și în textul laboratorului pentru a facilita parcurgerea ​cursivă ​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ă rugam să compilați ​**DOAR** codul de pe GitHub. Pentru raportarea problemelorcontactaț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ă rugăm să dați e-mail ​unuia dintre responsabili.
  
-===== Ce este DP? ===== 
-Similar cu greedy, tehnica de programare dinamica este folosită pentru rezolvarea **problemelor de optimizare**. ​ 
-In continuare vom folosi acronimul **DP (dynamic programming)**. 
  
 +===== Ce este Backtracking?​ =====
 +Backtracking este un algoritm care caută **una sau mai multe soluții** pentru o problema, printr-o căutare exhaustiva, mai eficientă însă în general decât o abordare „generează si testează”,​ de tip „forță brută”, deoarece un candidat parțial care nu duce la o soluție este abandonat. Poate fi folosit pentru orice problemă care presupune o căutare în **spațiul stărilor**.
 +În general, în timp ce cautăm o soluție e posibil să dăm de un deadend în urma unei alegeri greșite sau să găsim o soluție, dar să dorim să căutăm în continuare alte soluții. În acel moment trebuie să ne întoarcem pe pașii făcuți (**backtrack**) și la un moment dat să luăm altă decizie.
 +Este relativ simplu din punct de vedere conceptual, dar complexitatea algoritmului este exponentială.
  
-De asemenea, DP se poate folosi si pentru ​probleme ​in care nu cautam un optimcum ar fi **problemele de numarare**.+===== Importanța – aplicaţii practice ===== 
 +Există foarte multe probleme ​(de exempluproblemele NP-complete sau NP-dificile) care pot fi rezolvate prin algoritmi de tip backtracking mai eficient decât prin „forta bruta” (adică generarea tuturor alternativelor și selectarea soluțiilor). Atenție însă, complexitatea computațională este de cele mai multe ori **exponențială**. O eficientizare se poate face prin combinarea cu tehnici de propagare a restricțiilor. Orice problemă care are nevoie de parcurgerea spațiului de stări se poate rezolva cu backtracking
  
-Pentru restul notiunilor prezentate pana acum despre DP, va rugam sa consulati pagina laboratorului 3. +===== Descrierea problemei și a rezolvărilor ​===== 
-===== Exemple clasice ​=====+Pornind de la strategiile clasice de parcurgere a **spațiului de stări**, algoritmii de tip backtracking practic enumeră un set de candidați parțiali, care, după completarea definitivă,​ pot deveni soluții potențiale ale problemei inițiale. Exact ca strategiile de **parcurgere în lățime/​adâncime** și backtracking-ul are la bază expandarea unui nod curent, iar determinarea soluției se face într-o manieră incrementală. Prin natura sa, backtracking-ul este recursiv, iar în arborele expandat top-down se aplică operații de tipul pruning (tăiere) dacă soluția parțială nu este validă.
  
-Programarea Dinamică este cea mai flexibilă tehnica din programare. Cel mai ușor mod de a înțelege presupune parcurgerea cât mai multor exemple.+===== Algoritm de baza ===== 
 +<code C> 
 +/* Domain - domeniul curent (cu un cardinal ​mai mic decat la pasul trecut) 
 +Solution - solutia curenta pe care extindem catre cea finala */ 
 +back(Domain,​ Solution):​ 
 +    if check(Solution):​ 
 +        print(Solution) 
 +        return
  
-Propunem cateva categorii de recurente, pe care le vom grupa astfel+    for value in Domain
-  ​* ​ recurente de tip **SSM** ​(Subsecventa de Suma Maxima+        ​NextSolution = Solution.push(value
-  ​* ​ recurente de tip **RUCSAC** +        ​NextDomain = Domain.erase(value
-  *  recurente de tip **PODM** ​(Parantezare Optima de Matrici+        ​back(NextDomain,​ NextSolution) 
-  ​* ​ recurente de tip **numărat** +</​code>​
-  ​* ​ recurente pe **grafuri**+
  
 +{{ pa:​laboratoare:​perm_gen_viz.png?​700|}}
 +
 +===== Algoritm de baza (modificat pentru transmitere prin referinta) =====
 +<code C>
 +/* Domain - domeniul curent (cu un cardinal mai mic decat la pasul trecut)
 +Solution - solutia curenta pe care o extindem catre cea finala */
 +back(Domain,​ Solution):
 +    if check(Solution):​
 +        print(Solution)
 +        return
 +
 +    for value in Domain:
 +        /* DO */
 +        Solution = Solution.push(value)
 +        Domain = Domain.erase(value)
 +        ​
 +        /* RECURSION */
 +        back(Domain,​ Solution)
 +        ​
 +        /* UNDO */
 +        Solution = Solution.pop()
 +        Domain = Domain.insert(value)
 +</​code>​
 +
 +{{ :​pa:​laboratoare:​perm_ref_viz.png?​700 |}}
 +
 +===== Exemple clasice =====
 +Ne vom ocupa în continuare de următoarele probleme:
 +    * Permutări
 +    * Combinări
 +    * Aranjamente
 +    * Submulțimi
 +    * Generare de șiruri
 +    * Problema damelor
 +    * Problema șoricelului
 +    * Tic-Tac-Toe
 +    * Sudoku
 +    * Ultimate Tic-Tac-Toe
  
 <​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.+Sudoku și Ultimate Tic-Tac-Toe sunt probleme foarte greleÎn general nu putem explora tot spațiul stărilor pentru un input arbitrar dat.
 </​note>​ </​note>​
-===== Categoria 3: PODM ===== 
-Aceste recurente au o oarecare asemanare cu problema PODM (enunt + solutie). 
  
 +==== Permutări ====
  
-ATENTIE! Acest tip de recurente poate fi mai greu (decat celelalte). Puteti consulta ** acasa **materialele puse la dispozitie pentru intelege mai bine aceasta categorie.+=== Enunț ===
  
-Caracteristici:​ +Se dă un număr NSă se genereze ​toate permutările mulțimii formate din toate numerele ​de la 1 la N.
-  * 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]$**. +
-  * Se considera fiecare divizare in 2 subprobleme,​ data de intermediarul k +
-    * **$[i, k]$** si **$[k + 1, j]$ ** sunt cele 2 subprobleme pentru care cunoastem solutiile +
-    * atunci o solutie pentru **$[i,​j]$** ​se poate obtine imbinandu-le pe cele doua +
-    * ca sa gasim solutia cea mai buna +
-      * vom itera prin toate valorile k posibile +
-      * vom alege pe cea care maximizeaza solutia 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]$**). +
-    * Privind imaginea de ansamblu, adica cum se completeaza matricea, obervam ca matricea dp se completeaza **diagonala cu diagonala**.+
  
-==== Exemple ​clasice ==== +=== Exemple ===
-=== PODM ===+
  
-== Enunt == +<spoiler Exemplu 1>
-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 **.+
  
-Matricea $M_i$ are (prin conventie), dimensiunile $d_{i-1} d_{i}$.+N = 3 => M = {1, 2, 3}
  
-== Cerinta == +Solutie: 
-Se cere sa se gaseasca o ** parantezare optima de matrice ​** (PODM)adica sa se gaseasca o parantezare care sa minimizeze numarul de inmultiri scalare.+  {1, 2, 3} 
 +  ​{1, 3, 2} 
 +  ​{2, 1, 3} 
 +  ​{2, 3, 1} 
 +  * {3, 1, 2} 
 +  * {3, 21}
  
-== Exemple == +</spoiler>
-<​spoiler ​Exemplu 1>+
  
-$n 3$ +=== Soluții ===
-|i|0|1|2|3|| +
-|d|2|3|4|5||+
  
-Raspuns: ** 64 **  ​(inmultiri scalare)+=== Backtracking ​(algoritmul în cazul general=== 
 +<spoiler Implementare>​
  
-Explicatie: Avem 3 matrici: +<code cpp> 
-   ​A de dimensiuni (23) +/deoarece numerele sunt sterse din domeniu odata ce sunt folositesoluția generata este garantata 
-   ​B de (3, 4+sa nu contina duplicate. Astfel, atunci cand domeniul ajunge vid, soluția este intotdeauna corecta ​*
-   * C de (4, 5)+bool check(std::​vector<​int>​ solution{ 
 +    ​return true; 
 +}
  
-In functie de ordinea efectuarii inmultirilor matriciale , numarul total de inmultiri scalare poate sa fie foarte diferit: +void printSolution(std:​:vector<​intsolution{ 
-   * $(AB)C$ =$24 + 40 = 64$ de inmultiri +    ​for ​(auto &s : solution{ 
-       * explicatie: $X = (AB)$ genereaza $2 * 3 * 4 = 24$ inmultiri, $(XC)$ genereaza $2 * 4 * 5 = 40$ de inmultiri +        std::cout << s << " "; 
-   * $A(BC)$ =>  $60 + 30 = 90$ de inmultiri +    } 
-       * explicatie$X =(BC)$ genereaza $3 * 4 * 5 = 60$ inmultiri, $(AX)$ genereaza $2 * 3 * 5 = 30$ de inmultiri +    std::cout << "​\n";​ 
-   ​ +}
-Rezultatul optim se obtine pentru cea de a treia parantezare$(AB)C$. +
-  +
-</​spoiler>​+
  
-<spoiler Exemplu 2>+void back(std::​vector<intdomain, std::​vector<​int>​ solution) { 
 +    /* dupa ce am folosit toate elementele din domeniu putem verifica daca 
 +    am gasit o solutie */ 
 +    if (domain.size() == 0) { 
 +        if(check(solution)) { 
 +            printSolution(solution);​ 
 +        } 
 +        return; 
 +    }
  
-$n = 4$ +    /* incercam sa adaugam in solutie toate valorile din domeniu, pe rand */ 
-|i|0|1|2|3|4| +    for (unsigned int 0; i < domain.size();​ ++i) { 
-|d|2|3|4|2|3|+        /* cream o solutie noua si un domeniu nou care sunt identice cu cele 
 +        de la pasul curent */ 
 +        ​std::​vector<​int>​ newSolution(solution),​ newDomain(domain);​
  
-Raspuns: ​** 48 **  (inmultiri scalare)+        /adaugam in noua solutie elementul ales din domeniu ​*
 +        newSolution.push_back(domain[i]);​ 
 +        /stergem elementul ales din noul domeniu ​*
 +        newDomain.erase(newDomain.begin() + i);
  
-Explicatie: Avem 4 matrici: +        /apelam recursiv backtracking pe noul domeniu si noua solutie ​*/ 
-   A de dimensiuni (2, 3) +        back(newDomainnewSolution); 
-   B de (3, 4) +    } 
-   * C de (42+}
-   * D de (2, 3) +
-    +
-   In functie de ordinea efectuarii inmultirilor matriciale, numarul total de inmultiri scalare poate sa fie foarte diferit: +
-   * $(AB)C)D$ ​ => $24 + 16 + 12 = 52$ inmultiri +
-       * 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  +
-   * $(A(BC))D$ => $24 + 12 + 12 = 48$ inmultiri +
-       * 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 +
-   * $(AB)(CD)$ => $ = $ inmultiri +
-       * 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 +
-   * $A((BC)D)$ => $24 + 18 + 27 = 69$ inmultiri +
-       * 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 +
-   * $A(B(CD))$ => $24 + 36 + 18 = 78$ inmultiri +
-       * 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 +
-   ​Rezultatul optim se obtine pentru cea de a treia parantezare:​ $((A(BC))D)$. +
-  +
-</​spoiler>​+
  
-<spoiler Exemplu 3>+int main() { 
 +    /* dupa ce am citit n initializam domeniul cu n elemente, numerele de la 1 la n, 
 +    iar solutia este vida initial */ 
 +    std::vector<intdomain(n), solution; 
 +    for (int i = 0; i < n; ++i) { 
 +        domain[i] = i + 1; 
 +    }
  
-$n = 4$ +    /* apelam backtracking pe domeniul nostru, cautand solutia in vectorul solution */ 
-|i|0|1|2|3|4| +    ​back(domain,​ solution); 
-|d|13|5|89|3|34|+
 +</​code>​
  
-Raspuns** 2856 **  ​(inmultiri scalare)+Apelarea inițială (din "​main"​) se face astfel"​back(domain,​ solution);",​ unde domain reprezintă un vector cu elementele de la 1 la N, iar solution este un vector gol. 
 + 
 +<note important>​ 
 +Nu este indicată implementarea backtracking-ului astfel deoarece este foarte costisitor din punct de vedere al memoriei(se creează noi domenii și soluții la fiecare pas)
 +</​note>​
  
-Explicatie: Avem 4 matrici: 
-   * A de dimensiuni (13, 5) 
-   * B de (5, 89) 
-   * C de (89, 3) 
-   * D de (3, 34) 
-    
-   In functie de ordinea efectuarii inmultirilor matriciale , numarul total de inmultiri scalare poate sa fie foarte diferit: 
-   * $((AB)C)D$ => 10582 inmultiri 
-   * $(AB)(CD)$ => 54201 inmultiri 
-   * $(A(BC))D$ =>  2856 inmultiri 
-   * $A((BC)D)$ =>  4055 inmultiri 
-   * ... 
-Rezultatul optim se obtine pentru cea de a treia parantezare:​ $(A(BC))D$. 
-  
 </​spoiler>​ </​spoiler>​
  
-== TIPAR == +== Complexitate ​==
-A fost descris in detaliu mai sus (cand s-a vorbit de categorie). +
-  +
-== Numire recurenta === +
-$dp[i][j]$ = **numarul minim de inmultiri scalare** cu care se poate obtine produsul $M_i * M_{i+1} * ... *{M_j}$ ​+
  
-== Gasire recurenta == +Soluția va avea următoarele complexitati:
-  * **Cazul de baza** ​ +
-    * $dp[i][i] = 0 $ +
-      * NU avem nici un efort daca nu avem ce inmulti. +
-    * $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. +
-      * 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}$ +
-  * **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})$ +
-       * costul minim pentru $(M_i ... M_k)$ este $dp[i][k]$ +
-       * 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}$ +
-     * insumam cele 3 costuri intermediare+
  
-== Implementare == +    * complexitate temporala : $T(n)=O(n * n!)$ 
-Puteti rezolva ​si testa problema PODM pe infoarena [[https://​infoarena.ro/​problema/​podm|aici]].+        * explicație : Complexitatea generarii permutarilor,​ $O(n!)$, se înmultește cu complexitatea copierii vectorilor soluție ​si domeniu si a stergerii elementelor din domeniu, $O(n)$ 
 +    * complexitate spatiala ​$S(n)=O(n^2)$ 
 +        * explicație : Fiecare nivel de recursivitate are propria lui copie a soluției și a domeniuluiSunt n nivele de recursivitate,​ deci complexitatea spatială este $O(n * n) = O(n^2)$ 
 + 
 + 
 +=== Backtracking (date transmise prin referinta) === 
 +<spoiler Implementare>​
  
-Un exemplu de implementare in C++ se gaseste mai jos. 
-<spoiler Implementare C++> 
 <code cpp> <code cpp>
-// kInf este valoarea maxima - "​infinitul"​ nostru 
-const unsigned long long kInf = std::​numeric_limits<​unsigned long long>::​max();​ 
  
-// T = O(n ^ 3)  +/* deoarece numerele sunt sterse din domeniu odata ce sunt folosite, soluția generata este garantata sa nu contina duplicate. Astfel, atunci cand domeniul ajunge vid, soluția este intotdeauna corecta *
-// S = O(n ^ 2) - stocam n x n intregi in tabloul dp +bool check(std::vector<​int> ​solution) { 
- ​unsigned long long solve_podm(int n, const vector<​int> ​&d) { +    ​return true; 
-    ​// dp[i][j] = numarul MINIM inmultiri scalare cu codare poate fi calculat produsul +}
-    // ​           matricial M_i * M_i+1 * ... * M_j +
-    vector<​vector<​unsigned long long>> ​ dp(n + 1, vector<​unsigned long long> (n + 1, kInf));+
  
-    // Cazul de baza 1nu am ce inmulti ​ +void printSolution(std::​vector<​int>​ &​solution) { 
-    for (int i = 1; i <= n; ++i) { +    for (int s : solution) { 
-        ​dp[i][i] = 0ULL // 0 pe unsigned long long (voi folosi mai incolo si 1ULL)+        ​std::cout << s << " ";
     }     }
 +    std::cout << "​\n";​
 +}
  
-    // Cazul de baza 2matrice d[i - 1] x d[i] inmultia cu matrice d[i] x d[i + 1]  +void back(std::​vector<​int>​ &​domain,​ std::​vector<​int>​ &​solution) { 
-    // (matrice pe pozitii consecutive) +    /* dupa ce am folosit toate elementele din domeniu putem verifica daca 
-    ​for (int i 1; i < n; ++i) { +    am gasit o solutie *
-        ​dp[i][i + 1] =  1ULL * d[i - 1] * d[i] * d[i + 1] +    ​if (domain.size() ​== 0) { 
 +        ​if(check(solution)) { 
 +            printSolution(solution);​ 
 +        } 
 +        return;
     }     }
  
-    // Cazul general: +    /* incercam sa adaugam in solutie toate valorile din domeniu, pe rand */ 
-    // dp[i][j] = min(dp[i][k] + dp[k + 1][j] + d[i - 1] d[k] d[j]), k = i : j - 1 +    for (unsigned ​int 0domain.size(); ++i) { 
-    for (int len 2len <= n; ++len) {            // fixam lungimea intervalului ​(2, 3, 4, ...) +        ​/* retinem valoarea pe care o scoatem ​din domeniu ca sa o readaugam dupa 
-        for (int i = 1; i + len - 1 <= n; ++i) {    // fixam capatul ​din stanga: i +        ​apelarea recursiva a backtracking-ului */ 
-            int = i + len - 1                   // capatul din dreapta se deduce: j +        ​int tmp domain[i]
-             + 
-            // Iteram prin indicii dintre capete, spargand sirul de inmultiri in doua (paranteze). +        /* adaug elementul curent la potentiala solutie */ 
-            for (int k = i; k < j; ++k) { +        solution.push_back(domain[i])
-                // M_i * ... M_j  = (M_i * .. * M_k* (M_k+1 *... * M_j+        /* sterg elementul curent din domeniu ca sa il pot pasa prin referinta 
-                ​unsigned long long new_sol = dp[i][k] + dp[k + 1][j] + 1ULL d[i - 1] d[k] * d[j]+        si sa nu fie nevoie sa creez alt domeniu */ 
-                 +        domain.erase(domain.begin() + i); 
-                // actualizam ​solutia ​daca este mai buna + 
-                ​dp[i][j] = min(dp[i][j]new_sol);  +        /apelez recursiv backtracking pe domeniul si solutia modificate ​*
-            } +        back(domain,​ solution)
-        ​}+ 
 +        /* refac domeniul si solutia ​la modul in care aratau inainte de apelarea 
 +        ​recursiva a backtracking-ului,​ adica readaug elementul eliminat in 
 +        domeniu si il sterg din solutie */ 
 +        domain.insert(domain.begin() + i, tmp); 
 +        ​solution.pop_back();​
     }     }
 +}
  
-    ​// Rezultatul se afla in dp[1][n]: Numarul MINIM de inmultiri scalare +int main() { 
-    ​// pe care trebuie sa le facem pentru a obtine produsul M_1 * ... M_n +    ​/* dupa ce am citit initializam domeniul cu n elemente, numerele ​de la 1 la n, 
-    ​return dp[1][n];+    ​iar solutia este vida initial ​*/ 
 +    ​std::​vector<​int>​ domain(n), solution; 
 +    for (int i = 0; i < n; ++i) { 
 +        domain[i= i + 1; 
 +    }
  
 +    /* apelam backtracking pe domeniul nostru, cautand solutia in vectorul solution */
 +    back(domain,​ solution);
 } }
 </​code>​ </​code>​
-<​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. 
-</​note>​ 
- 
  
 +Apelarea initiala (din "int main") se face astfel: "​back(domain,​ solution);",​ unde domain reprezinta un vector cu elementele de la 1 la N, iar solution este un vector gol.
 </​spoiler>​ </​spoiler>​
-<​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. 
-</​note>​ 
  
- 
-<​note>​ 
-ATENTIE! La PA, in general, vom folosi conventia $ expresie \ \%  \ kMod $, care va fi detaliata in capitolul urmatorul din acest laborator. ​ 
-</​note>​ 
  
 == Complexitate == == Complexitate ==
-Intrucat solutia presupune fixarea capetelor unui subinterval (i, j), apoi alegerea unui intermediar (k), complexitatea este data de aceste 3 cicluri. 
-    * **complexitate temporala**:​ $T(n) = O(n^3)$ 
-    * **complexitate spatiala**: $S(n) = O(n^2)$ ​ 
  
 +Soluția va avea următoarele complexități:​
  
 +  * complexitate temporală : $T(n)=O(n * n!)$
 +    * explicație : Complexitatea generării permutărilor,​ $O(n!)$, se înmulțește cu complexitatea ștergerii elementelor din domeniu, $O(n)$
 +  * complexitate spatială : $S(n)=O(n)$
 +    * explicație : Spre deosebire de solutia anterioară,​ toate nivelele de recursivitate folosesc aceeași soluție și același domeniu. Complexitatea spatială este astfel redusă la $O(n)$
 +<​note>​
  
-===== Categoria 4: NUMARAT ===== +Această abordare ​este mai eficientă decât cea generală, deoarece se evită folosirea memoriei auxiliare.
-Aceste recurente au o oarecare asemanare:​ +
-  * toate numara 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) +
-      * 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). +
-==== Sfaturi / Reguli ==== +
-  * cand cautati o recurenta pentru o problema de numarare trebuie sa aveti grija la doua aspecte: +
-    * 1) sa **NU** numarati acelasi obiect de doua ori. +
-    * 2) sa numarati toate obiectele in cauza.  +
-  * 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.  +
-==== 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). +
-  * proprietati de de baza +
-    * $(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)  +
-  * invers modular +
-    * $ \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   +
-<spoiler Explicatii invers modular> ​    +
-    * ** definitie **: **b** este inversul modular a lui **a** in raport cu **MOD** daca $ a * b = 1 (modulo \ 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!!!** +
-      * ** 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)$. +
-            * din definitia inversului modular, reiese ca ** a ** si **b** nu sunt multipli ai lui **MOD** +
-            * introducand notatiile noastre in teorema si prelucrand obtinem +
-              * $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 }$+
  
- 
-</​spoiler>​ 
- 
-<​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:​ 
-  * C++ 
-    * **1LL / 1ULL** - constanta 1 pe 64 biti cu semn / fara semn 
-      * **1LL * a * b** - am grija ca rezultatul sa nu dea overflow si sa se stocheze direct pe 64biti (cu semn) 
-  * Java 
-    * **1L** - constanta 1 pe 64biti cu semn (in Java nu exista unsigned types) 
-      * **1L * a * b** - am grija ca rezultatul sa nu dea overflow si sa se stocheze direct pe 64biti (cu semn) 
 </​note>​ </​note>​
-==== Gardurile lui Gigel  ==== 
-=== Enunt === 
-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**. 
  
-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 piesa poate fi pusa in pozitie **orizontala** sau in pozitie ** verticala**. ​+=== Backtracking (tăierea ramurilor nefolositoare) === 
 +<spoiler Implementare>​
  
-=== Cerinta === +<code cpp>
-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**.+
  
-<spoiler Exemplu 123> +bool check(std::​vector<int&​solution) ​{ 
-{{pa:​laboratoare:​garduri_123.png}}+    return true; 
 +}
  
-$= 1$ sau $n = 2$ sau $n = 3$+void printSolution(std::​vector<​int>​ &​solution) { 
 +    for (auto s : solution) { 
 +        std::cout << s << " "; 
 +    } 
 +    std::cout << "\n"; 
 +}
  
 +void back(int step, int stop, std::​vector<​int>​ &​domain,​
 +        std::​vector<​int>​ &​solution,​ std::​unordered_set<​int>​ &​visited) {
 +    ​
 +    /* vom verifica o solutie atunci cand am adaugat deja N elemente in solutie,
 +    adica step == stop */
 +    if (step == stop) {
 +        /* deoarece am avut grija sa nu se adauge duplicate, "​check()"​ va returna
 +        intotdeauna "​true"​ */
 +        if(check(solution)) {
 +            printSolution(solution);​
 +        }
 +        return;
 +    }
  
-Raspuns: ​** **  (un singur gard)+    /Adaugam in solutie fiecare element din domeniu care *NUa fost vizitat 
 +    deja renuntand astfel la nevoia de a verifica duplicatele la final prin 
 +    functia "​check()"​ */ 
 +    for (unsigned int i = 0; i < domain.size();​ ++i) { 
 +        /* folosim elementul doar daca nu e vizitat inca */ 
 +        if (visited.find(domain[i]) == visited.end()) { 
 +            /* il marcam ca vizitat si taiem eventuale expansiuni nefolositoare 
 +            viitoare (ex: daca il adaug in solutie pe 3 nu voi mai avea 
 +            niciodata nevoie sa il mai adaug pe 3 in continuare) ​*
 +            visited.insert(domain[i]);
  
-Explicatie: Se poate forma un singur gard in fiecare caz, dupa cum este ilustrat si in figura ​**Garduri_123**. ​ +            /* adaugam elementul curent ​in solutie pe pozitia pasului curent 
-</​spoiler>​+            (step) ​*/ 
 +            ​solution[step] = domain[i];
  
 +            /* apelam recursiv backtracking pentru pasul urmator */
 +            back(step + 1, stop, domain, solution, visited);
  
-<spoiler Exemplu 4> +            /* stergem vizitarea elementului curent (ex: pentru N = 3, dupa ce 
-{{pa:​laboratoare:​garduri_4.png}}+            la pasul "step = 0" l-am pus pe 1 pe prima pozitie in solutie si 
 +            am continuat recursiv pana am ajuns la solutiile ​{1, 2, 3} si  
 +            ​{1, 3, 2}, ne dorim sa il punem pe 2 pe prima pozitie in solutie si 
 +            sa continuam recursiv pentru a ajunge la solutiile {2, 1, 3} etc.) */ 
 +            visited.erase(domain[i]);​ 
 +        ​} 
 +    } 
 +}
  
-$n = 4$+int main() { 
 +    /* dupa ce am citit initializam domeniul cu n elemente, numerele de la 1 la n, 
 +    iar solutia este initializata cu un vector de n elemente (deoarece o permutare 
 +    contine n elemente) */ 
 +    std::​vector<​int>​ domain(n), solution(n);​ 
 +    std::​unordered_set<​int>​ visited; 
 +    for (int i = 0; i < n; ++i) { 
 +        domain[i] ​i + 1; 
 +    }
  
-Raspuns: ​** 2 **  ​+    /apelam back cu step = 0 (atatea elemente avem adaugate in solutie), 
 +    stop = n (stim ca vrem sa adaugam n elemente in solutie pentru ca o 
 +    permutare e alcatuita din n elemente), domain este vectorul de valori 
 +    posibile, solution este vectorul care simuleaza stiva pe care o vom 
 +    umple, visited este un unordered_set (initial gol) in care retinem daca 
 +    un element din domeniu se afla deja in solutia curenta la un anumit pas *
 +    back(0, n, domain, solution, visited); 
 +
 +</​code>​ 
 + 
 +Apelarea inițială (din "​main"​) se face astfel: "​back(0,​ n, domain, solution, visited);",​ unde domain reprezintă un vector cu elementele de la 1 la N, iar solution este un vector de n elemente, 0 este pasul curent, n este pasul la care dorim să ne oprim, iar visited este map-ul care ne permite să ținem cont de ce elemente au fost vizitate sau nu.
  
-Explicatie: Se pot forma 2 garduri, in functie de cum asezam piesele, dupa cum este ilustrat si in 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! 
 </​spoiler>​ </​spoiler>​
  
 +== Complexitate ==
  
-<spoiler Exemplu 5> +Soluția va avea următoarele complexitați:
-{{pa:laboratoare:​garduri_5.png}}+
  
-$n = 5$+  * complexitate temporală : $T(n)=O(n * n!)$ 
 +    * explicație : Complexitatea generării permutărilor,​ O(n!), se înmulțește cu complexitatea iterării prin domeniu, $O(n)$ 
 +  * complexitate spatială : $S(n)=O(n)$ 
 +    * explicație : Toate nivelele de recursivitate folosesc aceeași soluție și același domeniu.
  
-Raspuns: ** 3 **  ​+<​note>​
  
-Explicatie: Se pot forma 3 garduri, in functie de cum asezam piesele, dupa cum este ilustrat si in figura **Garduri_5**. +Această soluție ​este optimă și are complexitatea temporală $T(n= O(n!)$. Nu putem să obținem o soluție ​mai bunăîntrucât trebuie să generăm n! permutări.
-  * 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 +
-  * 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 +
-</​spoiler>​+
  
 +De asemenea, este optimă și din punct de vedere spatial, întrucât trebuie să avem $S(n) = O(n)$, din cauza stocării permutării generate.
 +</​note>​
  
-<spoiler Exemplu 6> 
-{{pa:​laboratoare:​garduri_6.png}} 
  
-$n 6$+==== Combinări ====
  
-Raspuns: ** 4 **  ​+=== Enunț ===
  
-Explicatie: ​Se pot forma 4 garduri, in functie de cum asezam piesele, dupa cum este ilustrat ​si in figura **Garduri_6**. +Se dau numerele N si KSă se genereze toate combinările mulțimii formate din toate numerele ​de la 1 la Nluate câte K.
-  * 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 +
-  * 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 +
-</​spoiler>​+
  
-=== Recurenta ​=== +=== Exemple ​===
-== Numire recurenta == +
-$dp[i] $ = numarul de garduri de lungime i si inaltime 4 (nimic special - exact ceeea ce se cere in enunt)+
  
 +<spoiler Exemplu 1>
  
-Raspunsul la problema este $dp[n]$.+N = 4, K = 2 => M = {1, 2, 3, 4}
  
-== Gasire recurenta == +Soluție: 
-  * **Caz de baza** +  * {12} 
-    * $dp[1] = dp[2] = dp[3] = 1$; $dp[4]$ = +  * {13} 
-  * ** Caz general ** +  {1, 4} 
-    * atunci dorim sa formam un gard de lungime i ($ i >= 5 $) am vazut ca putem alege cum sa punem ultima/​ultimele piese +  {23} 
-      * **DACA** alegem ca ultima piesa sa fie pusa in pozitie verticalaatunci la stanga mai ramane de completat **un subgard de lungime $i-1$** ​ +  {2, 4} 
-        numarul de moduri in care putem face acest subgard este $dp[i-1]$ +  * {34}
-      **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$** +
-        numarul de moduri in care putem face acest subgard este $dp[i-4]$ +
-    * $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.+
  
 +</​spoiler>​
  
-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>​  +=== Soluții ===
- +
-== Implementare recurenta ​== +
-Aici puteti vedea un exemplu simplu de implementare in C++. +
  
-<spoiler Implementare ​in C++>+===Backtracking (tăierea ramurilor nefolositoare) === 
 +<spoiler Implementare>​
 <code cpp> <code cpp>
  
-#define MOD 1009 +bool check(std::​vector<​int> &​solution) { 
-int gardurile_lui_Gigel(int n) { +    return ​true
-    ​// cazurile de baza +}
-    if (n <= 3) return ​1+
-    if (n == 4) return 2;+
  
-    ​vector<​int> ​dp(1); // pastrez indexarea de la 1 ca in explicatii +void printSolution(std::​vector<​int> ​&​solution,​ std::​vector<​int>​ &​domain,​ int stop) { 
- +    for (unsigned i = 0; i < stop; ++i{ 
-    // cazurile de baza +        ​std::​cout << domain[solution[i]] << " "
-    dp[1] = dp[2= dp[3= 1+    ​
-    ​dp[4] = 2;+    std::cout << "​\n"​; 
 +}
  
-    ​// cazul general +void back(int step, int stop, std::​vector<​int>​ &​domain,​ 
-    ​for (int i 5; i <n; ++i) { +        std::​vector<​int>​ &​solution) { 
-        ​dp[i] = (dp[i - 1] + dp[i - 4]% MOD;+    ​/* vom verifica o solutie atunci cand am adaugat deja K elemente in solutie, 
 +    adica step == stop *
 +    ​if (step == stop) { 
 +        ​/* deoarece am avut grija sa se adauge elementele doar in ordine 
 +        crescatoare,​ "check()" va returna intotdeauna "​true"​ */ 
 +        if(check(solution)) { 
 +            printSolution(solution,​ domain, stop); 
 +        } 
 +        return;
     }     }
  
-    ​return dp[n];+    ​/* daca este primul pas, alegem fiecare element din domeniu ca potential 
 +    candidat pentru prima pozitie in solutie; altfel, pentru a elimina ramurile 
 +    in care de exemplu {2, 1} se va genera dupa ce s-a generat {1, 2} (adica 
 +    ar fi duplicat), vom folosi doar elementele din domeniu care sunt mai mari 
 +    decat ultimul element adaugat in solutie (solution[step - 1]) */ 
 +    unsigned i = step > 0 ? solution[step - 1] + 1 : 0; 
 +    for (; i < domain.size();​ ++i) { 
 +        solution[step] = i; 
 +        back(step + 1, stop, domain, solution); 
 +    }
 } }
  
 +int main() {
 +    /* dupa ce citim n si k initializam domeniul cu valorile de la 1 la n,
 +    iar solutia este initializata cu un vector de k elemente (fiindca o
 +    combinare de "n luate cate k" are k elemente) */
 +    std::​vector<​int>​ domain(n), solution(k);​
 +    for (int i = 0; i < n; ++i) {
 +        domain[i] = i + 1;
 +    }
 +
 +    back(0, k, domain, solution);
 +}
 </​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 $%$.+În această soluție ne bazăm pe faptul că toate combinările pot fi generate în 
 +ordine crescătoare,​ adică soluția {1, 3, 4} e echivalentă cu {4, 1, 3}.
  
-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. +<note>
-</spoiler>+
  
 +Această soluție este optimă întrucât toate soluțiile generate sunt corecte (de aceea funcția check întoarce true). Deoarece problema cere obținerea tuturor combinărilor,​ aceasta complexitate nu poate fi mai mică de Combinări(n,​ k).
  
-== Complexitate == +</​note>​
-  * **complexitate temporala**:​ $T = O(n)$ +
-    * explicatie: avem o singura parcurgere in care construim tabloul dp +
-    * se poate obtine $T=O(log n)$ folosind exponentiere pe matrice! +
-  * **complexitate spatiala**: $S = O(n)$  +
-    * explicatie: stocam tabloul dp +
-    * se poate ontine $S = O(1)$ folosind exponentiere pe matrice! ​+
  
-===== Tehnici folosite in DP ===== +</​spoiler>​
-De multe ori este nevoie sa folosim cateva tehnici pentru a obtine performanta maxima cu recurenta gasita.+
  
-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.+=== Complexitate ===
  
-==== Exponentiere pe matrice pentru recurente liniare ==== +Soluția va avea următoarele complexități:
-=== Recurente liniare=== +
-O recurenta liniara in contextul laboratorului de DP este de forma: +
-  * $dp[i] = \sum_{k=1}^{KMAX} c_k * dp[i - k]$ +
-    * pentru **KMAX o constanta** +
-    * de obicei, KMAX este foarte mica comparativ cu dimensinea n a problemei +
-    * $c_k$ constante reale (unele pot fi nule)+
  
-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}$.+  * complexitate temporală : $T(n)=O(Combinari(n,​ k))$ 
 +  ​complexitate spatială : $S(n)=O(n+k)=O(n)$ 
 +    ​explicație : $k <= n$, deci $O(n+k)=O(n)$
  
-<spoiler Complexitate recurente liniara>  +==== Problema șoricelului ====
-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 ...). +
-  * **complexitatea temporala** : $ T =O(n * KMAX) O(n) $ +
-    * reamintim ca acea valoarea KMAX este o constanta foarte mica in compartie cu n (ex. KMAX < 100) +
-  * **complexitatea spatiala** : $ S O(n) $+
  
-    * am presupus ca avem nevoie sa retinem doar tabloul dp   +=== Enunț ===
-</​spoiler>​+
  
-=== Exponentiere pe matrice ​=== +Se dă un număr N și o matrice ​pătratică de dimensiuni N x N în care elementele 
-Facem urmatoarele notatii: +egale cu 1 reprezintă ziduri ​(locuri prin care nu se poate trece)iar cele egale 
-  * $S_i$ = starea la pasul i +cu 0 reprezintă spații goaleAceastă matrice are un șoricel în celula (00) ș
-    * $S_i = (dp[i - k + 1], dp[i - k + 2], ...dp[- 1], dp[i])$ +o bucată ​de brânză în celula ​(N - 1, - 1). Scopul șoricelului e să ajungă la 
-  * $S_k$ = starea initiala (in care cunoaste cele k cazuri ​de baza) +bucata de brânză. Afișați toate modurile în care poate face asta știind că 
-    * $S_k = (dp[1]dp[2], ..., dp[k-1], dp[k])$ +acesta poate merge doar în dreapta sau în jos cu câte o celulă la fiecare pas.
-  * $C$ = matrice ce coeficienti constanti +
-    * are dimensiune $KMAX * KMAX$  +
-    * putem pune constante in clar +
-    * putem pune constantele $c_k$ care tin de problema curenta+
  
 +=== Exemple ===
  
 +<spoiler Exemplu 1>
  
-== Algoritm naiv == +  ​2 
-Putem formula problema astfel: +  * 
-  ​$S_k$ = este starea initiala +  ​* 0 0
-  * pentru a obtine starea urmatoare, aplicam algoritmul urmator +
-     * $S_i = S_{i-1}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$.+
  
-\begin{gather} +Există ​drum posibil:
- ​\begin{bmatrix} dp[i - k + 1] &  ... & dp[i-1] &  dp[i] \\ \end{bmatrix} =+
  
 +  * (0, 0)->(1, 0)->(1, 1)
  
-\begin{bmatrix} dp[i - k] &  ... & dp[i-2] &  dp[i-1] \\ \end{bmatrix} ​ +</​spoiler>​ 
-\begin{bmatrix} + 
-   0 & 0 &... & 0 & 0 & c_{k}\\ +<spoiler Exemplu ​2> 
-   1 & &... & & c_{k-1}\\ + 
-   ​&... & & 0 & c_{k-2}\\ +  * 3 
-   ... & ... & ... & ... & ...\\ +  ​* ​0 0 0 
-   &... & 1 & & c_{2}\\ +  ​* ​0 1 0 
-   0 & 0 &... & 0 & 1 & c_{1}\\ +  ​* ​0 0 0
-   ​\end{bmatrix} +
-\end{gather}+
  
 +Există 2 drumuri posibile:
  
 +  * (0,​0)->​(0,​1)->​(0,​2)->​(1,​2)->​(2,​2)
 +  * (0,​0)->​(1,​0)->​(2,​0)->​(2,​1)->​(2,​2)
  
-<spoiler Explicatie determinare C> 
-  * ultima coloana contine toti coeficientii $c_k$ intrucat $dp[i] = \sum_{k=1}^{KMAX} c_k * dp[i - k]$ 
-  * celelalte coloane contin doar cate o valoare nenula 
-    * pe coloana j  vom avea valoarea 1 pe linia $j+1$ ($j = 1 : KMAX - 1$) 
-      * cum obtinem, 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$  
-        * copierea se realizeaza prin inmultirea 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$) 
-          * deci s-a deplasat la stanga cu o pozitie! ​ 
-    * in noua stare, noua pozitie este deplasata cu o unitate la stanga fata de starea precedenta 
-      * de aceea pe coloana $j$, vrem sa avem elementul 1 pe linia $j + 1$ ($j = 1 : KMAX - 1$) 
-      * cand inmultim $S_{i-1}$ cu coloana $C_j$ **dorim sa** 
-        * ce copiam? 
-          * valoarea $dp[i - KMAX + j]$ din $S_{i-1}$ in $S_{i}$ 
-          * adica sa copiam a j-a valoare de pe linie  
-        * unde copiam? 
-          * de pe pozitia $j + 1$ pe pozitia $j$  ​ 
 </​spoiler>​ </​spoiler>​
  
-== Exponentiere logaritmica pe matrice == +<spoiler Exemplu 3>
-Algoritmul naiv de mai sus are dezavantajul ca are tot o complexitate temporala $O(n)$.+
  
-Sa executam cativa pasi de inductie pentru a vedea cum este determinat $S_i$. +  * 4 
-$$S_i = S_{i-1}C$$ +  * 0 0 0 
-$$S_i = S_{i-2}C^2$$ +  * 0 1 1 0 
-$$S_i = S_{i-3}C^3$$ +  * 0 0 0 0 
-$$...$$ +  * 0 0 0 0
-$$S_i = S_{k}C^{i -k}$$+
  
 +Există 4 drumuri posibile:
  
-In laboratorul ​2 (Divide et Imperaam invatat ca putem calcula $ x ^ n $ in timp logaritmic. Deoarece si inmultirea matricilor este asociativaputem calcula $C ^ n$ in timp logaritmic.+  * (0,​0)->​(1,​0)->​(2,0)->(2,1)->(2,2)->​(2,​3)->​(3,​3) 
 +  * (0,​0)->​(1,​0)->​(2,​0)->​(2,​1)->​(2,​2)->​(3,​2)->​(3,​3) 
 +  * (0,​0)->​(1,​0)->​(2,​0)->​(2,​1)->​(3,​1)->​(3,​2)->​(3,​3) 
 +  * (0,​0)->​(1,​0)->​(2,​0)->​(3,​0)->​(3,​1)->​(3,​2)->​(3,​3)
  
-Obtinem astfel o solutie cu urmatoarele complexitati:​ +</​spoiler>​
-  * ** complexitate temporala **: $T = O(KMAX^3 * log(n))$ +
-    * explicatie  +
-      * facem doar $O(log n)$ pasi, dar un pas implica inmultire de matrice +
-      * o inmultire de matrice patratica de dimensiune KMAX are $KMAX^3$ operatii +
-    * aceasta metoda este eficienta cand $KMAX << n$ (KMAX este mult mai mic decat n) +
-  * ** complexitatea spatiala **: $S = O(KMAX^3)$  +
-    * explicatie  +
-      * 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) ​=== +=== Soluții ​===
-Dupa cum am vazut mai sus, in problema cu garduri data de Gigel solutia este o recurenta liniara: +
-  *  $dp[1] = dp[2] = dp[3] = 1$; $d[4]=2$; +
-  *  $dp[i] = dp[i - 1] + dp[i - 4]$, pentru $i > 4$+
  
-== Exponentiere rapida :p == +=== Backtracking ​(transmitere prin referință) === 
-  * $ k = 4 $ +<spoiler Implementare>​
-  * $S_4 = (dp[1], dp[2], dp[3], dp[4]) = (1, 1, 1, 4)$ +
-  * $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: ​  +
-\begin{gather}  +
-   C = \begin{bmatrix} +
-   0 & 0 & 0 & 1\\ +
-   1 & 0 & 0 & 0\\ +
-   0 & 1 & 0 & 0\\ +
-   0 & 0 & 1 & 1\\ +
-   ​\end{bmatrix} +
-\end{gather}+
  
-<spoiler Implementare in 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. 
-  
 <code cpp> <code cpp>
  
-#define MOD  1009 +bool check(std::​vector<​std::​pair<​int,​ int> > &​solution,​ int walls[100][100]) { 
-#define KMAX 4+    for (unsigned i = 0; i < solution.size() - 1; ++i) { 
 +        /* line_prev si col_prev reprezinta celula in care se afla soricelul la 
 +        pasul i; line_next si col_next reprezinta celula in care se afla 
 +        la pasul i + 1; trebuie sa fim siguri ca soricelul nu a ajuns pe zid 
 +        si ca urmatoarea celula este sub sau in dreapta celulei curente */ 
 +        int line_prev = solution[i].first;​ 
 +        int line_next = solution[i + 1].first; 
 +        int col_prev = solution[i].second;​ 
 +        int col_next = solution[i + 1].second;
  
-// C = A +        ​/* walls[x][y== 1 inseamna ca este zid pe linia xcoloana y */ 
-void multiply_matrix(int A[KMAX][KMAX], int B[KMAX][KMAX], int C[KMAX][KMAX]) { +        if (walls[line_prev][col_prev== 1 || 
-    int tmp[KMAX][KMAX];+                !((line_next == line_prev + 1 && col_next == col_prev) || 
 +                (line_next == line_prev && col_next == col_prev + 1))) { 
 +            ​return false; 
 +        } 
 +    }
  
-    ​// tmp = A * B +    ​return true
-    for (int i = 0i < KMAX; ++i) { +}
-        for (int j = 0; j < KMAX; ++j) { +
-            unsigned long long sum = 0; // presupun ca suma  incape pe 64 de biti+
  
-            for (int k = 0; k KMAX; ++k) { +void printSolution(std::​vector<​std::​pair<​int, int> > &​solution) { 
-                sum += 1LL * A[i][k] * B[k][j]+    for (std::pair<int, int> s : solution) { 
-            }+        ​std::​cout << "​("​ << s.first << ","​ << s.second << "​)->";​ 
 +    } 
 +    std::cout << "​\n"​
 +}
  
-            tmp[i][j] = sum % MOD;+void back(std::​vector<​std::​pair<​int,​ int> > &​domain,​ int walls[100][100]
 +        std::​vector<​std::​pair<​int,​ int> > &​solution,​ int max_iter) { 
 +    /* daca am facut "​max_iter"​ pasi ma opresc si verific daca este corecta 
 +    solutia */ 
 +    if (solution.size() ​== max_iter) { 
 +        if(check(solution,​ walls)) { 
 +            printSolution(solution);
         }         }
 +        return;
     }     }
  
-    //  C = tmp +    /* avand domeniul initializat cu toate celulele din matrice, incercam sa 
-    ​memcpy(C, tmp, sizeof(tmp));+    adaugam oricare dintre aceste celule la solutie, verificand la final daca 
 +    solutia este buna *
 +    ​for (unsigned int i = 0; i < domain.size();​ ++i) { 
 +        /* pastram elementul curent pentru a-l readauga in domeniu dupa 
 +        apelarea recursiva */ 
 +        std::​pair<​intint> ​tmp = domain[i];​ 
 + 
 +        /* adaugam elementul curent la solutia candidat */ 
 +        solution.push_back(domain[i]);​ 
 +        /* stergem elementul curent din domeniu */ 
 +        domain.erase(domain.begin() + i); 
 + 
 +        /* apelam recursiv backtracking */ 
 +        back(domainwalls, solution, max_iter);​ 
 + 
 +        /* adaugam elementul sters din domeniu inapoi */ 
 +        domain.insert(domain.begin() + i, tmp)
 +        /* stergem elementul curent din solutia candidat pentru a o forma pe 
 +        urmatoarea */ 
 +        solution.pop_back(); 
 +    }
 } }
  
-// R = C^p +int main() { 
-void power_matrix(int C[KMAX][KMAX]int pint R[KMAX][KMAX]{ +    ​/* initializam domeniul si solutia ca vectori de perechi de int-uri; 
-    // tmp = I (matricea identitate) +    ​domeniul va contine initial toate perechile de indici posibile din 
-    int tmp[KMAX][KMAX]+    matrice ​((00)(0, 1... (n - 1, n - 1)), iar solutia va fi initial 
-    for (int i = 0; i < KMAX; ++i) { +    ​vida *
-        for (int j = 0; j < KMAX; ++j) { +    ​std::​vector<​std::​pair<​int, int> > domain, solution; 
-            ​tmp[i][j] = (i == j) ? 1 : 0;+ 
 +    fin >> n
 +    for (i = 0; i < n; ++i) { 
 +        for (j = 0; j < n; ++j) { 
 +            ​/* walls[i][j] == 1 daca pe pozitia ​(ij) este zid; altfel */ 
 +            fin >> walls[i][j];​ 
 +            domain.push_back({i,​ j});
         }         }
     }     }
  
-    ​while (p != 1) { +    ​/* apelam back cu domeniul format initial, cu matricea de ziduri, cu 
-        ​if ​ (p % == 0) { +    solutia vida si cu numarul maxim de iteratii ​2 * n - pentru ca 
-            ​multiply_matrix(CCC);     ​// C = C*C +    ​mergand doar in dreapta si in jos, in * n - 1 pasi va ajunge din 
-            ​p ​/= 2;                       // ramane ​de calculat C^(p/2) +    (0, 0) in (n - 1, n - 1) */ 
-        else +    back(domainwallssolution, 2 * n - 1); 
-            // reduc la cazul anterior+
-            ​multiply_matrix(tmp, Ctmp); // tmp tmp*C +</code> 
-            ​--p; ​                         ​// ramane de calculat C^(p-1)+ 
 +Apelarea initiala (din "int main") se face astfel: "​back(domain,​ walls, solution, 2 n - 1);", unde domain reprezinta un vector cu perechi in care sunt toate celulele matricii, solution este un vector gol, walls este matricea care ne arata daca este zid sau nu pe o anumita pozitie, iar 2 * n - 1 e numarul e pasi in care ar trebui ca soricelul sa ajunga la branza pe oriunde ar merge. 
 + 
 +</spoiler>​ 
 +=== Complexitate === 
 + 
 +Soluția va avea următoarele complexități:​ 
 + 
 +  * complexitate temporală : $T(n)=O(Aranjamente(n^2, 2n - 1))$ 
 +    * explicație:​ Initial in domeniu avem $n^2$ valori. Noi dorim sa generam toate submultimile ordonate ​de cate $2n-1$ elemente. Acestea sunt tocmai aranjamentele de $n^2$ luate cate $2n-1$. 
 +  * complexitate spatială : $S(n)=O(n^2)
 +    * explicație:​ Trebuie să stocăm informație despre drum, care are $2n-1$ celule; stocăm domeniul care are $n^2$ elemente 
 + 
 +=== Backtracking (tăierea ramurilor nefolositoare) === 
 +<spoiler Implementare>​ 
 + 
 +<code cpp> 
 + 
 +bool check(std::​vector<​std::​pair<​int,​ int> > &​solution) { 
 +    return true; 
 +} 
 + 
 +void printSolution(std::​vector<​std::​pair<​int,​ int> > &​solution) ​
 +    for (std::​pair<​int,​ int> s : solution) { 
 +        ​std::​cout << "(" << s.first << "," << s.second << ")->"; 
 +    } 
 +    std::cout << "​\n";​ 
 +
 + 
 +void back(int step, int stop, int walls[100][100],​ 
 +        std::​vector<​std::​pair<​int,​ int> > &​solution,​ int line_moves[2],​ 
 +        int col_moves[2]) { 
 +    ​/* ne oprim dupa ce am ajuns la pasul "​stop"​ si verificam daca solutia este 
 +    corecta */ 
 +    if (step == stop) { 
 +        /deoarece am eliminat ramurile nefolositoare am ajuns la o solutie care 
 +        sigur este corecta */ 
 +        if(check(solution)) { 
 +            printSolution(solution);​
         }         }
 +        return;
     }     }
  
-    // avem o parte din rezultat ​in C si o parte in tmp +    /* daca este primul pas stiu ca soricelul este in pozitia ​(00*/ 
-    multiply_matrix(Ctmp, R);           /rezultat ​tmp C +    if (step == 0) { 
-}+        /adaugam (0, 0) la solutia candidat */ 
 +        ​solution.push_back({0,​ 0});
  
-int garduri_rapide(int n) { +        ​/* apelam backtracking recursiv la pasul urmator *
-    ​// cazurile de baza +        back(step + 1, stop, walls, solution, line_moves, col_moves);
-    ​if ​(n <= 3) return ​1+
-    if (n == 4return 2;+
  
-    ​// construiesc matricea C +        ​/* scoatem (0, 0) din solutie */ 
-    int C[KMAX][KMAX] = { {0, 0, 0, 1}, +        ​solution.pop_back();​ 
-                          {1, 0, 0, 0}, +        ​return;​ 
-                          {0, 1, 0, 0}, +    }
-                          {0, 0, 1, 1}}; +
-   // vreau sa aplic formula S_n = S_4 * C^(n-4)+
  
-   // ​C^(n-4+    /* sunt doar doua mutari pe care le pot face intr-un pas: dreapta si jos; 
-   power_matrix(C, - 4, C);+    acestea sunt encodate prin vectorii de directii line_moves[2] = {0, 1} si 
 +    col_moves[2] = {1, 0} care reprezinta la indicele 0 miscarea in dreapta, iar 
 +    la indicele 1 miscarea in jos */ 
 +    for (unsigned int i = 0; i < 2; ++i) { 
 +        ​/* cream noua linie si noua coloana cu ajutorul vectorilor de directii */ 
 +        int new_line ​solution.back().first + line_moves[i];​ 
 +        int new_col = solution.back().second + col_moves[i];​ 
 +        int = (stop + 1/ 2;
  
-   // sol = S_4 C = dp[n] (se afla pe ultima pozitie din S_n+        ​/* daca linia si coloana sunt valide (nu ies din matrice) si nu este 
-   ​// deci voi folosi ultima coloana ​din C+        zid pe pozitia lor, putem continua pe acea celula */ 
-   ​int sol = 1 C[0][3] + 1 * C[1][31 * C[2][3] + * C[3][3]; +        if (new_line < && new_col < n && walls[new_line][new_col] == 0) { 
-   return sol % MOD;+            /* adaugam noua celula in solutia candidat; 
 +            NOTE: {new_line, new_col} este echivalent cu 
 +            std::​pair<​int,​ int>(new_line, new_col) si se numeste "​initializer 
 +            list"feature in C++11 */ 
 +            ​solution.push_back({new_line,​ new_col});​ 
 + 
 +            ​/* apelam backtracking recursiv la pasul urmator */ 
 +            back(step + 1, stop, walls, solution, line_moves, col_moves);​ 
 + 
 +            /* scoatem celula adaugata ​din solutie */ 
 +            solution.pop_back()
 +        } 
 +    } 
 +
 + 
 +int main() { 
 +    /initializam solutia ca vector de perechi de int-uri */ 
 +    std::​vector<​std::​pair<​int,​ int> > solution; 
 + 
 +    fin >> n; 
 +    for (i = 0; i < n; ++i) { 
 +        for (j = 0; j < n; ++j) { 
 +            /* citim matricea zidurilor; ​pentru zid, 0 altfel ​*
 +            fin >> walls[i][j]
 +        } 
 +    } 
 + 
 +    /* apelam back cu step = 0, stop = 2 * n - deoarece in 2 n - 1 
 +    pasi soricelul va ajunge la branza, vectorul de ziduri, vectorul in 
 +    care vom stoca solutia, vectorii de directii line_moves[2] = {0, 1} si 
 +    col_moves[2] = {1, 0}nu avem nevoie de domeniu deoarece folosind 
 +    ​vectorii de directii vom sti din ultima pozitie pusa in solutie cele 
 +    doua solutii in care putem merge, astfel domeniul nostru va fi alcatuit 
 +    din doua solutii la fiecare pas (daca ultima pozitie din solutie a fost 
 +    (5, 7) => domeniul pasului curent = {(5 + 0, 7 + 1) si (5 + 1, 7 + 0)} 
 +    care este egal cu {(5, 8), (6, 7)}. */ 
 +    back(0, 2 * n - 1, walls, solution, line_moves, col_moves);
 } }
  
 </​code>​ </​code>​
  
-<​note>​ +Pentru aceasta abordare la fiecare pas de backtracking vom merge doar in doua directii, in loc sa mergem in oricare celula din matricelucru care imbunatateste semnificativ complexitatea temporala.
-Remarcati faptul ca in functia de inmultire se foloseste o matrice temporara $tmp$. Motivul este ca vrem sa apelam functia $multiply(CC, C)$, unde C joaca atat rol de intrare cat si de iesire. Daca am pune rezultatele direct ​in Catunci am strica inputul inainte sa obtinem 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. 
- 
-</​note>​ 
 </​spoiler>​ </​spoiler>​
  
-<spoiler Comparatie solutii (studiu de caz pentru curiosi)>​ +=== Complexitate ​===
-In arhiva ** demo-lab04.zip ** gasiti o sursa completa in care se realizeaza:​ +
-  * o verificare a faptului ca cele 2 implementari (** gardurile_lui_Gigel** si **garduri_rapide**) produc aceleasi rezultate +
-  * un benchmark in care cele 2 implementari sunt comparate +
-    * pe sistem uzual (laptop) s-au obtinut urmatoarele rezulate: +
-<code bash> +
-test case: varianta simpla +
-100000000 sol 119; time 0.984545 s +
-test case: varianta rapida +
-100000000 sol 119; time 0.000021 s+
  
 +Soluția va avea urmatoarele complexitati:​
  
-test casevarianta simpla +  * complexitate temporală ​$T(n)=O(2^{2n})$ 
-n = 1000000000 sol = 812; time = 9.662377 s +      * explicațieavem de urmat un șir de $2n-1$ mutari, iar la fiecare pas avem 2 variante posibile 
-test casevarianta rapida +  complexitate spatiala ​: $S(n)=O(n)$ 
-n = 1000000000 sol = 812; time = 0.000022 s +      * explicație:​ stocam maximum $2n-1$ căsuțe
-</​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.+
  
-</​spoiler>​ +===== Pool probleme (pentru prezentări) ======
-  +
-===== Exercitii ​===== +
-<​note>​ +
-In acest laborator vom folosi scheletul de laborator din arhiva {{pa:​new_pa:​skel-lab04.zip}}. +
-</​note>​+
  
-=== DP or math? === +======= 1Word Search =======
-Fie un sir de **numere naturale strict pozitive**. Cate **subsiruri** (submultimi nevideau suma numerelor **para**?+
  
-<​note>​ +**Enunt:** Se dă o matrice de dimensiuni ''​m × n''​ formată din litere și un cuvânt ''​word''​Determinați dacă acest cuvânt poate fi format în matrice  
-**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$. +Cuvântul se construiește unind litere din celule adiacente (pe orizontală sau verticală)Nu aveți voie să folosiți aceeași celulă de două ori în formarea aceluiași cuvânt.
-</​note>​+
  
 +**Date de intrare:** O matrice de caractere de dimensiuni ''​m × n''​ și un șir de caractere ''​word''​.
  
-Task-uri: +**Date de ieșire:** Se afișează ''​true''​ dacă cuvântul există în matricealtfel ''​false''​.
-  ​Se cere o **solutie folosind DP**. +
-  * Inspectand recurenta gasita la punctul precedentincercati sa o inlocuiti cu o **formula matematica**. +
-  * Care este **complexitatea** pentru fiecare solutie (timp + spatiu)? Care este mai buna? De ce? :D+
  
-Deoarece rezultatul poate fi prea mare, se cere **restul impartirii** lui la $1000000007$ ($10^9 + 7$).+Problema ​se poate testa la:   
 +https://​leetcode.com/​problems/​word-search/​
  
-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.+======= 2Combination Sum II =======
  
 +**Enunt:** Se dă un șir de numere (care poate conține duplicate) și un număr țintă ''​target''​. Găsiți toate combinațiile unice de elemente din șir a căror sumă este exact ''​target''​.  ​
 +Fiecare element de pe o anumită poziție din șir poate fi folosit cel mult o dată într-o combinație. Setul final de soluții nu trebuie să conțină combinații duplicate.
  
-<spoiler Exemplu 1> +**Date de intrare:** Un vector de numere întregi ''​candidates''​ șun număr întreg ''​target''​.
-$n = 3$ +
-|i|1|2|3| +
-|v|2|6|4|+
  
-Raspuns$7$+**Date de ieșire:** O listă de liste de numere întregi, reprezentând combinațiile unice valide.
  
-ExplicatieToate subsirule posibile sunt +Problema se poate testa la:   
-  * $[2]$ +https://​leetcode.com/​problems/​combination-sum-ii/
-  * $[2, 6]$ +
-  * $[2, 6, 4]$ +
-  * $[2, 4]$ +
-  * $[6]$ +
-  * $[6, 4]$ +
-  * $[4]$ +
-Toate subsirurile de mai sus au suma para. +
-</spoiler>+
  
 +======= 3) Gray Code =======
  
-<spoiler Exemplu ​2+**Enunt:** Codul Gray de ordin ''​n''​ este o secvență ce conține toate cele $2^nșiruri binare de lungime ''​n'',​ cu proprietatea că oricare două șiruri consecutive diferă prin exact un singur bit.  ​ 
-$n = 3$ +Cerința este să generațo astfel de secvență validă pentru un ''​n''​ dat. 
-|i|1|2|3| +
-|v|2|1|3|+
  
-Raspuns$3$+**Date de intrare:** Un număr întreg ''​n''​ — lungimea șirurilor de biți.
  
-Explicatie: Toate subsirule posibile sunt +**Date de ieșire:** Se afișează secvența de $2^nnumere (în format zecimal sau binar)respectând regula codului Gray.
-  ​$[2]$ +
-  ​$[2, 1]$ +
-  ​$[2, 1, 3]$ +
-  ​* $[2, 3]$ +
-  * $[1]$ +
-  * $[13]$ +
-  * $[3]$+
  
-Subsirurile cu suma para sunt$[2]$, $[2, 1, 3]$, $[1, 3]$.+Problema se poate testa la  
 +https://​cses.fi/​problemset/​task/​2205
  
-</​spoiler>​+======= 4) Sudoku Solver =======
  
 +**Enunt:** Vi se cere să scrieți un program care rezolvă un puzzle Sudoku clasic (9 × 9) prin completarea celulelor goale.  ​
 +Pentru ca soluția să fie validă, trebuie respectate regulile clasice: fiecare cifră de la 1 la 9 trebuie să apară o singură dată pe fiecare rând, pe fiecare coloană și în fiecare dintre cele nouă careuri 3 × 3.
  
-<spoiler Exemplu 3> +**Date de intrare:** O matrice 9 × 9 de caractere reprezentând tabla de Sudoku inițială (celulele goale sunt marcate cu caracterul ''​.''​).
-$n = 3$ +
-|i|1|2|3| +
-|v|3|2|1|+
  
-Raspuns$3$+**Date de ieșire:** Matricea 9 × 9 completată cu soluția corectă.
  
-ExplicatieToate subsirule posibile sunt +Problema se poate testa la:   
-  * $[3]$ +https://​leetcode.com/​problems/​sudoku-solver/​
-  * $[3, 2]$ +
-  * $[3, 2, 1]$ +
-  * $[3, 1]$ +
-  * $[2]$ +
-  * $[2, 1]$ +
-  * $[1]$+
  
-Subsirurile cu suma para sunt: $[3, 2, 1]$, $[3, 1]$, $[2]$.+======= 5) Palindrome Partitioning =======
  
-</​spoiler>​+**Enunt:** Se dă un șir de caractere ''​s''​. Se cere să împărțiți șirul în fragmente, astfel încât fiecare fragment (subșir) rezultat să fie un palindrom. ​  
 +Returnați toate aceste partiționări posibile.
  
-<​note>​ +**Date de intrare:** Un șir de caractere ''​s''​.
-Moralaexista probleme pentru care gasim o solutie cu DP, dar pentru care poate exista si alte solutii mai bune (am ignorat citirea).+
  
-In problemele de numarat, exista o **sansa** buna sa putem gasi (si) o formula matematicacare poate fi implementata intr-un mod mai eficient decat recurenta DP +**Date de ieșire:** O listă de liste de șiruri de caractereunde fiecare listă interioară reprezintă ​partiționare validă.
-</​note>​+
  
-<spoiler Hint> +Problema se poate testa la:  ​ 
-Dar cate subsiruri au suma **impara**?​ +https://​leetcode.com/​problems/​palindrome-partitioning/
-</spoiler>+
  
-<spoiler Solutie>​ +======= 6) Knight'​s Tour =======
-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>​+
  
-=== Expresie booleana === +Enunt: ​Se cere să găsiți ​parcurgere validă a unei table de șah de dimensiuni 8 × 8 folosind un cal, astfel încât acesta să viziteze fiecare celulă a tablei exact o singură datăMutările trebuie să respecte regulile clasice de șah pentru cal (în formă de "​L"​).
-Se da expresie booleana corecta cu n termeniFiecare 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**).+Date de intrare: Două numere întregi ''​x''​ și ''​y'', ​care indică poziția inițială a calului pe tablă ​(coloana și rândul).
  
-Deoarece rezultatul poate fi prea mare, se cere **restul impartirii** lui la $1000000007$ ​($10^9 + 7$).+Date de ieșire: O matrice 8 × 8 în care fiecare celulă conține numărul pasului ​(de la 1 la 64la care a fost vizitată respectiva poziție.
  
-<​note>​ +Problema se poate testa la:
-In schelet vom codifica cu valori de tip char cele 5 stringuri: +
-  * **false**: '​F'​ +
-  * **true**: '​T'​ +
-  * **and**: '&'​ +
-  * **or**: '​|'​ +
-  * **xor**: '​^'​+
  
-Functia pe care va trebui sa o implementati voi va folosi variabilele **n** (numarul de termeni) si ** expr** (vectorul cu termenii expresiei). +https://​cses.fi/​problemset/​task/1689
-</note>+
  
 +===== Extra =====
  
-<​spoiler ​Exemplu 1+==== Exerciții ==== 
-$n = 5$ si $expr = ['​T',​ '&',​ '​F',​ '​^',​ '​T'​]$ (expr = [** true and false xor true**])+<​spoiler ​Aranjamente
 +Fie N și K două **numere naturale strict pozitive**. Se cere afișarea tuturor aranjamentelor de N elemente luate cate K din mulțimea {1, 2, ..., N}.
  
-Raspuns$2$+**Exemplu 1:** 
 +Fie N = 3, K = => M = {1, 2, 3}
  
-ExplicatieExista ​moduri corecte de a paranteza expresia astfel incat sa obtinem rezultatul ​**true** (1). +Soluție: 
-  * $ T&(F^T+  * {1, 2
-  * $ (T&F)^T +  ​{1, 3} 
-</spoiler>+  ​{2, 1} 
 +  ​{2, 3} 
 +  ​{3, 1} 
 +  * {3, 2} 
 + 
 +<​note>​ 
 +Se dorește o complexitate ​$T(n, k= A(n,k)$. 
 +</note>
  
-<spoiler Hint> +**Hint:** Folosiți-vă de problema ​**Permutări**. 
-    Complexitate temporală dorita este $O(n ^ 3)$. + 
-     +<​note>​ 
-    Optional, se pot defini functii ajutatoare precum ​**is_operand****is_operator**,​ **evaluate**.+Soluțiile se vor genera în ordine lexico-grafica! 
 + 
 +Checkerul așteaptă să le stocați în această ordine. 
 +</​note>​
 </​spoiler>​ </​spoiler>​
  
-<note tip+<spoiler Submulțimi
-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**.+Fie N un **număr natural strict pozitiv**. Se cere afișarea tuturor submulțimilor mulțimii {1, 2, ..., N}.
  
 +**Exemplu 1:**
 +Fie N = 4 => M = {1, 2, 3, 4}
  
-Pentru problema** DP or math?** partitionam toate subsirurile dupa critieriul **paritatii sumei subsirului** (cate sunt pare/​impare).\\ ​ +Soluție: 
-Pentru problema **expresie booleana** partitionam **toate parantezarile posibile dupa rezultatul lor** (cate dau true/​false).+{} - mulțimea vidă 
 +{1} 
 +{1, 2} 
 +{1, 2, 3} 
 +{1, 2, 3, 4} 
 +{1, 2, 4} 
 +{1, 3} 
 +{1, 3, 4} 
 +{1, 4} 
 +{2} 
 +{2, 3} 
 +{2, 3, 4} 
 +{2, 4} 
 +{3} 
 +{3, 4} 
 +{4}
  
 +<​note>​
 +Se dorește o complexitate $T(n) = O(2^n)$.
 </​note>​ </​note>​
-=== Bonus === 
-Asistentul va alege una dintre problemele din sectiunea Extra. 
  
-<spoiler Hint> +**Hint:** Folosiți-vă ​de problema **Combinari**.
-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. +
-</​spoiler>​+
  
-=== Extra === +<note
-<spoiler Extraterestrii+Soluțiile se vor genera în ordine lexico-grafica!
-Rezolvati problema [[https://​www.hackerrank.com/​contests/​test-practic-pa-2017-v1-plumbus/​challenges/​test-1-extraterestrii +
-| extraterestrii]] de la Test PA 2017. +
-</​spoiler>​+
  
-<spoiler Secvente> +Checkerul așteaptă să le stocați în această ordine. 
-Rezolvati problema [[https://​www.hackerrank.com/​contests/​test-practic-pa-2017-v1-plumbus/​challenges/​test-1-secvente +</note>
-| Secvente]] de la Test PA 2017.+
 </​spoiler>​ </​spoiler>​
  
 +<spoiler Problema damelor>
 +Problema damelor (sau problema reginelor) tratează plasarea a 8 regine de sah pe o tablă de șah de dimensiuni 8 x 8 astfel încat să nu existe două regine care se amenință reciproc. Astfel, se caută **o soluție** astfel încât nicio pereche de doua regine să nu fie pe același rând, pe aceeași coloană, sau pe aceeași diagonală. Problema cu opt regine este doar un caz particular pentru problema generală, care presupune plasarea a N regine pe o tablă de șah N x N în aceleasi condiții. Pentru această problemă, există soluții pentru toate numerele naturale N cu excepția lui N = 2 si N = 3.
  
 +**Exemplu 1:**
 +Fie N = 5
  
-<spoiler PA Country>​ +Soluție: 
-Rezolvati 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.+|X|-|-|-|-
 +|-|-|X|-|-
 +|-|-|-|-|X| 
 +|-|X|-|-|-| 
 +|-|-|-|X|-| 
 + 
 +X reprezintă o damă, - reprezintă spațiu gol. 
 + 
 +**Hint:** E nevoie să facem backtracking pe matrice sau e suficient pe vector? 
 + 
 +<​note>​ 
 +Se va caută o singură soluție ​ (**oricare** soluție corectă), care va fi returnată sub forma unui vector cu $n + 1$ elemente. 
 + 
 +Soluția este $sol[0], sol[1], ..., sol[n]$, unde $sol[i]$ = coloana unde vom plasa regina de pe linia i. 
 + 
 +Elementul 0 este nefolosit, dorim să păstrăm convenția cu indexare ​de la 1. 
 +</​note>​
 </​spoiler>​ </​spoiler>​
  
 +<spoiler Generare de șiruri>
 +Vi se dă o listă de caractere și o lista de frecvențe (pentru caracterul de pe
 +poziția i, frecvența de pe poziția i). Vi se cere să generați toate șirurile
 +care se pot forma cu aceste caractere și aceste frecvențe știind că nu pot fi
 +mai mult de K apariții consecutive ale aceluiași caracter.
  
 +**Exemplu 1:**
 +Fie caractere[] = {'​a',​ '​b',​ '​c'​},​ freq[] = {1, 1, 2}, K = 5
  
-  +Soluție: 
-<spoiler iepuri> +  * abcc 
-Rezolvati pe infoarena problema [[http://​infoarena.ro/​problema/​iepuri| iepuri]].+  * acbc 
 +  * accb 
 +  * bacc 
 +  * bcac 
 +  * bcca 
 +  * cabc 
 +  * cacb 
 +  * cbac 
 +  * cbca 
 +  * ccab 
 +  * ccba
  
-HintExponentiere logaritmica pe matrice+**Exemplu 2:** 
 +Fie caractere[] = {'​b',​ '​c'​},​ freq[] = {3, 2}, K = 2
  
 Solutie: Solutie:
-  * $dp[0] = X; dp[1] = Y; dp[0] = Z; $ +  * bbcbc 
-  * $dp[i] = (A dp[i-1] + B dp[i-2] + C dp[i-3]) \ \% \ 666013$+  * bbccb 
 +  ​bcbbc 
 +  ​bcbcb 
 +  * bccbb 
 +  * cbbcb 
 +  ​cbcbb
  
-Pentru punctaj maxim, pentru fiecare test se foloseste ecuatia matriceala atasata. +<​note>​ 
-Complexitate:​ $O(T * log(n))$.+Soluțiile se vor genera în ordine lexico-grafica!
  
 +Checkerul așteaptă să le stocați în această ordine.
 +</​note>​
 </​spoiler>​ </​spoiler>​
  
 +<spoiler Problema damelor (AC3)>
 +**Aplicați AC3 pe problema damelor.**
  
-<spoiler Minimum Path Sum> +Algoritmul AC-3 (Arc Consistency Algorithm) este de obicei folosit în probleme de satisfacere a constrângerilor (CSP)Acesta șterge arcele din arborele de stări care sigur nu se vor folosi niciodata.
-Rezolvati pe leetcode problema [[https://​leetcode.com/​problems/​minimum-path-sum/​description/#​| Minimum Path Sum]]. +
-</​spoiler>​+
  
 +AC-3 lucrează cu:
 +    * constrângeri
 +    * variabile
 +    * domenii de variabile
  
-<spoiler Lacusta>​ +O variabilă poate lua orice valoare din domeniul său la orice pasO constrângere este o relație sau o limitare a unor variabile.
-Rezolvati pe infoarena problema [[http://​infoarena.ro/​problema/​Lacusta| Lacusta]]. +
-</​spoiler>​+
  
 +**Exemplu AC-3:**
  
-<spoiler Suma4> +Considerăm A, o variabilă ce are domeniul D(A) = {0, 1, 2, 3, 4, 5, 6} și B o variabila ce are domeniul D(B) = {0, 1, 2, 3, 4}. Cunoaștem constrângerileC1 = "A trebuie să fie impar" și C2 = "A + B trebuie să fie egal cu 5"
-Rezolvati pe infoarena problema [[http://​infoarena.ro/​problema/​Suma4|Suma4]]+ 
-</​spoiler>+Algoritmul AC-3 va elimina în primul rând toate valorile pare ale lui A pentru a respecta C1 =D(A) = {1, 3, 5}. Apoi, va încerca să satisfacă C2, așa că va păstra în domeniul lui B toate valorile care adunate cu valori din D(A) pot da 5 => D(B) = {0, 2, 4}.
  
-<spoiler Subsir>​ +AC-3 a redus astfel domeniile lui A si B, reducând semnificativ timpul folosit de algoritmul backtracking.
-Rezolvati pe infoarena problema [[https://​www.infoarena.ro/​problema/​subsir|subsir]].+
 </​spoiler>​ </​spoiler>​
  
-<​spoiler ​2sah+<​spoiler ​Immortal
-Rezolvati pe infoarena problema ​[[https://​infoarena.ro/​problema/​2sah 2sah]].+[[https://www.infoarena.ro/​problema/​immortal ​Enunt]]
  
-Hint  Exponentiere logaritmica pe matrice+**Solutie:** 
 +[[http://​olimpiada.info/​oji2010/​index.php?​cid=arhiva | OJI 2010 - clasele 11-12]] 
 +</​spoiler>​
  
-O descrie detaliata se afla in [[http://olimpiada.info/oji2015/index.php?​cid=arhiva ​arhiva OJI 2015]].+<spoiler Backtracking problems>​ 
 +Articolul de pe [[https://leetcode.com/tag/​backtracking/| leetcode]] conține o listă cu diverse tipuri de probleme de programare dinamică, din toate categoriile discutate la PA (plus multe altele).
 </​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 
- 
-[1] [[http://​infoarena.ro/​problema/​podm]] 
- 
-[2] [[http://​infoarena.ro/​problema/​kfib]] 
  
 +===== Referințe =====
  
 +[0] Chapter **Backtracking**,​ “Introduction to Algorithms”,​ Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein
pa/laboratoare/laborator-04.1553069112.txt.gz · Last modified: 2019/03/20 10:05 by gabriel.bercaru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0