Differences

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

Link to this comparison view

pa:laboratoare:work-lab-03 [2018/02/19 12:42]
darius.neatu [RUCSAC]
pa:laboratoare:work-lab-03 [2018/02/21 01:42] (current)
darius.neatu
Line 1: Line 1:
-====== Laborator 3: Programare Dinamică ====== +am mutat [https://ocw.cs.pub.ro/courses/pa/laboratoare/laborator-03](aici)
-Responsabili:​ Darius Neațu, Răzvan Chițu +
- +
-===== Obiective laborator ===== +
-  * Ințelegerea noțiunilor de bază despre programare dinamică +
-  * Insușirea abilităților de implementare a algoritmilor bazați programare dinamică. +
- +
-===== Precizari initiale ===== +
-<​note>​ +
-Toate exemplele de cod se gasesc in {{pa:​demo-lab03.zip}}. +
- +
-Acestea apar incorporate si in textul laboratorului pentru a facilita parcurgerea cursiva a laboratorului. +
-</​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. +
-  * Va rugam sa incercati si codul din arhiva ** demo-lab03.zip**,​ inainte de a raporta ca ceva nu merge. :D +
-  * Pentru orice problema legata de continutul acestei pagini, va rugam sa dati email la [[neatudarius@gmail.com|neatudarius@gmail.com]]. +
-===== Ce este DP? ===== +
-Similar cu greedy, tehnica de programare dinamica este folosită pentru rezolvarea **problemelor de optimizare**.  +
-In continuare vom folosi acronimul **DP (dynamic programming)**. +
- +
-===== 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). +
- +
-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**. +
- +
-<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 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**. +
- +
-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. Motivul pentru care aceasta tehnica se numește Programare Dinamică este datorată flexibilității ei, ”valoarea” schimbându-și î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. +
- +
-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. +
- +
-</​spoiler>​ +
-===== 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. +
- +
-===== 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. +
- +
-===== 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**. +
- +
-Propunem cateva categorii de recurente, pe care le vom grupa astfel: +
-  *  recurente de tip **SSM** +
-  *  recurente de tip **RUCSAC** +
-  *  recurente de tip **PODM** +
-  *  recurente de tip **numărat** +
-  *  recurente pe **grafuri** +
- +
- +
-===== Categoria 1: SSM ===== +
-Aceste recurente au o oarecare asemanare cu problema SSM (enunt + solutie). +
- +
-==== SSM ==== +
-=== Enunt === +
-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. +
- +
-=== Cerinta === +
-Sa se determine subsecventa de suma maxima (notata **SSM**). +
- +
-=== Exemple === +
-<spoiler Exemplu 1> +
-n = 6 +
- +
-|i   | 1 | 2 | 3 | 4 | 5| 6 | +
-|v[i]| -10 | 2 | 3 | -1| 2 | -3 | +
- +
-Raspuns: SSM este intre 2 si 5 (pozitii). 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.  +
-</​spoiler>​ +
- +
-<spoiler Exemplu 2> +
-n = 4 +
- +
-|i   | 1 | 2 | 3 | 4 | +
-|v[i]| 10 | 20 | 30 | 40| +
- +
-RaspunsSSM este intre 1 si 4 (pozitii). Are suma 100. ($SSM = 10, 20, 30, 40$) +
- +
-Explicatie: deoarece toate numerele sunt **pozitive**,​ SSM cuprinde toate numerele. +
-</spoiler>​ +
- +
-<spoiler Exemplu 3> +
-n = 4 +
- +
-|i   | 1 | 2 | 3 | 4 | +
-|v[i]| -10 | -20 | -30 | -40| +
- +
-Raspuns: SSM este intre 1 si 1 (pozitii). Are suma -10. ($SSM = -10$) +
- +
-Explicatie: deoarece toate numerele sunt **negative**,​ SSM cuprinde doar cel mai mare numar. +
-</spoiler>​ +
- +
-=== Rezolvare === +
-== 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]$)**. +
- +
-== NUMIRE RECURENTA == +
-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: +
- +
-$ dp[i] $ = suma subsecventei de suma maxima (**suma SSM**) folosind ** DOAR ** primele i elemente din vectorul v si care se termina pe pozitia i +
- +
-= Mentiuni = +
-  * Pentru a mentine o conventie, 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 +
-    * $dp[i]$ reprezinta **solutia** pentru problema $v[1], ​..., v[i]$ si care se termina cu $v[i]$ +
-  * Solutia pentru problema initiala este maximul din vectorul $dp[i]$. +
- +
-== GASIRE RECURENTA == +
-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]$. +
- +
-  * **CAZUL DE BAZA** +
-    * In general in probleme putem avea mai multe cazuri de baza, care in principiu se leaga 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] $. +
-    * Explicatie: daca avem un singur element, atunci acesta formeaza singura subsecventa posibila, deci $ SSM = v[1] $ +
-   +
-  * ** CAZUL GENERAL ** +
-    * presupune inductiv ca 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) +
-    * 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]$ +
-    * decidem in functie de $ dp[i - 1]$ si $v[i] $ +
-      * ** daca ** $ dp[i - 1] >= 0 $ (cea mai buna solutie care se termina 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]$ +
-        * Explicatie: $dp[i-1] + v[i] >= v[i]$ (inca are rost sa extind) +
-      * ** daca ** $ dp[i - 1] < 0 $ (cea mai buna solutie care se termina pe i - 1 are cost negativ) +
-        * vom incepe o noua secventa cu $v[i]$, adica $dp[i] = v[i]$ +
-        * Explicatie: $v[i] > dp[i-1] + v[i]$, deci prin extindere nu obtin solutie maxima! +
- +
-== IMPLEMENTARE RECURENTA == +
-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 pornint e o cale gresita. +
- +
-<spoiler Exemplu implementare>​ +
- +
-Mai jos se afla un exemplu simplu de implementare a recurentei gasite in C++. +
- +
-<code cpp> +
- +
-// gaseste SSM pentru vectorul v cu n elemente +
-// pentru a mentine conventia din explicatii:​ +
-//      ​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 incepe de la 0) +
-                                  // am nevoie de dp[1], ..., dp[n] +
- +
- // caz de baza +
- dp[1] = v[1]; +
- +
- // 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 { +
- // incep o noua secventa +
- dp[i] = v[i]; +
-+
-+
- +
- // solutia e maximul din vectorul dp +
- int sol = dp[1]; +
- for (int i = 2; i <= n; ++i) { +
- if (dp[i] > sol) { +
- sol = dp[i]; +
-+
-+
- +
-        return sol; // aceasta este suma asociata cu SSM +
-+
- +
-</​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**. +
-Hint: definiti ** start[i] ** = pozitia pe care a inceput subsecventa care da solutia cu cost dp[i]. +
- +
-</​spoiler>​ +
- +
-=== Mentiuni === +
-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.  +
-  * **complexitate temporala **: $T = O(n)$ +
-  * **complexitate spatiala ** : $S = O(n)$ +
-         * desigur ca pentru problema SSM, nu era nevoie sa retinem, tablourile dp/start in 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). +
-         * in acest caz complexitatea spatiala devine $S = O(1)$  +
- +
-** 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 ==== +
-=== Enunt === +
-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$). +
- +
-=== Cerinta === +
-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). +
- +
-=== Exemple === +
-<spoiler Exemplu 1> +
-n = 6 +
- +
-|i   | 1 | 2 | 3 | 4 | 5| 6 | +
-|v[i]| 100 | 12 | 13 | -1| 15 | -30 | +
- +
-Raspuns: $SCMAX = 12, 13, 15$ ($SCMAX = v[2], v[3], v[5])$. +
- +
-Explicatie:  +
-Toate subsirurile ordonate strict crescator sunt: +
-  * $100$ +
-  * $12$ +
-  * $12, 13$ +
-  * $12, 13, 15$ +
-  * $12, 15$ +
-  * $13$ +
-  * $13, 15$ +
-  * $-1$ +
-  * $-1, 15$ +
-  * $15$ +
-  * $-30$ +
-Cel mentionat este singurul de lungime 3. +
-</​spoiler>​ +
- +
-<spoiler Exemplu 2> +
-n = 6 +
- +
-|i   | 1 | 2 | 3 | 4 | 5| 6 | +
-|v[i]| 100 | 12 | 13 | -1| 15 | 14 | +
- +
-Raspuns: +
-  * $SCMAX = 12, 13, 15$ ($SCMAX = v[2], v[3], v[5])$. +
-  * $SCMAX = 12, 13, 14$ ($SCMAX = v[2], v[3], v[6])$. +
- +
-Explicatie:  +
-Toate subsirurile ordonate strict crescator 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 solutii indicate au ambele lungime maxima. +
- +
-</​spoiler>​ +
- +
-=== Rezolvare === +
-== 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]$)**. +
- +
-    * 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]$) +
-    * Altfel.. Unde am putea sa il 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]$ +
- +
-== NUMIRE RECURENTA == +
-$ 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 +
- +
-= Mentiuni = +
-  * Ca sa rezolvam problema data, trebuie sa rezolvam o multime de subprobleme +
-    * $dp[i]$ reprezinta **solutia** pentru problema $v[1], ..., v[i]$ si care se termina cu $v[i]$ +
-  * Solutia pentru problema initiala este maximul din vectorul $dp[i]$. +
- +
-== GASIRE RECURENTA == +
-  * **CAZUL DE BAZA** +
-    * Si in problema SCMAX, cazul pentru $i = 1$ este caz de baza.  +
-          * daca avem un singur element, atunci avem o singura subsecventa de lungime 1, ea este solutia +
-          * $dp[1] = 1$   +
-   +
-  * ** CAZUL GENERAL ** +
-    * presupune inductiv ca 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$) +
-    * 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 ($j = 1 : n - 1$) +
-        * **daca** $v[j] < v[i] $, atunci subsirul crescator care se termina pe pozitia j, poate fi extinds la dreapta cu elementul v[i], generand 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) +
-        * 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)  +
-  +
-  Reunind cele spuse mai sus: +
-  * $dp[1] = 1$ +
-  * $dp[i] = 1 + max(dp[j])$,​ unde $j = 1 : i-1$ **și** $v[j] < v[i]$ +
- +
-== IMPLEMENTARE RECURENTA == +
-<spoiler Exemplu implementare>​ +
- +
-Mai jos se afla un exemplu simplu de implementare a recurentei gasite in C++. +
- +
-<code cpp> +
-// n   = numarul de elemente din vector +
-// v   = vectorul dat (v[1], v[2], ..., v[n] - indexare de la 1 ca in explicatii) +
- +
-void scmax(int n, vector<​int>​ &v) { +
- vector<​int>​ dp(n + 1);   // in explicatii indexarea incepe de la 1 +
- +
- // caz de baza +
- dp[1] = 1;   // [ v[1] ] este singurul subsir (crescator) care se termina pe 1 +
- +
- // caz general +
- for (int i = 2; i <= n; ++i) { +
- dp[i] = 1;   // [ v[i] ] - este un subsir (crescator) care se termina pe i +
-  +
- // incerc sa il pun pe v[i] la finalul tuturor solutiilor disponibile +
- // o solutie se termina cu un element v[j] +
- for (int j = 1; j < i; ++j) { +
- // solutia triviala: v[i] +
- if (v[j] < v[i]) { +
- // din (..., v[j]) pot obtine (..., v[j], v[i]) +
- // (caz in care prec[i] = j) +
- +
- // voi alege j-ul curent, cand alegerea imi gaseste o solutie mai buna decat ce am deja +
- if (dp[j] + 1 > dp[i]) { +
- dp[i] = dp[j] + 1; +
-+
-+
-+
-+
- +
- // solutia e maximul din vectorul dp +
- int sol = dp[1], pos = 1; +
- for (int i = 2; i <= n; ++i) { +
- if (dp[i] > sol) { +
- sol = dp[i]; +
- pos = i; +
-+
-+
- +
- return sol; +
-+
-</​code>​ +
-</​spoiler>​ +
- +
-<spoiler Exemplu implementare cu reconstituire>​ +
- +
-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]$. +
-     * daca nu exista un astfel de j, atunci $prec[i] = 0$ (prin conventie) +
-</​spoiler>​ +
-=== Mentiuni === +
-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). +
-  * **complexitate temporala **: $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.  +
-  * **complexitate spatiala ** : $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) +
- +
-===== Categoria 2: RUCSAC ===== +
-Aceste recurente au o oarecare asemanare cu problema RUCSASC - varianta discreta (enunt + solutie). +
- +
-==== RUCSAC ==== +
- +
-=== Enunt === +
-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:​ +
-  * $w_i$ = $weight_i$ = greutatea obiectului cu numarul i +
-  * $p_i$ = $price_i$ = pretul obiectului cu numarul i +
-      * $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). +
- +
-Se gandeste ca nu prea merge treaba cu ACS, asa ca se apuca de furat. El vrea sa gaseasca ** o submultime ​ de obiecte** pe care sa le fure (sa le bage in rucsac), astfel incat **suma profiturilor sa fie maxima**. +
- +
-Daca Gigel fura obiectul i, caracterizat de ($w_i, p_i$), atunci profitul adus de obiect este $p_i$ (presupunem ca il vinde cu cat valoreaza obiectul). +
- +
-=== Cerinta === +
-Sa se determine **profitul maxim** pentru Gigel. +
- +
-=== Exemple === +
-<spoiler Exemplu 1> +
-$n = 5$ si $W = 10$ +
- +
-| |1|2|3|4|5| +
-|w|3|3|1|1|2| +
-|p|6|3|2|8|5| +
- +
-Raspuns: **24** (profitul maxim) +
- +
-Explicatie: va alege toate obiectele :D. +
-</​spoiler>​ +
- +
-<spoiler Exemplu 2> +
-$n = 5$ si $W = 3$ +
- +
-| |1|2|3|4|5| +
-|w|3|3|1|1|2| +
-|p|6|3|2|8|5| +
- +
-Raspuns: **13** (profitul maxim) +
- +
-Explicatie: va alege obiectele cu indicii 4 si 5 (profit: 8 + 5) +
-</​spoiler>​ +
- +
-=== Rezolvare === +
-== TIPAR == +
-Cum am transpune tiparul de la SSM/SCMAX in problema RUCSAC? +
-  * stim care este profitul maxim pe care il obtine daca folosim +
-    * doar primul element +
-    * doar primele $2$ elemente +
-    * ... +
-    * doar primele $i-1$ elemente +
-  * ajung sa ma gandesc 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 +
-    * daca folosesc elementul i caracterizat de ($w_i, p_i$), in primul rand acesta trebuie sa incapa in ghiozdan... +
-        * 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). +
-        *   +
-== NUMIRE RECURENTA == +
-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: +
- +
-$ dp[i][cap] $ = profitul maxim (**profit RUCSAC**) obtinut folosind (doar o parte) din primele i obiecte ​ si avand un rucsac de **capacitate maxima cap** +
- +
-Observatii:​ +
-  * NU exista restrictie daca in solutia mentionata de $dp[i][cap]$ este folosit OBLIGATORIU elementul i +
-  * 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). +
- +
-== GASIRE RECURENTA == +
-  * **CAZUL DE BAZA** +
-  * Daca avem o submultime vida de obiecte selectate (furate). +
-    * $ dp[0][cap] = 0 $ +
-    * Explicatie: Daca nu furam obiecte, atunci profitul este 0 indiferent de capacitate. +
- +
-  * ** CAZUL GENERAL ** +
-    * $ dp[i][cap] = ? $ +
-    * presupune inductiv ca avem rezolvate toate subproblemele mai mici +
-      * subprobleme mai mici inseamna sa foloseasca mai putine obiecte sau un rucsac cu capacitatea mai mica +
-      * vedem daca prin folosirea obiectului i, obtinem cea mai buna solutie in $dp[i][cap]$ +
-        * **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 +
-          * solutia generata de acest caz: $dp[i][cap] = dp[i - 1][cap]$ +
-        * **folosesc obiectul i** +
-          * daca il folosesc, inseamna ca pentru el trebuie sa am rezervata in rucsac o capacitate egala 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 +
-            * fata de subproblema mentionata, castig in plus $p_i$ (profitul pe care il aduce acest obiect +
-          * solutia generata de acest caz: $dp[i][cap] = dp[i - 1][cap - w_i] + p_i$  +
-   +
-   ​Reunind cele spuse mai sus, obtinem: +
-   * $dp[0][cap] = 0$, pentru $cap = 0 : G$ +
-   * $dp[i][cap] = max(dp[i - 1], cap], dp[i - 1][cap - w_i] + p_i)$, pentru $i = 1: n$, $cap = 0:W$  +
- +
-== IMPLEMENTARE RECURENTA == +
-<spoiler Exemplu implementare>​ +
-Mai jos se afla un exemplu simplu de implementare a recurentei gasite in C++. +
- +
-<code cpp> +
- +
-// n   = numarul de obiecte din colectie +
-// W   = capacitatea maxima 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 ca folosim dp[0][*] pentru multimea vida +
-    //                   ​dp[*][0] pentru situatia in care ghiozdanul are capacitate 0 +
-    vector< vector<​int>​ > dp(n + 1); +
-    for (int i = 0; i <= n; ++i) { +
-        dp[i].resize(W + 1); +
-    } +
- +
-    // cazul de baza +
-    for (int cap = 0; cap <= W; ++cap) { +
-        dp[0][cap] = 0; +
-    } +
- +
-    // cazul general +
-    for (int i = 1; i <= n; ++i) { +
-        for (int cap = 0; cap <= W; ++cap) { +
-            // nu folosesc obiectu i => e solutia de la pasul i - 1 +
-            dp[i][cap] = dp[i-1][cap];​ +
- +
-            // folosesc obiectul i, deci trebuie sa rezerv w[i] unitati in rucsac +
-            // inseamna ca inainte trebuie sa ocup maxim cap - w[i] unitati +
-            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>​ +
- +
-=== Mentiuni === +
-Intrucat aceasta solutie presupune calculul iterativ (linie cu linie) a matricei dp, complexitatea este polinomiala. ​  +
-  * **complexitate temporala **: $T = O(n * W)$ +
-  * **complexitate spatiala ** : $S = O(n * W)$ +
-        * daca nu ne intereseaza sa reconstituim solutia (sa afisam submultimea efectiv), atunci putem sa NU stocam toata matricea dp +
-        * ca sa calculam o linie, avem nevoie doar de ultima linie +
-        * putem sa stocam la orice moment de timp doar ultima linie si linia curenta +
-        * complexitatea spatiala se reduce astfel la $S = O(W)$  +
-       +
-===== Exercitii ===== +
-=== Coin change === +
-=== CMLSC === +
-=== BONUS - TODO === +
- +
-===== Referințe ===== +
- +
-[0] Capitolul **Dynamic Programming** din **Introductions to Algorithms** de către T. H. Cormen, C. E. Leiserson, R. L. Rivest, C. Stein +
- +
-[1] [[http://​infoarena.ro/​problema/​ssm]] +
- +
-[2] [[http://​infoarena.ro/​problema/​scmax]] +
- +
-[3] [[http://​infoarena.ro/​problema/​rucsac]] +
- +
- +
- +
pa/laboratoare/work-lab-03.txt · Last modified: 2018/02/21 01:42 by darius.neatu
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