This shows you the differences between two versions of the page.
pa:laboratoare:laborator-03 [2019/10/18 08:14] darius.neatu [SCMAX] |
pa:laboratoare:laborator-03 [2023/03/15 16:54] (current) radu.nichita |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laborator 3: Programare Dinamică ====== | + | ====== Laborator 03: Programare Dinamică (1/2) ====== |
- | Responsabili: | + | |
- | * [[neatudarius@gmail.com|Darius Neațu]] | + | |
- | * [[visanr95@gmail.com|Radu Vișan]] | + | |
- | * [[cristb@gmail.com|Cristian Banu]] | + | |
- | * [[razvan.ch95@gmail.com|Răzvan Chițu]] | + | |
===== Obiective laborator ===== | ===== Obiective laborator ===== | ||
- | * Ințelegerea noțiunilor de bază despre programare dinamică | + | * înțelegerea noțiunilor de bază despre programare dinamică |
- | * Insușirea abilităților de implementare a algoritmilor bazați programare dinamică. | + | * însușirea abilităților de implementare a algoritmilor bazați programare dinamică. |
- | ===== Precizari initiale ===== | + | ===== Precizări inițiale ===== |
<note> | <note> | ||
- | Toate exemplele de cod se gasesc in {{pa:new_pa:demo-lab03.zip}}. | + | Toate exemplele de cod se găsesc pe pagina [[https://github.com/acs-pa/pa-lab/tree/main/demo/lab03|pa-lab::demo/lab03]]. |
- | Acestea apar incorporate si in textul laboratorului pentru a facilita parcurgerea cursiva a laboratorului. | + | Exemplele de cod apar încorporate și în textul laboratorului pentru a facilita parcurgerea cursivă a acestuia. ATENȚIE! Varianta actualizată a acestor exemple se găsește întotdeauna pe GitHub. |
</note> | </note> | ||
- | * Toate bucatile de cod prezentate in partea introductiva a laboratorului (inainte de exercitii) au fost testate. Cu toate acestea, este posibil ca din cauza mai multor factori (formatare, caractere invizibile puse de browser etc) un simplu copy-paste sa nu fie de ajuns pentru a compila codul. | + | * Toate bucățile de cod prezentate în partea introductivă a laboratorului (înainte de exerciții) au fost testate. Cu toate acestea, este posibil ca din cauza mai multor factori (formatare, caractere invizibile puse de browser etc) un simplu copy-paste să nu fie de ajuns pentru a compila codul. |
- | * Va rugam sa incercati si codul din arhiva ** demo-lab03.zip**, inainte de a raporta ca ceva nu merge. | + | * Vă rugam să compilați **DOAR** codul de pe GitHub. Pentru raportarea problemelor, contactați unul dintre maintaineri. |
- | * Pentru orice problema legata de continutul acestei pagini, va rugam sa dati email unuia dintre responsabili. | + | * Pentru orice problemă legată de conținutul acestei pagini, vă rugam să dați e-mail unuia dintre responsabili. |
===== Ce este DP? ===== | ===== Ce este DP? ===== | ||
- | Similar cu greedy, tehnica de programare dinamica este folosită in general pentru rezolvarea **problemelor de optimizare**. | + | Similar cu greedy, tehnica de programare dinamică este folosită în general pentru rezolvarea **problemelor de optimizare**. |
- | In continuare vom folosi acronimul **DP (dynamic programming)**. | + | În continuare vom folosi acronimul **DP (dynamic programming)**. |
- | De asemenea, DP se poate folosi si pentru probleme in care nu cautam un optim, cum ar fi **problemele de numarare** (vom exemplifica in lab04). | + | 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). |
===== Aplicatii DP ===== | ===== Aplicatii DP ===== | ||
- | Programarea dinamică are un **câmp larg de aplicare**, insa la PA ne vom rezuma la cateva aplicatii care vor fi mentionate pe parcursul laboratoarelor 3 si 4. De asemenea, aceasta tehnica va fi folosita si in laboratoarele de grafuri (ex. algoritmul Floyd-Warshall - pe care il veti implementa si la PA; algoritmi pe arbori etc). | + | 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 3 ș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). |
- | Programare 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**. | + | 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**. |
<spoiler Memoizare and more DP> | <spoiler Memoizare and more DP> | ||
Pentru a 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. | Pentru a 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. | ||
- | Pentru a nu recalcula soluțiile subproblemelor ce ar trebui rezolvate de mai multe ori, pe 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. Aceasta tehnica se numește **memoizare**. | + | Pentru a nu recalcula soluțiile subproblemelor ce ar trebui rezolvate de mai multe ori, pe 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**. |
- | Aceasta tehnică determina ”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” is schimba înțelesul logic de la o problema la alta. În probleme de minimizarea costului, ”valoarea” este reprezentata de costul minim. In probleme care presupun identificarea unei componente maxime, ”valoarea” este caracterizată de dimensiunea componentei. | + | 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. |
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. | 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. | ||
Line 42: | Line 38: | ||
</spoiler> | </spoiler> | ||
- | Cei curiosi pot citi [[https://en.wikipedia.org/wiki/Dynamic_programming#History|aici]] adevarul despre numele acestei tehnici. 8-) | + | Cei curioși pot citi [[https://en.wikipedia.org/wiki/Dynamic_programming#History|aici]] adevărul despre numele acestei tehnici. 8-) |
===== Ce determina DP? ===== | ===== Ce determina DP? ===== | ||
- | Aplicând aceasta 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. | + | 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. |
- | In cazul problemelor de numarare, aceasta tehnica ne va furniza **numarul cautat**. | + | În cazul problemelor de numărare, această tehnică ne va furniza **numărul căutat**. |
===== Tipar general DP ===== | ===== Tipar general DP ===== | ||
Aplicarea acestei tehnici de programare poate fi descompusă în următoarea secvență de pași: | Aplicarea acestei tehnici de programare poate fi descompusă în următoarea secvență de pași: | ||
Line 57: | Line 53: | ||
===== Exemple clasice ===== | ===== Exemple clasice ===== | ||
- | Programarea Dinamică este cea mai flexibilă tehnica din programare. Cel mai ușor mod de a o înțelege presupune parcurgerea cât mai multor exemple. | + | Programarea Dinamică este cea mai flexibilă tehnică din programare. Cel mai ușor mod de a o înțelege presupune parcurgerea cât mai multor exemple. |
- | Propunem cateva categorii de recurente, pe care le vom grupa astfel: | + | Propunem câteva categorii de recurențe, pe care le vom grupa astfel: |
- | * recurente de tip **SSM** (Subsecventa de Suma Maxima) | + | * recurențe de tip **SSM** (Subsecvența de Sumă Maximă) |
- | * recurente de tip **RUCSAC** | + | * recurențe de tip **RUCSAC** |
- | * recurente de tip **PODM** (Parantezare Optima de Matrici) | + | * recurențe de tip **PODM** (Parantezare Optimă de Matrici) |
- | * recurente de tip **numărat** | + | * recurențe de tip **numărat** |
- | * recurente pe **grafuri** | + | * recurențe pe **grafuri** |
<note> | <note> | ||
- | Pentru o problema data, este **posibil** sa gasim **mai multe recurente corecte **(mai multe solutii posibile). Evident, criteriul de alegere intre acestea va fi cel bazat pe complexitate. | + | Pentru o problemă dată, este **posibil** să găsim **mai multe recurențe corecte **(mai multe soluții posibile). Evident, criteriul de alegere între acestea va fi cel bazat pe complexitate. |
</note> | </note> | ||
+ | |||
+ | |||
===== Categoria 1: SSM ===== | ===== Categoria 1: SSM ===== | ||
- | Aceste recurente au o oarecare asemanare cu problema SSM (enunt + solutie). | + | Aceste recurențe au o oarecare asemănare cu problema SSM (enunț + soluție). |
+ | |||
+ | |||
+ | |||
+ | <note> | ||
+ | ATENȚIE! Rețineți diferența între următoarele 2 noțiuni! | ||
+ | |||
+ | |||
+ | * **subsecvență** ([[https://en.wikipedia.org/wiki/Substring | substring]] în engleză) pentru un vector ** v ** înseamnă un alt vector $u = [v[i], v[i+1],..., v[j]]$ unde $i <= j$. | ||
+ | |||
+ | * **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> | ||
==== SSM ==== | ==== SSM ==== | ||
- | === Enunt === | + | === Enunț === |
- | Fie un vector $ v $ cu $ n $ elemente intregi. O subsecventa de numere din sir este de forma: $s_i, s_{i+1}, ... , s_j$ ($i <= j$), avand suma asociata $s_{ij} = s_i + s_{i+1} + ... + s_j$. O subsecventa ** nu ** poate fi vida. | + | Fie un vector $ v $ cu $ n $ elemente întregi. O subsecvență de numere din șir este de forma: $v_i, v_{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ă. |
+ | |||
- | === Cerinta === | + | === Cerință === |
- | Sa se determine subsecventa de suma maxima (notata **SSM**). | + | Să se determine subsecvența de sumă maximă (notată **SSM**). |
=== Exemple === | === Exemple === | ||
Line 87: | Line 98: | ||
|v[i]| -10 | 2 | 3 | -1| 2 | -3 | | |v[i]| -10 | 2 | 3 | -1| 2 | -3 | | ||
- | Raspuns: SSM este intre 2 si 5 (pozitii). Are suma +6. ($SSM = 2, 3, -1, 2$) | + | Răspuns: SSM este între 2 și 5 (poziții). Are suma +6. ($SSM = 2, 3, -1, 2$) |
- | Explicatie: avem numere pozitive, deci exista o solutie simpla in care putem sa alegem doar un numar pozitiv/mai multe numere pozitive de pe pozitii alaturate (adica incercam sa evitam numere negative). Cele mai lungi subsecvente cu numere pozitive sunt 2,3 si 2. Observam ca daca extindem $2, 3$ la $2, 3, -1, 2$, desi am inclus un numar negativ, suma secventei creste. | + | 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> | ||
Line 98: | Line 109: | ||
|v[i]| 10 | 20 | 30 | 40| | |v[i]| 10 | 20 | 30 | 40| | ||
- | Raspuns: SSM este intre 1 si 4 (pozitii). Are suma 100. ($SSM = 10, 20, 30, 40$) | + | Răspuns: SSM este între 1 și 4 (poziții). Are suma 100. ($SSM = 10, 20, 30, 40$) |
- | Explicatie: deoarece toate numerele sunt **pozitive**, SSM cuprinde toate numerele. | + | Explicație: deoarece toate numerele sunt **pozitive**, SSM cuprinde toate numerele. |
</spoiler> | </spoiler> | ||
Line 109: | Line 120: | ||
|v[i]| -10 | -20 | -30 | -40| | |v[i]| -10 | -20 | -30 | -40| | ||
- | Raspuns: SSM este intre 1 si 1 (pozitii). Are suma -10. ($SSM = -10$) | + | Răspuns: SSM este între 1 si 1 (poziții). Are suma -10. ($SSM = -10$) |
- | Explicatie: deoarece toate numerele sunt **negative**, SSM cuprinde doar cel mai mare numar. | + | Explicație: deoarece toate numerele sunt **negative**, SSM cuprinde doar cel mai mare număr. |
</spoiler> | </spoiler> | ||
=== Rezolvare === | === Rezolvare === | ||
== Tipar == | == Tipar == | ||
- | Tiparul acestei probleme ne sugereaza ca o solutie este obtinuta incremental, in sensul ca **putem** privi problema astfel: gasim cea mai buna solutie folosind **primele $i-1$** elemente din sir, apoi incercam sa o **extindem** folosind elementul **i** (adica ne extindem la dreapta ~CU~ $v[i]$). | + | Tiparul acestei probleme ne sugerează că o soluție este obținută incremental, în sensul că **putem** privi problema astfel: gă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]$). |
- | == Numire recurenta == | + | == Numire recurență == |
- | Intrucat la fiecare pas trebuie sa retinem ** cea mai buna solutie** folosind un **prefix** din vectorul v, solutia va fi salvata intr-un tablou auxiliar definit astfel: | + | Î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: |
- | $ dp[i] $ = suma subsecventei de suma maxima (**suma SSM**) folosind ** doar primele i ** elemente din vectorul v si care se termina pe pozitia i | + | $ dp[i] $ = suma subsecvenței de sumă maximă (**suma SSM**) folosind ** doar primele i ** elemente din vectorul v și care **se termină pe poziția i** |
- | == Mentiuni == | + | == Mențiuni == |
- | * Pentru a mentine o conventie, toate tablourile de acest tip din laborator vor fi notate cu ** dp ** (dynamic programming). | + | * Pentru a menține o convenție, toate tablourile de acest tip din laborator vor fi notate cu ** dp ** (dynamic programming). |
- | * Ca sa rezolvam problema data, trebuie sa rezolvam o multime de subprobleme | + | * Ca să rezolvăm problema dată, trebuie să rezolvăm o mulțime de subprobleme |
- | * $dp[i]$ reprezinta **solutia** pentru problema $v[1], ..., v[i]$ si care se termina cu $v[i]$ | + | * $dp[i]$ reprezintă **soluția** pentru problema $v[1], ..., v[i]$ și care se termină cu $v[i]$ |
- | * Solutia pentru problema initiala este maximul din vectorul $dp[i]$. | + | * Soluția pentru problema inițială este maximul din vectorul $dp[i]$ - a.k.a. **max(dp[i])**. |
- | == Gasire recurenta == | + | == Găsire recurență == |
- | Intrucat dorim ca aceasta problema sa fie rezolvabila printr-un algoritm/bucata de cod, trebuie sa descriem o metoda concreta prin care vom calcula $dp[i]$. | + | Întrucât dorim ca această problemă să fie rezolvabilă printr-un algoritm/bucată de cod, trebuie să descriem o metodă concretă prin care vom calcula $dp[i]$. |
- | * **Cazul de baza** | + | * **Cazul de bază** |
- | * In general in probleme putem avea mai multe cazuri de baza, care in principiu se leaga de valori extreme are dimensiunilor subproblemelor. | + | * În general în probleme putem avea mai multe cazuri de bază, care în principiu se leagă de valori extreme are dimensiunilor subproblemelor. |
- | * In cazul SSM, avem un singur caz de baza, cand avem un singur element in prefix: $dp[1] = v[1] $. | + | * În cazul SSM, avem un singur caz de bază, când avem un singur element în prefix: $dp[1] = v[1] $. |
- | * Explicatie: daca avem un singur element, atunci acesta formeaza singura subsecventa posibila, deci $ SSM = v[1] $ | + | * Explicație: dacă avem un singur element, atunci acesta formează singura subsecvență posibilă, deci $ SSM = v[1] $ |
| | ||
* ** Cazul general ** | * ** Cazul general ** | ||
- | * presupune inductiv ca avem rezolvate toate subproblemele mai mici | + | * presupune inductiv că avem rezolvate toate subproblemele mai mici |
- | * in cazul SSM, presupunem ca avem calculat $ dp[i-1] $ si dorim sa calculam $ dp[i] $ (cunoastem cea mai buna solutie folosind primele i-1 elememente si vedem daca elementul de pe pozitia i o poate imbunatati) | + | * î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 daca $v[i]$ extinde cea mai buna solutie care se termina pe $v[i-1]$ sau se incepe o noua secventa cu $v[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 in functie de $ dp[i - 1]$ si $v[i] $ | + | * decidem în funcție de $ dp[i - 1]$ și $v[i] $ |
- | * ** daca ** $ dp[i - 1] >= 0 $ (cea mai buna solutie care se termina pe i - 1 are cost nenegativ) | + | * ** dacă ** $ dp[i - 1] >= 0 $ (cea mai bună soluție care se termină pe i - 1 are cost nenegativ) |
- | * extindem secventa care se termina cu v[i-1] folosind elementul v[i]: $dp[i] = dp[i-1] + v[i]$ | + | * extindem secvență care se termină cu v[i-1] folosind elementul v[i]: $dp[i] = dp[i-1] + v[i]$ |
- | * Explicatie: $dp[i-1] + v[i] >= v[i]$ (inca are rost sa extind) | + | * Explicație: $dp[i-1] + v[i] >= v[i]$ (încă are rost să extind) |
- | * ** daca ** $ dp[i - 1] < 0 $ (cea mai buna solutie care se termina pe i - 1 are cost negativ) | + | * ** dacă ** $ dp[i - 1] < 0 $ (cea mai bună soluție care se termină pe i - 1 are cost negativ) |
- | * vom incepe o noua secventa cu $v[i]$, adica $dp[i] = v[i]$ | + | * vom începe o nouă secvență cu $v[i]$, adică $dp[i] = v[i]$ |
- | * Explicatie: $v[i] > dp[i-1] + v[i]$, deci prin extindere nu obtin solutie maxima! | + | * Explicație: $v[i] > dp[i-1] + v[i]$, deci prin extindere nu obțin soluție maximă! |
- | == Implementare recurenta == | + | == Implementare recurență == |
- | In majoritatea problemelor de DP, gasirea recurentei ocupa cea mai mare parte a timpului de rezolvare (lucru adevarat si in cazul problemelor de la PA). De aceea, faptul ca ati reusit sa scrieti pe foaie lucruri foarte complicate poate fi un indiciu ca ati pornit pe o cale gresita. | + | Î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ă. |
<spoiler Exemplu implementare> | <spoiler Exemplu implementare> | ||
- | Mai jos se afla un exemplu simplu de implementare a recurentei gasite in C++. | + | 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 a recurenței găsite în C++. | ||
<code cpp> | <code cpp> | ||
- | // gaseste SSM pentru vectorul v cu n elemente | + | // găsește SSM pentru vectorul v cu n elemente |
- | // pentru a mentine conventia din explicatii: | + | // 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 | // - elementele sunt indexate de la 0, dar le folosesc doar pe cele care incep de la 1 | ||
// => v[1], ..., v[n] | // => v[1], ..., v[n] | ||
int SSM(int n, vector<int> &v) { | int SSM(int n, vector<int> &v) { | ||
- | vector<int> dp(n + 1); // vector cu n + 1 elemente (indexarea incepe de la 0) | + | vector<int> dp(n + 1); // vector cu n + 1 elemente (indexarea începe de la 0) |
// am nevoie de dp[1], ..., dp[n] | // am nevoie de dp[1], ..., dp[n] | ||
- | // caz de baza | + | // caz de bază |
dp[1] = v[1]; | dp[1] = v[1]; | ||
Line 175: | Line 188: | ||
dp[i] = dp[i - 1] + v[i]; | dp[i] = dp[i - 1] + v[i]; | ||
} else { | } else { | ||
- | // incep o noua secventa | + | // încep o nouă secvență |
dp[i] = v[i]; | dp[i] = v[i]; | ||
} | } | ||
} | } | ||
- | // solutia e maximul din vectorul dp | + | // soluția e maximul din vectorul dp |
int sol = dp[1]; | int sol = dp[1]; | ||
for (int i = 2; i <= n; ++i) { | for (int i = 2; i <= n; ++i) { | ||
Line 188: | Line 201: | ||
} | } | ||
- | return sol; // aceasta este suma asociata cu SSM | + | return sol; // aceasta este suma asociată cu SSM |
} | } | ||
</code> | </code> | ||
- | Daca dorim sa afisam si indicii intre care apare SSM, putem sa stocam si pozitia de start pentru fiecare solutie intermediara. Gasiti aceasta solutie in ** demo-lab03.zip**. | + | 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ă. . |
- | Hint: definiti ** start[i] ** = pozitia pe care a inceput subsecventa care da solutia cu cost dp[i]. | + | Hint: definiți ** start[i] ** = poziția pe care a început subsecvența care dă soluția cu cost dp[i]. |
</spoiler> | </spoiler> | ||
- | === Mentiuni === | + | === Mențiuni === |
- | Intrucat aceasta solutie presupune calculul iterativ (coloana cu coloana) a matricei dp, complexitatea este liniara. De asemenea, se mai parcurge o data dp pentru a gasi maximul. | + | Î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 temporala **: $T = O(n)$ | + | * **complexitate temporală **: $T = O(n)$ |
- | * **complexitate spatiala ** : $S = O(n)$ | + | * **complexitate spațială ** : $S = O(n)$ |
- | * desigur ca pentru problema SSM, nu era nevoie sa retinem, tablourile dp/start in memorie. | + | * desigur că pentru problema SSM, nu era nevoie sa reținem, tablourile dp/start în memorie. |
- | * puteam sa construim element cu element si maximul din dp in aceleasi timp (intrucat ne trebuie ultima valoare la fiecare pas si maximul global). | + | * 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). |
- | * in acest caz complexitatea spatiala devine $S = O(1)$ | + | * în acest caz complexitatea spațială devine $S = O(1)$ |
+ | |||
+ | ** 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").** | ||
- | ** Pentru a ilustra toti pasii posibili intr-o astfel de problema, totul a fost prezentat cat mai simplu (NU in toate problemele putem facem simplificari de tipul "NU am nevoie sa stochez tabloul dp").** | ||
==== SCMAX ==== | ==== SCMAX ==== | ||
- | === Enunt === | + | === Enunț === |
- | Fie un vector $ v $ cu $ n $ elemente intregi. Un subsir de numere din sir este de forma: $s_{i_1}, s_{i_2}, ... , {s_{i_k}}$. Un subsir ** nu ** poate fi vid ($k >= 1$). | + | 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$). |
- | === Cerinta === | + | === Cerința === |
- | Sa se determine subsirul crescator maximal (notat **SCMAX**) - un subsir ordonat strict crescator si are lungime maxima (daca sunt mai multe solutii, sa se gaseasca una oarecare). | + | 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). |
=== Exemple === | === Exemple === | ||
Line 222: | Line 236: | ||
|v[i]| 100 | 12 | 13 | -1| 15 | -30 | | |v[i]| 100 | 12 | 13 | -1| 15 | -30 | | ||
- | Raspuns: $SCMAX = 12, 13, 15$ ($SCMAX = v[2], v[3], v[5])$. | + | Răspuns: $SCMAX = 12, 13, 15$ ($SCMAX = v[2], v[3], v[5])$. |
- | Explicatie: | + | Explicație: |
- | Toate subsirurile ordonate strict crescator sunt: | + | Toate subșirurile ordonate strict crescător sunt: |
* $100$ | * $100$ | ||
* $12$ | * $12$ | ||
Line 237: | Line 251: | ||
* $15$ | * $15$ | ||
* $-30$ | * $-30$ | ||
- | Cel mentionat este singurul de lungime 3. | + | Cel menționat este singurul de lungime 3. |
</spoiler> | </spoiler> | ||
Line 246: | Line 260: | ||
|v[i]| 100 | 12 | 13 | -1| 15 | 14 | | |v[i]| 100 | 12 | 13 | -1| 15 | 14 | | ||
- | Raspuns: | + | Răspuns: |
* $SCMAX = 12, 13, 15$ ($SCMAX = v[2], v[3], v[5])$. | * $SCMAX = 12, 13, 15$ ($SCMAX = v[2], v[3], v[5])$. | ||
* $SCMAX = 12, 13, 14$ ($SCMAX = v[2], v[3], v[6])$. | * $SCMAX = 12, 13, 14$ ($SCMAX = v[2], v[3], v[6])$. | ||
- | Explicatie: | + | Explicație: |
- | Toate subsirurile ordonate strict crescator sunt: | + | Toate subșirurile ordonate strict crescător sunt: |
* $100$ | * $100$ | ||
* $12$ | * $12$ | ||
Line 265: | Line 279: | ||
* $15$ | * $15$ | ||
* $14$ | * $14$ | ||
- | Cele 2 solutii indicate au ambele lungime maxima. | + | Cele 2 soluții indicate au ambele lungime maximă. |
</spoiler> | </spoiler> | ||
Line 271: | Line 285: | ||
=== Rezolvare === | === Rezolvare === | ||
== Tipar== | == Tipar== | ||
- | Verificam daca se aplica tiparul de la SSM: **gasim cea mai buna solutie folosind primele $i-1$ elemente din sir, apoi incercam sa o extindem folosind elementul i (adica ne extindem la dreapta ~CU~ $v[i]$)**. | + | 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]$)**. |
- | * Daca avem cea mai buna solutie pentru intervalul $1, 2, .., i-1$ si care se termina cu $v[i-1]$, atunci incercam sa extindem solutia cu $v[i]$ (putem daca $v[i-1] < v[i]$) | + | * 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]$) |
- | * Altfel.. Unde am putea sa il punem pe $v[i]$? | + | * Altfel.. Unde am putea să îl punem pe $v[i]$? |
- | * Pai am putea sa incercam sa il punem la finalul solutiei care se termina pe $v[i-2]$, $v[i-3]$, ... sau $v[1]$ | + | * 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]$ |
- | == Numire recurenta == | + | == Numire recurență == |
- | $ dp[i] $ = lungimea celui mai lung subsir(**lungime SCMAX**) folosind (doar o parte) din primele i elemente din vectorul v si care se termina pe pozitia i | + | $ 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 |
- | == Mentiuni == | + | == Mențiuni == |
- | * Ca sa rezolvam problema data, trebuie sa rezolvam o multime de subprobleme | + | * Ca să rezolvăm problema dată, trebuie să rezolvăm o mulțime de subprobleme |
- | * $dp[i]$ reprezinta **solutia** pentru problema $v[1], ..., v[i]$ si care se termina cu $v[i]$ | + | * $dp[i]$ reprezintă **soluția** pentru problema $v[1], ..., v[i]$ și care se termină cu $v[i]$ |
- | * Solutia pentru problema initiala este maximul din vectorul $dp[i]$. | + | * Soluția pentru problema inițială este maximul din vectorul $dp[i]$. |
- | == Gasire recurenta == | + | == Găsire recurență == |
- | * **Cazul de baza** | + | * **Cazul de bază** |
- | * Si in problema SCMAX, cazul pentru $i = 1$ este caz de baza. | + | * Și în problema SCMAX, cazul pentru $i = 1$ este caz de bază. |
- | * daca avem un singur element, atunci avem o singura subsecventa de lungime 1, ea este solutia | + | * dacă avem un singur element, atunci avem o singură subsecvență de lungime 1, ea este soluția |
* $dp[1] = 1$ | * $dp[1] = 1$ | ||
| | ||
* ** Cazul general ** | * ** Cazul general ** | ||
- | * presupune inductiv ca avem rezolvate toate subproblemele mai mici | + | * presupune inductiv că avem rezolvate toate subproblemele mai mici |
- | * in cazul SCMAX, presupunem ca avem calculate $ dp[1], dp[2], ..., dp[i-1] $ si dorim sa calculam $ dp[i] $ (cunoastem cea mai buna solutie folosind primele j elemente si vedem daca elementul de pe pozitia i o poate imbunatati - $j = 1:i-1$) | + | * î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 stim unde e cel mai bine sa il pune pe $v[i]$ (dupa care v[j]?), incercam pentru toate valorile posibile ale lui j (unde $j = 1 : n - 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$) |
- | * **daca $v[j] < v[i] $**, atunci subsirul crescator care se termina pe pozitia j, poate fi extins la dreapta cu elementul v[i], generand lungimea ** dp[j] + 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$ (daca nu exista un astfel de j, valoarea lui max(...) este 0) | + | * 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 intampla totusi daca nu exista un j care sa indeplineasca conditia de mai sus? Atunci $v[i]$ va forma singur un subsir crescator de lungime 1 (care poate fi la un pas ulterior) | + | * 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: | Reunind cele spuse mai sus: | ||
* $dp[1] = 1$ | * $dp[1] = 1$ | ||
- | * $dp[i] = 1 + max(dp[j])$, unde $j = 1 : i-1$ **și** $v[j] < v[i]$ | + | * $dp[i] = 1 + max(dp[j])$, unde $j = 1 : i-1$ **și** $v[j] < v[i]$; $i=2:n$ |
- | == Implementare recurenta == | + | == Implementare recurență == |
<spoiler Exemplu implementare> | <spoiler Exemplu implementare> | ||
+ | Problema se poate testa pe infoarena: [[https://www.infoarena.ro/problema/scmax | Subșir crescător maximal]]. | ||
- | Mai jos se afla un exemplu simplu de implementare a recurentei gasite in C++. | + | Mai jos se află un exemplu simplu de implementare a recurentei găsite în C++. |
<code cpp> | <code cpp> | ||
- | // n = numarul de elemente din vector | + | // n = numărul de elemente din vector |
- | // v = vectorul dat (v[1], v[2], ..., v[n] - indexare de la 1 ca in explicatii) | + | // v = vectorul dat (v[1], v[2], ..., v[n] - indexare de la 1 ca în explicații) |
void scmax(int n, vector<int> &v) { | void scmax(int n, vector<int> &v) { | ||
- | vector<int> dp(n + 1); // in explicatii indexarea incepe de la 1 | + | vector<int> dp(n + 1); // în explicații indexarea începe de la 1 |
- | // caz de baza | + | // caz de bază |
- | dp[1] = 1; // [ v[1] ] este singurul subsir (crescator) care se termina pe 1 | + | dp[1] = 1; // [ v[1] ] este singurul subșir (crescător) care se termină pe 1 |
// caz general | // caz general | ||
for (int i = 2; i <= n; ++i) { | for (int i = 2; i <= n; ++i) { | ||
- | dp[i] = 1; // [ v[i] ] - este un subsir (crescator) care se termina pe i | + | dp[i] = 1; // [ v[i] ] - este un subșir (crescător) care se termină pe i |
- | // incerc sa il pun pe v[i] la finalul tuturor solutiilor disponibile | + | // încerc să îl pun pe v[i] la finalul tuturor soluțiilor disponibile |
- | // o solutie se termina cu un element v[j] | + | // o soluție se termină cu un element v[j] |
for (int j = 1; j < i; ++j) { | for (int j = 1; j < i; ++j) { | ||
- | // solutia triviala: v[i] | + | // soluția trivială: v[i] |
if (v[j] < v[i]) { | if (v[j] < v[i]) { | ||
- | // din (..., v[j]) pot obtine (..., v[j], v[i]) | + | // din (..., v[j]) pot obține (..., v[j], v[i]) |
- | // (caz in care prec[i] = j) | + | // (caz în care prec[i] = j) |
- | // voi alege j-ul curent, cand alegerea imi gaseste o solutie mai buna decat ce am deja | + | // voi alege j-ul curent, când alegerea îmi găsește o soluție mai bună decât ce am deja |
if (dp[j] + 1 > dp[i]) { | if (dp[j] + 1 > dp[i]) { | ||
dp[i] = dp[j] + 1; | dp[i] = dp[j] + 1; | ||
Line 338: | Line 353: | ||
} | } | ||
- | // solutia e maximul din vectorul dp | + | // soluția e maximul din vectorul dp |
int sol = dp[1], pos = 1; | int sol = dp[1], pos = 1; | ||
for (int i = 2; i <= n; ++i) { | for (int i = 2; i <= n; ++i) { | ||
Line 353: | Line 368: | ||
<spoiler Exemplu implementare cu reconstituire> | <spoiler Exemplu implementare cu reconstituire> | ||
+ | Problema se poate testa pe infoarena: [[https://www.infoarena.ro/problema/scmax | Subșir crescător maximal]]. | ||
- | In **demo-lab03.zip** gasiti un exemplu de implementare care arata si cum puteti reconstitui SCMAX. | ||
- | Fata de implementarea anterioara, in aceasta versiune se foloseste un tablou auxiliar prec. | ||
- | $prec[i]$ = indicele j al elementului v[j], pentru care $dp[j] + 1 == dp[i]$ (adica acel j pentru care subsirul crescator maximal care se termina cu $v[i]$ este extinderea cu un element a celui care se termina cu $v[j]$. | + | Î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. |
- | * daca nu exista un astfel de j, atunci $prec[i] = 0$ (prin conventie) | + | Față de implementarea anterioară, în această versiune se folosește un tablou auxiliar prec. |
+ | |||
+ | $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]$. | ||
+ | * dacă nu există un astfel de j, atunci $prec[i] = 0$ (prin convenție) | ||
</spoiler> | </spoiler> | ||
- | === Mentiuni === | + | === Mențiuni === |
- | Intrucat aceasta solutie presupune calculul iterativ (coloana cu coloana) a matricei dp, complexitatea este polinomiala (patratica - pentru fiecare element din tabloul, facem o trecere prin elementele deja calculate). | + | Î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). |
- | * **complexitate temporala **: $T = O(n^2)$ | + | * **complexitate temporală **: $T = O(n^2)$ |
- | * se poate obtine o solutie in complexitate $T = O(n log n)$ daca se foloseste o cautare binara pentru a gasi elementul j dorit (ex. [[https://www.infoarena.ro/job_detail/1248867?action=view-source | implemetare]]). | + | * 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 spatiala ** : $S = O(n)$ | + | * **complexitate spațială ** : $S = O(n)$ |
- | * NU putem obtine o complexitate spatiala mai buna, intrucat avem nevoie sa stocam cel putin vectorul dp (stocam si vectorul prec daca avem nevoie sa reconstituim SCMAX) | + | * 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) |
+ | |||
+ | |||
===== Categoria 2: RUCSAC ===== | ===== Categoria 2: RUCSAC ===== | ||
- | Aceste recurente au o oarecare asemanare cu problema RUCSAC - varianta discreta (enunt + solutie). | + | Aceste recurențe au o oarecare asemănare cu problema RUCSAC - varianta discretă (enunț + soluție). |
==== RUCSAC ==== | ==== RUCSAC ==== | ||
- | === Enunt === | + | === Enunț === |
- | Fie un set (vector) cu $ n $ obiecte (care nu pot fi taiate - varianta discreta a problemei). Fiecare obiect i are asociata o pereche ($w_i, p_i$) cu semnificatia: | + | Fie un set (vector) cu $ 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 numarul i | + | * $w_i$ = $weight_i$ = greutatea obiectului cu numărul i |
- | * $p_i$ = $price_i$ = pretul obiectului cu numarul i | + | * $p_i$ = $price_i$ = prețul obiectului cu numărul i |
* $w_i >= 0$ si $p_i > 0$ | * $w_i >= 0$ si $p_i > 0$ | ||
- | Gigel are la dispozitie un rucsac de ** volum infinit**, dar care suporta o **greutate maxima** (notata cu $W$ - weight knapsack). | + | Gigel are la dispoziție un rucsac de ** volum infinit**, dar care suportă o **greutate maximă** (notată cu $W$ - weight knapsack). |
- | El vrea sa gaseasca ** o submultime de obiecte** pe care sa le bage in rucsac, astfel incat **suma profiturilor sa fie maxima**. | + | 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ă**. |
- | Daca Gigel baga in rucsac obiectul i, caracterizat de ($w_i, p_i$), atunci profitul adus de obiect este $p_i$ (presupunem ca il vinde cu cat valoreaza obiectul). | + | 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). |
- | === Cerinta === | + | === Cerință === |
- | Sa se determine **profitul maxim** pentru Gigel. | + | Să se determine **profitul maxim** pentru Gigel. |
=== Exemple === | === Exemple === | ||
Line 395: | Line 415: | ||
|p|6|3|2|8|5| | |p|6|3|2|8|5| | ||
- | Raspuns: **24** (profitul maxim) | + | Răspuns: **24** (profitul maxim) |
- | Explicatie: va alege toate obiectele :D. | + | Explicație: va alege toate obiectele :D. |
</spoiler> | </spoiler> | ||
<spoiler Exemplu 2> | <spoiler Exemplu 2> | ||
- | $n = 5$ si $W = 3$ | + | $n = 5$ și $W = 3$ |
| |1|2|3|4|5| | | |1|2|3|4|5| | ||
Line 407: | Line 427: | ||
|p|6|3|2|8|5| | |p|6|3|2|8|5| | ||
- | Raspuns: **13** (profitul maxim) | + | Răspuns: **13** (profitul maxim) |
- | Explicatie: va alege obiectele cu indicii 4 si 5 (profit: 8 + 5) | + | Explicație: va alege obiectele cu indicii 4 si 5 (profit: 8 + 5) |
</spoiler> | </spoiler> | ||
=== Rezolvare === | === Rezolvare === | ||
== Tipar == | == Tipar == | ||
- | Cum am transpune tiparul de la SSM/SCMAX in problema RUCSAC? | + | Cum am transpune tiparul de la SSM/SCMAX în problema RUCSAC? |
- | * stim care este profitul maxim pe care il obtine daca folosim | + | * știm care este profitul maxim pe care îl obține dacă folosim |
* doar primul element | * doar primul element | ||
* doar primele $2$ elemente | * doar primele $2$ elemente | ||
* ... | * ... | ||
* doar primele $i-1$ elemente | * doar primele $i-1$ elemente | ||
- | * ajung sa ma gandesc la obiectul (elementul) i | + | * ajung să mă gândesc la obiectul (elementul) i |
- | * este posibil ca acesta sa nu apara neaparat in solutia cea mai buna, caz in care nu il folosesc, deci solutia maxima se gaseste intre cele mentionate mai sus | + | * 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 |
- | * daca folosesc elementul i caracterizat de ($w_i, p_i$), in primul rand acesta trebuie sa incapa in ghiozdan... | + | * dacă folosesc elementul i caracterizat de ($w_i, p_i$), în primul rând acesta trebuie să încapă în ghiozdan... |
* cum verific acest lucru? | * cum verific acest lucru? | ||
- | * o recurenta de tipul $ dp[i] = ... $ nu va fi suficienta, pentru ca in aceasta problema am 2 dimensiuni: ** obiectele ** (submultimile de indici) si ** greutatile ** (asociate cu obiectele / submultimile de obiecte). | + | * 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 recurenta == | + | == Numire recurență == |
- | Intrucat la fiecare pas trebuie sa retinem ** cea mai buna solutie** folosind un **prefix** din vectorul de obiecte, dar pentru ca trebuie sa punem si ** o restrictie de greutate** necesara (ocupata in rucsac), solutia va fi salvata intr-un tablou auxiliar definit astfel: | + | Î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: |
- | $ dp[i][cap] $ = profitul maxim (**profit RUCSAC**) obtinut folosind (doar o parte) din primele i obiecte si avand un rucsac de **capacitate maxima cap** | + | $ 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** |
- | Observatii: | + | Observații: |
- | * NU exista restrictie daca in solutia mentionata de $dp[i][cap]$ este folosit OBLIGATORIU elementul i | + | * **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). |
- | * Solutia problemei se gaseste in $dp[n][W]$ (profitul maxim folosind (doar o parte) din primele n elemente - adica toate; capacitatea maxima folosita este W - adica capacitatea maxima a rucsacului). | + | * Soluția problemei se găsește în $dp[n][W]$ (profitul maxim folosind (doar o parte) din 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ă). |
- | == Gasire recurenta == | + | == Găsire recurență == |
- | * **Cazul de baza** | + | * **Cazul de bază** |
- | * Daca avem o submultime vida de obiecte selectate. | + | * Dacă avem o submulțime vidă de obiecte selectate. |
* $ dp[0][cap] = 0 $ | * $ dp[0][cap] = 0 $ | ||
- | * Explicatie: Daca nu alegem obiecte, atunci profitul este 0 indiferent de capacitate. | + | * Explicație: Dacă nu alegem obiecte, atunci profitul este 0 indiferent de capacitate. |
* ** Cazul general ** | * ** Cazul general ** | ||
* $ dp[i][cap] = ? $ | * $ dp[i][cap] = ? $ | ||
- | * presupune inductiv ca avem rezolvate toate subproblemele mai mici | + | * presupune inductiv că avem rezolvate toate subproblemele mai mici |
- | * subprobleme mai mici inseamna sa foloseasca mai putine obiecte sau un rucsac cu capacitatea mai mica | + | * subprobleme mai mici înseamnă să folosească mai puține obiecte sau un rucsac cu capacitatea mai mică |
- | * vedem daca prin folosirea obiectului i, obtinem cea mai buna solutie in $dp[i][cap]$ | + | * vedem dacă prin folosirea obiectului i, obținem cea mai bună soluție in $dp[i][cap]$ |
* **NU folosesc obiectul i** | * **NU folosesc obiectul i** | ||
- | * in acest caz, o sa alegem cea mai buna solutie formata cu celelalte $i-1$ elemente si aceeasi capacitate a rucsacului | + | * în acest caz, o să alegem cea mai bună soluție formată cu celelalte $i-1$ elemente și aceeași capacitate a rucsacului |
- | * solutia generata de acest caz: $dp[i][cap] = dp[i - 1][cap]$ | + | * soluția generată de acest caz: $dp[i][cap] = dp[i - 1][cap]$ |
* **folosesc obiectul i** | * **folosesc obiectul i** | ||
- | * daca il folosesc, inseamna ca pentru el trebuie sa am rezervata in rucsac o capacitate egala cu $w_i$ | + | * dacă îl folosesc, înseamnă că pentru el trebuie să am rezervată în rucsac o capacitate egală cu $w_i$ |
- | * adica cand am selectat dintre primele $i-1$ elemente, nu trebuia sa ocup mai mult de $cap - w_i$ din capacitatea rucsacului | + | * adică când am selectat dintre primele $i-1$ elemente, nu trebuia să ocup mai mult de $cap - w_i$ din capacitatea rucsacului |
- | * fata de subproblema mentionata, castig in plus $p_i$ (profitul pe care il aduce acest obiect | + | * față de subproblema menționată, câștig în plus $p_i$ (profitul pe care îl aduce acest obiect |
- | * solutia generata de acest caz: $dp[i][cap] = dp[i - 1][cap - w_i] + p_i$ | + | * soluția generată de acest caz: $dp[i][cap] = dp[i - 1][cap - w_i] + p_i$ |
| | ||
- | Reunind cele spuse mai sus, obtinem: | + | Reunind cele spuse mai sus, obținem: |
- | * $dp[0][cap] = 0$, pentru $cap = 0 : G$ | + | * $dp[0][cap] = 0$, pentru $cap = 0 : W$ |
- | * $dp[i][cap] = max(dp[i - 1], cap], dp[i - 1][cap - w_i] + p_i)$ | + | * $dp[i][cap] = max(dp[i - 1][cap], dp[i - 1][cap - w_i] + p_i)$ |
* pentru $i = 1: n$, $cap = 0:W$ | * pentru $i = 1: n$, $cap = 0:W$ | ||
- | == Implementare recurenta == | + | == Implementare recurență == |
<spoiler Exemplu implementare> | <spoiler Exemplu implementare> | ||
- | Mai jos se afla un exemplu simplu de implementare a recurentei gasite in C++. | + | Problema se poate testa pe infoarena:[[https://www.infoarena.ro/problema/rucsac | Problema rucsacului]]. |
+ | |||
+ | Mai jos se află un exemplu simplu de implementare a recurenței găsite in C++. | ||
<code cpp> | <code cpp> | ||
- | // n = numarul de obiecte din colectie | + | // n = numărul de obiecte din colecție |
- | // W = capacitatea maxima a rucsacului | + | // W = capacitatea maximă a rucsacului |
// (w[i], p[i]) = caracteristicile obiectului i ($i = 1 : n) | // (w[i], p[i]) = caracteristicile obiectului i ($i = 1 : n) | ||
int rucsac(int n, int W, vector<int> &w, vector<int> &p) { | int rucsac(int n, int W, vector<int> &w, vector<int> &p) { | ||
// dp este o matrice de dimensiune (n + 1) x (W + 1) | // dp este o matrice de dimensiune (n + 1) x (W + 1) | ||
- | // pentru ca folosim dp[0][*] pentru multimea vida | + | // pentru că folosim dp[0][*] pentru mulțimea vidă |
- | // dp[*][0] pentru situatia in care ghiozdanul are capacitate 0 | + | // dp[*][0] pentru situația în care ghiozdanul are capacitate 0 |
- | vector< vector<int> > dp(n + 1); | + | vector< vector<int> > dp(n + 1, vector<int>(W + 1, 0)); |
- | for (int i = 0; i <= n; ++i) { | + | |
- | dp[i].resize(W + 1); | + | |
- | } | + | |
- | // cazul de baza | + | // cazul de bază |
for (int cap = 0; cap <= W; ++cap) { | for (int cap = 0; cap <= W; ++cap) { | ||
dp[0][cap] = 0; | dp[0][cap] = 0; | ||
Line 487: | Line 506: | ||
for (int i = 1; i <= n; ++i) { | for (int i = 1; i <= n; ++i) { | ||
for (int cap = 0; cap <= W; ++cap) { | for (int cap = 0; cap <= W; ++cap) { | ||
- | // nu folosesc obiectu i => e solutia de la pasul i - 1 | + | // nu folosesc obiectul i => e soluția de la pasul i - 1 |
dp[i][cap] = dp[i-1][cap]; | dp[i][cap] = dp[i-1][cap]; | ||
- | // folosesc obiectul i, deci trebuie sa rezerv w[i] unitati in rucsac | + | // folosesc obiectul i, deci trebuie să rezerv w[i] unități în rucsac |
- | // inseamna ca inainte trebuie sa ocup maxim cap - w[i] unitati | + | // înseamnă ca înainte trebuie să ocup maxim cap - w[i] unități |
if (cap - w[i] >= 0) { | if (cap - w[i] >= 0) { | ||
int sol_aux = dp[i-1][cap - w[i]] + p[i]; | int sol_aux = dp[i-1][cap - w[i]] + p[i]; | ||
Line 506: | Line 525: | ||
</spoiler> | </spoiler> | ||
- | === Mentiuni === | + | === Mențiuni === |
- | Intrucat aceasta solutie presupune calculul iterativ (linie cu linie) a matricei dp, complexitatea este polinomiala. | + | Întrucât această soluție presupune calculul iterativ (linie cu linie) a matricei dp, complexitatea este polinomială. |
- | * **complexitate temporala **: $T = O(n * W)$ | + | * **complexitate temporală **: $T = O(n * W)$ |
- | * **complexitate spatiala ** : $S = O(n * W)$ | + | * **complexitate spațială ** : $S = O(n * W)$ |
- | * daca nu ne intereseaza sa reconstituim solutia (sa afisam submultimea efectiv), atunci putem sa NU stocam toata matricea dp | + | * dacă nu ne interesează să reconstituim soluția (să afișăm submulțimea efectiv), atunci putem să NU stocăm toată matricea dp |
- | * ca sa calculam o linie, avem nevoie doar de ultima linie | + | * ca să calculăm o linie, avem nevoie doar de ultima linie |
- | * putem sa stocam la orice moment de timp doar ultima linie si linia curenta | + | * putem să stocăm la orice moment de timp doar ultima linie și linia curentă |
- | * complexitatea spatiala se reduce astfel la $S = O(W)$ | + | * complexitatea spațială se reduce astfel la $S = O(W)$ |
- | + | ||
===== Exercitii ===== | ===== Exercitii ===== | ||
<note> | <note> | ||
- | In laboratorul de astazi, implementarea exercitiilor nu va fi punctata. Cu toate acestea, daca doriti sa implementati problemele propuse spre rezolvare, puteti folosi scheletul de laborator din arhiva {{pa:new_pa:skel-lab03.zip}}. | + | Scheletul de laborator se găsește pe pagina [[https://github.com/acs-pa/pa-lab/tree/main/skel/lab03|pa-lab::skel/lab03]]. |
</note> | </note> | ||
Line 585: | Line 605: | ||
</spoiler> | </spoiler> | ||
+ | <hidden> | ||
<spoiler Solutie> | <spoiler Solutie> | ||
Problema preluata de [[https://leetcode.com/problems/coin-change/description/|aici]]. Solutia este [[https://leetcode.com/problems/coin-change/solution/ | aici]]. | Problema preluata de [[https://leetcode.com/problems/coin-change/description/|aici]]. Solutia este [[https://leetcode.com/problems/coin-change/solution/ | aici]]. | ||
</spoiler> | </spoiler> | ||
+ | </hidden> | ||
=== 2. CMLSC === | === 2. CMLSC === | ||
Line 677: | Line 699: | ||
+ | <hidden> | ||
<spoiler Solutie> | <spoiler Solutie> | ||
Problema preluata de [[https://infoarena.ro/problema/cmlsc|aici]]. | Problema preluata de [[https://infoarena.ro/problema/cmlsc|aici]]. | ||
Line 688: | Line 710: | ||
Complexitate: $O(n * m)$. | Complexitate: $O(n * m)$. | ||
</spoiler> | </spoiler> | ||
+ | </hidden> | ||
=== BONUS === | === BONUS === | ||
<spoiler Custi> | <spoiler Custi> | ||
Line 735: | Line 757: | ||
===== Referințe ===== | ===== Referințe ===== | ||
- | [0] Capitolul **Dynamic Programming** din **Introductions to Algorithms** de către T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein | + | [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]] | [1] [[http://infoarena.ro/problema/ssm]] | ||
Line 742: | Line 764: | ||
[3] [[http://infoarena.ro/problema/rucsac]] | [3] [[http://infoarena.ro/problema/rucsac]] | ||
- | |||
- | [4] [[https://www.geeksforgeeks.org/dynamic-programming/|Colecție de probleme de programare dinamică (geeksforgeeks.com)]] | ||
- | |||
- | [5] [[https://practice.geeksforgeeks.org/explore/?category%5B%5D=Dynamic%20Programming&page=1&sortBy=accuracy|Probleme de interviu care se rezolvă cu PD]] | ||
- | |||
- | [6] Sniedovich, Moshe. Dynamic programming: foundations and principles. CRC press, 2010. | ||
- | |||