Differences

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

Link to this comparison view

pa:laboratoare:laborator-01 [2018/03/02 14:15]
traian.rebedea [Turnurile din Hanoi]
pa:laboratoare:laborator-01 [2026/03/04 11:39] (current)
radu.nichita [Pool probleme (pentru prezentări)]
Line 1: Line 1:
-====== Laborator 01: Divide et Impera ​====== +====== Laborator 01: Programare Dinamică (1/2) ======
-Responsabili:​ +
-  * [[visanr95@gmail.com|Radu Vișan]] +
-  * [[cristb@gmail.com|Cristian Banu]] +
-  * [[neatudarius@gmail.com|Darius Neațu]] +
  
 ===== Obiective laborator ===== ===== Obiective laborator =====
 +  * înțelegerea noțiunilor de bază despre programare dinamică
 +  * însușirea abilităților de implementare a algoritmilor bazați programare dinamică.
  
-  * Înțelegerea conceptului teoretic din spatele descompunerii +===== Precizări inițiale ​=====
-  * Rezolvarea de probleme abordabile folosind conceptul ​ de Divide et Impera +
- +
- +
-===== Precizari initiale ​=====+
 <​note>​ <​note>​
-Toate exemplele de cod se gasesc in {{pa:new_pa:demo-lab01.zip}}.+Toate exemplele de cod se găsesc pe pagina [[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​algorithms/​lab01|pa-lab/​algorithms/​lab01]].
  
-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-lab01.zip**, inainte ​de a raporta ca ceva nu merge:D +  * Vă rugăm să compilați ​**DOAR** codul de pe GitHub. Pentru raportarea problemelor,​ contactați unul dintre maintaineri.  
-  * Pentru orice problema legata ​de continutul ​acestei pagini, ​va rugam sa dati email unuia dintre responsabili.+  * Pentru orice problemă legată ​de conținutul ​acestei pagini, ​vă rugam să dați e-mail ​unuia dintre responsabili.
  
-===== Importanţă – aplicaţii practice ​=====+===== Ce este DP? ===== 
 +Similar cu greedy, tehnica de programare dinamică este folosită în general pentru rezolvarea **problemelor de optimizare**.  
 +În continuare vom folosi acronimul **DP (dynamic programming)**.
  
-Paradigma Divide et Impera stă la baza construirii de algoritmi eficienți pentru ​diverse ​probleme+De asemenea, DP se poate folosi și pentru probleme ​în care nu căutăm un optim, cum ar fi **problemele de numărare** ​(vom exemplifica în lab04). 
-  ​Sortări (ex: MergeSort [[http://​www.sorting-algorithms.com/​merge-sort|[1]]],​ QuickSort [[http://​www.sorting-algorithms.com/​quick-sort|[2]]]+===== Aplicatii DP ===== 
-  Înmulțirea numerelor mari (ex: Karatsuba [[http://​en.wikipedia.org/​wiki/​Karatsuba_algorithm|[3]]]) +Programarea dinamică are un **câmp larg de aplicare**, însă la PA ne vom rezuma la câteva aplicații care vor fi menționate pe parcursul laboratoarelor ​și 4. De asemenea, această tehnică va fi folosită și în laboratoarele de grafuri ​(ex. algoritmul Floyd-Warshall - pe care îl veți implementa și la PA; algoritmi pe arbori etc).
-  * Analiza sintactică (ex: parsere top-down [[http://en.wikipedia.org/​wiki/​Top-down_parser|[4]]]) +
-  * Calcularea transformatei Fourier discretă (ex: FFT [[http://en.wikipedia.org/​wiki/​Fast_Fourier_transform|[5]]])+
  
-Un alt domeniu ​de utilizare a tehnicii ​divide et impera ​este programarea paralelă pe mai multe procesoaresub-problemele fiind executate pe mașini diferite.+Programarea dinamică presupune rezolvarea unei probleme prin **descompunerea ei în subprobleme** şi rezolvarea acestora. Spre deosebire ​de divide et impera, ​subproblemele nu sunt disjuncte, ci **se suprapun**.
  
-===== Prezentarea generală ​problemei =====+<spoiler Memoizare and more DP> 
 +Pentru ​evita recalcularea porțiunilor care se suprapun, rezolvarea se face pornind de la cele mai mici subprobleme şi folosindu-ne de rezultatul acestora calculăm subproblema imediat mai mare. Cele mai mici subprobleme sunt numite subprobleme unitare, acestea putând fi rezolvate într-o complexitate constantă, ex: cea mai mare subsecvență dintr-o mulțime de un singur element.
  
-O descriere ​tehnicii D&I: “Divide and Conquer algorithms break the problem into several sub-problems that are similar to the original problem but smaller in sizesolve the sub-problems recursively,​ and then combine these solutions to create a solution to the original ​problem.” [7]+Pentru ​nu recalcula soluțiile subproblemelor ce ar trebui rezolvate de mai multe oripe ramuri diferite, se reține soluția subproblemelor folosind o tabelă (matrice uni, bi sau multi-dimensională în funcție de problemă) cu rezultatul fiecărei subprobleme. Această tehnică se numește **memoizare**.
  
-Deci un algoritm D&I **împarte problema** în mai multe subprobleme similare cu problema inițială şi de dimensiuni mai mici, **rezolva sub-problemele** recursiv şi apoi **combina ​soluțiile** obţinute ​pentru ​a obține ​soluția ​problemei inițiale.+Această tehnică determină ”valoarea” ​soluției pentru ​fiecare din subprobleme. Mergând de la subprobleme mici la subprobleme din ce în ce mai mari ajungem la soluția ​optimă, la nivelul întregii probleme. ”Valoarea” își schimbă înțelesul logic de la o problemă la alta. În problemele de minimizarea costului, ”valoarea” este reprezentată de costul minim. În probleme care presupun identificarea unei componente maxime, ”valoarea” este caracterizată de dimensiunea componentei.
  
-Sunt trei pași pentru aplicarea algoritmului D&I:+După calcularea valorii pentru toate subproblemele se poate determina efectiv mulțimea de elemente care compun soluția. „Reconstrucția” soluţiei se face mergând din subproblemă în subproblemă,​ începând de la problema cu valoarea optimă ​și ajungând în subprobleme unitare. Metoda și recurența variază de la problemă la problemă, dar în urma unor exerciții practice va deveni din ce în ce mai facil să le identificați.
  
-  * **Divide**: împarte problema în una sau mai multe //probleme similare de dimensiuni mai mici//. 
-  * **Impera** (stăpânește):​ rezolva subprobleme recursiv; dacă dimensiunea sub-problemelor este mica se rezolva iterativ. 
-  * **Combină**:​ combină soluțiile sub-problemelor pentru a obține soluția problemei inițiale. 
  
-Complexitatea algoritmilor D&I se calculează după formula:+</​spoiler>​
  
-$T(n= D(n) + S(n) + C(n)$,+Cei curioși pot citi [[https://​en.wikipedia.org/​wiki/​Dynamic_programming#​History|aici]] adevărul despre numele acestei tehnici. 8-)
  
-unde $D(n)$, $S(n)$ şi $C(n)$ reprezintă complexitățile celor 3 pași descriși mai sus: dividestăpânește respectiv combină.+===== Ce determina DP? ===== 
 +Aplicând această tehnică determinăm **una din soluțiile optime**problema putând avea mai multe soluții optime. În cazul în care se dorește determinarea tuturor soluțiilor optime, algoritmul trebuie combinat cu unul de backtracking în vederea construcției soluțiilor.
  
-===== Probleme clasice ​=====+În cazul problemelor de numărare, această tehnică ne va furniza **numărul căutat**. 
 +===== Tipar general DP ===== 
 +Aplicarea acestei tehnici de programare poate fi descompusă în următoarea secvență de pași: 
 +  - Identificarea structurii și a metricilor utilizate în caracterizarea soluției optime; 
 +  - Determinarea unei metode de calcul recursiv pentru a afla valoarea fiecărei subprobleme;​ 
 +  - Calcularea "​bottom-up"​ a acestei valori (de la subproblemele cele mai mici la cele mai mari); 
 +  - Reconstrucția soluției optime pornind de la rezultatele obținute anterior.
  
-==== MergeSort ​==== +===== Exemple clasice ​=====
-=== Enunt === +
-Sortarea prin interclasare (MergeSort [[http://​www.sorting-algorithms.com/​merge-sort|[1]]]) este un algoritm de sortare de vectori ce folosește paradigma D&I:+
  
-  * **Divide**: împarte vectorul inițial în doi sub-vectori de dimensiune n/2. +Programarea Dinamică este cea mai flexibilă tehnică din programare. Cel mai ușor mod de o înțelege presupune parcurgerea cât mai multor exemple.
-  * **Stăpânește**:​ sortează cei doi sub-vectori recursiv folosind sortarea prin interclasare;​ recursivitatea se oprește când dimensiunea unui sub-vector este 1 (deja sortat). +
-  * **Combina**:​ Interclasează cei doi sub-vectori sortați pentru ​obține vectorul inițial sortat.+
  
-=== Pseudocod === +Propunem câteva categorii de recurențe, pe care le vom grupa astfel: 
-Mai jos gasiti algoritmul MergeSort scris in pseudocod.+  ​* ​ recurențe de tip **SSM** (Subsecvența de Sumă Maximă) 
 +  *  recurențe de tip **RUCSAC** 
 +  *  recurențe de tip **PODM** (Parantezare Optimă de Matrici) 
 +  *  recurențe de tip **numărat** 
 +  *  recurențe pe **grafuri**
  
-<spoiler Pseudocod+<note
-<code cpp> +Pentru o problemă dată, este **posibil** să găsim **mai multe recurențe corecte **(mai multe soluții posibile). Evidentcriteriul de alegere între acestea va fi cel bazat pe complexitate. 
-MergeSort(v,​ start, end) // v – vector, start – limită inferioră, end – limită superioară +</note>
- if (start == end) return; // condiția de oprire +
- mid = (start + end/ 2; // etapa divide +
- MergeSort(vstart, mid); // etapa stăpânește +
- MergeSort(v,​ mid + 1, end); +
- Merge(v, start, end); // etapa combină +
-  +
-Merge(v, start, end) // interclasare sub-vectori +
- // indecsi +
-        mid = (start + end) / 2; +
- i = start; +
- j = mid + 1; +
- k = 1;+
  
-        tmp = buffer temporar in care incap (end - start + 1) numere 
- while (i <= mid && j <= end)  
- if (v[i] <= v[j]) tmp[k++] = v[i++]; 
-              else tmp[k++] = v[j++]; 
-  
- while (i <= mid)  
- tmp[k++] = v[i++]; 
  
- while (j <= end)  
- tmp[k++] = v[j++]; 
  
- copy(v[start..end], tmp[1..k-1]);​ +===== Categoria 1: SSM ===== 
-</​code>​ +Aceste recurențe au o oarecare asemănare cu problema SSM (enunț + soluție).
-</​spoiler>​+
  
-<spoiler Implementare in C++> 
-Mai jos puteti gasi o implementare in C++. 
  
-<code cpp> 
-#include <​bits/​stdc++.h>​ 
-using namespace std; 
  
-vector<intv;+<note> 
 +ATENȚIE! Rețineți diferența între următoarele 2 noțiuni!
  
-void merge_halves(int left, int right) { 
- int mid = (left + right) / 2; 
- vector<​int>​ aux; 
- int i = left, j = mid + 1; 
-  
-        while (i <= mid && j <= right) { 
- if (v[i] <= v[j]) { 
- aux.push_back(v[i]);​ 
- i++; 
- } else { 
- aux.push_back(v[j]);​ 
- j++; 
- } 
- } 
-  
-        while (i <= mid) { 
- aux.push_back(v[i]);​ 
- i++; 
- } 
-  
-        while (j <= right) { 
- aux.push_back(v[j]);​ 
- j++; 
- } 
  
- for (int k = left; k <= right; k+++* **subsecvență** ​([[https://​en.wikipedia.org/​wiki/​Substring | substring]] în englezăpentru un vector ** ** înseamnă un alt vector $u = [v[i], v[i+1],..., v[j]]$ unde $i <= j$.
- v[k= aux[k - left]+
-+
-}+
  
 +* **subșir** ([[https://​en.wikipedia.org/​wiki/​Subsequence | 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> ​
  
-void merge_sort(int left, int right) { +==== SSM ==== 
- if (left >right) return ; +=== Enunț === 
- int mid (left + right) / 2; +Fie un vector $ v $ cu $ n $ elemente întregi. O subsecvență de numere din șir este de forma: $v_iv_{i+1}... v_j$ ($i <= j$), având suma asociată $s_{ij= v_i +  v_{i+1} + ... + v_j$. O subsecvență ** nu ** poate fi vidă.
-  +
-        ​merge_sort(leftmid); +
- merge_sort(mid ​+ 1, right); +
- merge_halves(leftright)+
-}+
  
-int main() { 
- random_device rd; 
- for (int i = 0; i < 10; i++) { 
- v.push_back(rd() % 100); 
- } 
  
- cout << "​Vectorul initial: "; 
- for (int i = 0; i < v.size(); i++) { 
- cout << v[i] << " "; 
- } 
- cout << "​\n";​ 
  
- merge_sort(0, v.size() - 1);+=== Cerință === 
 +Să se determine subsecvența de sumă maximă ​(notată **SSM**).
  
- cout << "​Vectorul sortat: "; +=== Exemple === 
- for (int i 0; i < v.size(); i++) { +<spoiler Exemplu 1> 
- cout << v[i] << " "; += 6
- +
- cout << "\n";+
  
- return 0; +|i   | 1 | 2 | 3 | 4 | 5| 6 | 
-}+|v[i]| -10 | 2 | 3 | -1| 2 | -3 |
  
-</code>+Răspuns: SSM este între 2 și 5 (poziții). Are suma +6. ($SSM = 2, 3, -1, 2$) 
 + 
 +Explicație:​ avem numere pozitive, deci există o soluție simplă în care putem să alegem doar un număr pozitiv/mai multe numere pozitive de pe poziții alăturate (adică încercăm să evităm numere negative). Cele mai lungi subsecvențe cu numere pozitive sunt 2,3 și 2. Observăm că dacă extindem $2, 3$ la $2, 3, -1, 2$, deși am inclus un număr negativ, suma secvenței crește. ​
 </​spoiler>​ </​spoiler>​
  
-== Complexitate === +<spoiler Exemplu 2> 
-Complexitatea algoritmului este dată de formula: $T(nD(n) + S(n) + C(n)$, unde $D(n)=O(1)$,​ $S(n) = 2 * T(n / 2)$ și $C(n) = O(n)$, rezulta $T(n) = 2 * T(n / 2) + O(n)$.+n = 4
  
-Folosind teorema Master [[http://​people.csail.mit.edu/​thies/​6.046-web/​master.pdf|[8]]] găsim complexitatea algoritmului:​ $T(n) = O(n * log(n))$.+|i   | 1 | 2 | 3 | 4 | 
 +|v[i]| 10 | 20 | 30 | 40|
  
 +Răspuns: SSM este între 1 și 4 (poziții). Are suma 100. ($SSM = 10, 20, 30, 40$)
  
-  * **complexitate temporala** ​$T=O(n ​log(n))$ +Explicațiedeoarece toate numerele sunt **pozitive**, SSM cuprinde toate numerele
-    ​Ce inseamna aceasta metrica? Masoara efectiv ​**timpul de executie** al algoritmului (nu include citiriafisari etc)+</​spoiler>​
-  * **complexitate spatiala** : $S=O(n)$ +
-    * Ce inseamna aceasta metrica? Masoara efectiv **memoria suplimentara** folosita de algoritm (in acest caz ne referim strict la buffer-ul temporar).+
  
-<note> +<spoiler Exemplu 3
-Retineti cele doua conventii despre complexitati de mai sus. Le vom folosi pentru restul semestrului. +4
-</note+
-==== Binary Search ==== +
-=== Enunt === +
-Se dă un **vector sortat crescător** ($v[1]$, $v[2]$, ..., $v[n]$) ce conține valori reale distincte și o valoare x. +
  
-Sa se găsească la ce **poziție** ​apare x în vectorul dat.+|i   | 1 | 2 | 3 | 4 | 
 +|v[i]| -10 | -20 | -30 | -40| 
 + 
 +Răspuns: SSM este între 1 si 1 (poziții). Are suma -10. ($SSM = -10$) 
 + 
 +Explicație: deoarece toate numerele sunt **negative**, SSM cuprinde doar cel mai mare număr. 
 +</​spoiler>​
  
 === Rezolvare === === Rezolvare ===
-BinarySearch (Cautare Binara)se rezolva cu un algoritm D&I:+== Tipar == 
 +Tiparul acestei probleme ne sugerează că o soluție este obținută incrementalîn sensul că **putem** privi problema astfelgăsim cea mai bună soluție folosind **primele $i-1$** elemente din șir, apoi încercăm să o **extindem** folosind elementul **i** (adică ne extindem la dreapta ~CU~ $v[i]$).
  
-  * **Divide**: împărțim vectorul în doi sub-vectori de dimensiune n/2. +== Numire recurență == 
-  ​* **Stăpânește**: aplicăm algoritmul de căutare binară pe sub-vectorul care conține valoarea căutată. +Întrucât la fiecare pas trebuie sa reținem ** cea mai bună soluție** folosind un **prefix** din vectorul v, soluția ​va fi salvată într-un tablou auxiliar definit astfel:
-  ​* **Combină**: soluția ​sub-problemei devine soluția problemei inițiale, motiv pentru care nu mai este nevoie de etapa de combinare.+
  
-=== Pseudocod === +$ dp[i] $ suma subsecvenței ​de sumă maximă ​(**suma SSM**folosind ** doar primele ​ i ** elemente ​din vectorul ​și care **se termină pe poziția i**
-<code cpp> +
-BinarySearch(v,​ left, right, x) {             // functia returneaza pozitia x pe care se afla numarul x (oricare pozitie) sa  +
-       // vom cauta cat timp intervalul ​de cautare nu a fost inca epuizat ​(are cel putin un element)  +
-       while (left <= right) { +
-            mid = (left + right) / 2          // mijlocul intervalului de cautare +
-              +
-            if (x == v[mid]) return mid;      // elementul cautat este cel din mijloc +
-            if (x < v[mid])  ​right = mid - 1; // elementul cautat este mai mic decat cel din mijloc, ne mutam in prima jumatate +
-            if (x > v[mid]) ​ left  = mid + 1; // elementul cautat este mai mare decat cel din mijloc, ne mutam in a doua jumatate +
-        } +
-         +
-        return -1;                            // in acest punct ajungem daca si numai daca x nu a fost gasit +
-+
-</​code>​+
  
-=== Complexitate === +== Mențiuni ​== 
-  * **complexitate temporala** : $T = O(log (n))$ +  * Pentru a menține o convenție, toate tablourile de acest tip din laborator vor fi notate cu ** dp ** (dynamic programming)
-    * se deduce din recurenta ​$T(n)=T(n/2) + O(1)  +  * Ca să rezolvăm problema dată, trebuie să rezolvăm o mulțime de subprobleme 
-  ​* * **complexitate spatiala** : $S=O(1)$ +    * $dp[i]reprezintă ​**soluția** pentru problema ​$v[1], ..., v[i]și care se termină cu $v[i]$ 
-    * nu avem structuri de date complexe auxiliare +  * Soluția pentru problema inițială este maximul din vectorul ​$dp[i]- a.k.a. **max(dp[i])**.
-    * atragem atentia ca acest algoritm ​se poate implementa si **recursiv**,​ caz in care complexitatea spatiala devine ​$O(log(n))$, intrucat salvam pe stiva $O(log(n))parametri ​(intregi, referinte)+
  
-==== Turnurile din Hanoi ==== +== Găsire recurență ​== 
-=== Enunt === +Întrucât dorim ca această problemă să fie rezolvabilă printr-un algoritm/​bucată ​de codtrebuie să descriem o metodă concretă prin care vom calcula ​$dp[i]$.
-Se considera 3 tije $S$ (**sursa**),​ $D$ (**destinatie**),​ $aux$ (**auxiliar**) şi n discuri ​de dimensiuni distincte ($12, ..., n$ - ordinea crescătoare a dimensiunilor) situate inițial toate pe tija $Sîn ordinea $1,2,...,n$ (de la vârf către baza)+
  
-Singura operație care se poate efectua este de a selecta un disc ce se află în vârful unei tije şi plasarea lui în vârful altei tije astfel încât să fie așezat deasupra unui disc de dimensiune mai mare decât a sa+  * **Cazul ​de bază** 
 +    * În general ​în probleme putem avea mai multe cazuri de bază, care în principiu se leagă de valori extreme are dimensiunilor subproblemelor. 
 +    * În cazul SSM, avem un singur caz de bază, când avem un singur element în prefix: $dp[1] = v[1] $. 
 +    * Explicație:​ dacă avem un singur element, atunci acesta formează singura subsecvență posibilă, deci $ SSM = v[1] $
  
-Sa se găsească un algoritm prin care se mută toate discurile de pe tija $S$ pe tija $D$ (problema turnurilor din Hanoi).+  * **Cazul general** 
 +    * presupune inductiv că avem rezolvate toate subproblemele mai mici 
 +    * în cazul SSM, presupunem că avem calculat $ dp[i-1] $ și dorim sa calculăm $ dp[i] $ (cunoaștem cea mai bună soluție folosind primele i-1 elememente și vedem dacă elementul de pe poziția i o poate îmbunătăți) 
 +    * la fiecare pas avem de ales dacă $v[i]$ extinde cea mai bună soluție ​care se termină pe $v[i-1]sau se începe o nouă secvență cu $v[i]$ 
 +    * decidem în funcție de $ dp[i - 1]$ și $v[i] $ 
 +      * ** dacă ** $ dp[i - 1] >= 0 $ (cea mai bună soluție care se termină ​pe i - 1 are cost nenegativ) 
 +        * extindem secvență care se termină cu v[i-1] folosind elementul v[i]: $dp[i] = dp[i-1] + v[i]$ 
 +        * Explicație:​ $dp[i-1] + v[i] >= v[i]$ (încă are rost să extind) 
 +      * ** dacă ** $ dp[i - 1] < 0 $ (cea mai bună soluție care se termină pe i - 1 are cost negativ) 
 +        * vom începe o nouă secvență cu $v[i]$, adică $dp[i] = v[i]$ 
 +        * Explicație:​ $v[i] > dp[i-1] + v[i]$, deci prin extindere nu obțin soluție maximă!
  
-=== Solutie === +== Implementare recurență ​== 
-Pentru rezolvarea problemei folosim următoarea strategie [[http://​www.mathcs.org/​java/​programs/​Hanoi/​index.html|[9]]]:+În majoritatea problemelor de DP, găsirea recurenței ocupă cea mai mare parte a timpului de rezolvare (lucru adevărat și în cazul problemelor de la PA)De aceea, faptul că ați reușit să scrieți pe foaie lucruri foarte complicate poate fi un indiciu ca ați pornit pe o cale greșită.
  
-  * mutam primele $n-1$ discuri de pe tija S pe tija aux folosindu-ne de tija D. +<spoiler Exemplu implementare>​
-  * mutam discul n pe tija D. +
-  * mutam apoi cele $n-1$ discuri de pe tija aux pe tija D folosindu-ne de tija S.+
  
-Ideea din spate este ca avem mereu o singura sursa si o singura destinatie sa atingem un scop. Intotdeauna a 3-a tija va fi considerata auxiliara si poate fi folosita pentru ​atinge scopul propus.+Problema se poate testa pe infoarena: [[https://​www.infoarena.ro/​problema/​ssm | Subsecvență de sumă maximă]]. 
 + 
 +Mai jos se află un exemplu simplu de implementare ​recurenței găsite în C++.
  
-=== Algoritm === 
 <code cpp> <code cpp>
-// muta n discuri de pe tija S pe tija D folosind tija aux 
-Hanoi(n, S, D, aux)  { 
-    if (n >= 1) { 
-        Hanoi(n - 1, S, aux, D);   // mut n-1 discuri de pe sursa (S) pe auxiliar (aux) 
-                                   // in aceasta subproblema sursa este S, destinatia este aux, intermediarul este D 
-                                    
-        Muta_disc(S,​ D);           // acum pot muta direct discul n de pe sursa (S) pe destinatie (D) 
-        ​ 
-        Hanoi(n - 1, aux, D, S);   // mut n-1 discuri de pe sursa (aux, aici sunt ele momentan) pe destinatie (D - scop final) 
-                                   // in aceasta subproblema,​ S este auxiliar, intrucat este tija libera 
-    } 
-} 
-</​code>​ 
  
 +// găsește SSM pentru vectorul v cu n elemente
 +// pentru a menține convenția din explicații:​
 +//      - elementele sunt indexate de la 0, dar le folosesc doar pe cele care incep de la 1
 +//                                          => v[1], ..., v[n]
 +int SSM(int n, vector<​int>​ &v) {
 + vector<​int>​ dp(n + 1);    // vector cu n + 1 elemente (indexarea începe de la 0)
 +                                  // am nevoie de dp[1], ..., dp[n]
  
-=== Complexitate === + // caz de bază 
-  * **complexitate temporala** : $T(n) O(2^n)$ + dp[1] v[1];
-    * se deduce din recurenta $T(n)=2*T(n - 1) + O(1)$   +
-  * * **complexitate spatiala** : $S(n) = O(n)$ +
-    * la un moment dat, nivelul maxim de recursivitate este n+
  
 + // caz general
 + for (int i = 2; i <= n; ++i) {
 + if (dp[i - 1] >= 0) {
 + // extinde la dreapta cu v[i]
 + dp[i] = dp[i - 1] + v[i];
 + } else {
 + // încep o nouă secvență
 + dp[i] = v[i];
 + }
 + }
  
-==== ZParcurgere ==== + // soluția e maximul din vectorul dp 
-=== Enunt === + int sol dp[1]; 
-Gigel are o tabla patratica de dimensiuni $2^n * 2^n$. Ar vrea sa scrie pe patratelele tablei numere naturale cuprinse intre $1$ si $2^n * 2^n$ conform unei parcurgeri mai deosebite pe care o numeste **Z-parcurgere**. ​+ for (int i 2; i <n; ++i) { 
 + if (dp[i] > sol) { 
 + sol ​dp[i]; 
 +
 + }
  
-O Z-parcurgere viziteaza recursiv cele patru cadrane ale tablei in ordinea: **stanga-sus**,​ **dreapta-sus**,​ **stanga-jos**,​ **dreapta-jos**.+        return sol; // aceasta este suma asociată cu SSM 
 +}
  
-La un moment dat Gigel ar vrea sa stie ce **numar de ordine** trebuie sa scrie conform Z-parcurgerii pe anumite patratele date prin coordonatele lor $( x, y )$. Gigel incepe umplerea tablei **intotdeauna** din coltul din stanga-sus. +</code>
-  +
-<spoiler Exemplu 1> +
-$n = 1$ si $(x, y) = (2, 2)$+
  
-Raspuns$4$+Dacă dorim să afișăm și indicii între care apare SSM, putem să stocăm și poziția de start pentru fiecare soluție intermediară. . 
 +Hintdefiniți ** start[i] ** = poziția pe care a început subsecvența care dă soluția cu cost dp[i].
  
-Explicatie: Matricea arata ca in exemplul urmator. 
-|1|2| 
-|3|4| 
 </​spoiler>​ </​spoiler>​
  
 +=== Mențiuni ===
 +Întrucât această soluție presupune calculul iterativ (coloană cu coloană) a matricei dp, complexitatea este liniară. De asemenea, se mai parcurge o dată dp pentru a găsi maximul. ​
 +  * **complexitate temporală **: $T = O(n)$
 +  * **complexitate spațială ** : $S = O(n)$
 +         * desigur că pentru problema SSM, nu era nevoie sa reținem, tablourile dp/start în memorie.
 +         * puteam sa construim element cu element și maximul din dp în aceleași timp (întrucât ne trebuie ultima valoare la fiecare pas și maximul global).
 +         * în acest caz complexitatea spațială devine $S = O(1)$ 
  
-<spoiler Exemplu 2> +** Pentru a ilustra toți pașii posibili într-o astfel de problemătotul a fost prezentat cât mai simplu ​(NU în toate problemele putem facem simplificări de tipul "NU am nevoie să stochez tabloul dp").**
-$n = 2$ si $(xy) = (3, 3)$+
  
-Raspuns: $13$+      
 +==== SCMAX ==== 
 +=== Enunț === 
 +Fie un vector $ v $ cu $ n $ elemente întregi. Un subșir de numere din șir este de forma: $v_{i_1}, v_{i_2}, ... , v_{i_k}$. Un subșir ** nu ** poate fi vid ($k >= 1$).
  
-Explicatie: Matricea arata ca in exemplul urmator+=== Cerința === 
-|1|2|5|6| +Să se determine subșirul crescător maximal (notat **SCMAX**) - un subșir ordonat strict crescător și are lungime maximă (dacă sunt mai multe soluții, să se gasească una oarecare)
-|3|4|7|8+ 
-|9|10|13|14| +=== Exemple === 
-|11|12|15|16|+<spoiler Exemplu 1> 
 +n = 6 
 + 
 +|i   | 1 | 2 | 3 | 4 | 5
 +|v[i]100 | 12 | 13 | -115 -30 | 
 + 
 +Răspuns: $SCMAX = 12, 13, 15$ ($SCMAX = v[2], v[3], v[5])$. 
 + 
 +Explicație:​  
 +Toate subșirurile ordonate strict crescător sunt: 
 +  * $100$ 
 +  * $12$ 
 +  * $12, 13$ 
 +  * $12, 13, 15$ 
 +  * $12, 15$ 
 +  * $13$ 
 +  * $13, 15$ 
 +  * $-1$ 
 +  * $-1, 15$ 
 +  * $15$ 
 +  * $-30$ 
 +Cel menționat este singurul de lungime 3.
 </​spoiler>​ </​spoiler>​
  
-=== Solutie === +<spoiler Exemplu 2> 
-Analizand modul in care se **completeaza** tabloul/​matricea din enunt, observam ca la fiecare etapa impartim matricea (**problema**) in 4 submatrici (**4 subprobleme**). De asemenea, sirul de numere pe care dorim sa il punem in matrice se imparte in 4 secvente, fiecare corespunzand unei submatrici.+6
  
-Observam astfel ca problema suporta **descompunerea** in **subprobleme disjuncte** si cu **structura similara**, ceea ce ne face sa ne gandim la o solutie cu Divide et Impera. +|i   | 1 | 2 | 3 | 4 | 5| 6 | 
- +|v[i]| 100 | 12 | 13 | -1| 15 | 14 |
  
-=== Complexitate === +Răspuns: 
-  * **complexitate temporala** : $O(n)$ +  * $SCMAX 12, 13, 15$ ($SCMAX v[2], v[3], v[5])$. 
-    * $\log_{4} (2^n) \frac{1}{2} \log _{2} (2^n=  \frac{1}{2} n   +  * $SCMAX 12, 1314$ ($SCMAX = v[2]v[3], v[6])$.
-  * * **complexitate spatiala** : $S=O(n)$ +
-    * stocam parametri pentru recursivitate  +
-    * solutia se poate implementa si iterativcaz in care $S = O(1)$; deoarece dimensinile spatiului de cautare sunt $2^n * 2^n$n este foarte mic ($n <= 15$), de aceea o solutie iterativa nu aduce nici un castig **efectiv** in aceasta situatie+
  
-===== Concluzii =====+Explicație:​  
 +Toate subșirurile ordonate strict crescător sunt: 
 +  * $100$ 
 +  * $12$ 
 +  * $12, 13$ 
 +  * $12, 13, 15$ 
 +  * $12, 13, 14$ 
 +  * $13$ 
 +  * $13, 15$ 
 +  * $13, 14$ 
 +  * $-1$ 
 +  * $-1, 15$ 
 +  * $-1, 14$ 
 +  * $15$ 
 +  * $14$ 
 +Cele 2 soluții indicate au ambele lungime maximă.
  
-Divide et impera este o tehnică folosită pentru a realiza solutii pentru o anumita clasa de probleme: acestea contin **subprobleme disjuncte** si cu **structura similara**. În cadrul acestei tehnici se disting trei etape: divide, stăpânește și combină.+</​spoiler>​
  
-Mai multe exemple ​de algoritmi care folosesc tehnica divide et impera puteți găsi la [[http://​www.cs.berkeley.edu/​~vazirani/​algorithms/​chap2.pdf|[11]]].+=== Rezolvare === 
 +== Tipar== 
 +Verificăm dacă se aplică tiparul ​de la SSM: **găsim cea mai buna soluție folosind primele $i-1$ elemente din șir, apoi încercăm să o extindem folosind elementul i (adică ne extindem ​la dreapta ​~CU~ $v[i]$)**.
  
-===== Exercitii ===== +    * Dacă avem cea mai bună soluție pentru intervalul $1, 2, .., i-1$ și care se termină cu $v[i-1]$, atunci încercăm să extindem soluția cu $v[i]$ (putem dacă $v[i-1] ​v[i]$) 
-<note> +    * Altfel.. Unde am putea să îl punem pe $v[i]$? 
-In acest laborator vom folosi scheletul de laborator din arhiva {{pa:​new_pa:​skel-lab01.zip}}+      * Păi am putea sa încercăm să îl punem la finalul soluției care se termină pe $v[i-2]$, $v[i-3]$, ... sau $v[1]$
-</​note>​+
  
-=== Count occurrences ​==+== Numire recurență ​== 
-Se da un sir sortat ​**v** cu **n** elemente. Gasiti numarul de elemente egale cu **x** din sir.+$ dp[i] $ lungimea celui mai lung subșir(**lungime SCMAX**) folosind (doar o parte) din primele i elemente din vectorul v și care se termină pe poziția i
  
-<spoiler Exemplu 1> +== Mențiuni == 
-$n = 6si $x = 10$ +  * Ca să rezolvăm problema dată, trebuie să rezolvăm o mulțime de subprobleme 
-|i|1|2|3|4|5|6| +    * $dp[i]reprezintă **soluția** pentru problema ​$v[1], ..., v[i]șcare se termină cu $v[i]$ 
-|v|1|2|4|10|10|20|+  * Soluția pentru problema inițială este maximul din vectorul $dp[i]$.
  
-Raspuns: $2$+== Găsire recurență == 
 +  * **Cazul de bază** 
 +    * Și în problema SCMAX, cazul pentru $i = 1$ este caz de bază.  
 +          * dacă avem un singur element, atunci avem o singură subsecvență de lungime 1, ea este soluția 
 +          * $dp[1] = 1$   
 +   
 +  * ** Cazul general ** 
 +      * presupune inductiv că avem rezolvate toate subproblemele mai mici 
 +      * în cazul SCMAX, presupunem că avem calculate $ dp[1], dp[2], ..., dp[i-1] $ și dorim să calculăm $ dp[i] $ (cunoaștem cea mai bună soluție folosind primele j elemente și vedem dacă elementul de pe poziția i o poate îmbunătăți - $j = 1:i-1$
 +      * deoarece nu știm unde e cel mai bine să îl pune pe $v[i]$ (după care v[j]?), încercăm pentru toate valorile posibile ale lui j (unde $j = 1 : i - 1$) 
 +        * **dacă $v[j] < v[i] $**, atunci subșirul crescător care se termină pe poziția j, poate fi extins la dreapta cu elementul v[i], generând lungimea ** dp[j] + 1 ** 
 +           * deci dp[i] = max(dp[j] + 1), $j = 1 : i - 1$ (dacă nu există un astfel de j, valoarea lui max(...) este 0) 
 +        * Ce se întamplă totuși dacă nu există un j care să îndeplinească condiția de mai sus? Atunci $v[i]$ va forma singur un subșir crescător de lungime 1 (care poate fi folosit la un pas ulterior)  
 +  
 +  Reunind cele spuse mai sus: 
 +  * $dp[1] = 1$ 
 +  * $dp[i] = 1 + max(dp[j])$,​ unde $j = 1 : i-1$ **și** $v[j] < v[i]$; $i=2:n$
  
-Explicatie10 apare de 2 ori in sir.+== Implementare recurență == 
 +<spoiler Exemplu implementare>​ 
 +Problema se poate testa pe infoarena[[https://​www.infoarena.ro/​problema/​scmax | Subșir crescător maximal]].
  
-</​spoiler>​+Mai jos se află un exemplu simplu de implementare a recurentei găsite în C++.
  
-Task-uri: 
-   * Aceasta problema este deja rezolvata. Pentru a va acomoda cu scheletul, va trebui sa faceti cativa pasi: 
-     * Rulati comanda $./​check.sh$ si cititi cum se foloseste checker-ul. 
-     * Rulati comanda necesara pentru a rula task-ul 1. Sursa nu implementeaza corect algoritmul si returneaza valori default. Din acest motiv primiti mesajul **WRONG ANSWER**. 
-     * Copiati urmatoarea sursa in folderul corespunzator. Rulati comanda anterioara. Observati mesajele afisate cand ati rezolvat corect un task. 
-<spoiler Solutie C++> 
-Sursa **main.cpp** asociata cu task 1 este mai jos. 
 <code cpp> <code cpp>
-#include <bits/stdc++.h>​ +// n   = numărul de elemente din vector 
-using namespace std;+// v   = vectorul dat (v[1], v[2], ..., v[n] - indexare de la 1 ca în explicații)
  
-class Task { +void scmax(int n, vector<​int>​ &v) { 
-public: + vector<​int>​ dp(n + 1);   // în explicații indexarea începe de la 1
- void solve() { +
- read_input(); +
- print_output(get_result());​ +
- }+
  
-private: + // caz de bază 
- void read_input() { + dp[1] 1  // [ v[1] ] este singurul subșir ​(crescătorcare se termină pe 1
- ifstream fin("​in"​);​ +
- fin >> n; +
- for (int i 0, ei < n; i++) { +
- fin >> e; +
- v.push_back(e)+
-+
- fin >> x; +
- fin.close();​ +
- }+
  
- int find_first() { + // caz general 
- int left 0, right = n - 1, mid, res = -1; + for (int i = 2; i <= n; ++i) { 
- while (left <right) { + dp[i] = 1;   // [ v[i] ] - este un subșir (crescător) care se termină pe i 
- mid = (left + right) ​2; +  
- if (v[mid>= x) { + // încerc să îl pun pe v[i] la finalul tuturor soluțiilor disponibile 
- res = mid; + // o soluție se termină cu un element v[j] 
- right mid - 1; + for (int j 1; j < i; ++j) { 
- } else { + // soluția trivială: v[i] 
- left = mid + 1; + if (v[j] < v[i]) { 
-+ // din (..., v[j]) pot obține (..., v[j], v[i]) 
-+ // (caz în care prec[i] ​j)
- return res; +
- }+
  
- int find_last() { + // voi alege j-ul curentcând alegerea îmi găsește o soluție mai bună decât ce am deja 
- int left = 0, right = n 1mid, res = -1; + if (dp[j] + 1 > dp[i]) { 
- while (left <= right) { + dp[i] ​dp[j] + 1; 
- mid = (left + right) / 2; + }
- if (v[mid<= x) { +
- res ​mid; +
- left = mid + 1; +
- else { +
- right = mid - 1;+
  }  }
  }  }
- return res; 
  }  }
  
- int get_result() { + // soluția e maximul din vectorul dp 
- int first find_first()+ int sol dp[1], pos = 1
- int last find_last(); + for (int 2; i <= n; ++i{ 
- if (first == -1 || last == -1) { + if (dp[i] > sol) { 
- return 0;+ sol = dp[i]; 
 + pos = i;
  }  }
- return last - first + 1; 
  }  }
  
- void print_output(int result) { + return ​sol;
- ofstream fout("​out"​);​ +
- fout << result; +
- fout.close();​ +
-+
- +
- int n, x; +
- vector<​int>​ v; +
-}; +
- +
-int main() { +
- Task task; +
- task.solve();​ +
- return ​0;+
 } }
 </​code>​ </​code>​
 </​spoiler>​ </​spoiler>​
  
-<​spoiler ​Solutie Java> +<​spoiler ​Exemplu implementare ​cu reconstituire
-Sursa **Main.java** asociata ​cu task 1 este mai jos. +Problema se poate testa pe infoarena: [[https://​www.infoarena.ro/​problema/​scmax | Subșir crescător maximal]].
-<code cpp+
-import java.io.File; +
-import java.io.IOException;​ +
-import java.io.PrintWriter;​ +
-import java.util.Scanner;+
  
-public class Main { 
- static class Task { 
- public final static String INPUT_FILE = "​in";​ 
- public final static String OUTPUT_FILE = "​out";​ 
  
- int n; +În [[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​demo/​lab03/​02-scmax|pa-lab::​demo/​lab03/​02-scmax]găsiți un exemplu de implementare care arată și cum puteți reconstitui SCMAX. 
- int[] v; +Față de implementarea anterioară,​ în această versiune se folosește un tablou auxiliar prec.
- int x;+
  
- private void readInput() { +$prec[i]$ ​indicele j al elementului ​v[j], pentru care $dp[j] + 1 == dp[i](adică acel j pentru care subșirul crescător maximal care se termină cu $v[i]$ este extinderea cu un element a celui care se termină cu $v[j]$. 
- try { +     * dacă nu există un astfel de j, atunci $prec[i] = 0$ (prin convenție
- Scanner sc new Scanner(new File(INPUT_FILE));​ +</​spoiler>​ 
- n = sc.nextInt();​ +=== Mențiuni === 
- v = new int[n]+Întrucât această soluție presupune calculul iterativ ​(coloană cu coloană) a matricei dp, complexitatea este polinomială (pătratică - pentru fiecare element din tabloul, facem o trecere prin elementele deja calculate). 
- for ​(int i = 0; i < n; i++) { +  * **complexitate temporală **: $T = O(n^2)$ 
- v[i] = sc.nextInt(); +    * se poate obține o soluție în complexitate $T = O(n log n)$ dacă se folosește o căutare binară pentru a găsi elementul j dorit (ex. [[https://​www.infoarena.ro/​job_detail/​1248867?​action=view-source | implemetare]]). ​ 
- } +  * **complexitate spațială ** : $S = O(n)$ 
- sc.nextInt()+    * NU putem obține o complexitate spatială mai bună, întrucât avem nevoie să stocăm cel puțin vectorul dp (stocăm și vectorul **prec** dacă avem nevoie să reconstituim SCMAX)
- sc.close(); +
- } catch (IOException e{ +
- throw new RuntimeException(e); +
- +
- }+
  
- private void writeOutput(int count) { 
- try { 
- PrintWriter pw = new PrintWriter(new File(OUTPUT_FILE));​ 
- pw.printf("​%d\n",​ count); 
- pw.close();​ 
- } catch (IOException e) { 
- throw new RuntimeException(e);​ 
- } 
- } 
  
- private int findFirst() { 
- int left = 0, right = n - 1, mid, res = -1; 
- while (left <= right) { 
- mid = (left + right) / 2; 
- if (v[mid] >= x) { 
- res = mid; 
- right = mid - 1; 
- } else { 
- left = mid + 1; 
- } 
- } 
- return res; 
- } 
  
- private int findLast() { 
- int left = 0, right = n - 1, mid, res = -1; 
- while (left <= right) { 
- mid = (left + right) / 2; 
- if (v[mid] <= x) { 
- res = mid; 
- left = mid + 1; 
- } else { 
- right = mid - 1; 
- } 
- } 
- return res; 
- } 
  
- private int getAnswer() { +===== Categoria 2: RUCSAC ===== 
- int first findFirst();​ +Aceste recurențe au o oarecare asemănare cu problema RUCSAC ​varianta discretă (enunț ​soluție).
- int last findLast();​ +
- if (first ​== -1 || last == -1) { +
- return 0; +
- } +
- return last first 1; +
- }+
  
- public void solve() { +==== RUCSAC ====
- readInput();​ +
- writeOutput(getAnswer());​ +
-+
- }+
  
- public static void main(String[] args+=== Enunț === 
- new Task().solve(); +Fie un set (vectorcu $ n $ obiecte ​(care nu pot fi tăiate - varianta discretă a problemei). Fiecare obiect i are asociată o pereche ​($w_i, p_i$cu semnificația:​ 
- } +  * $w_i$ = $weight_i$ = greutatea obiectului cu numărul i 
-}+  * $p_i$ = $price_i$ = prețul obiectului cu numărul i 
 +      * $w_i >= 0$ si $p_i > 0$
  
-</​code>​ +Gigel are la dispoziție un rucsac de ** volum infinit**, dar care suportă o **greutate maximă** (notată cu $W$ - weight knapsack).
-</​spoiler>​+
  
-   Intelegeti solutia oferita impreuna cu asistentul vostru. +El vrea să găsească ​** o submulțime ​ de obiecte** pe care sa le bage în rucsac, astfel încât **suma profiturilor să fie maximă**.
-   Care este complexitatea solutiei (timp + spatiu)? De ce?+
  
-=== SQRT === +Dacă Gigel bagă în rucsac obiectul i, caracterizat ​de ($w_i, p_i$), atunci profitul adus de obiect este $p_i(presupunem că îl vinde cu cât valorează obiectul).
-Se da un numar real **n**. Scrieti un algoritm ​de complexitate **O(log n)** care sa calculeze ​$sqrt(n)cu o precizie ​de $0.001$. +
  
 +=== Cerință ===
 +Să se determine **profitul maxim** pentru Gigel.
 +
 +=== Exemple ===
 <spoiler Exemplu 1> <spoiler Exemplu 1>
-$ n = 0.25 $+$n = 5$ si $W = 10$
  
-Raspunsorice valoare intre $0.499$ si $0.501$ ​(inclusiv)+| |1|2|3|4|5| 
 +|w|3|3|1|1|2| 
 +|p|6|3|2|8|5| 
 + 
 +Răspuns**24** ​(profitul maxim) 
 + 
 +Explicație:​ va alege toate obiectele :D.
 </​spoiler>​ </​spoiler>​
  
-<note+<spoiler Exemplu 2
-Pentru a putea trece testele, trebuie sa afisati rezultatul cu **cel putin ** 4 zecimale.+$n = 5$ și $W = 3$
  
-</​note>​+| |1|2|3|4|5| 
 +|w|3|3|1|1|2| 
 +|p|6|3|2|8|5|
  
-=== ZParcurgere === +Răspuns: **13** (profitul maxim)
-Rezolvati problema ZParcurgere folosind scheletul pus la dispozitie. Enuntul si explicatiile le gasiti in partea de seminar.+
  
-=== Exponentiere logaritmica === +Explicație:​ va alege obiectele cu indicii 4 si (profit: 8 + 5) 
-Se dau doua numere naturale **base** ​si  ​**exponent**. Scrieti un algoritm de complexitate $O(log (exponent))$ care sa calculeze ${base} ^ {exponent} \ \% \  MOD $. +</​spoiler>​
  
-<​note>​ +=== Rezolvare === 
-Intrucat expresia ​{base} ^ {exponent} ​$ este foarte maredorim sa aflam doar **restul** impartirii lui la un numar **MOD**.+== Tipar == 
 +Cum am transpune tiparul de la SSM/SCMAX în problema RUCSAC? 
 +  * știm care este profitul maxim pe care îl obține dacă folosim 
 +    * doar primul element 
 +    * doar primele ​$2elemente 
 +    * ... 
 +    * doar primele $i-1$ elemente 
 +  * ajung să mă gândesc la obiectul (elementul) i 
 +    * este posibil ca acesta să nu apară neapărat în soluția cea mai bunăcaz în care nu îl folosesc, deci soluția maximă se gasește între cele menționate mai sus 
 +    ​dacă folosesc elementul i caracterizat de ($w_i, p_i$), în primul rând acesta trebuie să încapă în ghiozdan... 
 +        ​cum verific acest lucru?  
 +        ​o recurență de tipul $ dp[i] = ... $ nu va fi suficientă,​ pentru că în această problemă am 2 dimensiuni: ​** obiectele ** (submulțimile de indici) și ** greutățile ​** (asociate cu obiectele / submulțmile de obiecte).
  
 +== Numire recurență ==
 +Întrucât la fiecare pas trebuie să reținem ** cea mai bună soluție** folosind un **prefix** din vectorul de obiecte, dar pentru că trebuie să punem și ** o restricție de greutate** necesară (ocupată in rucsac), soluția va fi salvată într-un tablou auxiliar definit astfel:
  
-Proprietati matematice necesare+$ dp[i][cap] $ = profitul maxim (**profit RUCSAC**) obținut folosind (doar o parte) din primele i obiecte și având un rucsac de **capacitate maximă cap** 
-    * $(a + b  \ \% \   MOD = ((a   \ \% \   MOD) + (b   \ \% \   MOD))   \ \% \   ​MOD ​$  + 
-    * $(b)   \ \% \   ​MOD ​((a   \ \% \   MOD) \ * (b   \ \% \   ​MOD)) ​  \ \% \   ​MOD ​+Observații
 +  * **NU*există restricție în folosirea obiectului i în soluția menționată de $dp[i][cap]$ (a.k.a. se poate folosi sau se poate ignora)
 +  * Soluția problemei se găsește în $dp[n][W]$ ​(profitul maxim folosind ​(doar o partedin primele n elemente - adică soluția bazată pe inspectarea tuturor obiectelor; capacitatea maximă folosită este W - adică soluția bazată pe ghiozdanul de capacitate maximă)
 + 
 +== Găsire recurență == 
 +  * **Cazul de bază** 
 +    * Dacă avem o submulțime vidă de obiecte selectate. 
 +      * dp[0][cap] = 0 $ 
 +      * Explicație:​ Dacă nu alegem obiecte, atunci profitul este 0 indiferent de capacitate. 
 + 
 +  * ** Cazul general ** 
 +    * $ dp[i][cap] = ? $ 
 +    * presupune inductiv că avem rezolvate toate subproblemele mai mici 
 +      * subprobleme mai mici înseamnă să folosească mai puține obiecte sau un rucsac cu capacitatea mai mică 
 +      * vedem dacă prin folosirea obiectului i, obținem cea mai bună soluție in $dp[i][cap]$ 
 +        * **NU folosesc obiectul i** 
 +          * în acest caz, o să alegem cea mai bună soluție formată cu celelalte $i-1$ elemente și aceeași capacitate ​rucsacului 
 +          ​soluția generată de acest caz: $dp[i][cap] ​dp[i - 1][cap]$ 
 +        ​**folosesc obiectul i** 
 +          * dacă îl folosesc, înseamnă că pentru el trebuie să am rezervată în rucsac o capacitate egală cu $w_i$ 
 +            * adică când am selectat dintre primele $i-1$ elemente, nu trebuia să ocup mai mult de $cap - w_i$ din capacitatea rucsacului 
 +            * față de subproblema menționată,​ câștig în plus $p_i$ (profitul pe care îl aduce acest obiect 
 +          * soluția generată de acest caz: $dp[i][cap] = dp[i - 1][cap - w_i] + p_i
   ​   ​
 +   ​Reunind cele spuse mai sus, obținem:
 +   * $dp[0][cap] = 0$, pentru $cap = 0 : W$
 +   * $dp[i][cap] = max(dp[i - 1][cap], dp[i - 1][cap - w_i] + p_i)$
 +     * pentru $i = 1: n$, $cap = 0:W$ 
  
-Atentie la inmultire! Rezultatul **temporar** poate provoca un overflow. +== Implementare recurență == 
-Solutii: +<spoiler Exemplu implementare> 
-  * **C++**: $a * b$ => $1LL * a * b$ +Problema se poate testa pe infoarena:​[[https://​www.infoarena.ro/​problema/​rucsac | Problema rucsacului]].
-  * **Java**: $a * b$ => $1L * a * b$ +
-</note>+
  
-<spoiler Exemplu 1> +Mai jos se află un exemplu simplu de implementare a recurenței găsite in C++.
-$ base  = 2 $,  $ exponent = 10$, $MOD = 5$+
  
-Raspuns: $4$+<code cpp>
  
-Explicatie: $2^{10} \ \% \ 5 4$ +// n   numărul de obiecte din colecție 
-</spoiler>+// W   = capacitatea maximă a rucsacului 
 +// (w[i], p[i]) = caracteristicile obiectului i ($i = 1 : n)
  
 +int rucsac(int n, int W, vector<​int>​ &w, vector<​int>​ &p) {
 +    // dp este o matrice de dimensiune (n + 1) x (W + 1)
 +    // pentru că folosim dp[0][*] pentru mulțimea vidă
 +    //                   ​dp[*][0] pentru situația în care ghiozdanul are capacitate 0
 +    vector< vector<​int>​ > dp(n + 1, vector<​int>​(W + 1, 0));
  
-==== Bonus ====+    // cazul de bază 
 +    for (int cap 0; cap <W; ++cap) { 
 +        dp[0][cap] ​0; 
 +    }
  
-<spoiler Inversiuni>​ +    // cazul general 
-Se da un sir $S$ de n numere intregi. Sa se detemine cate inversiuni sunt in sirul dat. Numim inversiune o pereche de indici $= i < < = n$ astfel incat $S[i] > S[j]$+    for (int i = 1; i <= n; ++i) { 
 +        for (int cap = 0; cap <= W; ++cap) { 
 +            // nu folosesc obiectul i => e soluția ​de la pasul i - 1 
 +            dp[i][cap] ​dp[i-1][cap]; 
 + 
 +            // folosesc obiectul i, deci trebuie să rezerv w[i] unități în rucsac 
 +            // înseamnă ca înainte trebuie să ocup maxim cap - w[i] unități 
 +            if (cap - w[i] >= 0) { 
 +                int sol_aux = dp[i-1][cap - w[i]] + p[i]; 
 +                 
 +                dp[i][cap] = max(dp[i][cap],​ sol_aux); 
 +            } 
 +        } 
 +    } 
 + 
 +    return dp[n][W]; 
 +
 +</code> 
 + 
 +</​spoiler>​ 
 + 
 +=== Mențiuni === 
 +Întrucât această soluție presupune calculul iterativ (linie cu linie) a matricei dp, complexitatea este polinomială. ​  
 +  * **complexitate temporală **: $T = O(* W)$ 
 +  * **complexitate spațială ** : $S = O(n * W)$ 
 +        * dacă nu ne interesează să reconstituim soluția (să afișăm submulțimea efectiv), atunci putem să NU stocăm toată matricea dp 
 +        * ca să calculăm o linie, avem nevoie doar de ultima linie 
 +        * putem să stocăm la orice moment de timp doar ultima linie șlinia curentă 
 +        * complexitatea spațială se reduce astfel la $= O(W)
  
-<note tip> 
- ​Exemplu:​ in sirul ''​{0 1 9 4 5 7 6 8 2}''​ sunt 12 inversiuni. 
-</​note>​ 
  
 +===== Implementarea algoritmilor standard =====
 <​note>​ <​note>​
-Aceasta problema nu are schelet.+[[https://​github.com/​acs-pa/​pa-lab/​tree/​main/​algorithms/​lab01|pa-lab/​algorithms/​lab01]] conține implementarea algoritmilor standard / tehnicilor / tiparelor de algoritmi pentru acest laborator.
 </​note>​ </​note>​
  
-</​spoiler>​ 
  
-==== Extra ==== +===== Pool probleme (pentru prezentări) =====
-<spoiler Statistici de ordine>​ +
-Se dă un vector de numere întregi neordonate. Scriind o funcție de partitionare,​ folosiți **Divide et Impera** pentru +
-    * a determina a k-lea element ca mărime din vector +
-    * a sorta vectorii prin QuickSort+
  
-Puteti testa problema partition [[https://​leetcode.com/​problems/​kth-largest-element-in-an-array/​description/​|aici]]. Problema [[https://​en.wikipedia.org/​wiki/​Quicksort|QuickSort]] (chiar si MergeSortpoate fi testata [[https://​infoarena.ro/​problema/​algsort|aici]].+======= 1Edit Distance =======
  
 +**Enunt:​**  ​
 +Se dau două șiruri de caractere ''​a''​ și ''​b''​.  ​
 +Într-o singură operație se poate:
 +  * insera un caracter,
 +  * șterge un caracter,
 +  * înlocui un caracter cu altul.
  
-<note tip> +Determinați numărul minim de operații necesare ​pentru ​a transforma șirul ​''​a'' ​în șirul ''​b''​.
- ​Exemplu: ​pentru ​vectorul ​''​{0 1 2 4 5 7 6 8 9}''​, al 3-lea element ca ordine este 2, iar vectorul sortat este {0 1 2 4 5 6 7 8 9} +
-</​note>​ +
-</​spoiler>​+
  
-<spoiler Missing number> +**Date de intrare:​**  ​ 
-Se dau $n - 1$ numere naturale distincte intre $0$ si $n - 1$. Scriind o functie ​de partitionare,​ determinati numarul lipsa.+Se citesc două șiruri ​de caractere.
  
-<note tip> +**Date de ieșire:​** ​  
- Exemplupentru $n = 9$ si vectorul ${0 1 9 4 5 7 6 8 2}$numarul lipsa este $3$+Se afișează un singur număr întreg — distanța minimă de editare dintre cele două șiruri. 
-</note>+ 
 +Problema se poate testa la  
 +https://​cses.fi/​problemset/​task/​1639 
 + 
 + 
 +======= 2) Projects ======= 
 + 
 +**Enunt:​** ​  
 +Se dau ''​n''​ proiecte. Fiecare proiect este descris prin trei valori: ziua de început ''​a'',​ ziua de sfârșit ''​b''​ și profitul ''​p''​. ​  
 +Poți realiza un proiect doar dacă nu se suprapune cu alt proiect ales (intervalele nu trebuie să aibă zile comune). 
 + 
 +Determinați profitul maxim care poate fi obținut alegând un subset de proiecte compatibile. 
 + 
 +**Date de intrare:​** ​  
 +Pe prima linie se află un număr întreg ''​n''​. ​  
 +Pe următoarele ''​n''​ linii se află câte trei numere întregi ''​a'',​ ''​b'',​ ''​p''​. 
 + 
 +**Date de ieșire:​** ​  
 +Se afișează un singur număr întreg — profitul maxim posibil. 
 + 
 +Problema se poate testa la:   
 +https://​cses.fi/​problemset/​task/​1140 
 + 
 + 
 +======= 3) Russian Doll Envelopes ======= 
 + 
 +**Enunt:​** ​  
 +Se dă un vector de plicuri, unde fiecare plic este reprezentat prin două valori: lățimea și înălțimea sa.   
 +Un plic poate fi introdus într-un alt plic dacă și numai dacă ambele dimensiuni (lățime și înălțime) sunt strict mai mici. 
 + 
 +Determinați numărul maxim de plicuri care pot fi introduse unul în altul. 
 + 
 +**Date de intrare:​** ​  
 +Un vector bidimensional ''​envelopes'',​ unde ''​envelopes[i] = [w_i, h_i]''​. 
 + 
 +**Date de ieșire:​** ​  
 +Un număr întreg reprezentând numărul maxim de plicuri care pot fi imbricate. 
 + 
 +Problema se poate testa la:   
 +https://​leetcode.com/​problems/​russian-doll-envelopes/​description/​ 
 + 
 + 
 +======= 4) Largest Divisible Subset ======= 
 + 
 +**Enunt:​** ​  
 +Se dă un vector de numere întregi distincte. ​  
 +Determinați cel mai mare subset astfel încât pentru orice două elemente ''​a''​ și ''​b''​ din subset, fie ''​a''​ îl divide pe ''​b'',​ fie ''​b''​ îl divide pe ''​a''​. 
 + 
 +Dacă există mai multe soluții, se poate afișa oricare dintre ele. 
 + 
 +**Date de intrare:​** ​  
 +Un vector de numere întregi distincte ''​nums''​. 
 + 
 +**Date de ieșire:​** ​  
 +Un vector care reprezintă un subset valid de dimensiune maximă. 
 + 
 +Problema se poate testa la:   
 +https://​leetcode.com/​problems/​largest-divisible-subset/​description/​ 
 + 
 + 
 +======= ​5) Best Time to Buy and Sell Stock III ======= 
 + 
 +**Enunt:​** ​  
 +Se dă un vector ''​prices''​unde ''​prices[i]''​ reprezintă prețul unei acțiuni în ziua ''​i''​  
 +Puteți efectua cel mult două tranzacții (o tranzacție constă într-o cumpărare urmată de o vânzare). ​  
 + 
 +Nu puteți deține mai multe acțiuni simultan (trebuie să vindeți înainte de a cumpăra din nou). 
 + 
 +Determinați profitul maxim care poate fi obținut. 
 + 
 +**Date de intrare:​** ​  
 +Un vector de numere întregi ''​prices''​. 
 + 
 +**Date de ieșire:​** ​  
 +Un număr întreg reprezentând profitul maxim posibil. 
 + 
 +Problema se poate testa la:   
 +https://​leetcode.com/​problems/​best-time-to-buy-and-sell-stock-iii/​description/​ 
 + 
 +===== Extra (studiu de caz pentru acasă) ​ =====
  
 +<spoiler Coin Change>
 +Rezolvati pe leetcode problema [[ https://​leetcode.com/​problems/​coin-change/​description/​ | Coin Change]].
 </​spoiler>​ </​spoiler>​
  
 +<spoiler CMLSC>
 +Rezolvati pe infoarena problema [[https://​infoarena.ro/​problema/​cmlsc| CMLSC]]. Similar, problema se poate găsi și [[Leetcode | https://​leetcode.com/​problems/​longest-common-subsequence/​description/​]].
 +</​spoiler>​
  
  
-<​spoiler ​Fractal+<​spoiler ​Custi
-Puteti rezolva aceasta problema ​pe [[https://​infoarena.ro/​problema/​fractalinfoarena]].+Rezolvati ​pe infoarena problema ​[[https://​infoarena.ro/​problema/​custi custi]].
 </​spoiler>​ </​spoiler>​
  
 +<spoiler Rucsac>
 +Modificati solutia de la Rucsac prezentata in laborator pentru a obtine o complexitate spatiala mai buna (se va retine un numar minim de linii din matrice).
 +Puteti testa solutia voastra pe infoarena la problema [[https://​infoarena.ro/​problema/​rucsac | rucsac]].
 +</​spoiler>​
  
-<​spoiler ​SSM+<​spoiler ​Sumele lui Gigel
-Puteti rezolva aceasta problema ​de [[https://​ocw.cs.pub.ro/​courses/​pa/​laboratoare/​laborator-03#ssm +Se da o matrice ​de dimensiuni $n * m$ si Q intrebari de forma"Care este suma din submatricea care are coltul stanga-sus (x, y) si coltul dreapta-jos (p,​q)?"​ 
-| aici]].+ 
 +Se considera proprietatea:​ Q este mult mai mare decat dimensiunile matricei. 
 + 
 +Sa se raspunda in mod eficient la cele Q intrebari. 
 +</​spoiler>​
  
 +<spoiler joctv>
 +Rezolvatie pe infoarena problema [[https://​infoarena.ro/​problema/​joctv| joctv]].
 +    ​
 <​note>​ <​note>​
-Pentru ​problema SSM vom studia o solutie potrivita ​in lab03Puteti sa incercati sa o rezolvati cu Divide et Impera pentru a exersa cele invatate astazi.+Solutie: Se fixeaza 2 linii pentru zona dreptunghiulara. Se reduce ​problema ​la SSM in O(n). 
 +Complexitate:​ $O(n^3)$.
 </​note>​ </​note>​
 </​spoiler>​ </​spoiler>​
  
-<spoiler Secventa descrescatoare>​ 
-Dându-se N numere întregi sub forma unei secvenţe de numere strict crescătoare,​ care se continuă cu o secvenţă de întregi strict descrescătoare,​ se doreşte determinarea punctului din întregul şir înaintea căruia toate elementele sunt strict crescătoare,​ şi dupa care, toate elementele sunt strict descrescătoare. Considerăm evident faptul că acest punct nu există dacă cele N numere sunt dispuse într-un şir fie doar strict crescător, fie doar strict descrescător. 
  
 +<​spoiler ​ Interleaving String>
 +Rezolvati pe leetcode problema [[ https://​leetcode.com/​problems/​interleaving-string/​description/​ |  Interleaving String]].
 +</​spoiler>​
 +
 +<spoiler Cutting rod>
 +https://​www.geeksforgeeks.org/​cutting-a-rod-dp-13/​ [[https://​www.geeksforgeeks.org/​cutting-a-rod-dp-13/​ | Cutting rod]] [0]
 +</​spoiler>​
 +
 +<spoiler Partition Problem>
 +[[ https://​www.geeksforgeeks.org/​partition-problem-dp-18/​|Partition Problem]]
 </​spoiler>​ </​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/​ssm]]
  
 +[2] [[http://​infoarena.ro/​problema/​scmax]]
  
 +[3] [[http://​infoarena.ro/​problema/​rucsac]]
  
pa/laboratoare/laborator-01.1519992940.txt.gz · Last modified: 2018/03/02 14:15 by traian.rebedea
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