Differences

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

Link to this comparison view

pa:laboratoare:work-lab-04 [2018/02/19 12:44]
darius.neatu [Exponentiere pe matrice pentru recurente liniare]
pa:laboratoare:work-lab-04 [2018/02/21 01:43] (current)
darius.neatu
Line 1: Line 1:
-====== Laborator 4: Programare Dinamică ====== +am mutat [https://ocw.cs.pub.ro/courses/​pa/​laboratoare/​laborator-04](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-lab04.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-lab04.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)**. +
- +
-Pentru restul notiunilor prezentate pana acum despre DP, va rugam sa consulati pagina laboratorului 3. +
-===== 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 3: PODM ===== +
-Aceste recurente au o oarecare asemanare cu problema PODM (enunt + solutie). +
- +
- +
-ATENTIE! Intrucat acest tip de recurente poate fi mai greu (decat celelalte), **doar il vom mentiona**. Puteti consulta ** acasa **materialele puse la dispozitie pentru intelege si aceasta categorie. +
- +
-Caracteristici:​ +
-  * Acest tip de problema presupune ca o putem formula ca pe o problema de tip ** subinterval $[i, j]$**.  +
-  * Daca dorim sa gasim optimul pentru acest interval, va trebuie sa luam in calcul toate combinatiile de 2 subproblemele care ar fi putut genera solutie pentru probleme **$[i, j]$**. +
-  * Se considera fiecare divizare in 2 subprobleme,​ data de intermediarul k +
-    * **$[i, k]$** si **$[k + 1, j]$ ** sunt cele 2 subprobleme pentru care cunoastem solutiile +
-    * atunci o solutie pentru **$[i,j]$** se poate obtine imbinandu-le pe cele doua +
-    * ca sa gasim solutia cea mai buna +
-      * vom itera prin toate valorile k posibile +
-      * vom alege pe cea care maximizeaza solutia problemei **$[i,​j]$** +
-  * Calculul se face de la intervale mici (probleme usoare - **$[i,i]$** sau **$[i, i+1]$**) spre probleme generale (dimensiune generala - ** $[i, j]$ **). In final se ajunge si la dimensiunile initiale (**$[1, n]$**). +
- +
-<spoiler PODM> +
-  * Enunt +
-Fie un produs matricial $M = M_1 M_2 ... M_n$. Putem pune paranteze in mai multe moduri si vom obtine acelasi rezultat (inmultire asociativa),​ dar este posibil sa obtinem numar diferit de ** inmultiri scalare **. +
- +
-Matricea $M_i$ are (prin conventie), dimensiunile $d_{i-1} d_{i}$. +
- +
- +
-  * Cerinta  +
-Se cere sa se gaseasca o ** parantezare optima de matrice ** (PODM), adica sa se gaseasca o parantezare care sa minimizeze numarul de inmultiri scalare. +
- +
-  * Recurenta +
-Se construieste o dinamica de tipul: +
-    * $dp[i][j]$ = numarul minim de inmultiri scalare cu care se poate obtine produsul $M_i * M_{i+1} * ... *{M_j}$  +
-      * $dp[i][i] = 0 $ +
-        * NU avem nici un efort daca nu avem ce inmulti +
-      * $dp[i][i+1] = d_{i-1} d_{i} d_{i+1}$ +
-        * daca inmultim 2 matrice de dimensiuni $d_{i-1} * d_{i}$ si $d_{i} * d_{i + 1}$, avem costul $d_{i-1} d_{i} d_{i-1}$ +
-      * $dp[i][j] = min(dp[i][k] + dp[k+1][j] + d_{i-1} d_{k} d_{j})$, unde $k = i : j - 1$ +
-        * daca avem de efectuat sirul de inmultiri $M_i ... M_j$, atunci putem pune paranteze oriunde si sa facem inmultirile astfel $(M_i ... M_k) (M_{k+1} ... M_{j})$ +
-          * costul minim pentru $(M_i ... M_k)$ este $dp[i][k]$ +
-          * costul minim pentru $(M_{k+1} ... M_j)$ este $dp[k + 1][j]$ +
-          * vom avea in final de inmultit 2 matrice de dimensiune $d_{i-1} * d_{k}$ si $d_{k} * d_{j}$, care are costul $d_{i-1}d_{k}d_{j}$ +
-          * insumam cele 3 costuri intermediare +
- +
-  * Complexitate +
-Intrucat solutia presupune fixarea capetelor unui subinterval (i, j), apoi alegerea unui intermediar (k), complexitatea este data de aceste 3 cicluri. +
-    * **complexitate temporala**:​ $T = O(n^3)$ +
-    * **complexitate spatiala**: $M = O(n^3)$  +
- +
-Puteti rezolva si testa problema PODM pe infoarena. +
-</spoiler>​ +
- +
-<spoiler Expresie booleana>​ +
-  * Enunt +
-Se da o expresie booleana exprimata prin stringurile **true**, **false**, **and**, **or**, **xor**. +
- +
-  * Cerinta +
-Numarati modurile in care se pot aseza paranteze astfel incat rezultatul sa fie **true**. Se respecta regulile de la logica (tabelele de adevar pentru operatiile AND, OR, XOR). +
- +
-  * Exemplu +
-    Fie expresia: ** true and false xor true**. Exista doua modalitati de parantezare asftel incat rezultatul sa fie **true**. +
- +
-  * Hint +
-    Complexitate temporală dorita este $O(n ^ 3)$. Gasiti vreo asemanare cu problema PODM? +
-</spoiler>​ +
- +
- +
-===== Categoria 4: NUMARAT ===== +
-Aceste recurente au o oarecare asemanare:​ +
-  * toate numara lucruri! :p +
-  * interesante sunt cazurile cand numarul cautat este foarte mare (altfel am putea apela la alte metode - exgenerarea tuturor candidatilor posibili cu backtracking) +
-      * in acest caz, deoarece numarul poate sa nu incapa pe un tip reprezentabil standard (exint pe 32/64 de biti), se cere (de obicei) restul impartirii numarului cautat la un numar **MOD** (vom folosi in continuare aceasta notatie). +
- +
- +
-==== Regulile de lucru cu clase de resturi ==== +
-Reamintim cateva proprietati matematice pe care ar trebui sa le aveti in vedere atunci in implementati pentru obtine corect resturile pentru anumite expresii. (corect poate sa insemne, de exemplu, sa evitati overflow :D - lucru neintuitiv cateodata). +
-  * proprietati de de baza +
-    * $(a + b)   \ \% \   MOD = ((a   \ \% \   MOD) + (b   \ \% \   ​MOD)) ​  \ \% \   MOD $  +
-    * $(a \ * b)   \ \% \   MOD = ((a   \ \% \   MOD) \ * (b   \ \% \   ​MOD)) ​  \ \% \   MOD $  +
-    * $(a - b)   \ \% \   MOD = ((a   \ \% \   MOD) - (b   \ \% \   MOD) + MOD)   \ \% \   MOD $ (restul nu poate fi ceva negativ; in C++ **%** nu functioneaza pe numere negative)  +
-  * invers modular +
-    * $ \frac{a}{b} ​  \ \% \   MOD = ((a   \ \% \   MOD) * (b ^ {MOD-2} ​  \ \% \   ​MOD)) ​  \ \% \   ​MOD)$ +
-         ​* ​ **DACA** MOD este prim; **DACA** a si b nu sunt multipli ai lui MOD   +
-<spoiler Explicatii invers modular> ​    +
-    * ** definitie **: **b** este inversul modular a lui **a** in raport cu **MOD** daca $ a * b = 1 (modulo \ MOD)$ +
-    * ** utilizare **: $ \frac{a}{b} ​  \ \% \   MOD = ((a   \ \% \   MOD) * (invers(b) ​  \ \% \   ​MOD)) ​  \ \% \   MOD $ +
-    * ** calculare **: deoarece la PA aceasta discutie are sens doar in contextul posibilitatii implementarii unei recurente DP in care folosim resturile doar pentru a evita overflow/imposibilitatea de a retine rezultatul pe tipurile standard de tip int (adica nu ne intereseaza sa dam o metoda generala pentru invers modular), vom simplifica problema - **MOD este prim!!!** +
-      * ** Mica teorema a lui Fermat**: Daca p este un numar prim si a este un număr intreg care nu este multiplu al lui p, atunci $a^{p-1} = 1 (modulo \ p)$. +
-            * din definitia inversului modular, reiese ca ** a ** si **b** nu sunt multipli ai lui **MOD** +
-            * introducand notatiile noastre in teorema si prelucrand obtinem +
-              * $a ^ {MOD - 1} = 1 (modulo \ MOD) <=> a * (a ^{MOD-2}) = 1 (modulo \ MOD)$ +
-              * deci inversul modular al lui a (in aceste conditii specifice) este $b = a ^ {MOD -2 }$ +
- +
- +
-</spoiler>​ +
- +
-==== Gardurile lui Gigel  ==== +
-=== Enunt === +
-Gigel trece de la furat obiecte cu un rucsac la numarat garduri (fiecare are micile lui placeri :D). El doreste sa construiasca un gard folosind in mod repetat **un singur tip de piesa**. +
- +
-O piesa are dimensiunile ** 4 x 1 ** (o unitate = 1m). Din motive irelevante pentru aceasta problema, orice gard construit trebuie sa aiba **inaltime 4m** in orice punct. +
- +
-O piesa poate fi pusa in pozitie **orizontala** sau in pozitie ** verticala**.  +
- +
-=== Cerinta === +
-Gigel se intreaba **cate garduri de lungime n** si inaltime 4 exista? Deoarece celalalt prenume al lui este Bulănel, el intuieste ca acest numar este foarte mare, de aceea va cere ** restul impartirii** acestui numar la **1009**. +
- +
-<spoiler Exemplu 123> +
-{{pa:​laboratoare:​garduri_123.png}} +
- +
-$n = 1$ sau $n = 2$ sau $n = 3$ +
- +
- +
-Raspuns: ** 1 **  (un singur gard) +
- +
-Explicatie: Se poate forma un singur gard in fiecare caz, dupa cum este ilustrat si in figura **Garduri_123**.  +
-</spoiler>​ +
- +
- +
-<spoiler Exemplu 4> +
-{{pa:laboratoare:​garduri_4.png}} +
- +
-$n = 4$ +
- +
-Raspuns: ** 2 **   +
- +
-Explicatie: Se pot forma 2 garduri, in functie de cum asezam piesele, dupa cum este ilustrat si in figura **Garduri_4**.  +
-Observam ca de fiecare daca cand punem o piesa in pozitie orizontala, de fapt suntem obligati sa punem 4 piese, una peste alta! +
-</spoiler>​ +
- +
- +
-<spoiler Exemplu 5> +
-{{pa:​laboratoare:​garduri_5.png}} +
- +
-$n = 5$ +
- +
-Raspuns: ** 3 **   +
- +
-Explicatie: Se pot forma 3 garduri, in functie de cum asezam piesele, dupa cum este ilustrat si in figura **Garduri_5**. +
-  * daca dorim ca acest gard sa se termine cu 4 piese in pozitie **orizontala** (una peste alta marcat cu rosu), atunci la stanga mai ramane de completat **un subgard de lungime 1**, in toate modurile posibile +
-  * daca dorim ca acest gard sa se termine cu o piesa in pozitie **verticala** (marcat cu rosu), atunci la stanga mai ramane de completat **un subgard de lungime 4**, in toate modurile posibile +
-</​spoiler>​ +
- +
- +
-<spoiler Exemplu 6> +
-{{pa:​laboratoare:​garduri_6.png}} +
- +
-$n = 6$ +
- +
-Raspuns: ** 4 **   +
- +
-Explicatie: Se pot forma 4 garduri, in functie de cum asezam piesele, dupa cum este ilustrat si in figura **Garduri_6**. +
-  * daca dorim ca acest gard sa se termine cu o piesa in pozitie **verticala** (marcat cu rosu), atunci la stanga mai ramane de completat **un subgard de lungime 5**, in toate modurile posibile +
-  * daca dorim ca acest gard sa se termine cu 4 piese in pozitie **orizontala** (una peste alta - marcat cu rosu), atunci la stanga mai ramane de completat **un subgard de lungime 2**, in toate modurile posibile +
-</​spoiler>​ +
- +
-=== RECURENTA === +
-== NUMIRE RECURENTA == +
-$dp[i$ = numarul de garduri de lungime i si inaltime 4 (nimic special - exact ceeea ce se cere in enunt) +
- +
- +
-Raspunsul la problema este $dp[n]$. +
- +
-== GASIRE RECURENTA == +
-  * **CAZ DE BAZA ** +
-    * $dp[1] = dp[2] = dp[3] = 1$; $dp[4]$ = 2 +
-  * ** CAZ GENERAL ** +
-    * atunci dorim sa formam un gard de lungime i ($ i >= 5 $) am vazut ca putem alege cum sa punem ultima/​ultimele piese +
-      * **DACA** alegem ca ultima piesa sa fie pusa in pozitie verticala, atunci la stanga mai ramane de completat **un subgard de lungime $i-1$**  +
-        * numarul de moduri in care putem face acest subgard este $dp[i-1]$ +
-      * **DACA** alegem ca ultima piesa sa fie in pozitie orizontala (de fapt punem 4 piese in pozitie orizontala),​ atunci la stanga mai ramane de completat **un subgard de lungime $i-4$** +
-        * numarul de moduri in care putem face acest subgard este $dp[i-4]$ +
-    * $dp[i] = (dp[i-1] + dp[i-4]) \ \% \  MOD$   +
- +
-== IMPLEMENTARE RECURENTA == +
-Aici puteti vedea un exemplu simplu de implementare in C++.  +
- +
-<spoiler Implementare in C++> +
-<code cpp> +
- +
-#define MOD 1009 +
-int gardurile_lui_Gigel(int n) { +
-    // cazurile de baza +
-    if (n <= 3) return 1; +
-    if (n == 4) return 2; +
- +
-    vector<​int>​ dp(n + 1); // pastrez indexarea de la 1 ca in explicatii +
- +
-    // cazurile de baza +
-    dp[1] = dp[2] = dp[3] = 1; +
-    dp[4] = 2; +
- +
-    // cazul general +
-    for (int i = 5; i <= n; ++i) { +
-        dp[i] = (dp[i - 1] + dp[i - 4]) % MOD; +
-    } +
- +
-    return dp[n]; +
-+
- +
-</​code>​ +
- +
-Mentionez ca am folosit expresia ​ $dp[i] = (dp[i - 1] + dp[i - 4]) \ \%  \ MOD$ in loc de $dp[i] = ((dp[i - 1]  \ \%  \ MOD) + (dp[i - 4]  \ \%  \ MOD)) \ \%  \ MOD$, deoarece [e valorile anterior calcule in dp a fost deja aplicata operatia $%$. +
- +
-Am plecat cu numerele $1, 1, 1, 2$ si la fiecare pas rezultatul stocat este $\ \% \ MOD$, deci tot ce este stocat **deja** in dp este un rest in raport cu MOD. NU mai era nevoie deci sa aplica **%** si pe termenii din paranteza. +
-</​spoiler>​ +
- +
- +
-== Complexitate == +
-  * **complexitate temporala**:​ $T = O(n)$ +
-    * explicatie: avem o singura parcurgere in care construim tabloul dp +
-    * se poate obtine $T=O(log n)$ folosind exponentiere pe matrice! +
-  * **complexitate spatiala**: $S = O(n)$  +
-    * explicatie: stocam tabloul dp +
-    * se poate ontine $S = O(1)$ folosind exponentiere pe matrice!  +
- +
-===== Tehnici folosite in DP ===== +
-De multe ori este nevoie sa folosim cateva tehnici pentru a obtine performanta maxima cu recurenta gasita. +
- +
-In laboratorul 3 se mentiona tehnica de memoizare (in prima parte a laboratorului). In acesta ne vom rezuma la cum putem folosi cunostintele de lucru matricial pentru a favorizarea implementarea unor anumite tipuri de recurente. +
- +
-==== Exponentiere pe matrice pentru recurente liniare ==== +
-=== Recurente liniare=== +
-O recurenta liniara in contextul laboratorului de DP este de forma: +
-  * $dp[i] = \sum_{k=1}^{KMAX} c_k * dp[i - k]$ +
-    * pentru **KMAX o constanta** +
-    * de obicei, KMAX este foarte mica comparativ cu dimensinea n a problemei +
-    * $c_k$ constante reale (unele pot fi nule) +
- +
-O astfel de recurenta ar insemna ca pentru a calcula ** costul problemei i **, imbinam costurile problemelor $i - 1, i-2, ...., i-k$, fiecare contribuind cu un anumit coeficient $c_{1}, c_{2}, ..., c_{k}$. +
- +
-<spoiler Complexitate recurente liniara>  +
-Presupunand ca nu mai exista alte specificatii ale problemei si ca avand cele KMAX cazuri de baza (primele KMAX valori ar trebui stiute/​deduse prin alte reguli), atunci un algoritm poate implementa recurenta de mai sus folosind 2 cicluri de tip for (for i = 1 : n, for k = 1 : KMAX ...). +
-  * **complexitatea temporala** : $ T =O(n * KMAX) = O(n) $ +
-    * reamintim ca acea valoarea KMAX este o constant foarte mica in compartie cu n (ex. KMAX < 100) +
-  * **complexitatea spatiala** : $ S = O(n) $ +
-    * am presupus ca avem nevoie sa retinem doar tabloul dp   +
-</​spoiler>​ +
- +
-=== Exponentiere pe matrice === +
-Facem urmatoarele notatii: +
-  * $S_i$ = starea la pasul i +
-    * $S_i = (dp[i - k + 1], dp[i - k + 2], ..., dp[i - 1], dp[i])$ +
-  * $S_k$ = starea initiala (in care cunoaste cele k cazuri de baza) +
-    * $S_k = (dp[1], dp[2], ..., dp[k-1], dp[k])$ +
-  * $C$ = matrice ce coeficienti constanti +
-    * are dimensiune $KMAX * KMAX$  +
-    * putem pune constante in clar +
-    * putem pune constantele $c_k$ care tin de problema curenta +
- +
- +
- +
-== Algoritm naiv == +
-Putem formula problema astfel: +
-  * $S_k$ = este starea initiala +
-  * pentru a obtine starea urmatoare, aplicam algoritmul urmator +
-     * $S_i = S_{i-1}C$ ​  +
-   +
-== Determinare C == +
-Pentru a determina elementele matricei C, trebuie sa ne uitam la inmultirea matriceala de mai sus si sa alegem elementele lui C astfel incat prin inmultirealui $S_{i-1}$ cu $C$ sa obtinem elementele din $S_i$. +
- +
-\begin{gather} +
- ​\begin{bmatrix} dp[i - k + 1] &  ... & dp[i-1] &  dp[i] \\ \end{bmatrix} = +
- +
- +
-\begin{bmatrix} dp[i - k] &  ... & dp[i-2] &  dp[i-1] \\ \end{bmatrix}  +
-\begin{bmatrix} +
-   0 & 0 &... & 0 & 0 & c_{k}\\ +
-   1 & 0 &... & 0 & 0 & c_{k-1}\\ +
-   0 & 1 &... & 0 & 0 & c_{k-2}\\ +
-   ... & ... & ... & ... & ...\\ +
-   0 & 0 &... & 1 & 0 & c_{2}\\ +
-   0 & 0 &... & 0 & 1 & c_{1}\\ +
-   ​\end{bmatrix} +
-\end{gather} +
- +
- +
- +
-<spoiler Explicatie determinare C> +
-  * ultima coloana contine toti coeficientii $c_k$ intrucat $dp[i] = \sum_{k=1}^{KMAX} c_k * dp[i - k]$ +
-  * celelalte coloane contin doar cate o valoare nenula +
-    * pe coloana j  vom avea valoarea 1 pe linia $j+1$ ($j = 1 : KMAX - 1$) +
-      * cum obtinem, de exemplu, $dp[i - 1]$? +
-      * pai avem $dp[i-1]$ chiar si in starea $S_{i-1}$, deci trebuie sa il copiam in starea $S_i$  +
-        * copierea se realizeaza prin inmultirea cu 1 +
-        * daca $dp[i-1]$ era pe ultima pozitiei (pozitia k) in starea $S_{i-1}$, in noua stare $S_i$ este pe penultima pozitie (pozitia $k-1$) +
-          * deci s-a deplasat la stanga cu o pozitie!  +
-    * in noua stare, noua pozitie este deplasata cu o unitate la stanga fata de starea precedenta +
-      * de aceea pe coloana $j$, vrem sa avem elementul 1 pe linia $j + 1$ ($j = 1 : KMAX - 1$) +
-      * cand inmultim $S_{i-1}$ cu coloana $C_j$ **dorim sa** +
-        * ce copiam? +
-          * valoarea $dp[i - KMAX + j]$ din $S_{i-1}$ in $S_{i}$ +
-          * adica sa copiam a j-a valoare de pe linie  +
-        * unde copiam? +
-          * de pe pozitia $j + 1$ pe pozitia $j$   +
-</​spoiler>​ +
- +
-== Exponentiere logaritmica pe matrice == +
-Algoritmul naiv de mai sus are dezavantajul ca are tot o complexitate temporala $O(n)$. +
- +
-Sa executam cativa pasi de inductie pentru a vedea cum este determinat $S_i$. +
-$$S_i = S_{i-1}C$$ +
-$$S_i = S_{i-2}C^2$$ +
-$$S_i = S_{i-3}C^3$$ +
-$$...$$ +
-$$S_i = S_{k}C^{i -k}$$ +
- +
- +
-In laboratorul 2 (Divide et Impera) am invatat ca putem calcula $ x ^ n $ in timp logaritmic. Deoarece si inmultirea matricilor este asociativa, putem calcula $C ^ n$ in timp logaritmic. +
- +
-Obtinem astfel o solutie cu urmatoarele complexitati:​ +
-  * ** complexitate temporala **: $T = O(KMAX^3 * log(n))$ +
-    * explicatie  +
-      * facem doar $O(log n)$ pasi, dar un pas implica inmultire de matrice +
-      * o inmultire de matrice patratica de dimensiune KMAX are $KMAX^3$ operatii +
-    * aceasta metoda este eficienta cand $KMAX << n$ (KMAX este mult mai mic decat n) +
-  * ** complexitatea spatiala **: $M = O(KMAX^3)$  +
-    * explicatie  +
-      * este nevoie sa stocam cateva matrici +
-Observatie! In ultimele calcule nu am sters contanta KMAX, intrucat apare la puterea a 3-a! $KMAX = 100$ implica $KMAX^3 = 10^6$, valoare care nu mai poate fi ignorata in practica ($KMAX^3$ poate fi comparabil cu n). +
- +
-=== Gardurile lui Gigel (optimizare) === +
-Dupa cum am vazut mai sus, in problema cu garduri data de gigel solutia este o recurenta liniara: +
-  *  $dp[1] = dp[2] = dp[3] = 1$; $d[4]=2$; +
-  *  $dp[i] = dp[i - 1] + dp[i - 4]$, pentru $i > 4$ +
- +
-== Exponentiere rapida :p == +
-  * $ k = 4 $ +
-  * $S_4 = (dp[1], dp[2], dp[3], dp[4]) = (1, 1, 1, 4)$ +
-  * $S_i = (dp[i-3], dp[i-2], dp[i-1], dp[i])$ +
-  * Raspunsul se afla efectuand operatia $S_n = S_4 * C^{n - 4}$, unde C are urmatorul continut: ​  +
-\begin{gather}  +
-   C = \begin{bmatrix} +
-   0 & 0 & 0 & 1\\ +
-   1 & 0 & 0 & 0\\ +
-   0 & 1 & 0 & 0\\ +
-   0 & 0 & 1 & 1\\ +
-   ​\end{bmatrix} +
-\end{gather} +
- +
-<spoiler Implementare in C++> +
-Mai jos se afla o implementare simplista in C++ care cuprinde toate etapele pe care trebuie sa le realizati in cod, dupa ce stiti cum arata recurenta sub forma matriceala. +
-  +
-<code cpp> +
- +
-#define MOD  1009 +
-#define KMAX 4 +
- +
-// C = A * B +
-void multiply_matrix(int A[KMAX][KMAX],​ int B[KMAX][KMAX],​ int C[KMAX][KMAX]) { +
-    int tmp[KMAX][KMAX];​ +
- +
-    // tmp = A * B +
-    for (int i = 0; i < KMAX; ++i) { +
-        for (int j = 0; j < KMAX; ++j) { +
-            unsigned long long sum = 0; // presupun ca suma  incape pe 64 de biti +
- +
-            for (int k = 0; k < KMAX; ++k) { +
-                sum += 1LL * A[i][k] * B[k][j]; +
-            } +
- +
-            tmp[i][j] = sum % MOD; +
-        } +
-    } +
- +
-    //  C = tmp +
-    memcpy(C, tmp, sizeof(tmp));​ +
-+
- +
-// R = C^p +
-void power_matrix(int C[KMAX][KMAX],​ int p, int R[KMAX][KMAX]) { +
-    // tmp = I (matricea identitate) +
-    int tmp[KMAX][KMAX];​ +
-    for (int i = 0; i < KMAX; ++i) { +
-        for (int j = 0; j < KMAX; ++j) { +
-            tmp[i][j] = (i == j) ? 1 : 0; +
-        } +
-    } +
- +
-    while (p != 1) { +
-        if  (p % 2 == 0) { +
-            multiply_matrix(C,​ C, C);     // C = C*C +
-            p /= 2;                       // ramane de calculat C^(p/2) +
-        } else { +
-            // reduc la cazul anterior: +
-            multiply_matrix(tmp,​ C, tmp); // tmp = tmp*C +
-            --p;                          // ramane de calculat C^(p-1) +
-        } +
-    } +
- +
-    // avem o parte din rezultat in C si o parte in tmp +
-    multiply_matrix(C,​ tmp, R);           // rezultat = tmp * C +
-+
- +
-int garduri_rapide(int n) { +
-    // cazurile de baza +
-    if (n <= 3) return 1; +
-    if (n == 4) return 2; +
- +
-    // construiesc matricea C +
-    int C[KMAX][KMAX] = { {0, 0, 0, 1}, +
-                          {1, 0, 0, 0}, +
-                          {0, 1, 0, 0}, +
-                          {0, 0, 1, 1}}; +
-   // vreau sa aplic formula S_n = S_4 * C^(n-4) +
- +
-   // C = C^(n-4) +
-   ​power_matrix(C,​ n - 4, C); +
- +
-   // sol = S_4 * C = dp[n] (se afla pe ultima pozitie din S_n, +
-   // deci voi folosi ultima coloana din C) +
-   int sol = 1 * C[0][3] + 1 * C[1][3] + 1 * C[2][3] + 2 * C[3][3]; +
-   ​return sol % MOD; +
-+
- +
-</​code>​ +
-</​spoiler>​ +
- +
-<spoiler Comparatie solutii (studiu de caz pentru curiosi)>​ +
-In arhiva ** demo-lab04.zip ** gasiti o sursa completa in care se realizeaza:​ +
-  * o verificare a faptului ca cele 2 implementari (** gardurile_lui_Gigel** si **garduri_rapide**) produc aceleasi rezultate +
-  * un benchmark in care cele 2 implementari sunt comparate +
-    * pe sistem uzual (laptop) s-au obtinut urmatoarele rezulate: +
-<code bash> +
-test case: varianta simpla +
-n = 100000000 sol = 119; time = 0.984545 s +
-test case: varianta rapida +
-n = 100000000 sol = 119; time = 0.000021 s +
- +
- +
-test case: varianta simpla +
-n = 1000000000 sol = 812; time = 9.662377 s +
-test case: varianta rapida +
-n = 1000000000 sol = 812; time = 0.000022 s +
-</​code>​  +
-     * se observa clar diferenta intre cele 2 solutii (am confirmat ceea ce spunea si teoria: $O(n) $ vs $O(log(n))$);​ aceasta tehnica imbunatateste drastic o solutie gasita relativ usor. +
- +
-</​spoiler>​ +
-  +
-===== Exercitii ===== +
-=== Azerah === +
-=== Exponentiere === +
-=== 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/​podm]] +
- +
-[2] [[http://​infoarena.ro/​problema/​kfib]] +
- +
-[3] [[http://​infoarena.ro/​problema/​iepuri]] +
- +
pa/laboratoare/work-lab-04.txt · Last modified: 2018/02/21 01:43 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