<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://www.algopedia.ro/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Cristian</id>
	<title>Algopedia - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://www.algopedia.ro/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Cristian"/>
	<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php/Special:Contributions/Cristian"/>
	<updated>2026-05-17T08:26:13Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.44.2</generator>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Main_Page&amp;diff=18592</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Main_Page&amp;diff=18592"/>
		<updated>2026-02-17T09:33:01Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
&amp;lt;p align=right&amp;gt;&amp;lt;sup&amp;gt;&#039;&#039;No &#039;&#039;&amp;lt;code&amp;gt;break&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;continue&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;int v[1005]&amp;lt;/code&amp;gt;&#039;&#039;, no nonsense&#039;&#039;&amp;lt;/sup&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
= Algopedia =&lt;br /&gt;
&lt;br /&gt;
== [[Cercul de informatică gimnaziu]] ==&lt;br /&gt;
[[Cercul de informatică gimnaziu]] este o colecție de lecții de algoritmi și programare pentru clasele V-VIII. Lecțiile acoperă programa de olimpiadă, dar nu se opresc aici, scopul final fiind de a forma minți algoritmice și a crea informaticieni cu cunoștințe de bază complete. În acest sens evităm &amp;quot;dopajul&amp;quot; (predarea în avans a unor cunoștințe, fără a preda prerechizitele necesare, în scopul obținerii unor rezultate mai bune la olimpiadă). Lecțiile sînt împărțite astfel încît să acopere anul școlar în ritmul de o lecție pe săptămînă, circa 40-44 de lecții la fiecare clasă. Fiecare lecție durează două ore. Unele lecții sînt concursuri cu participare de acasă, ele dublînd lecția din acea săptămînă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== [[Cercul de informatică IQ Academy]] ==&lt;br /&gt;
[[Cercul de informatică IQ Academy]] este un curs oferit de [http://iqacademy.ro/ IQ Academy], o inițiativă a [http://francu.org/ Fundației Frâncu pentru progres și educație] ce își propune formarea unor minți algoritmice, cu putere de abstractizare, cu “gândire computațională”, termen apărut relativ recent și susținut de oameni precum Mark Zuckerberg, creatorul Facebook.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Cercul de informatică, colegiul Tudor Vianu, 2012-2020 ==&lt;br /&gt;
Găsiți aici detalii despre [[Cercul de informatică - Colegiul Național de Informatică Tudor Vianu]] desfășurat în perioada 2012-2020.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Data Structures and  Algorithms ==&lt;br /&gt;
A future translation to English. Work in progress.&lt;br /&gt;
* [[Introduction to Algorithms]]&lt;br /&gt;
* [[Introductory Exercises]]&lt;br /&gt;
* [[Problems Involving Sequences]]&lt;br /&gt;
* [[Problems Involving Arrays]]&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Contact ==&lt;br /&gt;
Puteți contacta creatorul acestui site, Cristian Frâncu, la [mailto:cristian@francu.com cristian@francu.com]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Main_Page&amp;diff=18591</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Main_Page&amp;diff=18591"/>
		<updated>2026-02-17T09:32:38Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
= Algopedia =&lt;br /&gt;
&amp;lt;p align=right&amp;gt;&amp;lt;sup&amp;gt;&#039;&#039;No &#039;&#039;&amp;lt;code&amp;gt;break&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;continue&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;int v[1005]&amp;lt;/code&amp;gt;&#039;&#039;, no nonsense&#039;&#039;&amp;lt;/sup&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== [[Cercul de informatică gimnaziu]] ==&lt;br /&gt;
[[Cercul de informatică gimnaziu]] este o colecție de lecții de algoritmi și programare pentru clasele V-VIII. Lecțiile acoperă programa de olimpiadă, dar nu se opresc aici, scopul final fiind de a forma minți algoritmice și a crea informaticieni cu cunoștințe de bază complete. În acest sens evităm &amp;quot;dopajul&amp;quot; (predarea în avans a unor cunoștințe, fără a preda prerechizitele necesare, în scopul obținerii unor rezultate mai bune la olimpiadă). Lecțiile sînt împărțite astfel încît să acopere anul școlar în ritmul de o lecție pe săptămînă, circa 40-44 de lecții la fiecare clasă. Fiecare lecție durează două ore. Unele lecții sînt concursuri cu participare de acasă, ele dublînd lecția din acea săptămînă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== [[Cercul de informatică IQ Academy]] ==&lt;br /&gt;
[[Cercul de informatică IQ Academy]] este un curs oferit de [http://iqacademy.ro/ IQ Academy], o inițiativă a [http://francu.org/ Fundației Frâncu pentru progres și educație] ce își propune formarea unor minți algoritmice, cu putere de abstractizare, cu “gândire computațională”, termen apărut relativ recent și susținut de oameni precum Mark Zuckerberg, creatorul Facebook.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Cercul de informatică, colegiul Tudor Vianu, 2012-2020 ==&lt;br /&gt;
Găsiți aici detalii despre [[Cercul de informatică - Colegiul Național de Informatică Tudor Vianu]] desfășurat în perioada 2012-2020.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Data Structures and  Algorithms ==&lt;br /&gt;
A future translation to English. Work in progress.&lt;br /&gt;
* [[Introduction to Algorithms]]&lt;br /&gt;
* [[Introductory Exercises]]&lt;br /&gt;
* [[Problems Involving Sequences]]&lt;br /&gt;
* [[Problems Involving Arrays]]&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Contact ==&lt;br /&gt;
Puteți contacta creatorul acestui site, Cristian Frâncu, la [mailto:cristian@francu.com cristian@francu.com]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_19:_Cozi_%C8%99i_algoritmul_lui_Lee&amp;diff=18590</id>
		<title>Clasa a 7-a Lecția 19: Cozi și algoritmul lui Lee</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_19:_Cozi_%C8%99i_algoritmul_lui_Lee&amp;diff=18590"/>
		<updated>2026-02-16T16:23:26Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/nja4_LDg9Xk&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tipul coadă ==&lt;br /&gt;
&lt;br /&gt;
Coada (în engleză queue) este o &#039;&#039;grămadă&#039;&#039; de obiecte ordonate după ordinea &#039;&#039;&#039;FIFO&#039;&#039;&#039;: &#039;&#039;first in, first out&#039;&#039;. Aceasta înseamnă că putem adăuga obiecte în coadă, iar atunci când le vom scoate, le vom scoate în aceeași ordine în care le-am adăugat. Ne aducem aminte că, prin contrast, stiva scoate obiectele în ordine inversă față de cum au fost adăugate (ordine &#039;&#039;&#039;LIFO&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Coada ca tip de date abstract ===&lt;br /&gt;
&lt;br /&gt;
Ne aducem aminte despre ideea de tip de date abstract: &#039;&#039;un tip de date abstract&#039;&#039; este un model matematic. Acest model definește operații asupra tipului de date, precum și restricții asupra efectului acestor operații. Tipurile de date abstracte sunt utile în simplificarea algoritmilor, deoarece descompun problema în subprobleme mai mici, cunoscute și studiate.&lt;br /&gt;
&lt;br /&gt;
Tipul de date abstract coadă definește operațiile:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;enqueue&#039;&#039;, care adaugă un element la coadă&lt;br /&gt;
* &#039;&#039;dequeue&#039;&#039;, care scoate un element din coadă&lt;br /&gt;
* &#039;&#039;empty&#039;&#039;, care ne spune dacă coada este goală&lt;br /&gt;
* &#039;&#039;full&#039;&#039;, care ne spune dacă coada este plină&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Restricțiile sunt asupra ordinii de returnare a elementelor și anume ele trebuie returnate după regula &#039;&#039;primul venit primul plecat&#039;&#039;. De aceea se spune că o coadă este o structură de tip &#039;&#039;&#039;FIFO&#039;&#039;&#039; (&#039;&#039;first in, first out&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
[[Image:coada.gif|frame|none|Coada ca tip abstract de date]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Implementarea cozilor cu vectori circulari ===&lt;br /&gt;
&lt;br /&gt;
Precum am mai spus, tipurile de date abstracte separă funcționalitatea de implementare. În cazul cozii există mai multe implementări posibile. Noi vom studia una dintre cele mai folosite: implementarea cu vectori circulari. Această implementare:&lt;br /&gt;
&lt;br /&gt;
* Folosește un vector pentru a păstra elementele și doi indici, primul și ultimul care memorează poziția primului, respectiv ultimului element din coadă. Pentru a nu deplasa elemente atunci când scoatem un element din coadă vom incrementa doar poziția primului element. Atunci când adăugăm un element în coadă incrementăm poziția ultimului element.&lt;br /&gt;
* Pentru a refolosi golurile rămase în urmă la scoaterea din coadă și a nu deplasa cei doi indici la infinit vom defini o mărime maximă a cozii, n. Atunci când unul din indici depășește această mărime el revine la zero. Logic vorbind după ultimul element din vector urmează din nou primul. De aceea spunem că vectorul este circular. Formula de actualizare a indicilor devine &amp;lt;code&amp;gt;primul = (primul + 1) % n&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;ultimul = (ultimul + 1) % n&amp;lt;/code&amp;gt;, unde &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este numărul maxim de elemente din coadă.&lt;br /&gt;
&lt;br /&gt;
[[Image:coada-vector-circular.gif|frame|none|Implementare coadă cu vector circular]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Observăm că primul este poziția primului element din coadă, iar ultimul este poziția primului element liber din vector.&lt;br /&gt;
* Coadă goală. Cum răspundem la întrebarea &#039;&#039;este coada goală&#039;&#039;? Se observă că acest lucru se întâmplă când primul == ultimul.&lt;br /&gt;
* Coada plină. Cum răspundem la întrebarea &#039;&#039;este coada plină&#039;&#039;? Observăm că aceasta se întâmplă tot atunci când primul == ultimul. Pentru a diferenția între aceste două cazuri vom &#039;&#039;sacrifica&#039;&#039; un element din vector, folosind numai &amp;lt;code&amp;gt;n-1&amp;lt;/code&amp;gt; poziții. În felul acesta testul de coadă plină va fi &amp;lt;code&amp;gt;(ultimul + 1) % n == primul&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* În această implementare cele patru operații au complexitate &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Important&#039;&#039;&#039;: în practică, pentru a nu face împărțiri, dimensiunea maximă a cozii va fi o putere a lui doi, constantă definită în program.&lt;br /&gt;
* Unul din avantajele acestei implementări, de care veți auzi în detaliu în facultate este că scoaterea de elemente se poate face în paralel cu adăugarea, de exemplu în cazul când un proces generează elementele de adăugat și un alt proces, independent, colectează elementele spre procesare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple de probleme ce folosesc tipul coadă ===&lt;br /&gt;
&lt;br /&gt;
Exemple de probleme ce folosesc cozi în rezolvări:&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/maxim2 Maxim2] dată la concursul ONI 2019 clasa a 6-a (punctul 2)&lt;br /&gt;
* [https://www.nerdarena.ro/problema/livada Livada] dată la cercul de informatică Vianu 2014 clasa a 9-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
La [http://pbinfo.ro/ pbinfo.ro]:&lt;br /&gt;
&lt;br /&gt;
* [https://www.pbinfo.ro/probleme/873/vase Vase]&lt;br /&gt;
* [https://www.pbinfo.ro/probleme/1239/fractii3 Fracții3]&lt;br /&gt;
* [https://www.pbinfo.ro/probleme/2187/expand Expand] (grea)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Algoritmul lui Lee (BFS Fill) ==&lt;br /&gt;
&lt;br /&gt;
În anul 1961 C. Y. Lee a publicat un algoritm de studiu al căilor de conectare între două puncte în diagrame electronice. Algoritmul original presupune o rețea plană (un circuit electronic) în care, mulțumită proprietăților curentului electric, toate muchiile sunt de lungime unu. De-a lungul timpului (și aparent mai mult în România) acest algoritm a ajuns să fie asociat cu parcurgerea pe lățime a unei matrice, drept pentru care acesta este modul în care îl voi prezenta aici.&lt;br /&gt;
&lt;br /&gt;
Deoarece algoritmul lui Lee este o parcurgere pe lățime ar trebui predat după conceptul de parcurgere pe lățime. De aceea, în această lecţie, voi încerca să prezint ambele.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Aplicații ale parcurgerii pe lățime ===&lt;br /&gt;
&lt;br /&gt;
* Găsirea drumului minim de la un punct la un altul, sau la mai multe puncte finale.&lt;br /&gt;
* Mai puțin menționat: fill cu mai puțină memorie suplimentară (dar și mai lent)&lt;br /&gt;
&lt;br /&gt;
Pentru exemplificare, să considerăm următoarea problemă: se dă un labirint codificat ca o matrice cu zero și unu, zero semnificând loc liber și unu fiind element obstacol (zid). Se dau de asemenea un punct de start și unul de final. În matrice ne putem deplasa pe linie sau pe coloană, cu condiția să nu avem un obstacol. Să se calculeze, lungimea drumului minim între cele două puncte, dacă există un astfel de drum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Parcurgerea în adîncime ===&lt;br /&gt;
&lt;br /&gt;
Când am discutat despre algoritmul &#039;&#039;flood fill&#039;&#039; am menționat că el este o parcurgere a matricei &#039;&#039;în adâncime&#039;&#039;, deoarece traversarea elementelor matricei se face avansând din vecin în vecin până ce se ajunge la o fundătură (un obstacol, ieșire din matrice, sau element deja parcurs). Am denumit această parcurgere &#039;&#039;DFS&#039;&#039; (depth first search), aceasta însemnând că în alegerea noului element de vizitat preferăm adâncimea. Ne putem imagina această parcurgere cu un fir subțire de apă care curge într-o direcție oarecare, apoi, când nu mai poate schimbă direcția. Când ramura curentă se &amp;quot;înfundă&amp;quot; apa se va ramifica din firul principal undeva cel mai aproape de fundătură, acolo unde o poate lua pe altă direcție.&lt;br /&gt;
&lt;br /&gt;
Am menționat atunci că pentru acest tip de parcurgere avem nevoie de o &#039;&#039;&#039;stivă&#039;&#039;&#039; pentru memorarea elementelor ce urmează a fi parcurse. Stiva este fie explicită (nu am vorbit despre aceasta), fie implicită, într-o implementare recursivă. Această stivă are consecințe: ea adaugă &#039;&#039;O(M x N)&#039;&#039; memorie suplimentară.&lt;br /&gt;
&lt;br /&gt;
Folosind parcurgerea în adâncime, &#039;&#039;flood fill&#039;&#039;, putem răspunde la întrebarea dacă există drum între cele două puncte, dar nu putem calcula drumul minim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Parcurgerea pe lățime ===&lt;br /&gt;
&lt;br /&gt;
Prin contrast, ne putem imagina o parcurgere pe &#039;&#039;lățime&#039;&#039;. În această parcurgere vom traversa mai întâi elementele vecine cu elementul de start. Apoi vom continua cu elementele vecine cu ele. Și apoi cu elementele vecine cu vecinele, și așa mai departe. Putem vizualiza această parcurgere dacă ne imaginăm că turnăm apă în punctul de pornire. Ea se va împrăștia uniform în toate direcțiile. Dacă va da de obstacole ea va merge de-a lungul lor și le va ocoli. La orice moment distanța între orice punct de pe frontiera apei și punctul de start este aceeași.&lt;br /&gt;
&lt;br /&gt;
Denumim această parcurgere &#039;&#039;BFS&#039;&#039; (breadth first search), deoarece în alegerea noului element de parcurs vom prefera lățimea. La fiecare iterație &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; vom trata elementele aflate la distanță &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; de punctul original. Ele formează frontiera (marginea) apei, în exemplul nostru. După fiecare iterație frontiera va fi înlocuită cu o nouă frontieră aflată la distanță mai mare cu unu decât vechea frontieră.&lt;br /&gt;
&lt;br /&gt;
Precum vom vedea mai jos această parcurgere este similară cu cea în adâncime, diferența fiind că ea folosește o coadă în loc de stivă pentru memorarea elementelor ce urmează a fi parcurse. Spre deosebire de &#039;&#039;flood fill&#039;&#039;, coada trebuie să fie explicită. De aceea algoritmul poate părea foarte diferit de &#039;&#039;flood fill&#039;&#039;, dar, în realitate nu este.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplificare grafică ===&lt;br /&gt;
&lt;br /&gt;
Să vedem niște vizualizări ale algoritmului BFS fill preluate de la wikipedia: [https://en.wikipedia.org/wiki/Flood_fill algoritm flood fill]:&lt;br /&gt;
&lt;br /&gt;
[[File:Wfm_floodfill_animation_queue_wikipedia.gif|frame|none|Exemplu de BFS fill cu 4 direcții]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Implementarea parcurgerii pe lățime în matrice ===&lt;br /&gt;
&lt;br /&gt;
Cum implementăm parcurgerea pe lățime? Cum memorăm frontiera? O idee imediată ar fi să ținem două frontiere: cea curentă, în curs de procesare, și cea nouă, în curs de generare. La fiecare iterație calculăm o nouă frontieră pe baza celei dinainte. Apoi comutăm pe frontiera cea nouă. Ce conțin frontierele (ce stocăm)? Vom păstra identificatorii unici ai elementelor matricei, respectiv linia și coloana. Frontierele vor fi vectori de perechi (linie, coloană). Această metodă funcționează, dar nu este nici cea mai simplă și nici cea mai economică ca memorie.&lt;br /&gt;
&lt;br /&gt;
În practică nu vom face distincția între frontiera veche și cea nouă. Elementele din frontiera nouă vor fi adăugate la frontiera veche, la sfârșit. Vom avea astfel o singură frontieră de elemente de procesat. Vom procesa elementele de la începutul frontierei și vom adăuga elementele noi, vecinii, la finalul frontierei. În imaginea noastră cu apa punctele procesate vor descrie o spirală în jurul punctului de pornire.&lt;br /&gt;
&lt;br /&gt;
În acest fel ajungem la structura de date pe care o vom folosi pentru stocarea punctelor de frontieră: &#039;&#039;&#039;coada&#039;&#039;&#039;. Așa cum am menționat mai sus, în loc de &#039;&#039;&#039;stivă&#039;&#039;&#039; vom folosi o &#039;&#039;&#039;coadă&#039;&#039;&#039; de perechi (&#039;&#039;linie, coloană&#039;&#039;) pentru stocarea elementelor ce urmează a fi parcurse. La un pas vom extrage din coadă primul element și vom adăuga elementele vecine lui la finalul cozii, câtă vreme ele nu se află deja în &#039;&#039;&#039;coadă&#039;&#039;&#039; (coada conține numai elemente unice).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Algoritmul de parcurgere BFS ===&lt;br /&gt;
&lt;br /&gt;
Iată o schiță de algoritm, care nu este neapărat universal valabil, dar poate fi adaptat diverselor situații. Această versiune de algoritm își propune să găsească cel mai scurt drum de la &#039;&#039;(lstart, cstart)&#039;&#039; la &#039;&#039;(lend, cend)&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;&lt;br /&gt;
# Inițializează matricea labirint L[][] cu 0 pentru loc liber și -1 pentru obstacol&lt;br /&gt;
# Inițializează coada cu elementul de pornire (lstart, cstart)&lt;br /&gt;
# Setează L[lstart][cstart]  // distanța inițială&lt;br /&gt;
# Execută:&lt;br /&gt;
## Scoate primul element din coadă. Fie el linia (lin, col)&lt;br /&gt;
## Setează dist  // distanța acestui element&lt;br /&gt;
## Pentru fiecare vecin (l, c) al lui (lin, col)&lt;br /&gt;
### Dacă L[l][c] = 0 // nu se află în coadă și nici nu este obstacol&lt;br /&gt;
#### Adaugă (l, c) în coadă&lt;br /&gt;
#### Setează L[l][c] &lt;br /&gt;
# Câtă vreme coada nu este vidă și (lin, col) nu este destinația (lend, cend)&lt;br /&gt;
# Afișează L[lin][col] // distanța până la cel mai apropiat punct destinație&lt;br /&gt;
&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Note de implementare &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Vecinii unui element din matrice pot fi fie cei adiacenți pe linie și coloană, fie și cei pe diagonală, algoritmul funcționează la fel.&lt;br /&gt;
* De obicei vom folosi tehnicile învățate:&lt;br /&gt;
** Bordarea matricelor pentru a scăpa de testul de ieșire din matrice&lt;br /&gt;
** Vectorii de direcție pentru a genera ușor vecinii&lt;br /&gt;
* Problema poate fi considerată una de simulare: simulăm parcurgerea tuturor traseelor optime în același timp.&lt;br /&gt;
* Dacă nu există drum între cele două puncte algoritmul se va termina când coada devine goală.&lt;br /&gt;
* În funcție de ceea ce se cere (drum minim până la destinație, drum minim până la anumite puncte, sau drum minim până la toate elementele matricei) coada poate fi parcursă până la final, sau ne putem opri când anumite condiții ale simulării au fost îndeplinite, cum ar fi atingerea punctului destinație.&lt;br /&gt;
* Cum știm dacă un element se află deja în coadă? Printr-o matrice de frecvență: la momentul adăugării în coadă vom marca aceasta în matrice. De cele mai multe ori putem combina matricea de frecvență cu matricea de distanțe (ca în algoritmul de mai sus): un element diferit de zero semnifică distanța acelui element la punctul de start. În această notație obstacolele trebuie memorate ca numere negative pentru a nu fi confundate cu distanțe.&lt;br /&gt;
* Cum știm că am ajuns în punctul final? Putem să testăm explicit linia și coloana. Dar dacă avem mai multe puncte finale trebuie să le marcăm într-o matrice. Această matrice se poate de obicei combina cu matricea de frecvență și cu cea de distanțe, deoarece avem nevoie doar de un bit unu sau zero.&lt;br /&gt;
* Algoritmul poate fi adaptat să calculeze drumul minim de la mai multe puncte de pornire la mai multe puncte destinație. În acest caz vom inițializa coada cu toate punctele de pornire.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Reconstrucția drumului minim ===&lt;br /&gt;
&lt;br /&gt;
Pentru a reconstitui un drum minim între sursă și destinație vom porni invers: de la destinație și vom avansa în &amp;lt;code&amp;gt;L[][]&amp;lt;/code&amp;gt; pe elemente descrescătoare din unu în unu, până ce ajungem la sursă. &#039;&#039;&#039;Atenție&#039;&#039;&#039;, drumul nu este neapărat unic.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Implementarea cozii de coordonate ===&lt;br /&gt;
&lt;br /&gt;
Pentru coadă vom folosi un vector circular de mărime &#039;&#039;2(M+N)&#039;&#039; (vezi explicația la capitolul de complexități). Vom avea o variabilă, &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt; care păstrează indicele de adăugare în coadă, precum și o variabilă ultim care păstrează poziția primului element &#039;&#039;gol&#039;&#039;, în afara cozii. Acești doi indici vor avansa circular, modulo mărimea cozii.&lt;br /&gt;
&lt;br /&gt;
Coada poate să fie un vector de elemente tip &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;, sau doi vectori separați. Prima variantă este mai bună din punct de vedere &#039;&#039;cache&#039;&#039;, dar poate duce la risipă de memorie în anumite situații, dacă elementul de tip &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; nu ocupă un număr de octeți divizibil cu patru (sau cu lungimea cuvântului de memorie).&lt;br /&gt;
&lt;br /&gt;
Vom folosi trei funcții:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;enqueue( lin, col )&amp;lt;/code&amp;gt; care adaugă elementul &#039;&#039;(lin, col)&#039;&#039; în coadă.&lt;br /&gt;
* &amp;lt;code&amp;gt;dequeue()&amp;lt;/code&amp;gt; care extrage elementul &#039;&#039;(lin, col)&#039;&#039; din coadă.&lt;br /&gt;
* &amp;lt;code&amp;gt;emptyqueue()&amp;lt;/code&amp;gt; care returnează 1 dacă coada este vidă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Iată o posibilă implementare ce presupune că numărul de linii și coloane este maxim 255:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int prim, ultim;&lt;br /&gt;
unsigned char coadal[NCOADA], coadac[NCOADA]; // coordonate mici&lt;br /&gt;
&lt;br /&gt;
// adauga coordonate in coada&lt;br /&gt;
static inline void enqueue( int l, int c ) {&lt;br /&gt;
  coadal[ultim] = l;&lt;br /&gt;
  coadac[ultim] = c;&lt;br /&gt;
  ultim = (ultim + 1) % NCOADA;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scoate coordonate din coada&lt;br /&gt;
static inline int dequeue() {&lt;br /&gt;
  int retval = coadal[prim] * 256 + coadac[prim];&lt;br /&gt;
&lt;br /&gt;
  prim = (prim + 1) % NCOADA;&lt;br /&gt;
&lt;br /&gt;
  return retval;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza 1 cand coada este goala&lt;br /&gt;
static inline int emptyqueue() {&lt;br /&gt;
  return prim == ultim;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Observații: &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Funcția &amp;lt;code&amp;gt;dequeue()&amp;lt;/code&amp;gt; returnează două valori, linie și coloană. Ele fiind mici le-am combinat într-un singur întreg. Acest lucru este posibil în general, deoarece putem combina două valori &amp;lt;code&amp;gt;unsigned short&amp;lt;/code&amp;gt; într-un &amp;lt;code&amp;gt;unsigned int&amp;lt;/code&amp;gt;, ceea ce ne permite linii și coloane până în 65535, număr în general acoperitor pentru o matrice stocată în memorie.&lt;br /&gt;
* Astfel am putea să stocăm în coadă direct produsul returnat de funcția &amp;lt;code&amp;gt;dequeue()&amp;lt;/code&amp;gt;. Avantajele se datorează, în mare, &#039;&#039;cache-ului&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Complexități în timp și spațiu ===&lt;br /&gt;
&lt;br /&gt;
Timpul de execuție este &#039;&#039;O(M x N)&#039;&#039; deoarece fiecare element al matricei va fi luat în calcul cel mult o dată.&lt;br /&gt;
&lt;br /&gt;
Memoria necesară este &#039;&#039;O(M x N)&#039;&#039; deoarece avem nevoie de matricea de distanțe, matricea de frecvențe și cea de puncte terminale, precum și memoria ocupată de coadă. În realitate, de cele mai multe ori putem unifica matricele suplimentare, unindu-le cu matricea labirint. În acest caz memoria suplimentară este cea a cozii. La momentul când scriu aceste rânduri nu am găsit o estimare asupra dimensiunii maxime a cozii necesare. Intuitiv ea este &#039;&#039;O(M + N)&#039;&#039;, deoarece frontiera, într-un labirint fără obstacole, când punctul de pornire este în mijloc, nu va depăși M+N. Este neclar dacă o altă matrice și un alt punct de pornire o poate face să depășească &#039;&#039;M + N&#039;&#039; și cu cât. Ca practică de implementare sfatul meu este să o dimensionați de &#039;&#039;2(M + N)&#039;&#039;, acoperitor, deoarece este un număr oricum mult mai mic decât matricea deja stocată, &#039;&#039;M x N&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Datorită consumului atât de mic de memorie suplimentară, algoritmul lui Lee poate fi folosit ca și ca algoritm de fill pe matrice mari, sau când memoria suplimentară este mică. El va fi însă mai lent decât &#039;&#039;flood fill&#039;&#039;, de obicei de multe ori (20-30 de ori), ceea ce dovedește încă o dată că, pentru unii algoritmi, memoria suplimentară se traduce în timp de execuție mai mic.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Aplicații ===&lt;br /&gt;
&lt;br /&gt;
Algoritmul lui Lee este necesar când dorim să aflăm informații despre drumuri și distanțe:&lt;br /&gt;
&lt;br /&gt;
* Găsirea ieșirii dintr-un labirint / castel / hartă.&lt;br /&gt;
* Găsirea celui mai scurt drum între două puncte dintr-o matrice.&lt;br /&gt;
* În lumea reală, proiectarea de circuite electronice.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple de probleme ce folosesc algoritmul lui Lee ===&lt;br /&gt;
&lt;br /&gt;
Orice problemă rezolvabilă prin &#039;&#039;flood fill&#039;&#039; este, desigur, rezolvabilă și cu &#039;&#039;Lee&#039;&#039;, folosind mai puțină memorie, dar consumând mai mult timp pentru executarea programului. N-am găsit foarte multe exemple de probleme ce necesită &#039;&#039;Lee&#039;&#039;, dar nu se pot rezolva cu &#039;&#039;flood fill&#039;&#039;. Iată câteva exemple:&lt;br /&gt;
&lt;br /&gt;
Exemple de probleme ce folosesc algoritmul lui Lee în rezolvări:&lt;br /&gt;
&lt;br /&gt;
La [https://www.nerdarena.ro/ NerdArena]:&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/alee Alee] dată la OJI 2007 clasa a 10-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/insule Insule] dată la OJI 2009 clasa a 10-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/miting Miting] dată la OJI 2016 clasa a 10-a (grea)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
La [http://infoarena.ro/ InfoArena]:&lt;br /&gt;
&lt;br /&gt;
* [https://www.infoarena.ro/algoritmul-lee Articolul de la Infoarena] include câteva exemple&lt;br /&gt;
* [https://www.infoarena.ro/problema/muzeu Muzeu] &lt;br /&gt;
* [https://www.infoarena.ro/problema/traseu3 Traseu3] dată la ONI 2014, clasa a 9-a (Lee 3D, relativ ușoară)&lt;br /&gt;
* [https://www.infoarena.ro/problema/gheizere Gheizere] dată la ONI 2012, clasa a 10-a (problemă grea)&lt;br /&gt;
* [https://www.infoarena.ro/problema/ai AI] dată la OJI 2011, clasa a 10-a (enunț complex)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 19 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/livada Livada] &lt;br /&gt;
* [https://www.nerdarena.ro/problema/alee Alee] alee dată la OJI 2007 clasa a 10-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/insule Insule] dată la OJI 2009 clasa a 10-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_19:_Cozi_%C8%99i_algoritmul_lui_Lee&amp;amp;action=edit&amp;amp;redlink=1 Accesează rezolvarea temei 19]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_19:_Cozi_%C8%99i_algoritmul_lui_Lee&amp;diff=18589</id>
		<title>Clasa a 7-a Lecția 19: Cozi și algoritmul lui Lee</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_19:_Cozi_%C8%99i_algoritmul_lui_Lee&amp;diff=18589"/>
		<updated>2026-02-16T15:12:42Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/nja4_LDg9Xk&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tipul coadă ==&lt;br /&gt;
&lt;br /&gt;
Coada (în engleză queue) este o &#039;&#039;grămadă&#039;&#039; de obiecte ordonate după ordinea &#039;&#039;&#039;FIFO&#039;&#039;&#039;: &#039;&#039;first in, first out&#039;&#039;. Aceasta înseamnă că putem adăuga obiecte în coadă, iar atunci când le vom scoate, le vom scoate în aceeași ordine în care le-am adăugat. Ne aducem aminte că, prin contrast, stiva scoate obiectele în ordine inversă față de cum au fost adăugate (ordine &#039;&#039;&#039;LIFO&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Coada ca tip de date abstract ===&lt;br /&gt;
&lt;br /&gt;
Ne aducem aminte despre ideea de tip de date abstract: &#039;&#039;un tip de date abstract&#039;&#039; este un model matematic. Acest model definește operații asupra tipului de date, precum și restricții asupra efectului acestor operații. Tipurile de date abstracte sunt utile în simplificarea algoritmilor, deoarece descompun problema în subprobleme mai mici, cunoscute și studiate.&lt;br /&gt;
&lt;br /&gt;
Tipul de date abstract coadă definește operațiile:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;enqueue&#039;&#039;, care adaugă un element la coadă&lt;br /&gt;
* &#039;&#039;dequeue&#039;&#039;, care scoate un element din coadă&lt;br /&gt;
* &#039;&#039;empty&#039;&#039;, care ne spune dacă coada este goală&lt;br /&gt;
* &#039;&#039;full&#039;&#039;, care ne spune dacă coada este plină&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Restricțiile sunt asupra ordinii de returnare a elementelor și anume ele trebuie returnate după regula &#039;&#039;primul venit primul plecat&#039;&#039;. De aceea se spune că o coadă este o structură de tip &#039;&#039;&#039;FIFO&#039;&#039;&#039; (&#039;&#039;first in, first out&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
[[Image:coada.gif|frame|none|Coada ca tip abstract de date]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Implementarea cozilor cu vectori circulari ===&lt;br /&gt;
&lt;br /&gt;
Precum am mai spus, tipurile de date abstracte separă funcționalitatea de implementare. În cazul cozii există mai multe implementări posibile. Noi vom studia una dintre cele mai folosite: implementarea cu vectori circulari. Această implementare:&lt;br /&gt;
&lt;br /&gt;
* Folosește un vector pentru a păstra elementele și doi indici, primul și ultimul care memorează poziția primului, respectiv ultimului element din coadă. Pentru a nu deplasa elemente atunci când scoatem un element din coadă vom incrementa doar poziția primului element. Atunci când adăugăm un element în coadă incrementăm poziția ultimului element.&lt;br /&gt;
* Pentru a refolosi golurile rămase în urmă la scoaterea din coadă și a nu deplasa cei doi indici la infinit vom defini o mărime maximă a cozii, n. Atunci când unul din indici depășește această mărime el revine la zero. Logic vorbind după ultimul element din vector urmează din nou primul. De aceea spunem că vectorul este circular. Formula de actualizare a indicilor devine &amp;lt;code&amp;gt;primul = (primul + 1) % n&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;ultimul = (ultimul + 1) % n&amp;lt;/code&amp;gt;, unde &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este numărul maxim de elemente din coadă.&lt;br /&gt;
&lt;br /&gt;
[[Image:coada-vector-circular.gif|frame|none|Implementare coadă cu vector circular]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Observăm că primul este poziția primului element din coadă, iar ultimul este poziția primului element liber din vector.&lt;br /&gt;
* Coadă goală. Cum răspundem la întrebarea &#039;&#039;este coada goală&#039;&#039;? Se observă că acest lucru se întâmplă când primul == ultimul.&lt;br /&gt;
* Coada plină. Cum răspundem la întrebarea &#039;&#039;este coada plină&#039;&#039;? Observăm că aceasta se întâmplă tot atunci când primul == ultimul. Pentru a diferenția între aceste două cazuri vom &#039;&#039;sacrifica&#039;&#039; un element din vector, folosind numai &amp;lt;code&amp;gt;n-1&amp;lt;/code&amp;gt; poziții. În felul acesta testul de coadă plină va fi &amp;lt;code&amp;gt;(ultimul + 1) % n == primul&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* În această implementare cele patru operații au complexitate &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Important&#039;&#039;&#039;: în practică, pentru a nu face împărțiri, dimensiunea maximă a cozii va fi o putere a lui doi, constantă definită în program.&lt;br /&gt;
* Unul din avantajele acestei implementări, de care veți auzi în detaliu în facultate este că scoaterea de elemente se poate face în paralel cu adăugarea, de exemplu în cazul când un proces generează elementele de adăugat și un alt proces, independent, colectează elementele spre procesare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple de probleme ce folosesc tipul coadă ===&lt;br /&gt;
&lt;br /&gt;
Exemple de probleme ce folosesc cozi în rezolvări:&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/maxim2 Maxim] dată la concursul ONI 2019 clasa a 6-a (punctul 2)&lt;br /&gt;
* [https://www.nerdarena.ro/problema/livada Livada] dată la cercul de informatică Vianu 2014 clasa a 9-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
La [http://pbinfo.ro/ pbinfo.ro]:&lt;br /&gt;
&lt;br /&gt;
* [https://www.pbinfo.ro/probleme/873/vase Vase]&lt;br /&gt;
* [https://www.pbinfo.ro/probleme/1239/fractii3 Fracții3]&lt;br /&gt;
* [https://www.pbinfo.ro/probleme/2187/expand Expand] (grea)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Algoritmul lui Lee (BFS Fill) ==&lt;br /&gt;
&lt;br /&gt;
În anul 1961 C. Y. Lee a publicat un algoritm de studiu al căilor de conectare între două puncte în diagrame electronice. Algoritmul original presupune o rețea plană (un circuit electronic) în care, mulțumită proprietăților curentului electric, toate muchiile sunt de lungime unu. De-a lungul timpului (și aparent mai mult în România) acest algoritm a ajuns să fie asociat cu parcurgerea pe lățime a unei matrice, drept pentru care acesta este modul în care îl voi prezenta aici.&lt;br /&gt;
&lt;br /&gt;
Deoarece algoritmul lui Lee este o parcurgere pe lățime ar trebui predat după conceptul de parcurgere pe lățime. De aceea, în această lecţie, voi încerca să prezint ambele.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Aplicații ale parcurgerii pe lățime ===&lt;br /&gt;
&lt;br /&gt;
* Găsirea drumului minim de la un punct la un altul, sau la mai multe puncte finale.&lt;br /&gt;
* Mai puțin menționat: fill cu mai puțină memorie suplimentară (dar și mai lent)&lt;br /&gt;
&lt;br /&gt;
Pentru exemplificare, să considerăm următoarea problemă: se dă un labirint codificat ca o matrice cu zero și unu, zero semnificând loc liber și unu fiind element obstacol (zid). Se dau de asemenea un punct de start și unul de final. În matrice ne putem deplasa pe linie sau pe coloană, cu condiția să nu avem un obstacol. Să se calculeze, lungimea drumului minim între cele două puncte, dacă există un astfel de drum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Parcurgerea în adîncime ===&lt;br /&gt;
&lt;br /&gt;
Când am discutat despre algoritmul &#039;&#039;flood fill&#039;&#039; am menționat că el este o parcurgere a matricei &#039;&#039;în adâncime&#039;&#039;, deoarece traversarea elementelor matricei se face avansând din vecin în vecin până ce se ajunge la o fundătură (un obstacol, ieșire din matrice, sau element deja parcurs). Am denumit această parcurgere &#039;&#039;DFS&#039;&#039; (depth first search), aceasta însemnând că în alegerea noului element de vizitat preferăm adâncimea. Ne putem imagina această parcurgere cu un fir subțire de apă care curge într-o direcție oarecare, apoi, când nu mai poate schimbă direcția. Când ramura curentă se &amp;quot;înfundă&amp;quot; apa se va ramifica din firul principal undeva cel mai aproape de fundătură, acolo unde o poate lua pe altă direcție.&lt;br /&gt;
&lt;br /&gt;
Am menționat atunci că pentru acest tip de parcurgere avem nevoie de o &#039;&#039;&#039;stivă&#039;&#039;&#039; pentru memorarea elementelor ce urmează a fi parcurse. Stiva este fie explicită (nu am vorbit despre aceasta), fie implicită, într-o implementare recursivă. Această stivă are consecințe: ea adaugă &#039;&#039;O(M x N)&#039;&#039; memorie suplimentară.&lt;br /&gt;
&lt;br /&gt;
Folosind parcurgerea în adâncime, &#039;&#039;flood fill&#039;&#039;, putem răspunde la întrebarea dacă există drum între cele două puncte, dar nu putem calcula drumul minim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Parcurgerea pe lățime ===&lt;br /&gt;
&lt;br /&gt;
Prin contrast, ne putem imagina o parcurgere pe &#039;&#039;lățime&#039;&#039;. În această parcurgere vom traversa mai întâi elementele vecine cu elementul de start. Apoi vom continua cu elementele vecine cu ele. Și apoi cu elementele vecine cu vecinele, și așa mai departe. Putem vizualiza această parcurgere dacă ne imaginăm că turnăm apă în punctul de pornire. Ea se va împrăștia uniform în toate direcțiile. Dacă va da de obstacole ea va merge de-a lungul lor și le va ocoli. La orice moment distanța între orice punct de pe frontiera apei și punctul de start este aceeași.&lt;br /&gt;
&lt;br /&gt;
Denumim această parcurgere &#039;&#039;BFS&#039;&#039; (breadth first search), deoarece în alegerea noului element de parcurs vom prefera lățimea. La fiecare iterație &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; vom trata elementele aflate la distanță &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; de punctul original. Ele formează frontiera (marginea) apei, în exemplul nostru. După fiecare iterație frontiera va fi înlocuită cu o nouă frontieră aflată la distanță mai mare cu unu decât vechea frontieră.&lt;br /&gt;
&lt;br /&gt;
Precum vom vedea mai jos această parcurgere este similară cu cea în adâncime, diferența fiind că ea folosește o coadă în loc de stivă pentru memorarea elementelor ce urmează a fi parcurse. Spre deosebire de &#039;&#039;flood fill&#039;&#039;, coada trebuie să fie explicită. De aceea algoritmul poate părea foarte diferit de &#039;&#039;flood fill&#039;&#039;, dar, în realitate nu este.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplificare grafică ===&lt;br /&gt;
&lt;br /&gt;
Să vedem niște vizualizări ale algoritmului BFS fill preluate de la wikipedia: [https://en.wikipedia.org/wiki/Flood_fill algoritm flood fill]:&lt;br /&gt;
&lt;br /&gt;
[[File:Wfm_floodfill_animation_queue_wikipedia.gif|frame|none|Exemplu de BFS fill cu 4 direcții]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Implementarea parcurgerii pe lățime în matrice ===&lt;br /&gt;
&lt;br /&gt;
Cum implementăm parcurgerea pe lățime? Cum memorăm frontiera? O idee imediată ar fi să ținem două frontiere: cea curentă, în curs de procesare, și cea nouă, în curs de generare. La fiecare iterație calculăm o nouă frontieră pe baza celei dinainte. Apoi comutăm pe frontiera cea nouă. Ce conțin frontierele (ce stocăm)? Vom păstra identificatorii unici ai elementelor matricei, respectiv linia și coloana. Frontierele vor fi vectori de perechi (linie, coloană). Această metodă funcționează, dar nu este nici cea mai simplă și nici cea mai economică ca memorie.&lt;br /&gt;
&lt;br /&gt;
În practică nu vom face distincția între frontiera veche și cea nouă. Elementele din frontiera nouă vor fi adăugate la frontiera veche, la sfârșit. Vom avea astfel o singură frontieră de elemente de procesat. Vom procesa elementele de la începutul frontierei și vom adăuga elementele noi, vecinii, la finalul frontierei. În imaginea noastră cu apa punctele procesate vor descrie o spirală în jurul punctului de pornire.&lt;br /&gt;
&lt;br /&gt;
În acest fel ajungem la structura de date pe care o vom folosi pentru stocarea punctelor de frontieră: &#039;&#039;&#039;coada&#039;&#039;&#039;. Așa cum am menționat mai sus, în loc de &#039;&#039;&#039;stivă&#039;&#039;&#039; vom folosi o &#039;&#039;&#039;coadă&#039;&#039;&#039; de perechi (&#039;&#039;linie, coloană&#039;&#039;) pentru stocarea elementelor ce urmează a fi parcurse. La un pas vom extrage din coadă primul element și vom adăuga elementele vecine lui la finalul cozii, câtă vreme ele nu se află deja în &#039;&#039;&#039;coadă&#039;&#039;&#039; (coada conține numai elemente unice).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Algoritmul de parcurgere BFS ===&lt;br /&gt;
&lt;br /&gt;
Iată o schiță de algoritm, care nu este neapărat universal valabil, dar poate fi adaptat diverselor situații. Această versiune de algoritm își propune să găsească cel mai scurt drum de la &#039;&#039;(lstart, cstart)&#039;&#039; la &#039;&#039;(lend, cend)&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;&lt;br /&gt;
# Inițializează matricea labirint L[][] cu 0 pentru loc liber și -1 pentru obstacol&lt;br /&gt;
# Inițializează coada cu elementul de pornire (lstart, cstart)&lt;br /&gt;
# Setează L[lstart][cstart]  // distanța inițială&lt;br /&gt;
# Execută:&lt;br /&gt;
## Scoate primul element din coadă. Fie el linia (lin, col)&lt;br /&gt;
## Setează dist  // distanța acestui element&lt;br /&gt;
## Pentru fiecare vecin (l, c) al lui (lin, col)&lt;br /&gt;
### Dacă L[l][c] = 0 // nu se află în coadă și nici nu este obstacol&lt;br /&gt;
#### Adaugă (l, c) în coadă&lt;br /&gt;
#### Setează L[l][c] &lt;br /&gt;
# Câtă vreme coada nu este vidă și (lin, col) nu este destinația (lend, cend)&lt;br /&gt;
# Afișează L[lin][col] // distanța până la cel mai apropiat punct destinație&lt;br /&gt;
&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Note de implementare &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Vecinii unui element din matrice pot fi fie cei adiacenți pe linie și coloană, fie și cei pe diagonală, algoritmul funcționează la fel.&lt;br /&gt;
* De obicei vom folosi tehnicile învățate:&lt;br /&gt;
** Bordarea matricelor pentru a scăpa de testul de ieșire din matrice&lt;br /&gt;
** Vectorii de direcție pentru a genera ușor vecinii&lt;br /&gt;
* Problema poate fi considerată una de simulare: simulăm parcurgerea tuturor traseelor optime în același timp.&lt;br /&gt;
* Dacă nu există drum între cele două puncte algoritmul se va termina când coada devine goală.&lt;br /&gt;
* În funcție de ceea ce se cere (drum minim până la destinație, drum minim până la anumite puncte, sau drum minim până la toate elementele matricei) coada poate fi parcursă până la final, sau ne putem opri când anumite condiții ale simulării au fost îndeplinite, cum ar fi atingerea punctului destinație.&lt;br /&gt;
* Cum știm dacă un element se află deja în coadă? Printr-o matrice de frecvență: la momentul adăugării în coadă vom marca aceasta în matrice. De cele mai multe ori putem combina matricea de frecvență cu matricea de distanțe (ca în algoritmul de mai sus): un element diferit de zero semnifică distanța acelui element la punctul de start. În această notație obstacolele trebuie memorate ca numere negative pentru a nu fi confundate cu distanțe.&lt;br /&gt;
* Cum știm că am ajuns în punctul final? Putem să testăm explicit linia și coloana. Dar dacă avem mai multe puncte finale trebuie să le marcăm într-o matrice. Această matrice se poate de obicei combina cu matricea de frecvență și cu cea de distanțe, deoarece avem nevoie doar de un bit unu sau zero.&lt;br /&gt;
* Algoritmul poate fi adaptat să calculeze drumul minim de la mai multe puncte de pornire la mai multe puncte destinație. În acest caz vom inițializa coada cu toate punctele de pornire.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Reconstrucția drumului minim ===&lt;br /&gt;
&lt;br /&gt;
Pentru a reconstitui un drum minim între sursă și destinație vom porni invers: de la destinație și vom avansa în &amp;lt;code&amp;gt;L[][]&amp;lt;/code&amp;gt; pe elemente descrescătoare din unu în unu, până ce ajungem la sursă. &#039;&#039;&#039;Atenție&#039;&#039;&#039;, drumul nu este neapărat unic.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Implementarea cozii de coordonate ===&lt;br /&gt;
&lt;br /&gt;
Pentru coadă vom folosi un vector circular de mărime &#039;&#039;2(M+N)&#039;&#039; (vezi explicația la capitolul de complexități). Vom avea o variabilă, &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt; care păstrează indicele de adăugare în coadă, precum și o variabilă ultim care păstrează poziția primului element &#039;&#039;gol&#039;&#039;, în afara cozii. Acești doi indici vor avansa circular, modulo mărimea cozii.&lt;br /&gt;
&lt;br /&gt;
Coada poate să fie un vector de elemente tip &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;, sau doi vectori separați. Prima variantă este mai bună din punct de vedere &#039;&#039;cache&#039;&#039;, dar poate duce la risipă de memorie în anumite situații, dacă elementul de tip &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; nu ocupă un număr de octeți divizibil cu patru (sau cu lungimea cuvântului de memorie).&lt;br /&gt;
&lt;br /&gt;
Vom folosi trei funcții:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;enqueue( lin, col )&amp;lt;/code&amp;gt; care adaugă elementul &#039;&#039;(lin, col)&#039;&#039; în coadă.&lt;br /&gt;
* &amp;lt;code&amp;gt;dequeue()&amp;lt;/code&amp;gt; care extrage elementul &#039;&#039;(lin, col)&#039;&#039; din coadă.&lt;br /&gt;
* &amp;lt;code&amp;gt;emptyqueue()&amp;lt;/code&amp;gt; care returnează 1 dacă coada este vidă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Iată o posibilă implementare ce presupune că numărul de linii și coloane este maxim 255:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int prim, ultim;&lt;br /&gt;
unsigned char coadal[NCOADA], coadac[NCOADA]; // coordonate mici&lt;br /&gt;
&lt;br /&gt;
// adauga coordonate in coada&lt;br /&gt;
static inline void enqueue( int l, int c ) {&lt;br /&gt;
  coadal[ultim] = l;&lt;br /&gt;
  coadac[ultim] = c;&lt;br /&gt;
  ultim = (ultim + 1) % NCOADA;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scoate coordonate din coada&lt;br /&gt;
static inline int dequeue() {&lt;br /&gt;
  int retval = coadal[prim] * 256 + coadac[prim];&lt;br /&gt;
&lt;br /&gt;
  prim = (prim + 1) % NCOADA;&lt;br /&gt;
&lt;br /&gt;
  return retval;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza 1 cand coada este goala&lt;br /&gt;
static inline int emptyqueue() {&lt;br /&gt;
  return prim == ultim;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Observații: &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Funcția &amp;lt;code&amp;gt;dequeue()&amp;lt;/code&amp;gt; returnează două valori, linie și coloană. Ele fiind mici le-am combinat într-un singur întreg. Acest lucru este posibil în general, deoarece putem combina două valori &amp;lt;code&amp;gt;unsigned short&amp;lt;/code&amp;gt; într-un &amp;lt;code&amp;gt;unsigned int&amp;lt;/code&amp;gt;, ceea ce ne permite linii și coloane până în 65535, număr în general acoperitor pentru o matrice stocată în memorie.&lt;br /&gt;
* Astfel am putea să stocăm în coadă direct produsul returnat de funcția &amp;lt;code&amp;gt;dequeue()&amp;lt;/code&amp;gt;. Avantajele se datorează, în mare, &#039;&#039;cache-ului&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Complexități în timp și spațiu ===&lt;br /&gt;
&lt;br /&gt;
Timpul de execuție este &#039;&#039;O(M x N)&#039;&#039; deoarece fiecare element al matricei va fi luat în calcul cel mult o dată.&lt;br /&gt;
&lt;br /&gt;
Memoria necesară este &#039;&#039;O(M x N)&#039;&#039; deoarece avem nevoie de matricea de distanțe, matricea de frecvențe și cea de puncte terminale, precum și memoria ocupată de coadă. În realitate, de cele mai multe ori putem unifica matricele suplimentare, unindu-le cu matricea labirint. În acest caz memoria suplimentară este cea a cozii. La momentul când scriu aceste rânduri nu am găsit o estimare asupra dimensiunii maxime a cozii necesare. Intuitiv ea este &#039;&#039;O(M + N)&#039;&#039;, deoarece frontiera, într-un labirint fără obstacole, când punctul de pornire este în mijloc, nu va depăși M+N. Este neclar dacă o altă matrice și un alt punct de pornire o poate face să depășească &#039;&#039;M + N&#039;&#039; și cu cât. Ca practică de implementare sfatul meu este să o dimensionați de &#039;&#039;2(M + N)&#039;&#039;, acoperitor, deoarece este un număr oricum mult mai mic decât matricea deja stocată, &#039;&#039;M x N&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Datorită consumului atât de mic de memorie suplimentară, algoritmul lui Lee poate fi folosit ca și ca algoritm de fill pe matrice mari, sau când memoria suplimentară este mică. El va fi însă mai lent decât &#039;&#039;flood fill&#039;&#039;, de obicei de multe ori (20-30 de ori), ceea ce dovedește încă o dată că, pentru unii algoritmi, memoria suplimentară se traduce în timp de execuție mai mic.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Aplicații ===&lt;br /&gt;
&lt;br /&gt;
Algoritmul lui Lee este necesar când dorim să aflăm informații despre drumuri și distanțe:&lt;br /&gt;
&lt;br /&gt;
* Găsirea ieșirii dintr-un labirint / castel / hartă.&lt;br /&gt;
* Găsirea celui mai scurt drum între două puncte dintr-o matrice.&lt;br /&gt;
* În lumea reală, proiectarea de circuite electronice.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple de probleme ce folosesc algoritmul lui Lee ===&lt;br /&gt;
&lt;br /&gt;
Orice problemă rezolvabilă prin &#039;&#039;flood fill&#039;&#039; este, desigur, rezolvabilă și cu &#039;&#039;Lee&#039;&#039;, folosind mai puțină memorie, dar consumând mai mult timp pentru executarea programului. N-am găsit foarte multe exemple de probleme ce necesită &#039;&#039;Lee&#039;&#039;, dar nu se pot rezolva cu &#039;&#039;flood fill&#039;&#039;. Iată câteva exemple:&lt;br /&gt;
&lt;br /&gt;
Exemple de probleme ce folosesc algoritmul lui Lee în rezolvări:&lt;br /&gt;
&lt;br /&gt;
La [https://www.nerdarena.ro/ NerdArena]:&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/alee Alee] dată la OJI 2007 clasa a 10-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/insule Insule] dată la OJI 2009 clasa a 10-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/miting Miting] dată la OJI 2016 clasa a 10-a (grea)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
La [http://infoarena.ro/ InfoArena]:&lt;br /&gt;
&lt;br /&gt;
* [https://www.infoarena.ro/algoritmul-lee Articolul de la Infoarena] include câteva exemple&lt;br /&gt;
* [https://www.infoarena.ro/problema/muzeu Muzeu] &lt;br /&gt;
* [https://www.infoarena.ro/problema/traseu3 Traseu3] dată la ONI 2014, clasa a 9-a (Lee 3D, relativ ușoară)&lt;br /&gt;
* [https://www.infoarena.ro/problema/gheizere Gheizere] dată la ONI 2012, clasa a 10-a (problemă grea)&lt;br /&gt;
* [https://www.infoarena.ro/problema/ai AI] dată la OJI 2011, clasa a 10-a (enunț complex)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 19 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/livada Livada] &lt;br /&gt;
* [https://www.nerdarena.ro/problema/alee Alee] alee dată la OJI 2007 clasa a 10-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/insule Insule] dată la OJI 2009 clasa a 10-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_19:_Cozi_%C8%99i_algoritmul_lui_Lee&amp;amp;action=edit&amp;amp;redlink=1 Accesează rezolvarea temei 19]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_18:_Divide_et_impera,_mergesort,_quicksort&amp;diff=18588</id>
		<title>Clasa a 7-a Lecția 18: Divide et impera, mergesort, quicksort</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_18:_Divide_et_impera,_mergesort,_quicksort&amp;diff=18588"/>
		<updated>2026-02-10T17:40:31Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/bgMxb050keA&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnici de programare: divide et impera ==&lt;br /&gt;
&lt;br /&gt;
Denumită și divide and conquer sau dezbină și stăpânește, este o tehnică cunoscută de mii de ani conducătorilor. Este o tehnică de cucerire sau menținere a puterii asupra unui grup care ar avea putere mai mare dacă s-ar uni. Ținând acel grup dezbinat, fiecare facțiune în parte are putere mică și poate fi cucerită, sau condusă. Tehnica a fost aplicată de Cezar, Napoleon, iar, mai recent, de către Britanici în Africa și de către Stalin în Rusia. Dintre tehnicile folosite în lumea reală putem menționa:&lt;br /&gt;
&lt;br /&gt;
* Ajutorarea și promovarea celor care cooperează cu puterea, pedepsirea celor ce nu cooperează.&lt;br /&gt;
* Încurajarea cheltuielilor prostești, pentru a reduce capitalul disponibil organizării sau luptei.&lt;br /&gt;
* Încurajarea divizării oamenilor pentru a preveni formarea alianțelor.&lt;br /&gt;
* Încurajarea turnătorilor, cei care trâmbițează orice alianță posibil periculoasă.&lt;br /&gt;
* Sădirea de neîncredere între oameni.&lt;br /&gt;
&lt;br /&gt;
O parte din aceste tehnici se regăsesc și în țara noastră, precum și în majoritatea țărilor lumii, inclusiv cele dezvoltate. De exemplu, toate țările lumii folosesc un număr uriaș de taxe și impozite, fiecare din ele relativ mici. Când le însumăm constatăm că în jur de 90% din banii noștri se duc pe aceste taxe și impozite. Dacă ar exista o taxă unică de 90%, oamenii s-ar revolta. Astfel, tehnica de împărțire a unor taxe mari în mai multe taxe mici îi face pe oameni să nu le observe și să nu se revolte.&lt;br /&gt;
&lt;br /&gt;
Pentru exemple clasice de folosire a acestei tehnici în lumea reală vizitați pagina de [http://en.wikipedia.org/wiki/Divide_and_rule divide et impera] a wikipediei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica divide et impera (divide and conquer) în informatică ==&lt;br /&gt;
&lt;br /&gt;
În informatică divide et impera constă în împărțirea problemei în subprobleme mai mici, nesuprapuse (o tehnică în care subproblemele sunt suprapuse este programarea dinamică). Fiecare din aceste subprobleme se rezolvă separat, mai ușor, deoarece sunt mai mici. Rezultatul final se obține din combinarea rezultatelor subproblemelor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica divide et impera simplă (decrease and conquer) ==&lt;br /&gt;
&lt;br /&gt;
În următoarele exemple de divide et impera problema se reduce la o singură problemă, mai mică. Este un caz degenerat, în care implementarea poate fi iterativă (nu necesită recursivitate).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Algoritmul lui Euclid &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Și acest bine cunoscut algoritm poate fi văzut ca un exemplu de divide et impera. El reduce problema de la &amp;lt;code&amp;gt;cmmdc(a, b)&amp;lt;/code&amp;gt; la o problemă mai mică, &amp;lt;code&amp;gt;cmmdc(b, a mod b)&amp;lt;/code&amp;gt;. Complexitate: &#039;&#039;O(log min(a, b))&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Căutare binară &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Precum știm, căutarea binară împarte vectorul original în două, apoi decide care din cele două jumătăți ar putea să conțină elementul căutat. Apoi rezolvă problema mai mică. Complexitate: &#039;&#039;O(log n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Căutare punct fix&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Se dă un vector sortat de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente întregi distincte. Să se găsească dacă există indicele &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; astfel încât &amp;lt;code&amp;gt;v[i] == i&amp;lt;/code&amp;gt;. Găsiți un algoritm divide et impera care are complexitate &#039;&#039;O(log n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Puzzle balanță &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Avem 25 de bile care arată identic, una este ceva mai ușoară. Avem la dispoziție o balanță. Găsiți bila mai ușoară din trei cântăriri.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Selecția &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Complexitate: pe medie &#039;&#039;O(n)&#039;&#039;, în cel mai rău caz &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Este necesară la problema [https://www.nerdarena.ro/problema/antitir AntiTir].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Probleme NerdArena ===&lt;br /&gt;
&lt;br /&gt;
Următoarele probleme de la [https://www.nerdarena.ro/ NerdArena] sunt de divide et impera degenerat:&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/z ZParcurgere]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/tabela Tabela]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/antitir AntiTir] dată la Shumen 2012&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica divide et impera propriu zisă ==&lt;br /&gt;
&lt;br /&gt;
În următoarele exemple de divide et impera problema se reduce la două sau mai multe probleme mai mici. Implementarea este mult mai simplă dacă folosim recursivitate.&lt;br /&gt;
&lt;br /&gt;
=== Puzzle pavare ===&lt;br /&gt;
&lt;br /&gt;
Se dă o grilă de 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; x 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; pătrate din care lipsește un pătrat. Să se acopere cu piese formate din 3 pătrate aranjate în formă de litera L.&lt;br /&gt;
&lt;br /&gt;
[[File:pav2.jpg|frame|none|Exemplu de pavare cu L-uri]]&lt;br /&gt;
&lt;br /&gt;
Problema pare destul de grea, nu? În realitate, odată ce încercăm să folosim divide et impera ea devine destul de ușoară. Cum putem împărți problema în probleme mai mici?&lt;br /&gt;
&lt;br /&gt;
Să definim o problemă astfel:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;P(n, gol)&#039;&#039; = pavează un pătrat de latură 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; care are un pătrățel lipsă (un gol).&lt;br /&gt;
&lt;br /&gt;
O primă idee ar fi să încercăm să împărțim pătratul în patru pătrate de latură 2n-1 și să rezolvăm acele probleme. Avem, însă, o dificultate: doar unul din pătrate va avea un pătrățel lipsă. Celelalte trei vor fi pline. Ce ne facem?&lt;br /&gt;
&lt;br /&gt;
Am putea încerca să eliminăm artificial câte un pătrățel din celelalte trei pătrate. Cum putem face acest lucru? Cu condiția ca cele trei pătrățele să fie în formă de L și să le acoperim cu o piesă elementară. Ei bine avem cum să alegem pătratele în acest fel: le vom selecta pe cele de la centru! Astfel vom obține subproblemele:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;P(n, l, c)&#039;&#039; se descompune în:&lt;br /&gt;
    &#039;&#039;P(n-1, gol)&#039;&#039; (golul original)&lt;br /&gt;
    &#039;&#039;P(n-1, gol1)&#039;&#039;&lt;br /&gt;
    &#039;&#039;P(n-1, gol2)&#039;&#039;&lt;br /&gt;
    &#039;&#039;P(n-1, gol3)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Mergesort ===&lt;br /&gt;
&lt;br /&gt;
Algoritmul &#039;&#039;mergesort&#039;&#039;, sau sortare prin interclasare, funcționează astfel:&lt;br /&gt;
&lt;br /&gt;
* Divide vectorul de sortat în două părți egale (sau aproape egale)&lt;br /&gt;
* Reapelează &#039;&#039;mergesort&#039;&#039; pe acele părți&lt;br /&gt;
* La revenire avem două jumătăți sortate, deci putem sorta vectorul interclasând cei doi vectori în timp liniar&lt;br /&gt;
&lt;br /&gt;
Iată un algoritm:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mergesort( V[], i, j )&lt;br /&gt;
  mij = (i + j) / 2;&lt;br /&gt;
  daca i &amp;lt; mij&lt;br /&gt;
    mergesort( V[], i, mij )&lt;br /&gt;
  daca mij + 1 &amp;lt; j&lt;br /&gt;
    mergesort( V[], mij + 1, j )&lt;br /&gt;
  merge( V[], i, mij, j )&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;merge( V[], i, mij, j )&amp;lt;/code&amp;gt; interclasează vectorii sortați &amp;lt;code&amp;gt;V[i..mij]&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;V[mij+1..j]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are &#039;&#039;mergesort&#039;&#039;?&lt;br /&gt;
&lt;br /&gt;
Să calculăm numărul de operații, T(N), pentru a sorta un vector de N elemente:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T(N) = 2 · T(N/2) + N&lt;br /&gt;
T(N) = 2 · (2 · T(N/4) + N/2) + N&lt;br /&gt;
T(N) = 4 · T(N/4) + 2 · N&lt;br /&gt;
T(N) = 4 · (2 · T(N/8) + N/4) + 2 · N&lt;br /&gt;
T(N) = 8 · T(N/8) + 3 · N&lt;br /&gt;
...&lt;br /&gt;
T(N) = N · T(N/N) + log N · N&lt;br /&gt;
T(N) = N · T(1) + log N · N&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Deoarece &#039;&#039;T(1)&#039;&#039; este &#039;&#039;O(1)&#039;&#039; rezultă o complexitate &#039;&#039;O(N + N log N) = O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Câtă memorie suplimentară folosește algoritmul?&lt;br /&gt;
&lt;br /&gt;
Pentru interclasarea clasică avem nevoie de un alt vector, deci &#039;&#039;O(N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În concluzie avem complexitățile:&lt;br /&gt;
&lt;br /&gt;
* timp: &#039;&#039;O(n log n)&#039;&#039;&lt;br /&gt;
* memorie suplimentară: &#039;&#039;O(n)&#039;&#039; (aceasta e problema principală a algoritmului).&lt;br /&gt;
&lt;br /&gt;
Există variante ale algoritmului care folosesc mai puțină memorie (o idee evidentă este să folosim doar jumate din memorie deoarece în timpul interclasării putem suprascrie una din jumătăți).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Aplicație mergesort - inversiunile unei permutări ===&lt;br /&gt;
&lt;br /&gt;
O problemă care apare uneori la concursuri este următoarea: se dă o permutare a numerelor de la 1 la &#039;&#039;N&#039;&#039;. Să se numere câte inversiuni are ea. O inversiune este o pereche de indici &#039;&#039;(i, j)&#039;&#039; astfel încât  &amp;lt;code&amp;gt;i &amp;lt; j&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;P[i] &amp;gt; P[j]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
În linii mari, algoritmul este următorul:&lt;br /&gt;
&lt;br /&gt;
* Sortăm prima jumate a vectorului folosind un &#039;&#039;mergesort&#039;&#039; special, care ne returnează numărul de inversiuni din vectorul de sortat.&lt;br /&gt;
* Sortăm a doua jumate a vectorului.&lt;br /&gt;
* În acest moment știm inversiunile din cele două jumătăți - le adunăm.&lt;br /&gt;
* Interclasăm cele două jumătăți. În timpul interclasării când selectăm un număr din prima jumătate știm că numerele deja selectate din jumătatea a doua sunt mai mici, deci putem aduna numărul lor la numărul total de inversiuni.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Quicksort ===&lt;br /&gt;
&lt;br /&gt;
Algoritmul quicksort, sau sortare rapidă, inventat în 1959 de informaticianul britanic Tony Hoare, funcționează astfel:&lt;br /&gt;
&lt;br /&gt;
* Alege o poziție la întâmplare din vectorul de sortat. Vom denumi valoarea de la acea poziție pivot.&lt;br /&gt;
* Rearanjează vectorul astfel încât în prima parte să avem elemente mai mici sau egale cu pivotul iar în a doua parte elemente mai mari sau egale cu pivotul. Vom denumi acest pas pivotare.&lt;br /&gt;
* Reapelează quicksort pe acele două părți.&lt;br /&gt;
&lt;br /&gt;
Din acest algoritm avem de lămurit pasul de pivotare. El este același ca la quickselect, prezentat într-o lecție anterioară. El procedează astfel:&lt;br /&gt;
&lt;br /&gt;
* Pornește cu doi indici, unul la începutul vectorului și unul la final de vector, să le spunem &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Avansează indicele b până la primul element mai mare sau egal cu pivotul.&lt;br /&gt;
* Devansează (merge înapoi) indicele e până la primul element mai mic sau egal cu pivotul.&lt;br /&gt;
* Interschimbă elementele din vector de la pozițiile &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Repetă până ce indicii se ating, &amp;lt;code&amp;gt;b &amp;gt;= e&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată o implementare posibilă. Ea este implementarea originală a lui Hoare, transformată pentru a fi structurată:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void myqsort( int begin, int end ) {&lt;br /&gt;
  int aux, b = begin, e = end,&lt;br /&gt;
    pivot = v[(begin + end) / 2]; // poate fi orice pozitie intre begin si end - 1&lt;br /&gt;
&lt;br /&gt;
  while (v[b] &amp;lt; pivot) // cauta primul element mai mare sau egal cu pivotul&lt;br /&gt;
    b++;&lt;br /&gt;
&lt;br /&gt;
  while (v[e] &amp;gt; pivot) // cauta primul element mai mic sau egal cu pivotul&lt;br /&gt;
    e--;&lt;br /&gt;
&lt;br /&gt;
  while( b &amp;lt; e ) { // daca indicii nu s-au atins&lt;br /&gt;
    aux = v[b];    // interschimba elementele la pozitiile b si e&lt;br /&gt;
    v[b] = v[e];&lt;br /&gt;
    v[e] = aux;&lt;br /&gt;
    &lt;br /&gt;
    do // cauta primul element mai mare sau egal cu pivotul&lt;br /&gt;
      b++;&lt;br /&gt;
    while (v[b] &amp;lt; pivot);&lt;br /&gt;
&lt;br /&gt;
    do // cauta primul element mai mic sau egal cu pivotul&lt;br /&gt;
      e--;&lt;br /&gt;
    while (v[e] &amp;gt; pivot);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // acum [begin..e] sunt mai mici sau egale cu pivotul&lt;br /&gt;
  if ( begin &amp;lt; e )&lt;br /&gt;
    myqsort(begin, e);&lt;br /&gt;
  // si [e+1..end] sunt mai mari sau egale cu pivotul&lt;br /&gt;
  if ( e + 1 &amp;lt; end )&lt;br /&gt;
    myqsort(e + 1, end);&lt;br /&gt;
} &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are quicksort?&lt;br /&gt;
&lt;br /&gt;
Deoarece pivotarea este liniară, calculele sunt similare cu cele de la &#039;&#039;mergesort&#039;&#039;. Avem două diferențe:&lt;br /&gt;
&lt;br /&gt;
* Procesarea liniară se face la început, înainte de recursie.&lt;br /&gt;
* Împărțirea vectorului nu este neapărat în două părți egale, ceea ce duce la o complexitate &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; pe cazul cel mai rău.&lt;br /&gt;
&lt;br /&gt;
Pentru cazul mediu, când împărțirea este în două părți aproximativ egale, rezultă aceeași complexitate &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Câtă memorie suplimentară folosește algoritmul?&lt;br /&gt;
&lt;br /&gt;
Memoria este cea necesară stivei. Ea va fi, deci, proporțională cu cel mai lung lanț de apeluri recursive imbricate. Pe cazul cel mai rău el este &#039;&#039;O(N)&#039;&#039;, iar pe cazul mediu este &#039;&#039;O(log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimizare:&#039;&#039;&#039; putem reduce cazul cel mai rău astfel:&lt;br /&gt;
&lt;br /&gt;
* Observăm că al doilea apel recursiv este recursiv la coadă.&lt;br /&gt;
* Precum știm, el nu va consuma memorie.&lt;br /&gt;
* Putem reduce memoria stivei apelând mai întâi pentru partea mai mică ce rezultă după pivotare.&lt;br /&gt;
&lt;br /&gt;
În acest fel chiar și în cazul cel mai rău memoria suplimentară necesară va fi &#039;&#039;O(log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În concluzie avem complexitățile:&lt;br /&gt;
&lt;br /&gt;
* timp: &#039;&#039;O(N log N)&#039;&#039;&lt;br /&gt;
* memorie suplimentară: &#039;&#039;O(log N)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; deși putem selecta ca pivot orice element din vector, nu putem selecta ca pivot ultimul element. Aceasta ar duce la o buclă infinită.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Pivotarea Lomuto ===&lt;br /&gt;
&lt;br /&gt;
În școli, de multe ori se predă un alt algoritm de pivotare, probabil deoarece este considerat mai simplu. Nu o vom discuta aici, deoarece este foarte ineficientă. Iată diferențele față de pivotarea Hoare:&lt;br /&gt;
&lt;br /&gt;
* Lomuto este ceva mai simplu de reținut și ceva mai scurtă.&lt;br /&gt;
* Ambii algoritmi sunt cache friendly deoarece parcurg liniar vectorul.&lt;br /&gt;
* Lomuto face de trei ori mai multe interschimburi.&lt;br /&gt;
* Lomuto face &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; operații pe un vector sortat, Hoare doar &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
* Lomuto face &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; operații pe un vector cu elemente egale, Hoare doar &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie&#039;&#039;&#039;: Pivotarea Lomuto este un algoritm didactic, bun pentru înțelegerea pivotării, dar este contraindicat într-o implementare practică.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Randomizarea pivotului &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Dacă alegem ca pivot prima poziție din vector, sau cea din mijloc, &#039;&#039;adversarul&#039;&#039; poate, studiind algoritmul, să găsească un vector special pe care implementarea noastră să intre pe cazul cel mai rău, &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Putem lupta cu aceasta, alegând pivotul pe o poziție aleatoare din intervalul &amp;lt;code&amp;gt;[begin..end-1]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Probleme quicksort &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție!&#039;&#039;&#039; Algoritmul este celebru pentru bug-urile pe care le putem introduce la implementare. Un semn mai mic transformat în mai mic sau egal poate duce la bucle infinite sau la un vector nesortat.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție&#039;&#039;&#039;: pentru olimpiadă este bine să memorați algoritmul pe din-afară.&lt;br /&gt;
&lt;br /&gt;
Ați putea spune &#039;&#039;hei, dar la olimpiadă pot folosi algorithms!&#039;&#039; Este opțiunea voastră, dar eu nu aș risca. Motivele împotriva folosirii funcției de sortare de bibliotecă:&lt;br /&gt;
&lt;br /&gt;
* Ea primește ca parametru o funcție de comparație. Apelul acelei funcții este lent, fiind vorba de accesul unui pointer pentru a obține adresa funcției, apoi de punerea pe stivă a parametrilor. Toate acestea se reduc la o comparație sau două în codul nostru.&lt;br /&gt;
* Funcția de bibliotecă este o cutie neagră. Ea depinde de compilator și de biblioteca de funcții a acestuia. Ea este scrisă de alți oameni. Aveți mai multă încredere în cod scris de alții, care variază în funcție de calculatorul pe care se face evaluarea?&lt;br /&gt;
* În mod uzual funcțiile de bibliotecă consumă mai multă memorie, iar uneori mult mai multă. De ce? Bună întrebare. Nu știu.&lt;br /&gt;
* Algoritmul de mai sus are 23 de linii de cod efectiv. El este simplu și eficient și poate fi adaptat să sorteze orice tip de date, cu mici modificări. De ce am risca să apelăm o funcție de bibliotecă despre care nu știm nimic, când în 3 minute am scris acest cod?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ziua regelui ===&lt;br /&gt;
&lt;br /&gt;
Un rege organizează o petrecere mare de ziua lui. Petrecerea începe peste 24 de ore. Regele află că una din cele 1000 de sticle de vin pe care le va servi este otrăvită. Otrava acționează ciudat: omul care bea chiar și cea mai mică cantitate din această otravă nu are nici un simptom vreme de 24 de ore, apoi moare subit. Regele are sclavi la dispoziție pe care îi poate folosi. Lui nu îi pasă câți sclavi mor, dar îi pasă câți sclavi beau vin, deoarece un sclav care a băut vin nu poate fi folosit la treabă, ci trebuie izolat spre observare. Care este numărul minim de sclavi cu care regele poate afla sticla otrăvită?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 18 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/latin Latin] dată la cercul de informatică Vianu, gimnaziu 2013&lt;br /&gt;
* [https://www.nerdarena.ro/problema/pav Pav] dată la Lot IS 2008&lt;br /&gt;
* [https://www.nerdarena.ro/problema/forma Forma] dată la olimpiada pe sector 2012, clasa a 8-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/z ZParcurgere]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/tabela Tabela]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/antitir AntiTir] dată la Shumen 2012&lt;br /&gt;
* [https://www.nerdarena.ro/problema/hanoi1 Hanoi1]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_VII-a_lec%C8%9Bia_6_-_17_oct_2019&amp;diff=18587</id>
		<title>Clasa a VII-a lecția 6 - 17 oct 2019</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_VII-a_lec%C8%9Bia_6_-_17_oct_2019&amp;diff=18587"/>
		<updated>2026-02-07T10:13:53Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Tema - rezolvări =&lt;br /&gt;
== Intervale ==&lt;br /&gt;
Problema [http://varena.ro/problema/intervale intervale] este o problemă de precalculare. Ea cere să se răspundă la maxim 100 000 de întrebări de forma &amp;quot;cîte numere naturale cu exact &#039;&#039;K&#039;&#039; divizori primi se află în intervalul &#039;&#039;[A, B]&#039;&#039;&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Comentarii generale ===&lt;br /&gt;
* Mențiuni speciale lui &#039;&#039;&#039;Mircea Rebengiuc&#039;&#039;&#039; și &#039;&#039;&#039;Teo Tatomir&#039;&#039;&#039;, singurii care a reușit o soluție optimă, ce folosește două precalculări și liste!&lt;br /&gt;
* Felicitări celor ce au reușit o soluție foarte bună și ordonată: &#039;&#039;&#039;Tudor Voicu&#039;&#039;&#039;, &#039;&#039;&#039;Fares&#039;&#039;&#039;, &#039;&#039;&#039;Ilie&#039;&#039;&#039;, &#039;&#039;&#039;Petrescu&#039;&#039;&#039;, &#039;&#039;&#039;Rebengiuc&#039;&#039;&#039;, &#039;&#039;&#039;Tatomir&#039;&#039;&#039;.&lt;br /&gt;
* Încă nu știți &#039;&#039;fscanf&#039;&#039;, rău! Majoritatea ați avut probleme la citire, dar v-ați descurcat cu o complicație.&lt;br /&gt;
* Mulți dintre voi ați declarat vectorul ciur de întregi. El putea fi de caractere. Este o risipă de 3MB de memorie, vă poate duce la 0p la problemă la olimpiadă. Mare atenție!&lt;br /&gt;
* Mulți dintre voi ați risipit 14% din memorie, un milion de întregi = 4MB. Cum? Memorînd un vector de sume parțiale cu elemente 1, pentru &#039;&#039;K=0&#039;&#039;. Nice :-)&lt;br /&gt;
* Unii dintre voi ați dat o rezolvare ineficientă, răspunzînd la întrebări prin parcurgerea ciurului de la &#039;&#039;A&#039;&#039; la &#039;&#039;B&#039;&#039;. Era clar că o asemenea soluție va depăși timpul. Trebuia să folosiți sume parțiale, precalculare, pentru a răspunde la întrebări în &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Comentarii individuale ===&lt;br /&gt;
==== Prima grupă ====&lt;br /&gt;
* &#039;&#039;&#039;Aizic&#039;&#039;&#039;: Program foarte bun, bravo! Observații minore: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Badea&#039;&#039;&#039;: Program foarte bun, bravo! Observații minore: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Benescu&#039;&#039;&#039;: Program foarte bun, bravo! Observație importantă: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica! Observații minore: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Burac&#039;&#039;&#039;: Program foarte bun, bravo! Observații minore: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Calotă&#039;&#039;&#039;: Program foarte bun, bravo! Observație importantă: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă! Observații minore: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri. &lt;br /&gt;
* &#039;&#039;&#039;Chivu&#039;&#039;&#039;: Ideea programului tău este foarte interesantă: memorezi întrebările pe liste după &#039;&#039;K&#039;&#039;. Pînă aici este foarte bine, deoarece folosești puțină memorie. Dar apoi ai o greșeală, nu observi că &#039;&#039;K&#039;&#039; este, în fapt, limitat la 7. Valoarea 1000 este aruncată la păcăleală. Atenție la matematică! Dar asta nu e grav, deoarece nu afectează algoritmul, doar ocupi ceva mai multă memorie în vectorul de liste. Algoritmul tău este ceva mai lent deoarece faci o căutare liniară în liste pentru fiecare element al ciurului. Trebuia să faci invers, să parcurgi listele și să răspunzi la întrebări în &#039;&#039;O(1)&#039;&#039;. Pentru aceasta aveai nevoie de sume parțiale pe vectorul ciur. Vezi soluția. Observații: ciurul nu este vector de întregi, ci de caractere! Atenție! puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3. Vectorii &#039;&#039;a[], b[], r[] și next[]&#039;&#039; au toți aceeași dimensiune, dar tu ai declarat &#039;&#039;a[]&#039;&#039; mult mai mare.&lt;br /&gt;
* &#039;&#039;&#039;Cojocariu&#039;&#039;&#039;: Program foarte bun, bravo! Observații minore: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri. &lt;br /&gt;
* &#039;&#039;&#039;Dobre&#039;&#039;&#039;: Program bun, bravo! Citire corectă și simplă cu &#039;&#039;fscanf&#039;&#039;, bravo! Observație importantă: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă! Tu nu folosești linia 0 a matricei &#039;&#039;v[]&#039;&#039;. Deci risipeși 14% din memorie. Cam urît, nu? Te-ai complicat și la afișare, nu ai de ce să testezi dacă &#039;&#039;ciur[a]==k&#039;&#039;. În sumele parțiale scădem elementul din-naintea capătului de interval, adică &#039;&#039;v[k][a-1]&#039;&#039;. Motivul pentru care ratezi un test este pentru că te oprești cu calculul sumelor parțiale la &#039;&#039;BMAX-1&#039;&#039;. Trebuia să mergi pînă la &#039;&#039;BMAX&#039;&#039; inclusiv! Atenție la neatenție, te costă!&lt;br /&gt;
* &#039;&#039;&#039;Hossu&#039;&#039;&#039;: Programul este OK ca idee, dar, precum ți-ai dat seama, are complexitate prea mare. Pentru a răspunde la o întrebare parcurgi ciurul de la &#039;&#039;a&#039;&#039; la &#039;&#039;b&#039;&#039;, complexitate &#039;&#039;O(MAXB)&#039;&#039;, adică circa un milion. Trebuia să folosești sume parțiale pentru a răspunde la întrebări în &#039;&#039;O(1)&#039;&#039;. Citire corectă și simplă cu &#039;&#039;fscanf&#039;&#039;, bravo! Observație importantă: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă!&lt;br /&gt;
* &#039;&#039;&#039;Iordache&#039;&#039;&#039;: Program foarte bun, bravo! Observații minore: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, fără &#039;&#039;feof()&#039;&#039;, care este periculos de folosit (de aceea nici nu l-am predat). Citeai trei valori și întrebai dacă valoarea de return este 3. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Mocanu&#039;&#039;&#039;: Program foarte bun, bravo! Observații minore: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, fără &#039;&#039;feof()&#039;&#039;, care este periculos de folosit (de aceea nici nu l-am predat). Citeai trei valori și întrebai dacă valoarea de return este 3. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Mușat&#039;&#039;&#039;: Program bun, păcat că dimensionezi greșit matricea de sume parțiale. Interesant că iei 100p. &#039;&#039;K&#039;&#039; poate fi maxim 1000, iar tu ai dimensionat matricea de 9 elemente, deci ieși cu mult afară din ea. Observație importantă: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă! Puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3.&lt;br /&gt;
* &#039;&#039;&#039;Nicola&#039;&#039;&#039;: Program foarte bun, bravo! Observații minore: ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri. Observație majoră: programul nu funcționează corect deoarece ciurul se oprește la &#039;&#039;i * i &amp;lt;= n&#039;&#039;. Trebuia să mergi pînă la &#039;&#039;n&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Petcu&#039;&#039;&#039;: Program foarte bun, bravo! Observație minoră: ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri. Observație majoră: numărul maxim de divizori este 7, nu 9! Matematica!&lt;br /&gt;
* &#039;&#039;&#039;Ștefănescu&#039;&#039;&#039;: Program bun, bravo! Observații minore: te-ai complicat cu citirea. Puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri. Observație majoră: programul nu funcționează corect deoarece ciurul se oprește la &#039;&#039;d*d&amp;lt;=1000000&#039;&#039;. Trebuia să mergi pînă la &#039;&#039;d&amp;lt;=1000000&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Togan&#039;&#039;&#039;: Program foarte bun, bravo! Citire corectă și simplă cu &#039;&#039;fscanf&#039;&#039;, bravo! Observație minoră: ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri. Observație majoră: la final, la afișări, testezi &#039;&#039;k&amp;lt;9&#039;&#039;, în vreme ce peste tot în restul programului ai &#039;&#039;k&amp;lt;8&#039;&#039;. De mirare că nu pierzi puncte! Folosește constante pentru ca nu te mai încurca!&lt;br /&gt;
* &#039;&#039;&#039;Voicu&#039;&#039;&#039;: Program foarte bun, bravo!&lt;br /&gt;
&lt;br /&gt;
==== A doua grupă ====&lt;br /&gt;
* &#039;&#039;&#039;Asgari&#039;&#039;&#039;: Program foarte bun, bravo! Observații minore: citirea cu &#039;&#039;fgetc()&#039;&#039; e o idee bună, pentru viteză. Dar e ineficientă. Pe de o parte folosești &#039;&#039;isdigit()&#039;&#039; pentru viteză, pe de altă parte testezi în buclă dacă dai de EOF. Există și o funcție &#039;&#039;isspace()&#039;&#039; pentru a sări peste caractere spațiu și \n. Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri. De ce ai mai rezolvat separat intervale, dacă ai rezolvat reginald?&lt;br /&gt;
* &#039;&#039;&#039;Cadîr&#039;&#039;&#039;: Nu închizi fișiere! Foarte urît! Observație importantă: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă! Programul este OK ca idee, dar, precum ți-ai dat seama, are complexitate prea mare. Pentru a răspunde la o întrebare parcurgi ciurul de la &#039;&#039;a&#039;&#039; la &#039;&#039;b&#039;&#039;, complexitate &#039;&#039;O(MAXB)&#039;&#039;, adică circa un milion. Trebuia să folosești sume parțiale pentru a răspunde la întrebări în &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Dimulescu&#039;&#039;&#039;: Indentarea e bună, variabilele au denumiri bune, bravo, bun! Observații importante: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă! Doi, calculezi ciurul pînă la 500 000. Trebuia să îl calculezi pînă la 1 000 000. Programul este OK ca idee, dar, precum ți-ai dat seama, are complexitate prea mare. Pentru a răspunde la o întrebare parcurgi ciurul de la &#039;&#039;a&#039;&#039; la &#039;&#039;b&#039;&#039;, complexitate &#039;&#039;O(MAXB)&#039;&#039;, adică circa un milion. Trebuia să folosești sume parțiale pentru a răspunde la întrebări în &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Fares&#039;&#039;&#039;: Program foarte bun, bravo! Ai folosit soluția de la reginald ;-) Observații minore: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este 3.&lt;br /&gt;
* &#039;&#039;&#039;Ghica&#039;&#039;&#039;: Program foarte bun, bravo! Citire corectă și simplă cu &#039;&#039;fscanf&#039;&#039;, bravo! Atenție, nu închizi fișierul output! Numărul maxim de divizori este 7, nu 9, atenție la dimensionări! Atenție, &#039;&#039;k&#039;&#039; este maxim 1000! Indicele va depăși matricea &#039;&#039;v[][]&#039;&#039;! De mirare că ai luat 100p. Trebuia să testezi dacă valoarea lui &#039;&#039;k&#039;&#039; depășește 9, caz în care răspundeai cu 0.&lt;br /&gt;
* &#039;&#039;&#039;Grecu&#039;&#039;&#039;: Program foarte bun, bravo! Observație importantă: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă! Doi, ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Ilie&#039;&#039;&#039;: Program foarte bun, bravo! Ai folosit soluția de la reginald ;-)&lt;br /&gt;
* &#039;&#039;&#039;Ipate&#039;&#039;&#039;: Program foarte bun, bravo! Nu închizi fișiere! Ne supărăm! Ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Marcu&#039;&#039;&#039;: Observații importante: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă! Programul este OK ca idee, dar, precum ți-ai dat seama, are complexitate prea mare. Pentru a răspunde la o întrebare parcurgi ciurul de la &#039;&#039;a&#039;&#039; la &#039;&#039;b&#039;&#039;, complexitate &#039;&#039;O(MAXB)&#039;&#039;, adică circa un milion. Trebuia să folosești sume parțiale pentru a răspunde la întrebări în &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Moroșan&#039;&#039;&#039;: Soluție forță brută, care nu folosește nici ciurul lui Eratostene, nici sume parțiale pe vector. În plus, nici nu este corectă.&lt;br /&gt;
* &#039;&#039;&#039;Nicu&#039;&#039;&#039;: Program foarte bun, bravo! Observație minoră: ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Petrescu&#039;&#039;&#039;: Program foarte bun, bravo! Observație minoră: puteai să faci citirea cu un singur &#039;&#039;fscanf&#039;&#039;, citind trei valori și întrebînd dacă valoarea de return este EOF, sau 3.&lt;br /&gt;
* &#039;&#039;&#039;Popescu&#039;&#039;&#039;: &amp;lt;strike&amp;gt;Te rog nu trimite soluții la arhivă! Atenție!&amp;lt;/strike&amp;gt; &#039;&#039;(le-ai trimis înainte să înceapă tema, nu ești un bun oracol, nu prevezi viitorul - sau îl prevezi prea bine :-)&#039;&#039; Program foarte bun, bravo! Ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori (așa cum ai și testat în program). Ai risipit 14% din memorie stocînd un vector cu zerouri.&lt;br /&gt;
* &#039;&#039;&#039;Rebengiuc&#039;&#039;&#039;: Rezolvare excepțională, bravo! Optimă, folosind rezolvarea de la reginald. Atenție la readInt(). Dacă vrei să fii eficient trebuie să folosești &#039;&#039;isdigit()&#039;&#039;, iar, în cazul acesta și &#039;&#039;isspace()&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Rughiniș&#039;&#039;&#039;: Observație importantă: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă! Programul este OK ca idee, dar, precum ți-ai dat seama, are complexitate prea mare. Pentru a răspunde la o întrebare parcurgi ciurul de la &#039;&#039;a&#039;&#039; la &#039;&#039;b&#039;&#039;, complexitate &#039;&#039;O(MAXB)&#039;&#039;, adică circa un milion. Trebuia să folosești sume parțiale pentru a răspunde la întrebări în &#039;&#039;O(1)&#039;&#039;. Măcar dacă programul ar fi funcționat...&lt;br /&gt;
* &#039;&#039;&#039;Stancu&#039;&#039;&#039;: Soluție care nu folosește ciurul lui Eratostene decît pentru calculul numerelor prime, nu pentru calculul numărului de divizori primi. Nu folosește nici sume parțiale pe vector. David, tu poți mai mult de atît, dă-ți te rog silința. Rezolvarea asta nu îți face cinste.&lt;br /&gt;
* &#039;&#039;&#039;Tatomir&#039;&#039;&#039;: Rezolvare excepțională, bravo! Optimă, folosind soluția de la Reginald. Te-ai chinuit puțin la citire, recapitulează &#039;&#039;fscanf()&#039;&#039;. Vezi ce înseamnă să nu folosești constante? Ai modificat una din limite, dar nu și a doua, numărul de întrebări nu este un milion, ci 100 000.&lt;br /&gt;
* &#039;&#039;&#039;Teodorescu&#039;&#039;&#039;: Program foarte bun, bravo! Citire corectă și simplă cu &#039;&#039;fscanf&#039;&#039;, bravo! Observație importantă: ciurul putea fi de tip caracter, dar tu ai folosit tip întreg! Atenție la genul acesta de erori, te pot descalifica la olimpiadă! Ai folosit o linie a matricei pentru &#039;&#039;K=0&#039;&#039;, cînd doar 1 va avea 0 divizori. Ai risipit 14% din memorie stocînd un vector cu unu-uri.&lt;br /&gt;
* &#039;&#039;&#039;Voicu&#039;&#039;&#039;: Program bun, bravo! Îmi place că te gîndești la optimizări. Dar înainte să te apuci de ele, ce-ar fi să optimizezi lucrurile simple? Cum ar fi faptul că ciurul este un vector de elemente caracter, nu întregi! Sau cum ar fi faptul că tu folosești o matrice cu o linie în plus: linia zero nu este folosită. Asta duce la o risipă de memorie de 14%! Ideea de memoizare, calcul la cerere, nu e rea! Te-ai complicat la calculul diferențelor de sume parțiale, ai scris:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
                _included=0;&lt;br /&gt;
                if(part[k][a]!=part[k][a-1])&lt;br /&gt;
                    _included=1;///astfel, a se include in [a,b]&lt;br /&gt;
                fprintf(out,&amp;quot;%d\n&amp;quot;,_included+part[k][b]-part[k][a]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dacă ai fi scris cum am învățat la oră, era deajuns să scrii:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
                fprintf(out,&amp;quot;%d\n&amp;quot;, part[k][b]-part[k][a-1]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Mihai, încearcă să aplici întîi cunoștințele de bază. Nu te complica. Abia la final optimizezi.&lt;br /&gt;
&lt;br /&gt;
== Pătrate1 ==&lt;br /&gt;
Problema [http://varena.ro/problema/patrate1 pătrate1] de la vianuarena cere ca, dîndu-se o matrice pătrată de latură maxim 200 cu elemente 0 și 1, să se calculeze numărul de submatrice pătrate pline cu elemente 1.&lt;br /&gt;
&lt;br /&gt;
=== Comentarii generale ===&lt;br /&gt;
* Avertismente: &#039;&#039;&#039;Cadîr&#039;&#039;&#039; (nici o sursă).&lt;br /&gt;
&lt;br /&gt;
== Extratereștri ==&lt;br /&gt;
=== Comentarii generale ===&lt;br /&gt;
* Avertismente: &#039;&#039;&#039;Nicola&#039;&#039;&#039; (nici o sursă).&lt;br /&gt;
&lt;br /&gt;
= Tema opțională - rezolvări =&lt;br /&gt;
== Reginald ==&lt;br /&gt;
Problema [http://varena.ro/problema/reginald Reginald] este exact problema [http://varena.ro/problema/intervale intervale], cu mai puţină memorie disponibilă. Mai exact, nu mai putem stoca matricea de sume parțiale. Capetele intervalelor sînt maxim 4 milioane în loc de 1 milion, iar numărul de întrebări este 1 milion în loc de 100 000.&lt;br /&gt;
&lt;br /&gt;
== Flori 1 ==&lt;br /&gt;
Problema [http://varena.ro/problema/flori1 flori 1] este intenţionată ca o continuare a problemei [http://varena.ro/problema/flori flori], în care memoria a fost restricţionată drastic, de la 16MB la 2.5MB.&lt;br /&gt;
&lt;br /&gt;
= Tema opțională de gîndire - discuții =&lt;br /&gt;
Am discutat idei pentru cele două probleme de gîndire.&lt;br /&gt;
&lt;br /&gt;
Rezolvări aici [http://solpedia.francu.com/wiki/index.php/Clasa_a_VII-a_lec%C8%9Bia_5_-_10_oct_2019]&lt;br /&gt;
&lt;br /&gt;
= Lecție: recursivitate =&lt;br /&gt;
&lt;br /&gt;
&amp;lt;html5media height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.algopedia.ro/video/2019-2020/2019-10-17-clasa-7-lectie-info-06-720p.mp4&amp;lt;/html5media&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
O funcție este recursivă dacă se autoapelează. A scrie o funcție recursivă necesită, inițial, o încredere în faptul că funcția funcționează. În fapt, cînd pornim să scriem o funcție recursivă este bine să considerăm că ea deja funcționează!&lt;br /&gt;
&lt;br /&gt;
Reguli de scriere a unei funcții recursive:&lt;br /&gt;
* Înainte de a scrie cod este bine să ne clarificăm formula recurentă.&lt;br /&gt;
* Tratăm mai întîi cazurile simple, atunci cînd funcția poate returna o valoare fără a se autoapela.&lt;br /&gt;
* În final tratăm cazul de recursie, considerînd că funcția este deja scrisă pentru cazurile &amp;quot;mai mici&amp;quot;, folosindu-ne de formula recurentă găsită la început.&lt;br /&gt;
&lt;br /&gt;
== Exemple introductive ==&lt;br /&gt;
=== Factorial ===&lt;br /&gt;
Calcul n! Ne vom folosi de definiția recursivă: &amp;lt;math&amp;gt;n! = \begin{cases} 1, &amp;amp; \mbox{dacă }  n = 1  \\ n \cdot (n - 1)!,  &amp;amp; \mbox{dacă } n &amp;gt; 1. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;int fact( int n ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return n * fact( n - 1 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Putere ===&lt;br /&gt;
Calcul a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;. Ne vom folosi de definiția recurentă: &amp;lt;math&amp;gt;a^{n} = \begin{cases} 1, &amp;amp; \mbox{dacă }  n = 0  \\ a \cdot a^{n-1},  &amp;amp; \mbox{dacă } n &amp;gt; 0. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;int putere( int a, int n ) {&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return a * putere( a, n - 1 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Cmmdc ===&lt;br /&gt;
Calcul cmmdc a două numere. Ne vom folosi de definiția recursivă a lui Euclid: &amp;lt;math&amp;gt;cmmdc(a, b) = \begin{cases} a, &amp;amp; \mbox{dacă }  b = 0  \\ cmmdc(b, a \bmod b),  &amp;amp; \mbox{dacă } b &amp;gt; 0. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;int cmmdc( int a, int b ) {&lt;br /&gt;
  if ( b == 0 )&lt;br /&gt;
    return a;&lt;br /&gt;
  return cmmdc( b, a % b );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Exerciții ==&lt;br /&gt;
Încercați în clasă să scrieți următoarele funcții recursive.&lt;br /&gt;
=== Fibonacci ===&lt;br /&gt;
Să se scrie o funcție recursivă care, dat &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt;, calculează al &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt;-lea termen din șirul lui Fibonacci: &amp;lt;tt&amp;gt;0 1 1 2 3 5 8 13...&amp;lt;/tt&amp;gt;. Ne vom folosi de definiția recurentă: &amp;lt;math&amp;gt;fib(n) = \begin{cases} 0, &amp;amp; \mbox{dacă }  n = 1 \\ 1, &amp;amp; \mbox{dacă }  n = 2 \\ fib(n-1) + fib(n-2),  &amp;amp; \mbox{dacă } n &amp;gt; 2. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;int fib( int n ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return 0;&lt;br /&gt;
  if ( n == 2 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return fib( n - 1 ) + fib ( n - 2 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Suma cifrelor unui număr ===&lt;br /&gt;
Să se scrie o funcție recursivă care calculează suma cifrelor unui număr &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ne vom folosi de următoarea formulă recurentă: &amp;lt;math&amp;gt;suma(n) = \begin{cases} 0, &amp;amp; \mbox{dacă }  n = 0  \\ n % 10 + suma(n / 10),  &amp;amp; \mbox{dacă } n &amp;gt; 0. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;int sumac( int n ) {&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 0;&lt;br /&gt;
  return n % 10 + sumac( n / 10 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Putere în O(log n) ===&lt;br /&gt;
Calculați &amp;lt;tt&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt; în mod cît mai eficient (a și n numere naturale). Problema este cunoscută şi sub numele de ridicare la putere în timp logaritmic. Ideea din spatele acestei rezolvări este următoarea:&lt;br /&gt;
&lt;br /&gt;
*Dacă &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; este par, atunci &amp;lt;tt&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; = a&amp;lt;sup&amp;gt;2*n/2&amp;lt;/sup&amp;gt; = (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;n/2&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
*Dacă &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; este impar, atunci &amp;lt;tt&amp;gt;n-1&amp;lt;/tt&amp;gt; este par și avem &amp;lt;tt&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; = a * a&amp;lt;sup&amp;gt;n-1&amp;lt;/sup&amp;gt; =  a * a&amp;lt;sup&amp;gt;2*(n-1)/2&amp;lt;/sup&amp;gt; = a * (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;(n-1)/2&amp;lt;/sup&amp;gt; = a * (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;n/2&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Rezultă formula de recurență:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;a^{n} = \begin{cases} 1, &amp;amp; \mbox{dacă }  n = 0 \\ {(a^{2})}^{n/2},  &amp;amp; \mbox{dacă } n &amp;gt;0, n % 2 = 0 \\ a \times {(a^{2})}^{n/2},  &amp;amp; \mbox{dacă } n &amp;gt; 0, n % 2 = 1 \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
În formulele de mai sus am considerat că &amp;lt;tt&amp;gt;/&amp;lt;/tt&amp;gt; este împărțirea întreagă din limbajul C. Se observă că indiferent de paritatea lui &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt;, la fiecare pas al iterației putem transforma &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; în &amp;lt;tt&amp;gt;a * a&amp;lt;/tt&amp;gt; și apoi putem împărți &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; la &amp;lt;tt&amp;gt;2&amp;lt;/tt&amp;gt;. Doar în cazurile cînd &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; este impar vom acumula valoarea curentă a lui &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt; la produsul calculat. Iată soluția bazată pe această idee:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;int putere( int a, int n ) {&lt;br /&gt;
  int p;&lt;br /&gt;
&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  p =  putere( a * a, n / 2 );&lt;br /&gt;
  if ( n % 2 ) // n impar?&lt;br /&gt;
    p *= a;&lt;br /&gt;
&lt;br /&gt;
  return p;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Afișare ===&lt;br /&gt;
Să se afișeze un șir de caractere în ordine inversă. Șirul se termină cu &amp;lt;tt&amp;gt;&#039;\n&#039;&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ne vom folosi de următoarea idee: citim un caracter, apoi chemăm funcția recursivă pentru a afișa restul caracterelor, apoi afișăm caracterul.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;void afisare( FILE *fin, FILE *fout ) {&lt;br /&gt;
  char ch;&lt;br /&gt;
&lt;br /&gt;
  ch = fgetc( fin );&lt;br /&gt;
  if ( ch != &#039;\n&#039; ) {&lt;br /&gt;
    afisare( fin, fout );&lt;br /&gt;
    fputc( ch, fout );&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Descompunere în baza 2 ===&lt;br /&gt;
Să se scrie o funcție recursivă care, dat &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt;, îl afișează în baza 2. &lt;br /&gt;
&lt;br /&gt;
Similar cu exercițiul precedent, vom calcula ultima cifră a descompunerii (restul împărțirii la doi), apoi vom chema funcția cu &amp;lt;tt&amp;gt;n / 2&amp;lt;/tt&amp;gt;, iar la revenire vom afișa cifra.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;void baza2( int n, FILE *fout ) {&lt;br /&gt;
  int cf2;&lt;br /&gt;
&lt;br /&gt;
  if ( n &amp;gt; 0 ) {&lt;br /&gt;
    cf2 = n % 2;&lt;br /&gt;
    baza2( n / 2, fout );&lt;br /&gt;
    fprintf( fout, &amp;quot;%d&amp;quot;, cf2 );&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Interclasare vectori ordonați ===&lt;br /&gt;
Dîndu-se doi vectori ordonați să se calculeze un al treilea vector ordonat ce conține elementele lor.&lt;br /&gt;
&lt;br /&gt;
Funcția va primi ca parametri cei trei vectori și pozițiile curente în ei (inițial pozițiile de start, 0). La fiecare apel va alege un element pe care îl va copia. Apoi se va reapela cu indici actualizați (i1 și i3 sau i2 și i3).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;void interclaseaza( int n1, int v1[], int n2, int v2[], int v3[],&lt;br /&gt;
                   int i1, int i2, int i3 ) {&lt;br /&gt;
  if ( i1 &amp;lt; n1 ) { // mai avem elemente in primul vector?&lt;br /&gt;
    if ( i2 &amp;lt; n2 &amp;amp;&amp;amp; v2[i2] &amp;lt; v1[i1] ) { // v2[i2] exista si e mai mic?&lt;br /&gt;
      v3[i3] = v2[i2];                  // copiem v2[i1]&lt;br /&gt;
      interclaseaza( n1, v1, n2, v2, v3, i1, i2 + 1, i3 + 1 );&lt;br /&gt;
    } else {&lt;br /&gt;
      v3[i3] = v1[i1];                  // copiem v1[i1]&lt;br /&gt;
      interclaseaza( n1, v1, n2, v2, v3, i1 + 1, i2, i3 + 1 );&lt;br /&gt;
    }&lt;br /&gt;
  } else if ( i2 &amp;lt; n2 ) { // mai avem elemente in v2? (v1 s-a terminat)&lt;br /&gt;
    v3[i3] = v2[i2];&lt;br /&gt;
    interclaseaza( n1, v1, n2, v2, v3, i1, i2 + 1, i3 + 1 );&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Temă =&lt;br /&gt;
* [http://varena.ro/runda/2019-10-17-clasa-7-tema-6 Tema 6 clasa a 7&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;]:&lt;br /&gt;
** [http://varena.ro/problema/sumacfnr sumacfnr]: să se scrie o funcție recursivă care să calculeze suma cifrelor unui număr.&lt;br /&gt;
** [http://varena.ro/problema/sumadiv sumadiv]: să se scrie o funcție recursivă care să calculeze suma divizorilor unui număr. Ea va arăta astfel: &amp;lt;pre&amp;gt;int sumd( int n, int d ) {&amp;amp;#10;  ...&amp;amp;#10;}&amp;lt;/pre&amp;gt; și va fi apelată inițial astfel: &amp;lt;pre&amp;gt;sum = sumd( n, 1 );&amp;lt;/pre&amp;gt; Semnificaţia este &#039;&#039;suma divizorilor lui n care sînt mai mari sau egali cu d şi mai mici sau egali cu n / d&#039;&#039;. Mai exact trebuie să completați funcția &amp;lt;tt&amp;gt;sumd&amp;lt;/tt&amp;gt; din programul următor:&amp;lt;pre&amp;gt;#include &amp;lt;stdio.h&amp;gt;&amp;amp;#10;&amp;amp;#10;int sumd( int n, int d ) {&amp;amp;#10;  ...&amp;amp;#10;}&amp;amp;#10;&amp;amp;#10;int main() {&amp;amp;#10;  FILE *fin, *fout;&amp;amp;#10;  int n;&amp;amp;#10;&amp;amp;#10;  fin = fopen( &amp;quot;sumadiv.in&amp;quot;, &amp;quot;r&amp;quot; );&amp;amp;#10;  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&amp;amp;#10;  fclose( fin );&amp;amp;#10;&amp;amp;#10;  fout = fopen( &amp;quot;sumadiv.out&amp;quot;, &amp;quot;w&amp;quot; );&amp;amp;#10;  fprintf( fout, &amp;quot;%d\n&amp;quot;, sumd( n, 1 ) );&amp;amp;#10;  fclose( fout );&amp;amp;#10;&amp;amp;#10;  return 0;&amp;amp;#10;}&amp;lt;/pre&amp;gt; Atenţie mare la complexitatea algoritmului obţinut!&lt;br /&gt;
** [http://varena.ro/problema/nset nset]: să se scrie o un program care să calculeze numărul de cifre distincte ale unui număr, folosind recursivitate.&lt;br /&gt;
** [http://varena.ro/problema/invector invector] să se răstoarne un vector folosind o funcție recursivă. Vectorul trebuie modificat, nu doar afișat invers. Funcția va arăta astfel: &amp;lt;pre&amp;gt;void inv( int primul, int ultimul, int v[] ) {&amp;amp;#10;  ...&amp;amp;#10;}&amp;lt;/pre&amp;gt; unde &amp;lt;tt&amp;gt;primul&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;ultimul&amp;lt;/tt&amp;gt; sînt indicii de început, respecitiv sfîrșit care definesc subvectorul de răsturnat. Funcția va fi apelată inițial astfel: &amp;lt;pre&amp;gt;inv( 0, n-1, v );&amp;lt;/pre&amp;gt; Mai exact trebuie să completați funcția &amp;lt;tt&amp;gt;inv&amp;lt;/tt&amp;gt; din programul următor: &amp;lt;pre&amp;gt;#include &amp;lt;stdio.h&amp;gt;&amp;amp;#10;&amp;amp;#10;int v[100000];&amp;amp;#10;&amp;amp;#10;void inv( int primul, int ultimul, int v[] ) {&amp;amp;#10;  ...&amp;amp;#10;}&amp;amp;#10;&amp;amp;#10;int main() {&amp;amp;#10;  FILE *fin, *fout;&amp;amp;#10;  int n, i;&amp;amp;#10;&amp;amp;#10;  fin = fopen( &amp;quot;invector.in&amp;quot;, &amp;quot;r&amp;quot; );&amp;amp;#10;  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&amp;amp;#10;  for ( i = 0; i &amp;lt; n; i++ )&amp;amp;#10;    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;v[i] );&amp;amp;#10;  fclose( fin );&amp;amp;#10;&amp;amp;#10;  inv( 0, n-1, v );&amp;amp;#10;&amp;amp;#10;  fout = fopen( &amp;quot;invector.out&amp;quot;, &amp;quot;w&amp;quot; );&amp;amp;#10;  for ( i = 0; i &amp;lt; n; i++ )&amp;amp;#10;    fprintf( fout, &amp;quot;%d &amp;quot;, v[i] );&amp;amp;#10;  fprintf( fout, &amp;quot;\n&amp;quot; );&amp;amp;#10;  fclose( fout );&amp;amp;#10;&amp;amp;#10;  return 0;&amp;amp;#10;}&amp;lt;/pre&amp;gt;&lt;br /&gt;
** [http://varena.ro/problema/hanoi turnurile din Hanoi]: [http://www.puzzle.ro/ro/play_toh.htm exemplu de animație joc aici] să se scrie un program care să rezolve jocul turnurile din Hanoi (să efectueze mutările care duc la soluție).&lt;br /&gt;
&lt;br /&gt;
* [http://varena.ro/runda/2019-10-17-clasa-7-tema-6-optionala Tema 6 &#039;&#039;&#039;opțională&#039;&#039;&#039; clasa a 7&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;]:&lt;br /&gt;
** [http://varena.ro/problema/combinari combinări] &amp;lt;math&amp;gt;{C}_{n}^{k}&amp;lt;/math&amp;gt;&amp;lt;nowiki&amp;gt;- combinări de n luate cîte k. Date numerele naturale n și k, k &amp;amp;le; n, să se afișeze toate submulțimile mulțimii {1, 2, 3, ..., n} de k elemente.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
** [http://varena.ro/problema/v v] (ONI 2004 clasa a 7-a) Problema este modificată. Puteți lua doar 50% din punctaj. Pentru 100% &#039;&#039;&#039;nu este nevoie de parsing&#039;&#039;&#039;. Dacă introduceam cerință de parsing aș fi scăzut timpul permis.&lt;br /&gt;
&lt;br /&gt;
Rezolvări aici [http://solpedia.francu.com/wiki/index.php/Clasa_a_VII-a_lec%C8%9Bia_6_-_17_oct_2019]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_29:_Discutarea_problemelor_de_la_OJI_2024&amp;diff=18586</id>
		<title>Clasa a 7-a Lecția 29: Discutarea problemelor de la OJI 2024</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_29:_Discutarea_problemelor_de_la_OJI_2024&amp;diff=18586"/>
		<updated>2026-02-06T16:59:28Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/z4433kBdq2A|||||start=1&amp;amp;end=3540&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Parking ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/parking Parking] a fost dată la OJI 2024 Clasa a 7-a.&lt;br /&gt;
&lt;br /&gt;
Când vedem o astfel de problemă primul gând trebuie să fie: oricât de simplă ar fi soluția, chiar și cea mai simplă soluție va fi delicat de implementat și nu va fi scurtă. Este genul de problemă care, la concurs, ia timp. Dacă avem o altă problemă mai simplu de implementat, este bine să lăsăm parking la urmă.&lt;br /&gt;
&lt;br /&gt;
Deoarece enunțul este semi-încâlcit, să clarificăm anumite lucruri:&lt;br /&gt;
&lt;br /&gt;
* O mașină iese într-o serie dacă și numai dacă în direcția aleasă de deplasare nu există nici o altă mașină, până la ieșire.&lt;br /&gt;
* Decizia de ieșire sau nu se face static: într-o configurație de mașini toate &#039;&#039;se uită&#039;&#039; către ieșire în același timp. Dacă o văd, adică nu este blocată de o altă mașină, atunci ies.&lt;br /&gt;
* Mașinile care pot ieși, ies. Nu ne punem probleme că se pot intersecta cu alte mașini pe drum. Din punctul nostru de vedere este ca și cum ele dispar din parcare subit.&lt;br /&gt;
&lt;br /&gt;
Concluzie: în fiecare serie trebuie să decidem ce mașini &#039;&#039;văd&#039;&#039; o ieșire. Ele vor dispărea din parcare, apoi trecem la următoarea serie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție forță brută ===&lt;br /&gt;
&lt;br /&gt;
Sper că o soluție rezonabilă, ce va lua multe puncte, este accesibilă. O idee imediată este să reprezentăm totul într-o matrice bordată, cu numere diferite pentru vid (nimic), mașină orizontală, mașină verticală sau zid. Apoi vedem ce putem rezolva parcurgând matricea. Pentru o mașină oarecare din matrice vrem să răspundem la întrebările:&lt;br /&gt;
&lt;br /&gt;
* Există ieșire în vreun capăt al liniei/coloanei mele? Putem răspunde în &#039;&#039;O(1)&#039;&#039; la această întrebare, verificând valorile de pe bordură, în cele două capete.&lt;br /&gt;
* Există vreo altă mașină între mine și capetele de zid? Și la această întrebare putem răspunde în &#039;&#039;O(1)&#039;&#039;, dacă în parcurgerea matricei memorăm numărul de mașini întâlnite pe drum pe acea direcție. Conceptual, cel mai simplu, ne putem imagina că facem patru parcurgeri, câte una pentru fiecare direcție.&lt;br /&gt;
&lt;br /&gt;
Combinănd cele două întrebări putem răspunde la întrebarea dacă o mașină iese sau nu: o mașină iese dacă există ieșire și numărul de mașini din față este zero.&lt;br /&gt;
&lt;br /&gt;
Bun, deci putem calcula mașinile ce ies într-o serie în &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Le putem apoi șterge din matrice.&lt;br /&gt;
&lt;br /&gt;
Deoarece avem maxim &amp;lt;tt&amp;gt;s = 10 000&amp;lt;/tt&amp;gt; serii rezultă un algoritm &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;·s)&#039;&#039; care va efectua circa 10 miliarde de operații. E clar că va depăși timpul pentru teste mari, dar probabil va lua peste 40p dacă îl implementăm eficient. De exemplu observăm că matricea poate fi memorată cu elemente caracter, ceea ce reduce memoria necesară la 1MB, destul de puțin.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție ===&lt;br /&gt;
&lt;br /&gt;
Este clar că orice soluție care va parcurge matricea la fiecare serie nu este bună. Mai mult, nu putem nici să parcurgem toate mașinile la fiecare serie. Reducem complexitatea de zece ori, deoarece mașinile sunt de zece ori mai puține decât elementele matricei, dar și un miliard de operații este prea mult. Cu toate acestea, dacă nu ne vine altă idee, e clar că vom lua mai multe teste cu această optimizare.&lt;br /&gt;
&lt;br /&gt;
Pentru o soluție mai bună să observăm următoarele:&lt;br /&gt;
&lt;br /&gt;
* Pe orice ieșire din parcare va ieși cel mult o mașină pe serie.&lt;br /&gt;
* Numărul de ieșiri din parcare este &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
* Drept care numărul de mașini ce poate ieși într-o serie este &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În lumina acestor observații pare foarte risipitor să parcurgem &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; mașini pentru a alege &#039;&#039;O(n)&#039;&#039; dintre ele. Oare nu putem să verificăm doar acele &#039;&#039;O(n)&#039;&#039; mașini care au șanse să iasă?&lt;br /&gt;
&lt;br /&gt;
Ba da, dacă procedăm invers: în loc să ne uităm din mașină către ieșire, ne vom uita dinspre ieșire spre mașini. Mai exact, pentru a determina ce mașini ies într-o serie, folosind matricea anterioară, procedăm astfel: pentru fiecare ieșire posibilă vom parcurge linia sau coloana ei până ce dăm de o mașină sau de un zid.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm? Din păcate costul unei serii este tot &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Pare că nu am avansat. Sau poate da?&lt;br /&gt;
&lt;br /&gt;
Ce observăm? Că de la o serie la alta, când ne &amp;quot;uităm&amp;quot; dinspre ieșire, vom vedea fie aceeași mașină ca în seria trecută, fie o mașină mai departe, în spate. Deci nu are sens să repetăm căutarea pornind de la ieșire, din nou. Vom porni de unde am rămas la seria anterioară! Pentru aceasta avem nevoie de patru vectori de indici în linia sau coloana respectivă. Astfel, fiecare ieșire va genera &#039;&#039;O(n)&#039;&#039; calcule pe tot parcursul simulării (analiză amortizată).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Detalii importante de implementare: &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Nu putem șterge mașinile din matrice imediat ce constatăm că pot ieși. De ce? Deoarece s-ar putea să deschidem drumul unei mașini care nu ar fi ieșit în acea serie.&lt;br /&gt;
* Nu putem nici să le numărăm pur și simplu. Aceeași mașină poate ieși atât prin stânga cât și prin dreapta. Mașinile trebuie marcate cumva pentru a nu le contoriza de două ori.&lt;br /&gt;
* La final de serie ele trebuie cumva șterse din matrice.&lt;br /&gt;
&lt;br /&gt;
O soluție rezonabilă este ca, atunci când dăm peste o mașină ce poate ieși, să o memorăm într-un vector de perechi linie/coloană și să o marcăm în matrice ca zid. La final de serie numărul de elemente din vector este numărul de mașini care iese. Parcurgem acel vector și zeroizăm elementele de la acea linie și coloană.&lt;br /&gt;
&lt;br /&gt;
Pentru fiecare serie vom avea obligatoriu &#039;&#039;O(n)&#039;&#039; calcule, pentru parcurgerea ieșirilor, în total &#039;&#039;O(n·s)&#039;&#039; operații. La aceste calcule se adaugă cele &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; generate de jocul indicilor (analiza amortizată). Complexitatea totală este, deci, &#039;&#039;O(n·s + n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;, circa 40 milioane de operații deoarece vom parcurge 4000 de elemente pe bordură. Partea frumoasă este că memoria nu se mărește semnificativ, avem nevoie doar de patru vectori de câte o mie de elemente de tip &amp;lt;code&amp;gt;short&amp;lt;/code&amp;gt;, circa 8KB și de un vector de 4000 de perechi de elemente de tip &amp;lt;code&amp;gt;short&amp;lt;/code&amp;gt;, circa 16KB.&lt;br /&gt;
&lt;br /&gt;
O soluție posibilă în timp de concurs, nefactorizată tocmai pentru simplitatea codului, va avea circa 75 de linii de cod efectiv.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Ron ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/ron Ron] a fost dată la OJI 2024 Clasa a 7-a.&lt;br /&gt;
&lt;br /&gt;
Este o problemă &#039;&#039;două în una&#039;&#039;, cele două cerințe neavând nici o legătură una cu alta. Să le enunțăm cât mai simplu:&lt;br /&gt;
&lt;br /&gt;
Dându-se &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; maxim 50 000 de intervale cu capete numere până în două miliarde să se determine:&lt;br /&gt;
&lt;br /&gt;
# Numărul maxim de pătrate de numere prime ce se află în interiorul unui interval.&lt;br /&gt;
# Numărul de intervale contigue formate de intervale.&lt;br /&gt;
&lt;br /&gt;
Ambele probleme sunt &#039;&#039;de manual&#039;&#039;. Nu prea avem ce gândi la ele, implementăm soluția studiată la curs.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție cerința 1 ===&lt;br /&gt;
&lt;br /&gt;
O primă idee ar fi să calculăm un ciur al elementelor pătrate de numere prime. Dar acest ciur este prea mare, până în două miliarde.&lt;br /&gt;
&lt;br /&gt;
A doua idee este să calculăm doar ciurul numerelor prime pâna la radical din două miliarde, apoi să extragem numerele prime și să stocăm într-un alt vector pătratele lor. Vom avea circa 45000 de astfel de numere. Apoi, pentru un interval dat, vom căuta binar capetele lui în acest vector. Este clar că această soluție se va încadra în timp, iar memoria necesară este mică.&lt;br /&gt;
&lt;br /&gt;
La acest moment majoritatea se vor apuca de lucru, pe principiul &#039;&#039;n-am gândit aproape deloc, deci e bine&#039;&#039;. Și poate au și dreptate, la un concurs. Însă în viața reală ar fi bine să ne întrebăm: oare se poate mai bine?&lt;br /&gt;
&lt;br /&gt;
Desigur, iar implementarea va fi și mult mai lejeră. Ce-ar fi să studiem capetele de interval, folosind bruma de matematică necesară? Oare ce se întâmplă dacă extragem radical din capete? Nu cumva noul interval va acoperi exact aceleași numere prime al căror pătrate sunt acoperite de vechiul interval? Matematica grea este ușoară! Doar ce ușoară este grea 🙂.&lt;br /&gt;
&lt;br /&gt;
Soluția simplă: după calculul ciurului vom calcula sume parțiale pe acel ciur, pentru a putea răspunde repede la întrebări de forma câte numere prime se află într-un interval. Apoi, pentru fiecare interval de la intrare, vom întreba câte numere prime se află între radicalii capetelor.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție? &#039;&#039;&amp;lt;math&amp;gt;O(\sqrt{x} \log \log \sqrt{x})&amp;lt;/math&amp;gt;&#039;&#039; pentru ciur, unde x este coordonata maximă, 2 miliarde și &#039;&#039;O(n)&#039;&#039; pentru răspunsuri, total &amp;lt;math&amp;gt;(O(\sqrt{x} \log \log \sqrt{x} + n)&amp;lt;/math&amp;gt;. Vom avea circa 180 000 de operații pentru ciur și circa 2 milioane pentru întrebări (am considerat că radicalul se calculează în 200 de operații elementare). Deoarece citim circa 1MB de date, este clar că citirea va fi o mare parte din timpul de execuție.&lt;br /&gt;
&lt;br /&gt;
Memoria necesară? Cea pentru sume parțiale pe ciur, &amp;lt;math&amp;gt;O(\sqrt{x})&amp;lt;/math&amp;gt;, mai exact 90KB.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție cerința 2 ===&lt;br /&gt;
&lt;br /&gt;
Am văzut aici soluții nenecesar de complicate. Printre ele se află și soluția comisiei.&lt;br /&gt;
&lt;br /&gt;
Să considerăm capetele de intervale ca fiind paranteze deschise sau închise. Dacă le vom plasa pe o axă la coordonata corectă, ce vom vedea? Că un interval contiguu va avea tot atâtea paranteze deschise cât și închise. Un interval neacoperit are proprietatea că numărul de parateze deschise de la stânga lui este egal cu cele închise.&lt;br /&gt;
&lt;br /&gt;
Problema este echivalentă cu cea a parantezelor imbricate, făcută prin clasa a cincea, cred: câtă vreme numărul parantezelor deschise și încă neînchise este strict mai mare ca zero, suntem în interiorul unui interval contiguu. În momentul când cele două numere devin egale, se închide un interval contiguu și trebuie să adăugăm unu la soluție.&lt;br /&gt;
&lt;br /&gt;
Avem, deci, un algoritm banal:&lt;br /&gt;
&lt;br /&gt;
# Sortăm capetele de intervale, memorând cumva tipul de capăt, închis sau deschis.&lt;br /&gt;
# Pornim cu un contor de paranteze neînchise inițializat cu zero.&lt;br /&gt;
# Parcurgem capetele în ordine crescătoare și pentru fiecare paranteză deschisă adunăm 1, pentru fiecare paranteză închisă scădem 1.&lt;br /&gt;
# De fiecare dată când contorul devine zero adunăm 1 la rezultat.&lt;br /&gt;
&lt;br /&gt;
Se pune întrebarea ce facem atunci când la aceeași poziție avem și paranteze deschise și închise. Dacă urmărim enunțul, capetele stânga sunt inclusive, cele dreapta exclusive. Ceea ce înseamnă că dacă un interval se termină la poziția &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; și altul începe la poziția &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt;, ele vor fi unul în continuarea altuia și segmentul curent se continuă. Pentru a nu trece prin zero cu contorul nostru este necesar să tratăm capetele deschise înaintea celor închise.&lt;br /&gt;
&lt;br /&gt;
Ca detaliu de implementare, pentru a nu sorta perechi, având nevoie de o valoare 0 sau 1, o putem adăuga la capătul de interval: îl înmulțim cu 2 și adunăm valoarea 0/1. Atenție, aceasta înseamnă că putem ajunge la valori de patru miliarde, depășind întregul. &amp;lt;code&amp;gt;unsigned int&amp;lt;/code&amp;gt; to the rescue! 🙂&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție? E clar că timpul cel mai mare este cel de sortare: &#039;&#039;O(n log n)&#039;&#039;. Din nou, numărul de operații fiind de circa 1.7 milioane operații, citirea va fi parte majoră din timpul de execuție.&lt;br /&gt;
&lt;br /&gt;
Iar memoria? Stocăm 2n capete de interval, deci &#039;&#039;O(n)&#039;&#039;, mai exact 400KB.&lt;br /&gt;
&lt;br /&gt;
O soluție implementabilă în timp de concurs va avea circa 65 de linii de cod efectiv, din care circa 20 de linii pentru quick sort.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 29 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/parking Parking] dată la OJI 2024 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/ron Ron] dată la OJI 2024 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_29:_Discutarea_problemelor_de_la_OJI_2024 Accesează rezolvarea problemelor de la lecția 29]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_27:_Algoritmul_Union-Find&amp;diff=18585</id>
		<title>Clasa a 7-a Lecția 27: Algoritmul Union-Find</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_27:_Algoritmul_Union-Find&amp;diff=18585"/>
		<updated>2026-02-06T16:57:26Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/QCaiWycJljs|||||start=8250&amp;amp;end=8320&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Disjoint ==&lt;br /&gt;
&lt;br /&gt;
Se dau &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; mulţimi de numere, iniţial fiecare mulţime &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; conţinând un singur element, mai exact elementul i. Asupra acestor mulţimi se pot face 2 tipuri de operaţii, astfel:&lt;br /&gt;
&lt;br /&gt;
* operaţia de tipul 1: se dau două numere naturale &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; si &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt;, între 1 şi &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt;. Se cere să se reunească mulţimile în care se află elementul &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt;, respectiv elementul &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt; (se garantează că &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; si &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt; nu se vor afla în aceeaşi mulţime).&lt;br /&gt;
* operaţia de tipul 2: se dau două numere naturale &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; si &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt;, între 1 şi &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt;. Se cere să se afişeze DA dacă cele 2 elemente se află în aceeaşi mulţime, respectiv NU în caz contrar.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Date de intrare ===&lt;br /&gt;
&lt;br /&gt;
Pe prima linie a fişierului de intrare &amp;lt;tt&amp;gt;disjoint.in&amp;lt;/tt&amp;gt; se vor afla 2 numere, &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; şi &amp;lt;tt&amp;gt;M&amp;lt;/tt&amp;gt;, reprezentând numărul de mulţimi, respectiv numărul de operaţii efectuate. Pe următoarele &amp;lt;tt&amp;gt;M&amp;lt;/tt&amp;gt; linii se vor afla câte 3 numere, cod, &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; si &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt;, cod reprezentând tipul operaţiei, iar &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; si &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt; având semnificaţia din enunţ.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Date de ieşire ===&lt;br /&gt;
&lt;br /&gt;
În fişierul de ieşire disjoint.out se vor afişa mai multe linii, fiecare linie conţinând DA sau NU, reprezentând răspunsul la interogarea corespunzătoare din fişierul de intrare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Restricţii ===&lt;br /&gt;
&lt;br /&gt;
* 1 ≤ N ≤ 100 000&lt;br /&gt;
* 1 ≤ M ≤ 100 000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;tt&amp;gt;disjoint.in&amp;lt;/tt&amp;gt;&lt;br /&gt;
!&amp;lt;tt&amp;gt;disjoint.out&amp;lt;/tt&amp;gt;&lt;br /&gt;
|- style=&amp;quot;vertical-align:top;&amp;quot;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;4 6&amp;lt;br/&amp;gt;&lt;br /&gt;
1 1 2&amp;lt;br/&amp;gt;&lt;br /&gt;
1 3 4&amp;lt;br/&amp;gt;&lt;br /&gt;
2 1 3&amp;lt;br/&amp;gt;&lt;br /&gt;
2 1 2&amp;lt;br/&amp;gt;&lt;br /&gt;
1 1 3&amp;lt;br/&amp;gt;&lt;br /&gt;
2 1 4&amp;lt;/tt&amp;gt;&lt;br /&gt;
|&lt;br /&gt;
&amp;lt;tt&amp;gt;NU&amp;lt;br/&amp;gt;&lt;br /&gt;
DA&amp;lt;br/&amp;gt;&lt;br /&gt;
DA&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Algoritmul union-find ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/disjoint Disjoint] şi rezolvarea ei, este o unealtă foarte utilă din trusa de scule a algoritmistului. Să definim problema:&lt;br /&gt;
&lt;br /&gt;
Dată o mulţime &amp;lt;tt&amp;gt;A&amp;lt;/tt&amp;gt; de valori de la 0 la &amp;lt;tt&amp;gt;N-1&amp;lt;/tt&amp;gt; definim submulţimi disjuncte pe această mulţime, astfel încât aceste submulţimi să acopere mulţimea originală &amp;lt;tt&amp;gt;A&amp;lt;/tt&amp;gt;. Apoi începem să reunim unele dintre aceste mulţimi. Reuniunea se face menţionând două elemente ale submulţimilor ce se doresc reunite (dacă elementele se găsesc deja în aceeaşi submulţime nu vom face nimic). După ce efectuăm aceste operaţii de reuniune dorim să răspundem la întrebarea &#039;&#039;ce submulţimi avem?&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Cu alte cuvinte, cunoscând N şi ştiind că iniţial fiecare număr face parte din propria submulţime şi apoi dându-se mai multe perechi de numere &amp;lt;tt&amp;gt;(a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, a&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt;)&amp;lt;/tt&amp;gt; cu semnificaţia &#039;&#039;mulţimea din care face parte ai se reuneşte cu mulţimea din care face parte a&amp;lt;sub&amp;gt;j&amp;lt;/sub&amp;gt;&#039;&#039; trebuie ca în final să spunem ce submulţimi există.&lt;br /&gt;
&lt;br /&gt;
Pentru a implementa algoritmul vom avea nevoie de două operaţii:&lt;br /&gt;
&lt;br /&gt;
* reuneşte două mulţimi (&#039;&#039;union&#039;&#039;)&lt;br /&gt;
* găseşte mulţimea unui element (&#039;&#039;find&#039;&#039;)&lt;br /&gt;
&lt;br /&gt;
De aici şi denumirea problemei &#039;&#039;union-find&#039;&#039;. În română se regăseşte sub numele de colecţie de mulţimi disjuncte, dar se folosesc și denumirile &#039;&#039;DSU (disjoint set union)&#039;&#039; sau &#039;&#039;union-find&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție forță brută ===&lt;br /&gt;
&lt;br /&gt;
O soluție posibilă ar fi ca pentru fiecare număr &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; să memorăm mulțimea din care face el parte. Astfel, &amp;lt;code&amp;gt;m[i]&amp;lt;/code&amp;gt; va conține numărul mulțimii lui &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;. Dacă &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; fac parte din aceeași mulțime atunci &amp;lt;code&amp;gt;m[i] = m[j]&amp;lt;/code&amp;gt;, în caz contrar &amp;lt;code&amp;gt;m[i] ≠ m[j]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Inițial fiecare număr va avea propria lui mulțime, adică &amp;lt;code&amp;gt;m[i] = i&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Operația Find ====&lt;br /&gt;
&lt;br /&gt;
Precum am spus, &amp;lt;tt&amp;gt;find(i)&amp;lt;/tt&amp;gt; ne va da un identificator unic al mulțimii elementului &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;. În cazul nostru el va fi un număr între 0 și &amp;lt;tt&amp;gt;N-1&amp;lt;/tt&amp;gt;. Cum o implementăm? Returnând chiar &amp;lt;code&amp;gt;m[i]&amp;lt;/code&amp;gt;!&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int find( int i ) {&lt;br /&gt;
  return m[i];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această operație?&lt;br /&gt;
&lt;br /&gt;
Desigur &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Operația Union ====&lt;br /&gt;
&lt;br /&gt;
Operația &amp;lt;tt&amp;gt;union(i, j)&amp;lt;/tt&amp;gt; trebuie să reunească mulțimile din care fac parte elementele &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, respectiv &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;. Cum o implementăm? Suprascriind identificatorul mulțimii lui &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; cu identificatorul mulțimii lui &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, peste tot unde apare el.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void union( int i, int j ) {&lt;br /&gt;
  int k, mj = m[j];&lt;br /&gt;
&lt;br /&gt;
  for ( k = 0; k &amp;lt; N; k++ )&lt;br /&gt;
    if ( m[k] == mj )&lt;br /&gt;
      m[k] = m[i];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această operație?&lt;br /&gt;
&lt;br /&gt;
Desigur &#039;&#039;O(N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Concluzie &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Cu forța brută vom avea complexitățile:&lt;br /&gt;
&lt;br /&gt;
* Find: &#039;&#039;O(1)&#039;&#039; timp.&lt;br /&gt;
* Union: &#039;&#039;O(N)&#039;&#039; timp.&lt;br /&gt;
* Memorie necesară: &#039;&#039;O(N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție alternativă ===&lt;br /&gt;
&lt;br /&gt;
Să încercăm să optimizăm operația union. Actualizarea identificatorilor mulțimii durează mult. Ce putem face?&lt;br /&gt;
&lt;br /&gt;
O idee ar fi ca, în loc să actualizăm toți identificatorii mulțimii lui &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;, să îl actualizăm doar pe al lui &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;. Astfel, fiecare element între 0 și &amp;lt;tt&amp;gt;N-1&amp;lt;/tt&amp;gt; va ţine mine un şef al său din mulţime. Întocmai ca într-o companie, fiecare element al unei submulţimi va raporta unui şef, şefii vor raporta altor şefi şi aşa mai departe până ce se ajunge la şeful unic, conducătorul submulţimii. El nu are şef şi este considerat reprezentantul acelei submulţimi.&lt;br /&gt;
&lt;br /&gt;
Cum memorăm acest lucru? Printr-un simplu vector de şefi (similar vectorului &amp;lt;code&amp;gt;m[]&amp;lt;/code&amp;gt;) în care elementul &amp;lt;code&amp;gt;sef[i]&amp;lt;/code&amp;gt; este numărul din mulţimea 0..N-1 care este şeful lui &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;. Ce punem în elementele şefi supremi, cele care nu au șefi? Pentru a le distinge de elementele subalterni fiecare şef suprem este propriul lui şef. Dacă elementul &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; este şef suprem atunci &amp;lt;code&amp;gt;sef[i] = i&amp;lt;/code&amp;gt;. O alternativă la această reprezentare, dacă &#039;&#039;sacrificăm&#039;&#039; elementul 0, este să memorăm zero ca şef fictiv pentru elementele reprezentative ale mulţimilor.&lt;br /&gt;
&lt;br /&gt;
Inițial fiecare element este propriul său șef. Atunci când căutăm o mulțime, căutăm de fapt șeful suprem, care este propriul său șef. Când unificăm mulțimile lui &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; și ale lui &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; vom nota că șeful mulțimii &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; devine șeful șefului mulțimii &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Operația Find ====&lt;br /&gt;
&lt;br /&gt;
Precum am spus, &amp;lt;tt&amp;gt;find(i)&amp;lt;/tt&amp;gt; va returna șeful mulțimii din care face parte &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;. Cum îl găsim? Mergând la șeful lui &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, apoi la șeful șefului lui &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; și tot așa până la șeful mulțimii. Iată o implementare recursivă simplă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int find( int i ) {&lt;br /&gt;
  if ( sef[i] == i )     // daca am gasit seful suprem&lt;br /&gt;
    return i;            // il returnam&lt;br /&gt;
  return find( sef[i] ); // altfel returnam seful sefului&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această operație?&lt;br /&gt;
&lt;br /&gt;
Care este cazul cel mai rău? Când toate elementele se înșiră într-un lanț lung de șefi. În acest caz timpul este &#039;&#039;O(N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Operația Union ====&lt;br /&gt;
&lt;br /&gt;
Operația &amp;lt;tt&amp;gt;union(i, j)&amp;lt;/tt&amp;gt; trebuie să reunească mulțimile din care fac parte elementele &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, respectiv &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;. Cum o implementăm? Suprascriind identificatorul mulțimii lui &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; cu identificatorul mulțimii lui &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, peste tot unde apare el.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void union( int i, int j ) {&lt;br /&gt;
  int sefi, sefj;&lt;br /&gt;
&lt;br /&gt;
  sefi = find( i ); // seful suprem al lui i&lt;br /&gt;
  sefj = find( j ); // seful suprem al lui j&lt;br /&gt;
&lt;br /&gt;
  sef[sefj] = sefi; // seful suprem al lui sefj devine sefi&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această operație?&lt;br /&gt;
&lt;br /&gt;
Ea apelează de două ori &amp;lt;tt&amp;gt;find()&amp;lt;/tt&amp;gt;, iar apoi face &#039;&#039;O(1)&#039;&#039; operații.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Concluzie &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Cu soluția alternativă avem complexitățile:&lt;br /&gt;
&lt;br /&gt;
* Find: &#039;&#039;O(N)&#039;&#039; timp.&lt;br /&gt;
* Union: &#039;&#039;O(1)&#039;&#039; timp + complexitatea lui Find.&lt;br /&gt;
* Memorie necesară: &#039;&#039;O(N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Nu pare că am avansat, nu? Poate că nu. Observăm, însă, că timpul total depinde de Find: dacă reușim să îmbunătățim complexitatea lui Find automat vom îmbunătăți și complexitatea lui Union.&lt;br /&gt;
&lt;br /&gt;
Putem îmbunătăți timpul lui find la &#039;&#039;O(log N)&#039;&#039; cu o modificare destul de mică a structurii de date, dar nu voi prezenta acest lucru. Pentru cei curioși, putem memora cea mai lungă cale de șefi din fiecare mulțime, iar apoi vom atașa mulțimea mai mică la cea mai mare (alegând în mod potrivit dacă atașăm șeful lui &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; la &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; sau invers).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție eficientă folosind compresia căii ===&lt;br /&gt;
&lt;br /&gt;
Pentru a obţine cel mai eficient algoritm cunoscut avem nevoie de două optimizări: compresia căii și menținerea unui rang al fiecărei mulțimi. Voi prezenta aici doar una dintre ele, cea mai importantă, compresia căii. Ea face un lucru foarte simplu: schimbă puţin funcţia find ca, atunci când dorim să aflăm care este şeful nodului &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, să lege toţi şefii parcurşi direct la şeful suprem. Cu alte cuvinte vom parcurge aceiaşi şefi intermediari până la şeful suprem, dar odată găsit va trebui să ne întoarcem la şefii intermediari şi să le schimbăm şeful. Aceasta duce aproximativ la o dublare a opreaţiilor efectuate de find. Dar, precum spunea astronatul Neil Armstrong, un pas mic pentru un om poate fi un pas uriaş pentru omenire. O mică modificare ce duce la dublarea timpului lui find duce la o scurtare fantastică a următoarelor operaţiuni &#039;&#039;find&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
[[File:path-compression-find.png|frame|none|Exemplu de &#039;&#039;find&#039;&#039; cu compresia căii]]&lt;br /&gt;
&lt;br /&gt;
Această optimizare poartă denumirea de compresia căii, sau, în engleză, path compression.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Operația Find ====&lt;br /&gt;
&lt;br /&gt;
Iată o modificare mică a funcției anterioare, care face și compresia căii:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int find( int i ) {&lt;br /&gt;
  if ( sef[i] == i )   // daca am gasit seful suprem&lt;br /&gt;
    return i;          // il returnam&lt;br /&gt;
  return sef[i] = find( sef[i] ); // altfel legam i la seful suprem, pe care il si returnam&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Operația Union ====&lt;br /&gt;
&lt;br /&gt;
Operația &amp;lt;tt&amp;gt;union(i, j)&amp;lt;/tt&amp;gt; trebuie să reunească mulțimile din care fac parte elementele &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, respectiv &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;. Cum o implementăm? Suprascriind identificatorul mulțimii lui &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; cu identificatorul mulțimii lui &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, peste tot unde apare el.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void union( int i, int j ) {&lt;br /&gt;
  int sefi, sefj;&lt;br /&gt;
&lt;br /&gt;
  sefi = find( i ); // seful suprem al lui i&lt;br /&gt;
  sefj = find( j ); // seful suprem al lui j&lt;br /&gt;
&lt;br /&gt;
  sef[sefj] = sefi; // seful suprem al lui sefj devine sefi&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ce complexitate au aceste operații?&lt;br /&gt;
&lt;br /&gt;
În această implementare, ele sunt &#039;&#039;O(log N)&#039;&#039; amortizat, per operație, cu o constantă foarte mică. Demonstrația acestui lucru depășește cadrul acestui curs.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Concluzie &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Cu compresia căii avem complexitățile:&lt;br /&gt;
&lt;br /&gt;
* Find: &#039;&#039;O(log N)&#039;&#039; timp.&lt;br /&gt;
* Union: &#039;&#039;O(log N)&#039;&#039; timp.&lt;br /&gt;
* Memorie necesară: &#039;&#039;O(N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Notă: se poate mai bine. Dacă adăugăm și cealaltă optimizare menționată mai sus, rangul unei mulțimi, ajungem la o complexitate &#039;&#039;O(α(N))&#039;&#039;, unde &#039;&#039;α(N)&#039;&#039; este inversul funcției lui Ackermann, o funcție rapid crescătoare. Cu această modificare timpul amortizat este, practic, constant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Inversarea sensului ===&lt;br /&gt;
&lt;br /&gt;
Ocazional veți întâlni problema inversă: se pornește cu o structură conexă care, treptat, este deconectată. Pe parcurs, se cer informații despre conexitate. Aceasta este o problemă de &#039;&#039;&#039;partiționare&#039;&#039;&#039;, care nu admite o soluție directă la fel de simplă.&lt;br /&gt;
&lt;br /&gt;
Dar, când memoria permite stocarea șirului de operații, o soluție este:&lt;br /&gt;
&lt;br /&gt;
* Executăm toate operațiile de deconectare, obținând structura finală.&lt;br /&gt;
* Executăm în ordine inversă operațiile, care acum reconectează structura. Putem face aceste operații cu structuri de mulțimi disjuncte.&lt;br /&gt;
* Notăm răspunsurile la interogările de conexitate.&lt;br /&gt;
* Tipărim aceste răspunsuri în ordine inversă.&lt;br /&gt;
&lt;br /&gt;
Exemple de astfel de probleme: [https://www.nerdarena.ro/problema/bile3 Bile3], [https://www.nerdarena.ro/problema/ruine Ruine], pe care le aveți la temă, respectiv tema opțională.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 27 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/disjoint Disjoint]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/channels Channels] dată la Shumen 2012 juniori&lt;br /&gt;
* [https://www.nerdarena.ro/problema/bile3 Bile3]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/flota Flota]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/ruine Ruine]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_27:_Algoritmul_Union-Find&amp;amp;action=edit&amp;amp;redlink=1 Accesează rezolvarea temei 29]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_26:_Programare_dinamic%C4%83_(3)&amp;diff=18584</id>
		<title>Clasa a 7-a Lecția 26: Programare dinamică (3)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_26:_Programare_dinamic%C4%83_(3)&amp;diff=18584"/>
		<updated>2026-02-06T16:55:41Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video rezolvarea temei ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://www.youtube.com/watch?v=KdHv9T7Pp98|||||start=80&amp;amp;end=10280&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
În continuare vom studia exemple de probleme de programare dinamică și probleme suprapuse date la olimpiadă.&lt;br /&gt;
&lt;br /&gt;
== Problema Faleza ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/faleza Faleza] a fost dată la ONI 2017 clasa a 6-a. Ea este o problemă de un anumit gen, care se întâlnește din când în când la olimpiadă: probleme mascate, care au o soluție medie sau chiar grea, fără programare dinamică, dar care admit o soluție banală cu programare dinamică.&lt;br /&gt;
&lt;br /&gt;
Atâta vreme cât programarea dinamică nu este în programa de olimpiadă de clasa a șasea, după părerea mea genul acesta de probleme nu sunt bune de concurs, deoarece încurajează dopajul, nu gândirea algoritmică și aprofundarea cunoștințelor. În cazul problemei [https://www.nerdarena.ro/problema/faleza Faleza] cine știe programare dinamică o poate termina în 15 minute. Cine nu, va trebui să găsească un algoritm care nu e deloc evident (mai ales la casa a 6-a), petrecând, probabil, peste o oră.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluția comisiei ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Construim două tablouri pe care le notăm A și B, cu numerele din fișier, în A cele de pe o parte a falezei și în B cele de pe cealaltă. Pentru simplificarea implementării, dacă avem poziții i în care A[i] = 1 și B[i] = 1 putem să analizăm doar ce se întâmplă între două astfel de poziții consecutive.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Parcurgem element cu element și păstrăm mereu ultima poziție pe care se află o dală bună pentru șirul A respectiv pentru șirul B, alegând, în funcție de acestea, modul de a completa dalele deteriorate.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Timpul de executare este liniar.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
După părerea mea este puțin vagă. În mod cert e nevoie de o analiză care nu pare simplă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție cu programare dinamică ===&lt;br /&gt;
&lt;br /&gt;
Să simplificăm puțin enunțul: se cere să reparăm cât mai puține dale pentru a se putea ajunge de la un capăt la celălalt. Cu alte cuvinte se cere &#039;&#039;&#039;un drum care să treacă prin cât mai puține dale rupte&#039;&#039;&#039;. Dacă vom schimba codarea dalelor, cele rupte cu 1 și cele întregi cu 0, rezultă că trebuie să găsim un &#039;&#039;&#039;drum de sumă minimă&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
O observație simplă, dar esențială, este că drumul minim nu se va întoarce de la dreapta la stânga. Într-adevăr, este clar că nu are de ce. Putem, deci, calcula drumurile de la stânga la dreapta.&lt;br /&gt;
&lt;br /&gt;
Drept care o definiție evidentă a problemei de programare dinamică este:&lt;br /&gt;
&lt;br /&gt;
 S[l][c] = suma unui drum de sumă minimă pentru a ajunge în punctul (l, c).&lt;br /&gt;
&lt;br /&gt;
Demonstrație de optimalitate a subprobemelor o las în seama voastră, fiind simplă.&lt;br /&gt;
&lt;br /&gt;
Formula de recurență este iarăși simplă:&lt;br /&gt;
&lt;br /&gt;
* Într-o dală de sus putem ajunge fie dinspre stânga, fie din jos în sus.&lt;br /&gt;
* Într-o dală de jos putem ajunge fie dinspre stânga, fie din sus în jos.&lt;br /&gt;
&lt;br /&gt;
Vom lua în considerare minimul dintre cele două variante.&lt;br /&gt;
&lt;br /&gt;
Formula de recurență este:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;S[l][c] = \begin{cases} D[l][c], &amp;amp; \mbox{dacă } c = 0\\D[l][c] + min(S[l][c-1], S[1-l][c-1] + D[1-l][c]) &amp;amp; \mbox{în caz contrar}. \end{cases} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am considerat că &amp;lt;tt&amp;gt;D[l][c]&amp;lt;/tt&amp;gt; este codificarea dalei din linia &amp;lt;tt&amp;gt;l&amp;lt;/tt&amp;gt; și coloana &amp;lt;tt&amp;gt;c&amp;lt;/tt&amp;gt;. Liniile și coloanele sunt numerotate de la 0.&lt;br /&gt;
&lt;br /&gt;
Răspunsul cerut este minimul dintre &amp;lt;tt&amp;gt;S[0][n-1]&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;S[1][n-1]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Timpul este evident &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Memoria ar putea fi &#039;&#039;O(1)&#039;&#039; dacă dalele ar fi date pe linii, nu pe coloane. Așa cum este enunțul suntem obligați să le stocăm, ceea ce duce la memorie &#039;&#039;O(n)&#039;&#039;. Putem însă refolosi matricea de dale pentru a calcula sumele minime. Drept care memoria va fi de &amp;lt;tt&amp;gt;2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt; întregi, adică 800KB.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iată exemplul din enunț calculat conform formulei de mai sus.&lt;br /&gt;
&lt;br /&gt;
Dalele originale (0 = întreagă, 1 = ruptă):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|1&lt;br /&gt;
|1&lt;br /&gt;
|0&lt;br /&gt;
|0&lt;br /&gt;
|0&lt;br /&gt;
|1&lt;br /&gt;
|1&lt;br /&gt;
|1&lt;br /&gt;
|-&lt;br /&gt;
|1&lt;br /&gt;
|1&lt;br /&gt;
|1&lt;br /&gt;
|1&lt;br /&gt;
|1&lt;br /&gt;
|1&lt;br /&gt;
|1&lt;br /&gt;
|0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Calculul drumurilor minime pe coloane, de la stânga la dreapta:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|1&lt;br /&gt;
|2&lt;br /&gt;
|2&lt;br /&gt;
|2&lt;br /&gt;
|2&lt;br /&gt;
|3&lt;br /&gt;
|4&lt;br /&gt;
|5&lt;br /&gt;
|-&lt;br /&gt;
|1&lt;br /&gt;
|2&lt;br /&gt;
|3&lt;br /&gt;
|3&lt;br /&gt;
|3&lt;br /&gt;
|4&lt;br /&gt;
|5&lt;br /&gt;
|5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Poteci ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/poteci Poteci] a fost dată la ONI 2011 baraj gimnaziu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluția comisiei ===&lt;br /&gt;
&lt;br /&gt;
Fiind vorba de baraj, soluția este una bazată pe programare dinamică:&lt;br /&gt;
&lt;br /&gt;
* Pentru fiecare element din matrice vom calcula drumurile optime până la cele patru colțuri ale matricei.&lt;br /&gt;
* Parcurgem matricea și determinăm acel element pentru care suma celor patru drumuri este maximă.&lt;br /&gt;
&lt;br /&gt;
Pentru primul pas vom folosi programarea dinamică, o procedură apelată de patru ori. În fapt, vom calcula drumurile optime de la fiecare colț către toate elementele matricei. De exemplu, pentru distanța de la colțul &amp;lt;tt&amp;gt;(1, 1)&amp;lt;/tt&amp;gt; la elementul &amp;lt;tt&amp;gt;(i, j)&amp;lt;/tt&amp;gt; vom avea formula de recurență:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;D_{1,1}[l][c] = \begin{cases} 0, &amp;amp; \mbox{dacă } l = 0 \mbox{ sau } c = 0\\M[l][c] + \max{(D_{1,1}[l][c-1], D_{1,1}[l-1][c])} &amp;amp; \mbox{în caz contrar}. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
În final vom alege punctul din matrice (l, c) cu proprietatea:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;MAX = \max\limits_{1 \leq l \leq L, 1 \leq c \leq C}{(D_{1,1}[l][c] + D_{1,C}[l][c] + D_{L,1}[l][c] + D_{L,C}[l][c])}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Ea calculează patru matrice, deci atât timpul cât și memoria vor fi &#039;&#039;O(L·C)&#039;&#039;. Mai exact, vom avea nevoie de patru matrice de elemente întregi de 1000 x 1000 elemente, circa 16MB, plus matricea originală de frumuseți, elemente &amp;lt;code&amp;gt;short int&amp;lt;/code&amp;gt;, deci încă 2MB, total 18MB.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție alternativă ===&lt;br /&gt;
&lt;br /&gt;
O soluție care face mai puține calcule și folosește mai puțină memorie este următoarea.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație&#039;&#039;&#039;: perechea optimă de poteci va fi alcătuită din două poteci optime.&lt;br /&gt;
&lt;br /&gt;
Într-adevăr, dacă una din poteci nu ar fi optimă, am putea alege o potecă mai bună care ar duce la o pereche mai bună.&lt;br /&gt;
&lt;br /&gt;
De aici vine și ideea soluției: să calculăm potecile optime pentru cei doi și să alegem punctul de intersecție a lor. Pentru aceasta va trebui să calculăm nu doar frumusețile potecilor, ci și potecile în sine. La final vom calcula intersecția potecilor.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție? Ea este, desigur, tot &#039;&#039;O(L·C)&#039;&#039; atât ca timp cât și ca memorie. Însă vom calcula doar două matrice, deci timpul se reduce aproximativ la jumate. Iar memoria necesară este de o matrice pentru frumusețile originale (2MB) și o matrice de drumuri (1MB). Pentru a calcula intersecția putem refolosi matricea de drumuri. Necesarul de memorie este, deci, de numai 3MB, comparat cu cei 18MB ai soluției anterioare. Putem reduce și mai mult memoria dacă folosim pentru stocarea drumurilor matrice de biți, caz în care în loc de 1MB vom folosi 125KB, iar memoria totală folosită va fi 2.12MB. Iar folosind [https://en.wikipedia.org/wiki/Hirschberg%27s_algorithm algoritmul lui Hirschberg] putem elimina complet matricea de drumuri, ceea ce duce la 2MB memorie necesară.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzii:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Timpul se reduce la jumate.&lt;br /&gt;
&lt;br /&gt;
Memoria se reduce la o șesime&lt;br /&gt;
&lt;br /&gt;
== Problema Optim ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/optim Optim] a fost dată la ONI 2012 clasa a 8-a. Ea are o rezolvare fără programare dinamică, ineficientă și nu chiar banal de implementat și o rezolvare cu programare dinamică, eficientă și relativ ușor de implementat.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluția combinatorică, fără programare dinamică ===&lt;br /&gt;
&lt;br /&gt;
Soluția comisiei, cea combinatorică, se bazează pe generarea tuturor modurilor de a plasa &amp;lt;tt&amp;gt;K&amp;lt;/tt&amp;gt; înmulțiri între &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; operații și calculul expresiilor respective, din care vom reține minimul și maximul. Implementarea este cea de combinări modificată să calculeze incremental expresiile.&lt;br /&gt;
&lt;br /&gt;
Complexitatea este &#039;&#039;O(Comb(N, K))&#039;&#039;. Pentru valori maxime obținem Comb(30, 10), aproximativ 10 milioane operații.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție cu programare dinamică ===&lt;br /&gt;
&lt;br /&gt;
Problema are două părți echivalente, minimul și maximul. Să ne concetrăm pe maxim, minimul rezolvându-se similar.&lt;br /&gt;
&lt;br /&gt;
O definire directă a subproblemei ar putea fi:&lt;br /&gt;
&lt;br /&gt;
 MAX[i][j] = maximul care se poate obține cu numerele nr[0], nr[1], ..., nr[i] folosind i operații, din care j înmulțiri.&lt;br /&gt;
&lt;br /&gt;
Are ea proprietatea de optimalitate?&lt;br /&gt;
&lt;br /&gt;
Să vedem: dacă luăm o soluție optimă și eliminăm ultimul număr și ultima operație, ceea ce rămâne este optim?&lt;br /&gt;
&lt;br /&gt;
Nu neapărat. Dacă ultima operație este o adunare, vom avea o subproblemă optimă, dar dacă este o înmulțire, nu.&lt;br /&gt;
&lt;br /&gt;
Ce facem? Observăm că optimalitatea se păstrează la adunări, nu și la înmulțiri. Deci putem spune că, dacă &amp;lt;tt&amp;gt;MAX[i][j]&amp;lt;/tt&amp;gt; este optim și are &amp;lt;tt&amp;gt;m&amp;lt;/tt&amp;gt; înmulțiri în coadă, atunci și &amp;lt;tt&amp;gt;MAX[i-m-1][j-m]&amp;lt;/tt&amp;gt; este optim, el fiind format din suma produselor anterioare, care dacă nu ar fi optimă am putea construi o sumă mai mare în &amp;lt;tt&amp;gt;MAX[i][j]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Astfel, formula de recurență este:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;MAX[i][j] = \begin{cases} nr[0]\cdot nr[1]\cdot\mbox{…}\cdot nr[i], &amp;amp; \mbox{dacă } i = j\\ \max\limits_{0 \leq m \leq j}{(MAX[i-m-1][j-m] + nr[i-m]\cdot nr[i-m+1]\cdot\mbox{…}\cdot nr[i])} &amp;amp; \mbox{în caz contrar}. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Răspunsul la cerință va fi: &amp;lt;tt&amp;gt;MAX[N][K]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Ea este &#039;&#039;O(N·K&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Înlocuind vom obține circa 2430 operații, un număr incredibil de mic. Probabil că mai mult va dura citirea celor 30 de numere.&lt;br /&gt;
&lt;br /&gt;
Memoria folosită este cea a matricei de dimensiune &amp;lt;tt&amp;gt;N x K&amp;lt;/tt&amp;gt;, deci &#039;&#039;O(N·K)&#039;&#039;, din nou neglijabilă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Exemplu &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iată exemplul din enunț calculat conform formulei de mai sus.&lt;br /&gt;
&lt;br /&gt;
Fie numerele de la intrare:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Indice !! 0 !! 1 !! 2 !! 3 !! 4 !! 5&lt;br /&gt;
|-&lt;br /&gt;
! Valoare || 2 || 0 || 3 || -1 || 7 || -4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Calculul expresiilor maxime, pe linii, de la stânga la dreapta:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! rowspan=&amp;quot;2&amp;quot; | Numere !! colspan=&amp;quot;4&amp;quot; | Înmulțiri&lt;br /&gt;
|-&lt;br /&gt;
! 0 !! 1 !! 2 !! 3&lt;br /&gt;
|-&lt;br /&gt;
| 0 || 2 || || || &lt;br /&gt;
|-&lt;br /&gt;
| 1 || 2 || 0 || || &lt;br /&gt;
|-&lt;br /&gt;
| 2 || 5 || 3 || 0 || &lt;br /&gt;
|-&lt;br /&gt;
| 3 || 4 || 2 || 2 || 0&lt;br /&gt;
|-&lt;br /&gt;
| 4 || 11 || 9 || 9 || 7&lt;br /&gt;
|-&lt;br /&gt;
| 5 || 7 || 5 || 33 || 86&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Joc6 ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/joc6 Joc6] a fost dată la ONI 2011 baraj gimnaziu. Este o problemă tipică de programare dinamică.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluția comisiei ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Pentru fiecare jucător se determină secvenţele de bile cu valori consecutive. Acest lucru se poate realiza prin utilizarea unui vector caracteristic.&lt;br /&gt;
&lt;br /&gt;
După determinarea secvenţelor, se selectează secvenţa de sumă maximă ce poate fi obţinută prin utilizarea bilelor speciale, în funcţie de numărul de bile speciale pe care le are jucătorul curent. Se determină maximul sumelor maxime şi jucătorul care are acest maxim.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Din păcate explicațiile sunt destul de vagi. Ce bănuiesc eu:&lt;br /&gt;
&lt;br /&gt;
* Vom face o listă de secvențe, păstrând pentru fiecare secvență: (indice start, indice final, suma)&lt;br /&gt;
* Vom încerca să cuplăm secvențe astfel:&lt;br /&gt;
** Fie două secvențe consecutive dacă ele sunt despărțite de cel mult numărul de bile zero disponibile.&lt;br /&gt;
** Fie trei secvențe consecutive dacă avem măcar două bile zero, iar secvențele sunt despărțite de fix un zero.&lt;br /&gt;
&lt;br /&gt;
Desigur vom lua maximul sumelor.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Pentru detecția secvențelor avem nevoie de un vector de frecvență, deci &#039;&#039;O(n + p)&#039;&#039;. Pentru parcurgerea secvențelor avem nevoie de &#039;&#039;O(p)&#039;&#039; pași. Deoarece avem &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; jucători complexitatea totală este &#039;&#039;O(k·(n + p)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Dar memoria? Din cauza vectorului de frecvență vom avea nevoie de &#039;&#039;O(n + p)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
Este o soluție rezonabilă, poate nu foarte grea, dar care necesită creativitate și gândire algoritmică.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție cu programare dinamică ===&lt;br /&gt;
&lt;br /&gt;
Această soluție este destul de evidentă. Din păcate ea nu necesită prea multă gândire, ceea ce, după părerea mea, face această problemă să nu fie așa de bună pentru concurs. Totuși, fiind vorba despre baraj gimnaziu, nu este grav.&lt;br /&gt;
&lt;br /&gt;
Putem defini problema de programare dinamică astfel:&lt;br /&gt;
&lt;br /&gt;
 S[b][i] = suma maximă ce începe cu bila i, ce se poate obține folosind b bile zero&lt;br /&gt;
&lt;br /&gt;
Unde &amp;lt;tt&amp;gt;b&amp;lt;/tt&amp;gt; poate fi 0, 1, sau 2 în problema noastră, dar nimic nu ne împiedică să îl mărim.&lt;br /&gt;
&lt;br /&gt;
Demonstrația proprietății de optimalitate a subproblemelor este destul de ușoară, v-o las ca temă de gândire.&lt;br /&gt;
&lt;br /&gt;
Recurența este, iarăși, destul de clară:&lt;br /&gt;
&lt;br /&gt;
* Dacă bila &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; există la intrare, atunci o adăugăm la secvența anterioară ce începe cu bila &amp;lt;tt&amp;gt;i+1&amp;lt;/tt&amp;gt;.&lt;br /&gt;
* Dacă bila &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; nu există la intrare, atunci folosim o bilă zero. Vom avea deci aceeași valoare cu o secvență ce folosește mai puțin cu unu bile zero și care începe cu bila &amp;lt;tt&amp;gt;i+1&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Formula de recurență este:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;S[b][i] = \begin{cases} 0, &amp;amp; \mbox{dacă } i &amp;gt; n \mbox{ sau } b &amp;lt; 0\\S[b-1][i+1] &amp;amp; \mbox{dacă bila i nu există}\\i + S[b][i+1] &amp;amp; \mbox{dacă bila i există} \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Obținem un algoritm de complexitate similară ca timp și memorie.&lt;br /&gt;
&lt;br /&gt;
Programul iese foarte scurt, circa 40 de linii efective, mulțumită programării dinamice.&lt;br /&gt;
&lt;br /&gt;
Posibil să fie și ceva mai eficient, dar acest lucru nu contează prea mult deoarece timpul va fi dominat de citire.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Să presupunem că avem următoarele bile la intrare:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Bile&lt;br /&gt;
| 2 || 0 || 6 || 4 || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Atunci vom calcula tabelul următor:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! colspan=&amp;quot;2&amp;quot; | &lt;br /&gt;
! colspan=&amp;quot;6&amp;quot; | Bile&lt;br /&gt;
|-&lt;br /&gt;
! &lt;br /&gt;
! &lt;br /&gt;
! 1&lt;br /&gt;
! 2&lt;br /&gt;
! 3&lt;br /&gt;
! 4&lt;br /&gt;
! 5&lt;br /&gt;
! 6&lt;br /&gt;
|-&lt;br /&gt;
! rowspan=&amp;quot;3&amp;quot; | Zerouri&lt;br /&gt;
! 0&lt;br /&gt;
| 0 || 2 || 0 || 4 || 0 || 6&lt;br /&gt;
|-&lt;br /&gt;
! 1&lt;br /&gt;
| 2 || 6 || 4 || 10 || 6 || 6&lt;br /&gt;
|-&lt;br /&gt;
! 2&lt;br /&gt;
| 6 || 12 || 10 || 10 || 6 || 6&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Maximul în acest tabel este 12, care este și răspunsul cerut.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Zmax ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/zmax Zmax] a fost dată la ONI 2013 baraj gimnaziu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție cu programare dinamică ===&lt;br /&gt;
&lt;br /&gt;
Singura soluție de 100p a comisiei este cu programare dinamică.&lt;br /&gt;
&lt;br /&gt;
Vom defini următoarele subprobleme:&lt;br /&gt;
&lt;br /&gt;
 S[l][c] = cea mai mare sumă de elemente contigue aflate pe linia l, care se termină la coloana c&lt;br /&gt;
 D[l][c] = cea mai mare sumă de elemente contigue aflate pe linia l, care încep la coloana c&lt;br /&gt;
 N7[l][c] = cea mai mare sumă de elemente aflate în matrice în formă de cifra &#039;7&#039; care se termină la (l, c).&lt;br /&gt;
 Z[l][c] = cea mai mare sumă de elemente aflate în matrice în formă de &#039;Z&#039; a cărei linie de sus se termină la (l, c).&lt;br /&gt;
&lt;br /&gt;
* Primele două subprobleme se pot calcula folosind algoritmul clasic al lui Kadane.&lt;br /&gt;
* Subproblema &amp;lt;tt&amp;gt;N7&amp;lt;/tt&amp;gt; se calculează pe baza subproblemelor &amp;lt;tt&amp;gt;S&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;N7&amp;lt;/tt&amp;gt;, similar cu Kadane: putem continua 7-lea existent, sau să începem unul nou.&lt;br /&gt;
* Subproblema &amp;lt;tt&amp;gt;Z&amp;lt;/tt&amp;gt; se calculează pe baza subproblemelor &amp;lt;tt&amp;gt;D&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;N7&amp;lt;/tt&amp;gt;, similar cu Kadane: putem continua &amp;lt;tt&amp;gt;Z&amp;lt;/tt&amp;gt;-ul existent, sau să începem unul nou.&lt;br /&gt;
&lt;br /&gt;
Maximul din subproblema &amp;lt;tt&amp;gt;Z&amp;lt;/tt&amp;gt; este răspunsul la cerință.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Alte probleme&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/flori2 Flori2] a fost dată la ONI 2010 baraj gimnaziu. Nu este o problemă foarte grea, dar are multe detalii.&lt;br /&gt;
&lt;br /&gt;
Puteți găsi alte probleme căutând pe [https://www.nerdarena.ro/ NerdArena] după tagul &#039;&#039;programare dinamică&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 26 ==&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/optim Optim] dată la ONI 2012 clasa a 8-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/joc6 Joc6] dată la ONI 2011 baraj gimnaziu&lt;br /&gt;
* [https://www.nerdarena.ro/problema/faleza Faleza] dată la ONI 2017 clasa a 6-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională == &lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/poteci Poteci] dată la ONI 2011 baraj gimnaziu&lt;br /&gt;
* [https://www.nerdarena.ro/problema/zmax Zmax] dată la ONI 2013 baraj gimnaziu&lt;br /&gt;
* [https://www.nerdarena.ro/problema/flori2 Flori2] dată la ONI 2010 baraj gimnaziu&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_26:_Programare_dinamic%C4%83_(3) Accesează rezolvarea temei 26]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_25:_Programare_dinamic%C4%83_(2)&amp;diff=18583</id>
		<title>Clasa a 7-a Lecția 25: Programare dinamică (2)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_25:_Programare_dinamic%C4%83_(2)&amp;diff=18583"/>
		<updated>2026-02-06T16:53:43Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/z4433kBdq2A|||||start=5580&amp;amp;end=9780&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema rucsacului ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; veți avea la temă ca aplicație problema [https://www.nerdarena.ro/problema/rucsac1 Rucsac1].&lt;br /&gt;
&lt;br /&gt;
Se dă o mulțime formată din &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; obiecte, fiecare având o greutate și o valoare. Într-un rucsac putem încărca o greutate maxim &amp;lt;tt&amp;gt;G&amp;lt;/tt&amp;gt;. Ne dorim să încărcăm rucsacul cu obiecte astfel încât să maximizăm valoarea totală.&lt;br /&gt;
&lt;br /&gt;
Problema este una foarte practică, ne dorim să optimizăm raportul valoare/greutate, încărcând obiecte cât mai valoroase, dar cât mai ușoare.&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
Să presupunem că avem următoarele obiecte:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Nr. || 1 || 2 || 3 || 4 || 5 || 6&lt;br /&gt;
|-&lt;br /&gt;
!  Greutate&lt;br /&gt;
| 3 || 3 || 1 || 1 || 2 || 1&lt;br /&gt;
|-&lt;br /&gt;
! Valoare&lt;br /&gt;
| 7 || 4 || 2 || 9 || 4 || 5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Rucsacul are mărime 10. Care este valoarea maximă a obiectelor ce pot fi încărcate în rucsac?&lt;br /&gt;
&lt;br /&gt;
Singura posibilitate este să alegem obiectele &amp;lt;tt&amp;gt;{ 1, 2, 4, 5, 6 }&amp;lt;/tt&amp;gt;. Ele au greutatea totală 10 și valoarea totală 29.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Încercarea 1: după obiecte ===&lt;br /&gt;
&lt;br /&gt;
Definirea problemei de programare dinamică nu este evidentă. Voi încerca să arăt un mod de gândire posibil, care implică mai multe posibilități pe care le vom verifica și apoi elimina, ajungând în final la o definire bună a problemei.&lt;br /&gt;
&lt;br /&gt;
Să definim&lt;br /&gt;
&lt;br /&gt;
 V[i] = valoarea maximă ce se poate obține cu primele i greutăți, astfel încât greutatea totală să nu depășească G&lt;br /&gt;
&lt;br /&gt;
Are &amp;lt;code&amp;gt;V[i]&amp;lt;/code&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;code&amp;gt;V[i]&amp;lt;/code&amp;gt;. Ea va conține unele din primele &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; obiecte. Ultimul obiect este obiectul &amp;lt;tt&amp;gt;k ≤ i&amp;lt;/tt&amp;gt;. Atunci este &amp;lt;code&amp;gt;V[k]&amp;lt;/code&amp;gt; optimă?&lt;br /&gt;
&lt;br /&gt;
Nu neapărat! Este posibil să găsim o soluție &amp;lt;code&amp;gt;V[k]&amp;lt;/code&amp;gt; cu valoare mai mare, dar pe care să nu o putem folosi în construcția problemei &amp;lt;code&amp;gt;V[i]&amp;lt;/code&amp;gt; deoarece am depăși greutatea.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie:&#039;&#039;&#039; nu putem folosi programarea dinamică cu o astfel de definire a problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Încercarea 2: după greutatea totală ===&lt;br /&gt;
&lt;br /&gt;
Să facem o altă încercare. Să definim&lt;br /&gt;
&lt;br /&gt;
 V[g] = valoarea maximă ce se poate obține într-un rucsac de mărime g&lt;br /&gt;
&lt;br /&gt;
Are &amp;lt;code&amp;gt;V[g]&amp;lt;/code&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;code&amp;gt;V[g]&amp;lt;/code&amp;gt;. Ea va conține unele obiecte, a căror sumă a greutăților nu depășește &amp;lt;tt&amp;gt;g&amp;lt;/tt&amp;gt;. Fie ultimul obiect folosit obiectul &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; ce are greutate &amp;lt;tt&amp;gt;G&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;. Este &amp;lt;code&amp;gt;V[g-G&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; optimă?&lt;br /&gt;
&lt;br /&gt;
Răspunsul este: nu, deoarece s-ar putea ca optimul să includă &amp;lt;code&amp;gt;V[g]&amp;lt;/code&amp;gt; (pe care nu avem voie să o folosim de două ori).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie:&#039;&#039;&#039; nu putem folosi programarea dinamică nici cu această definire a problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Încercarea 3: după obiecte și greutate totală ===&lt;br /&gt;
&lt;br /&gt;
Să încercăm să combinăm cele două încercări de mai sus. Să definim&lt;br /&gt;
&lt;br /&gt;
 V[i][g] = valoarea maximă ce se poate obține cu primele i greutăți într-un rucsac de mărime g&lt;br /&gt;
&lt;br /&gt;
Are &amp;lt;code&amp;gt;V[i][g]&amp;lt;/code&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă &amp;lt;code&amp;gt;V[i][g]&amp;lt;/code&amp;gt;. Fie ultimul obiect folosit obiectul &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; ce are greutate &amp;lt;tt&amp;gt;G&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;. Este &amp;lt;code&amp;gt;V[k][g-G&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; optimă? Să presupunem că nu ar fi. Atunci am putea găsi o valoare mai mare, folosind primele &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; obiecte ce vor încăpea în greutatea &amp;lt;tt&amp;gt;g-G&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;. Putem folosi această valoare pentru a obține o valoare mai mare pentru problema originală, &amp;lt;code&amp;gt;V[i][g]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie:&#039;&#039;&#039; putem folosi programarea dinamică cu această definiție a problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Construim formula de recurență. Pentru a calcula &amp;lt;code&amp;gt;V[i][g]&amp;lt;/code&amp;gt; avem două variante:&lt;br /&gt;
&lt;br /&gt;
* Fie nu folosim greutatea &amp;lt;tt&amp;gt;G&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;, caz în care vom obține valoarea &amp;lt;code&amp;gt;V[i-1][g]&amp;lt;/code&amp;gt; (aceeași greutate, dar folosind primele &amp;lt;tt&amp;gt;i-1&amp;lt;/tt&amp;gt; obiecte).&lt;br /&gt;
* Fie folosim greutatea &amp;lt;tt&amp;gt;G&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;, caz în care vom obține valoarea &amp;lt;code&amp;gt;V[i-1][g-G&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;] + Pi&amp;lt;/code&amp;gt; (greutatea este cea care rămâne în rucsac după ce folosim &amp;lt;tt&amp;gt;G&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;, și apoi putem folosi primele &amp;lt;tt&amp;gt;i-1&amp;lt;/tt&amp;gt; obiecte).&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;V[i][g] = \begin{cases} 0, &amp;amp; \text{dacă } i = 0 \text{ sau } g = 0 \\ max(V[i-1][g], V[i-1][g-G_{i}] + P_{i}) &amp;amp; \text{în caz contrar}. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unde &amp;lt;tt&amp;gt;(G&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, P&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;)&amp;lt;/tt&amp;gt; sunt greutatea, respectiv valoarea obiectului &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Răspunsul la problemă este elementul &amp;lt;code&amp;gt;V[N][G]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
Fie obiectele din exemplul de mai sus:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Nr. || 1 || 2 || 3 || 4 || 5 || 6&lt;br /&gt;
|-&lt;br /&gt;
!  Greutate&lt;br /&gt;
| 3 || 3 || 1 || 1 || 2 || 1&lt;br /&gt;
|-&lt;br /&gt;
! Valoare&lt;br /&gt;
| 7 || 4 || 2 || 9 || 4 || 5&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Vom completa, pe linii, următorul tabel:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Obiect !! 0 !! 1 !! 2 !! 3 !! 4 !! 5 !! 6 !! 7 !! 8 !! 9 !! 10&lt;br /&gt;
|-&lt;br /&gt;
!0&lt;br /&gt;
|0||0||0||0||0||0||0||0||0||0||0&lt;br /&gt;
|-&lt;br /&gt;
!1&lt;br /&gt;
|0||0||0||7||7||7||7||7||7||7||7&lt;br /&gt;
|-&lt;br /&gt;
!2&lt;br /&gt;
|0||0||0||7||7||7||11||11||11||11||11&lt;br /&gt;
|-&lt;br /&gt;
!3&lt;br /&gt;
|0||2||2||7||9||9||11||13||13||13||13&lt;br /&gt;
|-&lt;br /&gt;
!4&lt;br /&gt;
|0||9||11||11||16||18||18||20||22||22||22&lt;br /&gt;
|-&lt;br /&gt;
!5&lt;br /&gt;
|0||9||11||13||16||18||20||22||22||24||26&lt;br /&gt;
|-&lt;br /&gt;
!6&lt;br /&gt;
|0||9||14||16||18||21||23||25||27||27||29&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Analiză ===&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(N·G)&#039;&#039;. Memoria folosită este &#039;&#039;O(G)&#039;&#039; dacă memorăm doar o linie din matrice. Dacă dorim să aflăm și o soluție maximală (obiectele din componența soluției), nu doar mărimea ei, vom avea nevoie de memorie &#039;&#039;O(N·G)&#039;&#039;, precum vom vedea mai jos.&lt;br /&gt;
&lt;br /&gt;
Există un algoritm care reduce necesarul de memorie la &#039;&#039;O(G)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Reconstrucția soluției ===&lt;br /&gt;
&lt;br /&gt;
Dorim să aflăm nu doar valoarea maximă, ci și obiectele selectate. Cum procedăm?&lt;br /&gt;
&lt;br /&gt;
Pentru a putea reconstrui soluția vom memora în fiecare căsuță, direcția din care provine ea, adică din care am calculat acel maxim. Direcția poate fi spre stânga-sus sau spre în sus. Apoi vom porni din elementul &amp;lt;code&amp;gt;V[N][G]&amp;lt;/code&amp;gt; și vom urma aceste direcții până ce ieșim din matrice. De fiecare dată când urmăm o diagonală reținem obiectul selectat, care este chiar rândul curent. Astfel, descoperirea obiectelor se face în ordine inversă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Distanța edit (Levenshtein) ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Se dau două șiruri de caractere, &amp;lt;tt&amp;gt;X&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;Y&amp;lt;/tt&amp;gt;. Putem efectua trei operațiuni asupra șirului &amp;lt;tt&amp;gt;X&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Inserare caracter la orice poziție&lt;br /&gt;
* Ștergere caracter la orice poziție&lt;br /&gt;
* Suprascriere - înlocuire a unui caracter cu un alt caracter&lt;br /&gt;
&lt;br /&gt;
Care este numărul minim de operații asupra șirului &amp;lt;tt&amp;gt;X&amp;lt;/tt&amp;gt; care îl transformă în șirul &amp;lt;tt&amp;gt;Y&amp;lt;/tt&amp;gt;?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Definim [https://en.wikipedia.org/wiki/Edit_distance distanța edit] ca fiind acest număr minim de operații&#039;&#039;&#039;. Deoarece operațiile sunt unele frecvente într-un editor de texte, de aici vine și denumirea de distanță edit.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Exemplu ====&lt;br /&gt;
&lt;br /&gt;
Acest exemplu este inspirat de la wikipedia.&lt;br /&gt;
&lt;br /&gt;
Fie șirurile:&lt;br /&gt;
&lt;br /&gt;
 X: KITTEN&lt;br /&gt;
 Y: SITTING&lt;br /&gt;
&lt;br /&gt;
Numărul minim de operații pentru a transforma &amp;lt;tt&amp;gt;X&amp;lt;/tt&amp;gt; în &amp;lt;tt&amp;gt;Y&amp;lt;/tt&amp;gt; este 3. De exemplu:&lt;br /&gt;
&lt;br /&gt;
# Înlocuim &amp;lt;tt&amp;gt;K&amp;lt;/tt&amp;gt; cu &amp;lt;tt&amp;gt;S&amp;lt;/tt&amp;gt;: &amp;lt;tt&amp;gt;KITTEN → SITTEN&amp;lt;/tt&amp;gt;&lt;br /&gt;
# Înlocuim &amp;lt;tt&amp;gt;E&amp;lt;/tt&amp;gt; cu &amp;lt;tt&amp;gt;I&amp;lt;/tt&amp;gt;: &amp;lt;tt&amp;gt;SITTEN → SITTIN&amp;lt;/tt&amp;gt;&lt;br /&gt;
# Adăugăm la final un &amp;lt;tt&amp;gt;G&amp;lt;/tt&amp;gt;: &amp;lt;tt&amp;gt;SITTIN → SITTING&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Soluție ====&lt;br /&gt;
&lt;br /&gt;
Problema este aproape identică cu subsecvența maximală comună a două șiruri. Vom defini:&lt;br /&gt;
&lt;br /&gt;
 D(i, j) = numărul minim de operații ce transformă șirul X[1..i]] în șirul Y[1..j]&lt;br /&gt;
&lt;br /&gt;
Nu voi face demonstrația optimalității subproblemelor, deoarece este similară cu ce de la subsecvența maximală comună a două șiruri.&lt;br /&gt;
&lt;br /&gt;
Obținem relația de recurență:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;\operatorname{D}_{X,Y}(i,j) = \begin{cases} \max(i,j) &amp;amp; \text{ dacă } \min(i,j)=0, \\ \min \begin{cases} \operatorname{D}_{X,Y}(i-1,j) + 1 \\ \operatorname{D}_{X,Y}(i,j-1) + 1 \\ \operatorname{D}_{X,Y}(i-1,j-1) + X_i \neq Y_j \end{cases} &amp;amp; \text{ în caz contrar.} \end{cases} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Distanța edit între cele două șiruri este &amp;lt;tt&amp;gt;D(m, n)&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Analiză ====&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(m·n)&#039;&#039;, unde &amp;lt;tt&amp;gt;m&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; sunt lungimile șirurilor. Memoria folosită este &#039;&#039;O(min(m, n))&#039;&#039; dacă memorăm doar o linie din matrice. Dacă dorim să aflăm și operațiile efectuate, nu doar numărul lor, vom avea nevoie de memorie &#039;&#039;O(m·n)&#039;&#039;, precum vom vedea mai jos.&lt;br /&gt;
&lt;br /&gt;
Există un algoritm care reduce necesarul de memorie la &#039;&#039;O(min(m, n))&#039;&#039; chiar și cu reconstrucția soluției.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Reconstrucția soluției ====&lt;br /&gt;
&lt;br /&gt;
Dorim să aflăm nu doar distanța dintre cele două șiruri, ci și o secvență de operații. Cum procedăm? În mod similar cu subsecvența maximală comună.&lt;br /&gt;
&lt;br /&gt;
Pentru a putea reconstrui soluția vom memora în fiecare căsuță din matrice direcția din care provine ea, adică din care am calculat acel maxim. Direcția poate fi spre stânga, spre sus, sau în diagonală stânga-sus. Apoi vom porni din elementul &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt;D[m][n]&amp;lt;/code&amp;gt; și vom urma aceste direcții până ce ieșim din matrice. Astfel:&lt;br /&gt;
&lt;br /&gt;
* Direcția spre stânga înseamnă o inserare de caracter.&lt;br /&gt;
* Direcție spre sus înseamnă o ștergere de caracter.&lt;br /&gt;
* Direcția diagonală înseamnă:&lt;br /&gt;
** O suprascriere dacă caracterele respective sunt diferite.&lt;br /&gt;
** Nimic (nici o operație) dacă caracterele sunt egale.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Înmulțirea optimală a unui șir de matrice ===&lt;br /&gt;
&lt;br /&gt;
Se dă un produs matricial de &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; matrice. Înmulțirea matricelor este asociativă, deci nu contează în ce ordine efectuăm înmulțirile. Dar numărul de operații elementare va diferi în funcție de ordine. Să se găsească o ordine de înmulțire care minimizează numărul de înmulțiri elementare.&lt;br /&gt;
&lt;br /&gt;
Vom discuta pe scurt această problemă, deoarece matematica necesară depășește nivelul clasei a șaptea.&lt;br /&gt;
&lt;br /&gt;
Cei interesați pot rezolva problema: [http://nerdarena.ro/problema/pdm Pdm] ca aplicație.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Numărare optime prin programarea dinamică ==&lt;br /&gt;
&lt;br /&gt;
Câte soluții distincte posibile admite o problemă de programare dinamică?&lt;br /&gt;
&lt;br /&gt;
Orice problemă de programare dinamică admite un optim. Soluția optimă nu este, de obicei, unică. Putem, deci, pune întrebarea:&lt;br /&gt;
&lt;br /&gt;
* Care este numărul de soluții optime ale unei probleme de programare dinamică?&lt;br /&gt;
&lt;br /&gt;
Răspunsul se poate da foarte ușor, folosind același tabel cu care calculăm subproblemele. Vom memora, pe lângă valoarea maximă (sau minimă) și numărul de moduri în care se poate ajunge la ea.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu: subsecvența crescătoare de lungime maximă ===&lt;br /&gt;
&lt;br /&gt;
Să luăm exemplul de data trecută: &lt;br /&gt;
&lt;br /&gt;
 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15&lt;br /&gt;
&lt;br /&gt;
Avem formula de recurență: &lt;br /&gt;
 &lt;br /&gt;
 L[i] = max(L[j]+1), j &amp;gt; i și V[j] ≥ V[i]&lt;br /&gt;
&lt;br /&gt;
Vom calcula și &amp;lt;code&amp;gt;MAX[i]&amp;lt;/code&amp;gt; ca fiind suma tuturor &amp;lt;code&amp;gt;MAX[j]&amp;lt;/code&amp;gt; pentru acei indecși &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; unde relația de maxim este satisfăcută.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Exemplu:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! V&lt;br /&gt;
| 0 || 8 || 4 || 12 || 2 || 10 || 6 || 14 || 1 || 9 || 5 || 13 || 3 || 11 || 7 || 15&lt;br /&gt;
|-&lt;br /&gt;
! L&lt;br /&gt;
| 6 || 4 || 5 || 3 || 5 || 3 || 4 || 2 || 4 || 3 || 3 || 2 || 3 || 2 || 2 || 1&lt;br /&gt;
|-&lt;br /&gt;
! MAX&lt;br /&gt;
| 4 || 7 || 2 || 2 || 2 || 3 || 2 || 1 || 7 || 2 || 3 || 1 || 2 || 1 || 1 || 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Avem, deci, patru secvențe crescătoare de lungime maximală, anume 6. Toate încep cu elementul 0. Două dintre ele continuă cu elementul 4, celelalte două cu elementul 2.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Explicație:&#039;&#039;&#039; elementul curent se poate atașa oricărei secvențe maximale anterior găsite. Dacă o secvență anterioară avea &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; posibilități de formare, atunci și noile secvențe formate vor fi tot în număr de &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt;. Dacă avem mai multe posibilități de secvențe anterioare, toate vor contribui în egală măsură. De aceea le însumăm.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Discuție: problema rucsacului ===&lt;br /&gt;
&lt;br /&gt;
Discuție despre cum calculăm numărul de soluții posibile în cazul problemei rucsacului: pe baza relației de recurență calculăm numărul de soluții. El se poate &#039;&#039;transfera&#039;&#039; de la elementul generator. Sau, în caz că ambele moduri de generare duc la același profit vom însuma numărul de soluții.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Probleme suprapuse (numărare soluții neoptimale) ==&lt;br /&gt;
&lt;br /&gt;
Există unele probleme care nu cer să se determine un optim, ci să se numere soluții. Ele nu se rezolvă prin programare dinamică, deoarece nu se cere optimul. Ele sunt adesea confundate cu probleme de programare dinamică.&lt;br /&gt;
&lt;br /&gt;
În realitate ele sunt probleme de numărare, folosind structura de probleme suprapuse.&lt;br /&gt;
&lt;br /&gt;
Să luăm câteva exemple.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Rucsac ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/rucsac Rucsac] cere să se calculeze câte subsecvențe distincte (necontigue) au suma egală cu un număr dat.&lt;br /&gt;
&lt;br /&gt;
Problema este echivalentă cu cea a rucsacului: numerele de la intrare reprezintă atât greutatea cât și valoarea obiectelor. Însă trebuie să selectăm obiecte astfel încât suma greutăților să fie exact &amp;lt;tt&amp;gt;K&amp;lt;/tt&amp;gt;, numărul dat.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Exemplu ====&lt;br /&gt;
&lt;br /&gt;
Fie numerele: &amp;lt;tt&amp;gt;8 3 6 5 2&amp;lt;/tt&amp;gt; și numărul dat &amp;lt;tt&amp;gt;K = 11&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Vom avea trei subsecvențe distincte a căror sumă este 11:&lt;br /&gt;
&lt;br /&gt;
 8 3&lt;br /&gt;
 3 6 2&lt;br /&gt;
 6 5&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Soluție ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație importantă:&#039;&#039;&#039; Nu putem folosi definiția anterioară a problemei, deoarece ea admite soluții care nu &#039;&#039;umplu&#039;&#039; exact rucsacul. Aceste soluții nu sunt admisibile.&lt;br /&gt;
&lt;br /&gt;
Vom defini problema ușor diferit:&lt;br /&gt;
&lt;br /&gt;
 V[i][g] = valoarea maximă ce se poate obține cu primele i greutăți într-un rucsac de mărime g complet umplut. Valoarea va fi zero dacă nu putem umple greutatea g.&lt;br /&gt;
&lt;br /&gt;
În cazul nostru, deoarece valoarea este egală cu greutatea acea valoare va fi chiar greutatea &amp;lt;tt&amp;gt;g&amp;lt;/tt&amp;gt;. Problema se transformă în există un mod de a forma o greutate &amp;lt;tt&amp;gt;g&amp;lt;/tt&amp;gt; cu primele &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; obiecte? Răspunsul este unu dacă este afirmativ, sau zero dacă nu se poate forma o greutate. Matricea va fi o matrice de zero și unu.&lt;br /&gt;
&lt;br /&gt;
Formula de recurență se modifică și ea, puțin:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;V[i][g] = \begin{cases} 0, &amp;amp; \mbox{dacă } i = 0 \text{ și } g &amp;gt; 0 \\ 1, &amp;amp; \text{dacă } g = 0 \\ or(V[i-1][g], V[i-1][g-G_{i}]) &amp;amp; \text{în caz contrar}. \end{cases} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
unde &amp;lt;tt&amp;gt;or(x, y)&amp;lt;/tt&amp;gt; este 1 dacă măcar unul dintre &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt; este 1, sau zero în caz contrar.&lt;br /&gt;
&lt;br /&gt;
Pe ultima linie a matricei &amp;lt;tt&amp;gt;V&amp;lt;/tt&amp;gt; vom avea 1 pentru greutățile ce se pot obține.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Acum: cum modificăm soluția să calculeze nu doar dacă o greutate se poate obține, ci în câte moduri distincte?&lt;br /&gt;
&lt;br /&gt;
Vom calcula:&lt;br /&gt;
&lt;br /&gt;
 NSOL[i][g] = numărul de moduri în care putem forma greutatea g folosind primele i numere. Acest număr va fi zero dacă greutatea nu se poate obține.&lt;br /&gt;
&lt;br /&gt;
Formula de recurență este aproape identică cu cea anterioară.&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;NSOL[i][g] = \begin{cases} 0, &amp;amp; \text{dacă } i = 0 \mbox{ și } g &amp;gt; 0 \\ 1, &amp;amp; \text{dacă } g = 0 \\ NSOL[i-1][g] + NSOL[i-1][g-G_{i}] &amp;amp; \text{în caz contrar}. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Numărul de moduri în care putem alege numere de sumă &amp;lt;tt&amp;gt;K&amp;lt;/tt&amp;gt; este &amp;lt;code&amp;gt;NSOL[N][K]&amp;lt;/code&amp;gt;, elementul din colțul din dreapta-jos al matricei &amp;lt;code&amp;gt;NSOL&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Exemplu ====&lt;br /&gt;
&lt;br /&gt;
Pentru exemplul anterior, avem numerele: &amp;lt;tt&amp;gt;8 3 6 5 2&amp;lt;/tt&amp;gt; și numărul dat K = 11.&lt;br /&gt;
&lt;br /&gt;
Vom calcula tabelul:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! !!0!!1!!2!!3!!4!!5!!6!!7!!8!!9!!10!!11&lt;br /&gt;
|-&lt;br /&gt;
!0&lt;br /&gt;
|1||0||0||0||0||0||0||0||0||0||0||0&lt;br /&gt;
|-&lt;br /&gt;
!1&lt;br /&gt;
|1||0||0||0||0||0||0||0||1||0||0||0&lt;br /&gt;
|-&lt;br /&gt;
!2&lt;br /&gt;
|1||0||0||1||0||0||0||0||1||0||0||1&lt;br /&gt;
|-&lt;br /&gt;
!3&lt;br /&gt;
|1||0||0||1||0||0||1||0||1||1||0||1&lt;br /&gt;
|-&lt;br /&gt;
!4&lt;br /&gt;
|1||0||0||1||0||1||1||0||2||1||0||2&lt;br /&gt;
|-&lt;br /&gt;
!5&lt;br /&gt;
|1||0||1||1||0||2||1||1||3||1||2||3&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Avem, deci, trei moduri de a obține suma 11.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Analiză ====&lt;br /&gt;
&lt;br /&gt;
Algoritmul are aceeași complexitate în timp și memorie ca și problema originală, anume &#039;&#039;O(N·K)&#039;&#039; timp și &#039;&#039;O(K)&#039;&#039; memorie, dacă memorăm doar o linie a matricei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Cifreco ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/cifreco Cifreco] a fost dată la ONI 2012 baraj gimnaziu. Deși se poate rezolva cu alte metode, ea are o rezolvare foarte eficientă și simplu de implementat folosind numărarea prin probleme suprapuse.&lt;br /&gt;
&lt;br /&gt;
Problema cere ca, dîndu-se două numere &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt;, să se găsească câte numere există între aceste valori cu proprietățile:&lt;br /&gt;
&lt;br /&gt;
* Au &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; cifre, tot atâtea cifre câte au și numerele &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt;.&lt;br /&gt;
* Suma cifrelor lor este &amp;lt;tt&amp;gt;N+8&amp;lt;/tt&amp;gt; (la fel ca și &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Soluție ====&lt;br /&gt;
&lt;br /&gt;
Să ne propunem mai întâi să rezolvăm o problemă mai simplă: câte numere de &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; cifre au suma cifrelor &amp;lt;tt&amp;gt;N+8&amp;lt;/tt&amp;gt;? Cu alte cuvinte nu vom ține cont de valorile &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Descompunerea în subprobleme suprapuse nu este foarte evidentă. Vom introduce o dimensiune auxiliară: vom varia suma cifrelor. Astfel, definim:&lt;br /&gt;
&lt;br /&gt;
 NSOL[N][S] = numărul de numere de N cifre a căror sumă este S.&lt;br /&gt;
&lt;br /&gt;
Ce formulă de recurență avem?&lt;br /&gt;
&lt;br /&gt;
Cum se poate forma un număr de &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; cifre? Punând pe prima poziție toate cifrele permise și apoi formând un număr cu &amp;lt;tt&amp;gt;N-1&amp;lt;/tt&amp;gt; cifre. Vom avea, deci:&lt;br /&gt;
&lt;br /&gt;
 NSOL[N][S] = NSOL[N-1][S-1] + NSOL[N-1][S-2] + ... + NSOL[N-1][N-1]&lt;br /&gt;
&lt;br /&gt;
Astfel:&lt;br /&gt;
&lt;br /&gt;
* Primul termen este numărul de numere care încep cu 1&lt;br /&gt;
* Al doilea termen este numărul de numere care încep cu 2&lt;br /&gt;
*...&lt;br /&gt;
* Ultimul termen este numărul de numere care încep cu &amp;lt;tt&amp;gt;S - N + 1&amp;lt;/tt&amp;gt;, aceasta fiind cea mai mare cifră pentru care cifrele rămase mai pot forma suma &amp;lt;tt&amp;gt;N-1&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Am putea să pornim la implementare. Însă mai putem face o optimizare. Să observăm că, conform definiției:&lt;br /&gt;
&lt;br /&gt;
 NSOL[N][S-1] = NSOL[N-1][S-2] + ... + NSOL[N-1][N-1]&lt;br /&gt;
&lt;br /&gt;
De aici rezultă o formulă mai simplă de calcul pentru &amp;lt;code&amp;gt;NSOL[N][S]&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 NSOL[N][S] = NSOL[N-1][S-1] + NSOL[N][S-1]&lt;br /&gt;
&lt;br /&gt;
Vom completa acest tabel pentru valori maxime. Astfel vom putea răspunde în &#039;&#039;O(1)&#039;&#039; la întrebări de forma:&lt;br /&gt;
&lt;br /&gt;
* Câte numere de &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; cifre au suma &amp;lt;tt&amp;gt;S&amp;lt;/tt&amp;gt;?&lt;br /&gt;
&lt;br /&gt;
Acum revenim la problema noastră. Trebuie să calculăm câte numere de &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; cifre au suma &amp;lt;tt&amp;gt;N+8&amp;lt;/tt&amp;gt; între &#039;&#039;&#039;x și y&#039;&#039;&#039;. Cum o rezolvăm?&lt;br /&gt;
&lt;br /&gt;
Ne propunem să calculăm câte numere sunt mai mici sau egale cu &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt;. Fie, de exemplu, &amp;lt;tt&amp;gt;k = 623&amp;lt;/tt&amp;gt;. Vom proceda astfel:&lt;br /&gt;
&lt;br /&gt;
* Vom calcula câte numere încep cu 1 și au suma cifrelor 11. Ele sunt toate mai mici strict ca 623. Vom folosi &amp;lt;code&amp;gt;NSOL[2][10]&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Vom calcula câte numere încep cu 2 și au suma cifrelor 11. Vom folosi &amp;lt;code&amp;gt;NSOL[2][9]&amp;lt;/code&amp;gt;.&lt;br /&gt;
* ...&lt;br /&gt;
* Vom calcula câte numere încep cu 5 și au suma cifrelor 11. Vom folosi &amp;lt;code&amp;gt;NSOL[2][6]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
În acest moment ne-au rămas doar numere care încep cu 6. Câte vor fi ele? Atâtea câte numere există mai mici ca 23, a căror sumă a cifrelor să fie 5. Reluăm, deci, problema de mai sus cu alți parametri, continuând să adunăm.&lt;br /&gt;
&lt;br /&gt;
Putem acum calcula:&lt;br /&gt;
&lt;br /&gt;
 NN(k) = numărul de numere mai mici sau egale cu un număr k.&lt;br /&gt;
&lt;br /&gt;
Pentru a răspunde la problemă vom calcula:&lt;br /&gt;
&lt;br /&gt;
 NN(x, y) = NN(y) - NN(x) + 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Calculul tabelului este &#039;&#039;O(N·Σ)&#039;&#039; - un număr foarte mic, circa 162. Mărimea tabelului va fi aceeași.&lt;br /&gt;
&lt;br /&gt;
Calculul răspunsului necesită o plimbare prin cifre, efectuată pentru fiecare cifră a numărului &amp;lt;tt&amp;gt;x&amp;lt;/tt&amp;gt; sau &amp;lt;tt&amp;gt;y&amp;lt;/tt&amp;gt;. Este, deci &#039;&#039;O(suma cifrelor) = O(N) - un număr și mai mic&#039;&#039;, circa 54.&lt;br /&gt;
&lt;br /&gt;
Aveți această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție!&#039;&#039;&#039; Calculul numărului de numere mai mici sau egale cu &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; se poate face mai ușor pornind de la cea mai din dreapta cifră, în loc de cea mai din stânga. Ideea este aceeași, doar ordinea de calcul se inversează.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Șir2 ===&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/sir2 Șir2] este o problemă tipică de numărare folosind probleme suprapuse. Ne vom ocupa doar de al doilea subpunct care cere să se calculeze în câte feluri se poate scrie un număr &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt; ca sumă de &amp;lt;tt&amp;gt;M&amp;lt;/tt&amp;gt; numere nenule. Ordinea termenilor contează.&lt;br /&gt;
&lt;br /&gt;
Vom avea o recurență similară cu cea de la [https://www.nerdarena.ro/problema/cifreco Cifreco]:&lt;br /&gt;
&lt;br /&gt;
 NSOL[M][S] = numărul de secvențe de numere nenule de lungime M a căror sumă este S.&lt;br /&gt;
&lt;br /&gt;
Recurența nu este foarte grea. Similar cu [https://www.nerdarena.ro/problema/cifreco Cifreco] avem:&lt;br /&gt;
&lt;br /&gt;
 NSOL[M][S] = NSOL[M-1][S-1] + NSOL[M-1][S-2] + ... + NSOL[M-1][M-1]&lt;br /&gt;
&lt;br /&gt;
Astfel:&lt;br /&gt;
&lt;br /&gt;
* Primul termen este numărul de secvențe care încep cu 1.&lt;br /&gt;
* Al doilea termen este numărul de secvențe care încep cu 2.&lt;br /&gt;
* ...&lt;br /&gt;
* Ultimul termen este numărul de secvențe care încep cu &amp;lt;tt&amp;gt;S-M+1&amp;lt;/tt&amp;gt;, aceasta fiind cel mai mare număr pentru care cifrele rămase mai pot forma suma &amp;lt;tt&amp;gt;M&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
La fel ca la [https://www.nerdarena.ro/problema/cifreco Cifreco], putem reduce această recurență la una mai simplă:&lt;br /&gt;
&lt;br /&gt;
 NSOL[M][S] = NSOL[M-1][S-1] + NSOL[M][S-1]&lt;br /&gt;
&lt;br /&gt;
Soluția problemei va fi elementul &amp;lt;code&amp;gt;NSOL[M][N]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Observații:&lt;br /&gt;
&lt;br /&gt;
* Aveți grijă la calcule, ele trebuie făcute modulo 104729.&lt;br /&gt;
* Din toate elementele unei sume putem scădea 1. Obținem un șir de elemente naturale, posibil nule, a căror sumă echivalentă trebuie micșorată cu &amp;lt;tt&amp;gt;M&amp;lt;/tt&amp;gt;.&lt;br /&gt;
* Astfel, problema se simplifică dacă:&lt;br /&gt;
** Considerați că numerele pot fi nule.&lt;br /&gt;
** Reduceți suma cerută, &amp;lt;tt&amp;gt;N&amp;lt;/tt&amp;gt;, înlocuind-o cu &amp;lt;tt&amp;gt;N-M&amp;lt;/tt&amp;gt;.&lt;br /&gt;
** Numărul de posibilități rămâne același, dar tabelul se simplifică.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm?&lt;br /&gt;
&lt;br /&gt;
El este &#039;&#039;O(M·(N-M))&#039;&#039; ca timp și memorie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 25 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/rucsac1 Rucsac1] ca aplicație la problema rucsacului.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/siruri2 Șiruri2] ca aplicație la distanța edit.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/rucsac Rucsac] ca aplicație de numărare cu probleme suprapuse.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cifreco Cifreco] dată la ONI 2012 baraj gimnaziu, ca aplicație de numărare cu probleme suprapuse.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/pdm Pdm] ca aplicație la înmulțirea optimală de matrice&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cutii Cutii] problemă simpatică&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_25:_Programare_dinamic%C4%83_(2) Accesează rezolvarea temei 25]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_24:_Programare_dinamic%C4%83_(1)&amp;diff=18582</id>
		<title>Clasa a 7-a Lecția 24: Programare dinamică (1)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_24:_Programare_dinamic%C4%83_(1)&amp;diff=18582"/>
		<updated>2026-02-06T16:51:45Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://www.youtube.com/watch?v=a0wFdh8DInQ|||||start=7600&amp;amp;end=11100&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Conceptul de programare dinamică ==&lt;br /&gt;
&lt;br /&gt;
=== Introducere ===&lt;br /&gt;
&lt;br /&gt;
Wikipedia definește [http://en.wikipedia.org/wiki/Dynamic_programming programarea dinamică] drept o metodă pentru rezolvarea unor probleme complexe prin descompunerea lor în subprobleme mai simple. Se poate aplica problemelor care prezintă proprietățile de &#039;&#039;suprapunere a subproblemelor&#039;&#039; și de &#039;&#039;substructură optimală&#039;&#039;. Atunci când se poate aplica, metoda ia mult mai puțin timp decât alte soluții naive.&lt;br /&gt;
&lt;br /&gt;
[[Image:Shortest_path_optimal_substructure.svg|frame|right|300px|Drumul cel mai scurt între două orașe poate fi rezolvat cu programare dinamică]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Substructura optimală&#039;&#039; înseamnă că soluția unei problemei de optimizare date poate fi obținută prin combinarea unor soluții optimale ale subproblemelor. Ca atare, primul pas către găsirea unei rezolvări prin această metodă este verificarea dacă problema are proprietatea substructurii optimale. &lt;br /&gt;
&lt;br /&gt;
Drumul cel mai scurt între două orașe poate fi rezolvat cu programare dinamică&lt;br /&gt;
&lt;br /&gt;
Astfel de substructuri optimale sunt în mod uzual descrise prin recurență. De exemplu, date niște orașe și distanțele între ele, drumul cel mai scurt de la orașul &amp;lt;tt&amp;gt;u&amp;lt;/tt&amp;gt; la orașul &amp;lt;tt&amp;gt;v&amp;lt;/tt&amp;gt; are proprietatea de substructură optimală: să luăm orice oraș intermediar &amp;lt;tt&amp;gt;w&amp;lt;/tt&amp;gt; pe drumul cel mai scurt, &amp;lt;tt&amp;gt;d&amp;lt;/tt&amp;gt;. Dacă &amp;lt;tt&amp;gt;d&amp;lt;/tt&amp;gt; este cu adevărat drumul cel mai scurt, atunci drumul &amp;lt;tt&amp;gt;d1&amp;lt;/tt&amp;gt; de la &amp;lt;tt&amp;gt;u&amp;lt;/tt&amp;gt; la &amp;lt;tt&amp;gt;w&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;d2&amp;lt;/tt&amp;gt; de la &amp;lt;tt&amp;gt;w&amp;lt;/tt&amp;gt; la &amp;lt;tt&amp;gt;v&amp;lt;/tt&amp;gt; sunt cu adevărat cele mai scurte drumuri între orașele corespunzătoare. Dacă drumul de la &amp;lt;tt&amp;gt;u&amp;lt;/tt&amp;gt; la &amp;lt;tt&amp;gt;w&amp;lt;/tt&amp;gt; nu ar fi cel mai scurt posibil ar însemna că am avea un alt drum mai scurt, ceea ce ar duce la un drum total &amp;lt;tt&amp;gt;d&amp;lt;/tt&amp;gt;, de la &amp;lt;tt&amp;gt;u&amp;lt;/tt&amp;gt; la &amp;lt;tt&amp;gt;v&amp;lt;/tt&amp;gt;, mai scurt decât cel original. Așadar, putem formula soluția problemei găsirii celui mai scurt drum într-o manieră recursivă, ceea ce fac algoritmii [https://en.wikipedia.org/wiki/Bellman-Ford_algorithm Bellman-Ford] și [https://en.wikipedia.org/wiki/Floyd-Warshall_algorithm Floyd-Warshall].&lt;br /&gt;
&lt;br /&gt;
Suprapunerea subproblemelor înseamnă că ele nu sunt independente, adică subproblemele au în comun alte subprobleme. Nu vom insista pe această proprietate decât atât cât să menționăm că dacă subproblemele nu se suprapun dar prezintă substructură optimală atunci putem aplica tehnica de [https://www.algopedia.ro/wiki/index.php/Clasa_a_7-a_Lec%C8%9Bia_18:_Divide_et_impera,_mergesort,_quicksort programare divide et impera (divide and conquer)]. De aceea [http://en.wikipedia.org/wiki/Quicksort quicksort] și [http://en.wikipedia.org/wiki/Mergesort mergesort] nu sunt clasificate ca probleme de programare dinamică.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Metodă ===&lt;br /&gt;
&lt;br /&gt;
Când încercăm să rezolvăm o problemă de programare dinamică întrebarea crucială la care trebuie să răspundem este &#039;&#039;putem calcula soluția finală pe baza unor subprobleme care sunt la rândul lor &#039;&#039;&#039;optimale&#039;&#039;&#039; &#039;&#039;? Pentru a răspunde la întrebare trebuie mai întâi să formulăm subproblemele. Dar pentru a formula subproblemele trebuie să avem în minte principiul substructurii optimale. Altfel s-ar putea să împărțim problema în subprobleme care nu au substructură optimală.&lt;br /&gt;
&lt;br /&gt;
Să recapitulăm: pentru a putea răspunde la întrebarea dacă o problemă admite o soluție prin programare dinamică trebuie să aflăm dacă are substructură optimală. Dar pentru a afla dacă are substructură optimală trebuie să găsim subproblemele. Pe care încercăm să le găsim folosind programarea dinamică.&lt;br /&gt;
&lt;br /&gt;
Nu cumva ne învârtim în cerc? Așa pare și așa și este, într-o oarecare măsură. Experiența este cea care ne învață cum să căutăm împărțirea în subprobleme astfel încât să obținem proprietatea de optimalitate. Tot experiența este cea care ne ajută să ne dăm seama rapid dacă împărțirea găsită are această proprietate sau nu.&lt;br /&gt;
&lt;br /&gt;
Odată ce demonstrăm că o împărțire în subprobleme se bazează doar pe subprobleme optimale, restul decurge, de obicei, destul de simplu: găsim o formulă de recurență, apoi o implementăm de jos în sus (sau bottom-up), pornind de la cele mai mici probleme, către cele mai mari, până la soluția cerută.&lt;br /&gt;
&lt;br /&gt;
În continuare vom lua câteva exemple.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Subsecvența crescătoare de lungime maximă ==&lt;br /&gt;
&lt;br /&gt;
Această problemă cere să găsim o subsecvență a unei secvențe de numere în care elementele sunt în ordine crescătoare, iar subsecvența este de lungime maximă. Subsecvența nu este neapărat contiguă, nici unică.&lt;br /&gt;
&lt;br /&gt;
Exemplu&lt;br /&gt;
&lt;br /&gt;
În secvența&lt;br /&gt;
&lt;br /&gt;
 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15&lt;br /&gt;
&lt;br /&gt;
o secvență crescătoare de lungime maximă este&lt;br /&gt;
&lt;br /&gt;
 0, 2, 6, 9, 13, 15&lt;br /&gt;
&lt;br /&gt;
Această secvență are lungime șase. Nu există nici o secvență crescătoare de lungime șapte. Cea mai lungă subsecvență crescătoare nu este unică. De exemplu&lt;br /&gt;
&lt;br /&gt;
 0, 4, 6, 9, 11, 15&lt;br /&gt;
&lt;br /&gt;
este o altă subsecvență crescătoare de aceeași lungime, în secvența de intrare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție ===&lt;br /&gt;
&lt;br /&gt;
Cum împărțim problema în subprobleme? Să încercăm astfel:&lt;br /&gt;
&lt;br /&gt;
 L[i] = lungimea celei mai lungi subsecvențe care începe la poziția i&lt;br /&gt;
&lt;br /&gt;
Are această împărțire proprietatea de optimalitate? Să presupunem că avem o subsecvență optimală crescătoare care trece prin indicii&lt;br /&gt;
&lt;br /&gt;
 i&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, i&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, i&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;, …, i&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Astfel&lt;br /&gt;
&lt;br /&gt;
 L[i&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;] = k&lt;br /&gt;
&lt;br /&gt;
Este secvența &amp;lt;tt&amp;gt;i&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, ..., i&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; optimală? Cu alte cuvinte lungimea secvenței optime care începe la &amp;lt;tt&amp;gt;i&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; este &amp;lt;tt&amp;gt;k-1&amp;lt;/tt&amp;gt;? Răspunsul este da, secvența care începe la i&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, anume i&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, ..., i&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt; este o secvență maximă, deoarece altfel am putea-o înlocui cu o secvență mai lungă, ceea ce ar duce la o subsecvență &amp;lt;tt&amp;gt;L[i&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;]&amp;lt;/tt&amp;gt; mai lungă decât cea inițială, care era optimală. Obținem o contradicție.&lt;br /&gt;
&lt;br /&gt;
Următorul pas este să găsim relația de recurență:&lt;br /&gt;
&lt;br /&gt;
 L[i] = max(L[j]+1), j &amp;gt; i și V[j] ≥ V[i]&lt;br /&gt;
&lt;br /&gt;
Următorul pas este să construim subproblemele bottom-up, de la problemele mai mici la cele mai mari. Pentru aceasta vom calcula vectorul &amp;lt;code&amp;gt;L&amp;lt;/code&amp;gt; de la coadă la cap, căutând pentru fiecare element &amp;lt;code&amp;gt;L[i]&amp;lt;/code&amp;gt; cel mai mare element &amp;lt;code&amp;gt;L[j]&amp;lt;/code&amp;gt; de după el &amp;lt;tt&amp;gt;(j &amp;gt; i)&amp;lt;/tt&amp;gt; astfel încât &amp;lt;code&amp;gt;V[j] ≥ V[i]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;code&amp;gt;L&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
Să calculăm cea mai lungă secvență crescătoare în secvența:&lt;br /&gt;
&lt;br /&gt;
 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15&lt;br /&gt;
&lt;br /&gt;
Vom calcula lungimile secvențelor maximale de la coadă la cap: &amp;lt;code&amp;gt;L[15]&amp;lt;/code&amp;gt;, apoi &amp;lt;code&amp;gt;L[14]&amp;lt;/code&amp;gt; și așa mai departe. Când ajungem la calculul lui &amp;lt;tt&amp;gt;L[6]&amp;lt;/tt&amp;gt; avem următoarea situație:&lt;br /&gt;
&lt;br /&gt;
[[File:max-increasing-seq.png|frame|none|Exemplu de calcul pentru elementul &#039;&#039;L[6]&#039;&#039; - cea mai lungă secvență crescătoare care începe la poziția 6.]]&lt;br /&gt;
&lt;br /&gt;
Pentru a calcula lungimea secvenței maximale care începe la poziția 6 vom căuta toate pozițiile mai mari &amp;lt;tt&amp;gt;(7, 8, 9, ...)&amp;lt;/tt&amp;gt;. Le vom lua în considerare doar pe acelea în care &amp;lt;code&amp;gt;V[i] &amp;gt;= V[6]&amp;lt;/code&amp;gt;. Dintre ele vom alege poziția unde &amp;lt;code&amp;gt;L[i]&amp;lt;/code&amp;gt; este maximă. Astfel vom găsi că &amp;lt;code&amp;gt;V[9] = 9&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;L[9] = 3&amp;lt;/code&amp;gt;. Vom avea, deci, o secvență de lungime 4. Ea este:&lt;br /&gt;
&lt;br /&gt;
 6, 9, 11, 15&lt;br /&gt;
&lt;br /&gt;
Exemplu de calcul pentru elementul &amp;lt;code&amp;gt;L[6]&amp;lt;/code&amp;gt; - cea mai lungă secvență crescătoare care începe la poziția 6.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Analiză ===&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;, unde n este lungimea secvenței. Memoria folosită este &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Există și un algoritm mai rapid, de complexitate &#039;&#039;O(n log n)&#039;&#039;, bazat pe căutare binară a valorilor de început.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Reconstrucția soluției ===&lt;br /&gt;
&lt;br /&gt;
Putem reconstrui o subsecvență maximală folosind încă un vector, &amp;lt;code&amp;gt;N[]&amp;lt;/code&amp;gt;. El memorează pentru fiecare indice &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; un alt indice &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; care urmează în secvență după &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;. Atunci când calculăm elementul curent, vom nota nu doar maximul găsit (plus unu) ci și poziția lui în vector, el fiind elementul următor în secvență.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Subșirul de sumă maximală ==&lt;br /&gt;
Această problemă cere să găsim un subșir de sumă maximă într-un șir de numere. Un subșir este contiguu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
Să considerăm șirul:&lt;br /&gt;
&lt;br /&gt;
 12, 14, 0, -4, 61, -39&lt;br /&gt;
&lt;br /&gt;
subșirul de sumă maximală este:&lt;br /&gt;
&lt;br /&gt;
 12, 14, 0, -4, 61&lt;br /&gt;
&lt;br /&gt;
și are sumă 83.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție ===&lt;br /&gt;
&lt;br /&gt;
Cum împărțim problema în subprobleme? Să încercăm astfel:&lt;br /&gt;
&lt;br /&gt;
 S[i] este suma maximală a unui șir care se termină la poziția i&lt;br /&gt;
&lt;br /&gt;
Are această împărțire proprietatea de optimalitate?&lt;br /&gt;
&lt;br /&gt;
Să presupunem că avem un subșir de sumă maximă și că el se află între indicii &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;. Cu alte cuvine suma maximală este&lt;br /&gt;
&lt;br /&gt;
 S[j] = V[i] + V[i+1] + ... + V[j-1] + V[j]&lt;br /&gt;
&lt;br /&gt;
Are șirul care se termină în &amp;lt;tt&amp;gt;j-1&amp;lt;/tt&amp;gt;, sumă maximală? Cu alte cuvinte este suma&lt;br /&gt;
&lt;br /&gt;
 S[j-1] = V[i] + V[i+1] + ... + V[j-1]&lt;br /&gt;
&lt;br /&gt;
maximală?&lt;br /&gt;
&lt;br /&gt;
Răspunsul este da, deoarece altfel am putea-o înlocui cu o sumă mai mare, ceea ce ar duce la o sumă mai mare decît cea inițială, care era optimală. Obținem o contradicție.&lt;br /&gt;
&lt;br /&gt;
Următorul pas este să găsim relația de recurență. Observăm că pentru o poziție &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; avem două variante: fie continuăm secvența anterioară, care se termină în &amp;lt;tt&amp;gt;i-1&amp;lt;/tt&amp;gt;, fie începem una nouă, care pornește în &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;. Vom continua secvența anterioară dacă adunând numărul curent obținem o sumă mai mare strict decât numărul curent. Altfel vom porni o sumă nouă. Relația de recurență este:&lt;br /&gt;
&lt;br /&gt;
 S[i] = max(V[i], S[i-1] + V[i])&lt;br /&gt;
&lt;br /&gt;
Următorul pas este să construim subproblemele bottom-up, de la problemele mai mici la cele mai mari. Pentru aceasta vom calcula vectorul &amp;lt;code&amp;gt;S&amp;lt;/code&amp;gt; calculând pentru fiecare element &amp;lt;code&amp;gt;S[i]&amp;lt;/code&amp;gt; maximul dintre &amp;lt;code&amp;gt;S[i-1] + V[i]&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;V[i]&amp;lt;/code&amp;gt;. Observăm că, în realitate, vom compara &amp;lt;code&amp;gt;S[i-1]&amp;lt;/code&amp;gt; cu zero.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;code&amp;gt;S&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
Fie secvența anterioară:&lt;br /&gt;
&lt;br /&gt;
 12, 14, 0, -4, 61, -39&lt;br /&gt;
&lt;br /&gt;
Vom calcula de la stânga la dreapta sumele maximale ce se termină la pozițiile respective:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! V&lt;br /&gt;
| 12&lt;br /&gt;
| 14&lt;br /&gt;
| 0&lt;br /&gt;
| -4&lt;br /&gt;
| 61&lt;br /&gt;
| -39&lt;br /&gt;
|-&lt;br /&gt;
! S&lt;br /&gt;
| 12&lt;br /&gt;
| 26 = max(14, 12 + 14)&lt;br /&gt;
| 26 = max(0, 0 + 26)&lt;br /&gt;
| 22 = max(-4, -4 + 26)&lt;br /&gt;
| 83 = max(61, 61 + 22)&lt;br /&gt;
| 44 = max(-39, -39 + 83)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Analiză ===&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(n)&#039;&#039;, unde n este lungimea șirului. Memoria folosită este &#039;&#039;O(1)&#039;&#039;, deoarece nu avem nevoie să stocăm elementele, ci doar ultima sumă calculată.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Reconstrucția soluției ===&lt;br /&gt;
&lt;br /&gt;
Putem ține minte șirul maximal ținând minte începutul și sfârșitul subșirului maximal atunci când îl găsim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluții alternative ===&lt;br /&gt;
&lt;br /&gt;
Această problemă poate fi rezolvată fără programare dinamică. Ne propunem același lucru, ca la pasul &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; să calculăm &amp;lt;code&amp;gt;S[i]&amp;lt;/code&amp;gt; cu aceeași semnificație. Acest &amp;lt;code&amp;gt;S[i]&amp;lt;/code&amp;gt; se poate calcula cu cunoștințe de nivel mic: sume parțiale. &amp;lt;code&amp;gt;S[i]&amp;lt;/code&amp;gt; va fi suma unei secvențe de la &amp;lt;tt&amp;gt;k&amp;lt;/tt&amp;gt; la &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, cu &amp;lt;tt&amp;gt;k ≤ i&amp;lt;/tt&amp;gt;. Deci, dacă vom calcula &amp;lt;code&amp;gt;P[]&amp;lt;/code&amp;gt;, sumele parțiale pe vector putem spune că &amp;lt;code&amp;gt;S[i] = P[i] - P[k]&amp;lt;/code&amp;gt;. Să ne uităm puțin la această expresie: cine variază în ea? &amp;lt;code&amp;gt;P[i]&amp;lt;/code&amp;gt; este constant, este suma elementelor până la &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;. Putem varia doar &amp;lt;code&amp;gt;P[k]&amp;lt;/code&amp;gt;. Este evident că dorim să alegem un &amp;lt;code&amp;gt;P[k]&amp;lt;/code&amp;gt; cât mai mic, nu?&lt;br /&gt;
&lt;br /&gt;
Algoritmul este unul banal: calculează sume parțiale, avansând &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;. Suma minimă care se termină în &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; este suma parțială curentă minus suma parțială minimă din trecut. La fiecare avans actualizează suma parțială minimă cu cea curentă, dacă este cazul.&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are aceleași complexități ca cel cu programare dinamică. El este însă un algoritm mai flexibil de adaptat unor modificări, cum ar fi suma maximă de lungime cel puțin &amp;lt;tt&amp;gt;K&amp;lt;/tt&amp;gt; sau suma maximă de lungime cel mult &amp;lt;tt&amp;gt;K&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Subșirul comun maximal al două șiruri ==&lt;br /&gt;
&lt;br /&gt;
Această problemă cere să găsim lungimea unui subșir de lungime maximă (contiguu) inclus în două șiruri.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
Date șirurile&lt;br /&gt;
&lt;br /&gt;
 ABABC&lt;br /&gt;
&lt;br /&gt;
și&lt;br /&gt;
&lt;br /&gt;
 CBABA&lt;br /&gt;
&lt;br /&gt;
un subșir maximal are lungime 3 și este&lt;br /&gt;
&lt;br /&gt;
 BAB&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție ===&lt;br /&gt;
&lt;br /&gt;
Cum împărțim problema în subprobleme? Să observăm că deoarece subșirul maximal face parte din ambele șiruri el se va termina la o poziție &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; în șirul &amp;lt;tt&amp;gt;X&amp;lt;/tt&amp;gt; și la o poziție &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; în șirul &amp;lt;tt&amp;gt;Y&amp;lt;/tt&amp;gt;. Și atunci să încercăm astfel:&lt;br /&gt;
&lt;br /&gt;
 M[i][j] = lungimea celui mai lung subșir care se termină la poziția i în X și la poziția j în Y.&lt;br /&gt;
&lt;br /&gt;
Are această împărțire proprietatea de optimalitate?&lt;br /&gt;
&lt;br /&gt;
Să presupunem că avem un subșir optimal care se termină la pozițiile &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, respectiv &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;. Atunci este subșirul &amp;lt;tt&amp;gt;M[i-1][j-1]&amp;lt;/tt&amp;gt; la rândul lui optimal? Cu alte cuvinte subșirul mai scurt cu unu, care se termină la pozițiile anterioare în &amp;lt;tt&amp;gt;X&amp;lt;/tt&amp;gt;, respectiv &amp;lt;tt&amp;gt;Y&amp;lt;/tt&amp;gt;, este optimal? Da, el este optimal, caci altfel l-am putea înlocui cu un șir mai lung, care ar duce la o soluție mai bună, ceea ce ar însemna că soluția originală nu a fost optimă.&lt;br /&gt;
&lt;br /&gt;
Următorul pas este să găsim relația de recurență:&lt;br /&gt;
&lt;br /&gt;
:&amp;lt;math&amp;gt;M[i][j] = \begin{cases} M[i-1][j-1]+1, &amp;amp; \mbox{dacă }  X[i] = Y[j]  \\0,  &amp;amp; \mbox{în caz contrar }. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Următorul pas este să construim subproblemele bottom-up, de la problemele mai mici la cele mai mari. Pentru aceasta vom calcula matricea &amp;lt;code&amp;gt;M&amp;lt;/code&amp;gt; pe linii, folosind formula de recurență.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din matrice.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
Pentru cele două șiruri de mai sus,&lt;br /&gt;
&lt;br /&gt;
 ABABC&lt;br /&gt;
&lt;br /&gt;
și&lt;br /&gt;
&lt;br /&gt;
 CBABA&lt;br /&gt;
&lt;br /&gt;
Vom calcula o matrice &amp;lt;code&amp;gt;M[5][5]&amp;lt;/code&amp;gt; care arată astfel:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &lt;br /&gt;
! C&lt;br /&gt;
! B&lt;br /&gt;
! A&lt;br /&gt;
! B&lt;br /&gt;
! A&lt;br /&gt;
|-&lt;br /&gt;
! A&lt;br /&gt;
| 0&lt;br /&gt;
| 0&lt;br /&gt;
| 1&lt;br /&gt;
| 0&lt;br /&gt;
| 1&lt;br /&gt;
|-&lt;br /&gt;
! B&lt;br /&gt;
| 0&lt;br /&gt;
| 1&lt;br /&gt;
| 0&lt;br /&gt;
| 2&lt;br /&gt;
| 0&lt;br /&gt;
|-&lt;br /&gt;
! A&lt;br /&gt;
| 0&lt;br /&gt;
| 0&lt;br /&gt;
| 2&lt;br /&gt;
| 0&lt;br /&gt;
| 3&lt;br /&gt;
|-&lt;br /&gt;
! B&lt;br /&gt;
| 0&lt;br /&gt;
| 1&lt;br /&gt;
| 0&lt;br /&gt;
| 3&lt;br /&gt;
| 0&lt;br /&gt;
|-&lt;br /&gt;
! C&lt;br /&gt;
| 1&lt;br /&gt;
| 0&lt;br /&gt;
| 0&lt;br /&gt;
| 0&lt;br /&gt;
| 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Observăm că avem două subșiruri comune de lungime 3: ABA și BAB.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Analiză ===&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(m·n)&#039;&#039;, unde &amp;lt;tt&amp;gt;m&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; sunt lungimile șirurilor. Memoria folosită este, la prima vedere, &#039;&#039;O(m·n)&#039;&#039;. În realitate, parcurgând matricea pe linii, nu avem nevoie să memorăm decât o linie din matrice pentru a calcula toate valorile. De aceea memoria necesară este &#039;&#039;O(min(m, n))&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Există și algoritmi mai eficienți bazați pe arbori sufix, despre care nu vom discuta aici.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Reconstrucția soluției ===&lt;br /&gt;
&lt;br /&gt;
Cum facem să aflăm nu doar lungimea ci și care este șirul comun maximal și unde apare el în cele două șiruri?&lt;br /&gt;
&lt;br /&gt;
Vom face calculul matricei noastre printr-o parcurgere pe linii. La fiecare pas ne vom întreba dacă elementul calculat (lungimea) este mai mare decât maximul curent. Dacă da, vom memora acel maxim împreună cu indicii &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt; la care s-a obținut el.&lt;br /&gt;
&lt;br /&gt;
La final vom avea poziția de start în primul șir ca fiind &amp;lt;tt&amp;gt;i - max + 1&amp;lt;/tt&amp;gt; și în al doilea șir ca fiind &amp;lt;tt&amp;gt;j - max + 1&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Subsecvența comună maximală a două șiruri ==&lt;br /&gt;
&lt;br /&gt;
Această problemă cere să găsim lungimea unei subsecvențe de lungime maximă inclusă în două secvențe. Prin secvență înțelegem caractere în ordinea originală, nu neapărat consecutive ca poziție.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
Date secvențele&lt;br /&gt;
&lt;br /&gt;
 X: ACDEGIKMPR&lt;br /&gt;
 Y: CMEFPGHRK&lt;br /&gt;
&lt;br /&gt;
o secvență maximală are lungime 4 și este&lt;br /&gt;
&lt;br /&gt;
 CEGK&lt;br /&gt;
&lt;br /&gt;
o altă secvență maximală este&lt;br /&gt;
&lt;br /&gt;
 CMPR&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Soluție ===&lt;br /&gt;
&lt;br /&gt;
Cum împărțim problema în subprobleme? Să încercăm să o împărțim în mod similar cu subșirul comun maximal:&lt;br /&gt;
&lt;br /&gt;
 M[i][j] = lungimea celei mai lungi subsecvențe incluse în șirurile X[1..i] și Y[1..j]&lt;br /&gt;
&lt;br /&gt;
Are această împărțire proprietatea de optimalitate?&lt;br /&gt;
&lt;br /&gt;
Să presupunem că avem o secvență optimală a prefixelor care se termină la pozițiile &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;, respectiv &amp;lt;tt&amp;gt;j&amp;lt;/tt&amp;gt;. Ea va trece prin anumite perechi &amp;lt;tt&amp;gt;(i&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, j&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;)&amp;lt;/tt&amp;gt;, strict crescătoare, cu proprietatea că &amp;lt;tt&amp;gt;X[i&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;] = Y[j&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;]&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
 S&amp;lt;sub&amp;gt;i,j&amp;lt;/sub&amp;gt; = (i&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;, j&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;) (i&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;, j&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;) ... (i&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;, j&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
cu proprietatea că:&lt;br /&gt;
&lt;br /&gt;
 X&amp;lt;sub&amp;gt;i&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&amp;lt;/sub&amp;gt;X&amp;lt;sub&amp;gt;i&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&amp;lt;/sub&amp;gt;... X&amp;lt;sub&amp;gt;i&amp;lt;sub&amp;gt;k-1&amp;lt;/sub&amp;gt;&amp;lt;/sub&amp;gt;X&amp;lt;sub&amp;gt;i&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;&amp;lt;/sub&amp;gt; = Y&amp;lt;sub&amp;gt;j&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&amp;lt;/sub&amp;gt;Y&amp;lt;sub&amp;gt;j&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&amp;lt;/sub&amp;gt;... Y&amp;lt;sub&amp;gt;j&amp;lt;sub&amp;gt;k-1&amp;lt;/sub&amp;gt;&amp;lt;/sub&amp;gt;Y&amp;lt;sub&amp;gt;j&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;&amp;lt;/sub&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Atunci este lungimea &amp;lt;tt&amp;gt;M[i&amp;lt;sub&amp;gt;k-1&amp;lt;/sub&amp;gt;][j&amp;lt;sub&amp;gt;k-1&amp;lt;/sub&amp;gt;]&amp;lt;/tt&amp;gt; la rândul ei optimală? Cu alte cuvinte subsecvența mai scurtă cu unu, care se termină la poziția &amp;lt;tt&amp;gt;i&amp;lt;sub&amp;gt;k-1&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; în &amp;lt;tt&amp;gt;X&amp;lt;/tt&amp;gt;, respectiv poziția &amp;lt;tt&amp;gt;j&amp;lt;sub&amp;gt;k-1&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; în &amp;lt;tt&amp;gt;Y&amp;lt;/tt&amp;gt;, este optimală? Da, ea este optimală, căci altfel am putea-o înlocui cu o secvență mai lungă, care ar duce la o soluție mai bună, ceea ce ar însemna că soluția originală nu a fost optimă.&lt;br /&gt;
&lt;br /&gt;
Următorul pas este să găsim relația de recurență:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;M[i][j] = \begin{cases} 0, &amp;amp; \mbox{dacă }  i = 0 \mbox{ sau } j = 0\\M[i-1][j-1]+1, &amp;amp; \mbox{dacă } X[i] = Y[j]\\max(M[i-1][j],M[i][j-1]) &amp;amp; \mbox{dacă }  X[i] \neq Y[j]. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cum demonstrăm această relație de recurență? Avem două cazuri de demonstrat:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Egalitate ====&lt;br /&gt;
&lt;br /&gt;
Prefixele &amp;lt;tt&amp;gt;X[1..i]&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;Y[1..j]&amp;lt;/tt&amp;gt; se termină în același caracter. În acest caz o subsecvență maximală a celor două prefixe se poate obține selectând acel ultim caracter. Putem, deci exclude acel caracter și calcula subsecvența maximală în prefixele &amp;lt;tt&amp;gt;X[1..i-1]&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;Y[1..j-1]&amp;lt;/tt&amp;gt;. La lungimea ei vom aduna unu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Diferență ====&lt;br /&gt;
&lt;br /&gt;
Prefixele &amp;lt;tt&amp;gt;X[1..i]&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;Y[1..j]&amp;lt;/tt&amp;gt; se termină în caractere diferite. Să luăm cazul anterior, avem secvențele:&lt;br /&gt;
&lt;br /&gt;
 X: ACDEGIKMPR&lt;br /&gt;
 Y: CMEFPGHRK&lt;br /&gt;
&lt;br /&gt;
Avem iarăși două cazuri:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cazul 1&#039;&#039;&#039;: subsecvența optimă se termină în &amp;lt;tt&amp;gt;R&amp;lt;/tt&amp;gt;. În acest caz ea nu se poate termina și în &amp;lt;tt&amp;gt;K&amp;lt;/tt&amp;gt;. Deci putem elimina ultimul caracter din &amp;lt;tt&amp;gt;Y&amp;lt;/tt&amp;gt; și calcula &amp;lt;tt&amp;gt;M[i][j-1]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;Cazul 2&#039;&#039;&#039;: subsecvența optimă nu se termină în &amp;lt;tt&amp;gt;R&amp;lt;/tt&amp;gt;. În acest caz putem elimina acest &amp;lt;tt&amp;gt;R&amp;lt;/tt&amp;gt; și calcula &amp;lt;tt&amp;gt;M[i-1][j]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Următorul pas este să construim subproblemele bottom-up, de la problemele mai mici la cele mai mari. Pentru aceasta vom calcula matricea &amp;lt;code&amp;gt;M&amp;lt;/code&amp;gt; pe linii, folosind formula de recurență.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este &amp;lt;code&amp;gt;M[n][m]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
&lt;br /&gt;
Pentru cele două șiruri de mai sus,&lt;br /&gt;
&lt;br /&gt;
 ACDEGIKMPR&lt;br /&gt;
&lt;br /&gt;
și&lt;br /&gt;
&lt;br /&gt;
 CMEFPGHRK&lt;br /&gt;
&lt;br /&gt;
Vom calcula o matrice &amp;lt;code&amp;gt;M[10][9]&amp;lt;/code&amp;gt; care arată astfel:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &lt;br /&gt;
! C !! M !! E !! F !! P !! G !! H !! R !! K&lt;br /&gt;
|-&lt;br /&gt;
!A&lt;br /&gt;
|0||0||0||0||0||0||0||0||0&lt;br /&gt;
|-&lt;br /&gt;
!C&lt;br /&gt;
|1||1||1||1||1||1||1||1||1&lt;br /&gt;
|-&lt;br /&gt;
!D&lt;br /&gt;
|1||1||1||1||1||1||1||1||1&lt;br /&gt;
|-&lt;br /&gt;
!E&lt;br /&gt;
|1||1||2||2||2||2||2||2||2&lt;br /&gt;
|-&lt;br /&gt;
!G&lt;br /&gt;
|1||1||2||2||2||3||3||3||3&lt;br /&gt;
|-&lt;br /&gt;
!I&lt;br /&gt;
|1||1||2||2||2||3||3||3||3&lt;br /&gt;
|-&lt;br /&gt;
!K&lt;br /&gt;
|1||1||2||2||2||3||3||3||4&lt;br /&gt;
|-&lt;br /&gt;
!M&lt;br /&gt;
|1||2||2||2||2||3||3||3||4&lt;br /&gt;
|-&lt;br /&gt;
!P&lt;br /&gt;
|1||2||2||2||3||3||3||3||4&lt;br /&gt;
|-&lt;br /&gt;
!R&lt;br /&gt;
|1||2||2||2||3||3||3||4||4&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Cea mai lungă secvență este de lungime 4, valoarea maximă ce apare în tabel.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Analiză ===&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(m · n)&#039;&#039;, unde &amp;lt;tt&amp;gt;m&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; sunt lungimile șirurilor. Memoria folosită este &#039;&#039;O(min(m,n))&#039;&#039; dacă memorăm doar o linie din matrice. Dacă dorim să aflăm și o secvență maximală, nu doar lungimea ei, vom avea nevoie de memorie &#039;&#039;O(m · n)&#039;&#039;, precum vom vedea mai jos.&lt;br /&gt;
&lt;br /&gt;
Există un algoritm care reduce necesarul de memorie la &#039;&#039;O(min(m, n))&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Reconstrucția soluției ===&lt;br /&gt;
&lt;br /&gt;
Dorim să aflăm nu doar lungimea, ci și o secvență maximală inclusă în ambele șiruri. Cum procedăm?&lt;br /&gt;
&lt;br /&gt;
Observăm că literele ce fac parte din secvențele maximale se obțin acolo unde, în matrice, avem ramura doi de calcul, când literele de la pozițiile respective sunt egale.&lt;br /&gt;
&lt;br /&gt;
Pentru a putea reconstrui soluția vom memora în fiecare căsuță, pe lângă lungimea maximă și direcția din care provine ea, adică din care am calculat acel maxim. Direcția poate fi spre stânga, spre în sus, sau în diagonală stânga-sus. Apoi vom porni din elementul &amp;lt;code&amp;gt;M[m][n]&amp;lt;/code&amp;gt; și vom urma aceste direcții până ce ieșim din matrice. De fiecare dată când urmăm o diagonală reținem litera. Astfel, descoperirea secvenței se face în ordine inversă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 24 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/dreptunghiuri Dreptunghiuri] pentru subsecvența crescătoare maximală. &#039;&#039;&#039;Atenţie:&#039;&#039;&#039; puteți pica teste cu TLE, este în regulă.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/ssm Subsecvența de sumă maximă]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sclm Subșirul comun de lungime maximă]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cmlsc Cel mai lung subșir comun]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_24:_Programare_dinamic%C4%83_(1) Accesează rezolvarea temei 24]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_22:_Generarea_elementelor_combinatoriale_prin_algoritmi_de_tip_succesor:_submul%C8%9Bimi,_permut%C4%83ri,_combin%C4%83ri,_aranjamente,_next_permutation&amp;diff=18581</id>
		<title>Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale prin algoritmi de tip succesor: submulțimi, permutări, combinări, aranjamente, next permutation</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_22:_Generarea_elementelor_combinatoriale_prin_algoritmi_de_tip_succesor:_submul%C8%9Bimi,_permut%C4%83ri,_combin%C4%83ri,_aranjamente,_next_permutation&amp;diff=18581"/>
		<updated>2026-02-06T16:49:54Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/CVOXXa6i8fs&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Permutări ==&lt;br /&gt;
&lt;br /&gt;
Permutările de &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt; elemente sunt definite ca numărul de moduri de a aranja în șir cele &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt; elemente. Cred că formula numărului de permutări distincte este destul de ușor de demonstrat, v-o las ca exercițiu: &lt;br /&gt;
&lt;br /&gt;
 P(N) = N!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Permutările ca funcții de rearanjare ===&lt;br /&gt;
&lt;br /&gt;
O permutare poate fi văzută ca o funcție de rearanjare a valorilor inițiale. Astfel, dacă permutarea noastră este&lt;br /&gt;
&lt;br /&gt;
 2 5 4 3 1&lt;br /&gt;
&lt;br /&gt;
aceasta înseamnă că la o aplicare a permutării pe prima poziție vom avea al doilea element din șirul nepermutat, pe a doua poziție vom avea al cincilea element din șirul original și așa mai departe.&lt;br /&gt;
&lt;br /&gt;
Deci, a aplica o permutare unor obiecte înseamnă a le schimba ordinea conform permutării. Fiecare număr din permutare semnifică poziţia care se va afla în final pe acea poziţie. De exemplu, dacă permutarea este &amp;lt;tt&amp;gt;(2 5 4 3 1)&amp;lt;/tt&amp;gt; și o aplicăm obiectelor &amp;lt;tt&amp;gt;(1 2 3 4 5)&amp;lt;/tt&amp;gt;, după prima aplicare obținem chiar permutarea, &amp;lt;tt&amp;gt;(2 5 4 3 1)&amp;lt;/tt&amp;gt;, deoarece pe poziţia 1 va veni numărul de pe poziţia 2, pe pozitia 2 va veni numărul de pe poziţia 5 şi aşa mai departe. După a doua aplicare obținem &amp;lt;tt&amp;gt;(5 1 3 4 2)&amp;lt;/tt&amp;gt;, după a treia &amp;lt;tt&amp;gt;(1 2 4 3 5)&amp;lt;/tt&amp;gt; și așa mai departe:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1 2 3 4 5&lt;br /&gt;
2 5 4 3 1&lt;br /&gt;
5 1 3 4 2&lt;br /&gt;
1 2 4 3 5&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cum stocăm o permutare? Dacă dorim să o aplicăm unui vector, o putem stoca într-un alt vector, cu mențiunea că, deoarece vectorii încep de la zero, pentru ușurința codului este preferabil să stocăm numerele din permutare minus unu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Cicluri ale permutărilor ===&lt;br /&gt;
&lt;br /&gt;
Ne putem pune, în mod natural, o întrebare: dacă pornim cu permutarea identică și aplicăm permutarea noastră în mod repetat, vom ajunge din nou la permutarea identică?&lt;br /&gt;
&lt;br /&gt;
Pentru a răspunde la această întrebare să studiem structura permutărilor. Dacă urmărim felul în care valorile își schimbă locurile vom observa că orice permutare conține cicluri, adică submulțimi ale mulțimii de valori care își schimbă circular valorile între ele. Ciclurile sunt disjuncte. &lt;br /&gt;
&lt;br /&gt;
Exemplu: fie permutarea de mai sus &amp;lt;tt&amp;gt;(2 5 4 3 1)&amp;lt;/tt&amp;gt;. Pornim cu poziția 1, unde &amp;lt;code&amp;gt;p[1]&amp;lt;/code&amp;gt; este 2. Avansăm pe poziția 2, unde &amp;lt;code&amp;gt;p[2]&amp;lt;/code&amp;gt; este 5. Avansăm pe poziția 5, unde &amp;lt;code&amp;gt;p[5] = 1&amp;lt;/code&amp;gt;, revenind astfel pe poziția 1. Am detectat astfel primul ciclu al permutării, format din &amp;lt;tt&amp;gt;(2, 5, 1)&amp;lt;/tt&amp;gt;. Continuăm cu primul element care nu face parte din ciclul găsit, care se află pe poziția 3. &amp;lt;code&amp;gt;p[3]&amp;lt;/code&amp;gt; este 4, avansăm pe poziția 4. &amp;lt;code&amp;gt;p[4]&amp;lt;/code&amp;gt; este 3, care încheie ciclul. Astfel, permutarea noastră se descompune în două cicluri disjuncte, &amp;lt;tt&amp;gt;(2, 5, 1)&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;(4, 3)&amp;lt;/tt&amp;gt;. Cum putem folosi această proprietate? Iată două exemple:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Exemplul 1 ====&lt;br /&gt;
&lt;br /&gt;
Să se spună dacă un vector de n elemente conține toate numerele de la 1 la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;. Memoria suplimentară disponibilă este &#039;&#039;O(1)&#039;&#039;. Avem voie să modificăm vectorul original.&lt;br /&gt;
&lt;br /&gt;
O soluție bazată pe ciclurile permutărilor este să parcurgem aceste cicluri și să facem elementele 0 pe măsură ce le parcurgem. Dacă vectorul conține o permutare a numerelor &amp;lt;code&amp;gt;1-n&amp;lt;/code&amp;gt; atunci fiecare ciclu trebuie să se încheie acolo unde a început. Dacă parcurgând ciclurile vectorului ajungem pe o poziție marcată cu 0 dar diferită de începutul ciclului înseamnă că nu avem o permutare. Ciclurile le parcurgem astfel: pornim cu primul element și parcurgem elementele până ne întoarcem. Apoi căutăm primul element diferit de 0 și reluăm. Ne oprim fie când găsim un ciclu incorect (caz în care nu avem o permutare), fie când ieșim din vector (caz în care permutarea este corectă).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Exemplul 2 ====&lt;br /&gt;
&lt;br /&gt;
Dată o permutare, după câte aplicări obținem din nou permutarea inițială?&lt;br /&gt;
&lt;br /&gt;
Observăm că la fiecare aplicare a permutării ciclurile se rotesc cu 1. Fiecare ciclu &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; va reveni la poziția inițială după un număr &amp;lt;code&amp;gt;l&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;&amp;lt;/code&amp;gt;, care este lungimea ciclului. De aceea va fi nevoie de &amp;lt;code&amp;gt;cmmmc(l&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;)&amp;lt;/code&amp;gt; pentru ca toate ciclurile să ajungă în poziția inițială.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numerotarea permutărilor (ranking / unranking) ===&lt;br /&gt;
&lt;br /&gt;
Să considerăm permutările a &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente, în ordine lexicografică. Ele capătă astfel un număr de ordine, prima permutare fiind numărul zero. Ne propunem să facem conversii între numărul de ordine și permutarea asociată.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Baza factorial ====&lt;br /&gt;
&lt;br /&gt;
Să luăm ca exemplu baza trei. La adunarea cu unu, o cifră pe poziția &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; se va mări cu unu atunci când cifra de pe poziția &amp;lt;code&amp;gt;k-1&amp;lt;/code&amp;gt; trece din 2 în 0. Astfel:&lt;br /&gt;
&lt;br /&gt;
 101222 + 1 =&lt;br /&gt;
 102000&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Câte numere putem reprezenta în baza trei pe șase cifre? Deoarece fiecare cifră poate avea trei valori vom avea 36 numere posibile.&lt;br /&gt;
&lt;br /&gt;
Aici ne vine ideea: noi ne-am dori ca pe 6 cifre să avem &amp;lt;tt&amp;gt;6!&amp;lt;/tt&amp;gt; numere reprezentabile. E destul de clar că dacă pentru cifra cea mai din dreapta avem o valoare posibilă, pentru următoarea cifră două valori, pentru următoarea trei, și așa mai departe, numărul de posibilități va fi &amp;lt;tt&amp;gt;6·5·4·3·2·1&amp;lt;/tt&amp;gt;, adică &amp;lt;tt&amp;gt;6!&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Deci, la fel cum reprezentăm numerele în baza trei, zece, sau doi, putem reprezenta numerele într-o bază variabilă, ce crește dinspre dreapta spre stânga. Vom denumi aceasta reprezentarea în baza factorial. Astfel, fiecare cifră este într-o bază diferită! Cifra &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; (dinspre dreapta spre stânga) se va reprezenta în baza &amp;lt;code&amp;gt;k!&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu, pe patru cifre, echivalent permutărilor de 4 elemente:&lt;br /&gt;
&lt;br /&gt;
 baza: 4321&lt;br /&gt;
    0: 0000&lt;br /&gt;
    1: 0010&lt;br /&gt;
    2: 0100&lt;br /&gt;
    3: 0110&lt;br /&gt;
    4: 0200&lt;br /&gt;
    5: 0210&lt;br /&gt;
    6: 1000&lt;br /&gt;
    7: 1010&lt;br /&gt;
    8: 1100&lt;br /&gt;
    9: 1110&lt;br /&gt;
   10: 1200&lt;br /&gt;
   11: 1210&lt;br /&gt;
   12: 2000&lt;br /&gt;
   13: 2010&lt;br /&gt;
   14: 2100&lt;br /&gt;
   15: 2110&lt;br /&gt;
   16: 2200&lt;br /&gt;
   17: 2210&lt;br /&gt;
   18: 3000&lt;br /&gt;
   19: 3010&lt;br /&gt;
   20: 3100&lt;br /&gt;
   21: 3110&lt;br /&gt;
   22: 3200&lt;br /&gt;
   23: 3210&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Observații: &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Baza crește de la dreapta către stânga.&lt;br /&gt;
* Valorile cifrelor sunt între zero și baza minus unu.&lt;br /&gt;
* Ultima cifră este mereu zero, nu este o greșeală!&lt;br /&gt;
* Numărul de numere reprezentabile pe &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; cifre este &amp;lt;code&amp;gt;n!&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Numărul de permutări de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente este &amp;lt;code&amp;gt;n!&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Sperăm, deci, să putem stabili o corespondență unu la unu între aceste numere și mulțimea permutărilor (ea se cheamă bijecție în limbaj matematic).&lt;br /&gt;
&lt;br /&gt;
Pare un pic complicat, dar nu este. Programele de conversie sunt aceleași ca și în baza zece, dar baza va varia de la 1 la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;. Iată un exemplu de program care generează toate codurile în baza factorial pe &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; cifre și le și recompune, pentru exemplificare, la codul în baza zece:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Program demonstrativ al bazei factorial&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
char nr[11];&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int b, n, k, kc, u;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;n ); // n cifre in baza factorial&lt;br /&gt;
  u = 1;&lt;br /&gt;
  // calculam ultimul numar de ordine (strict mai mare)&lt;br /&gt;
  for ( b = 2; b &amp;lt;= n; b++ )&lt;br /&gt;
    u *= b;&lt;br /&gt;
&lt;br /&gt;
  k = kc = 0;&lt;br /&gt;
  while ( k &amp;lt; u ) {&lt;br /&gt;
    // construim numarul nr[] in baza factorial&lt;br /&gt;
    kc = k;&lt;br /&gt;
    for ( b = 1; b &amp;lt;= n; b++ ) {&lt;br /&gt;
      nr[b - 1] = kc % b;&lt;br /&gt;
      kc /= b;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // afisam numarul nr[] in baza factorial&lt;br /&gt;
    printf( &amp;quot;%4d: &amp;quot;, k );&lt;br /&gt;
    for ( b = n; b &amp;gt; 0; b-- )&lt;br /&gt;
      fputc( &#039;0&#039; + nr[b - 1], stdout );&lt;br /&gt;
&lt;br /&gt;
    // recompunem numarul de ordine k din nr[]&lt;br /&gt;
    kc = 0;&lt;br /&gt;
    for ( b = n; b &amp;gt; 0; b-- )&lt;br /&gt;
      kc = kc * b + nr[b - 1];&lt;br /&gt;
&lt;br /&gt;
    printf( &amp;quot; %4d\n&amp;quot;, kc );&lt;br /&gt;
    &lt;br /&gt;
    k++;&lt;br /&gt;
  }&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate au aceste conversii?&lt;br /&gt;
&lt;br /&gt;
Se observă ușor că ele sunt &#039;&#039;O(n)&#039;&#039;, câtă vreme &amp;lt;code&amp;gt;n!&amp;lt;/code&amp;gt; nu depășește un întreg.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Conversie număr de ordine ➔ permutare (unranking) ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Problemă:&#039;&#039;&#039; se dă &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; cuprins între 0 și &amp;lt;code&amp;gt;n! - 1&amp;lt;/code&amp;gt;, să se afișeze a &amp;lt;code&amp;gt;k&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;&amp;lt;/code&amp;gt; permutare de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; în ordine lexicografică.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție:&#039;&#039;&#039; vom calcula numărul &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; în baza factorial cu &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; cifre. Apoi, pe baza acestor cifre, vom calcula permutarea.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Să presupunem că ne dorim permutarea de patru elemente cu numărul de ordin 11. Calculând &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; obținem &amp;lt;tt&amp;gt;1210&amp;lt;/tt&amp;gt;. Pentru a obține permutarea vom selecta numerele de la 1 la 4, astfel:&lt;br /&gt;
&lt;br /&gt;
 1210, vom selecta poziția 1 din cele patru cifre, adică numărul 2&lt;br /&gt;
 1210, vom selecta poziția 2 din cifrele rămase { 1, 3, 4 }, adică 4&lt;br /&gt;
 1210, vom selecta poziția 1 din cifrele rămase { 1, 3 }, adică 3&lt;br /&gt;
 1210, vom selecta poziția 0 din cifrele rămase { 1 }, adică 1&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Rezultă permutarea &amp;lt;tt&amp;gt;2 4 3 1&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Implementare:&#039;&#039;&#039; cea mai simplă implementare este cea în care păstrăm un vector de frecvență al elementelor folosite în permutare, să-i spunem &amp;lt;code&amp;gt;folosit[]&amp;lt;/code&amp;gt;, similar cu prima metodă de generare a permutărilor, cea mai simplă. Pentru fiecare cifră cf din numărul în baza factorial vom căuta al &amp;lt;code&amp;gt;cf&amp;lt;/code&amp;gt;-lea 0 în vectorul de frecvență (al &amp;lt;code&amp;gt;cf&amp;lt;/code&amp;gt;-lea număr nefolosit).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această implementare?&lt;br /&gt;
&lt;br /&gt;
Pentru fiecare cifră putem parcurge maxim &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; cifre, deci complexitatea este &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; ca timp și &#039;&#039;O(n)&#039;&#039; ca memorie.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de generare a permutărilor pe baza numărului lor de ordine:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Program demonstrativ: generare permutari pe baza numarului lor de ordine&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
char nr[10], folosit[10];&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int b, n, k, kc, u, i;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;n ); // n cifre in baza factorial&lt;br /&gt;
  u = 1;&lt;br /&gt;
  // calculam ultimul numar de ordine (strict mai mare)&lt;br /&gt;
  for ( b = 2; b &amp;lt;= n; b++ )&lt;br /&gt;
    u *= b;&lt;br /&gt;
&lt;br /&gt;
  k = kc = 0;&lt;br /&gt;
  while ( k &amp;lt; u ) {&lt;br /&gt;
    // construim numarul nr[] in baza factorial&lt;br /&gt;
    kc = k;&lt;br /&gt;
    for ( b = 1; b &amp;lt;= n; b++ ) {&lt;br /&gt;
      nr[b - 1] = kc % b;&lt;br /&gt;
      kc /= b;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // afisam numarul nr[] in baza factorial&lt;br /&gt;
    printf( &amp;quot;%4d: &amp;quot;, k );&lt;br /&gt;
    for ( b = n; b &amp;gt; 0; b-- )&lt;br /&gt;
      fputc( &#039;0&#039; + nr[b - 1], stdout );&lt;br /&gt;
&lt;br /&gt;
    // resetam vectorul de elemente folosite&lt;br /&gt;
    for ( b = n - 1; b &amp;gt;= 0; b-- )&lt;br /&gt;
      folosit[b] = 0;&lt;br /&gt;
&lt;br /&gt;
    // afisam permutarea&lt;br /&gt;
    fputc( &#039; &#039;, stdout );&lt;br /&gt;
    for ( b = n - 1; b &amp;gt;= 0; b-- ) { // parcurgem cifrele nr. in baza factorial&lt;br /&gt;
      i = -1;&lt;br /&gt;
      while ( nr[b]-- &amp;gt;= 0 )         // sarim peste nr[b]+1 elemente nefolosite&lt;br /&gt;
        do&lt;br /&gt;
          i++;&lt;br /&gt;
        while ( folosit[i] );&lt;br /&gt;
      folosit[i] = 1;                // marcam elementul ca folosit&lt;br /&gt;
      fputc( &#039;1&#039; + i, stdout );      // si il afisam&lt;br /&gt;
    }&lt;br /&gt;
    fputc( &#039;\n&#039;, stdout );&lt;br /&gt;
&lt;br /&gt;
    k++;&lt;br /&gt;
  }&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Iată ce afișează programul de mai sus pentru &amp;lt;code&amp;gt;n = 4&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
   0: 0000 1234&lt;br /&gt;
   1: 0010 1243&lt;br /&gt;
   2: 0100 1324&lt;br /&gt;
   3: 0110 1342&lt;br /&gt;
   4: 0200 1423&lt;br /&gt;
   5: 0210 1432&lt;br /&gt;
   6: 1000 2134&lt;br /&gt;
   7: 1010 2143&lt;br /&gt;
   8: 1100 2314&lt;br /&gt;
   9: 1110 2341&lt;br /&gt;
  10: 1200 2413&lt;br /&gt;
  11: 1210 2431&lt;br /&gt;
  12: 2000 3124&lt;br /&gt;
  13: 2010 3142&lt;br /&gt;
  14: 2100 3214&lt;br /&gt;
  15: 2110 3241&lt;br /&gt;
  16: 2200 3412&lt;br /&gt;
  17: 2210 3421&lt;br /&gt;
  18: 3000 4123&lt;br /&gt;
  19: 3010 4132&lt;br /&gt;
  20: 3100 4213&lt;br /&gt;
  21: 3110 4231&lt;br /&gt;
  22: 3200 4312&lt;br /&gt;
  23: 3210 4321&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Conversie permutare ➔ număr de ordine (ranking) ====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Problemă:&#039;&#039;&#039; se dă o permutare de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;. Să se afișeze numărul ei de ordine (în ordine lexicografică), &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;, cuprins între 0 și &amp;lt;code&amp;gt;n! - 1&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție:&#039;&#039;&#039; vom calcula numărul &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; în baza factorial cu &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; cifre, pe baza elementelor din permutare.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Să presupunem că ne dorim să aflăm numărul de ordine al permutării de 4 elemente: &amp;lt;tt&amp;gt;2 4 3 1&amp;lt;/tt&amp;gt;. Vom calcula cifrele numărului său de ordine în baza &amp;lt;tt&amp;gt;4!&amp;lt;/tt&amp;gt;, astfel:&lt;br /&gt;
&lt;br /&gt;
 2 4 3 1, 2 este a doua cifră încă nefolosită, deci prima cifră va fi 1 (2-1)&lt;br /&gt;
 2 4 3 1, 4 este a treia cifră încă nefolosită din cifrele rămase { 1, 3, 4 }, deci a doua cifră va fi 2 (3-1)&lt;br /&gt;
 2 4 3 1, 3 este a doua cifră încă nefolosită din cifrele rămase { 1, 3 }, deci a treia cifră va fi 1 (2-1)&lt;br /&gt;
 2 4 3 1, 1 este prima cifră încă nefolosită din cifrele rămase { 1 }, deci a treia cifră va fi 0 (1-1)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Rezultă numărul în baza factorial: &amp;lt;tt&amp;gt;1210&amp;lt;/tt&amp;gt;, pe care când îl convertim la baza zece avem &amp;lt;code&amp;gt;k = 11&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
La implementare nu vom avea nevoie să memorăm cifrele în baza &amp;lt;code&amp;gt;n!&amp;lt;/code&amp;gt;. Pe măsură ce le calculăm vom calcula direct &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;, numărul de ordine dorit.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această implementare?&lt;br /&gt;
&lt;br /&gt;
Pentru fiecare cifră putem parcurge maxim &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; cifre, deci complexitatea este &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; ca timp și &#039;&#039;O(n)&#039;&#039; ca memorie.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de generare a permutărilor pe baza numărului lor de ordine:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Program demonstrativ: calcul numar de ordine pe baza unei permutari&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
char nr[10], folosit[10], perm[10];&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int b, n, k, kc, u, i, cf;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;n ); // n cifre in baza factorial&lt;br /&gt;
  u = 1;&lt;br /&gt;
  // calculam ultimul numar de ordine (strict mai mare)&lt;br /&gt;
  for ( b = 2; b &amp;lt;= n; b++ )&lt;br /&gt;
    u *= b;&lt;br /&gt;
&lt;br /&gt;
  k = kc = 0;&lt;br /&gt;
  while ( k &amp;lt; u ) {&lt;br /&gt;
    // construim numarul nr[] in baza factorial&lt;br /&gt;
    kc = k;&lt;br /&gt;
    for ( b = 1; b &amp;lt;= n; b++ ) {&lt;br /&gt;
      nr[b - 1] = kc % b;&lt;br /&gt;
      kc /= b;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // afisam numarul nr[] in baza factorial&lt;br /&gt;
    printf( &amp;quot;%4d: &amp;quot;, k );&lt;br /&gt;
    for ( b = n; b &amp;gt; 0; b-- )&lt;br /&gt;
      fputc( &#039;0&#039; + nr[b - 1], stdout );&lt;br /&gt;
&lt;br /&gt;
    // resetam vectorul de elemente folosite&lt;br /&gt;
    for ( b = n - 1; b &amp;gt;= 0; b-- )&lt;br /&gt;
      folosit[b] = 0;&lt;br /&gt;
&lt;br /&gt;
    // generam permutarea&lt;br /&gt;
    fputc( &#039; &#039;, stdout );&lt;br /&gt;
    for ( b = n - 1; b &amp;gt;= 0; b-- ) { // parcurgem cifrele nr. in baza factorial&lt;br /&gt;
      i = -1;&lt;br /&gt;
      while ( nr[b]-- &amp;gt;= 0 )         // sarim peste nr[b]+1 elemente nefolosite&lt;br /&gt;
        do&lt;br /&gt;
          i++;&lt;br /&gt;
        while ( folosit[i] );&lt;br /&gt;
      folosit[i] = 1;                // marcam elementul ca folosit&lt;br /&gt;
      fputc( &#039;1&#039; + i, stdout );      // si il afisam&lt;br /&gt;
      perm[b] = i;                   // stocam elementul in permutare&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // resetam vectorul de elemente folosite&lt;br /&gt;
    for ( b = n - 1; b &amp;gt;= 0; b-- )&lt;br /&gt;
      folosit[b] = 0;&lt;br /&gt;
    &lt;br /&gt;
    // calculam numarul de ordine al permutarii curente&lt;br /&gt;
    kc = 0;&lt;br /&gt;
    for ( b = n - 1; b &amp;gt;= 0; b-- ) { // parcurgem elementele permutarii&lt;br /&gt;
      cf = 0;&lt;br /&gt;
      for ( i = perm[b] - 1; i &amp;gt;= 0; i-- ) // pornim in &#039;folosit[]&#039; in jos&lt;br /&gt;
        cf += (1 - folosit[i]);            // cautam cifre nefolosite&lt;br /&gt;
      folosit[perm[b]] = 1;                // marcam elementul ca folosit&lt;br /&gt;
      kc = kc * (b + 1) + cf; // adaugam cifra curenta la numarul de ordine&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    printf( &amp;quot; %4d\n&amp;quot;, kc );   // afisam numarul de ordine&lt;br /&gt;
&lt;br /&gt;
    k++;&lt;br /&gt;
  }&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Iată ce afișează programul de mai sus pentru &amp;lt;code&amp;gt;n = 4&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
   0: 0000 1234    0&lt;br /&gt;
   1: 0010 1243    1&lt;br /&gt;
   2: 0100 1324    2&lt;br /&gt;
   3: 0110 1342    3&lt;br /&gt;
   4: 0200 1423    4&lt;br /&gt;
   5: 0210 1432    5&lt;br /&gt;
   6: 1000 2134    6&lt;br /&gt;
   7: 1010 2143    7&lt;br /&gt;
   8: 1100 2314    8&lt;br /&gt;
   9: 1110 2341    9&lt;br /&gt;
  10: 1200 2413   10&lt;br /&gt;
  11: 1210 2431   11&lt;br /&gt;
  12: 2000 3124   12&lt;br /&gt;
  13: 2010 3142   13&lt;br /&gt;
  14: 2100 3214   14&lt;br /&gt;
  15: 2110 3241   15&lt;br /&gt;
  16: 2200 3412   16&lt;br /&gt;
  17: 2210 3421   17&lt;br /&gt;
  18: 3000 4123   18&lt;br /&gt;
  19: 3010 4132   19&lt;br /&gt;
  20: 3100 4213   20&lt;br /&gt;
  21: 3110 4231   21&lt;br /&gt;
  22: 3200 4312   22&lt;br /&gt;
  23: 3210 4321   23&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Optimizarea conversiilor ====&lt;br /&gt;
&lt;br /&gt;
Se pot face cele două conversii mai eficient? Răspunsul [https://en.wikipedia.org/wiki/Permutation#Algorithms_to_generate_permutations wikipedia] este că da, ele se pot efectua în &#039;&#039;O(n log n)&#039;&#039;, dar în practică algoritmii nu sunt folosiți deoarece:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este de obicei prea mic pentru ca diferența între &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; și &#039;&#039;O(n log n)&#039;&#039; să conteze.&lt;br /&gt;
* De cele mai multe ori când avem nevoie de generări de permutări avem situații speciale în care avem algoritmi mai eficienți.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Revenire la problema Permutări1 ====&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/permutari1 Permutări1] a fost dată ca un exercițiu de generare eficientă a permutărilor prin algoritmul cu liste. Ea poate fi rezolvată și altfel: putem citi numerele de ordine ale permutărilor și apoi putem genera și afișa acele permutări.&lt;br /&gt;
&lt;br /&gt;
Cum se compară acest algoritm cu cel original, ce generează toate permutările?&lt;br /&gt;
&lt;br /&gt;
Algoritmul original are complexitate &#039;&#039;O(n!)&#039;&#039;, deoarece generează toate permutările.&lt;br /&gt;
&lt;br /&gt;
Algoritmul cel nou va genera &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; permutări. O generare execută &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; operații. Complexitatea totală va fi, deci, &#039;&#039;O(k·n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Deoarece &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; este foarte mic (50 000) acest algoritm este mai rapid.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Permutări cu repetiție ===&lt;br /&gt;
&lt;br /&gt;
Definiție: permutările cu repetiție sunt toate modurile distincte în care putem așeza &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; obiecte, atunci când anumite obiecte sunt identice. De exemplu, permutările șirului &amp;lt;tt&amp;gt;1 1 1 2 2&amp;lt;/tt&amp;gt; sunt:&lt;br /&gt;
&lt;br /&gt;
 1 1 1 2 2&lt;br /&gt;
 1 1 2 1 2&lt;br /&gt;
 1 1 2 2 1&lt;br /&gt;
 1 2 1 1 2&lt;br /&gt;
 1 2 1 2 1&lt;br /&gt;
 1 2 2 1 1&lt;br /&gt;
 2 1 1 1 2&lt;br /&gt;
 2 1 1 2 1&lt;br /&gt;
 2 1 2 1 1&lt;br /&gt;
 2 2 1 1 1&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Numărul&#039;&#039;&#039; permutărilor cu repetiție este factorialul numărului de obiecte împărțit la factorialele numerelor de obiecte de același tip. De ce? Deoarece pentru fiecare permutare obișnuită, dacă avem t obiecte de același tip, vom avea &amp;lt;code&amp;gt;t!&amp;lt;/code&amp;gt; permutări care &#039;&#039;arată la fel&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Cum generăm permutări cu repetiție?&lt;br /&gt;
&lt;br /&gt;
Am studiat doi algoritmi de generare de permutări. Ambii pot fi ușor adaptați să genereze permutări cu repetiție.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Algoritmul cu vector de frecvență ====&lt;br /&gt;
&lt;br /&gt;
Modificarea pentru acest algoritm va fi aceea că un element în vectorul de frecvență va conține numărul de obiecte de acel tip. Atunci când așezăm un element în permutare îl scădem din vectorul de frecvență. La revenirea din recursivitate vom aduna unu la vectorul de frecvență, pentru a reveni la valoarea anterioară. Iată cele două implementări:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===== Permutări cu vector de frecvență =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Generare permutari prin recursivitate (pseudo backtracking)&lt;br /&gt;
// Permutari in O(n) cu vector de frecventa&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
char folosit[10], sol[10];&lt;br /&gt;
FILE *fin, *fout;&lt;br /&gt;
&lt;br /&gt;
void perm( int n, int poz ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( poz == n ) {           // daca am umplut n pozitii&lt;br /&gt;
    for ( i = 0; i &amp;lt; n; i++ ) // afiseaza combinarea curenta&lt;br /&gt;
      fprintf( fout, &amp;quot;%d &amp;quot;, (int)sol[i] );&lt;br /&gt;
    fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
  } else&lt;br /&gt;
    for ( sol[poz] = 1; sol[poz] &amp;lt;= n; sol[poz]++ )&lt;br /&gt;
      if ( !folosit[(int)sol[poz]] ) { // daca elementul curent nu e folosit&lt;br /&gt;
        folosit[(int)sol[poz]] = 1;    // marcheaza-l ca folosit&lt;br /&gt;
        perm( n, poz + 1 );            // genereaza restul permutarii&lt;br /&gt;
        folosit[(int)sol[poz]] = 0;    // demarcheaza elementul&lt;br /&gt;
      }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;perm.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;perm.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  perm( n, 0 ); // genereaza permutari de n, pornind in solutie de la poz 0&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===== Permutări cu repetiție cu vector de frecvență =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Generare permutari cu repetitie prin recursivitate (pseudo backtracking)&lt;br /&gt;
// Permutari in O(n) cu vector de frecventa&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
char nrel[15], sol[15];&lt;br /&gt;
FILE *fin, *fout;&lt;br /&gt;
&lt;br /&gt;
void perm( int n, int poz ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( poz == n ) {           // daca am umplut n pozitii&lt;br /&gt;
    for ( i = 0; i &amp;lt; n; i++ ) // afiseaza combinarea curenta&lt;br /&gt;
      fprintf( fout, &amp;quot;%d &amp;quot;, (int)sol[i] );&lt;br /&gt;
    fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
  } else&lt;br /&gt;
    for ( sol[poz] = 1; sol[poz] &amp;lt;= n; sol[poz]++ )&lt;br /&gt;
      if ( nrel[(int)sol[poz]] ) { // daca mai avem elemente sol[poz]&lt;br /&gt;
        nrel[(int)sol[poz]]--;     // marcheaza-l ca folosit&lt;br /&gt;
        perm( n, poz + 1 );        // genereaza restul permutarii&lt;br /&gt;
        nrel[(int)sol[poz]]++;     // adauga elementul la loc&lt;br /&gt;
      }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, i, e;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;perm.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ ) {&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;e );&lt;br /&gt;
    nrel[e]++;&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;perm.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  perm( n, 0 ); // genereaza permutari de n, pornind in solutie de la poz 0&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Algoritmul cu listă ====&lt;br /&gt;
&lt;br /&gt;
Modificarea acestui algoritm constă în faptul că un element al listei va conține o pereche (element, număr de apariții). Când parcurgem lista pentru a plasa elemente în permutare, vom scădea unu din numărul de apariții. Dacă numărul de apariții devine zero, vom scoate elementul din listă, readăugându-l la loc la revenirea din recursivitate. La revenirea din recursivitate vom aduna unu la numărul de apariții.&lt;br /&gt;
&lt;br /&gt;
Cei doi algoritmi își păstrează complexitățile, în sensul în care cel cu vectori de frecvență generează permutări în &#039;&#039;O(n)&#039;&#039; per permutare amortizat, iar cel cu listă are nevoie de &#039;&#039;O(1)&#039;&#039; amortizat pentru generarea unei permutări. Iată cele două implementări:&lt;br /&gt;
&lt;br /&gt;
===== Permutări cu listă =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Generare permutari prin recursivitate (pseudo backtracking)&lt;br /&gt;
// Permutari in O(1) cu lista&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define NIL -1&lt;br /&gt;
#define MAXN 10&lt;br /&gt;
&lt;br /&gt;
char next[MAXN + 1], // lista de numere 1..n&lt;br /&gt;
  sol[MAXN];         // contine permutarea curenta&lt;br /&gt;
FILE *fin, *fout;&lt;br /&gt;
&lt;br /&gt;
void perm( int n, int poz ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( poz == n ) {&lt;br /&gt;
    for ( i = 0; i &amp;lt; n; i++ ) // afiseaza permutarea curenta&lt;br /&gt;
      fprintf( fout, &amp;quot;%d &amp;quot;, (int)sol[i] );&lt;br /&gt;
    fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
  } else {&lt;br /&gt;
    i = 0; // pointer la elementul din-naintea elementului curent&lt;br /&gt;
    while ( next[i] != NIL ) { // pentru fiecare element din lista&lt;br /&gt;
      sol[poz] = next[i];      // il plasam in vectorul solutie&lt;br /&gt;
      next[i] = next[(int)next[i]]; // il eliminam din lista&lt;br /&gt;
      perm( n, poz + 1 );      // reapelam cu pozitia urmatoare&lt;br /&gt;
      i = next[i] = sol[poz];  // refacem legatura si avansam i in lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, i;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;perm.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // initializare lista numere&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    next[i] = i + 1;&lt;br /&gt;
  next[n] = NIL;&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;perm.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  perm( n, 0 ); // genereaza permutari de n, pornind in solutie de la poz 0&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===== Permutări cu repetiție cu listă =====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Generare permutari cu repetitie prin recursivitate (pseudo backtracking)&lt;br /&gt;
// Permutari in O(1) cu lista&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define NIL -1&lt;br /&gt;
#define MAXN 15&lt;br /&gt;
&lt;br /&gt;
char nrel[MAXN + 1], next[MAXN + 1], // lista de numere 1..k&lt;br /&gt;
  sol[MAXN];                         // contine permutarea curenta&lt;br /&gt;
FILE *fin, *fout;&lt;br /&gt;
&lt;br /&gt;
void perm( int n, int poz ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( poz == n ) {&lt;br /&gt;
    for ( i = 0; i &amp;lt; n; i++ ) // afiseaza permutarea curenta&lt;br /&gt;
      fprintf( fout, &amp;quot;%d &amp;quot;, (int)sol[i] );&lt;br /&gt;
    fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
  } else {&lt;br /&gt;
    i = 0; // pointer la elementul din-naintea elementului curent&lt;br /&gt;
    while ( next[i] != NIL ) {  // pentru fiecare element din lista&lt;br /&gt;
      sol[poz] = next[i];       // il plasam in vectorul solutie&lt;br /&gt;
      if ( --nrel[(int)next[i]] == 0 ) { // mai avem elemente tip next[i]?&lt;br /&gt;
        next[i] = next[(int)next[i]];    // il eliminam din lista&lt;br /&gt;
        perm( n, poz + 1 ); // reapelam cu pozitia urmatoare&lt;br /&gt;
        next[i] = sol[poz]; // refacem legatura&lt;br /&gt;
      } else&lt;br /&gt;
        perm( n, poz + 1 ); // reapelam cu pozitia urmatoare fara sa eliminam&lt;br /&gt;
&lt;br /&gt;
      nrel[(int)next[i]]++; // adaugam la loc elementul&lt;br /&gt;
      i = next[i]; // avansam i in lista de elemente&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, i, e;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;perm.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ ) {&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;e );&lt;br /&gt;
    nrel[e]++;&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // initializare lista numere&lt;br /&gt;
  i = next[0] = 1;&lt;br /&gt;
  while ( nrel[i] ) {&lt;br /&gt;
    next[i] = i + 1;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
  next[i - 1] = NIL; // marcam finalul de lista&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;perm.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  perm( n, 0 ); // genereaza permutari de n, pornind in solutie de la poz 0&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Next permutation ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Problemă:&#039;&#039;&#039; dată o permutare într-un șir, să se genereze următoarea permutare lexicografică.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție:&#039;&#039;&#039; vom proceda ca la incrementarea unui număr mare. Vom schimba permutarea la coadă, minimal, pentru a o transforma în permutarea următoare. Vom exemplifica algoritmul pe baza [https://www.nayuki.io/page/next-lexicographical-permutation-algorithm next lexicographical permutation algorithm], folosind exemplul &amp;lt;tt&amp;gt;(0 1 2 4 3 3 0)&amp;lt;/tt&amp;gt;. Pașii sunt următorii:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Găsirea sufixului descrescător maximal ====&lt;br /&gt;
&lt;br /&gt;
Pornind de la coadă parcurgem permutarea către stânga câtă vreme elementele sunt mai mare sau egale. Ne oprim la primul element strict mai mic. În cazul nostru obținem &amp;lt;tt&amp;gt;(0 1 2 | 4 3 3 0)&amp;lt;/tt&amp;gt;. În acest moment știm că sufixul este o permutare maximală, nu poate fi mărită decât aducând elemente dinspre stânga.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Mărirea elementului din stânga sufixului ====&lt;br /&gt;
&lt;br /&gt;
Deoarece sufixul descrescător nu se mai poate mări, pentru a modifica minimal permutarea va trebui să mărim elementul imediat următor, în cazul nostru 2. Cu ce îl vom înlocui? Cu cel mai mic element strict mai mare ca el, ce se află la dreapta lui. În cazul nostru îl vom înlocui cu 3. Obținem deci prima parte a permutării:&lt;br /&gt;
&lt;br /&gt;
 (0 1 3 ... )&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Rearanjarea sufixului ====&lt;br /&gt;
&lt;br /&gt;
În partea din dreapta avem acum elementele din sufix, dintre care dispare un element, cel folosit în prefix, și se adaugă primul element din stânga. În cazul nostru au rămas elementele &amp;lt;tt&amp;gt;(4 3 0)&amp;lt;/tt&amp;gt; și elementul 2. Cum formăm cea mai mică permutare lexicografică? Desigur ordonând crescător elementele rămase. În cazul nostru sufixul va deveni &amp;lt;tt&amp;gt;(0 2 3 4)&amp;lt;/tt&amp;gt;. Va rezulta permutarea:&lt;br /&gt;
&lt;br /&gt;
 (0 1 3 0 2 3 4)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Optimizarea implementării ====&lt;br /&gt;
&lt;br /&gt;
Putem scăpa de sortarea elementelor sufixului, la final, deoarece știm că sufixul este descrescător. Putem, deci, să inversăm elementele lui. Înainte de aceasta vom plasa elementul din stânga lui, cel ce a fost mărit. Unde îl plasăm? Astfel încât sufixul original să rămână descrescător (apoi crescător după inversare), deci la prima poziție dinspre dreapta spre stânga unde vom găsi un element strict mai mare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Algoritmul final ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1. Caută la coadă sufixul descrescător maximal. El va începe la poziția k.&lt;br /&gt;
   1. Dacă k este zero, toată permutarea este descrescătoare, deci ea este ultima, nu mai avem ce genera.&lt;br /&gt;
2. Caută primul element dinspre dreapta care este strict mai mare decît p[k-1].&lt;br /&gt;
   1. El va fi la poziția i.&lt;br /&gt;
3. Interschimbă elementele p[k-1] și p[i].&lt;br /&gt;
4. Inversează vectorul p[k..n-1]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Analiza este la fel ca la algoritmul de generare a permutărilor cu liste: la nivelul &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; (cu &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; numerotat crescător de la dreapta la stânga) se fac &#039;&#039;O(k)&#039;&#039; calcule. Va rezulta aceeași complexitate amortizată de &#039;&#039;O(1)&#039;&#039; per permutarea următoare. Surprinzător, nu?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Combinări ==&lt;br /&gt;
&lt;br /&gt;
Numim combinări de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; luate câte &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; și notăm cu &amp;lt;math&amp;gt;C_N^K&amp;lt;/math&amp;gt; submulțimile de &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; elemente dintr-o mulțime de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; elemente.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Combinări - formulă ===&lt;br /&gt;
&lt;br /&gt;
Cum le calculăm?&lt;br /&gt;
&lt;br /&gt;
* Știm câte permutări de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; elemente avem: &amp;lt;math&amp;gt;N!&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Primele &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; elemente ale acestor permutări vor constitui o combinare validă.&lt;br /&gt;
* Fiecare din prefixele de &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; elemente apare de &amp;lt;math&amp;gt;(N - K)!&amp;lt;/math&amp;gt; ori, deoarece prefixul se schimbă doar după ce restul de &amp;lt;math&amp;gt;N - K&amp;lt;/math&amp;gt; elemente au fost permutate în toate modurile posibile.&lt;br /&gt;
* Deci, numărul de prefixe distincte de &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; elemente este &amp;lt;math&amp;gt;\frac{N!}{(N-K)!}&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Prefixele au o ordine, spre deosebire de permutări. Vrem să luăm în considerare numai prefixele ordonate crescător.&lt;br /&gt;
* De câte ori apare un prefix ordonat crescător dar în altă permutare? De &amp;lt;math&amp;gt;K!&amp;lt;/math&amp;gt; ori. Deci trebuie să împărțim numărul de prefixe la &amp;lt;math&amp;gt;K!&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Deci, formula numărului de combinări de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; luate câte &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; este: &amp;lt;math&amp;gt;C_N^K = \frac{N!}{K! \cdot (N-K)!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Combinări - formule de recurență ===&lt;br /&gt;
&lt;br /&gt;
Din formula combinărilor, înlocuind &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; cu &amp;lt;math&amp;gt;N-1&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; cu &amp;lt;math&amp;gt;K-1&amp;lt;/math&amp;gt;, obținem ușor o primă recurență utilă în calculul rapid al unei singure permutări: &amp;lt;math&amp;gt;C_N^K = &lt;br /&gt;
 \frac{N}{K} \cdot C_{N-1}^{K-1}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
O altă formulă de recurență utilă se obține astfel:&lt;br /&gt;
&lt;br /&gt;
# Avem două variante de a forma combinările de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; luate câte &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt;.&lt;br /&gt;
# Prima variantă este să folosim primul element, rămânând să luăm &amp;lt;math&amp;gt;K-1&amp;lt;/math&amp;gt; elemente din celelalte &amp;lt;math&amp;gt;N-1&amp;lt;/math&amp;gt;. Vom putea, deci, forma &amp;lt;math&amp;gt;C_{N-1}^{K-1}&amp;lt;/math&amp;gt; combinări.&lt;br /&gt;
# A doua variantă este să nu folosim primul element, rămânând să luăm tot &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; elemente, dar din restul de &amp;lt;math&amp;gt;N-1&amp;lt;/math&amp;gt; elemente rămase. Vom putea, deci, forma &amp;lt;math&amp;gt;C_{N-1}^{K}&amp;lt;/math&amp;gt; combinări.&lt;br /&gt;
# Suma acestor două expresii este chiar numărul de combinări de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; luate câte &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt;: &amp;lt;math&amp;gt;C_N^K = C_{N-1}^{K-1} + C_{N-1}^K&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Combinări - triunghiul lui Pascal ===&lt;br /&gt;
&lt;br /&gt;
Din ultima recurență obținem o formulă foarte simplă a fiecărei combinări de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; luat câte &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt;, de două combinări de &amp;lt;math&amp;gt;N-1&amp;lt;/math&amp;gt;. Ele pot fi aranjate vizual într-un triunghi numit triunghiul lui Pascal:&lt;br /&gt;
&lt;br /&gt;
 0             1&lt;br /&gt;
 1           1   1&lt;br /&gt;
 2         1   2   1&lt;br /&gt;
 3       1   3   3   1&lt;br /&gt;
 4     1   4   6   4   1&lt;br /&gt;
 5   1   5  10  10   5   1&lt;br /&gt;
&lt;br /&gt;
Combinările de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; luate câte zero înseamnă că nu vom lua nici un element. Există o singură astfel de submulțime, mulțimea vidă. De aceea combinările de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; luate câte zero sunt 1.&lt;br /&gt;
&lt;br /&gt;
Triunghiul lui Pascal este util atunci când avem multe calcule ce implică diverse combinări. El se poate precalcula în &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; și va fi memorat ca o matrice (nu-l vom centra, ca în figură 🙂 ).&lt;br /&gt;
&lt;br /&gt;
Numerele din triunghiul lui Pascal se numesc și coeficienți binomiali deoarece linia &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; are ca numere coeficienții cu care se înmulțesc numerele din binomul &amp;lt;math&amp;gt;(a + b)^N&amp;lt;/math&amp;gt; dacă desfacem parantezele. Pentru &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; egal doi sau trei știm formulele de calcul prescurtat, ce implică numerele de pe liniile 2 și 3:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(a+b)^2 = a^2 + 2ab + b^2&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(a+b)^3 = a^3 + 3a^2b + 3ab^2 + b^3&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Iată un mod lejer de a obține formula la orice putere 🙂.&lt;br /&gt;
&lt;br /&gt;
Dacă înlocuim &amp;lt;math&amp;gt;a&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;b&amp;lt;/math&amp;gt; cu 1 în aceste formule obținem o altă formulă: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;(1+1)^n = C_n^0 + C_n^1 +C_n^2 + ... + C_n^n = 2^n&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Suma combinărilor este &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;. Cu alte cuvinte suma numerelor de pe linia n din triunghiul lui Pascal este &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Acest lucru îl știm, însă, de mai demult, de la submulțimi: știm că numărul submulțimilor unei mulțimi este &amp;lt;math&amp;gt;2^n&amp;lt;/math&amp;gt;. Iar combinările luate în totalitate formează chiar toate submulțimile unei mulțimi.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Combinări cu repetiție ===&lt;br /&gt;
&lt;br /&gt;
Combinările cu repetiție sunt similare cu cele obișnuite, doar că putem alege un obiect din mulțime de oricâte ori. De exemplu, combinările cu repetiție a patru obiecte luate câte trei sunt:&lt;br /&gt;
&lt;br /&gt;
 1 1 1 &lt;br /&gt;
 1 1 2 &lt;br /&gt;
 1 1 3 &lt;br /&gt;
 1 1 4 &lt;br /&gt;
 1 2 2 &lt;br /&gt;
 1 2 3 &lt;br /&gt;
 1 2 4 &lt;br /&gt;
 1 3 3 &lt;br /&gt;
 1 3 4 &lt;br /&gt;
 1 4 4 &lt;br /&gt;
 2 2 2 &lt;br /&gt;
 2 2 3 &lt;br /&gt;
 2 2 4 &lt;br /&gt;
 2 3 3 &lt;br /&gt;
 2 3 4 &lt;br /&gt;
 2 4 4 &lt;br /&gt;
 3 3 3 &lt;br /&gt;
 3 3 4 &lt;br /&gt;
 3 4 4 &lt;br /&gt;
 4 4 4 &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Cum deducem formula pentru combinările cu repetiție?&lt;br /&gt;
&lt;br /&gt;
Să luăm cazul de mai sus. Avem de ales din elementele 1 2 3 și 4, în această ordine, putând lua un element de mai multe ori. Să ne imaginăm că dispunem de trei separatoare pe care la putem plasa oriunde între cele patru numere. Numărul după care plasez un separator este luat. Iată:&lt;br /&gt;
&lt;br /&gt;
 1 1 1    1 | | | 2 3 4&lt;br /&gt;
 1 1 2    1 | | 2 | 3 4&lt;br /&gt;
 1 1 3    1 | | 2 3 | 4&lt;br /&gt;
 1 1 4    1 | | 2 3 4 |&lt;br /&gt;
 1 2 2    1 | 2 | | 3 4&lt;br /&gt;
 1 2 3    1 | 2 | 3 | 4 &lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dacă avem două separatoare unul după altul ele se referă la același număr.&lt;br /&gt;
&lt;br /&gt;
Este, sper, clar, că în acest fel putem genera toate combinările cu repetiție de 4 luate câte trei. Care este egal cu numărul de moduri în care putem plasa separatoarele. Avem cu totul 6 poziții pe care putem plasa, deoarece poziția 1 nu este validă - nu este după un număr. În rest, orice plasare este bună, deci formula va fi &amp;lt;math&amp;gt;C_6^3&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pe cazul general avem formula combinărilor cu repetiție: &amp;lt;math&amp;gt;CR_n^k=C_{n+k-1}^k&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Algoritmul de generare a combinărilor cu repetiție este aproape identic cu cel de generare a combinărilor fără repetiție. Diferența este că vom varia elementele începând cu elementul anterior, în loc de elementul anterior plus 1. Iată cele două implementări:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Combinări ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Generare combinari folosind recursivitate (pseudo backtracking)&lt;br /&gt;
// O(1) per combinare&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 15&lt;br /&gt;
&lt;br /&gt;
char sol[MAXN + 1]; // santinela pe prima pozitie&lt;br /&gt;
FILE *fin, *fout;&lt;br /&gt;
&lt;br /&gt;
void comb( int n, int k, int pos ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( pos &amp;gt; k ) {&lt;br /&gt;
    for ( i = 1; i &amp;lt;= k; i++ )&lt;br /&gt;
      fprintf( fout, &amp;quot;%d &amp;quot;, (int)sol[i] );&lt;br /&gt;
    fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
  } else&lt;br /&gt;
    for ( sol[pos] = sol[pos - 1] + 1; sol[pos] &amp;lt;= (n - (k - pos)); sol[pos]++ )&lt;br /&gt;
      comb( n, k, pos + 1 );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, k;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;comb.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  &lt;br /&gt;
  sol[0] = 0; // inutil, vreau sa subliniez ca este important&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;comb.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  comb( n, k, 1 );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
  &lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Combinări cu repetiție ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Generare combinari cu repetitie folosind recursivitate (pseudo backtracking)&lt;br /&gt;
// O(1) per combinare&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 15&lt;br /&gt;
&lt;br /&gt;
char sol[MAXN + 1]; // santinela pe prima pozitie&lt;br /&gt;
FILE *fin, *fout;&lt;br /&gt;
&lt;br /&gt;
void comb( int n, int k, int pos ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( pos &amp;gt; k ) {&lt;br /&gt;
    for ( i = 1; i &amp;lt;= k; i++ )&lt;br /&gt;
      fprintf( fout, &amp;quot;%d &amp;quot;, (int)sol[i] );&lt;br /&gt;
    fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
  } else&lt;br /&gt;
    for ( sol[pos] = sol[pos - 1]; sol[pos] &amp;lt;= n; sol[pos]++ )&lt;br /&gt;
      comb( n, k, pos + 1 );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, k;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;comb.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  &lt;br /&gt;
  sol[0] = 1; // pentru a seta primul element la 1&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;comb.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  comb( n, k, 1 );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
  &lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Algoritmul este ușor de analizat, el generând fiecare combinare în &#039;&#039;O(1)&#039;&#039; (excluzând afișarea).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Next combination ===&lt;br /&gt;
&lt;br /&gt;
Sper deosebire de next permutation care există în C++ STL, &#039;&#039;next_combination&#039;&#039; nu există. Să-l scriem noi 🙂. Este vorba, desigur, despre generarea următoarei combinări în ordine lexicografică.&lt;br /&gt;
&lt;br /&gt;
Dacă ne gândim puțin nu este prea greu: dată o combinare trebuie să generăm următoarea combinare. Să observăm:&lt;br /&gt;
&lt;br /&gt;
* Dacă putem adăuga 1 la ultimul element (dacă nu este egal cu &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;), adăugăm și obținem următoarea combinare.&lt;br /&gt;
* În caz contrar, dacă putem adăuga 1 la penultimul element (dacă nu este egal cu &amp;lt;code&amp;gt;n-1&amp;lt;/code&amp;gt;), adăugăm, apoi setăm ultimul element la valoarea acestui element plus unu.&lt;br /&gt;
* În caz contrar, dacă putem adăuga 1 la antepenultimul element (dacă nu este egal cu &amp;lt;code&amp;gt;n-2&amp;lt;/code&amp;gt;), adăugăm, apoi setăm următoarele două elemente din 1 în 1.&lt;br /&gt;
&lt;br /&gt;
Cred că observăm regula: vom parcurge combinarea curentă de la coadă spre cap până ce găsim un element ce poate fi mărit. Condiția ca un element pe poziția &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; să poată fi mărit este să putem adăuga după el &amp;lt;code&amp;gt;k-i&amp;lt;/code&amp;gt; elemente. Pentru aceasta pe poziția &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; putem pune ca element maxim valoarea &amp;lt;code&amp;gt;n - (k - i) = n -k + i&amp;lt;/code&amp;gt;. Pentru ca poziția la care ne vom opri să putem adăuga unu, trebuie ca valoarea ei sa fie strict mai mică.&lt;br /&gt;
&lt;br /&gt;
Iată un program care generează combinările de n luate câte k folosind algoritmul next_combination.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// Generare combinari folosind next_combination&lt;br /&gt;
// O(1) per combinare&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 24&lt;br /&gt;
&lt;br /&gt;
char sol[MAXN + 1]; // santinela pe prima pozitie&lt;br /&gt;
&lt;br /&gt;
// primeste o combinare in sol si genereaza urmatoarea combinare&lt;br /&gt;
// returneaza 0 daca nu mai avem combinari sau 1 in caz de succes&lt;br /&gt;
int next_comb( int n, int k ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = k;&lt;br /&gt;
  while ( sol[i] == n - k + i )&lt;br /&gt;
    i--;&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; 0 ) { // daca nu am terminat combinarile&lt;br /&gt;
    // i este pozitionat pe primul element ce poate fi marit&lt;br /&gt;
    // toate elementele de dupa pozitia i vor fi din 1 in 1&lt;br /&gt;
    sol[i]++;&lt;br /&gt;
    for ( i++; i &amp;lt;= k; i++ )&lt;br /&gt;
      sol[i] = sol[i - 1] + 1;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return i &amp;gt; 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, k, i;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;comb.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  for ( i = 1; i &amp;lt; k; i++ )&lt;br /&gt;
    sol[i] = i;&lt;br /&gt;
  &lt;br /&gt;
  sol[0] = 0;     // inutil, vreau sa subliniez ca este important&lt;br /&gt;
  sol[k] = k - 1; // pentru a genera prima combinare&lt;br /&gt;
  &lt;br /&gt;
  fout = fopen( &amp;quot;comb.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  while ( next_comb( n, k ) ) {&lt;br /&gt;
    for ( i = 1; i &amp;lt;= k; i++ )&lt;br /&gt;
      fprintf( fout, &amp;quot;%d &amp;quot;, sol[i] );&lt;br /&gt;
    fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
  &lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Putem demonstra că timpul de generare a următoarei combinări este &#039;&#039;O(1)&#039;&#039; amortizat (încercați să demonstrați).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Aranjamente ==&lt;br /&gt;
&lt;br /&gt;
Numim aranjamente de &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; luate câte &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; și notăm cu &amp;lt;math&amp;gt;A_N^K&amp;lt;/math&amp;gt; modurile de a aranja într-un șir, în ordine, &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; elemente din cele &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;. Ele sunt, de fapt, permutări parțiale, doar de anumite obiecte din cele &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Aranjamente - formulă ===&lt;br /&gt;
&lt;br /&gt;
Cum le calculăm?&lt;br /&gt;
&lt;br /&gt;
Avem mai multe moduri de a le calcula&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Permutări de combinări ===&lt;br /&gt;
&lt;br /&gt;
Generăm toate combinările posibile. Pe fiecare din ele o permutăm în toate modurile posibile. În acest fel obținem aranjamente distincte și le obținem pe toate. Rezultă o formulă cunoscută: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;A_N^K=C_N^K \cdot P_K&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Calcul similar cu cel al combinărilor ===&lt;br /&gt;
&lt;br /&gt;
* Știm câte permutări de &amp;lt;math&amp;gt;N&amp;lt;/math&amp;gt; elemente avem: &amp;lt;math&amp;gt;N!&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Primele &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; elemente ale acestor permutări vor constitui un aranjament valid.&lt;br /&gt;
* Fiecare din prefixele de &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; elemente apare de &amp;lt;math&amp;gt;(N - K)!&amp;lt;/math&amp;gt; ori, deoarece prefixul se schimbă doar după ce restul de &amp;lt;math&amp;gt;N - K&amp;lt;/math&amp;gt; elemente au fost permutate în toate modurile posibile.&lt;br /&gt;
* Deci, numărul de prefixe distincte de &amp;lt;math&amp;gt;K&amp;lt;/math&amp;gt; elemente este &amp;lt;math&amp;gt;\frac{N!}{(N-K)!}&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Acesta va fi numărul de aranjamente cerut.&lt;br /&gt;
&lt;br /&gt;
Din ambele metode rezultă aceeași formulă:&lt;br /&gt;
&amp;lt;math&amp;gt;A_N^K = \frac{N!}{(N-K)!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Proprietăți ale aranjamentelor ===&lt;br /&gt;
&lt;br /&gt;
# În programare, dacă avem nevoie să calculăm aranjamente vom folosi formula &amp;lt;math&amp;gt;A_N^K=C_N^K \cdot P_K&amp;lt;/math&amp;gt; . Putem precalcula combinările și permutările.&lt;br /&gt;
# Aranjamentele se comportă mai mult ca niște permutări decât ca niște combinări.&lt;br /&gt;
# Programul de generare prin funcție recursivă va semăna cu cel de permutări, nu cu cel de combinări.&lt;br /&gt;
# Putem aplica o procedură de &#039;&#039;next_arrangement&#039;&#039;. Ea va fi o combinație între cea de la permutări cu cea de la combinări.&lt;br /&gt;
# Putem aplica procedura de ranking / unranking de la permutări, cu diferența că baza factorial nu pornește de la 1 ci de la &amp;lt;math&amp;gt;n-k&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Puteți încerca să implementați generarea, next_arrangement și ranking/unranking, dacă vreți.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 22 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/nnr NNr] dată la interviu de inginer software la Google Inc&lt;br /&gt;
* [https://www.nerdarena.ro/problema/permutari1 Permutari1] ca aplicație la unranking&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sport Sport] dată la ONI 2011 clasa a 8a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/taburet Taburet] dată la ONI 2011 baraj gimnaziu, ca aplicație a permutărilor descrise ca funcție (rotațiile taburetelor)&lt;br /&gt;
* [https://www.nerdarena.ro/problema/2numere 2 Numere] dată la ONI 2008 baraj gimnaziu, ca aplicație a &#039;&#039;next_permutation&#039;&#039;&lt;br /&gt;
* [https://www.nerdarena.ro/problema/aranjamente Aranjamente] ca aplicație la generarea eficientă de aranjamente&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_22:_Generarea_elementelor_combinatoriale_prin_algoritmi_de_tip_succesor:_submul%C8%9Bimi,permut%C4%83ri,_combin%C4%83ri,_aranjamente,_next_permutation Accesează rezolvarea temei 22]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_21:_Cozi_duble_%C8%99i_maximul_%C3%AEn_fereastr%C4%83_glisant%C4%83&amp;diff=18580</id>
		<title>Clasa a 7-a Lecția 21: Cozi duble și maximul în fereastră glisantă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_21:_Cozi_duble_%C8%99i_maximul_%C3%AEn_fereastr%C4%83_glisant%C4%83&amp;diff=18580"/>
		<updated>2026-02-06T16:44:08Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Tipul deque (coadă dublă) ==&lt;br /&gt;
&lt;br /&gt;
Coada dublă este o coadă la care putem adăuga și scoate din ambele capete. Poate fi considerată ca o coadă și o stivă într-o singură structură de date, cu diferența că în coada dublă putem adăuga la începutul cozii.&lt;br /&gt;
&lt;br /&gt;
Cum implementăm o coadă dublă?&lt;br /&gt;
&lt;br /&gt;
O putem implementa la fel ca pe o coadă, într-un vector circular și folosind doi indici, &amp;lt;code&amp;gt;primul&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;ultimul&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* Adăugarea unui element la final de coadă dublă, precum și eliminarea unui element de la început de coadă dublă se face întocmai ca la o coadă obișnuită.&lt;br /&gt;
* Eliminarea unui element de la final se face astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int pop() {&lt;br /&gt;
  ultim = (ultim - 1 + QMAX) % QMAX;&lt;br /&gt;
  return coada[ultim];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Adăugarea unui element la început se face astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void pushFront( int e ) {&lt;br /&gt;
  prim = (prim - 1 + QMAX) % QMAX;&lt;br /&gt;
  coada[prim] = e;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Vom vedea că operațiunile pe biți pot simplifica puțin modificarea indicilor &amp;lt;code&amp;gt;primul&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;ultim&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Observație:&#039;&#039;&#039; toate operațiunile pe o coadă dublă se fac în timp constant, &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Maximum în fereastră glisantă (maximum over a sliding window) ==&lt;br /&gt;
&lt;br /&gt;
* Enunț: dându-se un șir de n numere considerăm cele &amp;lt;code&amp;gt;n – k + 1&amp;lt;/code&amp;gt; subsecvențe de &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; numere consecutive în șir (denumite și &#039;&#039;ferestre&#039;&#039; de lungime &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;). Putem vedea aceste ferestre ca pe o singură fereastră, deplasabilă, care ne dă acces la &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; elemente din vector odată. Se cere ca pentru fiecare fereastră să se găsească maximul.&lt;br /&gt;
* Algoritmul de rezolvare trivial (forță brută) recalculează maximul pentru fiecare din ferestre, având complexitate &#039;&#039;O(kn)&#039;&#039;.&lt;br /&gt;
* Pentru a reduce complexitatea găsirii maximului procedăm astfel: luăm prima fereastră, de la 0 la &amp;lt;code&amp;gt;k-1&amp;lt;/code&amp;gt;. Să spunem că maximul se află pe poziția &amp;lt;tt&amp;gt;p&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;. Pe măsură ce vom deplasa fereastra este imposibil ca elementele dinaintea maximului să fie vreodată maximul ferestrei, deoarece ele vor face parte împreună cu maximul din această fereastră. Să considerăm poziția celui mai mare element după maxim și la dreapta lui, în fereastră. Fie ea &amp;lt;tt&amp;gt;p&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt;. Rezultă că nici un element pe pozițiile intermediare &amp;lt;tt&amp;gt;i, p&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; 2&amp;lt;/tt&amp;gt;, nu poate fi vreodată maxim în fereastră, deoarece aceste elemente vor fi împreună cu &amp;lt;tt&amp;gt;p&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; în toate ferestrele care le conțin. Similar, considerând &amp;lt;tt&amp;gt;p&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; poziția următorului maxim, nici un element între &amp;lt;tt&amp;gt;p&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;p&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt;&amp;lt;/tt&amp;gt; nu va putea fi maxim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Image:maximum-fereastra-glisanta.gif|frame|none|În figură noul maxim va elimina din coadă maximele 2 și 3. Coada rămasă va conține maximul 1 și maximul nou]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
În urma acestor observații apare următorul algoritm:&lt;br /&gt;
&lt;br /&gt;
* Memorăm toate maximele descrescătoare din prima fereastră, într-o coadă dublă. Primul element din coadă este maximul ferestrei.&lt;br /&gt;
* Când deplasăm fereastra trebuie să actualizăm structura de maxime din coadă. Pentru aceasta dăm atenție elementului care dispare din fereastră și celui care intră în fereastră.&lt;br /&gt;
* Dacă elementul care iese nu este maximul curent, nu avem nimic de făcut.&lt;br /&gt;
* Dacă este maximul curent vom scoate primul element din coada de maxime.&lt;br /&gt;
* Dacă elementul proaspăt adăugat în fereastră este mai mic decât ultimul maxim din coadă se adaugă la coadă. În caz contrar el va &#039;&#039;arunca&#039;&#039; ultimul element, iar și încercăm din nou adăugarea în coadă.&lt;br /&gt;
* Rezultă că noul element din fereastră &#039;&#039;aruncă&#039;&#039; toate elementele de la urma cozii strict mai mici ca el, iar apoi îl adăugăm normal la coadă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Observații &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Atunci când eliminăm de la începutul cozii maximul care iese din fereastra glisantă &#039;&#039;&#039;coada dublă acționează ca o coadă&#039;&#039;&#039;.&lt;br /&gt;
* Atunci când eliminăm din coadă toate elementele de la final care sunt mai mici decât elementul ce intră în fereastra glisantă, &#039;&#039;&#039;coada dublă acționează ca o stivă&#039;&#039;&#039;.&lt;br /&gt;
* Atunci când adăugăm la final elementul care intră în fereastra glisantă &#039;&#039;&#039;coada dublă acționează atât ca o stivă cât și ca o coadă&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu de cod ===&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de implementare. În programul următor:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este numărul de elemente citit la intrare.&lt;br /&gt;
* &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; este mărimea ferestrei glisante.&lt;br /&gt;
* &amp;lt;code&amp;gt;deq[]&amp;lt;/code&amp;gt; este coada dublă ce memorează &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; elemente, dar va fi dimensionată la următoarea putere a lui 2.&lt;br /&gt;
* &amp;lt;code&amp;gt;rasp[]&amp;lt;/code&amp;gt; este vectorul de răspunsuri, dar care este folosit și la stocarea elementelor de la intrare, pentru a ști ce iese din fereastră; &amp;lt;code&amp;gt;rasp[]&amp;lt;/code&amp;gt; este maximul din fereastra ce începe la poziția &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  fin = fopen( &amp;quot;mfg.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ ) {&lt;br /&gt;
    if ( i &amp;gt;= k ) {&lt;br /&gt;
      if ( (max = deq[prim]) == rasp[i - k] ) // daca max iese din fereastra&lt;br /&gt;
        prim = (prim + 1) % MAXK_P2;          // elimina maximul din fereastra&lt;br /&gt;
      // stocheaza raspunsul pentru fereastra [i-k..i-1]&lt;br /&gt;
      // este ok sa suprascriem rasp[i - k], el nu va mai fi necesar&lt;br /&gt;
      rasp[i - k] = max; &lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    rasp[i] = h = readInt(); // stocam inaltimea curenta, e nevoie la iesire&lt;br /&gt;
    while ( ultim != prim &amp;amp;&amp;amp; deq[ultim - 1] &amp;lt; h ) // elimina din coada&lt;br /&gt;
      ultim = (ultim - 1 + MAXK_P2) % MAXK_P2;    // cata vreme h e mai mare&lt;br /&gt;
    deq[ultim] = h;          // depunem inaltimea curenta in coada&lt;br /&gt;
    ultim = (ultim + 1) % MAXK_P2;&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  rasp[n - k] = deq[prim];   // stocam separat ultimul maxim - caz particular&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Complexitate ===&lt;br /&gt;
&lt;br /&gt;
* La prima vedere pare că, la fiecare pas, putem elimina &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; elemente din coada dublă, deci complexitatea ar părea &#039;&#039;O(kn)&#039;&#039;.&lt;br /&gt;
* Folosind analiza amortizată rezultă complexitatea reală ca fiind &#039;&#039;O(n)&#039;&#039;. Fiecare element este adăugat în coadă o dată și este scos cel mult o dată.&lt;br /&gt;
* Memoria suplimentară folosită este mărimea maximă a cozii, adică &#039;&#039;O(k)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Aplicație: numărul maxim ce se poate forma prin eliminarea a K cifre ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Enunț:&#039;&#039;&#039; dat un număr de &#039;&#039;N&#039;&#039; cifre, dorim să eliminăm &#039;&#039;K&#039;&#039; cifre. Să se afișeze numărul maxim ce se poate forma astfel.&lt;br /&gt;
&lt;br /&gt;
Cum rezolvăm această problemă și ce legătură are ea cu maximul în fereastră glisantă?&lt;br /&gt;
&lt;br /&gt;
La prima vedere problema pare complicată. Numărul este foarte mare. Și atunci încercăm să o simplificăm:&lt;br /&gt;
&lt;br /&gt;
* Putem afla care este prima cifră a acestui număr?&lt;br /&gt;
* Știm că numărul va avea &#039;&#039;N-K&#039;&#039; cifre.&lt;br /&gt;
* Este clar că numărul va începe cu una din primele &#039;&#039;K+1&#039;&#039; cifre. De ce? Deoarece dacă el începe cu a &#039;&#039;K+2-a&#039;&#039; cifră, ar trebui să eliminăm &#039;&#039;K+1&#039;&#039; cifre de la începutul numărului, ceea ce nu putem.&lt;br /&gt;
* Vrem ca prima lui cifră să fie cât mai mare.&lt;br /&gt;
* Este, deci, logic, că prima cifră a numărului va fi maximul din primele &#039;&#039;K+1&#039;&#039; cifre. Să presupunem că cifra maximă este a cincea.&lt;br /&gt;
* Pentru ca a cincea cifră să devină prima trebuie să eliminăm primele patru cifre.&lt;br /&gt;
* Am rămas deci cu restul numărului, ce are &#039;&#039;N-5&#039;&#039; cifre (patru eliminate, una selectată), din care mai avem de eliminat &#039;&#039;K-4&#039;&#039; cifre.&lt;br /&gt;
* Putem deci relua metoda, cu un număr mai mic.&lt;br /&gt;
&lt;br /&gt;
Ce facem dacă în primele &#039;&#039;K+1&#039;&#039; cifre avem mai multe maxime dintre care să alegem? Vom alege primul maxim, deoarece nu vrem să eliminăm cifre maximale.&lt;br /&gt;
&lt;br /&gt;
Rezultă următorul algoritm:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Algoritmul 1 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;&lt;br /&gt;
# Citește de la intrare primele K cifre.&lt;br /&gt;
# Câtă vreme mai avem cifre de eliminat (K &amp;gt; 0)&lt;br /&gt;
## Citește încă o cifră de la intrare.&lt;br /&gt;
## Calculează maximul dintre primele K+1 cifre. Fie cifra C pe poziția P.&lt;br /&gt;
## Afișează la ieșire cifra C.&lt;br /&gt;
## Elimină din cifrele memorate primele P-1 cifre.&lt;br /&gt;
## K = K - (P - 1)&lt;br /&gt;
# Citește cifrele rămase la intrare și afișează-le.&lt;br /&gt;
&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Observații: &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Algoritmul selectează, pe rând, cifrele rămase, în loc să se concentreze pe cifrele eliminate.&lt;br /&gt;
* Algoritmul nu specifică cum găsim maximul cifrelor. O căutare liniară ar duce la un algoritm ineficient.&lt;br /&gt;
&lt;br /&gt;
Cum găsim eficient maximul din primele &#039;&#039;K+1&#039;&#039; cifre? Folosind o coadă dublă. Vom avea o fereastră glisantă de lungime variabilă. Ea va avea la început lungimea &#039;&#039;K+1&#039;&#039;, iar apoi va scădea pe măsură ce scade &#039;&#039;K&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Rezultă algoritmul:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Algoritmul 2 === &lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;&lt;br /&gt;
# Depune primele K cifre de la intrare într-o coadă dublă, păstrând doar maxime consecutive descrescătoare, dar memorând și pozițiile lor.&lt;br /&gt;
# Câtă vreme mai avem cifre de eliminat (K &amp;gt; 0)&lt;br /&gt;
## Citește încă o cifră de la intrare și depune-o în coadă cu poziția sa, eliminând cifrele mai mici ca ea.&lt;br /&gt;
## Prima pereche din coadă va fi maximul C, cu poziția sa P.&lt;br /&gt;
## Afișează la ieșire cifra C.&lt;br /&gt;
## Elimină prima pereche din coadă.&lt;br /&gt;
## K = K - (P - 1)&lt;br /&gt;
# Citește cifrele rămase la intrare și afișează-le&lt;br /&gt;
&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Observații: &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Algoritmul se poate aplica și pentru numărul minim, cu o singură diferență: nu putem alege ca primă cifră minimă o cifră zero. Următoarele cifre pot fi zero.&lt;br /&gt;
* În realitate, dacă urmărim acest algoritm cu atenție, coada va conține întotdeauna maximele celor &#039;&#039;K+1&#039;&#039; elemente din care dorim să găsim maximul. Explicație: cifrele eliminate sunt eliminate și din coadă. Singura cifră care dispare din coadă este cea afișată, pe care o compensăm la pasul 2.1 citind încă o cifră.&lt;br /&gt;
&lt;br /&gt;
Astfel, algoritmul de mai sus se simplifică, nemaiavând nevoie să stocăm indici, ci doar valori. Să vedem.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Algoritmul 3 ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;&lt;br /&gt;
# Depune primele K cifre de la intrare într-o coadă dublă, păstrând doar maxime consecutive descrescătoare.&lt;br /&gt;
# Câtă vreme mai avem cifre de citit&lt;br /&gt;
## Citește încă o cifră de la intrare și depune-o în coadă, eliminând cifrele mai mici ca ea.&lt;br /&gt;
## Prima cifră din coadă va fi maximul C&lt;br /&gt;
## Afișează la ieșire cifra C.&lt;br /&gt;
## Elimină cifra C din coadă.&lt;br /&gt;
&amp;lt;/tt&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 20 ==&lt;br /&gt;
&lt;br /&gt;
Ambele probleme de la temă cer calculul numărului minim/maxim obținut după eliminarea unor cifre. Genul acesta de prelucrare, eliminarea unor cifre ale unui număr astfel încât numărul rămas să fie maxim (sau minim) apare des la olimpiade.&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/mfg Maxim în fereastră glisantă] &lt;br /&gt;
* [https://www.nerdarena.ro/problema/cuburi Cuburi] dată la ONI 2012 clasa a 8-a. Ea cere eliminarea unui număr mic de cifre, totdeauna 3, ceea ce înseamnă că nu suntem obligați să implementăm o coadă dublă pentru a o rezolva, putem calcula minimul prin forță brută.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/maxim Maxim] dată la ONI 2007 clasa a 5-a. Este o problemă de clasa a 5-a la origine, adaptată pentru clasa a 7-a. Rezolvați-o folosind algoritmul din lecție.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații: &#039;&#039;&#039;&lt;br /&gt;
 &lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; rezolvarea voastră la problema maxim trebuie să folosească o coadă dublă și algoritmul de mai sus. Scopul este să învățăm să implementăm coada dublă.&lt;br /&gt;
&lt;br /&gt;
Notă: cei cărora le plac provocările, încercați să rezolvați problema maxim cu memorie &#039;&#039;O(Σ)&#039;&#039;, unde &#039;&#039;&#039;Σ&#039;&#039;&#039; este numărul de cifre, adică 10. Pentru aceasta trebuie să modificați implementarea cozii duble.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/jbird JBird] &lt;br /&gt;
* [https://www.nerdarena.ro/problema/Valutar Valutar] dată la OJI 2019 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_21:_Cozi_duble_%C8%99i_maximul_%C3%AEn_fereastr%C4%83_glisant%C4%83&amp;amp;action=edit&amp;amp;redlink=1 Accesează rezolvarea temei 21]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_19:_Cozi_%C8%99i_algoritmul_lui_Lee&amp;diff=18579</id>
		<title>Clasa a 7-a Lecția 19: Cozi și algoritmul lui Lee</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_19:_Cozi_%C8%99i_algoritmul_lui_Lee&amp;diff=18579"/>
		<updated>2026-02-06T16:42:00Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/nja4_LDg9Xk&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tipul coadă ==&lt;br /&gt;
&lt;br /&gt;
Coada (în engleză queue) este o &#039;&#039;grămadă&#039;&#039; de obiecte ordonate după ordinea &#039;&#039;&#039;FIFO&#039;&#039;&#039;: &#039;&#039;first in, first out&#039;&#039;. Aceasta înseamnă că putem adăuga obiecte în coadă, iar atunci când le vom scoate, le vom scoate în aceeași ordine în care le-am adăugat. Ne aducem aminte că, prin contrast, stiva scoate obiectele în ordine inversă față de cum au fost adăugate (ordine &#039;&#039;&#039;LIFO&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Coada ca tip de date abstract ===&lt;br /&gt;
&lt;br /&gt;
Ne aducem aminte despre ideea de tip de date abstract: &#039;&#039;un tip de date abstract&#039;&#039; este un model matematic. Acest model definește operații asupra tipului de date, precum și restricții asupra efectului acestor operații. Tipurile de date abstracte sunt utile în simplificarea algoritmilor, deoarece descompun problema în subprobleme mai mici, cunoscute și studiate.&lt;br /&gt;
&lt;br /&gt;
Tipul de date abstract coadă definește operațiile:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;enqueue&#039;&#039;, care adaugă un element la coadă&lt;br /&gt;
* &#039;&#039;dequeue&#039;&#039;, care scoate un element din coadă&lt;br /&gt;
* &#039;&#039;empty&#039;&#039;, care ne spune dacă coada este goală&lt;br /&gt;
* &#039;&#039;full&#039;&#039;, care ne spune dacă coada este plină&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Restricțiile sunt asupra ordinii de returnare a elementelor și anume ele trebuie returnate după regula &#039;&#039;primul venit primul plecat&#039;&#039;. De aceea se spune că o coadă este o structură de tip &#039;&#039;&#039;FIFO&#039;&#039;&#039; (&#039;&#039;first in, first out&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
[[Image:coada.gif|frame|none|Coada ca tip abstract de date]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Implementarea cozilor cu vectori circulari ===&lt;br /&gt;
&lt;br /&gt;
Precum am mai spus, tipurile de date abstracte separă funcționalitatea de implementare. În cazul cozii există mai multe implementări posibile. Noi vom studia una dintre cele mai folosite: implementarea cu vectori circulari. Această implementare:&lt;br /&gt;
&lt;br /&gt;
* Folosește un vector pentru a păstra elementele și doi indici, primul și ultimul care memorează poziția primului, respectiv ultimului element din coadă. Pentru a nu deplasa elemente atunci când scoatem un element din coadă vom incrementa doar poziția primului element. Atunci când adăugăm un element în coadă incrementăm poziția ultimului element.&lt;br /&gt;
* Pentru a refolosi golurile rămase în urmă la scoaterea din coadă și a nu deplasa cei doi indici la infinit vom defini o mărime maximă a cozii, n. Atunci când unul din indici depășește această mărime el revine la zero. Logic vorbind după ultimul element din vector urmează din nou primul. De aceea spunem că vectorul este circular. Formula de actualizare a indicilor devine &amp;lt;code&amp;gt;primul = (primul + 1) % n&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;ultimul = (ultimul + 1) % n&amp;lt;/code&amp;gt;, unde &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este numărul maxim de elemente din coadă.&lt;br /&gt;
&lt;br /&gt;
[[Image:coada-vector-circular.gif|frame|none|Implementare coadă cu vector circular]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Observăm că primul este poziția primului element din coadă, iar ultimul este poziția primului element liber din vector.&lt;br /&gt;
* Coadă goală. Cum răspundem la întrebarea &#039;&#039;este coada goală&#039;&#039;? Se observă că acest lucru se întâmplă când primul == ultimul.&lt;br /&gt;
* Coada plină. Cum răspundem la întrebarea &#039;&#039;este coada plină&#039;&#039;? Observăm că aceasta se întâmplă tot atunci când primul == ultimul. Pentru a diferenția între aceste două cazuri vom &#039;&#039;sacrifica&#039;&#039; un element din vector, folosind numai &amp;lt;code&amp;gt;n-1&amp;lt;/code&amp;gt; poziții. În felul acesta testul de coadă plină va fi &amp;lt;code&amp;gt;(ultimul + 1) % n == primul&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* În această implementare cele patru operații au complexitate &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;Important&#039;&#039;&#039;: în practică, pentru a nu face împărțiri, dimensiunea maximă a cozii va fi o putere a lui doi, constantă definită în program.&lt;br /&gt;
* Unul din avantajele acestei implementări, de care veți auzi în detaliu în facultate este că scoaterea de elemente se poate face în paralel cu adăugarea, de exemplu în cazul când un proces generează elementele de adăugat și un alt proces, independent, colectează elementele spre procesare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple de probleme ce folosesc tipul coadă ===&lt;br /&gt;
&lt;br /&gt;
Exemple de probleme ce folosesc cozi în rezolvări:&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/maxim2 Maxim] dată la concursul ONI 2019 clasa a 6-a (punctul 2)&lt;br /&gt;
* [https://www.nerdarena.ro/problema/livada Livada] dată la cercul de informatică Vianu 2014 clasa a 9-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
La [http://pbinfo.ro/ pbinfo.ro]:&lt;br /&gt;
&lt;br /&gt;
* [https://www.pbinfo.ro/?pagina=probleme&amp;amp;id=873 Vase]&lt;br /&gt;
* [https://www.pbinfo.ro/?pagina=probleme&amp;amp;id=1239 Fracții3]&lt;br /&gt;
* [https://www.pbinfo.ro/?pagina=probleme&amp;amp;id=2187 Expand] (grea)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Algoritmul lui Lee (BFS Fill) ==&lt;br /&gt;
&lt;br /&gt;
În anul 1961 C. Y. Lee a publicat un algoritm de studiu al căilor de conectare între două puncte în diagrame electronice. Algoritmul original presupune o rețea plană (un circuit electronic) în care, mulțumită proprietăților curentului electric, toate muchiile sunt de lungime unu. De-a lungul timpului (și aparent mai mult în România) acest algoritm a ajuns să fie asociat cu parcurgerea pe lățime a unei matrice, drept pentru care acesta este modul în care îl voi prezenta aici.&lt;br /&gt;
&lt;br /&gt;
Deoarece algoritmul lui Lee este o parcurgere pe lățime ar trebui predat după conceptul de parcurgere pe lățime. De aceea, în această lecţie, voi încerca să prezint ambele.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Aplicații ale parcurgerii pe lățime ===&lt;br /&gt;
&lt;br /&gt;
* Găsirea drumului minim de la un punct la un altul, sau la mai multe puncte finale.&lt;br /&gt;
* Mai puțin menționat: fill cu mai puțină memorie suplimentară (dar și mai lent)&lt;br /&gt;
&lt;br /&gt;
Pentru exemplificare, să considerăm următoarea problemă: se dă un labirint codificat ca o matrice cu zero și unu, zero semnificând loc liber și unu fiind element obstacol (zid). Se dau de asemenea un punct de start și unul de final. În matrice ne putem deplasa pe linie sau pe coloană, cu condiția să nu avem un obstacol. Să se calculeze, lungimea drumului minim între cele două puncte, dacă există un astfel de drum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Parcurgerea în adîncime ===&lt;br /&gt;
&lt;br /&gt;
Când am discutat despre algoritmul &#039;&#039;flood fill&#039;&#039; am menționat că el este o parcurgere a matricei &#039;&#039;în adâncime&#039;&#039;, deoarece traversarea elementelor matricei se face avansând din vecin în vecin până ce se ajunge la o fundătură (un obstacol, ieșire din matrice, sau element deja parcurs). Am denumit această parcurgere &#039;&#039;DFS&#039;&#039; (depth first search), aceasta însemnând că în alegerea noului element de vizitat preferăm adâncimea. Ne putem imagina această parcurgere cu un fir subțire de apă care curge într-o direcție oarecare, apoi, când nu mai poate schimbă direcția. Când ramura curentă se &amp;quot;înfundă&amp;quot; apa se va ramifica din firul principal undeva cel mai aproape de fundătură, acolo unde o poate lua pe altă direcție.&lt;br /&gt;
&lt;br /&gt;
Am menționat atunci că pentru acest tip de parcurgere avem nevoie de o &#039;&#039;&#039;stivă&#039;&#039;&#039; pentru memorarea elementelor ce urmează a fi parcurse. Stiva este fie explicită (nu am vorbit despre aceasta), fie implicită, într-o implementare recursivă. Această stivă are consecințe: ea adaugă &#039;&#039;O(M x N)&#039;&#039; memorie suplimentară.&lt;br /&gt;
&lt;br /&gt;
Folosind parcurgerea în adâncime, &#039;&#039;flood fill&#039;&#039;, putem răspunde la întrebarea dacă există drum între cele două puncte, dar nu putem calcula drumul minim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Parcurgerea pe lățime ===&lt;br /&gt;
&lt;br /&gt;
Prin contrast, ne putem imagina o parcurgere pe &#039;&#039;lățime&#039;&#039;. În această parcurgere vom traversa mai întâi elementele vecine cu elementul de start. Apoi vom continua cu elementele vecine cu ele. Și apoi cu elementele vecine cu vecinele, și așa mai departe. Putem vizualiza această parcurgere dacă ne imaginăm că turnăm apă în punctul de pornire. Ea se va împrăștia uniform în toate direcțiile. Dacă va da de obstacole ea va merge de-a lungul lor și le va ocoli. La orice moment distanța între orice punct de pe frontiera apei și punctul de start este aceeași.&lt;br /&gt;
&lt;br /&gt;
Denumim această parcurgere &#039;&#039;BFS&#039;&#039; (breadth first search), deoarece în alegerea noului element de parcurs vom prefera lățimea. La fiecare iterație &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; vom trata elementele aflate la distanță &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; de punctul original. Ele formează frontiera (marginea) apei, în exemplul nostru. După fiecare iterație frontiera va fi înlocuită cu o nouă frontieră aflată la distanță mai mare cu unu decât vechea frontieră.&lt;br /&gt;
&lt;br /&gt;
Precum vom vedea mai jos această parcurgere este similară cu cea în adâncime, diferența fiind că ea folosește o coadă în loc de stivă pentru memorarea elementelor ce urmează a fi parcurse. Spre deosebire de &#039;&#039;flood fill&#039;&#039;, coada trebuie să fie explicită. De aceea algoritmul poate părea foarte diferit de &#039;&#039;flood fill&#039;&#039;, dar, în realitate nu este.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplificare grafică ===&lt;br /&gt;
&lt;br /&gt;
Să vedem niște vizualizări ale algoritmului BFS fill preluate de la wikipedia: [https://en.wikipedia.org/wiki/Flood_fill algoritm flood fill]:&lt;br /&gt;
&lt;br /&gt;
[[File:Wfm_floodfill_animation_queue_wikipedia.gif|frame|none|Exemplu de BFS fill cu 4 direcții]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Implementarea parcurgerii pe lățime în matrice ===&lt;br /&gt;
&lt;br /&gt;
Cum implementăm parcurgerea pe lățime? Cum memorăm frontiera? O idee imediată ar fi să ținem două frontiere: cea curentă, în curs de procesare, și cea nouă, în curs de generare. La fiecare iterație calculăm o nouă frontieră pe baza celei dinainte. Apoi comutăm pe frontiera cea nouă. Ce conțin frontierele (ce stocăm)? Vom păstra identificatorii unici ai elementelor matricei, respectiv linia și coloana. Frontierele vor fi vectori de perechi (linie, coloană). Această metodă funcționează, dar nu este nici cea mai simplă și nici cea mai economică ca memorie.&lt;br /&gt;
&lt;br /&gt;
În practică nu vom face distincția între frontiera veche și cea nouă. Elementele din frontiera nouă vor fi adăugate la frontiera veche, la sfârșit. Vom avea astfel o singură frontieră de elemente de procesat. Vom procesa elementele de la începutul frontierei și vom adăuga elementele noi, vecinii, la finalul frontierei. În imaginea noastră cu apa punctele procesate vor descrie o spirală în jurul punctului de pornire.&lt;br /&gt;
&lt;br /&gt;
În acest fel ajungem la structura de date pe care o vom folosi pentru stocarea punctelor de frontieră: &#039;&#039;&#039;coada&#039;&#039;&#039;. Așa cum am menționat mai sus, în loc de &#039;&#039;&#039;stivă&#039;&#039;&#039; vom folosi o &#039;&#039;&#039;coadă&#039;&#039;&#039; de perechi (&#039;&#039;linie, coloană&#039;&#039;) pentru stocarea elementelor ce urmează a fi parcurse. La un pas vom extrage din coadă primul element și vom adăuga elementele vecine lui la finalul cozii, câtă vreme ele nu se află deja în &#039;&#039;&#039;coadă&#039;&#039;&#039; (coada conține numai elemente unice).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Algoritmul de parcurgere BFS ===&lt;br /&gt;
&lt;br /&gt;
Iată o schiță de algoritm, care nu este neapărat universal valabil, dar poate fi adaptat diverselor situații. Această versiune de algoritm își propune să găsească cel mai scurt drum de la &#039;&#039;(lstart, cstart)&#039;&#039; la &#039;&#039;(lend, cend)&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;&lt;br /&gt;
# Inițializează matricea labirint L[][] cu 0 pentru loc liber și -1 pentru obstacol&lt;br /&gt;
# Inițializează coada cu elementul de pornire (lstart, cstart)&lt;br /&gt;
# Setează L[lstart][cstart]  // distanța inițială&lt;br /&gt;
# Execută:&lt;br /&gt;
## Scoate primul element din coadă. Fie el linia (lin, col)&lt;br /&gt;
## Setează dist  // distanța acestui element&lt;br /&gt;
## Pentru fiecare vecin (l, c) al lui (lin, col)&lt;br /&gt;
### Dacă L[l][c] = 0 // nu se află în coadă și nici nu este obstacol&lt;br /&gt;
#### Adaugă (l, c) în coadă&lt;br /&gt;
#### Setează L[l][c] &lt;br /&gt;
# Câtă vreme coada nu este vidă și (lin, col) nu este destinația (lend, cend)&lt;br /&gt;
# Afișează L[lin][col] // distanța până la cel mai apropiat punct destinație&lt;br /&gt;
&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Note de implementare &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Vecinii unui element din matrice pot fi fie cei adiacenți pe linie și coloană, fie și cei pe diagonală, algoritmul funcționează la fel.&lt;br /&gt;
* De obicei vom folosi tehnicile învățate:&lt;br /&gt;
** Bordarea matricelor pentru a scăpa de testul de ieșire din matrice&lt;br /&gt;
** Vectorii de direcție pentru a genera ușor vecinii&lt;br /&gt;
* Problema poate fi considerată una de simulare: simulăm parcurgerea tuturor traseelor optime în același timp.&lt;br /&gt;
* Dacă nu există drum între cele două puncte algoritmul se va termina când coada devine goală.&lt;br /&gt;
* În funcție de ceea ce se cere (drum minim până la destinație, drum minim până la anumite puncte, sau drum minim până la toate elementele matricei) coada poate fi parcursă până la final, sau ne putem opri când anumite condiții ale simulării au fost îndeplinite, cum ar fi atingerea punctului destinație.&lt;br /&gt;
* Cum știm dacă un element se află deja în coadă? Printr-o matrice de frecvență: la momentul adăugării în coadă vom marca aceasta în matrice. De cele mai multe ori putem combina matricea de frecvență cu matricea de distanțe (ca în algoritmul de mai sus): un element diferit de zero semnifică distanța acelui element la punctul de start. În această notație obstacolele trebuie memorate ca numere negative pentru a nu fi confundate cu distanțe.&lt;br /&gt;
* Cum știm că am ajuns în punctul final? Putem să testăm explicit linia și coloana. Dar dacă avem mai multe puncte finale trebuie să le marcăm într-o matrice. Această matrice se poate de obicei combina cu matricea de frecvență și cu cea de distanțe, deoarece avem nevoie doar de un bit unu sau zero.&lt;br /&gt;
* Algoritmul poate fi adaptat să calculeze drumul minim de la mai multe puncte de pornire la mai multe puncte destinație. În acest caz vom inițializa coada cu toate punctele de pornire.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Reconstrucția drumului minim ===&lt;br /&gt;
&lt;br /&gt;
Pentru a reconstitui un drum minim între sursă și destinație vom porni invers: de la destinație și vom avansa în &amp;lt;code&amp;gt;L[][]&amp;lt;/code&amp;gt; pe elemente descrescătoare din unu în unu, până ce ajungem la sursă. &#039;&#039;&#039;Atenție&#039;&#039;&#039;, drumul nu este neapărat unic.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Implementarea cozii de coordonate ===&lt;br /&gt;
&lt;br /&gt;
Pentru coadă vom folosi un vector circular de mărime &#039;&#039;2(M+N)&#039;&#039; (vezi explicația la capitolul de complexități). Vom avea o variabilă, &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt; care păstrează indicele de adăugare în coadă, precum și o variabilă ultim care păstrează poziția primului element &#039;&#039;gol&#039;&#039;, în afara cozii. Acești doi indici vor avansa circular, modulo mărimea cozii.&lt;br /&gt;
&lt;br /&gt;
Coada poate să fie un vector de elemente tip &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;, sau doi vectori separați. Prima variantă este mai bună din punct de vedere &#039;&#039;cache&#039;&#039;, dar poate duce la risipă de memorie în anumite situații, dacă elementul de tip &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; nu ocupă un număr de octeți divizibil cu patru (sau cu lungimea cuvântului de memorie).&lt;br /&gt;
&lt;br /&gt;
Vom folosi trei funcții:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;enqueue( lin, col )&amp;lt;/code&amp;gt; care adaugă elementul &#039;&#039;(lin, col)&#039;&#039; în coadă.&lt;br /&gt;
* &amp;lt;code&amp;gt;dequeue()&amp;lt;/code&amp;gt; care extrage elementul &#039;&#039;(lin, col)&#039;&#039; din coadă.&lt;br /&gt;
* &amp;lt;code&amp;gt;emptyqueue()&amp;lt;/code&amp;gt; care returnează 1 dacă coada este vidă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Iată o posibilă implementare ce presupune că numărul de linii și coloane este maxim 255:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int prim, ultim;&lt;br /&gt;
unsigned char coadal[NCOADA], coadac[NCOADA]; // coordonate mici&lt;br /&gt;
&lt;br /&gt;
// adauga coordonate in coada&lt;br /&gt;
static inline void enqueue( int l, int c ) {&lt;br /&gt;
  coadal[ultim] = l;&lt;br /&gt;
  coadac[ultim] = c;&lt;br /&gt;
  ultim = (ultim + 1) % NCOADA;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scoate coordonate din coada&lt;br /&gt;
static inline int dequeue() {&lt;br /&gt;
  int retval = coadal[prim] * 256 + coadac[prim];&lt;br /&gt;
&lt;br /&gt;
  prim = (prim + 1) % NCOADA;&lt;br /&gt;
&lt;br /&gt;
  return retval;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza 1 cand coada este goala&lt;br /&gt;
static inline int emptyqueue() {&lt;br /&gt;
  return prim == ultim;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Observații: &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Funcția &amp;lt;code&amp;gt;dequeue()&amp;lt;/code&amp;gt; returnează două valori, linie și coloană. Ele fiind mici le-am combinat într-un singur întreg. Acest lucru este posibil în general, deoarece putem combina două valori &amp;lt;code&amp;gt;unsigned short&amp;lt;/code&amp;gt; într-un &amp;lt;code&amp;gt;unsigned int&amp;lt;/code&amp;gt;, ceea ce ne permite linii și coloane până în 65535, număr în general acoperitor pentru o matrice stocată în memorie.&lt;br /&gt;
* Astfel am putea să stocăm în coadă direct produsul returnat de funcția &amp;lt;code&amp;gt;dequeue()&amp;lt;/code&amp;gt;. Avantajele se datorează, în mare, &#039;&#039;cache-ului&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Complexități în timp și spațiu ===&lt;br /&gt;
&lt;br /&gt;
Timpul de execuție este &#039;&#039;O(M x N)&#039;&#039; deoarece fiecare element al matricei va fi luat în calcul cel mult o dată.&lt;br /&gt;
&lt;br /&gt;
Memoria necesară este &#039;&#039;O(M x N)&#039;&#039; deoarece avem nevoie de matricea de distanțe, matricea de frecvențe și cea de puncte terminale, precum și memoria ocupată de coadă. În realitate, de cele mai multe ori putem unifica matricele suplimentare, unindu-le cu matricea labirint. În acest caz memoria suplimentară este cea a cozii. La momentul când scriu aceste rânduri nu am găsit o estimare asupra dimensiunii maxime a cozii necesare. Intuitiv ea este &#039;&#039;O(M + N)&#039;&#039;, deoarece frontiera, într-un labirint fără obstacole, când punctul de pornire este în mijloc, nu va depăși M+N. Este neclar dacă o altă matrice și un alt punct de pornire o poate face să depășească &#039;&#039;M + N&#039;&#039; și cu cât. Ca practică de implementare sfatul meu este să o dimensionați de &#039;&#039;2(M + N)&#039;&#039;, acoperitor, deoarece este un număr oricum mult mai mic decât matricea deja stocată, &#039;&#039;M x N&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Datorită consumului atât de mic de memorie suplimentară, algoritmul lui Lee poate fi folosit ca și ca algoritm de fill pe matrice mari, sau când memoria suplimentară este mică. El va fi însă mai lent decât &#039;&#039;flood fill&#039;&#039;, de obicei de multe ori (20-30 de ori), ceea ce dovedește încă o dată că, pentru unii algoritmi, memoria suplimentară se traduce în timp de execuție mai mic.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Aplicații ===&lt;br /&gt;
&lt;br /&gt;
Algoritmul lui Lee este necesar când dorim să aflăm informații despre drumuri și distanțe:&lt;br /&gt;
&lt;br /&gt;
* Găsirea ieșirii dintr-un labirint / castel / hartă.&lt;br /&gt;
* Găsirea celui mai scurt drum între două puncte dintr-o matrice.&lt;br /&gt;
* În lumea reală, proiectarea de circuite electronice.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple de probleme ce folosesc algoritmul lui Lee ===&lt;br /&gt;
&lt;br /&gt;
Orice problemă rezolvabilă prin &#039;&#039;flood fill&#039;&#039; este, desigur, rezolvabilă și cu &#039;&#039;Lee&#039;&#039;, folosind mai puțină memorie, dar consumând mai mult timp pentru executarea programului. N-am găsit foarte multe exemple de probleme ce necesită &#039;&#039;Lee&#039;&#039;, dar nu se pot rezolva cu &#039;&#039;flood fill&#039;&#039;. Iată câteva exemple:&lt;br /&gt;
&lt;br /&gt;
Exemple de probleme ce folosesc algoritmul lui Lee în rezolvări:&lt;br /&gt;
&lt;br /&gt;
La [https://www.nerdarena.ro/ NerdArena]:&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/alee Alee] dată la OJI 2007 clasa a 10-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/insule Insule] dată la OJI 2009 clasa a 10-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/miting Miting] dată la OJI 2016 clasa a 10-a (grea)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
La [http://infoarena.ro/ InfoArena]:&lt;br /&gt;
&lt;br /&gt;
* [https://www.infoarena.ro/algoritmul-lee Articolul de la Infoarena] include câteva exemple&lt;br /&gt;
* [https://www.infoarena.ro/problema/muzeu Muzeu] &lt;br /&gt;
* [https://www.infoarena.ro/problema/traseu3 Traseu3] dată la ONI 2014, clasa a 9-a (Lee 3D, relativ ușoară)&lt;br /&gt;
* [https://www.infoarena.ro/problema/gheizere Gheizere] dată la ONI 2012, clasa a 10-a (problemă grea)&lt;br /&gt;
* [https://www.infoarena.ro/problema/ai AI] dată la OJI 2011, clasa a 10-a (enunț complex)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 19 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/livada Livada] &lt;br /&gt;
* [https://www.nerdarena.ro/problema/alee Alee] alee dată la OJI 2007 clasa a 10-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/insule Insule] dată la OJI 2009 clasa a 10-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_19:_Cozi_%C8%99i_algoritmul_lui_Lee&amp;amp;action=edit&amp;amp;redlink=1 Accesează rezolvarea temei 19]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_18:_Divide_et_impera,_mergesort,_quicksort&amp;diff=18578</id>
		<title>Clasa a 7-a Lecția 18: Divide et impera, mergesort, quicksort</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_18:_Divide_et_impera,_mergesort,_quicksort&amp;diff=18578"/>
		<updated>2026-02-06T16:39:45Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/bgMxb050keA&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnici de programare: divide et impera ==&lt;br /&gt;
&lt;br /&gt;
Denumită și divide and conquer sau dezbină și stăpânește, este o tehnică cunoscută de mii de ani conducătorilor. Este o tehnică de cucerire sau menținere a puterii asupra unui grup care ar avea putere mai mare dacă s-ar uni. Ținând acel grup dezbinat, fiecare facțiune în parte are putere mică și poate fi cucerită, sau condusă. Tehnica a fost aplicată de Cezar, Napoleon, iar, mai recent, de către Britanici în Africa și de către Stalin în Rusia. Dintre tehnicile folosite în lumea reală putem menționa:&lt;br /&gt;
&lt;br /&gt;
* Ajutorarea și promovarea celor care cooperează cu puterea, pedepsirea celor ce nu cooperează.&lt;br /&gt;
* Încurajarea cheltuielilor prostești, pentru a reduce capitalul disponibil organizării sau luptei.&lt;br /&gt;
* Încurajarea divizării oamenilor pentru a preveni formarea alianțelor.&lt;br /&gt;
* Încurajarea turnătorilor, cei care trâmbițează orice alianță posibil periculoasă.&lt;br /&gt;
* Sădirea de neîncredere între oameni.&lt;br /&gt;
&lt;br /&gt;
O parte din aceste tehnici se regăsesc și în țara noastră, precum și în majoritatea țărilor lumii, inclusiv cele dezvoltate. De exemplu, toate țările lumii folosesc un număr uriaș de taxe și impozite, fiecare din ele relativ mici. Când le însumăm constatăm că în jur de 90% din banii noștri se duc pe aceste taxe și impozite. Dacă ar exista o taxă unică de 90%, oamenii s-ar revolta. Astfel, tehnica de împărțire a unor taxe mari în mai multe taxe mici îi face pe oameni să nu le observe și să nu se revolte.&lt;br /&gt;
&lt;br /&gt;
Pentru exemple clasice de folosire a acestei tehnici în lumea reală vizitați pagina de [http://en.wikipedia.org/wiki/Divide_and_rule divide et impera] a wikipediei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica divide et impera (divide and conquer) în informatică ==&lt;br /&gt;
&lt;br /&gt;
În informatică divide et impera constă în împărțirea problemei în subprobleme mai mici, nesuprapuse (o tehnică în care subproblemele sunt suprapuse este programarea dinamică). Fiecare din aceste subprobleme se rezolvă separat, mai ușor, deoarece sunt mai mici. Rezultatul final se obține din combinația rezultatelor subproblemelor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica divide et impera simplă (decrease and conquer) ==&lt;br /&gt;
&lt;br /&gt;
În următoarele exemple de divide et impera problema se reduce la o singură problemă, mai mică. Este un caz degenerat, în care implementarea poate fi iterativă (nu necesită recursivitate).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Algoritmul lui Euclid &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Și acest bine cunoscut algoritm poate fi văzut ca un exemplu de divide et impera. El reduce problema de la &amp;lt;code&amp;gt;cmmdc(a, b)&amp;lt;/code&amp;gt; la o problemă mai mică, &amp;lt;code&amp;gt;cmmdc(b, a mod b)&amp;lt;/code&amp;gt;. Complexitate: &#039;&#039;O(log min(a, b))&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Căutare binară &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Precum știm, căutarea binară împarte vectorul original în două, apoi decide care din cele două jumătăți ar putea să conțină elementul căutat. Apoi rezolvă problema mai mică. Complexitate: &#039;&#039;O(log n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Căutare punct fix&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
Se dă un vector sortat de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente întregi distincte. Să se găsească dacă există indicele &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; astfel încât &amp;lt;code&amp;gt;v[i] == i&amp;lt;/code&amp;gt;. Găsiți un algoritm divide et impera care are complexitate &#039;&#039;O(log n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Puzzle balanță &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Avem 25 de bile care arată identic, una este ceva mai ușoară. Avem la dispoziție o balanță. Găsiți bila mai ușoară din trei cântăriri.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Selecția &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Complexitate: pe medie &#039;&#039;O(n)&#039;&#039;, în cel mai rău caz &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Este necesară la problema [https://www.nerdarena.ro/problema/antitir AntiTir].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Probleme NerdArena ===&lt;br /&gt;
&lt;br /&gt;
Următoarele probleme de la [https://www.nerdarena.ro/ NerdArena] sunt de divide et impera degenerat:&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/z ZParcurgere]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/tabela Tabela]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/antitir AntiTir] dată la Shumen 2012&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica divide et impera propriu zisă ==&lt;br /&gt;
&lt;br /&gt;
În următoarele exemple de divide et impera problema se reduce la două sau mai multe probleme mai mici. Implementarea este mult mai simplă dacă folosim recursivitate.&lt;br /&gt;
&lt;br /&gt;
=== Puzzle pavare ===&lt;br /&gt;
&lt;br /&gt;
Se dă o grilă de 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; x 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; pătrate din care lipsește un pătrat. Să se acopere cu piese formate din 3 pătrate aranjate în formă de litera L.&lt;br /&gt;
&lt;br /&gt;
[[File:pav2.jpg|frame|none|Exemplu de pavare cu L-uri]]&lt;br /&gt;
&lt;br /&gt;
Problema pare destul de grea, nu? În realitate, odată ce încercăm să folosim divide et impera ea devine destul de ușoară. Cum putem împărți problema în probleme mai mici?&lt;br /&gt;
&lt;br /&gt;
Să definim o problemă astfel:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;P(n, gol)&#039;&#039; = pavează un pătrat de latură 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; care are un pătrățel lipsă (un gol).&lt;br /&gt;
&lt;br /&gt;
O primă idee ar fi să încercăm să împărțim pătratul în patru pătrate de latură 2n-1 și să rezolvăm acele probleme. Avem, însă, o dificultate: doar unul din pătrate va avea un pătrățel lipsă. Celelalte trei vor fi pline. Ce ne facem?&lt;br /&gt;
&lt;br /&gt;
Am putea încerca să eliminăm artificial câte un pătrățel din celelalte trei pătrate. Cum putem face acest lucru? Cu condiția ca cele trei pătrățele să fie în formă de L și să le acoperim cu o piesă elementară. Ei bine avem cum să alegem pătratele în acest fel: le vom selecta pe cele de la centru! Astfel vom obține subproblemele:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;P(n, l, c)&#039;&#039; se descompune în:&lt;br /&gt;
    &#039;&#039;P(n-1, gol)&#039;&#039; (golul original)&lt;br /&gt;
    &#039;&#039;P(n-1, gol1)&#039;&#039;&lt;br /&gt;
    &#039;&#039;P(n-1, gol2)&#039;&#039;&lt;br /&gt;
    &#039;&#039;P(n-1, gol3)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Mergesort ===&lt;br /&gt;
&lt;br /&gt;
Algoritmul &#039;&#039;mergesort&#039;&#039;, sau sortare prin interclasare, funcționează astfel:&lt;br /&gt;
&lt;br /&gt;
* Divide vectorul de sortat în două părți egale (sau aproape egale)&lt;br /&gt;
* Reapelează &#039;&#039;mergesort&#039;&#039; pe acele părți&lt;br /&gt;
* La revenire avem două jumătăți sortate, deci putem sorta vectorul interclasând cei doi vectori în timp liniar&lt;br /&gt;
&lt;br /&gt;
Iată un algoritm:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mergesort( V[], i, j )&lt;br /&gt;
  mij = (i + j) / 2;&lt;br /&gt;
  daca i &amp;lt; mij&lt;br /&gt;
    mergesort( V[], i, mij )&lt;br /&gt;
  daca mij + 1 &amp;lt; j&lt;br /&gt;
    mergesort( V[], mij + 1, j )&lt;br /&gt;
  merge( V[], i, mij, j )&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;merge( V[], i, mij, j )&amp;lt;/code&amp;gt; interclasează vectorii sortați &amp;lt;code&amp;gt;V[i..mij]&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;V[mij+1..j]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are &#039;&#039;mergesort&#039;&#039;?&lt;br /&gt;
&lt;br /&gt;
Să calculăm numărul de operații, T(N), pentru a sorta un vector de N elemente:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
T(N) = 2 · T(N/2) + N&lt;br /&gt;
T(N) = 2 · (2 · T(N/4) + N/2) + N&lt;br /&gt;
T(N) = 4 · T(N/4) + 2 · N&lt;br /&gt;
T(N) = 4 · (2 · T(N/8) + N/4) + 2 · N&lt;br /&gt;
T(N) = 8 · T(N/8) + 3 · N&lt;br /&gt;
...&lt;br /&gt;
T(N) = N · T(N/N) + log N · N&lt;br /&gt;
T(N) = N · T(1) + log N · N&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Deoarece &#039;&#039;T(1)&#039;&#039; este &#039;&#039;O(1)&#039;&#039; rezultă o complexitate &#039;&#039;O(N + N log N) = O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Câtă memorie suplimentară folosește algoritmul?&lt;br /&gt;
&lt;br /&gt;
Pentru interclasarea clasică avem nevoie de un alt vector, deci &#039;&#039;O(N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În concluzie avem complexitățile:&lt;br /&gt;
&lt;br /&gt;
* timp: &#039;&#039;O(n log n)&#039;&#039;&lt;br /&gt;
* memorie suplimentară: &#039;&#039;O(n)&#039;&#039; (aceasta e problema principală a algoritmului).&lt;br /&gt;
&lt;br /&gt;
Există variante ale algoritmului care folosesc mai puțină memorie (o idee evidentă este să folosim doar jumate din memorie deoarece în timpul interclasării putem suprascrie una din jumătăți).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Aplicație mergesort - inversiunile unei permutări ===&lt;br /&gt;
&lt;br /&gt;
O problemă care apare uneori la concursuri este următoarea: se dă o permutare a numerelor de la 1 la &#039;&#039;N&#039;&#039;. Să se numere câte inversiuni are ea. O inversiune este o pereche de indici &#039;&#039;(i, j)&#039;&#039; astfel încât  &amp;lt;code&amp;gt;i &amp;lt; j&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;P[i] &amp;gt; P[j]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
În linii mari, algoritmul este următorul:&lt;br /&gt;
&lt;br /&gt;
* Sortăm prima jumate a vectorului folosind un &#039;&#039;mergesort&#039;&#039; special, care ne returnează numărul de inversiuni din vectorul de sortat.&lt;br /&gt;
* Sortăm a doua jumate a vectorului.&lt;br /&gt;
* În acest moment știm inversiunile din cele două jumătăți - le adunăm.&lt;br /&gt;
* Interclasăm cele două jumătăți. În timpul interclasării când selectăm un număr din prima jumătate știm că numerele deja selectate din jumătatea a doua sunt mai mici, deci putem aduna numărul lor la numărul total de inversiuni.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Quicksort ===&lt;br /&gt;
&lt;br /&gt;
Algoritmul quicksort, sau sortare rapidă, inventat în 1959 de informaticianul britanic Tony Hoare, funcționează astfel:&lt;br /&gt;
&lt;br /&gt;
* Alege o poziție la întâmplare din vectorul de sortat. Vom denumi valoarea de la acea poziție pivot.&lt;br /&gt;
* Rearanjează vectorul astfel încât în prima parte să avem elemente mai mici sau egale cu pivotul iar în a doua parte elemente mai mari sau egale cu pivotul. Vom denumi acest pas pivotare.&lt;br /&gt;
* Reapelează quicksort pe acele două părți.&lt;br /&gt;
&lt;br /&gt;
Din acest algoritm avem de lămurit pasul de pivotare. El este același ca la quickselect, prezentat într-o lecție anterioară. El procedează astfel:&lt;br /&gt;
&lt;br /&gt;
* Pornește cu doi indici, unul la începutul vectorului și unul la final de vector, să le spunem &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Avansează indicele b până la primul element mai mare sau egal cu pivotul.&lt;br /&gt;
* Devansează (merge înapoi) indicele e până la primul element mai mic sau egal cu pivotul.&lt;br /&gt;
* Interschimbă elementele din vector de la pozițiile &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Repetă până ce indicii se ating, &amp;lt;code&amp;gt;b &amp;gt;= e&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată o implementare posibilă. Ea este implementarea originală a lui Hoare, transformată pentru a fi structurată:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void myqsort( int begin, int end ) {&lt;br /&gt;
  int aux, b = begin, e = end,&lt;br /&gt;
    pivot = v[(begin + end) / 2]; // poate fi orice pozitie intre begin si end - 1&lt;br /&gt;
&lt;br /&gt;
  while (v[b] &amp;lt; pivot) // cauta primul element mai mare sau egal cu pivotul&lt;br /&gt;
    b++;&lt;br /&gt;
&lt;br /&gt;
  while (v[e] &amp;gt; pivot) // cauta primul element mai mic sau egal cu pivotul&lt;br /&gt;
    e--;&lt;br /&gt;
&lt;br /&gt;
  while( b &amp;lt; e ) { // daca indicii nu s-au atins&lt;br /&gt;
    aux = v[b];    // interschimba elementele la pozitiile b si e&lt;br /&gt;
    v[b] = v[e];&lt;br /&gt;
    v[e] = aux;&lt;br /&gt;
    &lt;br /&gt;
    do // cauta primul element mai mare sau egal cu pivotul&lt;br /&gt;
      b++;&lt;br /&gt;
    while (v[b] &amp;lt; pivot);&lt;br /&gt;
&lt;br /&gt;
    do // cauta primul element mai mic sau egal cu pivotul&lt;br /&gt;
      e--;&lt;br /&gt;
    while (v[e] &amp;gt; pivot);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // acum [begin..e] sunt mai mici sau egale cu pivotul&lt;br /&gt;
  if ( begin &amp;lt; e )&lt;br /&gt;
    myqsort(begin, e);&lt;br /&gt;
  // si [e+1..end] sunt mai mari sau egale cu pivotul&lt;br /&gt;
  if ( e + 1 &amp;lt; end )&lt;br /&gt;
    myqsort(e + 1, end);&lt;br /&gt;
} &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are quicksort?&lt;br /&gt;
&lt;br /&gt;
Deoarece pivotarea este liniară, calculele sunt similare cu cele de la &#039;&#039;mergesort&#039;&#039;. Avem două diferențe:&lt;br /&gt;
&lt;br /&gt;
* Procesarea liniară se face la început, înainte de recursie.&lt;br /&gt;
* Împărțirea vectorului nu este neapărat în două părți egale, ceea ce duce la o complexitate &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; pe cazul cel mai rău.&lt;br /&gt;
&lt;br /&gt;
Pentru cazul mediu, când împărțirea este în două părți aproximativ egale, rezultă aceeași complexitate &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Câtă memorie suplimentară folosește algoritmul?&lt;br /&gt;
&lt;br /&gt;
Memoria este cea necesară stivei. Ea va fi, deci, proporțională cu cel mai lung lanț de apeluri recursive imbricate. Pe cazul cel mai rău el este &#039;&#039;O(N)&#039;&#039;, iar pe cazul mediu este &#039;&#039;O(log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimizare:&#039;&#039;&#039; putem reduce cazul cel mai rău astfel:&lt;br /&gt;
&lt;br /&gt;
* Observăm că al doilea apel recursiv este recursiv la coadă.&lt;br /&gt;
* Precum știm, el nu va consuma memorie.&lt;br /&gt;
* Putem reduce memoria stivei apelând mai întâi pentru partea mai mică ce rezultă după pivotare.&lt;br /&gt;
&lt;br /&gt;
În acest fel chiar și în cazul cel mai rău memoria suplimentară necesară va fi &#039;&#039;O(log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În concluzie avem complexitățile:&lt;br /&gt;
&lt;br /&gt;
* timp: &#039;&#039;O(N log N)&#039;&#039;&lt;br /&gt;
* memorie suplimentară: &#039;&#039;O(log N)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; deși putem selecta ca pivot orice element din vector, nu putem selecta ca pivot ultimul element. Aceasta ar duce la o buclă infinită.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Pivotarea Lomuto ===&lt;br /&gt;
&lt;br /&gt;
În școli, de multe ori se predă un alt algoritm de pivotare, probabil deoarece este considerat mai simplu. Nu o vom discuta aici, deoarece este foarte ineficientă. Iată diferențele față de pivotarea Hoare:&lt;br /&gt;
&lt;br /&gt;
* Lomuto este ceva mai simplu de reținut și ceva mai scurtă.&lt;br /&gt;
* Ambii algoritmi sunt cache friendly deoarece parcurg liniar vectorul.&lt;br /&gt;
* Lomuto face de trei ori mai multe interschimburi.&lt;br /&gt;
* Lomuto face &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; operații pe un vector sortat, Hoare doar &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
* Lomuto face &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; operații pe un vector cu elemente egale, Hoare doar &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie&#039;&#039;&#039;: Pivotarea Lomuto este un algoritm didactic, bun pentru înțelegerea pivotării, dar este contraindicat într-o implementare practică.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Randomizarea pivotului &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Dacă alegem ca pivot prima poziție din vector, sau cea din mijloc, &#039;&#039;adversarul&#039;&#039; poate, studiind algoritmul, să găsească un vector special pe care implementarea noastră să intre pe cazul cel mai rău, &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Putem lupta cu aceasta, alegând pivotul pe o poziție aleatoare din intervalul &amp;lt;code&amp;gt;[begin..end-1]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Probleme quicksort &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție!&#039;&#039;&#039; Algoritmul este celebru pentru bug-urile pe care le putem introduce la implementare. Un semn mai mic transformat în mai mic sau egal poate duce la bucle infinite sau la un vector nesortat.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție&#039;&#039;&#039;: pentru olimpiadă este bine să memorați algoritmul pe din-afară.&lt;br /&gt;
&lt;br /&gt;
Ați putea spune &#039;&#039;hei, dar la olimpiadă pot folosi algorithms!&#039;&#039; Este opțiunea voastră, dar eu nu aș risca. Motivele împotriva folosirii funcției de sortare de bibliotecă:&lt;br /&gt;
&lt;br /&gt;
* Ea primește ca parametru o funcție de comparație. Apelul acelei funcții este lent, fiind vorba de accesul unui pointer pentru a obține adresa funcției, apoi de punerea pe stivă a parametrilor. Toate acestea se reduc la o comparație sau două în codul nostru.&lt;br /&gt;
* Funcția de bibliotecă este o cutie neagră. Ea depinde de compilator și de biblioteca de funcții a acestuia. Ea este scrisă de alți oameni. Aveți mai multă încredere în cod scris de alții, care variază în funcție de calculatorul pe care se face evaluarea?&lt;br /&gt;
* În mod uzual funcțiile de bibliotecă consumă mai multă memorie, iar uneori mult mai multă. De ce? Bună întrebare. Nu știu.&lt;br /&gt;
* Algoritmul de mai sus are 23 de linii de cod efectiv. El este simplu și eficient și poate fi adaptat să sorteze orice tip de date, cu mici modificări. De ce am risca să apelăm o funcție de bibliotecă despre care nu știm nimic, când în 3 minute am scris acest cod?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ziua regelui ===&lt;br /&gt;
&lt;br /&gt;
Un rege organizează o petrecere mare de ziua lui. Petrecerea începe peste 24 de ore. Regele află că una din cele 1000 de sticle de vin pe care le va servi este otrăvită. Otrava acționează ciudat: omul care bea chiar și cea mai mică cantitate din această otravă nu are nici un simptom vreme de 24 de ore, apoi moare subit. Regele are sclavi la dispoziție pe care îi poate folosi. Lui nu îi pasă câți sclavi mor, dar îi pasă câți sclavi beau vin, deoarece un sclav care a băut vin nu poate fi folosit la treabă, ci trebuie izolat spre observare. Care este numărul minim de sclavi cu care regele poate afla sticla otrăvită?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 18 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/latin Latin] dată la cercul de informatică Vianu, gimnaziu 2013&lt;br /&gt;
* [https://www.nerdarena.ro/problema/pav Pav] dată la Lot IS 2008&lt;br /&gt;
* [https://www.nerdarena.ro/problema/forma Forma] dată la olimpiada pe sector 2012, clasa a 8-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/z ZParcurgere]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/tabela Tabela]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/antitir AntiTir] dată la Shumen 2012&lt;br /&gt;
* [https://www.nerdarena.ro/problema/hanoi1 Hanoi1]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18577</id>
		<title>Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18577"/>
		<updated>2026-02-06T16:37:10Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/W59ZLyfMZlc&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica Two Pointers (doi pointeri) ==&lt;br /&gt;
&lt;br /&gt;
În informatica de concurs avem tehnici semi-banale cărora le dăm o denumire pentru a putea să le referim ușor într-o discuție. Este și cazul acestei metode. Deși relativ evidentă, ea are un nume 🙂 Pe scurt, &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; folosește doi indici într-un vector ce avansează pe rând, similar cu interclasarea, în căutarea rezultatului. Este o tehnică ce produce în general algoritmi liniari. Ea decurge în mod natural din căutarea binară, atunci când avem un algoritm relativ simplu ce folosește multiple căutări binare, ale căror rezultate sunt fie crescătoare fie descrescătoare.&lt;br /&gt;
&lt;br /&gt;
Să luăm câteva exemple.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Pereche de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt; de elemente sortate crescător, să se spună câte o perechi există de indici &amp;lt;code&amp;gt;(i, j)&amp;lt;/code&amp;gt; astfel încât &amp;lt;code&amp;gt;v[i] + v[j] == X&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Cum am putea rezolva problema? Pentru fiecare număr &amp;lt;code&amp;gt;v[i]&amp;lt;/code&amp;gt; să căutăm un număr &amp;lt;code&amp;gt;v[j]&amp;lt;/code&amp;gt; cu &amp;lt;code&amp;gt;j &amp;gt; i&amp;lt;/code&amp;gt; astfel încât &amp;lt;code&amp;gt;v[i] + v[j] == X&amp;lt;/code&amp;gt;. Dacă fixăm &amp;lt;code&amp;gt;v[i]&amp;lt;/code&amp;gt; atunci &amp;lt;code&amp;gt;v[j]&amp;lt;/code&amp;gt; poate fi determinat prin căutare binară, deoarece vectorul este ordonat crescător. Vom obține, deci, un algoritm &#039;&#039;O(N log N)&#039;&#039;, unde &#039;&#039;&#039;N&#039;&#039;&#039; este numărul de elemente din vector.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie vectorul: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
v[] = { 20, 25, 30, 35, 40, 50, 55, 65, 75 }&lt;br /&gt;
indici:  0   1   2   3   4   5   6   7   8&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
și &amp;lt;code&amp;gt;X = 75&amp;lt;/code&amp;gt;. Pentru fiecare &amp;lt;code&amp;gt;v[i]&amp;lt;/code&amp;gt; vom căuta binar ultimul număr mai mic sau egal cu &amp;lt;code&amp;gt;X - v[i]&amp;lt;/code&amp;gt;. Astfel:&lt;br /&gt;
&lt;br /&gt;
* Pentru &amp;lt;code&amp;gt;i = 0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;v[i] = 20&amp;lt;/code&amp;gt; căutăm valoarea &amp;lt;code&amp;gt;X - v[i] = 55&amp;lt;/code&amp;gt; și găsim &amp;lt;code&amp;gt;j = 6&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;v[j] = 55&amp;lt;/code&amp;gt; soluție. &lt;br /&gt;
* Pentru &amp;lt;code&amp;gt;i = 1&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;v[i] = 25&amp;lt;/code&amp;gt; căutăm valoarea &amp;lt;code&amp;gt;50&amp;lt;/code&amp;gt; și găsim &amp;lt;code&amp;gt;j = 5&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;v[j] = 50&amp;lt;/code&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;code&amp;gt;i = 2&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;v[i] = 30&amp;lt;/code&amp;gt; căutăm valoarea &amp;lt;code&amp;gt;45&amp;lt;/code&amp;gt; și găsim &amp;lt;code&amp;gt;j = 4&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;v[j] = 40&amp;lt;/code&amp;gt; (ultima mai mică sau egală cu 45) nu este soluție.&lt;br /&gt;
* Pentru &amp;lt;code&amp;gt;i = 3&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;v[i] = 35&amp;lt;/code&amp;gt; căutăm valoarea &amp;lt;code&amp;gt;40&amp;lt;/code&amp;gt; și găsim &amp;lt;code&amp;gt;j = 4&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;v[j] = 40&amp;lt;/code&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;code&amp;gt;i = 4&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;v[i] = 40&amp;lt;/code&amp;gt; ar trebui să căutăm valoarea &amp;lt;code&amp;gt;35&amp;lt;/code&amp;gt; care este mai mică decât &amp;lt;code&amp;gt;v[i]&amp;lt;/code&amp;gt; așa încât ne oprim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce observăm? Că valorile lui &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; sunt descrescătoare. Este destul de clar că așa și trebuie să fie, nu?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație importantă&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tehnica Two Pointers&#039;&#039;&#039;: atunci când o succesiune de &#039;&#039;&#039;căutări binare&#039;&#039;&#039; într-un vector au ca rezultat o &#039;&#039;&#039;secvență monotonă&#039;&#039;&#039; de numere (crescătoare sau descrescătoare), căutările binare pot fi înlocuite cu &#039;&#039;&#039;căutări liniare&#039;&#039;&#039;. Această înlocuire și noua metodă rezultată poartă denumirea de &#039;&#039;&#039;Two Pointers&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are noul algoritm?&lt;br /&gt;
&lt;br /&gt;
La prima vedere , pentru fiecare element &amp;lt;code&amp;gt;v[i]&amp;lt;/code&amp;gt; vom căuta liniar perechea lui, ceea ce ar rezulta într-un algoritm &#039;&#039;O(N)&#039;&#039;. În realitate, indicele &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; se va muta mereu către stânga, drept care el nu va putea parcurge mai mult de &#039;&#039;O(N)&#039;&#039; elemente în toate căutările liniare. Deci, folosind analiza amortizată, deși oricare din căutările liniare poate fi &#039;&#039;O(N)&#039;&#039;, suma tuturor acestor căutări nu poate depăși &#039;&#039;O(N)&#039;&#039;. Drept care metoda Two Pointers duce la un algoritm &#039;&#039;O(N)&#039;&#039;, superior celui cu căutare binară, care era &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Subsecvență de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt; de elemente nenule, să se spună câte o perechi există de indici &amp;lt;code&amp;gt;(i, j)&amp;lt;/code&amp;gt; astfel încât &amp;lt;code&amp;gt;v[i] + v[i+1] + v[i+2] + ... + v[j] == X&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Și aici avem un algoritm relativ simplu: calculăm vectorul &amp;lt;code&amp;gt;s[]&amp;lt;/code&amp;gt; de sume parțiale și pentru fiecare element pe poziția &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; în vector vom căuta binar în vectorul &amp;lt;code&amp;gt;s[]&amp;lt;/code&amp;gt; valoarea &amp;lt;code&amp;gt;s[i-1] + X&amp;lt;/code&amp;gt;. Dacă găsim această valoare la poziția &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; avem că &amp;lt;code&amp;gt;s[j] - s[i-1] == X&amp;lt;/code&amp;gt; și deci suma elementelor de la &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; la &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; în vectorul &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt; este &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt;, deci avem o soluție.&lt;br /&gt;
&lt;br /&gt;
Complexitatea acestui algoritm va fi, din nou, &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce observăm, însă? Că rezultatele căutărilor binare (indicii &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt;) vor fi în ordine crescătoare, deoarece sumele parțiale sunt strict crescătoare. Putem, deci, folosi &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; pentru a căuta indicii &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; prin căutare liniară: pentru fiecare avans al lui &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; vom avansa indicele &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; până ce suma devine mai mare sau egală cu cea dorită, moment la care testăm egalitatea.&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(N)&#039;&#039;. Mai mult, deoarece ne interesează doar suma subsecvenței de la &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; la &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; putem renunța la vectorul de sume parțiale și lucra direct pe vectorul original &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt;, menținând incremental suma subsecvenței studiate.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Problema NrTri ===&lt;br /&gt;
&lt;br /&gt;
Am discutat într-o lecție trecută problema [https://www.nerdarena.ro/problema/nrtri NrTri]. Ea cere să găsiți într-un vector numărul de tripleți ce pot forma laturile unui triunghi. Aici avem o soluție cu căutare binară: după ce sortăm elementele, pentru fiecare pereche de indici &amp;lt;code&amp;gt;(i, j)&amp;lt;/code&amp;gt; vom căuta binar cel mai mare element &amp;lt;code&amp;gt;k &amp;gt; j&amp;lt;/code&amp;gt; cu proprietatea că &amp;lt;code&amp;gt;v[k]&amp;lt;/code&amp;gt;. Toate elementele între &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; (inclusiv &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;) formează un triplet valabil cu perechea &amp;lt;code&amp;gt;(i, j)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Această soluție are complexitatea &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; log N)&#039;&#039;. Ca și în exemplele anterioare, observăm că atunci când &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; avansează căutarea binară va returna valori &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; crescătoare. Și aici putem aplica tehnica &#039;&#039;Two Pointers&#039;&#039;, avansând liniar indicele &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Rezultă un algoritm O(N2). Observați că tehnica aceasta reduce complexitatea cu acel factor log N. În practică nu este clar că vom putea întotdeauna diferenția între căutarea binară și Two Pointers. Cu toate acestea metoda merită, deoarece codul rezultat este mai simplu și constanta de implementare scade considerabil.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tipul de date struct ==&lt;br /&gt;
&lt;br /&gt;
Până acum am învățat tipuri de date simple (&amp;lt;code&amp;gt;int&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;char&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;long long&amp;lt;/code&amp;gt;, etc) și tipuri de date compuse (ce grupează mai multe tipuri de date simple) anume tablouri, fie ele unidimensionale (vectori), bidimensionale (matrice), sau chiar de dimensiuni mai mari. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; };&lt;br /&gt;
...&lt;br /&gt;
struct nrmare n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
sau, mai scurt (putem declara variabile la definirea tipului &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Elementele declarate în interiorul unui &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; se numesc membri. Putem accesa membrii unei variabile tip &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; cu operatorul punct. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
t += n1.cf[i] + n2.cf[i];&lt;br /&gt;
n3.cf[i] = t % 10;&lt;br /&gt;
t /= 10;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Tipul &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; reprezintă un alt mod de a grupa date simple într-o &#039;&#039;structură&#039;&#039;. Aceste date vor fi reprezentate în memorie la rând, una după alta.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Diferențe&#039;&#039;&#039; între tipul &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; și tipul tablou:&lt;br /&gt;
&lt;br /&gt;
* Tipul struct poate grupa date de tipuri diferite, tipul &amp;lt;code&amp;gt;tablou&amp;lt;/code&amp;gt; nu.&lt;br /&gt;
* Tipul struct accesează elementele sale pe bază de denumire, tipul tablou accesează elementele numeric, pe bază de indice (poziție).&lt;br /&gt;
* În consecință tipul &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; grupează un număr relativ mic de date.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Avantaje&#039;&#039;&#039; ale tipului &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Gruparea datelor duce la o ordine a codului.&lt;br /&gt;
* Codul devine mai clar și mai ușor de citit, deci o depanare mai ușoară.&lt;br /&gt;
* Folosit corect tipul struct poate duce la un acces mai localizat al memorie (&#039;&#039;cache friendly&#039;&#039;) și, deci, la o scăderea a timpului de execuție.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; probleme cu tipul &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Orice variabilă de tip &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; va fi completată până la &amp;lt;code&amp;gt;int&amp;lt;/code&amp;gt;. Actual, acest tip este pe patru octeți, ceea ce înseamnă că un struct va ocupa un număr de octeți divizibil cu patru. Dacă vom declara un struct cu un &amp;lt;code&amp;gt;int&amp;lt;/code&amp;gt; și un &amp;lt;code&amp;gt;char&amp;lt;/code&amp;gt; el va ocupa 8 octeți și nu 5!&lt;br /&gt;
&lt;br /&gt;
* Un vector de structuri poate fi înlocuit întotdeauna cu mai mulți vectori, câte unul pentru fiecare membru. Decizia de a folosi struct trebuie luată în funcție de modul de accesare a membrilor în algoritmul nostru. O decizie proastă poate duce la deteriorarea timpului de execuție din motive de cache.&lt;br /&gt;
&lt;br /&gt;
* Combinația între cele două de mai sus poate fi destul de rea: un struct care ocupă mai mult, chiar dacă nu duce la o depășire de memorie, va crește necesarul de memorie ceea ce duce la creșterea timpului de execuție din motive de &#039;&#039;cache&#039;&#039;. Aceasta, combinat cu un acces prost la memorie, poate duce la o creștere semnificativă a timpului de execuție - poate chiar dublarea lui!&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie&#039;&#039;&#039;: folosiți tipul &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; numai dacă știți exact ce faceți. Recomandarea mea este ca deocamdată să nu îl folosiți.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: problema bile1 ===&lt;br /&gt;
&lt;br /&gt;
Implementarea listelor se face în general bine folosind tipul &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;. În mod natural o celulă a listei va conține o informație și o legătură la următoarea celulă (next). În particular am învățat acea structură de date, vectorul de liste, pe care îl foloseam pentru a evita sortarea, memorând pentru fiecare linie a unei matrice acele coloane care ne interesează, unde apar elemente interesante pe acea linie. Să luăm un caz concret.&lt;br /&gt;
&lt;br /&gt;
În problema [https://www.nerdarena.ro/problema/bile1 Bile1] se cere să simulăm căderea unor bile printr-o matrice cu obstacole. Am rezolvat-o memorând obstacolele ca liste de coloane &#039;&#039;agățate&#039;&#039; pe liniile unde apar obstacole. Pentru aceasta am folosit doi vectori. Iată soluția de atunci:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;CPP&amp;quot; line&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                   // capetele de lista&lt;br /&gt;
short col[NOBST + 1], next[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                        // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    col[i] = c;         // asezam coloana c in lista liniei l&lt;br /&gt;
    next[i] = lin[l];   // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = col[pc] - 1; // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = next[pc];   // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Este clar că atunci când accesăm o celulă a listei vom avea nevoie atât de numărul de coloană cât și de next, pentru a avansa la elementul următor. Deci poate ar fi o idee bună să le grupăm, pentru &#039;&#039;cache&#039;&#039;. Partea bună este că atât coloana cât și următorul element sunt mici, deci putem folosi tipul &amp;lt;code&amp;gt;short&amp;lt;/code&amp;gt;, ceea ce înseamnă că o celulă va ocupa două elemente &amp;lt;code&amp;gt;short&amp;lt;/code&amp;gt;, adică patru octeți, fiind aliniată la întreg.&lt;br /&gt;
&lt;br /&gt;
Iată programul modificat să folosească tipul &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                                    // capetele de lista&lt;br /&gt;
struct cell { short col; short next; } obst[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                            // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    // asezam coloana c in lista liniei l&lt;br /&gt;
    // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    obst[i] = (struct cell){ .col = c, .next = lin[l] }; // cream noua celula&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = obst[pc].col - 1;                   // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = obst[pc].next; // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați cum putem atribui o variabilă de tip &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;, folosind denumirile membrilor săi: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
obst[i] = (struct cell){ .col = c, .next = lin[l] };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: numere mari ===&lt;br /&gt;
&lt;br /&gt;
Numerele mari se pot reprezenta în mod natural ca un struct ce conține numărul de cifre și vectorul de cifre, precum am văzut mai sus. Avantajul constă în gruparea datelor și în claritatea codului ce menține numarul de cifre corect - nu mai avem nevoie să returnăm în funcția de înmulțire numărul de cifre al rezultatului, ci îl setăm chiar în numărul mare trimis ca parametru.&lt;br /&gt;
&lt;br /&gt;
Atunci când trimitem un &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; mare ca parametru (care ocupă mai mult de un întreg) este bine să-l trimitem ca pointer, deoarece evităm copierea. În acest caz accesul la &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt; se face un pic anevoios. &lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de funcție de adunare număr mic la număr mare:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += (*n).cf[i];&lt;br /&gt;
    (*n).cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; (*n).ncf )&lt;br /&gt;
    (*n).ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați că avem nevoie de două paranteze, un asterisc și un punct pentru a apela un membru al structurii trimise ca pointer. De aceea limbajul C ne oferă o alternativă mai simplă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
    // forme echivalente:&lt;br /&gt;
    (*n).cf[i];&lt;br /&gt;
&lt;br /&gt;
    // este totuna cu&lt;br /&gt;
    n-&amp;gt;cf[i];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Precum vedeți săgețica este echivalentul punctului atunci când variabila este de tip pointer la &amp;lt;code&amp;gt;struct&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de implementare de numere mari:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXNCF 1000&lt;br /&gt;
&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mic a din numarul mare n&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (a &amp;lt;= n)&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void scadeNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  a = -a;&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;lt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    if ( n-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n-&amp;gt;cf[i] += 10;&lt;br /&gt;
      a--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n-&amp;gt;cf[n-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mare n2 la numarul mare n1&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void adunaNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, max, t;&lt;br /&gt;
&lt;br /&gt;
  max = n1-&amp;gt;ncf &amp;lt; n2-&amp;gt;ncf ? n2-&amp;gt;ncf : n1-&amp;gt;ncf;&lt;br /&gt;
  t = 0;&lt;br /&gt;
  for ( i = 0; i &amp;lt; max; i++ ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] + n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
  }&lt;br /&gt;
  if ( t &amp;gt; 0 ) {&lt;br /&gt;
    n1-&amp;gt;cf[i] = t;&lt;br /&gt;
    max++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  n1-&amp;gt;ncf = max;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mare n2 din numarul mare n1&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (n2 &amp;lt;= n1)&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void scadeNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n2-&amp;gt;ncf || t &amp;lt; 0 ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] - n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    if ( n1-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n1-&amp;gt;cf[i] += 10;&lt;br /&gt;
      t--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n1-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n1-&amp;gt;cf[n1-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n1-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// inmulteste numarul mic a cu numarul mare n cu rezultatul in n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void inmultesteNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
  long long t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n-&amp;gt;ncf || t &amp;gt; 0 ) {&lt;br /&gt;
    t = t + a * (long long)n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// tipareste un numar mare n&lt;br /&gt;
void printN( struct nrmare *n ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( n-&amp;gt;ncf == 0 )&lt;br /&gt;
    fputc( &#039;0&#039;, stdout );&lt;br /&gt;
  else&lt;br /&gt;
    for ( i = n-&amp;gt;ncf - 1; i &amp;gt;= 0; i-- )&lt;br /&gt;
      fputc( &#039;0&#039; + n-&amp;gt;cf[i], stdout );&lt;br /&gt;
  fputc( &#039;\n&#039;, stdout );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  // n1, n2, n3 declarate global, initial zero&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 75464: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;75464 - 75464: &amp;quot; );&lt;br /&gt;
  scadeNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 834205: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 834205 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  adunaNn( &amp;amp;n2, 1024586 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586: &amp;quot; );&lt;br /&gt;
  adunaNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586 - 1024586: &amp;quot; );&lt;br /&gt;
  scadeNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 * 2043984094: &amp;quot; );&lt;br /&gt;
  inmultesteNn( &amp;amp;n1, 2043984094 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Metoda Greedy ==&lt;br /&gt;
&lt;br /&gt;
Uneori trebuie să rezolvăm probleme ce necesită să găsim o soluție minimă sau maximă. Una din cele mai simple și evidente încercări de rezolvare este ca, la fiecare pas, să luăm decizia optimă pe moment - decizie &#039;&#039;lacomă&#039;&#039; - cel mai bun lucru la momentul actual.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dat un șir de numere pozitive și negative să se calculeze suma maximă ce se poate forma cu exact &#039;&#039;K&#039;&#039; numere.&lt;br /&gt;
&lt;br /&gt;
Soluție greedy: la fiecare pas vom selecta maximul dintre numerele rămase. Vom porni cu maximul. Apoi cu al doilea maxim și așa mai departe. La fiecare pas &#039;&#039;ne lăcomim&#039;&#039; la cel mai mare număr posibil.&lt;br /&gt;
&lt;br /&gt;
Este, sper, destul de clar că în acest caz obținem soluția optimă. Însă nu întotdeauna este așa, precum vom vedea.&lt;br /&gt;
&lt;br /&gt;
Să vedem câteva exemple de algoritmi greedy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Plată cu număr minim de bancnote puteri ale lui 2 ===&lt;br /&gt;
&lt;br /&gt;
Dispunem de bancnote puteri ale lui 2. Pentru fiecare putere a lui 2 vom avea un număr de bancnote disponibile, posibil niciuna. Dat &#039;&#039;&#039;X&#039;&#039;&#039;, dorim să achităm suma &#039;&#039;&#039;X&#039;&#039;&#039; cu un număr minim de bancnote posibile. Cum procedăm?&lt;br /&gt;
&lt;br /&gt;
Vom proceda greedy: selectăm bancnotele de valoare maximă ce încap în &#039;&#039;&#039;X&#039;&#039;&#039;. Fie epuizăm numărul de bancnote de acea putere și trecem mai departe, fie ne rămân bancnote dar suma de plată devine mai mică decât 2 la acea putere. Procedăm în continuare la fel.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dispunem de următoarele bancnote: 3 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;, 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;, 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, 5 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;, 6 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; și ne dorim să achităm suma 53. Procedăm astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  Pas 1: selectăm 1 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;. Suma rămasă: 21 = 53 - 32&lt;br /&gt;
  Pas 2: selectăm 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;. Suma rămasă: 13 = 21 - 8&lt;br /&gt;
  Pas 3: selectăm 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;. Suma rămasă: 5 = 13 - 8&lt;br /&gt;
  Pas 4: selectăm 2 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;. Suma rămasă: 1 = 5 - 4&lt;br /&gt;
  Pas 5: selectăm 1 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt;. Suma rămasă: 0 = 1 - 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Astfel numărul de bancnote este 7, iar numărul este plătit astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
53 = 32 + 8 + 4 + 4 + 2 + 2 + 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sortarea prin selecție ===&lt;br /&gt;
&lt;br /&gt;
Ne putem gândi la sortarea prin selecție ca având un pas de bază greedy: la fiecare trecere prin vector selectăm în mod greedy maximul și apoi îl trecem la coadă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul minim de intervale care acoperă un interval ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o mulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; ce acoperă un alt interval mai mare să se găsească o submulţime minimă a acestei mulţimi care conţine intervale ce acoperă același interval original.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Intervalul acoperit este &amp;lt;code&amp;gt;[0, 18]&amp;lt;/code&amp;gt;. Ne dorim să păstrăm cât mai puține intervale care să acopere același interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idee de soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
O idee naturală este să sortăm intervalele după punctul de început, iar apoi să le luăm în ordine, unul câte unul. Având un interval curent selectat, am putea să selectăm intervalul care începe cât mai târziu dar al cărui interval este în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
Pe exemplul de mai sus observăm că nu funcționează, deoarece am selecta intervalele 4, 1 și apoi nu am mai avea ce să selectăm în continuare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom parcurge intervalele în ordinea capătului de început, ca mai devreme. Dar vom selecta următorul interval pe acela care se &#039;&#039;&#039;închide ultimul&#039;&#039;&#039; dintre cele care au capătul stânga în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
În cazul nostru vom selecta intervalele 4, 5 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul maxim de intervale disjuncte ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o submulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; să se găsească o submulţime maximală a acestei mulţimi care conţine doar intervale disjuncte (neintersectante).&lt;br /&gt;
&lt;br /&gt;
Această problemă este totuna cu numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele (de ce?).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idei de soluţie &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Această problemă apare destul de des la olimpiade, deghizată într-o formă sau alta. Deşi pare o problemă grea, în realitate ea se rezolvă uşor. O primă idee ar fi să încercăm să ordonăm intervalele după punctul de început, apoi să le luăm în ordine, unul câte unul. Pentru fiecare interval ne întrebăm dacă îl intersectează pe cel anterior. Dacă da, îl aruncăm şi mergem mai departe. Dacă nu, îl adăugăm la submulţime şi îl reţinem ca interval ultim. Această soluţie nu funcţionează şi e destul de evident de ce: am putea avea un prim interval foarte lung, care acoperă toate celelalte intervale.&lt;br /&gt;
&lt;br /&gt;
Cu toate acestea rezolvarea nu e total greşită. În fapt, ea aproape funcţionează! Are nevoie doar de o modificare ce se impune, analizînd contraexemplul anterior: de ce nu este bun acel prim interval foarte lung? Deoarece el se termină după celelalte. Şi atunci ne putem întreba, în mod natural, ce s-ar întâmpla dacă am alege intervalul care se închide primul, nu care începe primul? Desigur că acest algoritm funcţionează. Vă las vouă să vă luptaţi cu demonstraţia, care decurge cam aşa: să presupunem că soluţia generată de acest algoritm nu e optimă. Înseamnă că există o altă soluţie cu mai multe intervale. Acea soluţie trebuie să difere undeva faţă de cea găsită de noi. Mergeţi pe această cale şi trebuie să ajungeţi la o contradicţie cum că soluţia optimă nu poate avea mai multe segmente.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Atunci vom selecta, în ordine, intervalele 3 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Soluţie ====&lt;br /&gt;
&lt;br /&gt;
Care este algoritmul, în mare?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
# Iniţial mulţimea M este vidă.&lt;br /&gt;
# Ordonează crescător intervalele după punctul de închidere.&lt;br /&gt;
# Consideră ultima închidere (care nu există încă) cu valoarea minus infinit.&lt;br /&gt;
# Pentru fiecare interval, în ordine:&lt;br /&gt;
## Dacă el conţine ultima închidere&lt;br /&gt;
### Ignoră-l.&lt;br /&gt;
## Altfel&lt;br /&gt;
### Selectează intervalul curent şi adaugă-l la mulţimea M.&lt;br /&gt;
### Setează ultima închidere pe închiderea intervalului selectat&lt;br /&gt;
# Afişează mulţimea M.&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ce se întâmplă dacă avem mai multe intervale care se termină în acelaşi punct? Atunci putem reţine intervalul cel mai mic. De ce? Deoarece este cel mai convenabil. Să presupunem că soluţia maximală conţinea un alt interval care se termina în acel punct. Atunci putem înlocui acel interval cu intervalul cel mai mic, obţinînd o altă soluţie maximală.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Considerente de implementare ====&lt;br /&gt;
&lt;br /&gt;
Implementarea directă: memorăm intervalele ca perechi, prin capetele lor. Sortăm perechile după capătul din dreapta. Apoi facem o parcurgere a perechilor. Această implementare necesită &#039;&#039;O(N log N)&#039;&#039; timp și &#039;&#039;O(N)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
Implementare relaxată: atunci când coordonatele sunt suficient de mici astfel încât să putem folosi un vector care să memoreze un întreg pentru fiecare coordonată, putem încerca o abordare mai simplă: nu memorăm intervalele. Ci, pentru fiecare interval &amp;lt;code&amp;gt;[ai, bi]&amp;lt;/code&amp;gt;, vom seta &amp;lt;code&amp;gt;început[bi] = ai&amp;lt;/code&amp;gt;. Cu alte cuvinte vom memora capetele stânga la poziția capetelor dreapta. Apoi parcurgem vectorul în ordine, acoperind segmentul de coordonate care acoperă toate intervalele, testând dacă elementul de la poziția curentă în vector este mai mic decât ultima închidere memorată. Dacă nu, atunci selectăm intervalul și mutăm ultima închidere la poziția curentă în vector. Trebuie să avem grijă la următoarele:&lt;br /&gt;
&lt;br /&gt;
* Să iniţializăm vectorul de coordonate cu o valoare distinctă de capete de interval. Matematic am putea să le iniţializăm cu minus infinit, astfel încât să nu fie selectate niciodată.&lt;br /&gt;
* Atunci când &#039;&#039;aşezăm&#039;&#039; intervalele pe vector să avem grijă ca dacă găsim deja o valoare în vector să o înlocuim numai dacă cea curentă este mai mare (intervalul este mai mic).&lt;br /&gt;
&lt;br /&gt;
Această soluţie necesită &#039;&#039;O(N + CMAX)&#039;&#039; timp şi memorie, &#039;&#039;&#039;CMAX&#039;&#039;&#039; fiind coordonata maximă. De aici rezultă când o putem folosi: atunci când &#039;&#039;&#039;CMAX&#039;&#039;&#039; este suficient de mic.&lt;br /&gt;
&lt;br /&gt;
==== Generalizare ====&lt;br /&gt;
&lt;br /&gt;
Putem generaliza această problemă: să se calculeze mulţimea maximală de dreptunghiuri neintersectante. Sau de paralelipipede. Sau de paralelipipede n-dimensionale. Cum se rezolvă aceste probleme? Din nefericire aceste probleme fac parte dintr-o clasă de probleme numite &#039;&#039;NP-hard&#039;&#039;, pentru care nu se cunoaşte o soluţie bună.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu când greedy nu funcționează: prolema rucsacului ===&lt;br /&gt;
&lt;br /&gt;
Problema rucsacului cere să &#039;&#039;umplem&#039;&#039; cât mai bine un rucsac care poate duce o greutate maximă, dispunând de obiecte de diverse greutăți.&lt;br /&gt;
&lt;br /&gt;
Exemplu: Vreau să umplu cât mai bine un rucsac de 100 și am greutăți 50, 30, 25 și 25.&lt;br /&gt;
&lt;br /&gt;
Prin metoda greedy vom alege greutățile cele mai mari, în ordine: 50 și 30, după care nu mai putem adăuga nimic. Astfel vom obține greutatea totală 80. Dacă am alege însă greutățile 50, 25 și 25 am putea umple complet rucsacul.&lt;br /&gt;
&lt;br /&gt;
De remarcat totuși că chiar și atunci când metoda greedy nu calculează optimul, un algoritm greedy bine gândit va da o soluție bună, aproape de optim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 16 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/politic Politic] dată la ONI 2007 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumax SumaX] dată la Shumen Juniori 2015&lt;br /&gt;
* [https://www.nerdarena.ro/problema/acoperire Acoperire]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/char Char] dată la ONI 2010 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Pentru doritorii să încerce și soluția problemei numărul minim de numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele puteți rezolva problema [https://www.nerdarena.ro/problema/baloane Baloane].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/startrek Star Trek] dată la OJI 2016 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy Accesează rezolvarea temei 16]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_15:_Citire_/_scriere_rapid%C4%83&amp;diff=18576</id>
		<title>Clasa a 7-a Lecția 15: Citire / scriere rapidă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_15:_Citire_/_scriere_rapid%C4%83&amp;diff=18576"/>
		<updated>2026-02-06T16:31:53Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video curs ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/edb5JQiZxoY&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Citire/scriere rapidă cu fgetc() / fputc() ==&lt;br /&gt;
&lt;br /&gt;
Știm că atunci când avem de citit numere la intrare &amp;lt;code&amp;gt;fscanf()&amp;lt;/code&amp;gt; este foarte lentă. Știm că putem citi mai rapid folosind &amp;lt;code&amp;gt;fgetc()&amp;lt;/code&amp;gt; și calculând numerele. Am prezentat, în trecut, o funcție de citire a întregilor bazată pe &amp;lt;code&amp;gt;fgetc()&amp;lt;/code&amp;gt;. Am spus că, printre elevi, circulă denumirea de parsing. Vom folosi și noi această denumire, deși ea nu este tocmai corectă, parsingul fiind un capitol al informaticii care se ocupă cu mult mai mult decât citirea întregilor.&lt;br /&gt;
&lt;br /&gt;
Nu am prezentat niciodată o funcție de scriere eficientă a întregilor, care să nu folosească &amp;lt;code&amp;gt;fprintf()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Citire/scriere rapidă cu fgetc() / fputc() ==&lt;br /&gt;
&lt;br /&gt;
Iată, mai jos, atât funcția de citire cu care suntem obișnuiți, cât și o funcție de scriere, ambele bazate pe &amp;lt;code&amp;gt;fgetc()&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;fputc()&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// citire intreg cu semn&lt;br /&gt;
int readInt() {&lt;br /&gt;
  int ch, res = 0, semn = 1;&lt;br /&gt;
&lt;br /&gt;
  while ( isspace( ch = fgetc( fin ) ) );&lt;br /&gt;
  if ( ch == &#039;-&#039; ) {&lt;br /&gt;
    semn = -1;&lt;br /&gt;
    ch = fgetc( fin );&lt;br /&gt;
  }&lt;br /&gt;
  do&lt;br /&gt;
    res = 10 * res + ch - &#039;0&#039;;&lt;br /&gt;
  while ( isdigit( ch = fgetc( fin ) ) );&lt;br /&gt;
&lt;br /&gt;
  return semn * res;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scriere intreg cu semn&lt;br /&gt;
void writeInt( int x ) {&lt;br /&gt;
  int i;&lt;br /&gt;
  char cifre[10];&lt;br /&gt;
&lt;br /&gt;
  if ( x &amp;lt; 0 ) {&lt;br /&gt;
    fputc( &#039;-&#039;, fout );&lt;br /&gt;
    x = -x;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  do {&lt;br /&gt;
    cifre[i++] = &#039;0&#039; + x % 10;&lt;br /&gt;
    x /= 10;&lt;br /&gt;
  } while ( x &amp;gt; 0 );&lt;br /&gt;
&lt;br /&gt;
  while ( i &amp;gt; 0 )&lt;br /&gt;
    fputc( cifre[--i], fout );&lt;br /&gt;
&lt;br /&gt;
  fputc( &#039;\n&#039;, fout ); // separatorul, poate sa difere (&#039; &#039;?)&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Cum procedează funcția de scriere rapidă? Ea folosește împărțiri la constanta 10, împărțiri mult studiate în literatura de specialitate. Ele nu sunt traduse în cod mașină ca împărțiri. Compilatorul folosește matematică în clasă de resturi &#039;&#039;modulo&#039;&#039;, folosind înmulțiri cu inversul modular și operații shift. Împărțirea la 10 va fi mai rapidă decât o împărțire oarecare, dar mai lentă decât o adunare.&lt;br /&gt;
&lt;br /&gt;
Se poate mai bine?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Citire/scriere rapidă cu fread() / fwrite() ==&lt;br /&gt;
&lt;br /&gt;
Funcțiile &amp;lt;code&amp;gt;fread()&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;fwrite()&amp;lt;/code&amp;gt; se află la baza funcțiilor &amp;lt;code&amp;gt;fscanf()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;fprintf()&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;fgetc()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;fputc()&amp;lt;/code&amp;gt;. Ele sunt cele mai de jos funcții de citire / scriere care folosesc bufferele sistemului de operare.&lt;br /&gt;
&lt;br /&gt;
Cum le vom folosi? Citirea de pe disc, sau alt mediu extern, este foarte lentă. Ceea ce ia cel mai mult este citirea primului octet. Octeții care urmează se citesc mult mai rapid. De aceea este foarte ineficient să citim câte un octet o dată folosind &amp;lt;code&amp;gt;fread()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Vom citi, în schimb, câte un bloc de date mai mare în memorie, din care vom returna câte un octet (caractere) la cerere. Acest bloc se numește &#039;&#039;buffer&#039;&#039; și este folosit și în funcțiile sistem, &amp;lt;code&amp;gt;fgetc()&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;fscanf()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată cum arată un parsing scris cu &amp;lt;code&amp;gt;fread()&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;fwrite()&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;ctype.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define BUFSIZE (128 * 1024)&lt;br /&gt;
&lt;br /&gt;
FILE *fin, *fout;&lt;br /&gt;
int rpos = BUFSIZE - 1; char rbuf[BUFSIZE];&lt;br /&gt;
int wpos = 0; char wbuf[BUFSIZE];&lt;br /&gt;
&lt;br /&gt;
int v[2000000];&lt;br /&gt;
&lt;br /&gt;
// citire caracter&lt;br /&gt;
static inline char readChar() {&lt;br /&gt;
  if ( !(rpos = (rpos + 1) &amp;amp; (BUFSIZE - 1)) )&lt;br /&gt;
    fread( rbuf, 1, BUFSIZE, fin );&lt;br /&gt;
  return rbuf[rpos];&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
// citire intreg cu semn&lt;br /&gt;
int readInt() { // citeste un intreg&lt;br /&gt;
  int ch, res = 0, semn = 1;&lt;br /&gt;
&lt;br /&gt;
  while ( isspace( ch = readChar() ) );&lt;br /&gt;
  if ( ch == &#039;-&#039; ) {&lt;br /&gt;
    semn = -1;&lt;br /&gt;
    ch = readChar();&lt;br /&gt;
  }&lt;br /&gt;
  do&lt;br /&gt;
    res = 10 * res + ch - &#039;0&#039;;&lt;br /&gt;
  while ( isdigit( ch = readChar() ) );&lt;br /&gt;
&lt;br /&gt;
  return semn * res;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scriere caracter&lt;br /&gt;
static inline void writeChar( char ch ) {&lt;br /&gt;
  wbuf[wpos] = ch;&lt;br /&gt;
  if ( !(wpos = (wpos + 1) &amp;amp; (BUFSIZE - 1)) )&lt;br /&gt;
    fwrite( wbuf, 1, BUFSIZE, fout );&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
// scriere intreg cu semn&lt;br /&gt;
void writeInt( int x ) {&lt;br /&gt;
  int i;&lt;br /&gt;
  char cifre[10];&lt;br /&gt;
&lt;br /&gt;
  if ( x &amp;lt; 0 ) {&lt;br /&gt;
    writeChar( &#039;-&#039; );&lt;br /&gt;
    x = -x;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  do {&lt;br /&gt;
    cifre[i++] = &#039;0&#039; + x % 10;&lt;br /&gt;
    x /= 10;&lt;br /&gt;
  } while ( x &amp;gt; 0 );&lt;br /&gt;
&lt;br /&gt;
  while ( i &amp;gt; 0 )&lt;br /&gt;
    writeChar( cifre[--i] );&lt;br /&gt;
&lt;br /&gt;
  writeChar( &#039;\n&#039; ); // separatorul, poate sa difere (&#039; &#039;?)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scrie caracterele ramase in buffer&lt;br /&gt;
static inline void flushBuf() {&lt;br /&gt;
  fwrite( wbuf, 1, wpos, fout );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Cum folosim aceste funcții? Astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  fin = fopen( &amp;quot;input.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  n = readInt();&lt;br /&gt;
  // ... alte citiri&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;input.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  writeInt( n );&lt;br /&gt;
  // ... alte scrieri&lt;br /&gt;
  flushBuf(); // ne asiguram ca bufferul este complet scris&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Comparații moduri de citire / scriere ==&lt;br /&gt;
&lt;br /&gt;
Ce funcții ar trebui să folosim atunci când facem parsing? Care este diferența de viteză între ele?&lt;br /&gt;
&lt;br /&gt;
Comparația între diverse funcții nu este ușoară, deoarece sistemul de operare va încărca în memorie fișierele des folosite, așa numitul &#039;&#039;caching&#039;&#039;. Aceasta face ca citirile să se efectueze din memorie, și nu de pe disc, ceea ce le va face să apară în mod artificial mai rapide decât sunt în realitate. Pentru o testare reală ar trebui să ne asigurăm că fișierele nu se află în cache.&lt;br /&gt;
&lt;br /&gt;
Iată o comparație între diversele tipuri de citire/scriere. Ea măsoară două milioane de citiri și două milioane de scrieri de întregi folosind cele trei metode: &amp;lt;code&amp;gt;fscanf()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;fprintf()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;fgetc()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;fputc()&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;fread()&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;fwrite()&amp;lt;/code&amp;gt;. Ca metodologie folosită, am executat fiecare metodă de zece ori și am luat cel mai mic timp obținut. Aceasta înseamnă, probabil, că fișierul de intrare se află în cache. Pe de altă parte același lucru este foarte probabil să se întâmple și pe evaluatorul de la olimpiadă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Timpi Pentium 4 2.40GHz / HDD (NerdArena) !! 2 milioane citiri !! 2 milioane scrieri&lt;br /&gt;
|-&lt;br /&gt;
| fscanf() / fprintf() || 994ms || 895ms&lt;br /&gt;
|-&lt;br /&gt;
| fgetc() / fputc() || 348ms (2.9x) || 598ms (1.5x)&lt;br /&gt;
|-&lt;br /&gt;
| fread() / fwrite() || 166ms (6x) || 419ms (2.1x)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Timpi core i7-7700HQ 2.8GHz / SSD PCIe (laptop tipic) !! 2 milioane citiri !! 2 milioane scrieri&lt;br /&gt;
|-&lt;br /&gt;
| fscanf() / fprintf() || 234ms || 135ms&lt;br /&gt;
|-&lt;br /&gt;
| fgetc() / fputc() || 72ms (3.25x) || 95ms (1.4x)&lt;br /&gt;
|-&lt;br /&gt;
| fread() / fwrite() || 45ms (5.2x) || 62ms (2.2x)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Desigur că lucrurile pot sta diferit între Windows și Linux.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Temă 15 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/rw Read / Write] ca aplicație la parsing&lt;br /&gt;
* [https://www.nerdarena.ro/problema/campionat Campionat] dată la OJSEPI 2021, clasa 7-a &lt;br /&gt;
* [https://www.nerdarena.ro/problema/puzzle3 Puzzle3] dată la OJI 2018, clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_15:_Citire_/_scriere_rapid%C4%83&amp;amp;action=edit&amp;amp;redlink=1 Accesează rezolvarea temei 15]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_13:_Analiz%C4%83_amortizat%C4%83_(2)&amp;diff=18575</id>
		<title>Clasa a 7-a Lecția 13: Analiză amortizată (2)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_13:_Analiz%C4%83_amortizat%C4%83_(2)&amp;diff=18575"/>
		<updated>2026-02-06T16:13:04Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video curs ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/_oXoXO7EEWY&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Unific ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/unific Unific] a fost dată la OJI 2013 clasa a 7-a. Problema definește o procedură prin care două numere pot fi unificate, dacă au măcar o cifră în comun. Apoi cere să se aplice pe un vector unificări de elemente adiacente până ce nu se mai poate unifica nimic. Întotdeauna se va face prima unificare posibilă de la începutul vectorului.&lt;br /&gt;
&lt;br /&gt;
Soluția forță brută este să căutăm prima pereche unificabilă de la începutul vectorului și să o unificăm. Apoi reluăm, de la începutul vectorului. Ea este pătratică, deci &#039;&#039;&#039;va depăși timpul pe teste mari&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
O soluție mai bună este ca la momentul citirii unui număr nou să îl unificăm cu el dinainte, apoi rezultatul cu cel dinainte și tot așa până ce nu mai putem unifica. Apoi citim următorul număr și reluăm.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție? &lt;br /&gt;
&lt;br /&gt;
La prima vedere un număr nou poate fi unificat de cel mult n ori, ceea ce duce la o complexitate pătratică. Desigur că nu e așa. Deoarece fiecare număr poate fi adăugat o singură dată și eliminat o singură dată (prin unificare) rezultă că &#039;&#039;&#039;numărul de unificări este maxim 2n. Analiza amortizată ne spune că soluția este liniară în numărul de unificări&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
O parte grea este unificarea propriu zisă. Deși algoritmul este clar, există destule cazuri particulare, iar implementarea dă suficiente bătăi de cap, așa încât mulți elevi au greșit-o la olimpiadă, denumind-o &#039;&#039;problemă tractor&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
În realitate nu este chiar așa, ea putându-se implementa în circa 90 de linii de cod. &lt;br /&gt;
&lt;br /&gt;
=== Implementare soluție ===&lt;br /&gt;
&lt;br /&gt;
==== Subprograme cu parametri de ieșire ====&lt;br /&gt;
&lt;br /&gt;
În problema de față ne poate fi folositoare o funcție care returnează valori folosind parametri de ieșire. Un parametru de ieșire reprezintă o variabilă care păstrează valoarea calculată în funcție și în afara ei. Am mai vorbit despre ei, dar poate este un moment bun să reluăm.&lt;br /&gt;
&lt;br /&gt;
Știm să scriem &#039;&#039;&#039;subprograme cu parametri de intrare&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int subprogram (int variabila1, int variabila2) {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Pentru a putea avea un parametru de ieșire, suntem vom declara acest parametru ca pointer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int subprogram (int variabila1, int *variabila2) {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Vom avea grijă ca oriunde folosim &amp;lt;code&amp;gt;*variabila2&amp;lt;/code&amp;gt; să păstram simbolul &amp;lt;code&amp;gt;*&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Când chemăm subprogramul undeva, vom avea grijă să îi spunem compilatorului care este parametrul de ieșire:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
a = subprogram (var1, &amp;amp;amp;var2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Simbolul &amp;lt;code&amp;gt;&amp;amp;&amp;lt;/code&amp;gt; raportează adresa de memorie unde este stocată acea variabilă, astfel că, executarea programului va folosi adresele de memorie, nu valorile în sine din variabile. Așa se explică de ce trimitem parametrii comenzii &amp;lt;code&amp;gt;fscanf()&amp;lt;/code&amp;gt; cu &amp;lt;code&amp;gt;&amp;amp;&amp;lt;/code&amp;gt;, pentru ca funcția să poată returna variabilele modificate.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de program care folosește parametri de ieșire. Programul următor calculează suma și produsul a două numere într-un subprogram de tip &amp;lt;code&amp;gt;int&amp;lt;/code&amp;gt;. Suma este returnată de către subprogram iar produsul de către parametrul de ieșire al subprogramului:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int suma_produs(int a, int b, int *produs) {&lt;br /&gt;
    *produs = a * b;&lt;br /&gt;
    return a + b;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    FILE *fin, *fout;&lt;br /&gt;
    int nr1, nr2, suma, prod;&lt;br /&gt;
&lt;br /&gt;
    fin = fopen(&amp;quot;sp.in&amp;quot;, &amp;quot;r&amp;quot;);&lt;br /&gt;
    fscanf(fin, &amp;quot;%d%d&amp;quot;, &amp;amp;nr1, &amp;amp;nr2);&lt;br /&gt;
    fclose(fin);&lt;br /&gt;
&lt;br /&gt;
    suma = suma_produs(nr1, nr2, &amp;amp;prod);&lt;br /&gt;
&lt;br /&gt;
    fout = fopen(&amp;quot;sp.out&amp;quot;, &amp;quot;w&amp;quot;);&lt;br /&gt;
    fprintf(fout, &amp;quot;%d %d\n&amp;quot;, suma, prod);&lt;br /&gt;
    fclose(fout);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Urmează sugestii de implementare. Le puteți ignora dacă doriți :-)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Programul principal ====&lt;br /&gt;
&lt;br /&gt;
În programul principal:&lt;br /&gt;
&lt;br /&gt;
* vom folosi &#039;&#039;&#039;o stivă&#039;&#039;&#039; în care vom păstra atâtea doar numere care nu mai pot produce o unificare. Să numim stiva &amp;lt;code&amp;gt;num[]&amp;lt;/code&amp;gt;;&lt;br /&gt;
* vom determina &#039;&#039;&#039;cea mai folosită cifră&#039;&#039;&#039;, prin încărcarea tuturor cifrelor într-un vector de frecvență. Să îl numim &amp;lt;code&amp;gt;c[]&amp;lt;/code&amp;gt;. Pe măsură ce vom citi numerele la intrare, vom prelucra cifrele acestuia în vectorul &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt;. Astfel rezolvăm punctul a al problemei;&lt;br /&gt;
* vom verifica &#039;&#039;&#039;dacă putem face o unificare&#039;&#039;&#039;. Vom verifica dacă stiva are cel puțin două elemente, și dacă da, vom apela o funcție care verifică dacă ultimele două elemente din stivă se pot unifica. Să numim această funcție &amp;lt;code&amp;gt;unific()&amp;lt;/code&amp;gt;. Ea va avea antetul astfel: &amp;lt;code&amp;gt;long long unific (long long a, long long b)&amp;lt;/code&amp;gt;. Funcția ne va returna una din situațiile:&lt;br /&gt;
** fie cele două numere se pot unifica și rezultă un nou număr, caz în care scoatem ultimele două numere din stivă și adăugăm noul număr; numărul de elemente din stivă scade cu unu.&lt;br /&gt;
** fie cele două numere se pot unifica și dispar, caz în care pur și simplu scoatem ultimele două elemente din stivă; numărul de elemente din stivă scade cu 2.&lt;br /&gt;
** fie cele două numere nu se pot unifica.&lt;br /&gt;
* doar în cazul în care ultimele două numere se pot unifica și rezultă un singur număr (primul din cele trei cazuri anterioare) vom continua procedura de unificare, verificând ultimele două numere pe stivă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Subprogramul unific() ====&lt;br /&gt;
&lt;br /&gt;
Antetul programului este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
long long unific (long long a, long long b) &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Acesta va returna rezultatul unificării a două numere, a și b. Rezultatul poate avea unul dintre următoarele valori:&lt;br /&gt;
&lt;br /&gt;
* numărul rezultat prin unificare;&lt;br /&gt;
* -1, dacă dispar ambele numere;&lt;br /&gt;
* -2, dacă nu se unifică.&lt;br /&gt;
&lt;br /&gt;
Vom implementa procedura descrisă în cerință. Pentru a simplifica calculele, vom defini 2 vectori de frecvență, unul pentru fiecare număr. În &amp;lt;code&amp;gt;va[10]&amp;lt;/code&amp;gt; vom salva cifrele numărului &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; și în &amp;lt;code&amp;gt;vb[10]&amp;lt;/code&amp;gt; vom salva cifrele numărului &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt;. Putem crea o altă funcție, care să calculeze acești vectori sau o putem face aici.&lt;br /&gt;
&lt;br /&gt;
De asemenea, vom avea nevoie să calculăm cea mai mare putere a lui 10, mai mică sau egală cu &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;, respectiv cu &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt;. Să le notăm &amp;lt;code&amp;gt;pa&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;pb&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Vom parcurge cei doi vectori de frecvență, va și vb și vom vedea dacă găsim cel puțin o cifră comună. Dacă da, atunci trebuie să creăm noul număr. Pentru că există posibilitatea ca după unificare, ambele numere să nu mai aibă nici o cifră, vom crea noul &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; și noul &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt;. Apoi vom verifica dacă oricare dintre cele două numere noi sunt diferite de 0. Dacă sunt diferite de 0, va trebui să construim numărul din nou.&lt;br /&gt;
&lt;br /&gt;
Pentru a calcula noul &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; sau noul &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt;, vom crea altă funcție, să o numim &amp;lt;code&amp;gt;nrnou()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Subprogramul nrnou() ====&lt;br /&gt;
&lt;br /&gt;
Antetul programului este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
long long nrnou (long long a, long long p10, int v[], int *nrcf) &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Acesta va returna noul număr și numărul lui de cifre în parametrul de ieșire &amp;lt;code&amp;gt;*nrcf&amp;lt;/code&amp;gt;. Explicația parametrilor este următoarea:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;: numărul din care se elimină cifrele;&lt;br /&gt;
* &amp;lt;code&amp;gt;pa&amp;lt;/code&amp;gt;: puterea cea mai mare a lui 10 care încape în &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;, inițial;&lt;br /&gt;
* &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt;: vectorul de frecvență al cifrelor rămase în noul număr. La apelare va fi de fapt vectorul &amp;lt;code&amp;gt;va[]&amp;lt;/code&amp;gt; sau &amp;lt;code&amp;gt;vb[]&amp;lt;/code&amp;gt;, pe care îl vom folosi pentru calcule;&lt;br /&gt;
* &amp;lt;code&amp;gt;nrcf&amp;lt;/code&amp;gt;: numărul de cifre al noului număr (se calculeaza și returnează).&lt;br /&gt;
&lt;br /&gt;
Modul de calcul ar trebui să fie deja evident. Câtă vreme sunt cifre în numărul &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;, vom calcula prima cifră cu ajutorul puterii pa și vom elimina prima cifră din &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;. Dacă cifra există în vectorul de frecvență o adăugăm la numărul nou construit. Vom actualiza numărul de cifre pentru noul număr.&lt;br /&gt;
&lt;br /&gt;
Atenție, tot aici trebuie să ajustăm noul număr când singurele cifre rămase sunt 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema MaxArea ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/maxarea MaxArea] a fost dată la concursul Shumen pentru juniori în 2015.&lt;br /&gt;
&lt;br /&gt;
Rezolvarea folosește o stivă, similar cu problemele [https://www.nerdarena.ro/problema/tower Tower] sau [https://www.nerdarena.ro/problema/maxp MaxP]. Vom memora o stivă de dreptunghiuri de înălțimi din ce în ce mai mari. Când adăugăm o înălțime &amp;lt;code&amp;gt;h&amp;lt;/code&amp;gt; vom închide toate dreptunghiurile de înălțimi mai mari de pe stivă. Apoi depunem pe stivă dreptunghiul curent.&lt;br /&gt;
&lt;br /&gt;
Avem două cazuri, dacă dreptunghiul curent este de înălțime strict mai mare vom crea un nou dreptunghi pe stivă de &#039;&#039;lățime 1 + lățimea însumată a dreptunghiurilor eliminate&#039;&#039;. Altfel, în caz de egalitate, vom mări lățimea dreptunghiului din vârful stivei cu același număr, &#039;&#039;1 + lătimea însumată a dreptunghiurilor eliminate&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
De remarcat că problema cere citirea a un milion de întregi de 5 cifre. Incluzând separatorul, spațiu, aceasta înseamnă circa 6MB. Soluția fiind &#039;&#039;O(n)&#039;&#039;, la fel ca și citirea, este clar că ea va fi dominată ca timp de citire. De aceea este important să nu citim cu &amp;lt;code&amp;gt;fscanf()&amp;lt;/code&amp;gt;, ci cu &amp;lt;code&amp;gt;fgetc()&amp;lt;/code&amp;gt; (parsing în limbajul olimpicilor).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație:&#039;&#039;&#039; spre finalul numerelor pe stivă se vor afla foarte multe înălțimi egale. O optimizare ar fi, deci, să &#039;&#039;grupăm&#039;&#039; acele înălțimi. În loc de o înălțime putem stoca o pereche &amp;lt;code&amp;gt;(h, ap)&amp;lt;/code&amp;gt; unde &amp;lt;code&amp;gt;ap&amp;lt;/code&amp;gt; ne spune numărul de apariții ale acelei înălțimi. Când adăugăm o înălțime egală la stivă vom aduna de fapt 1 la &amp;lt;code&amp;gt;ap&amp;lt;/code&amp;gt;. Astfel mărimea stivei rămâne mică.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Skyline ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/skyline Skyline] este o problemă clasică. Cere să se găsească dreptunghiul de arie maximă într-un skyline, unde un skyline este linia lăsată de zgârie-nori. Cu alte cuvinte o secvență de dreptunghiuri aliniate cu axa &#039;&#039;Ox&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Rezolvarea folosește o stivă, similar cu problemele [https://www.nerdarena.ro/problema/tower Tower] sau [https://www.nerdarena.ro/problema/maxp MaxP]. Vom memora o stivă de dreptunghiuri de înălțimi din ce în ce mai mari. Când adăugăm un dreptunghi de înălțime &amp;lt;code&amp;gt;h&amp;lt;/code&amp;gt; vom închide toate dreptunghiurile de înălțime mai mare de pe stivă, având grijă să le înlocuim cu un dreptunghi egal cu suma lungimilor lor și de înălțime egală cu &amp;lt;code&amp;gt;h&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Complexitatea este &#039;&#039;O(n)&#039;&#039; prin analiză amortizată și &#039;&#039;O(n)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Temă 13  ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/unific Unific] dată la OJI 2013 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/maxarea MaxArea] dată la Shumen Juniori 2015&lt;br /&gt;
* [https://www.nerdarena.ro/problema/skyline Skyline] problemă clasică de analiză amortizată&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/dreptunghi1 Dreptunghi1] problemă clasică de analiză amortizată&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php/Clasa_a_7-a_Lec%C8%9Bia_13:_Analiz%C4%83_amortizat%C4%83_(2) Accesează rezolvarea temei 13]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_12:_Analiz%C4%83_amortizat%C4%83_(1)&amp;diff=18574</id>
		<title>Clasa a 7-a Lecția 12: Analiză amortizată (1)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_12:_Analiz%C4%83_amortizat%C4%83_(1)&amp;diff=18574"/>
		<updated>2026-02-06T16:11:34Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video curs ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/PW0OHW6kBeM&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Despre analiza amortizată ==&lt;br /&gt;
&lt;br /&gt;
Citat din [http://en.wikipedia.org/wiki/Introduction_to_Algorithms CLRS]: În analiza amortizată facem media timpului necesar pentru a executa o secvență operații, împărțindu-l la toate operațiile executate. &lt;br /&gt;
&lt;br /&gt;
Prin analiza amortizată putem să arătăm că costul mediu al unei operații este mic, atunci când împărțim la numărul de operații, cu toate că o singură operație din secvență ar putea fi &amp;quot;scumpă&amp;quot;. Acest cost mediu per operație se mai numește și costul amortizat.&lt;br /&gt;
&lt;br /&gt;
Pentru a înțelege conceptul cât mai bine, iată un clip preluat de pe canalul [https://www.youtube.com/channel/UCTCvWvqjktIq0uvM3trAHCg NerdFirst]:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/T7W5E-5mljc&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
În continuare vom lua câteva exemple clasice de analiză amortizată.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Incrementarea unui contor binar ===&lt;br /&gt;
&lt;br /&gt;
Să presupunem că vrem să incrementăm un număr binar ale cărui cifre se află într-un vector. Numărul are &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; cifre, cea mai puțin semnificativă cifră (ultima din număr) fiind stocată prima în vector. &lt;br /&gt;
&lt;br /&gt;
Cum arată această incrementare?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
i = 0;&lt;br /&gt;
while ( v[i] == 1 ) {&lt;br /&gt;
  v[i] = 0;&lt;br /&gt;
  i++;&lt;br /&gt;
}&lt;br /&gt;
v[i] = 1;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Vom face această operațiune de &amp;lt;code&amp;gt;2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;&amp;lt;/code&amp;gt; ori, pentru a trece prin toate valorile binare posibile. Întrebarea este care este numărul de operații per incrementare?&lt;br /&gt;
&lt;br /&gt;
Un răspuns intuitiv ar fi că numărul maxim de operații la o incrementare oarecare este &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;, deci probabil că numărul mediu de operații este &amp;lt;code&amp;gt;n/2&amp;lt;/code&amp;gt;, ceea ce ne duce la un timp &#039;&#039;O(n)&#039;&#039; per incrementare. Desigur că răspunsul este incorect. Să încercăm să-l calculăm mai exact. Vom calcula numărul total de operații pe parcursul tuturor incrementărilor și îl vom împărți la numărul de incrementări, &amp;lt;code&amp;gt;2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Să observăm că ultima cifră își va schimba valoarea la fiecare incrementare. Penultima numai din doi în doi. Antepenultima numai din patru în patru și așa mai departe. De unde rezultă că numărul total de cifre schimbate (egal cu numărul de operații total) este:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;T(n) = 2^n + 2^{n-1} + 2^{n-2} + \cdots + 2^1&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Această sumă se calculează folosind formula sumei unei progresii geometrice, pe care o veți învăța abia în clasa a zecea. Până atunci să folosim un artificiu: să observăm că numărul &amp;lt;code&amp;gt;T(n)&amp;lt;/code&amp;gt; este &amp;lt;code&amp;gt;111...110&amp;lt;/code&amp;gt; în baza doi. El conține &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; cifre 1, iar ultima cifră este zero deoarece 20 lipsește din sumă. &lt;br /&gt;
&lt;br /&gt;
Ce se întâmplă dacă adunăm 2 la acest număr? Vom obține:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;111\ldots110_{(2)} + 2_{(10)} = 100\ldots0_{(2)} \quad \text{(1 urmat de } n+1 \text{ zerouri)} &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Acest număr este &amp;lt;code&amp;gt;2n+1&amp;lt;/code&amp;gt;. Rezultă că:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;T(n) + 2 = 2n + 1&amp;lt;/math&amp;gt; și deci &amp;lt;math&amp;gt; T(n) = 2n + 1 - 2 &amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Acesta este numărul total de operații, pentru toate incrementările. Împărțind la numărul de incrementări, care este &amp;lt;code&amp;gt;2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;&amp;lt;/code&amp;gt;, obținem aproximativ două operațiuni per incrementare!&lt;br /&gt;
&lt;br /&gt;
Iată cum, folosind analiza amortizată, am obținut un rezultat mult mai bun. &lt;br /&gt;
&lt;br /&gt;
În loc de &#039;&#039;O(n)&#039;&#039; am aflat că incrementarea unui contor binar este &#039;&#039;O(1)&#039;&#039;!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Permutări ===&lt;br /&gt;
&lt;br /&gt;
Am studiat în lecțiile trecute algoritmi de generare a permutărilor. Întrebarea, ca și la exercițiul anterior, este câte operații ne costă generarea unei permutări? &lt;br /&gt;
&lt;br /&gt;
Un prim răspuns, intuitiv, este că trebuie să așezăm n elemente în vector pentru fiecare permutare, deci probabil costul este &#039;&#039;O(n)&#039;&#039;. Desigur că este greșit. Să încercăm să îl calculăm:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Permutări cu liste ====&lt;br /&gt;
&lt;br /&gt;
Să considerăm al doilea algoritm studiat, cel cu liste, deoarece calculul este mai ușor. În acest caz avem &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; niveluri de recursivitate, la fiecare nivel așezând cifre, astfel:&lt;br /&gt;
&lt;br /&gt;
* pe nivelul &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; vom așeza &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; cifre;&lt;br /&gt;
* pentru fiecare cifră de pe nivelul &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, pe nivelul &amp;lt;math&amp;gt;n-1&amp;lt;/math&amp;gt; vom așeza &amp;lt;math&amp;gt;n-1&amp;lt;/math&amp;gt; cifre;&lt;br /&gt;
* pentru fiecare cifră de pe nivelul &amp;lt;math&amp;gt;n-1&amp;lt;/math&amp;gt;, pe nivelul &amp;lt;math&amp;gt;n-2&amp;lt;/math&amp;gt; vom așeza &amp;lt;math&amp;gt;n-2&amp;lt;/math&amp;gt; cifre;&lt;br /&gt;
* ...&lt;br /&gt;
* pentru fiecare cifră de pe nivelul &amp;lt;math&amp;gt;2&amp;lt;/math&amp;gt;, pe nivelul &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; vom așeza o cifră.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație:&#039;&#039;&#039; ceea ce am denumit nivelul &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; este de fapt prima poziție în vectorul în care generăm permutările, nivelul &amp;lt;math&amp;gt;n-1&amp;lt;/math&amp;gt; este de fapt a doua poziție, și așa mai departe, nivelul &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt; fiind ultima poziție din vector.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Ce operațiuni efectuăm pe nivelul &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;? &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
În primul rând o parcurgere a tuturor elementelor, pentru a vedea dacă le putem folosi, deci &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; operații. Apoi, pentru fiecare element așezat vom chema recursiv funcția, deci vom avea &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; apeluri la nivelul &amp;lt;math&amp;gt;k-1&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt; \begin{aligned}&lt;br /&gt;
T(k) &amp;amp;= k + k \cdot T(k - 1) \\[1em]&lt;br /&gt;
T(k) &amp;amp;= k + k \cdot \left( (k - 1) + (k - 1) \cdot \left( (k - 2) + (k - 2) \cdot \left( \cdots + 3 \cdot (2 + 2 \cdot T(1)) \cdots \right) \right) \right) \\[1em]&lt;br /&gt;
T(k) &amp;amp;= k + k(k - 1) + k(k - 1)(k - 2) + \cdots + k(k - 1)(k - 2) \cdots 3 + k(k - 1)(k - 2) \cdots 3 \cdot 2 + k(k - 1)(k - 2) \cdots 3 \cdot 2 \cdot T(1)\\[1em]&lt;br /&gt;
\end{aligned}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dar &amp;lt;math&amp;gt;T(1)&amp;lt;/math&amp;gt; este &amp;lt;math&amp;gt;1&amp;lt;/math&amp;gt;, deoarece vom așeza un singur element. Așa încât:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt; \begin{aligned}&lt;br /&gt;
T(k) &amp;amp;= k + k(k - 1) + k(k - 1)(k - 2) + \cdots + k(k - 1)(k - 2) \cdots 3 + k(k - 1)(k - 2) \cdots 3 \cdot 2 + k(k - 1)(k - 2) \cdots 3 \cdot 2 \cdot 1 \\[1em]&lt;br /&gt;
T(k) &amp;amp;= \frac{k!}{(k - 1)!} + \frac{k!}{(k - 2)!} + \frac{k!}{(k - 3)!} + \cdots + \frac{k!}{2!} + \frac{k!}{1!} + \frac{k!}{0!} \\[1em]&lt;br /&gt;
T(k) &amp;amp;= k! \left( \frac{1}{(k - 1)!} + \frac{1}{(k - 2)!} + \frac{1}{(k - 3)!} + \cdots + \frac{1}{2!} + \frac{1}{1!} + \frac{1}{0!} \right)\\[1em]&lt;br /&gt;
\end{aligned}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Suma:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;\frac{1}{0!} + \frac{1}{1!} + \frac{1}{2!} + \cdots + \frac{1}{(k - 1)!}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
va fi studiată în clasa a zecea. Se demonstrează că ea este mai mică decât o constantă &amp;lt;math&amp;gt;e = 2.71828...&amp;lt;/math&amp;gt;. Drept pentru care:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;T(k) \approx e \cdot k!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Numărul total de operații se obține pentru &amp;lt;math&amp;gt;k = n&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;T(n) = e \cdot n!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Deoarece avem &amp;lt;math&amp;gt;n!&amp;lt;/math&amp;gt; permutări rezultă că generarea unei permutări ne costă &#039;&#039;O(e) = O(1)&#039;&#039;! &lt;br /&gt;
&lt;br /&gt;
Din nou un rezultat surprinzător. Desigur că acest rezultat nu ia în calcul afișarea permutărilor, ci doar generarea lor în vector.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Permutări cu vector de frecvență ====&lt;br /&gt;
&lt;br /&gt;
Să considerăm acum primul algoritm, cel care folosește un vector de incluziune pentru a ști dacă o cifră a mai fost folosită, înainte de a o plasa pe poziția curentă. Calculul este similar, până ajungem la întrebarea cât ne costă să așezăm o cifră. Aici ne împotmolim, deoarece nu este clar. &lt;br /&gt;
&lt;br /&gt;
La primul nivel ne costă o operație per cifră, dar undeva la mijloc vom face tot &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; operații pentru a așeza doar &amp;lt;math&amp;gt;n/2&amp;lt;/math&amp;gt; cifre, deoarece parcurgem toate cifrele pentru a determina care sunt cele așezabile. Ce facem în acest caz? Să folosim din nou matematica. Să notăm cu &amp;lt;math&amp;gt;T(k)&amp;lt;/math&amp;gt; costul pentru a așeza permutările de &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; (ultimele &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; poziții ale vectorului de permutări). În final ne va interesa &amp;lt;math&amp;gt;T(n)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ce operațiuni efectuăm pe nivelul &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt;? În primul rând o parcurgere a tuturor elementelor, pentru a vedea dacă le putem folosi, deci &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt; operații. Apoi, pentru fiecare element așezat vom chema recursiv funcția, deci vom avea &amp;lt;math&amp;gt;k&amp;lt;/math&amp;gt; apeluri la nivelul &amp;lt;math&amp;gt;k-1&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;\begin{aligned}&lt;br /&gt;
T(k) &amp;amp;= n + k \cdot T(k - 1) \\[1em]&lt;br /&gt;
T(k) &amp;amp;= n + k \left( n + (k - 1) \left[ n + (k - 2) \left( \cdots + 3(n + 2 \cdot T(1)) \cdots \right) \right] \right) \\[1em]&lt;br /&gt;
T(k) &amp;amp;= n + kn + k(k - 1)n + k(k - 1)(k - 2)n + \cdots + k(k - 1)(k - 2) \cdots 3 \cdot n + k(k - 1)(k - 2) \cdots 3 \cdot 2 \cdot T(1)&lt;br /&gt;
\end{aligned}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Dar &amp;lt;math&amp;gt;T(1)&amp;lt;/math&amp;gt; este &amp;lt;math&amp;gt;n&amp;lt;/math&amp;gt;, deoarece vom parcurge toate elementele și vom așeza unul singur. Așa încât:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt; \begin{aligned}&lt;br /&gt;
T(k) &amp;amp;= n + n \left( k + k(k - 1) + k(k - 1)(k - 2) + \cdots + k(k - 1)(k - 2) \cdots 3 + k(k - 1)(k - 2) \cdots 3 \cdot 2 \right) \\[1em]&lt;br /&gt;
T(k) &amp;amp;= n + n \left( \frac{k!}{(k - 1)!} + \frac{k!}{(k - 2)!} + \frac{k!}{(k - 3)!} + \cdots + \frac{k!}{2!} + \frac{k!}{1!} \right) \\[1em]&lt;br /&gt;
T(k) &amp;amp;= n + nk! \left( \frac{1}{(k - 1)!} + \frac{1}{(k - 2)!} + \frac{1}{(k - 3)!} + \cdots + \frac{1}{2!} + \frac{1}{1!} \right)&lt;br /&gt;
\end{aligned}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Suma dintre paranteze este, din nou, mai mică decât &amp;lt;math&amp;gt;e-1&amp;lt;/math&amp;gt;, unde &amp;lt;math&amp;gt;e&amp;lt;/math&amp;gt; este constanta &amp;lt;math&amp;gt;e = 2.71828....&amp;lt;/math&amp;gt;. Drept pentru care:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;T(k) \approx n + (e - 1) \cdot n \cdot k!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Numărul total de operații se obține pentru &amp;lt;math&amp;gt;k = n&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;math&amp;gt;T(n) = n + (e - 1) \cdot n \cdot n!&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Atunci când împărțim acest număr la numărul de permutări generate, &amp;lt;math&amp;gt;n!&amp;lt;/math&amp;gt;, obținem costul per permutare ca fiind aproximativ &amp;lt;math&amp;gt;(e - 1) \cdot n&amp;lt;/math&amp;gt;, adică &#039;&#039;O(n)&#039;&#039;. De unde rezultă că acest algoritm este inferior celui cu liste.&lt;br /&gt;
&lt;br /&gt;
Ar trebui să vedem că algoritmul cu liste este de &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; ori mai rapid, unde &amp;lt;math display=&amp;quot;inline&amp;quot;&amp;gt;x = \frac{(e - 1) \cdot n}{e} = \left( 1 - \frac{1}{e} \right) \cdot n&amp;lt;/math&amp;gt;. Pentru n=10 numărul &amp;lt;math&amp;gt;x&amp;lt;/math&amp;gt; este circa &amp;lt;math&amp;gt;6.32&amp;lt;/math&amp;gt;. În realitate vom obține mai puțin deoarece algoritmul cu liste are o constantă multiplicativă ceva mai mare. Pe algoritmii prezentați am măsurat circa &amp;lt;math&amp;gt;3.8&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Clădiri vizibile ===&lt;br /&gt;
&lt;br /&gt;
Să presupunem că avem &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; clădiri înșirate pe o stradă, lipite una de alta. Se cunosc înălțimile fiecărei clădiri. Mergând pe acoperișurile clădirilor ne întrebăm, pentru fiecare acoperiș, ce clădire vedem la acea înălțime uitându-ne spre stânga.&lt;br /&gt;
&lt;br /&gt;
Să traducem această problemă în limbaj informatic: dat un vector v de n numere naturale să se calculeze un vector &amp;lt;code&amp;gt;w&amp;lt;/code&amp;gt;, unde &amp;lt;code&amp;gt;w[i]&amp;lt;/code&amp;gt; este &amp;lt;code&amp;gt;v[j]&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; cel mai mare indice &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; astfel încât &amp;lt;code&amp;gt;v[j] &amp;gt; v[i]&amp;lt;/code&amp;gt;. Cu alte cuvinte pentru fiecare element din vectorul &amp;lt;code&amp;gt;v&amp;lt;/code&amp;gt; vom căuta primul element înapoi mai mare decât el.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluția forță brută&#039;&#039;&#039; este ca pentru fiecare indice &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; să căutăm spre stânga primul indice &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; unde găsim un element mai mare. Această soluție este &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție elegantă:&#039;&#039;&#039; parcurgem &amp;lt;code&amp;gt;v&amp;lt;/code&amp;gt; și menținem o stivă &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; de elemente interesante, în care așezăm la rând elemente din &amp;lt;code&amp;gt;v&amp;lt;/code&amp;gt;, astfel: la pasul &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; vom șterge din &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; ultimele elemente, cele mai mici decât &amp;lt;code&amp;gt;v[i]&amp;lt;/code&amp;gt;. Ultimul element rămas în &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; este chiar &amp;lt;code&amp;gt;w[i]&amp;lt;/code&amp;gt;. Apoi îl adăugăm pe &amp;lt;code&amp;gt;v[i]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  Fie vectorul de înălțimi &#039;&#039;v = [7 3 2 5 4 3 9 7]&#039;&#039;&lt;br /&gt;
  Stiva s = []&lt;br /&gt;
  Vectorul w = []&lt;br /&gt;
&lt;br /&gt;
Pasul 0:&lt;br /&gt;
  Scoatem din s toate elementele mai mici sau egale cu v[0] == 7&lt;br /&gt;
  s este goală, nu există nici un element mai mare ca v[0] == 7&lt;br /&gt;
  Calculăm w[0] = vîrful stivei = nu există, deci îl vom marca cu X &lt;br /&gt;
    (X este o valoare ce indică lipsa unui element mai mare ca v[0] la stînga)&lt;br /&gt;
  Adăugăm v[0] == 7 la s&lt;br /&gt;
  Obținem:&lt;br /&gt;
    v = [7 3 2 5 4 3 9 7]&lt;br /&gt;
    w = [X]&lt;br /&gt;
    s = [7]&lt;br /&gt;
&lt;br /&gt;
Pasul 1:&lt;br /&gt;
  Scoatem din s toate elementele mai mici sau egale cu v[1] == 3&lt;br /&gt;
  Nu există astfel de elemente, deci stiva rămîne nemodificată&lt;br /&gt;
  Calculăm w[1] = vîrful stivei = 7&lt;br /&gt;
  Adăugăm v[1] == 3 la s&lt;br /&gt;
  Obținem:&lt;br /&gt;
    v = [7 3 2 5 4 3 9 7]&lt;br /&gt;
    w = [X 7]&lt;br /&gt;
    s = [7 3]&lt;br /&gt;
&lt;br /&gt;
Pasul 2:&lt;br /&gt;
  Scoatem din s toate elementele mai mici sau egale cu v[2] == 2&lt;br /&gt;
  Nu există astfel de elemente, deci stiva rămîne nemodificată&lt;br /&gt;
  Calculăm w[2] = vîrful stivei = 3&lt;br /&gt;
  Adăugăm v[2] == 2 la s&lt;br /&gt;
  Obținem:&lt;br /&gt;
    v = [7 3 2 5 4 3 9 7]&lt;br /&gt;
    w = [X 7 3]&lt;br /&gt;
    s = [7 3 2]&lt;br /&gt;
&lt;br /&gt;
Pasul 3:&lt;br /&gt;
  Scoatem din s toate elementele mai mici sau egale cu v[3] == 5&lt;br /&gt;
  Vom elimina ultimele două elemente, 3 și 2, deci stiva devine s = [7]&lt;br /&gt;
  Calculăm w[3] = vîrful stivei = 7&lt;br /&gt;
  Adăugăm v[3] == 5 la s&lt;br /&gt;
  Obținem:&lt;br /&gt;
    v = [7 3 2 5 4 3 9 7]&lt;br /&gt;
    w = [X 7 3 7]&lt;br /&gt;
    s = [7 5]&lt;br /&gt;
&lt;br /&gt;
Pasul 4:&lt;br /&gt;
  Scoatem din s toate elementele mai mici sau egale cu v[4] == 4&lt;br /&gt;
  Nu există astfel de elemente, deci stiva rămîne nemodificată&lt;br /&gt;
  Calculăm w[4] = vîrful stivei = 5&lt;br /&gt;
  Adăugăm v[4] == 4 la s&lt;br /&gt;
  Obținem:&lt;br /&gt;
    v = [7 3 2 5 4 3 9 7]&lt;br /&gt;
    w = [X 7 3 7 5]&lt;br /&gt;
    s = [7 5 4]&lt;br /&gt;
&lt;br /&gt;
Pasul 5:&lt;br /&gt;
  Scoatem din s toate elementele mai mici sau egale cu v[5] == 3&lt;br /&gt;
  Nu există astfel de elemente, deci stiva rămîne nemodificată&lt;br /&gt;
  Calculăm w[5] = vîrful stivei = 4&lt;br /&gt;
  Adăugăm v[5] == 3 la s&lt;br /&gt;
  Obținem:&lt;br /&gt;
    v = [7 3 2 5 4 3 9 7]&lt;br /&gt;
    w = [X 7 3 7 5 4]&lt;br /&gt;
    s = [7 5 4 3]&lt;br /&gt;
&lt;br /&gt;
Pasul 6:&lt;br /&gt;
  Scoatem din s toate elementele mai mici sau egale cu v[6] == 9&lt;br /&gt;
  Vom elimina toate elementele din s, deci stiva devine vidă&lt;br /&gt;
  Calculăm w[6] = vîrful stivei = nu există, deci îl vom marca cu X&lt;br /&gt;
  Adăugăm v[6] == 9 la s&lt;br /&gt;
  Obținem:&lt;br /&gt;
    v = [7 3 2 5 4 3 9 7]&lt;br /&gt;
    w = [X 7 3 7 5 4 X]&lt;br /&gt;
    s = [9]&lt;br /&gt;
&lt;br /&gt;
Pasul 7:&lt;br /&gt;
  Scoatem din s toate elementele mai mici sau egale cu v[7] == 7&lt;br /&gt;
  Nu există astfel de elemente, deci stiva rămîne nemodificată&lt;br /&gt;
  Calculăm w[7] = vîrful stivei = 9&lt;br /&gt;
  Adăugăm v[7] == 7 la s&lt;br /&gt;
  Obținem:&lt;br /&gt;
    v = [7 3 2 5 4 3 9 7]&lt;br /&gt;
    w = [X 7 3 7 5 4 X 9]&lt;br /&gt;
    s = [9 7]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm? Ar părea că la fiecare pas putem scoate din vectorul s n elemente, caz în care algoritmul ar avea complexitate &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Să încercăm să calculăm numărul total de operații.&lt;br /&gt;
&lt;br /&gt;
Deși nu știm câte operații vom face la fiecare pas, observăm următorul lucru: fiecare element din vectorul &amp;lt;code&amp;gt;v&amp;lt;/code&amp;gt; este adăugat fix o dată și scos fix o dată. Atunci când eliminăm elemente din &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; scoatem acele elemente adăugate anterior. Drept pentru care numărul total de operații va fi &amp;lt;code&amp;gt;2n&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Complexitatea amortizată a adăugării unui element în s este, de fapt &#039;&#039;O(1)&#039;&#039;, iar întregul algoritm are complexitate &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Problema bibliotecarului ===&lt;br /&gt;
&lt;br /&gt;
Un bibliotecar primește cărți una câte una și le returnează în aceeași ordine, la cerere. În spatele ghișeului el are doi subalterni. Bibliotecarul trebuie să dea cărțile primite unuia din subalterni. Poate de asemenea să ceară înapoi cărți de la subalterni. Fiecare subaltern ține o stivă de cărți. Atunci când bibliotecarul îi dă o carte subalternul o pune peste toate celelalte, în stiva sa. Atunci când bibliotecarul îi cere o carte o ia pe prima din stivă și i-o returnează. Voi trebuie să găsiți algoritmul de funcționare a bibliotecarului astfel încât după un număr mare de depuneri și cereri de cărți numărul total de operații (înmânări de cărți) să fie cât mai mic din punct de vedere al complexității. Puteți presupune că vor fi 500000 de depuneri de cărți și 500000 de cereri, în orice ordine coerentă.&lt;br /&gt;
&lt;br /&gt;
Rezolvarea directă, evidentă, este, surprinzător, și cea corectă: bibliotecarul va da toate cărțile primite subalternului 1. Când i se cere o carte, i-o va cere subalternului 2. Dacă subalternul 2 nu are cărți atunci el îi va cere, pe rând cărți subalternului 1 și i le va da subalternului 2, până ce subalternul 1 nu mai are cărți. Apoi returnează cartea pe care i-o va da subalternul 2.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Funcționează corect acest algoritm?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Desigur, urmăriți un exemplu și vă veți convinge. Dar pare foarte ineficient! Pentru o carte cerută este posibil ca bibliotecarul să facă n operații! Să facem același calcul ca și mai devreme: orice carte va fi depusă la primul subaltern, apoi scoasă și depusă la cel de-al doilea subaltern și apoi returnată. Drept pentru care numărul total de operații va fi 3n, iar costul amortizat al unei operații de depunere sau de scoatere va fi &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Problema StrGen ===&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/strgen strgen] a fost dată la testul de evaluare Nerdvana din 1 noiembrie 2023.&lt;br /&gt;
&lt;br /&gt;
Am dat această problemă în ideea rezolvării cu liste, dar am menționat atunci că există o rezolvare cu două stive și analiză amortizată. Să o reluăm.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Soluție cu două stive ====&lt;br /&gt;
&lt;br /&gt;
Există o soluție eficientă ce folosește două stive. Prima stivă este cea în care adăugăm atunci când inserăm un caracter. La momentul avansului poziției scoatem un caracter din prima stivă și îl depunem într-o a doua stivă.&lt;br /&gt;
&lt;br /&gt;
Dacă nu vom ajunge să reluăm poziția de la 1, înseamnă că șirul nostru final este format din caracterele din stiva 2 afișate în sens invers scoaterii din stivă, urmate de caracterele din stiva 1 afișate în sensul scoaterii din stivă.&lt;br /&gt;
&lt;br /&gt;
Ce facem dacă poziția se reia de la 1? Înseamnă că stiva 1 devine goală. Caz în care vom scoate elementele din stiva 2, unul câte unul, și le vom depune în stiva 1. Astfel stiva 2 devine goală, iar stiva 1 conține caracterele șirului curent.&lt;br /&gt;
&lt;br /&gt;
La final afișăm stiva 2 în sens invers și stiva 1 în sens direct.&lt;br /&gt;
&lt;br /&gt;
Iată un program posibil (26 de linii de cod efectiv):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXL 2000000&lt;br /&gt;
&lt;br /&gt;
char stiva1[MAXL + 1] = { &#039;$&#039; }, stiva2[MAXL + 1];&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  char ch;&lt;br /&gt;
  int i, n1 = 1, n2 = 0;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;strgen.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  while ( (ch = fgetc( fin )) != &#039;\n&#039; )&lt;br /&gt;
    if ( ch == &#039;&amp;gt;&#039; ) {&lt;br /&gt;
      stiva2[n2++] = stiva1[--n1]; &lt;br /&gt;
      if ( n1 == 0 )&lt;br /&gt;
        while ( n2 &amp;gt; 0 )&lt;br /&gt;
          stiva1[n1++] = stiva2[--n2];&lt;br /&gt;
    } else&lt;br /&gt;
      stiva1[n1++] = ch;&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;strgen.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n2; i++ )&lt;br /&gt;
    fputc( stiva2[i], fout );&lt;br /&gt;
  for ( i = n1 - 1; i &amp;gt;= 0; i-- )&lt;br /&gt;
    fputc( stiva1[i], fout );&lt;br /&gt;
  fputc( &#039;\n&#039;, fout );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
La prima vedere pare că fiecare operație poate fi &#039;&#039;O(N)&#039;&#039; ceea ce ar rezulta într-o complexitate de &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. În realitate nu este așa, deoarece pentru fiecare caracter scos din stiva 2 și inserat în stiva 1 vom avea o operație de scoatere din stivă ce se va efectua în &#039;&#039;O(1)&#039;&#039;. Suma tuturor operațiilor nu poate depăși &#039;&#039;O(N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Câtă memorie folosim? Desigur tot &#039;&#039;O(N)&#039;&#039;, circa 2 bytes per element, deci 2MB (mai puțină decât în soluția cu listă).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Concluzii ==&lt;br /&gt;
&lt;br /&gt;
Cu aceasta am încheiat exemplele de analiză amortizată. Remarcați ce au ele în comun: de cele mai multe ori în analiza amortizată avem o stivă. Un vector în care depunem la coadă elemente și din care ștergem tot de la coadă toate elementele mai mari ca elementul pe care îl adăugăm. Evident poate fi pe dos în cazul când ne interesează minime.&lt;br /&gt;
&lt;br /&gt;
Oare ne folosește la ceva analiza amortizată, sau este doar ceva pur academic? &lt;br /&gt;
&lt;br /&gt;
Ei bine, ea se dă destul de frecvent la olimpiadă (este posibil să nu o fi remarcat până acum pentru că nu știați semnalmentele după care să vă uitați). În lecția următoare vom vedea probleme cu analiză amortizată date la olimpiada de informatică și alte concursuri.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Temă 12 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/tower Tower], dată la Shumen 2016 juniori&lt;br /&gt;
* [https://www.nerdarena.ro/problema/nrtri NrTri], dată pregătire Vianu clasa a 9-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională == &lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/nrtri1 NrTri1], problemă identică cu [https://www.nerdarena.ro/problema/nrtri NrTri] cu limitele modificate.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php/Clasa_a_7-a_Lec%C8%9Bia_12:_Analiz%C4%83_amortizat%C4%83_(1) Accesează rezolvarea temei 12]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_11:_Numere_mari,_exponen%C8%9Biere_rapid%C4%83,_element_majoritar&amp;diff=18573</id>
		<title>Clasa a 7-a Lecția 11: Numere mari, exponențiere rapidă, element majoritar</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_11:_Numere_mari,_exponen%C8%9Biere_rapid%C4%83,_element_majoritar&amp;diff=18573"/>
		<updated>2026-02-06T16:09:29Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/YwpoxbhQoBM&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Operații cu numere mari ==&lt;br /&gt;
&lt;br /&gt;
=== Reprezentarea numerelor mari ===&lt;br /&gt;
&lt;br /&gt;
Unele probleme necesită lucrul cu numere mai mari decât ne permite tipul &amp;lt;code&amp;gt;long long&amp;lt;/code&amp;gt; (cu aproximație 18 cifre zecimale, mai exact 2&amp;lt;sup&amp;gt;64&amp;lt;/sup&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
Ce facem în acest caz? Stocăm aceste numere în vectori, câte o cifră în fiecare element al vectorului. &lt;br /&gt;
&lt;br /&gt;
Numerele mari se reprezintă într-un vector, ultima lor cifra pe prima poziție a vectorului, penultima pe a doua poziție, și așa mai departe. De exemplu, numărul &#039;&#039;&#039;24537&#039;&#039;&#039; se reprezintă ca în figura alăturată.&lt;br /&gt;
&lt;br /&gt;
[[Image:nr-mare-1.gif|frame|none|Reprezentarea în vector a unui număr mare]]&lt;br /&gt;
&lt;br /&gt;
Observați că am reprezentat vectorul invers, de la dreapta la stânga, pentru ușurința citirii numărului. &lt;br /&gt;
&lt;br /&gt;
De ce reprezentăm cifrele numărului de la coadă la cap? Deoarece atunci când numerele cresc sau se micșorează cifrele lor nu trebuie deplasate în vector pentru a rămâne aliniate la începutul vectorului.&lt;br /&gt;
&lt;br /&gt;
Observăm că vectorul &amp;lt;code&amp;gt;v&amp;lt;/code&amp;gt; are ca elemente cifre, adică întregi cuprinși între 0 și 9. Pentru a folosi cât mai puțină memorie vom folosi cel mai mic întreg existent în limbajul C și anume tipul caracter. Să nu uităm că tipul caracter poate fi văzut atât ca și caracter cât și ca un întreg pe opt biți, cuprins între -128 și 127. Această reprezentare a numerelor mari se numește reprezentarea în zecimal despachetat: &amp;lt;code&amp;gt;char v[100]&amp;lt;/code&amp;gt; .&lt;br /&gt;
&lt;br /&gt;
Pentru cei curioși există și reprezentarea zecimal împachetat, în care reprezentăm două cifre zecimale pe caracter, respectiv o cifră pe primii 4 biți și o cifră pe ultimii patru biți. În această lecție nu vom vorbi despre această reprezentare.&lt;br /&gt;
&lt;br /&gt;
De asemenea, pentru un număr mare trebuie să ținem minte și numărul său de cifre. În exemplul nostru acesta este 5. Printre olimpici circulă o versiune de numere mari în care numărul de cifre este memorat chiar în primul element al vectorului, &amp;lt;code&amp;gt;v[0]&amp;lt;/code&amp;gt;. Aceasta nu este o idee bună din mai multe motive:&lt;br /&gt;
&lt;br /&gt;
* Dacă numărul mare are mai mult de 255 de cifre vom depăși tipul unsigned char.&lt;br /&gt;
* Amestecă un număr de cifre cu cifrele însele, punând la grămadă mere cu dovlecei.&lt;br /&gt;
* Codul devine mai greu citibil.&lt;br /&gt;
&lt;br /&gt;
Ca ultimă mențiune, trebuie să avem grijă ca cifrele nefolosite din vector să fie setate la zero. Acest lucru este foarte important atunci când numărul poate să crească, cum ar fi la adunare, sau înmulțire.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Afișarea numerelor mari ===&lt;br /&gt;
&lt;br /&gt;
Cum afișăm un număr mare? &lt;br /&gt;
&lt;br /&gt;
Afișând toate cifrele sale de la final de vector spre început. Avem însă un caz particular: numărul zero. Convenția de reprezentare a unui număr mare este ca prima lui cifră (cea mai semnificativă) să fie nenulă. &lt;br /&gt;
&lt;br /&gt;
În cazul lui zero acest lucru nu se poate. De aceea numărul mare zero va fi reprezentat ca având zero cifre. De aceea, la afișare nu vom afișa nimic, deoarece vom încerca să afișăm zero cifre. Acesta este cazul particular la afișare, dacă numărul mare are zero cifre vom afișa zero. &lt;br /&gt;
&lt;br /&gt;
Iată codul de afișare a unui număr mare:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    fprintf( fout, &amp;quot;0\n&amp;quot; );&lt;br /&gt;
  else {&lt;br /&gt;
    for ( i = n; i &amp;gt; 0; i-- )&lt;br /&gt;
      fputc( &#039;0&#039; + v[i-1], fout );&lt;br /&gt;
    fputc( &#039;\n&#039;, fout );&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Adunarea unui număr mic la unul mare ===&lt;br /&gt;
&lt;br /&gt;
Fie &amp;lt;code&amp;gt;v&amp;lt;/code&amp;gt; un număr mare cu &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; cifre. Dorim să adunăm la el un număr &#039;&#039;mic&#039;&#039;, &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Pentru aceasta vom proceda similar cu procedura de adunare &#039;&#039;de mână&#039;&#039;: vom aduna a la prima cifră a numărului, vom stoca pe prima poziție ultima cifră a acestui număr, apoi vom calcula transportul prin împărțire la zece. Acest transport se adună la a doua cifră, stocând ultima cifră a rezultatului pe a doua poziție și calculând noul transport prin împărțire la zece. &lt;br /&gt;
&lt;br /&gt;
Diferența față de adunarea &#039;&#039;de mână&#039;&#039; este că transportul poate fi mai mare decât 9. Iată secvența de cod care adună numărul mare &amp;lt;code&amp;gt;(n, v)&amp;lt;/code&amp;gt; cu numărul &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += v[i];&lt;br /&gt;
    v[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
  if ( i &amp;gt; n )&lt;br /&gt;
    n = i;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Scăderea unui număr mic din unul mare ===&lt;br /&gt;
&lt;br /&gt;
Scăderea unui număr mic din unul mare este similară cu adunarea. &lt;br /&gt;
&lt;br /&gt;
Transportul va fi negativ. Trebuie însă să fim atenți la un lucru: operația &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt; (modulo) în C nu se comportă ca la matematică. La matematică restul împărțirii a două numere este pozitiv sau zero. În C restul împărțirii unui număr negativ la unul pozitiv este negativ sau zero. De aceea codul trebuie modificat față de adunare pentru a calcula restul pozitiv al împărțirii la zece. &lt;br /&gt;
&lt;br /&gt;
De asemenea trebuie să avem grijă să scădem numărul de cifre, dacă este cazul. Vom păstra în &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; poziția ultimei cifre diferite de zero. Iată secvența de cod care scade numărul mic a din numărul mare &amp;lt;code&amp;gt;(n, v)&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  a = -a;&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;lt; 0 ) {&lt;br /&gt;
    a += v[i];&lt;br /&gt;
    v[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    if ( v[i] &amp;lt; 0 ) {&lt;br /&gt;
      v[i] += 10;&lt;br /&gt;
      a--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
  // ajustam numarul de cifre, deoarece primele cifre pot fi zero&lt;br /&gt;
  while ( n &amp;gt; 0 &amp;amp;&amp;amp; v[n-1] == 0 )&lt;br /&gt;
    n--;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție&#039;&#039;&#039;: programul de mai sus presupune că &#039;&#039;&#039;numărul mic este mai mic sau egal cu numărul mare&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Adunarea a două numere mari ===&lt;br /&gt;
&lt;br /&gt;
Adunarea a două numere mari este similară cu adunarea unui număr mare cu unul mic. &lt;br /&gt;
&lt;br /&gt;
Vom folosi un transport și vom aduna cifră cu cifră cele două numere mari. În final vom testa dacă mai avem transport. Dacă avem el este maxim o cifră (demonstrați). Iată secvența de cod care adună numărul mare &amp;lt;code&amp;gt;(n2, v2)&amp;lt;/code&amp;gt; la &amp;lt;code&amp;gt;(n1, v1)&amp;lt;/code&amp;gt; depunând rezultatul tot în &amp;lt;code&amp;gt;(n1, v1)&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  n1 = n1 &amp;lt; n2 ? n2 : n1;&lt;br /&gt;
  t = 0;&lt;br /&gt;
  for ( i = 0; i &amp;lt; n1; i++ ) {&lt;br /&gt;
    t = t + v1[i] + v2[i];&lt;br /&gt;
    v1[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
  }&lt;br /&gt;
  if ( t &amp;gt; 0 ) {&lt;br /&gt;
    v1[i] = t;&lt;br /&gt;
    n1++;&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Scăderea a două numere mari ===&lt;br /&gt;
&lt;br /&gt;
Scăderea a două numere mari este similară cu scăderea unui număr mare din unul mic. &lt;br /&gt;
&lt;br /&gt;
Vom folosi un transport negativ și vom scădea cifră cu cifră cele două numere mari. După ce se termină cifrele numărului de scăzut vom continua scăderea până ce transportul devine zero. &lt;br /&gt;
&lt;br /&gt;
Ca și la scăderea unui număr mic va trebui să avem grijă să scădem numărul de cifre dacă este cazul. Iată secvența de cod care scade numărul mare &amp;lt;code&amp;gt;(n2, v2)&amp;lt;/code&amp;gt; din numărul mare &amp;lt;code&amp;gt;(n1, v1)&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n2 || t &amp;lt; 0 ) {&lt;br /&gt;
    t = t + v1[i] - v2[i];&lt;br /&gt;
    v1[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    if ( v1[i] &amp;lt; 0 ) {&lt;br /&gt;
      v1[i] += 10;&lt;br /&gt;
      t--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
  // ajustam numarul de cifre, deoarece primele cifre pot fi zero&lt;br /&gt;
  while ( n1 &amp;gt; 0 &amp;amp;&amp;amp; v1[n1-1] == 0 )&lt;br /&gt;
    n1--;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Înmulțirea unui număr mare cu unul mic ===&lt;br /&gt;
&lt;br /&gt;
Înmulțirea unui număr mare cu unul mic este similară cu adunarea unui număr mare cu unul mic. &lt;br /&gt;
&lt;br /&gt;
Vom folosi un transport la care adunăm cifra curentă înmulțită cu numărul mic. Ultima cifră a transportului se stochează în cifra curentă, iar transportul se împarte la zece, apoi trecem la cifra următoare. După ce se termină cifrele numărului mare vom continua procedura până ce transportul devine zero. Trebuie să avem grijă să actualizăm numărul de cifre dacă este cazul. &lt;br /&gt;
&lt;br /&gt;
Iată secvența de cod care înmulțește numărul mic &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; cu numărul mare &amp;lt;code&amp;gt;(n, v)&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n || t &amp;gt; 0 ) {&lt;br /&gt;
    t = t + a * v[i];&lt;br /&gt;
    v[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n )&lt;br /&gt;
    n = i;&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n )&lt;br /&gt;
    n = i;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Împărțirea unui număr mare la unul mic ===&lt;br /&gt;
&lt;br /&gt;
Împărțirea unui număr mare la unul mic se face invers față de celelalte operații de până acum. &lt;br /&gt;
&lt;br /&gt;
Vom parcurge numărul mare de la cea mai semnificativă cifră (cifra cu indice &amp;lt;code&amp;gt;n - 1&amp;lt;/code&amp;gt;) către cifra de indice zero (prima din vector). Vom avea un rest curent; vom adăuga fiecare cifră a numărului mare la coada restului, înmulțind restul cu zece și adunând cifra. Împărțind restul curent la numărul mic obținem încă o cifră din cât, pe care o putem suprascrie peste cifra din numărul mare. La final vom avea câtul în numărul mare, iar ultimul rest este chiar restul împărțirii. &lt;br /&gt;
&lt;br /&gt;
Este posibil câtul să aibă mai puține cifre decât numărul original, drept pentru care va trebui să reducem numărul de cifre căutând prima sa cifră nenulă. &lt;br /&gt;
&lt;br /&gt;
Iată secvența de cod care împarte numărul mare &amp;lt;code&amp;gt;(n, v)&amp;lt;/code&amp;gt; la numărul mic &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
  t = 0;&lt;br /&gt;
  for ( i = n - 1; i &amp;gt;= 0; i-- ) {&lt;br /&gt;
    t = t * 10 + v[i];&lt;br /&gt;
    v[i] = t / a;&lt;br /&gt;
    t %= a;&lt;br /&gt;
  }&lt;br /&gt;
  // ajustam numarul de cifre, deoarece primele cifre pot fi zero&lt;br /&gt;
  while ( n &amp;gt; 0 &amp;amp;&amp;amp; v[n-1] == 0 )&lt;br /&gt;
    n--;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Exponențiere rapidă ==&lt;br /&gt;
&lt;br /&gt;
Calculați &amp;lt;code&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;&amp;lt;/code&amp;gt; în mod cât mai eficient (&amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; numere naturale). Problema este cunoscută şi sub numele de ridicare la putere în timp logaritmic. Ideea din spatele acestei rezolvări este următoarea:&lt;br /&gt;
&lt;br /&gt;
* Dacă &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este par, atunci &amp;lt;code&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; = a&amp;lt;sup&amp;gt;2*n/2&amp;lt;/sup&amp;gt; = (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;n/2&amp;lt;/sup&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* Dacă &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este impar, atunci &amp;lt;code&amp;gt;n-1&amp;lt;/code&amp;gt; este par și avem &amp;lt;code&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; = a * a&amp;lt;sup&amp;gt;n-1&amp;lt;/sup&amp;gt; = a * a&amp;lt;sup&amp;gt;2*(n-1)/2&amp;lt;/sup&amp;gt; = a * (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;(n-1)/2&amp;lt;/sup&amp;gt; = a * (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;n/2&amp;lt;/sup&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
În formulele de mai sus am considerat că &amp;lt;code&amp;gt;/&amp;lt;/code&amp;gt; este împărțirea întreagă din limbajul C. De aceea atunci când &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este impar &amp;lt;code&amp;gt;(n-1)/2&amp;lt;/code&amp;gt; este totuna cu &amp;lt;code&amp;gt;n/2&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Se observă că indiferent de paritatea lui &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;, la fiecare pas al iterației putem transforma &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; în &amp;lt;code&amp;gt;a * a&amp;lt;/code&amp;gt; și apoi putem împărți &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; la 2. Doar în cazurile când &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este impar vom acumula valoarea curentă a lui &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; la produsul calculat. &lt;br /&gt;
&lt;br /&gt;
Iată soluția bazată pe această idee:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
    int a, n;&lt;br /&gt;
    scanf(&amp;quot;%d%d&amp;quot;, &amp;amp;a, &amp;amp;n);&lt;br /&gt;
&lt;br /&gt;
    int p = 1;&lt;br /&gt;
    while (n &amp;gt; 0) {&lt;br /&gt;
        if (n % 2 == 1)&lt;br /&gt;
            p = p * a;&lt;br /&gt;
        a = a * a;&lt;br /&gt;
        n = n / 2;&lt;br /&gt;
    }&lt;br /&gt;
    printf(&amp;quot;%d&amp;quot;, p);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm? De câte ori se va executa bucla &amp;lt;code&amp;gt;while&amp;lt;/code&amp;gt;? La fiecare pas al buclei &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; se împarte la doi. &lt;br /&gt;
&lt;br /&gt;
De câte ori putem împărți un număr &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; la doi, înainte ca el să devină zero? Să considerăm cel mai mic &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; astfel încât &amp;lt;code&amp;gt;n≤2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt;&amp;lt;/code&amp;gt;. Este destul de clar că bucla noastră while se va executa de &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; ori, deci complexitatea algoritmului este &#039;&#039;O(k)&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Dar, precum am menționat la exercițiul anterior, dacă avem &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; astfel încât &amp;lt;code&amp;gt;2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt;=n&amp;lt;/code&amp;gt;, atunci &#039;&#039;k=O(log n)&#039;&#039;, deci complexitatea acestei soluții va fi logaritmică, &#039;&#039;O(log n)&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
Ce înseamnă acest lucru? Că timpul necesar calculului este proporțional cu un număr &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;, unde &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; este exponentul lui 2 astfel încât &amp;lt;code&amp;gt;n≤2&amp;lt;sup&amp;gt;k&amp;lt;/sup&amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Element majoritar ==&lt;br /&gt;
&lt;br /&gt;
[https://www.nerdarena.ro/problema/majoritar Majoritar]: dat un vector cu n elemente să se spună dacă conține un element majoritar. Un element majoritar este un element care apare de cel puțin &amp;lt;code&amp;gt;n/2 + 1&amp;lt;/code&amp;gt; ori. Încercați să dați o soluție mai bună decât sortarea.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Răspuns&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Dacă există un element majoritar, &amp;lt;code&amp;gt;maj&amp;lt;/code&amp;gt;, rezultă că fiecare din elementele vectorului diferite de &amp;lt;code&amp;gt;maj&amp;lt;/code&amp;gt; poate fi cuplat cu un element egal cu &amp;lt;code&amp;gt;maj&amp;lt;/code&amp;gt; astfel încât după ce toate cuplările au fost făcute să rămână cel puțin un element &amp;lt;code&amp;gt;maj&amp;lt;/code&amp;gt; necuplat. Ideea de rezolvare este să simulăm această cuplare. Vom porni cu primul element al vectorului ca un candidat de element majoritar. Parcurgem elementele vectorului ținând minte câți candidați necuplați avem. De fiecare dată când dăm peste un element egal cu candidatul incrementăm numărul de elemente necuplate. Când dăm peste un element diferit scădem numărul de elemente necuplate. Dacă la un moment dat vom avea zero candidați necuplați și mai vine un element diferit de candidat rezultă că am eliminat din vector un număr egal de candidați și non-candidați, drept pentru care putem reporni problema pentru vectorul rămas: vom considera elementul curent drept candidat, cu o apariție.&lt;br /&gt;
&lt;br /&gt;
La final vom rămâne cu un candidat ce trebuie acum verificat: numărăm de câte ori apare el in vector printr-o parcurgere. Dacă apare de măcar &amp;lt;code&amp;gt;n/2+1&amp;lt;/code&amp;gt; ori am găsit elementul majoritar, altfel el nu există. &lt;br /&gt;
&lt;br /&gt;
Iată o implementare posibilă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int v[3000000];&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, i, nr, maj;&lt;br /&gt;
&lt;br /&gt;
  // citire vector&lt;br /&gt;
  fin = fopen( &amp;quot;majoritar.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;v[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // cautam candidatul la element majoritar&lt;br /&gt;
  maj = v[0];&lt;br /&gt;
  nr = 1;&lt;br /&gt;
  for ( i = 1; i &amp;lt; n; i++ )&lt;br /&gt;
    if ( v[i] == maj ) // cita vreme avem maj incrementam numarul de maj gasite&lt;br /&gt;
      nr++;&lt;br /&gt;
    else {&lt;br /&gt;
      nr--;            // altfel decrementam numarul de maj&lt;br /&gt;
      if ( nr &amp;lt; 0 ) {  // daca am scazut sub zero&lt;br /&gt;
        maj = v[i];    // alegem drept candidat elementul curent&lt;br /&gt;
        nr = 1;        // care apare o data&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
  // verificare candidat la element majoritar&lt;br /&gt;
  nr = 0;&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ ) // numaram de cit ori apare maj in vector&lt;br /&gt;
    if ( v[i] == maj )&lt;br /&gt;
      nr++;&lt;br /&gt;
&lt;br /&gt;
  fout = fopen ( &amp;quot;majoritar.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  if ( nr &amp;gt; n / 2 ) // daca maj apare de mai mult de n/2 ori este majoritar&lt;br /&gt;
    fprintf( fout, &amp;quot;%d %d\n&amp;quot;, maj, nr );&lt;br /&gt;
  else&lt;br /&gt;
    fprintf( fout, &amp;quot;-1\n&amp;quot; );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Timpul este evident liniar, &#039;&#039;O(n)&#039;&#039;. Dar memoria?&lt;br /&gt;
&lt;br /&gt;
Memoria este și ea &#039;&#039;O(n)&#039;&#039;. Există o mică confuzie aici, putem avea senzația că nu este nevoie să memorăm elementele parcurse. De fapt trebuie să le memorăm deoarece avem nevoie de ele la pasul doi, cel în care verificăm candidatul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Temă 11 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/fib Fib] problemă de adunare a două numere mari&lt;br /&gt;
* [https://www.nerdarena.ro/problema/anaf ANAF] problemă de operații între numere mari și numere mici&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Temă opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cod Cod] dată la ONI 2016 clasa a 6-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php/Clasa_a_7-a_Lec%C8%9Bia_11:_Numere_mari,_exponen%C8%9Biere_rapid%C4%83,_element_majoritar Accesează rezolvarea temei 11]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_10:_Fill_recursiv_(flood_fill)&amp;diff=18572</id>
		<title>Clasa a 7-a Lecția 10: Fill recursiv (flood fill)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_10:_Fill_recursiv_(flood_fill)&amp;diff=18572"/>
		<updated>2026-02-06T16:06:58Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/EB97GkmjVgQ&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Despre flood fill ==&lt;br /&gt;
&lt;br /&gt;
Algoritmul fill &#039;&#039;umple&#039;&#039; toate golurile accesibile de la un punct dat. &lt;br /&gt;
&lt;br /&gt;
Golurile pot fi elemente zero într-o matrice, de exemplu, iar vecinii pot fi definiți ca elementele adiacente pe linie și coloană (acesta este cazul cel mai întâlnit). Alteori vecinii pot fi definiți ca având un punct comun cu elementul curent, ceea ce include și elementele alăturate pe diagonală. &lt;br /&gt;
&lt;br /&gt;
Să luăm un exemplu clasic:&lt;br /&gt;
&lt;br /&gt;
Se dă o matrice &amp;lt;code&amp;gt;a[m][n]&amp;lt;/code&amp;gt; cu elemente 0 și 1, 0 semnificând liber și 1 semnificând zid, precum și o poziție în matrice, &#039;&#039;(l, c)&#039;&#039;. Să se spună câte elemente 0 sunt accesibile de la &#039;&#039;(l, c)&#039;&#039; avansând doar pe linie sau pe coloană.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int fill( int l, int c ) {          // umple de la coloana l, c&lt;br /&gt;
  int suma = 1;                     // contorizam punctul curent&lt;br /&gt;
&lt;br /&gt;
  a[l][c] = 1;                      // marcam pozitia ca parcursa&lt;br /&gt;
  if ( l &amp;gt; 0 &amp;amp;&amp;amp; a[l-1][c] == 0 )    // ne deplasam in sus&lt;br /&gt;
    suma += fill( l - 1, c );&lt;br /&gt;
  if ( l &amp;lt; m-1 &amp;amp;&amp;amp; a[l+1][c] == 0  ) // ne deplasam in jos&lt;br /&gt;
    suma += fill( l + 1, c );&lt;br /&gt;
  if ( c &amp;gt; 0 &amp;amp;&amp;amp; a[l][c-1] == 0  )   // ne deplasam spre stinga&lt;br /&gt;
    suma += fill( l, c - 1 );&lt;br /&gt;
  if ( c &amp;lt; n-1 &amp;amp;&amp;amp; a[l][c+1] == 0  ) // ne deplasam spre dreapta&lt;br /&gt;
    suma += fill( l, c + 1 );&lt;br /&gt;
&lt;br /&gt;
  return suma;&lt;br /&gt;
}&lt;br /&gt;
...&lt;br /&gt;
int main() {&lt;br /&gt;
  int suma;&lt;br /&gt;
  ...&lt;br /&gt;
  suma = 0;&lt;br /&gt;
  if ( a[l][c] == 0 )&lt;br /&gt;
    suma = fill( l, c );&lt;br /&gt;
  printf( &amp;quot;%d&amp;quot;, suma );&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Ordinea direcțiilor nu contează. Cele patru apeluri recursive pot fi în orice ordine.&lt;br /&gt;
* Vecinii pot fi în număr de 4 - cei adiacenți pe linie și pe coloană, ca în exemplul de mai sus, sau pot fi în număr de 8 - și cei adiacenți pe diagonale.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(m·n)&#039;&#039; ca timp, deoarece poate parcurge toate elementele matricei, iar prelucrarea per element este &#039;&#039;O(1)&#039;&#039;. Memoria ocupată este de asemenea &#039;&#039;O(m·n)&#039;&#039; deoarece fiecare apel recursiv necesită &#039;&#039;O(1)&#039;&#039; memorie și putem avea &#039;&#039;O(m·n)&#039;&#039; apeluri recursive unul în altul.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție!&#039;&#039;&#039; Acesta este principalul impediment al metodei! Ea folosește foarte multă memorie suplimentară.&lt;br /&gt;
&lt;br /&gt;
== Exemplificare grafică ==&lt;br /&gt;
&lt;br /&gt;
Să vedem niște vizualizări ale algoritmului flood fill preluate de la wikipedia: [https://en.wikipedia.org/wiki/Flood_fill algoritm flood fill].&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot; style=&amp;quot;text-align: center;&amp;quot;&lt;br /&gt;
| [[File:Recursive_Flood_Fill_4_wikipedia.gif|thumb|center|200px|Flood fill cu 4 direcții]]&lt;br /&gt;
| [[File:Recursive_Flood_Fill_8_wikipedia.gif|thumb|center|200px|Flood fill cu 8 direcții]]&lt;br /&gt;
| [[File:Wfm_floodfill_animation_stack_wikipedia.gif|thumb|center|200px|Exemplu mare de flood fill cu 4 direcții]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Optimizări ale algoritmului ==&lt;br /&gt;
&lt;br /&gt;
=== Bordare matrice ===&lt;br /&gt;
&lt;br /&gt;
O primă optimizare de timp este clasică. Pentru a face programul atât mai rapid cât și mai scurt putem borda matricea de jur împrejur cu elementul zid, în cazul nostru 1. Astfel vom scuti testul de ieșire a coordonatelor din matrice. &lt;br /&gt;
&lt;br /&gt;
Funcția se simplifică:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int fill( int l, int c ) {    // umple de la coloana l, c&lt;br /&gt;
  int suma = 1;               // contorizam punctul curent&lt;br /&gt;
&lt;br /&gt;
  a[l][c] = 1;                // marcam pozitia ca parcursa&lt;br /&gt;
  if ( a[l-1][c] == 0 )       // ne deplasam in sus&lt;br /&gt;
    suma += fill( l - 1, c );&lt;br /&gt;
  if ( a[l+1][c] == 0  )      // ne deplasam in jos&lt;br /&gt;
    suma += fill( l + 1, c );&lt;br /&gt;
  if ( a[l][c-1] == 0  )      // ne deplasam spre stinga&lt;br /&gt;
    suma += fill( l, c - 1 );&lt;br /&gt;
  if ( a[l][c+1] == 0  )      // ne deplasam spre dreapta&lt;br /&gt;
    suma += fill( l, c + 1 );&lt;br /&gt;
&lt;br /&gt;
  return suma;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Reducere memorie folosită ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Cum calculăm memoria ocupată? &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Țineți minte: fiecare apel recursiv ocupă memorie echivalentul unui int. Pe un calculator de 32 de biți, cum sunt cele pe care lucrăm noi (ele sunt de fapt de 64 de biți, dar programele sunt compilate în modul 32 de biți), aceasta înseamnă patru octeți. &lt;br /&gt;
&lt;br /&gt;
Aceasta este adresa de întoarcere din funcție. Alte elemente care ocupă memorie pe stivă sunt parametrii transmiși și variabilele declarate în funcție. În cazul nostru vom avea 16 octeți cei doi parametri, variabila declarată în funcție și adresa de întoarcere din funcție.&lt;br /&gt;
&lt;br /&gt;
Dacă micșorarea memoriei folosite este esențială putem elimina 12 octeți din 16, declarând variabilele &amp;lt;code&amp;gt;l&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;suma&amp;lt;/code&amp;gt; globale, astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int l, c, suma;       // variabile globale&lt;br /&gt;
...&lt;br /&gt;
void fill() {&lt;br /&gt;
  suma++;&lt;br /&gt;
  a[l][c] = 1;&lt;br /&gt;
  l--;&lt;br /&gt;
  if ( a[l][c] == 0 ) // apelam pentru (l - 1, c)&lt;br /&gt;
    fill();&lt;br /&gt;
  l++;&lt;br /&gt;
  l++;&lt;br /&gt;
  if ( a[l][c] == 0 ) // apelam pentru (l + 1, c)&lt;br /&gt;
    fill();&lt;br /&gt;
  l--;&lt;br /&gt;
  c--;&lt;br /&gt;
  if ( a[l][c] == 0 ) // apelam pentru (l, c - 1)&lt;br /&gt;
    fill();&lt;br /&gt;
  c++;&lt;br /&gt;
  c++;&lt;br /&gt;
  if ( a[l][c] == 0 ) // apelam pentru (l, c + 1)&lt;br /&gt;
    fill();&lt;br /&gt;
  c--;                // acum (l, c) sunt cele de la inceput&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Este o modificare urâtă, care face codul nelizibil, dar uneori necesară la olimpiadă. Nu uitați că chiar și în această formă algoritmul va folosi 4 octeți pe apel recursiv imbricat pentru adresa de revenire din funcție.&lt;br /&gt;
&lt;br /&gt;
Observație: variabilele &amp;lt;code&amp;gt;l&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; fiind globale ele trebuie să aibă aceeași valoare la ieșirea din funcție ca și la intrarea în funcție. De aceea ultima instrucțiune &amp;lt;code&amp;gt;c--;&amp;lt;/code&amp;gt; este importantă!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Aplicații ce folosesc flood fill și concluzii ==&lt;br /&gt;
&lt;br /&gt;
=== Găsire ieșire din labirint ===&lt;br /&gt;
&lt;br /&gt;
Folosind flood fill putem găsi un punct de ieșire dintr-un labirint memorat într-o matrice. Să presupunem că elementele &#039;&#039;0&#039;&#039; înseamnă loc gol și cele &#039;&#039;1&#039;&#039; înseamnă zid. Ieșirile sunt puncte &#039;&#039;0&#039;&#039; pe marginile matricei. Dat un punct din interiorul matricei, există un drum spre ieșire? Dacă da, putem găsi atât coordonatele ieșirii cât și drumul parcurs.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de program care afișează coordonatele ieșirii:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int fill( int l, int c ) {&lt;br /&gt;
  int retval;&lt;br /&gt;
&lt;br /&gt;
  if ( lab[l][c] == BORDER ) // am ajuns la margine?&lt;br /&gt;
    return l * MAXN + c;     // returnam (l, c)&lt;br /&gt;
    &lt;br /&gt;
  lab[l][c] = 1;                   // marcam punctul curent ca vizitat&lt;br /&gt;
  retval = 0;                      // 0 inseamna ca nu am gasit iesire&lt;br /&gt;
  if ( lab[l + 1][c] != 1 )        // daca nu este zid&lt;br /&gt;
    retval = fill(l + 1, c);       // cautam in jos&lt;br /&gt;
  if ( retval == 0 ) {             // daca nu am gasit iesire&lt;br /&gt;
    if ( lab[l - 1][c] != 1 )      // si nu este zid&lt;br /&gt;
      retval = fill(l - 1, c);     // cautam in sus&lt;br /&gt;
    if ( retval == 0 ) {           // daca nu am gasit iesire&lt;br /&gt;
      if ( lab[l][c + 1] != 1 )    // si nu este zid&lt;br /&gt;
        retval = fill(l, c + 1);   // cautam in dreapta&lt;br /&gt;
      if ( retval == 0 )           // daca nu am gasit iesire&lt;br /&gt;
        if ( lab[l][c - 1] != 1 )  // si nu este zid&lt;br /&gt;
          retval = fill(l, c - 1); // cautam in stinga&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return retval;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, l, c, ls, cs, p;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;iesire.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d &amp;quot;, &amp;amp;n, &amp;amp;ls, &amp;amp;cs );&lt;br /&gt;
  for ( l = 0; l &amp;lt; n; l++ ) {&lt;br /&gt;
    for ( c = 0; c &amp;lt; n; c++ )     // citim linia l a matricei&lt;br /&gt;
      lab[l][c] = fgetc( fin ) - &#039;0&#039;;&lt;br /&gt;
    fgetc( fin ); // citim &#039;\n&#039;&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  for ( c = 0; c &amp;lt; n; c++ ) { // bordare conditionala margini&lt;br /&gt;
    if ( lab[0][c] == 0 )&lt;br /&gt;
      lab[0][c] = BORDER; &lt;br /&gt;
    if ( lab[n - 1][c] == 0 )&lt;br /&gt;
      lab[n - 1][c] = BORDER;&lt;br /&gt;
    if ( lab[c][0] == 0 )&lt;br /&gt;
      lab[c][0] = BORDER;&lt;br /&gt;
    if ( lab[c][n - 1] == 0 )&lt;br /&gt;
      lab[c][n - 1] = BORDER;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  p = fill( ls - 1, cs - 1 );&lt;br /&gt;
  &lt;br /&gt;
  fout = fopen( &amp;quot;iesire.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  if ( p &amp;gt; 0 )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d %d\n&amp;quot;, 1 + p / MAXN, 1 + p % MAXN );&lt;br /&gt;
  else&lt;br /&gt;
    fprintf( fout, &amp;quot;-1\n&amp;quot; );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Arie goluri ===&lt;br /&gt;
&lt;br /&gt;
Calcul arie goluri: dorim să aflăm numărul de elemente zero accesibile din punctul inițial.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de program care afișează aria golului accesibil din punctul inițial:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 1000&lt;br /&gt;
#define BORDER 2&lt;br /&gt;
&lt;br /&gt;
char lab[MAXN + 2][MAXN + 2]; // matricea labirint&lt;br /&gt;
&lt;br /&gt;
// Executa flood fill de la coordonate (l, c)&lt;br /&gt;
// returneaza aria zonei din care se porneste fill (cite puncte 0)&lt;br /&gt;
int fill( int l, int c ) {&lt;br /&gt;
  int aria;&lt;br /&gt;
&lt;br /&gt;
  lab[l][c] = 1;                   // marcam punctul curent ca vizitat&lt;br /&gt;
  aria = 1;                        // contorizam punctul curent&lt;br /&gt;
  if ( lab[l + 1][c] == 0 )        // daca nu este zid&lt;br /&gt;
    aria += fill(l + 1, c);        // cautam in jos&lt;br /&gt;
  if ( lab[l - 1][c] == 0 )        // daca nu este zid&lt;br /&gt;
    aria += fill(l - 1, c);        // cautam in sus&lt;br /&gt;
  if ( lab[l][c + 1] == 0 )        // daca nu este zid&lt;br /&gt;
    aria += fill(l, c + 1);        // cautam in dreapta&lt;br /&gt;
  if ( lab[l][c - 1] == 0 )        // daca nu este zid&lt;br /&gt;
    aria += fill(l, c - 1);        // cautam in stinga&lt;br /&gt;
&lt;br /&gt;
  return aria;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, l, c, ls, cs, a;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;arie.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d &amp;quot;, &amp;amp;n, &amp;amp;ls, &amp;amp;cs );&lt;br /&gt;
  for ( l = 1; l &amp;lt;= n; l++ ) {&lt;br /&gt;
    for ( c = 1; c &amp;lt;= n; c++ )  // citim linia l a matricei&lt;br /&gt;
      lab[l][c] = fgetc( fin ) - &#039;0&#039;;&lt;br /&gt;
    fgetc( fin ); // citim &#039;\n&#039;&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  for ( c = 1; c &amp;lt;= n; c++ ) // bordare margini&lt;br /&gt;
    lab[0][c] = lab[n + 1][c] = lab[c][0] = lab[c][n + 1] = BORDER;&lt;br /&gt;
&lt;br /&gt;
  a = fill( ls, cs ); // calcul arie accesibila din (ls, cs)&lt;br /&gt;
  &lt;br /&gt;
  fout = fopen( &amp;quot;arie.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;%d\n&amp;quot;, a );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Număr goluri ===&lt;br /&gt;
&lt;br /&gt;
Calcul număr de goluri: denumite și componente conexe, putem folosi fill pentru a număra câte goluri disjuncte există (astfel încât să nu se poată ajunge de la un gol la altul). Pentru aceasta vom parcurge matricea în orice ordine și vom porni câte un fill din fiecare loc unde găsim un element 0.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de program:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 1000&lt;br /&gt;
#define BORDER 2&lt;br /&gt;
&lt;br /&gt;
char lab[MAXN + 2][MAXN + 2]; // matricea labirint&lt;br /&gt;
&lt;br /&gt;
// Executa flood fill de la coordonate (l, c)&lt;br /&gt;
void fill( int l, int c ) {&lt;br /&gt;
  lab[l][c] = 1;            // marcam punctul curent ca vizitat&lt;br /&gt;
  if ( lab[l + 1][c] == 0 ) // daca nu este zid&lt;br /&gt;
    fill(l + 1, c);         // cautam in jos&lt;br /&gt;
  if ( lab[l - 1][c] == 0 ) // daca nu este zid&lt;br /&gt;
    fill(l - 1, c);         // cautam in sus&lt;br /&gt;
  if ( lab[l][c + 1] == 0 ) // daca nu este zid&lt;br /&gt;
    fill(l, c + 1);         // cautam in dreapta&lt;br /&gt;
  if ( lab[l][c - 1] == 0 ) // daca nu este zid&lt;br /&gt;
    fill(l, c - 1);         // cautam in stinga&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, l, c, ls, cs, nr;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;nrgol.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d &amp;quot;, &amp;amp;n, &amp;amp;ls, &amp;amp;cs );&lt;br /&gt;
  for ( l = 1; l &amp;lt;= n; l++ ) {&lt;br /&gt;
    for ( c = 1; c &amp;lt;= n; c++ )  // citim linia l a matricei&lt;br /&gt;
      lab[l][c] = fgetc( fin ) - &#039;0&#039;;&lt;br /&gt;
    fgetc( fin ); // citim &#039;\n&#039;&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  for ( c = 1; c &amp;lt;= n; c++ ) // bordare margini&lt;br /&gt;
    lab[0][c] = lab[n + 1][c] = lab[c][0] = lab[c][n + 1] = BORDER;&lt;br /&gt;
&lt;br /&gt;
  nr = 0;&lt;br /&gt;
  for ( l = 1; l &amp;lt;= n; l++ )&lt;br /&gt;
    for ( c = 1; c &amp;lt;= n; c++ ) // pentru fiecare punct din labirint&lt;br /&gt;
      if ( lab[l][c] == 0 ) {  // daca nu este zid si nu a fost umplut deja&lt;br /&gt;
        nr++;                  // contorizam un nou gol&lt;br /&gt;
        fill( l, c );          // umplem zona&lt;br /&gt;
      }&lt;br /&gt;
        &lt;br /&gt;
  fout = fopen( &amp;quot;nrgol.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;%d\n&amp;quot;, nr );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Perimetru goluri ===&lt;br /&gt;
&lt;br /&gt;
Calcul perimetru goluri: în locul ariei golurilor putem calcula perimetrul lor, anume numărul de puncte din goluri care se învecinează cu un punct care nu face parte din gol. Trebuie să avem grijă când testăm dacă un punct este pe perimetru.&lt;br /&gt;
&lt;br /&gt;
Un punct se află pe perimetru dacă:&lt;br /&gt;
&lt;br /&gt;
* Unul din vecinii săi este zid,&lt;br /&gt;
* Sau unul din vecinii săi este margine bordată.&lt;br /&gt;
&lt;br /&gt;
Un punct &#039;&#039;&#039;nu neapărat se află pe perimetru&#039;&#039;&#039; dacă:&lt;br /&gt;
&lt;br /&gt;
* Unul din vecinii săi este un alt punct parcurs anterior.&lt;br /&gt;
&lt;br /&gt;
Cum ne dăm seama dacă vecinul este margine, zid, sau punct anterior? &lt;br /&gt;
&lt;br /&gt;
Vom umple golurile cu un număr distinct. În exemplul de mai jos marginile și zidurile sunt codificate cu 1, iar umplerea se face cu 2.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de program care afișează perimetrul golului accesibil din punctul inițial:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 1000&lt;br /&gt;
#define BORDER 1&lt;br /&gt;
#define FILL 2&lt;br /&gt;
&lt;br /&gt;
char lab[MAXN + 2][MAXN + 2]; // matricea labirint&lt;br /&gt;
&lt;br /&gt;
// Executa flood fill de la coordonate (l, c)&lt;br /&gt;
// returneaza perimetrul zonei din care se porneste fill (cite puncte 0)&lt;br /&gt;
int fill( int l, int c ) {&lt;br /&gt;
  int perimetrul;&lt;br /&gt;
&lt;br /&gt;
  lab[l][c] = FILL;               // marcam punctul curent ca vizitat&lt;br /&gt;
  perimetrul = lab[l - 1][c] == 1 // verificam daca punctul curent este pe&lt;br /&gt;
    || lab[l + 1][c] == 1         // perimetru&lt;br /&gt;
    || lab[l][c - 1] == 1&lt;br /&gt;
    || lab[l][c + 1] == 1;        // daca da, contorizam punctul curent&lt;br /&gt;
  if ( lab[l + 1][c] == 0 )       // daca nu este zid&lt;br /&gt;
    perimetrul += fill(l + 1, c); // cautam in jos&lt;br /&gt;
  if ( lab[l - 1][c] == 0 )       // daca nu este zid&lt;br /&gt;
    perimetrul += fill(l - 1, c); // cautam in sus&lt;br /&gt;
  if ( lab[l][c + 1] == 0 )       // daca nu este zid&lt;br /&gt;
    perimetrul += fill(l, c + 1); // cautam in dreapta&lt;br /&gt;
  if ( lab[l][c - 1] == 0 )       // daca nu este zid&lt;br /&gt;
    perimetrul += fill(l, c - 1); // cautam in stinga&lt;br /&gt;
&lt;br /&gt;
  return perimetrul;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, l, c, ls, cs, p;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;perimetru.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d &amp;quot;, &amp;amp;n, &amp;amp;ls, &amp;amp;cs );&lt;br /&gt;
  for ( l = 1; l &amp;lt;= n; l++ ) {&lt;br /&gt;
    for ( c = 1; c &amp;lt;= n; c++ )  // citim linia l a matricei&lt;br /&gt;
      lab[l][c] = fgetc( fin ) - &#039;0&#039;;&lt;br /&gt;
    fgetc( fin ); // citim &#039;\n&#039;&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  for ( c = 0; c &amp;lt; n; c++ ) // bordare margini&lt;br /&gt;
    lab[0][c] = lab[n + 1][c] = lab[c][0] = lab[c][n + 1] = BORDER;&lt;br /&gt;
&lt;br /&gt;
  p = fill( ls, cs ); // calcul perimetru accesibil din (ls, cs)&lt;br /&gt;
  &lt;br /&gt;
  fout = fopen( &amp;quot;perimetru.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;%d\n&amp;quot;, p );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Eliminare zid ===&lt;br /&gt;
&lt;br /&gt;
Ne propunem să eliminăm acea pătrațică zid care reduce la minim numărul de goluri.&lt;br /&gt;
&lt;br /&gt;
Cum procedăm?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție forță brută&#039;&#039;&#039;: o soluție forță brută ar fi să eliminăm pe rând fiecare bucată de zid și să numărăm golurile formate.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Deoarece numărul de ziduri poate fi &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; iar numărarea golurilor este și ea tot &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; rezultă o complexitate de &#039;&#039;O(n&amp;lt;sup&amp;gt;4&amp;lt;/sup&amp;gt;)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Se poate mai bine? Desigur.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție optim&#039;&#039;&#039;: soluția optimă este similară cu cea care numără golurile:&lt;br /&gt;
&lt;br /&gt;
* Umplem toate golurile.&lt;br /&gt;
* La fiecare gol umplem cu o valoare diferită de fill.&lt;br /&gt;
* La final parcurgem matricea căutând pătrățica zid care are ca vecini cele mai multe goluri diferite (eticheta diferită).&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
* Umplerea golurilor este &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;.&lt;br /&gt;
* Verificarea zidurilor este &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Timpul total va fi, deci, &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Concluzii ==&lt;br /&gt;
&lt;br /&gt;
Fill este o unealtă simplă și utilă, ușor de folosit la concursuri. Principalul ei neajuns este memoria ocupată. &lt;br /&gt;
&lt;br /&gt;
Ce facem în situația când matricea este prea mare și memoria disponibilă insuficientă pentru a folosi flood fill? &lt;br /&gt;
&lt;br /&gt;
În această situație putem folosi BFS fill (breadth first search), care, atunci când este aplicat pe o matrice se mai numește și algoritmul Lee. El folosește o coadă pentru a menține frontiera deja acoperită, reducând memoria la &#039;&#039;O(m+n)&#039;&#039; însă fiind ceva mai lent. &lt;br /&gt;
&lt;br /&gt;
Vom discuta acest algoritm într-o altă lecție.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Temă 10 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/oras Oraș] dată la .campion 2011&lt;br /&gt;
* [https://www.nerdarena.ro/problema/enclave Enclave] de exercițiu în flood fill&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Temă opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/ozn OZN] dată la barajul pentru Shumen din Tudor Vianu anul 2014, juniori&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_10:_Fill_recursiv_(flood_fill) Accesează rezolvarea temei 10]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_8:_Recursivitate_(2)&amp;diff=18571</id>
		<title>Clasa a 7-a Lecția 8: Recursivitate (2)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_8:_Recursivitate_(2)&amp;diff=18571"/>
		<updated>2026-02-06T16:03:03Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/yPXRdAuZv6w&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Exemple de funcții recursive ==&lt;br /&gt;
&lt;br /&gt;
Iată câteva probleme care se rezolvă ușor cu funcții recursive:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Palindrom ===&lt;br /&gt;
&lt;br /&gt;
Verificare palindrom: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int putere( int n ) {             // calculam recursiv cea mai mare&lt;br /&gt;
  int p = 1;                      // putere a lui 10 mai mica decit n&lt;br /&gt;
&lt;br /&gt;
  if ( n &amp;lt; 10 )                   // cand n are o singura cifra&lt;br /&gt;
    return 1;                     // cea mai mare putere este 1&lt;br /&gt;
  return 10 * putere( n / 10 );   // adaugam un zero si taiem o cifra&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int palindrom( int n, int p10 ) { // pe baza lui n si a puterii lui 10&lt;br /&gt;
  if ( n &amp;lt; 10 )                   // un numar de o cifra este palindrom&lt;br /&gt;
    return 1;&lt;br /&gt;
  if ( n / p10 != n % 10 )        // testam prima si ultima cifra&lt;br /&gt;
    return 0;&lt;br /&gt;
  return palindrom( n % p10 / 10, p10 / 100 ); // taie prima si ultima cifra&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, pow10;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  pow10 = putere( n );&lt;br /&gt;
  if ( palindrom( n, pow10 ) )&lt;br /&gt;
    printf( &amp;quot;%d este palindrom\n&amp;quot;, n );&lt;br /&gt;
  else&lt;br /&gt;
    printf( &amp;quot;%d nu este palindrom\n&amp;quot;, n );&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Permutări ===&lt;br /&gt;
&lt;br /&gt;
Dându-se &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; să se afișeze toate permutările mulțimii numerelor de la 1 la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; în ordine lexicografică. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int v[10];&lt;br /&gt;
char folosit[10];&lt;br /&gt;
&lt;br /&gt;
void perm( int n, int poz ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( poz &amp;gt;= n ) {             // daca am umplut n pozitii&lt;br /&gt;
    for ( i = 0; i &amp;lt; n; i++ )   // afiseaza combinarea curenta&lt;br /&gt;
      printf( &amp;quot;%d &amp;quot;, v[i] );&lt;br /&gt;
    printf( &amp;quot;\n&amp;quot; );&lt;br /&gt;
  } else&lt;br /&gt;
    for ( v[poz] = 1; v[poz] &amp;lt;= n; v[poz]++ )&lt;br /&gt;
      if ( !folosit[v[poz]] ) { // daca elementul curent nu e deja folosit&lt;br /&gt;
        folosit[v[poz]] = 1;    // marcheaza-l ca folosit&lt;br /&gt;
        perm( n, poz + 1 );     // genereaza restul permutarii&lt;br /&gt;
        folosit[v[poz]] = 0;    // demarcheaza elementul&lt;br /&gt;
      }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  perm( n, 0 );&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Calculul depășește nivelul acestei lecții. Dar putem vedea că este ineficient: pe măsură ce plasăm numere în vector și vectorul &amp;lt;code&amp;gt;folosit[]&amp;lt;/code&amp;gt; se &#039;&#039;umple&#039;&#039;, vom plasa pe poziția curentă tot mai puține numere, dar le vom parcurge pe toate &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;. Cum am putea îmbunătăți algoritmul? Cumva ar fi bine să parcurgem doar elementele pe care le vom plasa. Avem, deci, nevoie ca la fiecare poziție să avem o mulțime a numerelor rămase de plasat, mulțime pe care să o putem parcurge în &#039;&#039;O(n - poz)&#039;&#039; și nu în &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Avem două idei:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Permutări cu interschimbare (swap) ====&lt;br /&gt;
&lt;br /&gt;
Am putea să menținem acea mulțime chiar în vectorul &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt;. Convenția este că dacă am ajuns la poziția &amp;lt;code&amp;gt;poz&amp;lt;/code&amp;gt; am plasat deja &amp;lt;code&amp;gt;poz&amp;lt;/code&amp;gt; elemente, iar cifrele rămase for fi în vector la pozițiile &amp;lt;code&amp;gt;poz..n-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Vom scrie, la început, numerele de la 1 la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; în vectorul &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt;. Vrem să plasăm un element pe poziția &amp;lt;code&amp;gt;poz&amp;lt;/code&amp;gt;. El va fi ales de pe o poziția &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; care se află după &amp;lt;code&amp;gt;poz&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;i ≤ poz&amp;lt;/code&amp;gt;. Vom interschimba, deci, elementele aflate pe pozițiile &amp;lt;code&amp;gt;poz&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt;. Aceasta îmi garantează că elementele rămase pe pozițiile &amp;lt;code&amp;gt;poz+1..n-1&amp;lt;/code&amp;gt; sunt elemente încă nefolosite în permutare. Putem apela recursiv funcția. La revenire vom repune elementele în poziția originală, prin încă o interschimbare a elementelor la poziția &amp;lt;code&amp;gt;poz&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Avantajul acestei metode este clar, va parcurge doar elementele plasate. Are și vreun dezavantaj?&lt;br /&gt;
&lt;br /&gt;
Dezavantajul este că permutările nu vor fi generate în ordine lexicografică. În funcție de aplicație acest lucru s-ar putea să nu conteze.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Permutări cu liste ====&lt;br /&gt;
&lt;br /&gt;
Am putea să menținem elementele în ordinea lor crescătoare într-o listă. Atunci când folosim un element, plasându-l pe poziția poz, îl eliminăm din listă. Apoi reapelăm. La revenirea din apel plasăm elementul înapoi în listă și trecem la următorul element.&lt;br /&gt;
&lt;br /&gt;
Această metodă, implementată corect, este mai rapidă și generează și permutările în ordine lexicografică, deoarece elementele din listă sunt permanent în ordinea inițială.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Plată suma ===&lt;br /&gt;
&lt;br /&gt;
Moduri plată suma cu monede 1, 2, 5: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int mon[3] = { 1, 2, 5 };&lt;br /&gt;
&lt;br /&gt;
int suma( int s, int nrmon ) { // in cite feluri platim s folosind nrmon monede&lt;br /&gt;
  if ( nrmon == 0 )            // daca nu mai avem monede&lt;br /&gt;
    return 0;                  // nu putem plati (zero moduri de plata)&lt;br /&gt;
  if ( s &amp;lt; 0 )                 // o suma negativa nu se poate plati&lt;br /&gt;
    return 0;&lt;br /&gt;
  if ( s == 0 )                // o suma de zero se plateste intr-un singur fel&lt;br /&gt;
    return 1;&lt;br /&gt;
  // putem plati in doua feluri de combinatii&lt;br /&gt;
  // - fie in combinatii care se termina cu ultima moneda din vector&lt;br /&gt;
  // - fie in combinatii care nu folosesc deloc ultima moneda din vector&lt;br /&gt;
  return suma( s - mon[nrmon - 1], nrmon ) + suma( s, nrmon - 1 );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int s;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;s );&lt;br /&gt;
  printf( &amp;quot;%d este platibila in %d moduri\n&amp;quot;, s, suma( s, 3 ) );&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Memoizare (continuare) ==&lt;br /&gt;
&lt;br /&gt;
Anul trecut am vorbit despre [# memoizare]. Am spus atunci că este o metodă de a face un program mai rapid fără a-i schimba modul în care el funcționează. Ideea ei este ca atunci când efectuăm un calcul scump să păstrăm valoarea calculată într-un tablou pentru a economisi timp în cazul când în viitor vom avea din nou nevoie de acea valoare.&lt;br /&gt;
&lt;br /&gt;
Tot atunci am prezentat un exemplu avansat de memoizare folosit într-o funcție recursivă, mai exact în calculul recursiv al unui element din șirul lui Fibonacci.&lt;br /&gt;
&lt;br /&gt;
Merită menționat că tehnica memoizării se aplică mai ales în funcții recursive. Ea ne permite foarte simplu să scădem timpul de execuție al programului folosind mai multă memorie. Această tehnică este cu atât mai eficientă cu cât ne așteptăm să apelăm de multe ori aceeași funcție cu aceiași parametri. De aceea funcția trivială de calcul al unui termen din șirul lui Fibonacci este un exemplu perfect de memoizare, ea avînd un număr exponențial de apeluri, în varianta nememoizată.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important:&#039;&#039;&#039; memoizarea nu schimbă logica sau structura algoritmului folosit. Acesta este un mare avantaj. Avem o opțiune între câtă memorie folosim și cât timp câștigăm. Aceste tehnici, în care putem alege între timp și memorie, sunt destul de rare în algoritmică.&lt;br /&gt;
&lt;br /&gt;
Să vedem două exemple clasice de memoizare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Factorial ===&lt;br /&gt;
&lt;br /&gt;
Algoritmul simplu pentru calculul lui n!, fără memoizare, este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
long long fact( int n ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return n * fact( n - 1 );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Cel cu memoizare va folosi un vector ce va stoca elementele deja calculate:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
long long val[20] = { 1 };&lt;br /&gt;
&lt;br /&gt;
// exemplu de funcție factorial implementată folosind memoizare&lt;br /&gt;
long long fact( int n ) {&lt;br /&gt;
  if ( val[n] == 0 )             // valoare inca necalculata&lt;br /&gt;
    val[n] = n * fact( n - 1 );  // o calculam in tabel&lt;br /&gt;
&lt;br /&gt;
  return val[n];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Fibonacci ===&lt;br /&gt;
&lt;br /&gt;
Algoritmul direct de calcul al celui de-al n-lea termen al șirului lui Fibonacci:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int fib( int n ) {&lt;br /&gt;
  if ( n == 1 || n == 2 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return fib( n-1 ) + fib( n-2 );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Cel cu memoizare va folosi un vector ce va stoca elementele deja calculate:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int val[1000] = { 1, 1 };&lt;br /&gt;
&lt;br /&gt;
// exemplu de funcție fibonacci implementată folosind memoizare&lt;br /&gt;
int fib( int n ) {&lt;br /&gt;
  if ( val[n] == 0 ) // inca necalculat&lt;br /&gt;
    val[n] = fib( n-1 ) + fib( n-2 ); // il calculam in tabel&lt;br /&gt;
  return val[n]; // returnam valoarea din tabel&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Recursie Top-Down și Bottom-Up ==&lt;br /&gt;
&lt;br /&gt;
=== Recursie Top-Down ===&lt;br /&gt;
&lt;br /&gt;
Există două moduri de implementare a unei soluții recursive:&lt;br /&gt;
&lt;br /&gt;
Recursie &#039;&#039;&#039;Top-Down&#039;&#039;&#039;: când pornim cu o problemă mai mare și apelăm funcția pentru probleme mai mici pe care le folosim ulterior în apelul curent. În subproblema de bază returnăm un rezultat de bază, cum ar fi zero sau unu. În această categorie sunt aproape toate exemplele de până acum cu excepția cmmdc, interclasare vectori, combinări, permutări și sumă de monede.&lt;br /&gt;
&lt;br /&gt;
=== Recursie Bottom-Up ===&lt;br /&gt;
&lt;br /&gt;
Recursie &#039;&#039;&#039;Bottom-Up&#039;&#039;&#039;: când pornim cu o problemă mai mare și cu un acumulator de soluție, care inițial este vid (zero sau unu), iar în apelurile ulterioare reducem mărimea problemei adăugând (construind) soluția finală în acumulator. Exemplele de combinări și permutări sunt Bottom-Up în care acumulatorul este vectorul și nu este transmis în mod explicit, deși am putea face acest lucru.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemple de recursie Bottom-Up cu acumulator ===&lt;br /&gt;
&lt;br /&gt;
Implementați bottom-up cu acumulator exemplele făcute până acum.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Factorial ====&lt;br /&gt;
&lt;br /&gt;
Iată o comparație a celor două implementări ale funcției factorial, implementată recursiv top-down și bottom-up cu acumulator:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Top-down&lt;br /&gt;
!Bottom-up&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int fact( int n ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return n * fact( n - 1 );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  printf( &amp;quot;%d! = %d\n&amp;quot;, n, fact( n ) );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int fact( int n, int ac ) {&lt;br /&gt;
  if ( n == 1 )                 // cind n = 1 am terminat calculul in ac&lt;br /&gt;
    return ac;&lt;br /&gt;
  return fact( n - 1, n * ac ); // adaugam n la acumulator&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  printf( &amp;quot;%d! = %d\n&amp;quot;, n, fact( n, 1 ) );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Putere ====&lt;br /&gt;
&lt;br /&gt;
Iată o comparație a celor două implementări ale funcției putere, implementată recursiv top-down și bottom-up cu acumulator:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Top-down&lt;br /&gt;
!Bottom-up&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int putere( int a, int n ) {&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return a * putere( a, n - 1 );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, a;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d%d&amp;quot;, &amp;amp;a, &amp;amp;n );&lt;br /&gt;
  printf( &amp;quot;%d^%d = %d\n&amp;quot;, a, n, putere( a, n ) );&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int putere( int a, int n, int ac ) {&lt;br /&gt;
  if ( n == 0 ) // cind puterea e zero acumulatorul are rezultatul&lt;br /&gt;
    return ac;&lt;br /&gt;
  return putere( a, n - 1, a * ac ); // acumuleaza a la rezultat&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, a;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d%d&amp;quot;, &amp;amp;a, &amp;amp;n );&lt;br /&gt;
  printf( &amp;quot;%d^%d = %d\n&amp;quot;, a, n, putere( a, n, 1 ) );&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== CMMDC ====&lt;br /&gt;
&lt;br /&gt;
De remarcat că funcția recursivă care calculează cmmdc a două numere, prezentată în lecția trecută, este deja bottom-up cu acumulator:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int cmmdc( int a, int b ) {&lt;br /&gt;
  if ( b == 0 )&lt;br /&gt;
    return a;&lt;br /&gt;
  return cmmdc( b, a % b );&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Fibonacci ====&lt;br /&gt;
&lt;br /&gt;
Iată o comparație a celor două implementări ale șirului lui Fibonacci, implementat recursiv top-down și bottom-up cu acumulator:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Top-down&lt;br /&gt;
!Bottom-up&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int fib( int n ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return 0;&lt;br /&gt;
  if ( n == 2 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return fib( n - 1 ) + fib ( n - 2 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ea va fi apelată astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;f = fib( 15 );&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unde 15 este numărul termenului dorit.&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int fib( int n, int f1, int f2 ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return f1;&lt;br /&gt;
  return fib( n - 1, f2, f1 + f2 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ea va fi apelată astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;f = fib( 15, 0, 1 );&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unde 0 și 1 sunt primii doi termeni ai șirului, iar 15 este numărul termenului dorit.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Suma cifrelor unui număr ====&lt;br /&gt;
&lt;br /&gt;
Iată o comparație a celor două implementări ale sumei cifrelor unui număr, implementată recursiv top-down și bottom-up cu acumulator:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Top-down&lt;br /&gt;
!Bottom-up&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int sumac( int n ) {&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 0;&lt;br /&gt;
  return n % 10 + sumac( n / 10 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ea va fi apelată astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;s = sumac( 2324103 );&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unde 2324103 este numărul căruia îi calculăm suma cifrelor.&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
int sumac( int n, int suma ) {&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return suma;&lt;br /&gt;
  return sumac( n / 10, suma + n % 10 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ea va fi apelată astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;s = sumac( 2324103, 0 );&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unde 2324103 este numărul căruia îi calculăm suma cifrelor, iar 0 este inițializarea acumulatorului sumă.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Putere în O(log n) ====&lt;br /&gt;
&lt;br /&gt;
Iată o comparație a celor două implementări ale funcției putere, implementată &#039;&#039;O(log n)&#039;&#039; recursiv top-down și bottom-up cu acumulator:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Top-down&lt;br /&gt;
!Bottom-up&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// a este numărul de ridicat la putere&lt;br /&gt;
// n este puterea&lt;br /&gt;
int putere( int a, int n ) {&lt;br /&gt;
  int p;&lt;br /&gt;
&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  p =  putere( a * a, n / 2 );&lt;br /&gt;
  if ( n % 2 ) // n impar?&lt;br /&gt;
    p *= a;&lt;br /&gt;
&lt;br /&gt;
  return p;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, a;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d%d&amp;quot;, &amp;amp;a, &amp;amp;n );&lt;br /&gt;
  printf( &amp;quot;%d^%d = %d\n&amp;quot;, a, n, putere( a, n ) );&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// in permanenta a^n * ac detine rezultatul&lt;br /&gt;
// cind n devine zero a^n * ac = ac, deci rezultatul este chiar ac&lt;br /&gt;
int putere( int a, int n, int ac ) {&lt;br /&gt;
  if ( n == 0 ) // cind puterea e zero acumulatorul are rezultatul&lt;br /&gt;
    return ac;&lt;br /&gt;
&lt;br /&gt;
  if ( n % 2 )                         // cind n impar&lt;br /&gt;
    ac *= a;                           // acumuleaza a la rezultat&lt;br /&gt;
&lt;br /&gt;
  return putere( a * a, n / 2, ac );   // totuna cu (a*a)^(n/2)&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, a;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d%d&amp;quot;, &amp;amp;a, &amp;amp;n );&lt;br /&gt;
  printf( &amp;quot;%d^%d = %d\n&amp;quot;, a, n, putere( a, n, 1 ) );&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Interclasare vectori ordonați ====&lt;br /&gt;
&lt;br /&gt;
De remarcat că interclasarea a doi vectori ordonați, prezentată data trecută, este bottom-up cu acumulator (vectorul &amp;lt;code&amp;gt;v3[]&amp;lt;/code&amp;gt; este acumulatorul).&lt;br /&gt;
&lt;br /&gt;
Funcția va primi ca parametri cei trei vectori și pozițiile curente în ei (inițial pozițiile de start, 0). La fiecare apel va alege un element pe care îl va copia. Apoi se va reapela cu indici actualizați (&amp;lt;code&amp;gt;i1&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;i3&amp;lt;/code&amp;gt; sau &amp;lt;code&amp;gt;i2&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;i3&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void interclaseaza( int n1, int v1[], int n2, int v2[], int v3[],&lt;br /&gt;
                   int i1, int i2, int i3 ) {&lt;br /&gt;
  if ( i1 &amp;lt; n1 ) { // mai avem elemente in primul vector?&lt;br /&gt;
    if ( i2 &amp;lt; n2 &amp;amp;&amp;amp; v2[i2] &amp;lt; v1[i1] ) { // v2[i2] exista si e mai mic?&lt;br /&gt;
      v3[i3] = v2[i2];                  // copiem v2[i1]&lt;br /&gt;
      interclaseaza( n1, v1, n2, v2, v3, i1, i2 + 1, i3 + 1 );&lt;br /&gt;
    } else {&lt;br /&gt;
      v3[i3] = v1[i1];                  // copiem v1[i1]&lt;br /&gt;
      interclaseaza( n1, v1, n2, v2, v3, i1 + 1, i2, i3 + 1 );&lt;br /&gt;
    }&lt;br /&gt;
  } else if ( i2 &amp;lt; n2 ) { // mai avem elemente in v2? (v1 s-a terminat)&lt;br /&gt;
    v3[i3] = v2[i2];&lt;br /&gt;
    interclaseaza( n1, v1, n2, v2, v3, i1, i2 + 1, i3 + 1 );&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Palindrom ====&lt;br /&gt;
&lt;br /&gt;
Verificarea că un număr este palindrom este, în fapt, mai ușor de scris bottom-up. În acumulator vom construi inversul numărului &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;. La final vom compara inversul cu originalul. Iată o comparație a celor două implementări ale testului de palindrom, implementat recursiv top-down și bottom-up cu acumulator:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Top-down&lt;br /&gt;
!Bottom-up&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int putere( int n ) {             // calculam recursiv cea mai mare&lt;br /&gt;
  int p = 1;                      // putere a lui 10 mai mica decit n&lt;br /&gt;
&lt;br /&gt;
  if ( n &amp;lt; 10 )                   // cind n are o singura cifra&lt;br /&gt;
    return 1;                     // cea mai mare putere este 1&lt;br /&gt;
  return 10 * putere( n / 10 );   // adaugam un zero si taiem o cifra&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int palindrom( int n, int p10 ) { // pe baza lui n si a puterii lui 10&lt;br /&gt;
  if ( n &amp;lt; 10 )                   // un numar de o cifra este palindrom&lt;br /&gt;
    return 1;&lt;br /&gt;
  if ( n / p10 != n % 10 )        // testam prima si ultima cifra&lt;br /&gt;
    return 0;&lt;br /&gt;
  return palindrom( n % p10 / 10, p10 / 100 ); // taie prima si ultima cifra&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, pow10;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  pow10 = putere( n );&lt;br /&gt;
  if ( palindrom( n, pow10 ) )&lt;br /&gt;
    printf( &amp;quot;%d este palindrom\n&amp;quot;, n );&lt;br /&gt;
  else&lt;br /&gt;
    printf( &amp;quot;%d nu este palindrom\n&amp;quot;, n );&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int palindrom( int n, int nc, int ac ) {&lt;br /&gt;
  if ( n == 0 )            // cind n=0 ac este inversul lui nc&lt;br /&gt;
    return nc == ac;       // daca sint egale, returnam 1, altfel 0&lt;br /&gt;
  return palindrom( n / 10, nc, ac * 10 + n % 10 ); // taiem o cifra tin n&lt;br /&gt;
}                                                   // si o adaugam la ac&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  if ( palindrom( n, n, 0 ) )&lt;br /&gt;
    printf( &amp;quot;%d este palindrom\n&amp;quot;, n );&lt;br /&gt;
  else&lt;br /&gt;
    printf( &amp;quot;%d nu este palindrom\n&amp;quot;, n );&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Transformarea recursiei din Top-Down în Bottom-Up ===&lt;br /&gt;
&lt;br /&gt;
Orice funcție recursivă de tip Top-Down care pe orice cale de execuție are un singur apel recursiv se poate transforma într-o funcție de tip Bottom-Up prin adăugarea unui parametru acumulator. De ce ne interesează acest lucru? Datorită recursiei la coadă. Recursia la coadă este definită ca un apel recursiv exact la ieșirea din funcție. Toate exemplele pe care le-am dat la recursia bottom-up sînt recursive la coadă. Exemplele date la top-down nu sînt recursive la coadă, cu excepția lui cmmdc.&lt;br /&gt;
&lt;br /&gt;
Recursia la coadă este foarte utilă, deoarece nu consumă stivă. În mod normal fiecare apel de funcție consumă cel puțin patru octeți pe stivă (adresa de întoarcere). Ei bine, în cazul apelurilor recursive la coadă stiva consumată este zero (ca să fim exacţi nu este zero, ci &#039;&#039;O(1)&#039;&#039;). Explicația acestui fapt este prea tehnică pentru a o detalia aici. Ea ține de modul în care este organizat calculatorul modern Von Neumann și de modul în care se execută apelurile de funcții.&lt;br /&gt;
&lt;br /&gt;
Ce înseamnă pentru noi acest lucru? Înseamnă că putem chema o funcție, recursiv, de un milion de ori, fără să depășim memoria stivă. Ce înseamnă pentru omenire acest lucru? Înseamnă că putem avea întregi limbaje care nu conțin instrucțiuni de ciclare (while sau for), cum ar fi limbajele funcționale (Scheme, Lisp) sau limbajele logice (Prolog). Acest lucru este posibil deoarece recursivitatea la coadă poate înlocui cu succes buclele. Aceste limbaje se bazează pe definiția recursivă a algoritmului. În unele cazuri programele sînt mai clare într-o gândire recursivă.&lt;br /&gt;
&lt;br /&gt;
== Tema 8 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/fibonacci Fibonacci]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/primrec Primrec]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/maxrec Maxrec]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/factorizare Factorizare]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumprim Sumprim]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/fibrec Fibrec]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/aparitii2 Aparitii2]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/permutari1 Permutări1] Problema cere ca, dându-se indicii anumitor permutări să se calculeze suma acelor permutări.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/aranjamente Aranjamente] Problema cere generarea eficientă a tuturor aranjamentelor și a calculului unor proprietăți ale lor.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/optim Optim] dată la ONI 2012 clasa a 8-a, problemă ce se poate rezolva recursiv.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/balance Balance] dată la Shumen 2012 juniori, problemă pentru avansați, deoarece ea necesită backtracking, ceea ce nu am predat.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Temă de gândire, opțională:&#039;&#039;&#039; Care ar fi un algoritm iterativ de rezolvare a turnurilor din Hanoi? Încercați să găsiți reguli simple, nu doar să mimați algoritmul recursiv folosind o stivă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_8:_Recursivitate_(2) Accesează rezolvarea temei 8]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_7:_Recursivitate_(1)&amp;diff=18570</id>
		<title>Clasa a 7-a Lecția 7: Recursivitate (1)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_7:_Recursivitate_(1)&amp;diff=18570"/>
		<updated>2026-02-06T16:00:39Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/n_LKos0gG0Q&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Despre recursivitate ==&lt;br /&gt;
&lt;br /&gt;
O funcție este recursivă dacă se autoapelează. A scrie o funcție recursivă necesită, inițial, o încredere în faptul că funcția funcționează. În fapt, când pornim să scriem o funcție recursivă este bine să considerăm că ea deja funcționează!&lt;br /&gt;
&lt;br /&gt;
Reguli de scriere a unei funcții recursive:&lt;br /&gt;
&lt;br /&gt;
* Înainte de a scrie cod este bine să ne clarificăm formula recurentă.&lt;br /&gt;
* Tratăm mai întâi cazurile simple, atunci când funcția poate returna o valoare fără a se autoapela.&lt;br /&gt;
* În final, tratăm cazul de recursie, considerând că funcția este deja scrisă pentru cazurile &#039;&#039;mai mici&#039;&#039;, folosindu-ne de formula recurentă găsită la început.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Iată un videoclip de prezentare cu exemple de funcții recursive, cu imagini *:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.youtube.com/watch?v=ngCos392W4w&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; * Exemplele de cod din film sunt scrise într-un pseudocod, suficient de bine explicate astfel încât să fie ușor redactate în orice limbaj de programare, inclusiv C.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Exemple introductive ==&lt;br /&gt;
&lt;br /&gt;
=== Factorial ===&lt;br /&gt;
&lt;br /&gt;
Calcul n! Ne vom folosi de definiția recursivă: &amp;lt;math&amp;gt;n! = \begin{cases} 1, &amp;amp; \mbox{dacă }  n = 1  \\ n \cdot (n - 1)!,  &amp;amp; \mbox{dacă } n &amp;gt; 1. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int fact( int n ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return n * fact( n - 1 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Putere ===&lt;br /&gt;
Calcul a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;. Ne vom folosi de definiția recurentă: &amp;lt;math&amp;gt;a^{n} = \begin{cases} 1, &amp;amp; \mbox{dacă }  n = 0  \\ a \cdot a^{n-1},  &amp;amp; \mbox{dacă } n &amp;gt; 0. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int putere( int a, int n ) {&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return a * putere( a, n - 1 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Cmmdc ===&lt;br /&gt;
Calcul cmmdc a două numere. Ne vom folosi de definiția recursivă a lui Euclid: &amp;lt;math&amp;gt;cmmdc(a, b) = \begin{cases} a, &amp;amp; \mbox{dacă }  b = 0  \\ cmmdc(b, a \bmod b),  &amp;amp; \mbox{dacă } b &amp;gt; 0. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int cmmdc( int a, int b ) {&lt;br /&gt;
  if ( b == 0 )&lt;br /&gt;
    return a;&lt;br /&gt;
  return cmmdc( b, a % b );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Exerciții ==&lt;br /&gt;
&lt;br /&gt;
Încercați să scrieți următoarele funcții recursive.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Fibonacci ===&lt;br /&gt;
Să se scrie o funcție recursivă care, dat &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;, calculează al &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;-lea termen din șirul lui Fibonacci: &amp;lt;code&amp;gt;0 1 1 2 3 5 8 13...&amp;lt;/code&amp;gt;. Ne vom folosi de definiția recurentă: &amp;lt;math&amp;gt;fib(n) = \begin{cases} 0, &amp;amp; \mbox{dacă }  n = 1 \\ 1, &amp;amp; \mbox{dacă }  n = 2 \\ fib(n-1) + fib(n-2),  &amp;amp; \mbox{dacă } n &amp;gt; 2. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int fib( int n ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return 0;&lt;br /&gt;
  if ( n == 2 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return fib( n - 1 ) + fib ( n - 2 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Suma cifrelor unui număr ===&lt;br /&gt;
Să se scrie o funcție recursivă care calculează suma cifrelor unui număr &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ne vom folosi de următoarea formulă recurentă: &amp;lt;math&amp;gt;suma(n) = \begin{cases} 0, &amp;amp; \mbox{dacă }  n = 0  \\ n % 10 + suma(n / 10),  &amp;amp; \mbox{dacă } n &amp;gt; 0. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int sumac( int n ) {&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 0;&lt;br /&gt;
  return n % 10 + sumac( n / 10 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Putere în O(log n) ===&lt;br /&gt;
Calculați &amp;lt;tt&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt; în mod cât mai eficient (a și n numere naturale). Problema este cunoscută şi sub numele de ridicare la putere în timp logaritmic. Ideea din spatele acestei rezolvări este următoarea:&lt;br /&gt;
&lt;br /&gt;
*Dacă &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este par, atunci &amp;lt;tt&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; = a&amp;lt;sup&amp;gt;2*n/2&amp;lt;/sup&amp;gt; = (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;n/2&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
*Dacă &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este impar, atunci &amp;lt;code&amp;gt;n-1&amp;lt;/code&amp;gt; este par și avem &amp;lt;tt&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; = a * a&amp;lt;sup&amp;gt;n-1&amp;lt;/sup&amp;gt; =  a * a&amp;lt;sup&amp;gt;2*(n-1)/2&amp;lt;/sup&amp;gt; = a * (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;(n-1)/2&amp;lt;/sup&amp;gt; = a * (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;n/2&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Rezultă formula de recurență:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;a^{n} = \begin{cases} 1, &amp;amp; \mbox{dacă }  n = 0 \\ {(a^{2})}^{n/2},  &amp;amp; \mbox{dacă } n &amp;gt;0, n % 2 = 0 \\ a \times {(a^{2})}^{n/2},  &amp;amp; \mbox{dacă } n &amp;gt; 0, n % 2 = 1 \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
În formulele de mai sus am considerat că &amp;lt;code&amp;gt;/&amp;lt;/code&amp;gt; este împărțirea întreagă din limbajul C. Se observă că indiferent de paritatea lui &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;, la fiecare pas al iterației putem transforma &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; în &amp;lt;code&amp;gt;a * a&amp;lt;/code&amp;gt; și apoi putem împărți &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; la &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;. Doar în cazurile când &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este impar vom acumula valoarea curentă a lui &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; la produsul calculat. Iată soluția bazată pe această idee:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int putere( int a, int n ) {&lt;br /&gt;
  int p;&lt;br /&gt;
&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  p =  putere( a * a, n / 2 );&lt;br /&gt;
  if ( n % 2 ) // n impar?&lt;br /&gt;
    p *= a;&lt;br /&gt;
&lt;br /&gt;
  return p;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Afișare ===&lt;br /&gt;
Să se afișeze un șir de caractere în ordine inversă. Șirul se termină cu &amp;lt;code&amp;gt;&#039;\n&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ne vom folosi de următoarea idee: citim un caracter, apoi chemăm funcția recursivă pentru a afișa restul caracterelor, apoi afișăm caracterul.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void afisare( FILE *fin, FILE *fout ) {&lt;br /&gt;
  char ch;&lt;br /&gt;
&lt;br /&gt;
  ch = fgetc( fin );&lt;br /&gt;
  if ( ch != &#039;\n&#039; ) {&lt;br /&gt;
    afisare( fin, fout );&lt;br /&gt;
    fputc( ch, fout );&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Descompunere în baza 2 ===&lt;br /&gt;
Să se scrie o funcție recursivă care, dat &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;, îl afișează în baza 2. &lt;br /&gt;
&lt;br /&gt;
Similar cu exercițiul precedent, vom calcula ultima cifră a descompunerii (restul împărțirii la doi), apoi vom chema funcția cu &amp;lt;code&amp;gt;n / 2&amp;lt;/code&amp;gt;, iar la revenire vom afișa cifra.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void baza2( int n, FILE *fout ) {&lt;br /&gt;
  int cf2;&lt;br /&gt;
&lt;br /&gt;
  if ( n &amp;gt; 0 ) {&lt;br /&gt;
    cf2 = n % 2;&lt;br /&gt;
    baza2( n / 2, fout );&lt;br /&gt;
    fprintf( fout, &amp;quot;%d&amp;quot;, cf2 );&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Interclasare vectori ordonați ===&lt;br /&gt;
Dîndu-se doi vectori ordonați să se calculeze un al treilea vector ordonat ce conține elementele lor.&lt;br /&gt;
&lt;br /&gt;
Funcția va primi ca parametri cei trei vectori și pozițiile curente în ei (inițial pozițiile de start, 0). La fiecare apel va alege un element pe care îl va copia. Apoi se va reapela cu indici actualizați (i1 și i3 sau i2 și i3).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void interclaseaza( int n1, int v1[], int n2, int v2[], int v3[],&lt;br /&gt;
                   int i1, int i2, int i3 ) {&lt;br /&gt;
  if ( i1 &amp;lt; n1 ) { // mai avem elemente in primul vector?&lt;br /&gt;
    if ( i2 &amp;lt; n2 &amp;amp;&amp;amp; v2[i2] &amp;lt; v1[i1] ) { // v2[i2] exista si e mai mic?&lt;br /&gt;
      v3[i3] = v2[i2];                  // copiem v2[i1]&lt;br /&gt;
      interclaseaza( n1, v1, n2, v2, v3, i1, i2 + 1, i3 + 1 );&lt;br /&gt;
    } else {&lt;br /&gt;
      v3[i3] = v1[i1];                  // copiem v1[i1]&lt;br /&gt;
      interclaseaza( n1, v1, n2, v2, v3, i1 + 1, i2, i3 + 1 );&lt;br /&gt;
    }&lt;br /&gt;
  } else if ( i2 &amp;lt; n2 ) { // mai avem elemente in v2? (v1 s-a terminat)&lt;br /&gt;
    v3[i3] = v2[i2];&lt;br /&gt;
    interclaseaza( n1, v1, n2, v2, v3, i1, i2 + 1, i3 + 1 );&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 6 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumacfnr Sumacfnr]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumadiv Sumadiv]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/nset Nset]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/invector Invector]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/hanoi Turnurile din Hanoi]: [http://www.puzzle.ro/ro/play_toh.htm exemplu de animație joc aici] să se scrie un program care să rezolve jocul turnurile din Hanoi (să efectueze mutările care duc la soluție).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Indicații pentru tema obligatorie ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2. Sumadiv - suma divizorilor unui număr&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Rezolvați problema [https://www.nerdarena.ro/problema/sumadiv Sumadiv]: să se scrie o funcție recursivă care să calculeze suma divizorilor unui număr. &lt;br /&gt;
&lt;br /&gt;
Ea va arăta astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int sumd( int n, int d ) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
și va fi apelată inițial astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
sum = sumd( n, 1 );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Semnificaţia este suma divizorilor lui &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; care sunt mai mari sau egali cu &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt; şi mai mici sau egali cu &amp;lt;code&amp;gt;n / d&amp;lt;/code&amp;gt;. Mai exact trebuie să completați funcția &amp;lt;code&amp;gt;sumd&amp;lt;/code&amp;gt; din programul următor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int sumd( int n, int d ) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;sumadiv.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;sumadiv.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;%d\n&amp;quot;, sumd( n, 1 ) );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Atenţie mare la complexitatea algoritmului obţinut!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4. invector - răsturnare vector&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Rezolvați problema [https://www.nerdarena.ro/problema/invector Invector] să se răstoarne un vector folosind o funcție recursivă. Vectorul trebuie modificat, nu doar afișat invers. Funcția va arăta astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void inv( int primul, int ultimul, int v[] ) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
unde primul și ultimul sunt indicii de început, respectiv sfârșit care definesc subvectorul de răsturnat. Funcția va fi apelată inițial astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
inv( 0, n-1, v );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Mai exact trebuie să completați funcția inv din programul următor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int v[100000];&lt;br /&gt;
&lt;br /&gt;
void inv( int primul, int ultimul, int v[] ) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, i;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;invector.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;v[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  inv( 0, n-1, v );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;invector.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d &amp;quot;, v[i] );&lt;br /&gt;
  fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tema opțională  ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/combinari combinări] &amp;lt;math&amp;gt;{C}_{n}^{k}&amp;lt;/math&amp;gt;&amp;lt;nowiki&amp;gt;- combinări de n luate câte k. Date numerele naturale n și k, k &amp;amp;le; n, să se afișeze toate submulțimile mulțimii {1, 2, 3, ..., n} de k elemente.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
* [https://www.nerdarena.ro/problema/v v] (ONI 2004 clasa a 7-a) Problema este modificată. Puteți lua doar 50% din punctaj. Pentru 100% &#039;&#039;&#039;nu este nevoie de parsing&#039;&#039;&#039;. Dacă introduceam cerință de parsing aș fi scăzut timpul permis.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php/Clasa_a_7-a_Lec%C8%9Bia_7:_Recursivitate_(1) Accesează rezolvarea temei 7]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_5:_Precalculare&amp;diff=18569</id>
		<title>Clasa a 7-a Lecția 5: Precalculare</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_5:_Precalculare&amp;diff=18569"/>
		<updated>2026-02-06T15:58:57Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/n_LKos0gG0Q|||||start=6180&amp;amp;end=10200&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Despre precalculare ==&lt;br /&gt;
&lt;br /&gt;
Precalcularea este un termen general pentru folosirea unor structuri auxiliare de date, uneori fără o legătură directă cu problema de rezolvat, care ne ajută în rezolvarea problemei. &lt;br /&gt;
&lt;br /&gt;
Aceste structuri de date sunt calculate la început, înainte de calculul propriu zis, ceea ce duce la denumire: precalculare.&lt;br /&gt;
&lt;br /&gt;
De obicei precalcularea reduce complexitatea anumitor pași din algoritm, prețul ei fiind un calcul mic la început, astfel încât, per total, algoritmul va fi mai rapid.&lt;br /&gt;
&lt;br /&gt;
În engleză acest subiect este numit precomputation. Wikipedia oferă exemple de algoritmi complecși de precalculare. Noi vom discuta în continuare despre algoritmi de precalculare de bază, ce apar uzual în programare și la concursuri.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Santinela în căutarea liniară ===&lt;br /&gt;
&lt;br /&gt;
Precum știm, codul clasic de căutare a elementului e în vectorul &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt; de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
i = 0;&lt;br /&gt;
while ( i &amp;lt; n &amp;amp;&amp;amp; v[i] != e )&lt;br /&gt;
  i++;&lt;br /&gt;
if ( i &amp;lt; n ) // ... testul daca am gasit elementul&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am discutat cum putem elimina unul din testele din &amp;lt;code&amp;gt;while&amp;lt;/code&amp;gt; folosind o santinelă. Vom așeza e pe prima poziție în afara vectorului. Astfel, ne asigurăm că vom găsi elementul în vector. Putem, deci, să nu mai testăm condiția &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt;. Noul cod va fi:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
v[n] = e;&lt;br /&gt;
i = 0;&lt;br /&gt;
while ( v[i] != e )&lt;br /&gt;
  i++;&lt;br /&gt;
if ( i &amp;lt; n ) // ... testul daca am gasit elementul&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Așezarea santinelei în vector poate fi un exemplu foarte simplu de precalculare. Ea va folosi un element al vectorului, ce va trebui dimensionat cu unu în plus.&lt;br /&gt;
&lt;br /&gt;
* Avantaje: cod mai rapid și mai scurt.&lt;br /&gt;
* Schimbă complexitatea algoritmului: nu.&lt;br /&gt;
* Timp precalculare: &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* Memorie extra folosită: &#039;&#039;O(1)&#039;&#039; (un element în plus).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Bordarea ===&lt;br /&gt;
&lt;br /&gt;
Bordarea, tehnica prin care &#039;&#039;înconjurăm&#039;&#039; matricea cu elemente distincte, ar putea fi și ea considerată o precalculare. În loc de o santinelă avem acum multiple santinele. Codul de testare al depășirii matricei se simplifică și devine mai eficient.&lt;br /&gt;
&lt;br /&gt;
* Avantaje: cod mai rapid și mai scurt.&lt;br /&gt;
* Schimbă complexitatea algoritmului: nu.&lt;br /&gt;
* Memorie extra folosită: &#039;&#039;O(n)&#039;&#039; (&#039;&#039;&#039;n&#039;&#039;&#039; este dimensiunea matricei).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Precalculare de tip caracter ===&lt;br /&gt;
&lt;br /&gt;
Dacă avem nevoie să aflăm dacă un caracter dat este cifră, testul clasic este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
if ( ch &amp;gt;= &#039;0&#039; &amp;amp;&amp;amp; ch &amp;lt;= &#039;9&#039; )&lt;br /&gt;
  // ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Putem elimina un test dacă precalculăm un vector de stegulețe, pentru toate caracterele, care să aibă 1 pe pozițiile caracterelor cifre și 0 în rest. Astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
char ecifra[128];&lt;br /&gt;
&lt;br /&gt;
// precalculare: initializare ecifra[]&lt;br /&gt;
for ( ch = &#039;0&#039;; ch &amp;lt;= &#039;9&#039;; ch++ )&lt;br /&gt;
  ecifra[ch] = 1;&lt;br /&gt;
&lt;br /&gt;
// test cifra&lt;br /&gt;
if ( ecifra[ch] )&lt;br /&gt;
  // ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Vom calcula la început câte o valoare per cifră. Apoi, în algoritm vom scuti unul din teste.&lt;br /&gt;
&lt;br /&gt;
Din fericire nu trebuie să facem explicit acest lucru. Biblioteca ne oferă funcția:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int isdigit( int ch )&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aceasta face exact ce ne dorim. &lt;br /&gt;
&lt;br /&gt;
Există și alte funcții, cum ar fi &amp;lt;code&amp;gt;isalpha()&amp;lt;/code&amp;gt; (testare literă) și &amp;lt;code&amp;gt;isalnum()&amp;lt;/code&amp;gt; (testare cifra sau litera).&lt;br /&gt;
&lt;br /&gt;
* Avantaje: cod mai rapid și mai scurt.&lt;br /&gt;
* Schimbă complexitatea algoritmului: nu.&lt;br /&gt;
* Timp precalculare: &#039;&#039;O(Σ)&#039;&#039; (unde &#039;&#039;&#039;Σ&#039;&#039;&#039; este numărul de caractere).&lt;br /&gt;
* Memorie extra folosită: &#039;&#039;O(Σ)&#039;&#039; (unde &#039;&#039;&#039;Σ&#039;&#039;&#039; este numărul de caractere).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Prelucrare valori cifre în baze de numerație ===&lt;br /&gt;
&lt;br /&gt;
Să luăm ca exemplu problema [https://www.nerdarena.ro/problema/criptic criptic]. La intrare se aflau numere în baza 62, ce trebuiau citite ca întregi în memorie. Pentru aceasta trebuia să calculăm valoarea unei cifre. Codul simplu și direct va fi:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
char ch;&lt;br /&gt;
// ...&lt;br /&gt;
ch = fgetc( fin );&lt;br /&gt;
if ( ch &amp;gt;= &#039;0&#039; &amp;amp;&amp;amp; ch &amp;lt;= &#039;9&#039; )&lt;br /&gt;
  v = ch - &#039;0&#039;;&lt;br /&gt;
else if ( ch &amp;gt;= &#039;a&#039; &amp;amp;&amp;amp; ch &amp;lt;= &#039;z&#039; )&lt;br /&gt;
  v = ch - &#039;a&#039; + 10;&lt;br /&gt;
else&lt;br /&gt;
  v = ch - &#039;A&#039; + 36;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
El va face 2 sau 4 teste de inegalitate. Pentru a nu avea nici un test putem precalcula într-un vector valorile cifrelor în baza 62:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int val[128];&lt;br /&gt;
// ...&lt;br /&gt;
for ( ch = &#039;0&#039;; ch &amp;lt;= &#039;9&#039;; ch++ ) // valorile cifrelor&lt;br /&gt;
  val[ch] = ch - &#039;0&#039;;&lt;br /&gt;
for ( ch = &#039;a&#039;; ch &amp;lt;= &#039;z&#039;; ch++ )&lt;br /&gt;
  val[ch] = ch - &#039;a&#039; + 10;&lt;br /&gt;
for ( ch = &#039;A&#039;; ch &amp;lt;= &#039;Z&#039;; ch++ )&lt;br /&gt;
  val[ch] = ch - &#039;A&#039; + 36;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Apoi, la folosire:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
char ch;&lt;br /&gt;
// ...&lt;br /&gt;
ch = fgetc( fin );&lt;br /&gt;
v = val[ch];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Avantaje: cod mai rapid și mai scurt.&lt;br /&gt;
* Schimbă complexitatea algoritmului: nu.&lt;br /&gt;
* Timp precalculare: &#039;&#039;O(Σ)&#039;&#039; (unde &#039;&#039;&#039;Σ&#039;&#039;&#039; este numărul de caractere).&lt;br /&gt;
* Memorie extra folosită: &#039;&#039;O(Σ)&#039;&#039; (unde &#039;&#039;&#039;Σ&#039;&#039;&#039; este numărul de caractere).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ciurul lui Eratostene ===&lt;br /&gt;
&lt;br /&gt;
V-aţi gândit vreodată că ciurul lui Eratostene este de fapt o precalculare a unui vector de frecvenţă &amp;lt;code&amp;gt;ciur[]&amp;lt;/code&amp;gt;, care, pentru fiecare număr &amp;lt;code&amp;gt;p&amp;lt;/code&amp;gt;, calculează &amp;lt;code&amp;gt;ciur[p]&amp;lt;/code&amp;gt; care ne spune dacă &amp;lt;code&amp;gt;p&amp;lt;/code&amp;gt; este prim?&lt;br /&gt;
&lt;br /&gt;
Cum am calcula primalitatea tuturor numerelor până la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; în absența ciurului? Am verifica de primalitate fiecare din numere, în &#039;&#039;O(p)&#039;&#039;. Complexitatea algoritmului ar fi: &amp;lt;math&amp;gt;\sqrt{1} + \sqrt{2} + \sqrt{3} + ... + \sqrt{n}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Această sumă este &#039;&#039;&amp;lt;math&amp;gt;O(n \sqrt{n})&amp;lt;/math&amp;gt;&#039;&#039; (demonstrația depășește nivelul cercului nostru). În realitate lucrurile stau puțin mai bine, în sensul că nu vom ajunge cu divizorii până la &#039;&#039;&amp;lt;math&amp;gt;\sqrt{k}&amp;lt;/math&amp;gt;&#039;&#039; decât pentru &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; prim. O estimare a numerelor prime până la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este &amp;lt;math&amp;gt;\frac{n}{log{n}}&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
De aceea, o estimare ajustată a complexității ar fi &#039;&#039;&amp;lt;math&amp;gt;O(\frac{n\sqrt{n}}{log{n}})&amp;lt;/math&amp;gt;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Am putea face și o altă optimizare: să împărțim &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; doar la numerele prime de până la el. Aceasta mai reduce complexitatea într-un mod ce ar complica și mai mult formula.&lt;br /&gt;
&lt;br /&gt;
Să nu uităm un lucru important: &#039;&#039;&#039;factorizarea folosește împărțiri&#039;&#039;&#039;. Ceea ce, pentru numerele cu care lucrăm noi, introduce o constantă multiplicativă foarte mare, circa 15 până la 30, comparabilă cu optimizări de tipul &#039;&#039;O(log n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Prin comparație, ciurul lui Eratostene general are complexitatea &#039;&#039;O(n log n)&#039;&#039; și nu folosește împărțiri, iar pentru cazul testului de primalitate el poate fi adus la &#039;&#039;O(n log log n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Să ne reamintim unele din aplicațiile ciurului lui Eratostene:&lt;br /&gt;
&lt;br /&gt;
* Numărul de divizori primi (unici sau neunici) ai tuturor numerelor până la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;&lt;br /&gt;
* Suma divizorilor primi (unici sau neunici) ai tuturor numerelor până la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;&lt;br /&gt;
* Numărul de divizori ai tuturor numerelor până la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;&lt;br /&gt;
* Suma divizorilor tuturor numerelor până la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;&lt;br /&gt;
* Descompunerea în factori primi a tuturor numerelor până la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;&lt;br /&gt;
* Numărul de numere prime cu fiecare din numerele până la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzii:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* Avantaje: cod mai rapid și mai scurt.&lt;br /&gt;
* Schimbă complexitatea algoritmului: da.&lt;br /&gt;
* Timp precalculare: &#039;&#039;O(n log n)&#039;&#039; sau &#039;&#039;O( n log log n)&#039;&#039;.&lt;br /&gt;
* Memorie extra folosită: &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Suma între i și j ===&lt;br /&gt;
&lt;br /&gt;
În multe probleme apare următoarea subproblemă: dat un vector &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt; de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente întregi, să se răspundă la multe întrebări de forma &#039;&#039;&#039;care este suma elementelor vectorului între poziţiile &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; şi &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt;?&#039;&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
În acest caz vom precalcula o structură de date auxiliară, şi anume un vector &amp;lt;code&amp;gt;s[]&amp;lt;/code&amp;gt;, astfel încât &amp;lt;code&amp;gt;s[i]&amp;lt;/code&amp;gt; este suma elementelor de la 0 până la &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; (inclusiv). El se mai numeşte şi vectorul sumei prefixelor lui &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt;. Construcția acestui vector se poate face liniar deoarece avem relaţia &amp;lt;code&amp;gt;s[i] = s[i-1] + v[i]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Odată calculat acest vector putem răspunde în timp constant la întrebări. Suma elementelor între &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; şi &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt; este &amp;lt;code&amp;gt;s[j] - s[i-1]&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Complexitatea algoritmului forță brută de calcul al unei sume ar fi &#039;&#039;O(n)&#039;&#039;, pe medie. Precalcularea o reduce la &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
* Avantaje: cod mai rapid și mai scurt.&lt;br /&gt;
* Schimbă complexitatea algoritmului: da.&lt;br /&gt;
* Timp precalculare: &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
* Memorie extra folosită: &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Sumele parțiale pot fi folosite și pentru a răspunde rapid la alt tip de întrebări: &#039;&#039;dat S, care este cea mai mare poziție i în vector cu proprietatea că suma elementelor de la 0 la i este mai mică sau egală cu S?&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Sumele parțiale într-un vector cu elemente naturale au proprietatea că sunt crescătoare. De aceea putem căuta suma S binar în vectorul de sume parțiale. Răspunsul la întrebare ar fi &#039;&#039;O(n)&#039;&#039; prin forță brută. Folosind sume parțiale putem răspunde în &#039;&#039;O(log n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Suma între (l1, c1) şi (l2, c2) ===&lt;br /&gt;
&lt;br /&gt;
Este problema anterioară extinsă la matrice: avem o matrice de numere întregi şi întrebări de forma: &#039;&#039;&#039;care este suma elementelor matricei în dreptunghiul care are drept colţuri opuse (l1, c1) şi (l2, c2)?&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În mod similar vom precalcula o matrice auxiliară cu suma elementelor matricei originale între &#039;&#039;(0, 0)&#039;&#039; şi &#039;&#039;(i, j)&#039;&#039;. Ea poate fi calculată în timp constant per element, astfel:&lt;br /&gt;
&lt;br /&gt;
[[Image:matrice-numar-1-v2.gif|frame|none|Calcul matrice sume parțiale în dreptunghiuri care pornesc în origine]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;code&amp;gt;s[l][c] = s[l][c-1] + s[l-1][c] - s[l-1][c-1] + m[l][c]&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; este matricea pe care o precalculăm;&lt;br /&gt;
* &amp;lt;code&amp;gt;m&amp;lt;/code&amp;gt; este matricea cu valorile inițiale.&lt;br /&gt;
&lt;br /&gt;
Odată calculată matricea s[][] putem răspunde în timp constant la întrebări. Suma elementelor între (l1, c1) şi (l2, c2) este:&lt;br /&gt;
&lt;br /&gt;
[[Image:matrice-numar-1-dreptunghi-v2.gif|frame|none|Calcul suma numerelor în orice dreptunghi]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
: &amp;lt;code&amp;gt;S = s[l2][c2] - s[l2][c1-1] - s[l1-1][c2] + s[l1-1][c1-1]&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;S&amp;lt;/code&amp;gt; este suma căutată;&lt;br /&gt;
* &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; este matricea pe care o precalculăm.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Complexitatea algoritmului forță brută de calcul al unei sume ar fi &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;, pe medie. Precalcularea o reduce la &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
* Avantaje: cod mai rapid și mai scurt.&lt;br /&gt;
* Schimbă complexitatea algoritmului: da.&lt;br /&gt;
* Timp precalculare: &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;.&lt;br /&gt;
* Memorie extra folosită: &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul de biţi 1 într-un întreg ===&lt;br /&gt;
&lt;br /&gt;
Cum putem calcula numărul de cifre 1 din reprezentarea în baza 2 a unui număr?&lt;br /&gt;
&lt;br /&gt;
* Direct: &#039;&#039;shift and mask&#039;&#039;. Complexitate &#039;&#039;O(nr. biţi)&#039;&#039;&lt;br /&gt;
* Folosind expresia &amp;lt;code&amp;gt;x &amp;amp; (x - 1)&amp;lt;/code&amp;gt;. Complexitate &#039;&#039;O(nr. biţi)&#039;&#039;&lt;br /&gt;
* Cu precalculare: ţinem un vector în care elementul &amp;lt;code&amp;gt;v[x]&amp;lt;/code&amp;gt; este numărul de biţi 1 din reprezentarea în baza 2 a lui &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Concluzii precalculare număr de biți 1:&lt;br /&gt;
&lt;br /&gt;
* Avantaje: cod mai rapid.&lt;br /&gt;
* Schimbă complexitatea algoritmului: da.&lt;br /&gt;
* Timp precalculare: &#039;&#039;O(xMax log xMax)&#039;&#039;.&lt;br /&gt;
* Memorie extra folosită: &#039;&#039;O(2nr. biți)&#039;&#039;, sau &#039;&#039;O(xMax)&#039;&#039;. Poate fi prohibitiv dacă &#039;&#039;&#039;xMax&#039;&#039;&#039; este mare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Întrebări:&lt;br /&gt;
&lt;br /&gt;
* Se poate mai bine decât &#039;&#039;O(nr. biţi)&#039;&#039; &#039;&#039;&#039;fără memorie suplimentară&#039;&#039;&#039; (adică &#039;&#039;O(1)&#039;&#039; memorie suplimentară)?&lt;br /&gt;
* Putem adapta metoda precalculării și la numere &#039;&#039;&#039;xMax&#039;&#039;&#039; mari, gen numere întregi pe &amp;lt;code&amp;gt;int&amp;lt;/code&amp;gt;?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Vector de diferențe (difference array) ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Mai poartă denumirea și de sume de intervale sau șmenul lui Mars&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Se dă un vector cu &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente, iniţial zero. O operaţiune pe acest vector constă în incrementarea fiecărui element între indicii &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; şi &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt;. Se dau &amp;lt;code&amp;gt;m&amp;lt;/code&amp;gt; operaţiuni de executat. Să se afişeze valorile finale ale vectorului.&lt;br /&gt;
&lt;br /&gt;
Soluţia forţă brută implică execuţia celor &amp;lt;code&amp;gt;m&amp;lt;/code&amp;gt; operaţiuni. O operaţiune constă în incrementarea a maxim n elemente din vector, deci avem &amp;lt;code&amp;gt;n x m&amp;lt;/code&amp;gt; operaţii, plus afişarea elementelor finale. Complexitatea totală este &#039;&#039;O(mn)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
În următorul exemplu avem un vector inițial zero și se cere să executăm două operațiuni: operațiunea a va incrementa elementele de la pozițiile 2 la 13 și operațiunea b care va incrementa elementele de la pozițiile 6 la 9.&lt;br /&gt;
&lt;br /&gt;
[[File:mars-1D-brut.gif|frame|none|Operațiuni aplicate cu metoda forță brută]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Îmbunătățire&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Se poate mai bine? Putem optimiza timpul unei operaţiuni. Ideea este următoarea: în loc să incrementăm pe rând elementele vectorului, am putea să le grupăm. Pentru aceasta vom întârzia incrementarea, marcând doar locurile unde trebuie să adunăm.&lt;br /&gt;
&lt;br /&gt;
Cu alte cuvinte: am putea considera intervalele ca pe nişte paranteze aşezate pe o dreaptă. O paranteză deschisă înseamnă că de acum înainte trebuie să adunăm 1 la toate elementele care urmează. O paranteză închisă înseamnă că ne putem opri din adunat 1. Să presupunem că nu avem capete de interval suprapuse. Atunci am putea memora parantezele într-un vector separat. Vom memora 1 pentru o paranteză deschisă, -1 pentru o paranteză închisă şi 0 în caz că nu avem nici un capăt de interval în acel element.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ipoteză:&#039;&#039;&#039; vectorul &amp;lt;code&amp;gt;s[]&amp;lt;/code&amp;gt; de sume parţiale ale acestui vector constituie chiar rezolvarea problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Avem, deci: &amp;lt;code&amp;gt;s[i] = v[0] + v[1] + ... + v[i]&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Înseamnă că &amp;lt;code&amp;gt;v[k]&amp;lt;/code&amp;gt; se va aduna la toate sumele &amp;lt;code&amp;gt;s[k], s[k+1], ..., s[n]&amp;lt;/code&amp;gt;. Ceea ce înseamnă că dacă pe poziția &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; în vectorul &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt; avem valoarea unu, acea valoare se va propaga în toate elementele lui &amp;lt;code&amp;gt;s[]&amp;lt;/code&amp;gt; începând cu poziția &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dorim să aplicăm un interval [2, 13], deci vom plasa 1 pe poziția 2. El se va propaga până la finalul vectorului &amp;lt;code&amp;gt;s[]&amp;lt;/code&amp;gt;. Dar noi vrem ca începând cu poziția 14 acel 1 să nu se mai propage. Deoarece nu putem acest lucru, vom face altceva: vom plasa valoarea -1 pe poziția 14. Ea se va propaga, și ea, până la final de vector &amp;lt;code&amp;gt;s[]&amp;lt;/code&amp;gt;, adunându-se la fiecare element o singură dată. Ea va anula propagarea elementului 1 plasat la poziția 2.&lt;br /&gt;
&lt;br /&gt;
Similar, dacă dorim să aplicăm intervalul [6, 9] vom plasa 1 pe poziția 6 în &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt; și -1 pe poziția 10.&lt;br /&gt;
&lt;br /&gt;
După ce am plasat toate intervalele, vom calcula în &amp;lt;code&amp;gt;s[]&amp;lt;/code&amp;gt; sumele parțiale ale lui &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt;. Vectorul &amp;lt;code&amp;gt;s[]&amp;lt;/code&amp;gt; este răspunsul, valorile finale cerute.&lt;br /&gt;
&lt;br /&gt;
În fapt, nu avem nevoie de doi vectori. Putem suprascrie vectorul &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt;, deoarece nu mai avem nevoie de elementele marker.&lt;br /&gt;
&lt;br /&gt;
[[File:mars-1D-optim.gif|frame|none|Operațiuni aplicate prin precalculare]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Funcţionează acest algoritm dacă avem capete de interval suprapuse?&#039;&#039;&#039; Da, cu condiţia ca elementul în care se suprapun capete să fie suma acelor capete, adică o sumă de 1 şi -1. Aceasta înseamnă că atunci când generăm vectorul de paranteze nu vom scrie &amp;lt;code&amp;gt;s[i] = 1&amp;lt;/code&amp;gt; ci &amp;lt;code&amp;gt;s[i]++&amp;lt;/code&amp;gt; (la fel şi pentru -1, vom decrementa).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Generalizare 1:&#039;&#039;&#039; putem generaliza această metodă pentru problema în care intervalele nu adună 1, ci orice număr, constant pe parcursul intervalului. Cum generalizăm? O paranteză deschisă nu va aduna 1, ci constanta respectivă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Generalizare 2:&#039;&#039;&#039; dar dacă vectorul iniţial are valori diferite de zero? În acest caz vom calcula un vector separat, iniţializat cu zero, pe care facem calculul sumelor parţiale. Acest vector ne va spune cu cât s-a mărit fiecare element zero. În final vom aduna acest vector de sume parţiale la vectorul iniţial.&lt;br /&gt;
&lt;br /&gt;
Acest exemplu de precalculare, este cunoscut printre unii din voi drept &#039;&#039;șmenul lui Mars&#039;&#039;, denumire care îmi displace deoarece sugerează că informatica este o colecție de șmenuri.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Când nu funcţionează această soluţie?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Atunci când coordonatele intervalelor sunt prea mari pentru ca vectorul de sume parţiale să încapă în memorie. &lt;br /&gt;
&lt;br /&gt;
Să presupunem că ni se cere numărul maxim din vectorul de sume parţiale. În acest caz există o soluţie alternativă, mai generală (în sensul în care rezolvă o clasă mai mare de probleme cu intervale), care nu folosește precalculare: putem memora capetele de intervale, pe care apoi le ordonăm crescător, ţinând minte de ce tip sunt, închis sau deschis. &lt;br /&gt;
&lt;br /&gt;
Apoi le parcurgem în ordine, calculînd sumele parţiale fără să le stocăm. Suma parţială maximă este răspunsul cerut.&lt;br /&gt;
&lt;br /&gt;
Desigur că atunci când vectorul încape în memorie, e mai uşoară precalcularea.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
* Avantaje: cod mai rapid.&lt;br /&gt;
* Schimbă complexitatea algoritmului: da.&lt;br /&gt;
* Timp precalculare: &#039;&#039;O(k) - k&#039;&#039; numărul de intervale.&lt;br /&gt;
* Memorie extra folosită: &#039;&#039;O(MAXCOORD)&#039;&#039;. Poate fi prohibitiv dacă &#039;&#039;&#039;MAXCOORD&#039;&#039;&#039; este mare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Matrice de differențe (difference arrays 2D) ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Mai poartă denumirea de sume de dreptunghiuri&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Acest tip de precalculare se poate extinde în două dimensiuni, pe matrice: se dau &amp;lt;code&amp;gt;m&amp;lt;/code&amp;gt; dreptunghiuri care adună 1 la toate elementele respective dintr-o matrice. De data aceasta vom folosi o matrice de sume parțiale, ce necesită &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
Nu voi detalia algoritmul, iată partea de plasare a unui dreptunghi &#039;&#039;(l1, c1)&#039;&#039; -&amp;gt; &#039;&#039;(l2, c2)&#039;&#039;. Desenul arată punctele unde se adună/scade 1:&lt;br /&gt;
&lt;br /&gt;
[[File:mars-2D-optim.gif|frame|none|Operațiuni aplicate prin precalculare]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Această metodă nu se justifică decât în măsura în care este facil de programat, deoarece avem o soluție de aceeași complexitate care folosește varianta 1D, pe vector, folosind doar &#039;&#039;O(n)&#039;&#039; memorie, ca la problema [https://www.nerdarena.ro/problema/flori1 flori1].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Exemple de probleme cu precalculare ==&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/paisprezece Paisprezece]: necesită un vector ce păstrează numere prime, ciurul lui Eratostene.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/intervale Intervale]: ciurului lui Eratostene, sume parţiale.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/reginald Reginald], o optimizare a problemei anterioare.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/puncte1 Puncte1]: o rezolvare posibilă folosește precalcularea sumelor parţiale ale unei matrice&lt;br /&gt;
* [https://www.nerdarena.ro/problema/extraterestri Extraterestri] precalculare vector sume prefixe parțiale (a.k.a. șmenul lui Mars)&lt;br /&gt;
* [https://www.nerdarena.ro/problema/extraterestri1 Extraterestri1] Mars + normalizare coordonate&lt;br /&gt;
* [https://www.nerdarena.ro/problema/flori Flori] Precalculare matrice sume prefixe parţiale (a.k.a. şmenul lui Mars 2D)&lt;br /&gt;
* [https://www.nerdarena.ro/problema/flori1 Flori1] exemplu de problemă nerezolvabilă cu precalculare 2D din lipsă de memorie&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Concluzii ==&lt;br /&gt;
&lt;br /&gt;
* Precalcularea foloseşte atunci când avem de răspuns la mai multe întrebări costisitoare. Aceste întrebări pot fi explicite, cerute de enunţ, sau implicite, decurgând din metoda de rezolvare.&lt;br /&gt;
* Precalcularea poate folosi şi atunci când numărul de întrebări este mult mai mare decât numărul de răspunsuri posibile, caz în care putem precalcula toate răspunsurile într-un tabel înainte de a răspunde la întrebări. Această precalculare seamănă cu memoizarea. Diferența constă în faptul că precalcularea este executată explicit înainte de calcul, pe când memoizarea ține minte valori calculate în timpul calculului.&lt;br /&gt;
* Precalcularea foloseşte o structură de date adiţională şi memorie suplimentară. Uneori putem economisi memorie folosindu-ne chiar de structura de date iniţială.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 5 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/intervale Intervale] dată pregătire OJI 2013 clasa a 9-a la Vianu&lt;br /&gt;
* [https://www.nerdarena.ro/problema/patrate1 Patrate1] dată la cercul de informatică Vianu 2013 clasa a 6-a rezolvată în cel mult &#039;&#039;O(n&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;)&#039;&#039;!&lt;br /&gt;
* [https://www.nerdarena.ro/problema/extraterestri Extraterestri] dată la OJI 2012 clasa a 8-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională  ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/reginald Reginald] dată la pregătire olimpiadă 2013 clasele 7/8/9/10 la Vianu&lt;br /&gt;
* [https://www.nerdarena.ro/problema/flori1 Flori1] dată la pregătire baraj gimnaziu 2013 la Vianu&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Opțional, gândiți-vă la următoarele întrebări - calcul număr biți 1 în reprezentarea binară a unui întreg:&lt;br /&gt;
&lt;br /&gt;
* Se poate mai bine decât &#039;&#039;O(nr. biţi)&#039;&#039; fără memorie suplimentară (adică &#039;&#039;O(1)&#039;&#039; memorie suplimentară)?&lt;br /&gt;
* Putem adapta metoda precalculării și la numere &#039;&#039;&#039;xMax&#039;&#039;&#039; mari, gen numere întregi pe &amp;lt;code&amp;gt;int&amp;lt;/code&amp;gt;?&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php/Clasa_a_7-a_Lec%C8%9Bia_5:_Precalculare Accesează rezolvarea temei 5]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_4:_Liste_(2)&amp;diff=18568</id>
		<title>Clasa a 7-a Lecția 4: Liste (2)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_4:_Liste_(2)&amp;diff=18568"/>
		<updated>2026-02-06T15:56:38Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video curs ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/WvM90m3wRPo|||||start=4835&amp;amp;end=8880&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Operații cu liste ==&lt;br /&gt;
&lt;br /&gt;
Să vedem câteva exemple de manipulare a listelor. În toate aceste exemple vom considera liste de întregi reprezentate astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int key[MAXL];  // valori intregi memorate de lista&lt;br /&gt;
int next[MAXL]; // următorul element in lista&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Lista este definită de prima sa celulă:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int l;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Lungimea unei liste===&lt;br /&gt;
&lt;br /&gt;
Să se calculeze lungimea unei liste.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// returneaza numarul de elemente al unei liste&lt;br /&gt;
int listLen( int l ) {&lt;br /&gt;
  int len = 0;         // initial lungimea este zero&lt;br /&gt;
&lt;br /&gt;
  while ( l != NIL ) { // l pointeaza la o celula?&lt;br /&gt;
    len++;             // numaram acea celula&lt;br /&gt;
    l = next[l];       // si avansam la urmatoarea celula&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return len; // returnam numarul de celule numarate&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Căutare liniară element in listă===&lt;br /&gt;
&lt;br /&gt;
Să se caute indicele (pointerul) primei celule ce conține un element.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// returneaza indicele celulei care contine elementul e&lt;br /&gt;
// sau NIL daca el nu exista&lt;br /&gt;
int listFind( int e, int l ) {&lt;br /&gt;
  while ( l != NIL &amp;amp;&amp;amp; key[l] != e ) // avem o celula in l si nu contine e?&lt;br /&gt;
    l = next[l];                    // avansam la urmatoarea celula&lt;br /&gt;
&lt;br /&gt;
  return l; // l va fi fie celula elementului gasit, fie NIL cind negasit&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Răsturnare listă===&lt;br /&gt;
&lt;br /&gt;
Să se răstoarne o listă. La final lista inițială va fi distrusă. Lista cea nouă va conține exact celulele listei originale, dar cu elementele în ordine inversă.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// rastoarna lista primita si returneaza lista rasturnata&lt;br /&gt;
// distruge lista initiala&lt;br /&gt;
int listReverse( int l ) {&lt;br /&gt;
  int cell,  // pointer la o celula&lt;br /&gt;
    r = NIL; // lista noua, cea rasturnata&lt;br /&gt;
    &lt;br /&gt;
  while ( l != NIL ) {&lt;br /&gt;
    cell = l;       // memoram celula curenta, capul listei l&lt;br /&gt;
    l = next[l];    // avansam in lista l&lt;br /&gt;
    next[cell] = r; // adaugam celula curenta in capul listei r&lt;br /&gt;
    r = cell;       // cell devine lista r&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return r;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Eliminare duplicate în listă sortată crescător===&lt;br /&gt;
&lt;br /&gt;
Dată o listă ale cărei elemente sunt ordonate crescător, să se transforme în listă mulțime prin eliminarea duplicatelor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// transforma o lista ordonata crescator intr-o lista multime&lt;br /&gt;
// prin eliminarea duplicatelor&lt;br /&gt;
// nu schimba elementul initial al listei&lt;br /&gt;
void listSet( int l ) {&lt;br /&gt;
  if ( l != NIL ) {&lt;br /&gt;
    while ( next[l] != NIL )&lt;br /&gt;
      if ( key[next[l]] == key[l] ) // elementul urm este egal cu cel curent?&lt;br /&gt;
        next[l] = next[next[l]];    // daca da, elimina elementul urmator&lt;br /&gt;
      else&lt;br /&gt;
        l = next[l];                // altfel avanseaza la elementul urmator&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Exemplu de folosire a funcțiilor de mai sus===&lt;br /&gt;
&lt;br /&gt;
Iată un program care folosește funcțiile de mai sus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// Exemple de implementare diverse operatii elementare pe liste&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define NIL -1&lt;br /&gt;
#define MAXL 1000&lt;br /&gt;
&lt;br /&gt;
int key[MAXL];  // valori intregi memorate de lista&lt;br /&gt;
int next[MAXL]; // următorul element in lista&lt;br /&gt;
&lt;br /&gt;
// returneaza numarul de elemente al unei liste&lt;br /&gt;
int listLen( int l ) {&lt;br /&gt;
  int len = 0;         // initial lungimea este zero&lt;br /&gt;
&lt;br /&gt;
  while ( l != NIL ) { // l pointeaza la o celula?&lt;br /&gt;
    len++;             // numaram acea celula&lt;br /&gt;
    l = next[l];       // si avansam la urmatoarea celula&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return len; // returnam numarul de celule numarate&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza indicele celulei care contine elementul e&lt;br /&gt;
// sau NIL daca el nu exista&lt;br /&gt;
int listFind( int e, int l ) {&lt;br /&gt;
  while ( l != NIL &amp;amp;&amp;amp; key[l] != e ) // avem o celula in l si nu contine e?&lt;br /&gt;
    l = next[l];                    // avansam la urmatoarea celula&lt;br /&gt;
&lt;br /&gt;
  return l; // l va fi fie celula elementului gasit, fie NIL cind negasit&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// rastoarna lista primita si returneaza lista rasturnata&lt;br /&gt;
// distruge lista initiala&lt;br /&gt;
int listReverse( int l ) {&lt;br /&gt;
  int cell,  // pointer la o celula&lt;br /&gt;
    r = NIL; // lista noua, cea rasturnata&lt;br /&gt;
    &lt;br /&gt;
  while ( l != NIL ) {&lt;br /&gt;
    cell = l;       // memoram celula curenta, capul listei l&lt;br /&gt;
    l = next[l];    // avansam in lista l&lt;br /&gt;
    next[cell] = r; // adaugam celula curenta in capul listei r&lt;br /&gt;
    r = cell;       // cell devine lista r&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return r;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// transforma o lista ordonata crescator intr-o lista multime&lt;br /&gt;
// prin eliminarea duplicatelor&lt;br /&gt;
// nu schimba elementul initial al listei&lt;br /&gt;
void listSet( int l ) {&lt;br /&gt;
  if ( l != NIL ) {&lt;br /&gt;
    while ( next[l] != NIL )&lt;br /&gt;
      if ( key[next[l]] == key[l] ) // elementul urm este egal cu cel curent?&lt;br /&gt;
        next[l] = next[next[l]];    // daca da, elimina elementul urmator&lt;br /&gt;
      else&lt;br /&gt;
        l = next[l];                // altfel avanseaza la elementul urmator&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// tipareste elementele unei liste cu un spatiu intre ele&lt;br /&gt;
void listPrint( int l, FILE *f ) {&lt;br /&gt;
  while ( l != NIL ) {&lt;br /&gt;
    fprintf( f, &amp;quot;%d &amp;quot;, key[l] );&lt;br /&gt;
    l = next[l];&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, i, e1, e2, l, r;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;lista.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;n, &amp;amp;e1, &amp;amp;e2 );&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    l = NIL;&lt;br /&gt;
  else {&lt;br /&gt;
    l = 0;&lt;br /&gt;
    for ( i = 0; i &amp;lt; n; i++ ) {&lt;br /&gt;
      fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;key[i] );&lt;br /&gt;
      next[i] = i + 1;&lt;br /&gt;
    }&lt;br /&gt;
    next[n - 1] = NIL;&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  &lt;br /&gt;
  fout = fopen( &amp;quot;lista.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;lista citita: [&amp;quot; );&lt;br /&gt;
  listPrint( l, fout );&lt;br /&gt;
  fprintf( fout, &amp;quot;]\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;lungime: %d\n&amp;quot;, listLen( l ) );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;celula cu cheie %d are indice %d\n&amp;quot;, e1, listFind( e1, l ) );&lt;br /&gt;
  fprintf( fout, &amp;quot;celula cu cheie %d are indice %d\n&amp;quot;, e2, listFind( e2, l ) );&lt;br /&gt;
&lt;br /&gt;
  r = listReverse( l );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;lista inversata: [&amp;quot; );&lt;br /&gt;
  listPrint( r, fout );&lt;br /&gt;
  fprintf( fout, &amp;quot;]\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  listSet( r );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;lista multime: [&amp;quot; );&lt;br /&gt;
  listPrint( r, fout );&lt;br /&gt;
  fprintf( fout, &amp;quot;]\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;lungime: %d\n&amp;quot;, listLen( r ) );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Programul de mai sus a fost executat pe următorul fișier de intrare, lista.in:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
10 6 1&lt;br /&gt;
9 9 7 7 6 5 5 4 3 3 2&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Exemple de probleme rezolvate cu liste ==&lt;br /&gt;
&lt;br /&gt;
===Problema Onigim===&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/onigim Onigim] a fost dată la ONI 2013 clasa a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;. Ea admite o rezolvare foarte scurtă cu liste, desigur peste nivelul clasei a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Observăm că:&lt;br /&gt;
&lt;br /&gt;
* Toți elevii care au același număr de elevi &#039;&#039;sub ei&#039;&#039; vor avea același punctaj.&lt;br /&gt;
* Elevii pot fi, deci, împărțiți în grupuri de elevi cu același punctaj.&lt;br /&gt;
* Numărul de grupuri de elevi este exact numărul de punctaje distincte.&lt;br /&gt;
* Deoarece punctajele distincte se dau în ordine crescătoare, un algoritm banal ar fi să parcurgem grupurile de elevi în ordinea crescătoare a numărului de elevi &#039;&#039;sub&#039;&#039; acel grup.&lt;br /&gt;
* Toți elevii dintr-un grup primesc nota punctajului curent, apoi avansăm la următorul grup.&lt;br /&gt;
&lt;br /&gt;
Cum implementăm acest algoritm destul de ușor? Trebuie să memorăm cumva aceste grupuri. Astfel, un grup este format din ID-uri, iar toate ID-urile au ceva în comun, care este unic: numărul de elevi &#039;&#039;sub&#039;&#039; acele ID-uri.&lt;br /&gt;
&lt;br /&gt;
Astfel ne vine ideea să folosim liste: ce-ar fi ca pentru fiecare număr posibil de elevi &#039;&#039;sub&#039;&#039; să menținem câte o listă de ID-uri? Dacă pentru un număr de elevi &#039;&#039;sub&#039;&#039; nu există ID-uri lista va fi vidă.&lt;br /&gt;
&lt;br /&gt;
Iată gruparea de ID-uri de elevi pe liste, conform exemplului din problemă:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|&lt;br /&gt;
&amp;lt;pre&amp;gt;6 4&lt;br /&gt;
100 150 175 200&lt;br /&gt;
4 2 0 0 3 4&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:lista-onigim.gif|frame|none|Exemplul din problema onigim - concept structură de date]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Explicații&#039;&#039;&#039;: avem patru punctaje distincte obținute, 100 150 175 și 200. Avem șase elevi numerotați de la 1 la 6. În desen observăm că avem o listă de doi copii care au zero copii &#039;&#039;sub&#039;&#039; ei, anume cei cu ID-urile 4 și 3. De aceea ei sunt &#039;&#039;agățați&#039;&#039; la lista zero. Avem alți doi copii care au patru copii &#039;&#039;sub&#039;&#039; ei, cu ID-urile 1 și 6. Ei sunt &#039;&#039;agățați&#039;&#039; la lista patru. Și tot așa, mai avem câte un singur copil în grupurile 2 și 3.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cum rezolvăm problema&#039;&#039;&#039; pe baza acestei structuri? Printr-o parcurgere a listelor în ordine, de sus în jos și de la stînga la dreapta. Toate ID-urile din prima listă, 3 și 4, vor primi primul punctaj, adică 100. Toate ID-urile din a doua listă, adică 2, vor primi următorul punctaj, adică 150. Și așa mai departe. Printr-o singură parcurgere putem rezolva toate cele trei puncte ale problemei. Succes! :-)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;De remarcat&#039;&#039;&#039;: o implementare îngrijită a acestui algoritm va avea circa 50 de linii, iar ca linii de program efectiv probabil 40.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Problema Macheta===&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/macheta Macheta] a fost dată la ONI 2011 clasa a 8-a. Ea admite o rezolvare foarte scurtă cu liste, care nu apare printre rezolvările oficiale.&lt;br /&gt;
&lt;br /&gt;
Observăm că:&lt;br /&gt;
&lt;br /&gt;
* O clădire &#039;&#039;C&#039;&#039; este vizibilă dacă există o coordonată &#039;&#039;x&#039;&#039; pe care se află numai clădiri mai mici, în fața clădirii &#039;&#039;C&#039;&#039;.&lt;br /&gt;
* Putem, astfel, verifica vizibilitatea unei clădiri verificând vizibilitatea fiecărei coordonate &#039;&#039;x&#039;&#039; acoperite de ea.&lt;br /&gt;
&lt;br /&gt;
Deja avem un algoritm rezonabil: pentru fiecare clădire &#039;&#039;C&#039;&#039;, parcurgem fiecare coordonată &#039;&#039;x&#039;&#039; a ei, iar pentru fiecare coordonată verificăm toate clădirile &#039;&#039;B&#039;&#039; dacă:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;B&#039;&#039; este în fața lui &#039;&#039;C&#039;&#039; (&#039;&#039;y&#039;&#039; mai mic)&lt;br /&gt;
* &#039;&#039;B&#039;&#039; acoperă coordonata &#039;&#039;x&#039;&#039;&lt;br /&gt;
* &#039;&#039;B&#039;&#039; are înălțime mai mare ca &#039;&#039;C&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Pentru fiecare clădire vom parcurge fiecare coordonată a ei, apoi fiecare altă clădire. Rezultă o complexitate &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; L&amp;lt;sub&amp;gt;x&amp;lt;/sub&amp;gt;)&#039;&#039;. După înlocuiri rezultă circa 10 milioane de operații, pare că algoritmul acesta banal s-ar încadra în timp.&lt;br /&gt;
&lt;br /&gt;
Dar să mergem mai departe cu observațiile:&lt;br /&gt;
&lt;br /&gt;
* Deoarece vom verifica doar clădirile din fața lui &#039;&#039;C&#039;&#039;, ne interesează doar clădirile cu &#039;&#039;y&#039;&#039; mai mic.&lt;br /&gt;
* Pare a fi util să parcurgem clădirile în ordinea crescătoare a lui &#039;&#039;y&#039;&#039;.&lt;br /&gt;
* Pentru fiecare coordonată &#039;&#039;x&#039;&#039; a clădirii &#039;&#039;C&#039;&#039; curente ne interesează maximul de înălțime de pînă acum, la acea coordonată.&lt;br /&gt;
* Astfel, putem memora un vector de înălțimi maxime, care pentru fiecare coordonată &#039;&#039;x&#039;&#039; memorează cea mai înaltă clădire vizibilă la coordonata &#039;&#039;x&#039;&#039;, pînă la acest moment.&lt;br /&gt;
&lt;br /&gt;
Cum implementăm acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Cumva trebuie să parcurgem clădirile în ordinea crescătoare a lui &#039;&#039;y&#039;&#039;. Soluțiile oficiale sar imediat la sortare. Dar să nu ne grăbim! Sortarea este lentă, ar fi bine să o evităm dacă putem.&lt;br /&gt;
&lt;br /&gt;
Și desigur că putem: ce-ar fi să păstram pentru fiecare coordonată &#039;&#039;y&#039;&#039; o listă de clădiri ce încep la acea coordonată? Apoi parcurgem clădirile în ordinea lui &#039;&#039;y&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Iată gruparea de clădiri pe liste, conform exemplului din problemă:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:macheta.jpg|frame|none|Exemplul din problema macheta - desen]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:lista-macheta.gif|frame|none|Exemplul din problema macheta - concept structură de date]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Explicații&#039;&#039;&#039;: avem o clădire care începe la &#039;&#039;y&#039;&#039;=1 - clădirea 3. Avem o clădire care începe la &#039;&#039;y&#039;&#039;=2 - clădirea 2. Avem două clădiri care încep la &#039;&#039;y&#039;&#039;=3 - 4 și 5. Și o clădire care începe la &#039;&#039;y&#039;&#039;=6 - clădirea 1.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cum rezolvăm problema&#039;&#039;&#039; pe baza acestei structuri? Printr-o parcurgere a listelor în ordine, de sus în jos și de la stânga la dreapta. Pentru fiecare clădire &#039;&#039;C&#039;&#039; parcurgem coordonatele ei &#039;&#039;x&#039;&#039;, verificând vectorul de înălțimi &amp;lt;&amp;lt;code&amp;gt;h[x]&amp;lt;/code&amp;gt;. Dacă măcar una din înălțimi este mai mică, clădirea este vizibilă. Atenție, înălțimile trebuie actualizate dacă înălțimea lui &#039;&#039;C&#039;&#039; este mai mare decît cea a lui &amp;lt;code&amp;gt;h[x]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;De remarcat&#039;&#039;&#039;: o implementare îngrijită a acestui algoritm va avea circa 50 de linii, iar ca linii de program efectiv probabil 40.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Construcția listelor este &#039;&#039;O(N)&#039;&#039;, numărul de clădiri. Parcurgerea listelor este &#039;&#039;O(N + L&amp;lt;sub&amp;gt;y&amp;lt;/sub&amp;gt;)&#039;&#039;. Pentru fiecare clădire vom prelucra în timp constant fiecare coordonată &#039;&#039;x&#039;&#039; a sa. Rezultă complexitatea finală &#039;&#039;O(N L&amp;lt;sub&amp;gt;x&amp;lt;/sub&amp;gt; + L&amp;lt;sub&amp;gt;y&amp;lt;/sub&amp;gt;)&#039;&#039;. După înlocuiri rezultă circa 100 000 de operații. Memoria va fi &#039;&#039;O(L&amp;lt;sub&amp;gt;x&amp;lt;/sub&amp;gt;)&#039;&#039;, adică neglijabilă.&lt;br /&gt;
&lt;br /&gt;
Din nou, o problemă intenționată spre a fi grea devine simplă prin folosirea listelor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 4 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]), folosind liste!&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/onigim Onigim], dată la ONI 2013 clasa a 5-a;&lt;br /&gt;
* [https://www.nerdarena.ro/problema/macheta Macheta], dată la ONI 2011 clasa a 8-a;&lt;br /&gt;
* [https://www.nerdarena.ro/problema/criptic Criptic].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Tema opțională==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Detecție listă cu buclă&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Să se determine dacă o listă se întoarce la ea însăși (unul din elemente pointează la o celulă din-naintea lui). Nu aveți voie să vă legați de valorile memorate, ele pot fi duplicat. Vă puteți folosi strict de legăturile listei, vectorul &amp;lt;code&amp;gt;next[]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Vă rămâne ca temă de gândire:&lt;br /&gt;
&lt;br /&gt;
* Ușor: &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; - unde &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este numărul de elemente al listei&lt;br /&gt;
* Greu: &#039;&#039;O(n)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_4:_Liste_(2) Accesează rezolvarea temei 4]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_4:_Liste_(2)&amp;diff=18567</id>
		<title>Clasa a 7-a Lecția 4: Liste (2)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_4:_Liste_(2)&amp;diff=18567"/>
		<updated>2026-02-06T15:55:30Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video curs ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/WvM90m3wRPo|||||start=4835&amp;amp;end=8880&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Operații cu liste ==&lt;br /&gt;
&lt;br /&gt;
Să vedem câteva exemple de manipulare a listelor. În toate aceste exemple vom considera liste de întregi reprezentate astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int key[MAXL];  // valori intregi memorate de lista&lt;br /&gt;
int next[MAXL]; // următorul element in lista&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Lista este definită de prima sa celulă:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int l;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Lungimea unei liste===&lt;br /&gt;
&lt;br /&gt;
Să se calculeze lungimea unei liste.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// returneaza numarul de elemente al unei liste&lt;br /&gt;
int listLen( int l ) {&lt;br /&gt;
  int len = 0;         // initial lungimea este zero&lt;br /&gt;
&lt;br /&gt;
  while ( l != NIL ) { // l pointeaza la o celula?&lt;br /&gt;
    len++;             // numaram acea celula&lt;br /&gt;
    l = next[l];       // si avansam la urmatoarea celula&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return len; // returnam numarul de celule numarate&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Căutare liniară element in listă===&lt;br /&gt;
&lt;br /&gt;
Să se caute indicele (pointerul) primei celule ce conține un element.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// returneaza indicele celulei care contine elementul e&lt;br /&gt;
// sau NIL daca el nu exista&lt;br /&gt;
int listFind( int e, int l ) {&lt;br /&gt;
  while ( l != NIL &amp;amp;&amp;amp; key[l] != e ) // avem o celula in l si nu contine e?&lt;br /&gt;
    l = next[l];                    // avansam la urmatoarea celula&lt;br /&gt;
&lt;br /&gt;
  return l; // l va fi fie celula elementului gasit, fie NIL cind negasit&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Răsturnare listă===&lt;br /&gt;
&lt;br /&gt;
Să se răstoarne o listă. La final lista inițială va fi distrusă. Lista cea nouă va conține exact celulele listei originale, dar cu elementele în ordine inversă.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// rastoarna lista primita si returneaza lista rasturnata&lt;br /&gt;
// distruge lista initiala&lt;br /&gt;
int listReverse( int l ) {&lt;br /&gt;
  int cell,  // pointer la o celula&lt;br /&gt;
    r = NIL; // lista noua, cea rasturnata&lt;br /&gt;
    &lt;br /&gt;
  while ( l != NIL ) {&lt;br /&gt;
    cell = l;       // memoram celula curenta, capul listei l&lt;br /&gt;
    l = next[l];    // avansam in lista l&lt;br /&gt;
    next[cell] = r; // adaugam celula curenta in capul listei r&lt;br /&gt;
    r = cell;       // cell devine lista r&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return r;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Eliminare duplicate în listă sortată crescător===&lt;br /&gt;
&lt;br /&gt;
Dată o listă ale cărei elemente sunt ordonate crescător, să se transforme în listă mulțime prin eliminarea duplicatelor.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// transforma o lista ordonata crescator intr-o lista multime&lt;br /&gt;
// prin eliminarea duplicatelor&lt;br /&gt;
// nu schimba elementul initial al listei&lt;br /&gt;
void listSet( int l ) {&lt;br /&gt;
  if ( l != NIL ) {&lt;br /&gt;
    while ( next[l] != NIL )&lt;br /&gt;
      if ( key[next[l]] == key[l] ) // elementul urm este egal cu cel curent?&lt;br /&gt;
        next[l] = next[next[l]];    // daca da, elimina elementul urmator&lt;br /&gt;
      else&lt;br /&gt;
        l = next[l];                // altfel avanseaza la elementul urmator&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Exemplu de folosire a funcțiilor de mai sus===&lt;br /&gt;
&lt;br /&gt;
Iată un program care folosește funcțiile de mai sus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// Exemple de implementare diverse operatii elementare pe liste&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define NIL -1&lt;br /&gt;
#define MAXL 1000&lt;br /&gt;
&lt;br /&gt;
int key[MAXL];  // valori intregi memorate de lista&lt;br /&gt;
int next[MAXL]; // următorul element in lista&lt;br /&gt;
&lt;br /&gt;
// returneaza numarul de elemente al unei liste&lt;br /&gt;
int listLen( int l ) {&lt;br /&gt;
  int len = 0;         // initial lungimea este zero&lt;br /&gt;
&lt;br /&gt;
  while ( l != NIL ) { // l pointeaza la o celula?&lt;br /&gt;
    len++;             // numaram acea celula&lt;br /&gt;
    l = next[l];       // si avansam la urmatoarea celula&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return len; // returnam numarul de celule numarate&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza indicele celulei care contine elementul e&lt;br /&gt;
// sau NIL daca el nu exista&lt;br /&gt;
int listFind( int e, int l ) {&lt;br /&gt;
  while ( l != NIL &amp;amp;&amp;amp; key[l] != e ) // avem o celula in l si nu contine e?&lt;br /&gt;
    l = next[l];                    // avansam la urmatoarea celula&lt;br /&gt;
&lt;br /&gt;
  return l; // l va fi fie celula elementului gasit, fie NIL cind negasit&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// rastoarna lista primita si returneaza lista rasturnata&lt;br /&gt;
// distruge lista initiala&lt;br /&gt;
int listReverse( int l ) {&lt;br /&gt;
  int cell,  // pointer la o celula&lt;br /&gt;
    r = NIL; // lista noua, cea rasturnata&lt;br /&gt;
    &lt;br /&gt;
  while ( l != NIL ) {&lt;br /&gt;
    cell = l;       // memoram celula curenta, capul listei l&lt;br /&gt;
    l = next[l];    // avansam in lista l&lt;br /&gt;
    next[cell] = r; // adaugam celula curenta in capul listei r&lt;br /&gt;
    r = cell;       // cell devine lista r&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return r;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// transforma o lista ordonata crescator intr-o lista multime&lt;br /&gt;
// prin eliminarea duplicatelor&lt;br /&gt;
// nu schimba elementul initial al listei&lt;br /&gt;
void listSet( int l ) {&lt;br /&gt;
  if ( l != NIL ) {&lt;br /&gt;
    while ( next[l] != NIL )&lt;br /&gt;
      if ( key[next[l]] == key[l] ) // elementul urm este egal cu cel curent?&lt;br /&gt;
        next[l] = next[next[l]];    // daca da, elimina elementul urmator&lt;br /&gt;
      else&lt;br /&gt;
        l = next[l];                // altfel avanseaza la elementul urmator&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// tipareste elementele unei liste cu un spatiu intre ele&lt;br /&gt;
void listPrint( int l, FILE *f ) {&lt;br /&gt;
  while ( l != NIL ) {&lt;br /&gt;
    fprintf( f, &amp;quot;%d &amp;quot;, key[l] );&lt;br /&gt;
    l = next[l];&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, i, e1, e2, l, r;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;lista.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;n, &amp;amp;e1, &amp;amp;e2 );&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    l = NIL;&lt;br /&gt;
  else {&lt;br /&gt;
    l = 0;&lt;br /&gt;
    for ( i = 0; i &amp;lt; n; i++ ) {&lt;br /&gt;
      fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;key[i] );&lt;br /&gt;
      next[i] = i + 1;&lt;br /&gt;
    }&lt;br /&gt;
    next[n - 1] = NIL;&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  &lt;br /&gt;
  fout = fopen( &amp;quot;lista.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;lista citita: [&amp;quot; );&lt;br /&gt;
  listPrint( l, fout );&lt;br /&gt;
  fprintf( fout, &amp;quot;]\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;lungime: %d\n&amp;quot;, listLen( l ) );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;celula cu cheie %d are indice %d\n&amp;quot;, e1, listFind( e1, l ) );&lt;br /&gt;
  fprintf( fout, &amp;quot;celula cu cheie %d are indice %d\n&amp;quot;, e2, listFind( e2, l ) );&lt;br /&gt;
&lt;br /&gt;
  r = listReverse( l );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;lista inversata: [&amp;quot; );&lt;br /&gt;
  listPrint( r, fout );&lt;br /&gt;
  fprintf( fout, &amp;quot;]\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  listSet( r );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;lista multime: [&amp;quot; );&lt;br /&gt;
  listPrint( r, fout );&lt;br /&gt;
  fprintf( fout, &amp;quot;]\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  fprintf( fout, &amp;quot;lungime: %d\n&amp;quot;, listLen( r ) );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Programul de mai sus a fost executat pe următorul fișier de intrare, lista.in:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
10 6 1&lt;br /&gt;
9 9 7 7 6 5 5 4 3 3 2&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Exemple de probleme rezolvate cu liste ==&lt;br /&gt;
&lt;br /&gt;
===Problema Onigim===&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/onigim Onigim] a fost dată la ONI 2013 clasa a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;. Ea admite o rezolvare foarte scurtă cu liste, desigur peste nivelul clasei a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Observăm că:&lt;br /&gt;
&lt;br /&gt;
* Toți elevii care au același număr de elevi &#039;&#039;sub ei&#039;&#039; vor avea același punctaj.&lt;br /&gt;
* Elevii pot fi, deci, împărțiți în grupuri de elevi cu același punctaj.&lt;br /&gt;
* Numărul de grupuri de elevi este exact numărul de punctaje distincte.&lt;br /&gt;
* Deoarece punctajele distincte se dau în ordine crescătoare, un algoritm banal ar fi să parcurgem grupurile de elevi în ordinea crescătoare a numărului de elevi &#039;&#039;sub&#039;&#039; acel grup.&lt;br /&gt;
* Toți elevii dintr-un grup primesc nota punctajului curent, apoi avansăm la următorul grup.&lt;br /&gt;
&lt;br /&gt;
Cum implementăm acest algoritm destul de ușor? Trebuie să memorăm cumva aceste grupuri. Astfel, un grup este format din ID-uri, iar toate ID-urile au ceva în comun, care este unic: numărul de elevi &#039;&#039;sub&#039;&#039; acele ID-uri.&lt;br /&gt;
&lt;br /&gt;
Astfel ne vine ideea să folosim liste: ce-ar fi ca pentru fiecare număr posibil de elevi &#039;&#039;sub&#039;&#039; să menținem câte o listă de ID-uri? Dacă pentru un număr de elevi &#039;&#039;sub&#039;&#039; nu există ID-uri lista va fi vidă.&lt;br /&gt;
&lt;br /&gt;
Iată gruparea de ID-uri de elevi pe liste, conform exemplului din problemă:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|&lt;br /&gt;
&amp;lt;pre&amp;gt;6 4&lt;br /&gt;
100 150 175 200&lt;br /&gt;
4 2 0 0 3 4&amp;lt;/pre&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:lista-onigim.gif|frame|none|Exemplul din problema onigim - concept structură de date]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Explicații&#039;&#039;&#039;: avem patru punctaje distincte obținute, 100 150 175 și 200. Avem șase elevi numerotați de la 1 la 6. În desen observăm că avem o listă de doi copii care au zero copii &#039;&#039;sub&#039;&#039; ei, anume cei cu ID-urile 4 și 3. De aceea ei sunt &#039;&#039;agățați&#039;&#039; la lista zero. Avem alți doi copii care au patru copii &#039;&#039;sub&#039;&#039; ei, cu ID-urile 1 și 6. Ei sunt &#039;&#039;agățați&#039;&#039; la lista patru. Și tot așa, mai avem câte un singur copil în grupurile 2 și 3.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cum rezolvăm problema&#039;&#039;&#039; pe baza acestei structuri? Printr-o parcurgere a listelor în ordine, de sus în jos și de la stînga la dreapta. Toate ID-urile din prima listă, 3 și 4, vor primi primul punctaj, adică 100. Toate ID-urile din a doua listă, adică 2, vor primi următorul punctaj, adică 150. Și așa mai departe. Printr-o singură parcurgere putem rezolva toate cele trei puncte ale problemei. Succes! :-)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;De remarcat&#039;&#039;&#039;: o implementare îngrijită a acestui algoritm va avea circa 50 de linii, iar ca linii de program efectiv probabil 40.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Problema Macheta===&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/macheta Macheta] a fost dată la ONI 2011 clasa a 8-a. Ea admite o rezolvare foarte scurtă cu liste, care nu apare printre rezolvările oficiale.&lt;br /&gt;
&lt;br /&gt;
Observăm că:&lt;br /&gt;
&lt;br /&gt;
* O clădire &#039;&#039;C&#039;&#039; este vizibilă dacă există o coordonată &#039;&#039;x&#039;&#039; pe care se află numai clădiri mai mici, în fața clădirii &#039;&#039;C&#039;&#039;.&lt;br /&gt;
* Putem, astfel, verifica vizibilitatea unei clădiri verificând vizibilitatea fiecărei coordonate &#039;&#039;x&#039;&#039; acoperite de ea.&lt;br /&gt;
&lt;br /&gt;
Deja avem un algoritm rezonabil: pentru fiecare clădire &#039;&#039;C&#039;&#039;, parcurgem fiecare coordonată &#039;&#039;x&#039;&#039; a ei, iar pentru fiecare coordonată verificăm toate clădirile &#039;&#039;B&#039;&#039; dacă:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;B&#039;&#039; este în fața lui &#039;&#039;C&#039;&#039; (&#039;&#039;y&#039;&#039; mai mic)&lt;br /&gt;
* &#039;&#039;B&#039;&#039; acoperă coordonata &#039;&#039;x&#039;&#039;&lt;br /&gt;
* &#039;&#039;B&#039;&#039; are înălțime mai mare ca &#039;&#039;C&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Pentru fiecare clădire vom parcurge fiecare coordonată a ei, apoi fiecare altă clădire. Rezultă o complexitate &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; L&amp;lt;sub&amp;gt;x&amp;lt;/sub&amp;gt;)&#039;&#039;. După înlocuiri rezultă circa 10 milioane de operații, pare că algoritmul acesta banal s-ar încadra în timp.&lt;br /&gt;
&lt;br /&gt;
Dar să mergem mai departe cu observațiile:&lt;br /&gt;
&lt;br /&gt;
* Deoarece vom verifica doar clădirile din fața lui &#039;&#039;C&#039;&#039;, ne interesează doar clădirile cu &#039;&#039;y&#039;&#039; mai mic.&lt;br /&gt;
* Pare a fi util să parcurgem clădirile în ordinea crescătoare a lui &#039;&#039;y&#039;&#039;.&lt;br /&gt;
* Pentru fiecare coordonată &#039;&#039;x&#039;&#039; a clădirii &#039;&#039;C&#039;&#039; curente ne interesează maximul de înălțime de pînă acum, la acea coordonată.&lt;br /&gt;
* Astfel, putem memora un vector de înălțimi maxime, care pentru fiecare coordonată &#039;&#039;x&#039;&#039; memorează cea mai înaltă clădire vizibilă la coordonata &#039;&#039;x&#039;&#039;, pînă la acest moment.&lt;br /&gt;
&lt;br /&gt;
Cum implementăm acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Cumva trebuie să parcurgem clădirile în ordinea crescătoare a lui &#039;&#039;y&#039;&#039;. Soluțiile oficiale sar imediat la sortare. Dar să nu ne grăbim! Sortarea este lentă, ar fi bine să o evităm dacă putem.&lt;br /&gt;
&lt;br /&gt;
Și desigur că putem: ce-ar fi să păstram pentru fiecare coordonată &#039;&#039;y&#039;&#039; o listă de clădiri ce încep la acea coordonată? Apoi parcurgem clădirile în ordinea lui &#039;&#039;y&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Iată gruparea de clădiri pe liste, conform exemplului din problemă:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:macheta.jpg|frame|none|Exemplul din problema macheta - desen]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:lista-macheta.gif|frame|none|Exemplul din problema macheta - concept structură de date]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Explicații&#039;&#039;&#039;: avem o clădire care începe la &#039;&#039;y&#039;&#039;=1 - clădirea 3. Avem o clădire care începe la &#039;&#039;y&#039;&#039;=2 - clădirea 2. Avem două clădiri care încep la &#039;&#039;y&#039;&#039;=3 - 4 și 5. Și o clădire care începe la &#039;&#039;y&#039;&#039;=6 - clădirea 1.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cum rezolvăm problema&#039;&#039;&#039; pe baza acestei structuri? Printr-o parcurgere a listelor în ordine, de sus în jos și de la stânga la dreapta. Pentru fiecare clădire &#039;&#039;C&#039;&#039; parcurgem coordonatele ei &#039;&#039;x&#039;&#039;, verificând vectorul de înălțimi &amp;lt;&amp;lt;code&amp;gt;h[x]&amp;lt;/code&amp;gt;. Dacă măcar una din înălțimi este mai mică, clădirea este vizibilă. Atenție, înălțimile trebuie actualizate dacă înălțimea lui &#039;&#039;C&#039;&#039; este mai mare decît cea a lui &amp;lt;code&amp;gt;h[x]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;De remarcat&#039;&#039;&#039;: o implementare îngrijită a acestui algoritm va avea circa 50 de linii, iar ca linii de program efectiv probabil 40.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm?&lt;br /&gt;
&lt;br /&gt;
Construcția listelor este &#039;&#039;O(N)&#039;&#039;, numărul de clădiri. Parcurgerea listelor este &#039;&#039;O(N + L&amp;lt;sub&amp;gt;y&amp;lt;/sub&amp;gt;)&#039;&#039;. Pentru fiecare clădire vom prelucra în timp constant fiecare coordonată &#039;&#039;x&#039;&#039; a sa. Rezultă complexitatea finală &#039;&#039;O(N L&amp;lt;sub&amp;gt;x&amp;lt;/sub&amp;gt; + L&amp;lt;sub&amp;gt;y&amp;lt;/sub&amp;gt;)&#039;&#039;. După înlocuiri rezultă circa 100 000 de operații. Memoria va fi &#039;&#039;O(L&amp;lt;sub&amp;gt;x&amp;lt;/sub&amp;gt;)&#039;&#039;, adică neglijabilă.&lt;br /&gt;
&lt;br /&gt;
Din nou, o problemă intenționată spre a fi grea devine simplă prin folosirea listelor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 4 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]), folosind liste!&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/onigim Onigim], dată la ONI 2013 clasa a 5-a;&lt;br /&gt;
* [https://www.nerdarena.ro/problema/macheta Macheta], dată la ONI 2011 clasa a 8-a;&lt;br /&gt;
* [https://www.nerdarena.ro/problema/criptic Criptic].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Tema opțională==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Detecție listă cu buclă&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Să se determine dacă o listă se întoarce la ea însăși (unul din elemente pointează la o celulă din-naintea lui). Nu aveți voie să vă legați de valorile memorate, ele pot fi duplicat. Vă puteți folosi strict de legăturile listei, vectorul &amp;lt;code&amp;gt;next[]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Vă rămâne ca temă de gândire:&lt;br /&gt;
&lt;br /&gt;
* Ușor: &#039;&#039;O(n2)&#039;&#039; - unde &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; este numărul de elemente al listei&lt;br /&gt;
* Greu: &#039;&#039;O(n)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_4:_Liste_(2) Accesează rezolvarea temei 4]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_3:_Liste_(1)&amp;diff=18566</id>
		<title>Clasa a 7-a Lecția 3: Liste (1)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_3:_Liste_(1)&amp;diff=18566"/>
		<updated>2026-02-06T15:53:01Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/yMeFuBS5ovA|||||start=5795&amp;amp;end=12840&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
==Conceptului de liste==&lt;br /&gt;
&lt;br /&gt;
===Definiție===&lt;br /&gt;
&lt;br /&gt;
În informatică &#039;&#039;&#039;o listă înlănțuită este o structură de date care constă dintr-un grup de noduri care împreună reprezintă o secvență&#039;&#039;&#039;. În forma ei cea mai simplă, fiecare nod este format din date și o referință (înlănțuire) către nodul următor în secvență. Lista înlănțuită permite inserarea și ștergerea eficientă a elementelor de pe orice poziție în secvență.&lt;br /&gt;
&lt;br /&gt;
[[Image:lista-exemplu.gif|frame|none|Exemplu de listă înlănțuită]]&lt;br /&gt;
&lt;br /&gt;
După cum puteți să vedeți, există o diferență mare între vectori și liste. Vectorii au poziții consecutive în care stocăm date pe când listele au referințe pentru a putea determina ordinea. Referințele pot avea oricare valori, neavând obligația să fie într-o ordine anume.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Pentru a înțelege mai bine diferența dintre liste și vectori, urmăriți clipul de mai jos:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.youtube.com/watch?v=b5QR4AmrspU&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Implementare===&lt;br /&gt;
&lt;br /&gt;
O implementare posibilă este folosind doi vectori: un vector &amp;lt;code&amp;gt;v[]&amp;lt;/code&amp;gt; care memorează elementele listei (numere întregi de exemplu) și un vector &amp;lt;code&amp;gt;next[]&amp;lt;/code&amp;gt;, care memorează indicele următorului element din listă. &lt;br /&gt;
&lt;br /&gt;
Perechea &amp;lt;code&amp;gt;(v[i], next[i])&amp;lt;/code&amp;gt; conține o celulă sau un nod al listei. Această celulă este memorată la poziția i, dar ea &#039;&#039;&#039;nu este celula numărul i&#039;&#039;&#039;! Ordinea în care memorăm celulele listei în vector nu are nici o relevanță. Importantă este înlănțuirea acestor celule, dată de vectorul &amp;lt;code&amp;gt;next[]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ultima celulă a listei nu va avea un element următor. Avem nevoie de o valoare specială, terminator de listă pe care să o memorăm în vectorul &amp;lt;code&amp;gt;next[]&amp;lt;/code&amp;gt; pentru ultima celulă. Aici se practică două variante:&lt;br /&gt;
&lt;br /&gt;
# Valoarea 0. În acest caz în care nu vom putea folosi celula 0 din perechea de vectori. Vom vedea că nu e chiar o risipă, deoarece de multe ori vom folosi o santinelă pe poziția zero.&lt;br /&gt;
&lt;br /&gt;
# Valoarea -1. În acest caz putem folosi toate celulele din perechea de vectori.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Operații cu liste===&lt;br /&gt;
&lt;br /&gt;
====Crearea unei liste cu elemente citite la intrare====&lt;br /&gt;
&lt;br /&gt;
Vom folosi valoarea -1 ca terminator de listă:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#define NIL -1&lt;br /&gt;
&lt;br /&gt;
fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
for ( i = 0; i &amp;lt; n; i++ ) {&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;v[i] );&lt;br /&gt;
  next[i] = i + 1;&lt;br /&gt;
}&lt;br /&gt;
next[n - 1] = NIL; // ultimul element din listă&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Observăm că fiecare element se leagă de cel de după el, mai puțin ultimul.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are citirea unei liste?&lt;br /&gt;
&lt;br /&gt;
Desigur &#039;&#039;O(n)&#039;&#039;, ca și citirea unui vector.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Afișarea unei liste====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
i = 0;&lt;br /&gt;
while ( i != NIL ) { // avem o celulă din listă, nu terminatorul&lt;br /&gt;
  fprintf( fout, &amp;quot;%d &amp;quot;, v[i] ); // afișăm elementul listei&lt;br /&gt;
  i = next[i]; // trecem la următorul element din listă&lt;br /&gt;
}&lt;br /&gt;
fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are scrierea unei liste?&lt;br /&gt;
&lt;br /&gt;
Desigur &#039;&#039;O(n)&#039;&#039;, ca și scrierea unui vector.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Inserare element în listă după celula i====&lt;br /&gt;
&lt;br /&gt;
Să presupunem că la poziția &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; avem celula cu elementul 45, ca mai jos. Dorim să inserăm o celulă cu elementul 12, aflată la poziția pos imediat după celula &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt;. Iată cum vom proceda:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:lista-inserare-desen.gif|frame|none|Inserare element - concept]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:lista-inserare-implementare.gif|frame|none|Inserare element - implementare]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// pos conține poziția nodului de inserat&lt;br /&gt;
next[pos] = next[i];&lt;br /&gt;
next[i] = pos;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are inserarea?&lt;br /&gt;
&lt;br /&gt;
Desigur, &#039;&#039;O(1)&#039;&#039;. Comparați cu un vector, unde inserarea este &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Ștergere din listă a celulei aflate după celula i====&lt;br /&gt;
&lt;br /&gt;
Să presupunem că la poziția &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; avem celula cu elementul 45, ca mai jos. Dorim să eliminăm celula care apare în listă după celula &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt;, cea cu elementul 10, aflat la poziția &amp;lt;code&amp;gt;j&amp;lt;/code&amp;gt;. Iată cum vom proceda:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[Image:lista-stergere-desen.gif|frame|none|Ștergere element - concept]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[Image:lista-stergere-implementare.gif|frame|none|Ștergere element - implementare]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
next[i] = next[next[i]];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are inserarea?&lt;br /&gt;
&lt;br /&gt;
Desigur, &#039;&#039;O(1)&#039;&#039;. Comparați cu un vector, unde ștergerea este &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Valoarea elementului numărul k în listă====&lt;br /&gt;
&lt;br /&gt;
Pentru a afla valoarea celui de-al &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;-lea element în listă va trebui să parcurgem primele &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; elemente, de la începutul listei. Să presupunem că prima celulă din listă este &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt;. Atunci codul va fi:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
for ( j = 1; j &amp;lt; k; j++ ) // vom avansa de k-1 ori&lt;br /&gt;
  i = next[i];            // avansam la urmatoarea celula din lista&lt;br /&gt;
printf( &amp;quot;Elementul numarul %d in lista este %d\n&amp;quot;, k, v[i] )&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are aflarea elementului &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;?&lt;br /&gt;
&lt;br /&gt;
Ea este &#039;&#039;O(k)&#039;&#039;. Comparați cu un vector, unde este &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Exemple de probleme rezolvate cu liste ==&lt;br /&gt;
&lt;br /&gt;
===Aplicație 1: v-ați ascunselea===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;n&#039;&#039;&#039; copii numerotați de la 1 la &#039;&#039;&#039;n&#039;&#039;&#039; și așezați în cerc numără la v-ați ascunselea, din &#039;&#039;&#039;k&#039;&#039;&#039; în &#039;&#039;&#039;k&#039;&#039;&#039;, începând cu copilul 1. Date &#039;&#039;&#039;n&#039;&#039;&#039; și &#039;&#039;&#039;k&#039;&#039;&#039; să se afișeze ordinea în care ies copiii din cerc.&lt;br /&gt;
&lt;br /&gt;
O rezolvare simplă este să memorăm numerele de la 1 la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; într-un vector și apoi să avansăm &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; elemente și să afișăm elementul curent, iar apoi să îl setăm pe zero. Reluăm căutarea de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; ori, ignorând elementele zero:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int v[100];&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, k, i, j, pos;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    v[i] = i + 1;&lt;br /&gt;
  pos = n - 1;&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ ) {&lt;br /&gt;
    // avanseaza k elemente nenule&lt;br /&gt;
    for ( j = 0; j &amp;lt; k; j++ ) {&lt;br /&gt;
      pos = (pos + 1) % n;&lt;br /&gt;
      while ( v[pos] == 0 )&lt;br /&gt;
        pos = (pos + 1) % n;&lt;br /&gt;
    }&lt;br /&gt;
    printf( &amp;quot;%d &amp;quot;, v[pos] );&lt;br /&gt;
    v[pos] = 0;&lt;br /&gt;
  }&lt;br /&gt;
  printf( &amp;quot;\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are acest algoritm? &lt;br /&gt;
&lt;br /&gt;
Dacă mai avem &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; copii în cerc îi vom număra de &amp;lt;code&amp;gt;k/i&amp;lt;/code&amp;gt; ori. Restul elementelor fiind zero, vom face &amp;lt;code&amp;gt;k/i&amp;lt;/code&amp;gt; parcurgeri ale întregului vector, deci &amp;lt;code&amp;gt;n·k/i&amp;lt;/code&amp;gt; operații. Rezultă numărul de operații total:&lt;br /&gt;
&lt;br /&gt;
: Număr operații = n*n/n + n*n/(n-1) + n*n/(n-2) + ... + n*n/2 + n*n/1.&lt;br /&gt;
&lt;br /&gt;
Dăm factor comun n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
: Număr operații = n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;(1/n + 1/(n-1) + 1/(n-2) + ... + 1/2 + 1/1).&lt;br /&gt;
&lt;br /&gt;
Suma dintre paranteze va fi studiată în clasa a zecea. Ea este aproximativ &amp;lt;code&amp;gt;log n&amp;lt;/code&amp;gt;. Rezultă complexitatea soluției: &#039;&#039;O(n·k·log n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Observăm o anume risipă în această soluție: deși avem poate doar doi copii în listă, noi vom număra până la &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;. Putem optimiza algoritmul numărând până la &amp;lt;code&amp;gt;k % i&amp;lt;/code&amp;gt;, unde &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; este numărul de copii rămași în cerc.&lt;br /&gt;
&lt;br /&gt;
Iată o soluție care folosește această idee:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int v[100];&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, k, i, j, pos, a;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    v[i] = i + 1;&lt;br /&gt;
  pos = n - 1;&lt;br /&gt;
  for ( i = n; i &amp;gt; 0; i-- ) { // numarul de copii scade de la n la 0             &lt;br /&gt;
    // numarul de copii ramasi in cerc este i                                    &lt;br /&gt;
    // avansam k % i deoarece nu are rost sa numaram dincolo de numarul          &lt;br /&gt;
    // de copii din cerc                                                         &lt;br /&gt;
    a = k % i == 0 ? i : k % i; // trebuie sa avansam macar un copil             &lt;br /&gt;
    for ( j = 0; j &amp;lt; a; j++ ) {&lt;br /&gt;
      pos = (pos + 1) % n;      // avansam indicele - numaram copilul            &lt;br /&gt;
      while ( v[pos] == 0 )     // gasim urmatorul copil                         &lt;br /&gt;
        pos = (pos + 1) % n;&lt;br /&gt;
    }&lt;br /&gt;
    printf( &amp;quot;%d &amp;quot;, v[pos] );&lt;br /&gt;
    v[pos] = 0;&lt;br /&gt;
  }&lt;br /&gt;
  printf( &amp;quot;\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
La fiecare pas vom trece maxim o dată prin toți copiii rămași în cerc. Vom parcurge, deci, cel mult o dată întregul vector de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente pentru fiecare copil care iese. Complexitatea va fi &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
O soluție alternativă este una în care la fiecare pas eliminăm copilul din cerc ștergând valoarea sa din vector, prin deplasarea elementelor vectorului. Iată acea soluție:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int v[100];&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, k, i, j, pos;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    v[i] = i + 1;&lt;br /&gt;
  pos = 0;&lt;br /&gt;
  for ( i = n; i &amp;gt; 0; i-- ) { // numarul de copii scade de la n la 0             &lt;br /&gt;
    pos = (pos + k - 1) % i;  // sarim peste k-1 copii in vector de marime i     &lt;br /&gt;
    printf( &amp;quot;%d &amp;quot;, v[pos] );  // afisam copilul                                  &lt;br /&gt;
    for ( j = pos + 1; j &amp;lt; i; j++ ) // eliminam copilul din vector               &lt;br /&gt;
      v[j - 1] = v[j];&lt;br /&gt;
  }&lt;br /&gt;
  printf( &amp;quot;\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
De data aceasta avansul la următorul copil este &#039;&#039;O(1)&#039;&#039;. Dar eliminarea elementului din vector este &#039;&#039;O(i)&#039;&#039; pe cazul cel mai rău, când mai avem &amp;lt;code&amp;gt;i&amp;lt;/code&amp;gt; copii în cerc. Drept care vom avea:&lt;br /&gt;
&lt;br /&gt;
: Număr operații = n + (n-1) + (n-2) + ... + 2 + 1&lt;br /&gt;
&lt;br /&gt;
Aceasta fiind suma lui Gauss, rezultă o complexitate &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup)&#039;&#039;, identică cu cea anterioară. Ea ne duce însă către o soluție mai bună. Care este pasul scump în această soluție? Eliminarea elementului din vector! Precum am văzut, eliminarea este rapidă într-o listă. Drept care hai să încercăm o soluție cu liste.&lt;br /&gt;
&lt;br /&gt;
Vom așeza numerele de la 1 la n într-o listă. Restul algoritmului este identic cu cel anterior&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int v[100], next[100];&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, k, i, j, pos;&lt;br /&gt;
&lt;br /&gt;
  scanf( &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k );&lt;br /&gt;
  // initializam lista&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ ) {&lt;br /&gt;
    v[i] = i + 1;&lt;br /&gt;
    next[i] = (i + 1) % n;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  pos = n - 1; // pos e pozitia copilului din-nainte de copilul de eliminat&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ ) {&lt;br /&gt;
    // avanseaza k - 1 elemente deoarece eliminarea conteaza ca un element&lt;br /&gt;
    for ( j = 1; j &amp;lt; k; j++ )&lt;br /&gt;
      pos = next[pos];&lt;br /&gt;
    printf( &amp;quot;%d &amp;quot;, v[next[pos]] );&lt;br /&gt;
    next[pos] = next[next[pos]];&lt;br /&gt;
  }&lt;br /&gt;
  printf( &amp;quot;\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remarcăm folosirea unei liste circulare. Ea nu are un prim sau un ultim element ci doar un element de pornire. Ea corespunde realității - copiii sunt așezați în cerc. Acum putem elimina copii numărați în mod eficient, dar parcurgerea elementelor nu mai este &#039;&#039;O(1)&#039;&#039; ci &#039;&#039;O(k)&#039;&#039;. Per total complexitatea se reduce la &#039;&#039;O(n∙k)&#039;&#039;. Pentru k mult mai mic decât &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; această soluție va fi mai bună decât cele anterioare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Aplicație 2: bile1===&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/bile1 Bile1] a fost dată la ONI 2012 clasa a 7-a. Problema poate fi rezolvată folosind o matrice caracteristică a obstacolelor. Dar dacă memoria permisă ar fi fost mai mică nu o puteam rezolva în acest fel. &lt;br /&gt;
&lt;br /&gt;
Rezolvarea cu liste necesită doar &#039;&#039;O(m+n+p)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
Vom memora atâtea liste câte coloane avem. Fiecare listă va stoca numerele de coloane la care avem obstacole în acea listă. Liniile ce nu au obstacole vor fi reprezentate ca liste vide.&lt;br /&gt;
&lt;br /&gt;
Iată conceptul structurii de date pe care o vom folosi:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:bile1.jpg|frame|none|Exemplul din problema bile1]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:lista-bile1.gif|frame|none|Exemplul din desenul problemei - concept structură de date]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Pentru implementare vom folosi ca structuri de date:&lt;br /&gt;
&lt;br /&gt;
* un vector &amp;lt;code&amp;gt;b[n]&amp;lt;/code&amp;gt; care memorează linia cu bile,n fiind numărul de coloane;&lt;br /&gt;
* un vector &amp;lt;code&amp;gt;liste[m]&amp;lt;/code&amp;gt; care pentru fiecare linie pointează la o listă de coloane pe care se află obstacole, &amp;lt;code&amp;gt;m&amp;lt;/code&amp;gt; fiind numărul de linii;&lt;br /&gt;
* doi vectori &amp;lt;code&amp;gt;col[p]&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;next[p]&amp;lt;/code&amp;gt; care memorează coloanele obstacolelor și următorul obstacol pe linie, &amp;lt;code&amp;gt;p&amp;lt;/code&amp;gt; fiind numărul de obstacole.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Algoritmul folosit este următorul:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
1. Citește m, n și p, setează i la zero&lt;br /&gt;
2. Citește fiecare obstacol (l, c) si:&lt;br /&gt;
  2.1 col[i] = c&lt;br /&gt;
  2.2 adaugă coloana c la lista de pe linia l, liste[l]:&lt;br /&gt;
    2.2.1 next[i] = liste[l]&lt;br /&gt;
    2.2.2 liste[l] = i&lt;br /&gt;
  2.3 i = i + 1&lt;br /&gt;
3. Citește vectorul de bile&lt;br /&gt;
4. Pentru fiecare linie l de la 1 la m&lt;br /&gt;
  4.1 parcurge lista de coloane liste[l] si pentru fiecare obstacol col[i] din listă:&lt;br /&gt;
    4.1.1 actualizează vectorul de bile la indicele coloanei, col[i]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Cum se compară cu algoritmul inițial care folosește o matrice caracteristică?&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Structură de date&lt;br /&gt;
!Complexitate timp&lt;br /&gt;
!Complexitate memorie&lt;br /&gt;
!Explicații&lt;br /&gt;
|-&lt;br /&gt;
| Matrice de frecvență a obstacolelor&lt;br /&gt;
| &amp;lt;math&amp;gt;O(m\times n + p)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(m\times n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Parcurgerea matricei de frecvență este &amp;lt;math&amp;gt;O(m\times n)&amp;lt;/math&amp;gt;, iar prelucrarea obstacolelor este &amp;lt;math&amp;gt;O(p)&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Vector de liste de coloane ale obstacolelor&lt;br /&gt;
| &amp;lt;math&amp;gt;O(m + n + p)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(m + n + p)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Vom parcurge strict obstacolele, de unde rezultă complexitatea &amp;lt;math&amp;gt;O(p)&amp;lt;/math&amp;gt;. Parcurgem și numerele inițiale, pentru citire, deci &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;. Parcurgem și toate liniile matricei, pentru a verifica dacă avem o listă cu obstacole, deci &amp;lt;math&amp;gt;O(m)&amp;lt;/math&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Observăm că atât timpul cât și memoria se îmbunătățesc substanțial. Problema fiind dată la clasa a șaptea într-o vreme când listele nu apăreau la această clasă este puțin probabil că aceasta a fost soluția dorită. &lt;br /&gt;
&lt;br /&gt;
Cu toate acestea este un caz de studiu interesant unde o structură simplă de date, pe care olimpici și profesori o ignoră adesea în rezolvarea problemelor de olimpiadă, poate duce la un algoritm simplu și foarte eficient.&lt;br /&gt;
&lt;br /&gt;
Despre ce structură este vorba? Despre un vector de liste. Rețineți: această structură poate rezolva multe probleme date la concursuri.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Aplicație 3: zigzag===&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/zigzag Zigzag] dată la ONI 2012 clasa a 7-a cere de la concurenți o soluție matematică, ce presupune calculul coordonatelor literelor unui șir într-o matrice de dimensiuni cunoscute. Problema are, însă, o rezolvare foarte ușoară cu un vector de liste, o rezolvare ce nu presupune matematică și nici creativitate, ci doar o implementare îngrijită.&lt;br /&gt;
&lt;br /&gt;
Ca și la bile1, vom ține minte pentru fiecare linie a matricei de codificare câte o listă cu coloanele unde apar litere. Vom genera aceste liste parcurgând în matrice pozițiile &amp;lt;code&amp;gt;(l, c)&amp;lt;/code&amp;gt; unde s-ar afla textul (înainte de a citi acel text!) și adăugând coloana &amp;lt;code&amp;gt;c&amp;lt;/code&amp;gt; la lista liniei &amp;lt;code&amp;gt;l&amp;lt;/code&amp;gt;. Apoi vom parcurge aceste liste, în ordine, și vom atașa fiecărei celule, pe lângă coloană, o literă din text. De fapt vom vedea că nici nu avem nevoie să stocăm coloanele, ci doar literele. În final vom parcurge celulele în ordinea alocării lor, deoarece ea este chiar ordinea textului decodificat.&lt;br /&gt;
&lt;br /&gt;
Iată conceptul structurii de date pe care o vom folosi:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:zigzag.jpg|frame|none|Exemplul din problema zigzag]]&lt;br /&gt;
| valign=&amp;quot;top&amp;quot;|[[File:lista-zigzag.gif|frame|none|Exemplul din desenul problemei - concept structură de date]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
O diferență față de bile1 este că va trebui să ținem nu numai vectorul cu începutul listelor, ci și un vector cu ultimul element din listă pentru a putea face adăugarea la coadă foarte rapid. O alternativă ar fi să parcurgem pozițiile in ordine inversă (pare mai complicat, însă). Să denumim vectorii &amp;lt;code&amp;gt;prim[]&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;ultim[]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată algoritmul de generare a listelor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
  // pregatim listele cu pozitiile in matricea cheie&lt;br /&gt;
  // cheie este cheia, iar n este numărul de caractere al mesajului,&lt;br /&gt;
  // c aloca celulele listei si este si coloana in acelasi timp&lt;br /&gt;
  l = 0;&lt;br /&gt;
  pas = 1; // initial linia creste din unu in unu&lt;br /&gt;
  // nu putem avea celule pe indexul 0 deoarece cu zero codificam finalul&lt;br /&gt;
  // de lista, deci codificam coloanele incepind cu 1, no biggie&lt;br /&gt;
  for ( c = 1; c &amp;lt;= n; c++ ) {&lt;br /&gt;
    // pentru fiecare coloana a matricei calculam&lt;br /&gt;
    // linia corespunzatoare literei (este una singura!)&lt;br /&gt;
    // liniile vor fi de la zero, nici un motiv sa le numerotam de la 1&lt;br /&gt;
    // avem perechea (l, c): aseaza c in lista l&lt;br /&gt;
    next[c] = 0;          // nu urmeaza nimeni după mine, sint ultima celula&lt;br /&gt;
    if ( prim[l] == 0 )   // lista este vida&lt;br /&gt;
      prim[l] = ultim[l] = c;&lt;br /&gt;
    else                  // avem un ultim element&lt;br /&gt;
      ultim[l] = next[ultim[l]] = c;// atasam celula la coada ultimului element&lt;br /&gt;
&lt;br /&gt;
    // avanseaza linia&lt;br /&gt;
    l = l + pas;&lt;br /&gt;
    if ( l &amp;lt; 0 || l &amp;gt;= cheie ) { // daca am depasit marginea inversam pasul&lt;br /&gt;
      pas = -pas;&lt;br /&gt;
      l = l + pas + pas; // adunam de doua ori pasul pentru a ne intoarce&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Este, sper, destul de clar că atât timpul cât și memoria vor fi liniare în numărul de caractere de la intrare. Din nou, folosirea unui vector de liste rezultă într-un algoritm simplu și eficient.&lt;br /&gt;
&lt;br /&gt;
Vă rămâne ca temă să continuați această implementare. Mai aveți de parcurs listele in ordinea naturală și atașat literele din text, iar la final să parcurgeți celulele în ordinea alocării lor (cu indicele c) și să afișați caracterele lor.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 3 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C în CodeBlocks, trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/bile1 bile1] dată la ONI 2012 clasa a 7a rezolvată cu vector de liste de obstacole.&lt;br /&gt;
* [https://www.nerdarena.ro/problema/zigzag zigzag] dată la ONI 2012 clasa a 7a rezolvată cu vectori de liste. Chiar dacă există și alte rezolvări, scopul este să exersați listele, nu să luați 100p la problemă!&lt;br /&gt;
* [https://www.nerdarena.ro/problema/lacoada la coadă] ca aplicație la liste&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_3:_Liste_(1) Accesează rezolvarea temei 3]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18565</id>
		<title>Clasa a 7-a Lecția 2: Problema selecției și stive</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18565"/>
		<updated>2026-02-06T15:49:44Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/gy3zK4E8SSo|||||start=7400&amp;amp;end=9880&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
== Problema selecției ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Problema&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Dat un șir de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; numere și o poziție &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; în acel șir să se spună ce element s-ar afla pe acea poziție dacă șirul ar fi sortat.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie șirul &amp;lt;code&amp;gt;3 9 23 13 18 8 3 10 2 10 15 21&amp;lt;/code&amp;gt; și poziția &amp;lt;code&amp;gt;k = 6&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dacă am ordona șirul am obține 2 3 3 8 9 10 10 13 15 18 21 23. Pe poziția 6 se află elementul 10, care este și răspunsul dorit.&lt;br /&gt;
&lt;br /&gt;
===Soluție forță brută===&lt;br /&gt;
&lt;br /&gt;
O soluție ce vine ușor în minte este să sortăm vectorul și apoi să afișăm elementul de la poziția &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;. Nu este eficientă, complexitatea ei va fi &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; cu sortare prin selecție, sau &#039;&#039;O(n log n)&#039;&#039; cu sortări mai rapide.&lt;br /&gt;
&lt;br /&gt;
===Soluția folosind algoritmul Quick select===&lt;br /&gt;
&lt;br /&gt;
Aplicăm un pas de pivotare în mod repetat. Algoritmul este prezentat aici: [https://en.wikipedia.org/wiki/Quickselect quickselect].&lt;br /&gt;
&lt;br /&gt;
Ideea pivotării este următoarea: alegem un așa numit pivot. El este un element oarecare din vector. Putem să alegem elementul aflat la jumatea vectorului, &amp;lt;code&amp;gt;v[n/2]&amp;lt;/code&amp;gt;. Sau putem să îl alegem la întâmplare, folosind o funcție ce generează numere aleatoare, cum ar fi funcția &amp;lt;code&amp;gt;rand()&amp;lt;/code&amp;gt; în C (parte din biblioteca &amp;lt;code&amp;gt;stdlib.h&amp;lt;/code&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
Fie &amp;lt;code&amp;gt;p&amp;lt;/code&amp;gt; indicele pivotului și &amp;lt;code&amp;gt;val = v[p]&amp;lt;/code&amp;gt; valoarea acelui pivot. Ne propunem să separăm vectorul în două: prima parte va conține elemente mai mici sau egale cu pivotul &amp;lt;code&amp;gt;val&amp;lt;/code&amp;gt;. A doua parte va conține elemente mai mari sau egale cu pivotul &amp;lt;code&amp;gt;val&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Odată efecutată pivotarea vom ști în care din cele două părți se va afla elementul pe poziția &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; în vectorul sortat. Putem, deci, selecta acea parte, continuând algoritmul doar pentru acel subvector.&lt;br /&gt;
&lt;br /&gt;
Există mai mulți algoritmi de pivotare, toți liniari. Vă rămâne ca temă să implementați unul din ei. În linkul de pe [https://en.wikipedia.org/wiki/Quickselect Wikipedia] de mai sus găsiți algoritmul propus de Lomuto, care nu este cel mai eficient. Ca bonus, v-aș recomanda să implementați [https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme algoritmul original al lui Tony Hoare]. &lt;br /&gt;
&lt;br /&gt;
Atenție! El nu este structurat! Va trebui să îl modificați! Sau, voi aprecia și mai mult dacă găsiți propria voastră implementare :-) .&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce complexitate are acest algoritm?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom aplica în mod repetat pivotarea, ce are complexitate &#039;&#039;O(n)&#039;&#039;. Pe medie vom păstra, la fiecare pas, jumătate din vector. Complexitatea va fi, deci, &#039;&#039;O(n + n/2 + n/4 + ... + 1)&#039;&#039;. Observăm că suma nu va depăși niciodată &#039;&#039;2n&#039;&#039; (deși se va apropia de acea valoare). Complexitatea este, deci &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Atenție! Aceasta este o complexitate medie! Pe cazul cel mai rău putem să selectăm mereu o parte a vectorului care are cu un element mai puțin ca vectorul curent, ceea ce duce la o complexitate de &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; pe cazul cel mai rău.&lt;br /&gt;
&lt;br /&gt;
Aplicăm repetat pivotarea quickselect. Calcul aproximativ al complexității pe cazul mediu: este &#039;&#039;O(n)&#039;&#039;, în loc de &#039;&#039;O(n log n)&#039;&#039; dacă am fi sortat.&lt;br /&gt;
&lt;br /&gt;
Pentru cei curioși, există o îmbunătățire a algoritmului quick select ce îi schimbă complexitatea la &#039;&#039;O(n)&#039;&#039; chiar și pe cazul cel mai rău, dar ea depășește nivelul cursului nostru. Puteți citi mai multe despre medianul medianelor aici: [https://en.wikipedia.org/wiki/Median_of_medians https://en.wikipedia.org/wiki/Median_of_medians] .&lt;br /&gt;
&lt;br /&gt;
== Stive ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este un tip de date abstract?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Un tip de date abstract este un model matematic, ce privește tipul de date din punctul de vedere al utilizatorului. Astfel, el definește operații asupra datelor și comportamentul acestor operații, dar nu specifică o implementare.&lt;br /&gt;
&lt;br /&gt;
Tipurile de date abstracte separă funcționalitatea de implementare. Acest lucru este util pentru analiza complexității algoritmilor. Este de menționat faptul că unele TDA au multiple implementări, cu avantaje și dezavantaje, precum vom vedea în lecțiile următoare.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este o stivă?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Stiva (în engleză &#039;&#039;stack&#039;&#039;) este un concept simplu, pe care l-am mai folosit în trecut, fără a sublinia faptul că folosim o stivă. &lt;br /&gt;
&lt;br /&gt;
O stivă este o &#039;&#039;grămadă&#039;&#039; de obiecte ordonate după ordinea LIFO: last in, first out. Aceasta înseamnă că putem adăuga obiecte în stivă, iar atunci când le vom scoate, le vom scoate în ordine inversă față de cum le-am adăugat. Vom vedea în lecțiile următoare că, prin contrast, coada scoate obiectele în aceeași ordine în care au fost adăugate.&lt;br /&gt;
&lt;br /&gt;
Stiva este un tip de date abstract în care definim operațiile:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;push(value)&amp;lt;/code&amp;gt; - inserează o valoare dată la sfârșitul listei;&lt;br /&gt;
* &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt; - returnează valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; - șterge valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;code&amp;gt;empty&amp;lt;/code&amp;gt; - returnează 1 dacă stiva este goală (poate fi implementată și în cadrul operației top).&lt;br /&gt;
&lt;br /&gt;
Pentru aceste operații există restricția menționată, anume că valorile trebuie returnate cu regula &#039;&#039;&#039;LIFO&#039;&#039;&#039; (Last In, First Out).&lt;br /&gt;
&lt;br /&gt;
Pentru a înțelege mai bine ce este o stivă, urmăriți clipul de mai jos:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.youtube.com/watch?v=I37kGX-nZEI&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Implementare: stivă de întregi pozitivi sau zero===&lt;br /&gt;
&lt;br /&gt;
În practică stiva se implementează aproape întotdeauna ca un vector căruia i se adaugă la coadă elemente:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#define MAXN 1000&lt;br /&gt;
&lt;br /&gt;
int stack[MAXN];       // stocarea valorilor propriu-zise&lt;br /&gt;
int stack_pointer = 0; // indice la prima valoare nefolosita in vector (totuna cu numărul de elemente din stiva)&lt;br /&gt;
&lt;br /&gt;
// adauga valoarea &#039;val&#039; la stiva, daca aceasta nu este plina&lt;br /&gt;
// returneaza 1 in caz de succes, 0 in caz de stiva plina&lt;br /&gt;
int push( int val ) {&lt;br /&gt;
  if ( stack_pointer &amp;amp;gt;= MAXN )  // stiva este plina?&lt;br /&gt;
    return 0;                   // returnam eroare&lt;br /&gt;
&lt;br /&gt;
  stack[stack_pointer++] = val; // adaugam valoarea in stiva&lt;br /&gt;
  return 1;                     // returnam succes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returnează valoarea aflată în virful stivei, dacă ea exista&lt;br /&gt;
// in caz contrar returneaza -1&lt;br /&gt;
int top() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0 ? stack[stack_pointer - 1] : -1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// elimina ultimul element din stiva daca se poate&lt;br /&gt;
// returneaza 1 daca operatia a reusit, 0 in caz de eroare&lt;br /&gt;
int pop() {&lt;br /&gt;
  if ( stack_pointer == 0 ) // testam cazul de eroare&lt;br /&gt;
    return 0;&lt;br /&gt;
&lt;br /&gt;
  stack_pointer--; // daca avem elemente pe stiva, il eliminam pe ultimul&lt;br /&gt;
  return 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza 1 daca stiva este goala, 0 in caz contrar&lt;br /&gt;
int empty() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* În această implementare, cele patru operații au complexitate &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* Remarcați o folosire corectă a funcțiilor: ele implementează operațiuni ce au sens separat, de sine stătător.&lt;br /&gt;
* Remarcați denumirile funcțiilor: ele sugerează operațiunea implementată.&lt;br /&gt;
* Am folosit instrucțiunea return în mijlocul funcției. Aceasta este o excepție de la regulă. Pe caz general, atunci când la intrarea într-o funcție avem de tratat cazuri particulare de ieșire imediată din funcție, putem folosi instrucțiunea return. Codul devine astfel mai clar, iar restul corpului funcției nu se deplasează spre dreapta prin indentare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Sfaturi în utilizarea stivelor pentru rezolvarea problemelor===&lt;br /&gt;
&lt;br /&gt;
* Pentru problemele noastre, stiva se va implementa cu ajutorul unui vector;&lt;br /&gt;
* Operațiile &amp;lt;code&amp;gt;top&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;empty&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; presupun verificarea și manipularea numărului de elemente din vector. În exemplul de implementare numărul de elemente este denumit &amp;lt;code&amp;gt;stack_pointer&amp;lt;/code&amp;gt;. Dar dacă vectorul vostru denumește numărul de elemente &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;, atunci veți opera cu &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;;&lt;br /&gt;
* Nu este neapărată nevoie să scrieți funcții ca cele de mai sus pentru a lucra cu stive. Exemplul de mai sus își dorește să explice noțiunea de tip de date abstract care definește acele operații, iar implementarea arată cum s-ar implementa ele. Deci, ne interesează modul de funcționare al stivei, dar implementarea o putem face cum simțim că se potrivește mai bine problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 2 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C în CodeBlocks, trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/selectie Selecție]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/betisoare Bețișoare], dată la ONI 2014 clasa a 6a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/zuma Zuma], dată la.campion 2011&lt;br /&gt;
* [https://www.nerdarena.ro/problema/swap Swap], dată la ONI 2013 baraj gimnaziu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive Accesează rezolvarea temei 2]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_1:_Reguli_de_programare_%C3%AEn_limbajul_C_%C8%99i_complexit%C4%83%C8%9Bi_algoritmice&amp;diff=18564</id>
		<title>Clasa a 7-a Lecția 1: Reguli de programare în limbajul C și complexități algoritmice</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_1:_Reguli_de_programare_%C3%AEn_limbajul_C_%C8%99i_complexit%C4%83%C8%9Bi_algoritmice&amp;diff=18564"/>
		<updated>2026-02-06T15:45:58Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/YJYXh3XEPUE|||||start=4920&amp;amp;end=10770&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
== Reguli de programare în limbajul C ==&lt;br /&gt;
&lt;br /&gt;
* Variabilele simple &#039;&#039;&#039;nu&#039;&#039;&#039; se inițializează la declarare. Ele se inițializează cât mai aproape de secțiunea de program care le folosește. Cu alte cuvinte orice variabilă se inițializează cât mai jos posibil. De ce? Pentru citibilitatea codului. Imaginați-vă că la linia 300 vedem o instrucțiune &amp;lt;code&amp;gt;a = a + y;&amp;lt;/code&amp;gt;. Ne punem întrebarea cu ce valoare a fost inițializată &amp;lt;tt&amp;gt;a&amp;lt;/tt&amp;gt;. Imaginați-vă că trebuie să mergem câteva pagini în sus pentru a constata că variabila a fost inițializată la declarare &amp;lt;code&amp;gt;int a = 1&amp;lt;/code&amp;gt;;&lt;br /&gt;
* Vectorii încep de la 0, nu de la 1. Este o regulă importantă, nu o preferință personală. Avantaje: când folosim modulo, vectori caracteristici, etc.&lt;br /&gt;
* Vectorii au dimensiunile specificate de cerință. Ei nu se declară &amp;lt;code&amp;gt;int v[10010]&amp;lt;/code&amp;gt;. Aceasta se cheamă &#039;&#039;programare aproximativă&#039;&#039; și nu este o practică acceptată la nivel de înalt. Dacă la matematică vi s-ar cere să rezolvați ecuația &#039;&#039;2 &#039;&#039;&#039;·&#039;&#039;&#039; x = 5&#039;&#039; și ați răspunde că &#039;&#039;x&#039;&#039; este un număr între doi și trei nu cred că ați putea fi considerați cei mai mari matematicieni ai țării. Programarea aproximativă reprezintă lenea minții. &#039;&#039;Să punem câteva elemente în plus, în caz că depășesc vectorul&#039;&#039;. Cine depășește vectorul? Zâna memoriei? Nu, voi. Soluția corectă este să vă corectați gândirea și să vă ascuțiți mintea pentru ca algoritmul vostru să &#039;&#039;&#039;nu&#039;&#039;&#039; depășească vectorul. Altfel veți rămâne incapabili de a scrie programe în lumea reală unde aproximarea nu e acceptabilă.&lt;br /&gt;
* Reguli de indentare, exemple:&lt;br /&gt;
** Indentarea se face la două sau la patru spații. Două sunt suficiente pentru citibilitate. Atenție: setați code::blocks să nu folosească caractere TAB.&lt;br /&gt;
** Acolada deschisă se pune pe același rând cu instrucțiunea precedentă.&lt;br /&gt;
** Acolada închisă se pune pe rândul următor aliniată sub instrucțiunea de care aparține.&lt;br /&gt;
** Dacă după &amp;lt;code&amp;gt;else&amp;lt;/code&amp;gt; urmează un &amp;lt;code&amp;gt;if&amp;lt;/code&amp;gt; atunci instrucțiunea &amp;lt;code&amp;gt;if&amp;lt;/code&amp;gt; se pune imediat după &amp;lt;code&amp;gt;else&amp;lt;/code&amp;gt; pe aceeași linie, iar corpul de instrucțiuni aparținând lui &amp;lt;code&amp;gt;else&amp;lt;/code&amp;gt; se indentează cu două spații, nu cu patru. Astfel, în cazul unor instrucțiuni de genul &amp;lt;code&amp;gt;if ... else if ... else if ...&amp;lt;/code&amp;gt; se evită migrarea codului către dreapta prin indentări inutile.&lt;br /&gt;
* Fără &amp;lt;code&amp;gt;break / continue / return / goto&amp;lt;/code&amp;gt; în interiorul buclelor. Ele distrug programarea structurată și, implicit, zeci de ani de experiență a omenirii.&lt;br /&gt;
* Folosim &amp;lt;code&amp;gt;for&amp;lt;/code&amp;gt; pentru bucle cu număr cunoscut de pași, &amp;lt;code&amp;gt;while&amp;lt;/code&amp;gt; în celelalte cazuri. Aceasta este o regulă de programare ordonată, care face codul mai citibil.&lt;br /&gt;
* Nu avem voie să modificăm variabila de buclă &amp;lt;code&amp;gt;for&amp;lt;/code&amp;gt; și nici limita ei de final. Dacă este un ciclu cu număr cunoscut de pași nu avem de ce să modificăm aceste variabile.&lt;br /&gt;
* Când eliminăm un element din vector micșorăm numărul lui de elemente cu 1 (&amp;lt;code&amp;gt;n--&amp;lt;/code&amp;gt;). În felul acesta &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; continuă să arate numărul de elemente din vector.&lt;br /&gt;
* Nu aveți voie să aveți warnings, vă pot fi fatale: declarați &amp;lt;code&amp;gt;int main()&amp;lt;/code&amp;gt; și folosiți &amp;lt;code&amp;gt;return 0&amp;lt;/code&amp;gt; la final. Eliminați orice alte warnings deoarece majoritatea sunt o problemă reală. Unele concursuri compilează sursa cu opriri la warnings (adică tratează warning-urile ca erori). Unele warnings se referă la variabile neinițializate sau la număr diferit de &amp;quot;%d&amp;quot; versus variabile citite, în &amp;lt;code&amp;gt;fscanf&amp;lt;/code&amp;gt;, deci sunt importante. Este &#039;&#039;&#039;obligatoriu&#039;&#039;&#039; să setați Code::Blocks să vă afișeze toate warnings. Pentru aceasta, după crearea proiectului, navigați în meniul &#039;&#039;Project&#039;&#039;, submeniul &#039;&#039;Options&#039;&#039;. În acea fereastră setați opțiunile &#039;&#039;-Wall&#039;&#039; și &#039;&#039;-O2&#039;&#039;.&lt;br /&gt;
* Comentați codul când e ceva complicat, de exemplu o formulă. Scrieți ideea algoritmului, dacă nu e ceva trivial. Nu trebuie să umpleți de comentarii, dar minimal. Mă ajutați mult în corectarea temei.&lt;br /&gt;
* Denumiți variabilele mai clar. Nu lungi, dar să aibă semnificație: &amp;lt;tt&amp;gt;prod&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;sum&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;nrcf&amp;lt;/tt&amp;gt;, etc.&lt;br /&gt;
* Nu abuzați stegulețele. Exemplu unde nu sunt necesare break sau stegulețe: căutarea unui element &amp;lt;tt&amp;gt;e&amp;lt;/tt&amp;gt; în vectorul &amp;lt;tt&amp;gt;v&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
i = 0&lt;br /&gt;
while ( (i &amp;lt; n) &amp;amp;&amp;amp; (v[i] != e) )&lt;br /&gt;
    i++;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* Folosiți corect fișiere, le deschideți cu &amp;lt;code&amp;gt;fopen&amp;lt;/code&amp;gt;, iar la final obligatoriu le închideți cu &amp;lt;code&amp;gt;fclose&amp;lt;/code&amp;gt;. În nici un caz nu folosiți &amp;lt;code&amp;gt;freopen&amp;lt;/code&amp;gt;. &lt;br /&gt;
* Folosiți doar funcțiile de intrare/ieșire C, &amp;lt;code&amp;gt;fscanf / fprintf / fgetc / fputc / fgets&amp;lt;/code&amp;gt;. Aveți aici un sumar al acestor funcții: [[instrucțiuni de intrare/ieșire în limbajul C]]&lt;br /&gt;
* Nu folosiți funcții gen &amp;lt;code&amp;gt;sort&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;qsort&amp;lt;/code&amp;gt;. Aveți nevoie de dispensă specială ☺&lt;br /&gt;
&lt;br /&gt;
=== Declararea vectorilor cu inițializare ===&lt;br /&gt;
&lt;br /&gt;
Precum știm, un vector poate primi valori la declarare. De exemplu:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int dl[4] = { 0, -1, 0, 1 };&lt;br /&gt;
int dc[4] = { 1, 0, -1, 0 };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tabel de descriptori citire/scriere ===&lt;br /&gt;
Dimensiunile diverselor tipuri: în C &#039;&#039;&#039;char&#039;&#039;&#039; are 1 octet, &#039;&#039;&#039;short&#039;&#039;&#039; are 2 octeți, &#039;&#039;&#039;int&#039;&#039;&#039; are 4 octeți, &#039;&#039;&#039;long long&#039;&#039;&#039; are 8 octeți.&lt;br /&gt;
&lt;br /&gt;
Să recapitulăm care sunt descriptorii de citire/scriere folosiți de &amp;lt;tt&amp;gt;scanf()&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;printf()&amp;lt;/tt&amp;gt; pentru fiecare din tipurile învățate:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Tip variabilă&lt;br /&gt;
!Bytes&lt;br /&gt;
!Valori limită&lt;br /&gt;
!Format folosit (litere)&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;char&amp;lt;/tt&amp;gt;&lt;br /&gt;
| 1&lt;br /&gt;
| style=&amp;quot;text-align:right;&amp;quot; | &amp;lt;tt&amp;gt;-128&amp;lt;/tt&amp;gt;&amp;amp;nbsp;...&amp;amp;nbsp;&amp;lt;tt&amp;gt;127&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;&#039;&#039;&#039;%d&#039;&#039;&#039;&amp;lt;/tt&amp;gt; dacă vrem să afișăm numărul întreg&amp;lt;br/&amp;gt;&amp;lt;tt&amp;gt;&#039;&#039;&#039;%c&#039;&#039;&#039;&amp;lt;/tt&amp;gt; dacă vrem să afișăm caracterul&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;unsigned char&amp;lt;/tt&amp;gt;&lt;br /&gt;
| 1&lt;br /&gt;
| style=&amp;quot;text-align:right;&amp;quot; | &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;&amp;amp;nbsp;...&amp;amp;nbsp;&amp;lt;tt&amp;gt;255&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;&#039;&#039;&#039;%u&#039;&#039;&#039;&amp;lt;/tt&amp;gt; dacă vrem să afișăm numărul întreg&amp;lt;br/&amp;gt;&amp;lt;tt&amp;gt;&#039;&#039;&#039;%c&#039;&#039;&#039;&amp;lt;/tt&amp;gt; dacă vrem să afișăm caracterul&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;short&amp;lt;/tt&amp;gt;&lt;br /&gt;
| 2&lt;br /&gt;
| style=&amp;quot;text-align:right;&amp;quot; | &amp;lt;tt&amp;gt;-32768&amp;lt;/tt&amp;gt;&amp;amp;nbsp;...&amp;amp;nbsp;&amp;lt;tt&amp;gt;32767&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;&#039;&#039;&#039;%hd&#039;&#039;&#039;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;unsigned short&amp;lt;/tt&amp;gt;&lt;br /&gt;
| 2&lt;br /&gt;
| style=&amp;quot;text-align:right;&amp;quot; | &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;&amp;amp;nbsp;...&amp;amp;nbsp;&amp;lt;tt&amp;gt;65535&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;&#039;&#039;&#039;%hu&#039;&#039;&#039;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;int&amp;lt;/tt&amp;gt;&lt;br /&gt;
| 4&lt;br /&gt;
| style=&amp;quot;text-align:right;&amp;quot; | &amp;lt;tt&amp;gt;-2147483648&amp;lt;/tt&amp;gt;&amp;amp;nbsp;...&amp;amp;nbsp;&amp;lt;tt&amp;gt;2147483647&amp;lt;/tt&amp;gt;&amp;lt;br/&amp;gt;aproximativ &#039;&#039;&#039;două miliarde&#039;&#039;&#039; (2 cu nouă zerouri)&lt;br /&gt;
| &amp;lt;tt&amp;gt;&#039;&#039;&#039;%d&#039;&#039;&#039;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;unsigned int&amp;lt;/tt&amp;gt;&lt;br /&gt;
| 4&lt;br /&gt;
| style=&amp;quot;text-align:right;&amp;quot; | &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;&amp;amp;nbsp;...&amp;amp;nbsp;&amp;lt;tt&amp;gt;4294967295&amp;lt;/tt&amp;gt;&amp;lt;br/&amp;gt;aproximativ &#039;&#039;&#039;patru miliarde&#039;&#039;&#039; (4 cu nouă zerouri)&lt;br /&gt;
| &amp;lt;tt&amp;gt;&#039;&#039;&#039;%u&#039;&#039;&#039;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;long long&amp;lt;/tt&amp;gt;&lt;br /&gt;
| 8&lt;br /&gt;
| style=&amp;quot;text-align:right;&amp;quot; | &amp;lt;tt&amp;gt;-9223372036854775808&amp;lt;/tt&amp;gt;&amp;amp;nbsp;...&amp;amp;nbsp;&amp;lt;tt&amp;gt;9223372036854775807&amp;lt;/tt&amp;gt;&amp;lt;br/&amp;gt;aproximativ &#039;&#039;&#039;9·10&amp;lt;sup&amp;gt;18&amp;lt;/sup&amp;gt;&#039;&#039;&#039; (9 cu 18 zerouri)&lt;br /&gt;
| &amp;lt;tt&amp;gt;&#039;&#039;&#039;%lld&#039;&#039;&#039;&amp;lt;/tt&amp;gt; în GNU/Linux și varena.ro&amp;lt;br/&amp;gt;&amp;lt;tt&amp;gt;&#039;&#039;&#039;%I64d&#039;&#039;&#039;&amp;lt;/tt&amp;gt; în Windows&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;unsigned long long&amp;lt;/tt&amp;gt;&lt;br /&gt;
| 8&lt;br /&gt;
| style=&amp;quot;text-align:right;&amp;quot; | &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;&amp;amp;nbsp;...&amp;amp;nbsp;&amp;lt;tt&amp;gt;18446744073709551615&amp;lt;/tt&amp;gt;&amp;lt;br/&amp;gt;aproximativ &#039;&#039;&#039;18·10&amp;lt;sup&amp;gt;18&amp;lt;/sup&amp;gt;&#039;&#039;&#039; (18 cu 18 zerouri)&lt;br /&gt;
| &amp;lt;tt&amp;gt;&#039;&#039;&#039;%llu&#039;&#039;&#039;&amp;lt;/tt&amp;gt; în GNU/Linux și varena.ro&amp;lt;br/&amp;gt;&amp;lt;tt&amp;gt;&#039;&#039;&#039;%I64u&#039;&#039;&#039;&amp;lt;/tt&amp;gt; în Windows&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;double&amp;lt;/tt&amp;gt;&lt;br /&gt;
| 8&lt;br /&gt;
| style=&amp;quot;text-align:right;&amp;quot; | valorile minime și maxime nu au aceeași relevanță,&amp;lt;br/&amp;gt;dar erorile de reprezentare cresc cu mărimea numărului&lt;br /&gt;
| &amp;lt;tt&amp;gt;&#039;&#039;&#039;%lf&#039;&#039;&#039;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Complexități algoritmice ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Algoritm&lt;br /&gt;
!Complexitate timp&lt;br /&gt;
!Complexitate memorie&lt;br /&gt;
! Explicații&lt;br /&gt;
|-&lt;br /&gt;
| Descompunerea lui &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; în factori primi&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\sqrt{n})&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Putem testa divizorii până la &amp;lt;math&amp;gt;O(\sqrt{n})&amp;lt;/math&amp;gt;. Dacă la final n este diferit de 1, el este un factor prim al lui n la puterea 1.&lt;br /&gt;
|-&lt;br /&gt;
| Ridicare la putere a&amp;lt;sup&amp;gt;b&amp;lt;/sup&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log{b})&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Vezi exponențiere logaritmică&lt;br /&gt;
|-&lt;br /&gt;
| Calcul număr de zile între două date calendaristice corecte z1.l1.a1 și z2.l2.a2&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Partea importantă este calculul numărului de ani bisecți între a1 și a2 în &#039;&#039;O(1)&#039;&#039;. Ea se reduce la problema calculului numărului de numere divizibile cu &#039;&#039;k&#039;&#039; între &#039;&#039;a&#039;&#039; și &#039;&#039;b&#039;&#039;.&lt;br /&gt;
|-&lt;br /&gt;
| Determinare dacă o secvență de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; numere de la intrare este bitonă (sau munte)&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Pe măsură ce citim numerele decidem proprietatea cerută, nu este nevoie să le memorăm.&lt;br /&gt;
|-&lt;br /&gt;
| Determinare corectitudine șir de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; paranteze închise și deschise&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Folosim un contor de paranteze deschise și încă neînchise. Nu avem nevoie să memorăm parantezele.&lt;br /&gt;
|-&lt;br /&gt;
| Determinare corectitudine șir de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; paranteze rotunde, drepte și acolade&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Similar anterior, dar menținem o stivă de paranteze deschise și încă neînchise, de aceea memoria este liniară.&lt;br /&gt;
|-&lt;br /&gt;
| Determinare corectitudine șir de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; paranteze de &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; tipuri&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Similar anterior, trebuie să avem grijă ca atunci când la intrare avem o paranteză închisă să testăm dacă pe stivă se află paranteza deschisă corectă în timp constant. Folosim un vector care pentru fiecare tip de paranteză închisă memorează codul parantezei deschise corespunzătoare.&lt;br /&gt;
|-&lt;br /&gt;
| Inserare element în vector de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Elementele de după poziția de inserție trebuie deplasate către dreapta.&lt;br /&gt;
|-&lt;br /&gt;
| Ștergere element din vector de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Elementele de după poziția de ștergere trebuie deplasate către stânga.&lt;br /&gt;
|-&lt;br /&gt;
| Interclasare a doi vectori ordonați de &amp;lt;code&amp;gt;n1&amp;lt;/code&amp;gt;, respectiv &amp;lt;code&amp;gt;n2&amp;lt;/code&amp;gt; elemente&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n1 + n2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n1 + n2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Sortare prin selecție a &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; numere - select sort&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Sortare prin numărare (cu vectori de frecvență) a &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; numere - counting sort&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n + X_{max})&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(X_{max})&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;X_{max}&amp;lt;/math&amp;gt; este valoarea maximă dintre numerele de la intrare. Va trebui să parcurgem vectorul de frecvență. La memorie se adaugă &#039;&#039;n&#039;&#039; dacă dorim să creăm un vector cu elementele sortate.&lt;br /&gt;
|-&lt;br /&gt;
| Elementul majoritar într-un vector de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Algoritmul face două parcurgeri ale elementelor, deci trebuie să le memorăm.&lt;br /&gt;
|-&lt;br /&gt;
| Calculul tuturor numerelor prime de la 1 la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; folosind ciurul lui Eratostene&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n \log \log{n})&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Descompunerea în factori primi a tuturor numerelor de la 2 la &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n \log{n})&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Vom calcula un ciur special în care pentru fiecare poziție &#039;&#039;i&#039;&#039; calculăm cel mai mare divizor prim al lui &#039;&#039;i&#039;&#039;. Pentru a obține descompunerea în factori primi a lui &#039;&#039;i&#039;&#039; vom afla divizorii în ordine descrescătoare, împărțind repetat pe &#039;&#039;i&#039;&#039; la ciur[i].&lt;br /&gt;
|-&lt;br /&gt;
| Căutare binară în vector ordonat de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; elemente&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\log{n})&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(n)&amp;lt;/math&amp;gt;&lt;br /&gt;
| &lt;br /&gt;
|-&lt;br /&gt;
| Calculul multiplicității numărului &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; în &amp;lt;code&amp;gt;n!&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(\sqrt{a} + \log{a} \log{n})&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt;&lt;br /&gt;
| Termenul &amp;lt;math&amp;gt;O(\sqrt{a})&amp;lt;/math&amp;gt; provine din descompunerea în factori primi a lui &#039;&#039;a&#039;&#039;. Apoi, pentru fiecare divizor prim al lui &#039;&#039;a&#039;&#039; (&amp;lt;math&amp;gt;O(\log{a})&amp;lt;/math&amp;gt;) vom calcula de câte ori se împarte &#039;&#039;n&#039;&#039; la acel divizor (&amp;lt;math&amp;gt;O(\log{n})&amp;lt;/math&amp;gt;).&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Funcții în limbajul C ==&lt;br /&gt;
Funcțiile permit programelor complicate să fie parcelate în blocuri mici, fiecare din ele fiind mai ușor de scris, citit și modificat (întreținut). Am întâlnit deja funcția &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; și am folosit funcții de intrare ieșire precum &amp;lt;code&amp;gt;fscanf()&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;fprintf()&amp;lt;/code&amp;gt;, precum și funcții matematice din bibliotecile standard precum &amp;lt;code&amp;gt;sqrt()&amp;lt;/code&amp;gt; sau &amp;lt;code&amp;gt;abs()&amp;lt;/code&amp;gt;. Vom vedea în continuare cum putem să scriem propriile noastre funcții.&lt;br /&gt;
&lt;br /&gt;
=== De ce funcții ===&lt;br /&gt;
* Pentru a nu repeta cod.&lt;br /&gt;
* Pentru organizare, citibilitate, ușurinta înțelegerii codului și întreținerea codului.&lt;br /&gt;
* Recursivitate.&lt;br /&gt;
&lt;br /&gt;
=== Sintaxa: cum scriem o funcție ===&lt;br /&gt;
==== Sintaxa (simplificare) ====&lt;br /&gt;
&amp;lt;tt&amp;gt;&lt;br /&gt;
  &amp;lt;span style=&amp;quot;color:darkblue&amp;quot;&amp;gt;tip-returnat&amp;lt;/span&amp;gt; nume-funcție( &amp;lt;span style=&amp;quot;color:darkblue&amp;quot;&amp;gt;tip-1&amp;lt;/span&amp;gt; param-1, &amp;lt;span style=&amp;quot;color:darkblue&amp;quot;&amp;gt;tip-2&amp;lt;/span&amp;gt; param-2, ..., &amp;lt;span style=&amp;quot;color:darkblue&amp;quot;&amp;gt;tip-n&amp;lt;/span&amp;gt; param-n ) {&lt;br /&gt;
    &amp;lt;span style=&amp;quot;color:green&amp;quot;&amp;gt;... declarații variabile ...&lt;br /&gt;
    &amp;amp;nbsp;&lt;br /&gt;
    ... cod funcție ...&amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;amp;nbsp;&lt;br /&gt;
    &amp;lt;span style=&amp;quot;color:darkblue&amp;quot;&amp;gt;return&amp;lt;/span&amp;gt; valoare-return; &amp;lt;span style=&amp;quot;color:darkred&amp;quot;&amp;gt;// dacă tipul returnat nu este &#039;void&#039;&amp;lt;/span&amp;gt;&lt;br /&gt;
  }&lt;br /&gt;
&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Exemplu ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
// functie ce returneaza valoarea minima dintre două numere&lt;br /&gt;
int min(int a, int b) {&lt;br /&gt;
   int rezultat; // declaratie de variabila locala&lt;br /&gt;
 &lt;br /&gt;
   if (a &amp;lt; b)&lt;br /&gt;
      rezultat = a;&lt;br /&gt;
   else&lt;br /&gt;
      rezultat = b;&lt;br /&gt;
 &lt;br /&gt;
   return rezultat; &lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Apelul ====&lt;br /&gt;
O funcție este apelată astfel: &lt;br /&gt;
&lt;br /&gt;
==== Apelul funcțiilor ====&lt;br /&gt;
Pentru a apela (sau chema) o funcție vom scrie numele funcției urmat, între paranteze, de parametrii ceruți. Ei pot fi expresii, nu doar variabile. Expresiile se vor apela, iar rezultatele lor vor fi depuse, la intrarea în funcție, în variabilele parametru corespunzătoare, conform ordinii din listă. Dacă funcția returnează o valoare ea poate fi folosită sau stocată într-o variabilă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;&lt;br /&gt;
  rez = nume-functie( valoare-1, valoare-2, ..., valoare-n );&lt;br /&gt;
&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de folosire a funcției &amp;lt;code&amp;gt;min()&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
// functie ce returnează valoarea minima dintre două numere&lt;br /&gt;
int min(int a, int b) {&lt;br /&gt;
   int rezultat; // declaratie de variabila locala&lt;br /&gt;
 &lt;br /&gt;
   if (a &amp;lt; b)&lt;br /&gt;
      rezultat = a;&lt;br /&gt;
   else&lt;br /&gt;
      rezultat = b;&lt;br /&gt;
 &lt;br /&gt;
   return rezultat; &lt;br /&gt;
 &lt;br /&gt;
int main () {&lt;br /&gt;
   int x, y, rez;&lt;br /&gt;
 &lt;br /&gt;
   x = 200;&lt;br /&gt;
   y = 100;&lt;br /&gt;
   rez = min( x, y ); // chemam functia pentru a obtine valoarea minima&lt;br /&gt;
 &lt;br /&gt;
   printf( &amp;quot;Valoarea minima este: %d\n&amp;quot;, rez );&lt;br /&gt;
 &lt;br /&gt;
   return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Parametri și valoare returnată ===&lt;br /&gt;
Precum am văzut, o funcție primește o listă de parametri și returnează o valoare. Funcția poate să nu returneze nici o valoare, caz în care tipul returnat va fi declarat ca &amp;lt;code&amp;gt;void&amp;lt;/code&amp;gt;, sau &amp;quot;vid&amp;quot; în limba engleză.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție&#039;&#039;&#039;: transmisia parametrilor în funcție se face &#039;&#039;&#039;prin copiere&#039;&#039;&#039;! Aceasta înseamnă că la apelul funcției valorile din apel vor fi atribuite parametrilor ce sunt variabile nou create ce primesc acele valori.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplul 1&#039;&#039;&#039;: ce afișează următorul program:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void inc( int a ) {&lt;br /&gt;
  a++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int x;&lt;br /&gt;
&lt;br /&gt;
  x = 0;&lt;br /&gt;
  inc( x );&lt;br /&gt;
  printf( &amp;quot;%d\n&amp;quot;, x );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Parametrul &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt; este o variabilă nou creată la intrarea în funcție în care se copiază valoarea variabilei &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;. Funcția modifică (incrementează) valoarea lui &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;, dar aceasta nu influențează valoarea lui &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt;, ele fiind variabile diferite. De aceea programul va afișa &amp;lt;tt&amp;gt;0&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplul 2&#039;&#039;&#039;: ce afișează următorul program:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void inc( int a[10] ) {&lt;br /&gt;
  a[0]++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int v[10];&lt;br /&gt;
&lt;br /&gt;
  v[0] = 17;&lt;br /&gt;
  inc( v );&lt;br /&gt;
  printf( &amp;quot;%d\n&amp;quot;, v[0] );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Surpriză, programul va afișa 18! Elementul zero al vectorului a fost modificat. Și atunci cum rămâne cu transmisia prin copiere? Răspunsul este greu de dat fără cunoștințe despre pointeri. Vectorii sunt ei înșiși adrese de variabile întregi. Parametrul vector se transmite prin copiere, însă ceea ce se copiază este adresa unde se află șirul de întregi ai vectorilor. De aceea modificarea unui vector într-o funcție are ca efect modificarea vectorului.&lt;br /&gt;
&lt;br /&gt;
Pentru moment este îndeajuns să țineți minte că variabilele simple se transmit prin copiere, iar vectorii (matricele, etc) se transmit ca atare, fără a fi copiați.&lt;br /&gt;
&lt;br /&gt;
=== Cum transmitem corect vectorii unei funcții ===&lt;br /&gt;
Un vector este un &#039;&#039;tip de date abstract&#039;&#039; care constă dintr-o pereche (&#039;&#039;&#039;n&#039;&#039;&#039;, &#039;&#039;&#039;v&#039;&#039;&#039;). &#039;&#039;&#039;n&#039;&#039;&#039; este lungimea vectorului (numărul de elememente), iar &#039;&#039;&#039;v&#039;&#039;&#039; este vectorul propriu zis. Ele nu se pot separa. De aceea vom transmite funcției atât vectorul cât și lungimea sa.&lt;br /&gt;
&lt;br /&gt;
==== Exemplu: suma elementelor unui vector ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int sumaVect( int n, int v[] ) {&lt;br /&gt;
  int i, s;&lt;br /&gt;
&lt;br /&gt;
  s = 0;&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    s += v[i];&lt;br /&gt;
&lt;br /&gt;
  return s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int x;&lt;br /&gt;
  int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };&lt;br /&gt;
&lt;br /&gt;
  x = sumaVect( 10, a );&lt;br /&gt;
  printf( &amp;quot;Suma este %d\n&amp;quot;, x );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Modificarea numărului de elemente al vectorului într-o funcție ===&lt;br /&gt;
Ce se întâmplă dacă funcția modifică numărul de elemente al vectorului, de exemplu inserează un element în vector? Ar trebui să modificăm numărul de elemente, ceea ce nu putem face în funcție, din cauza transmiterii prin copiere a parametrilor. O soluție poate fi ca funcția să returneze noul număr de elemente al vectorului.&lt;br /&gt;
&lt;br /&gt;
==== Exemplu: ștergerea unei valori din vector ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// sterge valoarea e din v[] de pe toate pozitiile unde apare&lt;br /&gt;
int stergVect( int n, int v[], int e ) {&lt;br /&gt;
  int i, j;&lt;br /&gt;
&lt;br /&gt;
  j = 0;&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    if ( v[i] != e ) {&lt;br /&gt;
      v[j] = v[i];&lt;br /&gt;
      j++;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
  return j;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, i;&lt;br /&gt;
  int a[10] = { 1, 2, 3, 4, 2, 3, 2, 8, 9, 2 };&lt;br /&gt;
&lt;br /&gt;
  n = 10;&lt;br /&gt;
  n = stergVect( n, a, 2 );&lt;br /&gt;
  printf( &amp;quot;Vectorul are acum %d elemente:&amp;quot;, n );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    printf( &amp;quot; %d&amp;quot;, a[i] );&lt;br /&gt;
  printf( &amp;quot;\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Programul va afișa:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;Vectorul are acum 6 elemente: 1 3 4 3 8 9&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pentru matrice cărora funcția le modifică dimensiunile vom vedea mai jos cum putem proceda.&lt;br /&gt;
&lt;br /&gt;
=== Exemple ===&lt;br /&gt;
Scrieți funcții care să rezolve:&lt;br /&gt;
&lt;br /&gt;
==== Inversare vector ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// inverseaza (rastoarna) elementele unui vector&lt;br /&gt;
void invVect( int n, int v[] ) {&lt;br /&gt;
  int i, j, aux;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  j = n - 1;&lt;br /&gt;
  while ( i &amp;lt; j ) {&lt;br /&gt;
    aux = v[i];&lt;br /&gt;
    v[i] = v[j];&lt;br /&gt;
    v[j] = aux;&lt;br /&gt;
    i++;&lt;br /&gt;
    j--;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, i;&lt;br /&gt;
  int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };&lt;br /&gt;
&lt;br /&gt;
  n = 10;&lt;br /&gt;
  invVect( n, a );&lt;br /&gt;
  printf( &amp;quot;Vectorul inversat este:&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    printf( &amp;quot; %d&amp;quot;, a[i] );&lt;br /&gt;
  printf( &amp;quot;\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Inserare element în vector la o poziție dată ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// insereaza un element e in vectorul v de n elemente la pozitia poz&lt;br /&gt;
// daca poz este mai mare strict decit n nu face nimic&lt;br /&gt;
int insVect( int e, int n, int v[], int poz ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( poz &amp;lt;= n ) { // putem insera?&lt;br /&gt;
    for ( i = n; i &amp;gt; poz; i-- )&lt;br /&gt;
      v[i] = v[i - 1];&lt;br /&gt;
    v[poz] = e;&lt;br /&gt;
    n++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  return n;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, e, i;&lt;br /&gt;
  int a[20] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };&lt;br /&gt;
&lt;br /&gt;
  n = 10;&lt;br /&gt;
  e = 15;&lt;br /&gt;
  n = insVect( e, n, a, 5 ); // insereaza 15 pe pozitia 5&lt;br /&gt;
  printf( &amp;quot;Vectorul cu %d inserat pe pozitia 5 este:&amp;quot;, e );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    printf( &amp;quot; %d&amp;quot;, a[i] );&lt;br /&gt;
  printf( &amp;quot;\n&amp;quot; );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Citire întreg fără semn din fișier folosind doar fgetc() ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;ctype.h&amp;gt; // pentru isdigit()&lt;br /&gt;
&lt;br /&gt;
FILE *fin;&lt;br /&gt;
&lt;br /&gt;
// Citeste un intreg de la intrare&lt;br /&gt;
int getInt() {&lt;br /&gt;
  int ch, n;&lt;br /&gt;
&lt;br /&gt;
  n = 0;&lt;br /&gt;
  while ( !isdigit( ch = fgetc( fin ) ) ); // ignora non-cifre&lt;br /&gt;
  do n = n * 10 + ch - &#039;0&#039;;&lt;br /&gt;
  while ( isdigit( ch = fgetc( fin ) ) );&lt;br /&gt;
&lt;br /&gt;
  return n;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Citim un vector din fisierul &#039;fin&#039; si il afisam&lt;br /&gt;
int main() {&lt;br /&gt;
  int n, i;&lt;br /&gt;
  int v[20];&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;exemplu.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  n = getInt();&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    v[i] = getInt();&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;Vectorul citit este:&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    printf( &amp;quot; %d&amp;quot;, v[i] );&lt;br /&gt;
  printf( &amp;quot;\n&amp;quot; );&lt;br /&gt;
  &lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Observați că am declarat variabila fișier în afara lui &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; pentru a putea fi vizibilă în funcție. Vom vorbi despre aceasta mai jos.&lt;br /&gt;
&lt;br /&gt;
=== Reguli de vizibilitate ale variabilelor în funcții ===&lt;br /&gt;
În C avem două tipuri de variabile: locale și globale.&lt;br /&gt;
&lt;br /&gt;
* Variabilele locale se declară în interiorul unei funcții (sau în interiorul lui &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt;) și sunt vizibile (apelabile, folosibile) doar în acea funcție. Se cheamă că variabilele aparțin acelei funcții.&lt;br /&gt;
* Variabilele globale se declara în afara oricărei funcții și sunt vizibile din toate funcțiile, inclusiv &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt;. Precum știm deja ele sunt și inițializate automat cu zero.&lt;br /&gt;
&lt;br /&gt;
Ce se întâmplă dacă denumim o variabilă locală cu același nume ca una globală? Cea locală are prioritate.&lt;br /&gt;
&lt;br /&gt;
==== Exemplu ====&lt;br /&gt;
Ce afișează următorul program?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int a = 100; // declarare variabila globala&lt;br /&gt;
&lt;br /&gt;
void afiseaza() {&lt;br /&gt;
  printf( &amp;quot;a global este %d\n&amp;quot;, a );&lt;br /&gt;
}&lt;br /&gt;
 &lt;br /&gt;
int main () {&lt;br /&gt;
  int a; // declarare variabila locala cu acelasi nume&lt;br /&gt;
&lt;br /&gt;
  a = 30; // atribuire variabila locala&lt;br /&gt;
&lt;br /&gt;
  afiseaza();&lt;br /&gt;
 &lt;br /&gt;
  printf( &amp;quot;a local este %d\n&amp;quot;, a );&lt;br /&gt;
 &lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Programul va afișa:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;a global este 100&lt;br /&gt;
a local este 30&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Modificarea parametrilor cu efect la ieșirea din funcție ===&lt;br /&gt;
Ce facem dacă totuși ne dorim să modificăm o variabilă transmisă ca parametru? În acest caz vom apela funcția cu adresa de memorie (zisă și &#039;&#039;pointer&#039;&#039;) a variabilei. Vom folosi doi operatori noi:&lt;br /&gt;
* &amp;lt;tt&amp;gt;&amp;amp;&amp;lt;/tt&amp;gt; se aplică unei variabile și extrage adresa de memorie a variabilei&lt;br /&gt;
* &amp;lt;tt&amp;gt;*&amp;lt;/tt&amp;gt; se aplică unei adrese de memorie și extrage conținutul variabilei de la acea adresă&lt;br /&gt;
&lt;br /&gt;
Ar fi bine să evitați, deocamdată, deoarece mai întâi trebuie să învățați noțiunea de &#039;&#039;pointeri&#039;&#039;, care nu face scopul acestei lecții.&lt;br /&gt;
&lt;br /&gt;
==== Exemplu: functia swap ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
void swap( int *pa, int *pb ) {&lt;br /&gt;
  int aux;&lt;br /&gt;
&lt;br /&gt;
  aux = *pa;&lt;br /&gt;
  *pa = *pb;&lt;br /&gt;
  *pb = aux;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  int a, b;&lt;br /&gt;
&lt;br /&gt;
  a = 100;&lt;br /&gt;
  b = 200;&lt;br /&gt;
  swap( &amp;amp;a, &amp;amp;b );&lt;br /&gt;
  printf( &amp;quot;a=%d b=%d\n&amp;quot;, a, b );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Declarații de variabile și vizibilitatea acestora. Așezarea lor pe stivă.  &lt;br /&gt;
* Conversia de tip când chemăm funcția cu alte tipuri decât cele declarate.&lt;br /&gt;
&lt;br /&gt;
=== Funcțiile și programarea structurată ===  &lt;br /&gt;
Instrucțiunea &amp;lt;tt&amp;gt;return&amp;lt;/tt&amp;gt; este echivalentul unui salt la finalul funcției. Programare evident nestructurată. De aceea, deși limbajul C ne permite, nu o vom folosi &#039;&#039;&#039;decât la finalul funcției&#039;&#039;&#039;, ca ultima instrucțiune. C este un limbaj vechi. Limbaje gen Pascal au rezolvat problema nestructurării eliminând instrucțiunea &amp;lt;tt&amp;gt;return&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Când scriem funcții? ===  &lt;br /&gt;
Bun, am învățat funcții. Dar cum iau decizia să scriu o funcție sau nu?  &lt;br /&gt;
* Atunci când nu putem evita repetiția de cod prin alte mijloace (factorizare, bucle, etc)  &lt;br /&gt;
* Atunci când programul devine prea lung pentru a fi citit ușor, de obicei undeva la 80-100 de linii. Ca regulă generală o funcție ar fi bine să aibă până în 100 de linii, dar desigur regula nu este bătută în cuie.  &lt;br /&gt;
* Atunci când simțim că o bucată semnificativă de cod are o coeziune, reprezintă un tot unitar, o putem transforma în funcție.  &lt;br /&gt;
* Atunci când scriem o bucată de cod care execută un calcul general, gen sortare, sau citirea unui întreg, atunci o putem scrie ca funcție pentru a o refolosi în viitor. Vom vedea că aceasta este baza de pornire a bibliotecilor de funcții.  &lt;br /&gt;
* Nu este bine să &amp;quot;rupem&amp;quot; artificial programul de dragul funcțiilor. Dacă tot programul ar avea 40 de linii, introducerea unor funcții i-ar scădea lizibilitatea.  &lt;br /&gt;
* Pe cazul general, la olimpiade și pentru programele mici care rezolvă probleme de concurs încercați să evitați funcțiile inutile, doar de dragul artei. Când tot programul, scris elegant, ar avea 80 de linii este destul de greu să justifici introducerea unor funcții.&lt;br /&gt;
&lt;br /&gt;
=== Cum denumim funcțiile? ===  &lt;br /&gt;
O funcție trebuie denumită cât mai sugestiv. Funcții denumite &amp;lt;code&amp;gt;citire()&amp;lt;/code&amp;gt; sau &amp;lt;code&amp;gt;solve()&amp;lt;/code&amp;gt; cu am văzut la colegii voștri mai mari fac codul mai necitibil decât dacă ele nu existau. Ele sunt echivalentul variabilelor denumite &amp;lt;code&amp;gt;contor&amp;lt;/code&amp;gt; sau &amp;lt;code&amp;gt;stegulet&amp;lt;/code&amp;gt;. Așa cum stegulețul ar fi bine să se numească &amp;lt;code&amp;gt;terminat&amp;lt;/code&amp;gt; sau &amp;lt;code&amp;gt;fara_zero&amp;lt;/code&amp;gt;, la fel și funcțiile trebuie denumite sugestiv. Pentru cele două funcții de mai sus nume bune ar fi &amp;lt;code&amp;gt;citesteVectorInaltimi()&amp;lt;/code&amp;gt; sau &amp;lt;code&amp;gt;calculSumePartiale()&amp;lt;/code&amp;gt;. Observați că primul cuvânt din denumirea unei funcții începe cu literă mică, celelalte cu literă mare. Aceasta este o convenție de codare. Pentru variabile formate din mai multe cuvinte vom folosi numai litere mici, unind cuvintele cu underscore (sau underline, caracterul &#039;_&#039;).&lt;br /&gt;
&lt;br /&gt;
=== Considerente de viteză ===  &lt;br /&gt;
Atunci când funcția poate fi chemată de un număr mare de ori calculatorul pierde un timp semnificativ în apelul de funcție. Un exemplu tipic ar fi când funcția este chemată într-o buclă care se execută de un milion de ori. Ce putem face?  &lt;br /&gt;
* Evitați astfel de funcții, dacă nu dăunează eleganței și citibilității programului: introduceți codul funcției în loc de apel.  &lt;br /&gt;
* Dacă totuși trebuie să folosiți o funcție aveți opțiunea să o declarați &amp;lt;tt&amp;gt;inline&amp;lt;/tt&amp;gt;. Aceasta înseamnă că compilatorul nu va genera cod care să cheme funcția ci va înlocui apelul cu chiar corpul funcției, peste tot unde apare el. Este ca și cum funcția nu ar exista, păstrând însă citibilitatea programului (nu și scurtimea executabilului generat, din păcate).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 1 ==&lt;br /&gt;
Să se rezolve următoarele probleme (program C în CodeBlocks, trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/rapper Rapper]&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/snake Snake]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Atenție!&#039;&#039; Implementările trebuie să conțină funcțiile menționate la prezentarea soluțiilor problemelor, mai sus. Mai exact:&lt;br /&gt;
&lt;br /&gt;
* La problema [https://www.nerdarena.ro/problema/rapper Rapper], veți scrie și utiliza următoarele funcții (folosind doar &amp;lt;code&amp;gt;fgetc()&amp;lt;/code&amp;gt; pentru citirea caracterelor):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#define LINLEN 100&lt;br /&gt;
&lt;br /&gt;
char linie[LINLEN + 1]; // unu in plus pentru terminatorul de linie &#039;\0&#039;&lt;br /&gt;
&lt;br /&gt;
// citeste o linie si calculeaza numarul de nume si pozitia ultimului spatiu&lt;br /&gt;
void citesteLinie( FILE *fin, int *pnnume, int *psp ) {&lt;br /&gt;
  // ... corpul functiei&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// calculeaza nota prezenta in linia curenta&lt;br /&gt;
// nota este un numar intre 0 si 1000, pentru a nu avea un numar cu zecimale&lt;br /&gt;
// i este pozitia ultimului spatiu din linie&lt;br /&gt;
int calcNota( int i ) {&lt;br /&gt;
  int nota = 0;&lt;br /&gt;
&lt;br /&gt;
  // ... corpul functiei&lt;br /&gt;
&lt;br /&gt;
  return nota;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// exemplu de utilizare&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int i, nnume, sp;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;rapper.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  // ...&lt;br /&gt;
  citesteLinie( fin, &amp;amp;nnume, &amp;amp;sp ); // citeste prima linie&lt;br /&gt;
  while ( nnume &amp;gt; 0 ) {    // linia are macar un spatiu - nu este ultima&lt;br /&gt;
    nlin++;                // numaram inca o linie&lt;br /&gt;
    // ...&lt;br /&gt;
    citesteLinie( fin, &amp;amp;nnume, &amp;amp;sp ); // citeste urmatoarea linie&lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  // ...&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* La problema [https://www.nerdarena.ro/problema/snake snake], veți scrie și utiliza următoarele funcții:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#define MAXN 1000&lt;br /&gt;
#define ZID -1&lt;br /&gt;
#define MAR -2&lt;br /&gt;
&lt;br /&gt;
int dlin[4] = { 0, 1, 0, -1 };&lt;br /&gt;
int dcol[4] = { 1, 0, -1, 0 };&lt;br /&gt;
&lt;br /&gt;
int tabla[MAXN][MAXN];&lt;br /&gt;
&lt;br /&gt;
// citeste perechi de coordonate la intrare si le marcheaza in matrice cu mark&lt;br /&gt;
void citestePozitii( FILE *fin, int mark ) {&lt;br /&gt;
  // corpul functiei&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// date:&lt;br /&gt;
// n - dimensiunea tablei&lt;br /&gt;
// (l c) - pozitia curenta&lt;br /&gt;
// *dir - directia curenta&lt;br /&gt;
//&lt;br /&gt;
// calculeaza (*lurm *curm) pozitia urmatoare si *dir pozitia urmatoare&lt;br /&gt;
inline static void genUrm( int n, int l, int c,&lt;br /&gt;
                           int *dir, int *lurm, int *curm ) {&lt;br /&gt;
  // ... corpul functiei&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// exemplu de utilizare&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int nivel, n, m, dir, lcap, ccap, lurm, curm, valcoada, aux;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;snake.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;nivel, &amp;amp;n, &amp;amp;m );&lt;br /&gt;
  citestePozitii( fin, ZID ); // citeste zidurile&lt;br /&gt;
  citestePozitii( fin, MAR ); // citeste merele&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  lcap = ccap = dir = 0;&lt;br /&gt;
  // ... alte initializari&lt;br /&gt;
  genUrm( n, lcap, ccap, &amp;amp;dir, &amp;amp;lurm, &amp;amp;curm ); // urmatoarea pozitie&lt;br /&gt;
  while ( m &amp;gt; 0 &amp;amp;&amp;amp; &amp;lt;conditie non-intersectie&amp;gt; ) {&lt;br /&gt;
    // ... bucla simulare, mutare sarpe, etc&lt;br /&gt;
    genUrm( n, lcap, ccap, &amp;amp;dir, &amp;amp;lurm, &amp;amp;curm ); // urmatoarea pozitie&lt;br /&gt;
  }&lt;br /&gt;
  // ...  &lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php/Clasa_a_7-a_Lec%C8%9Bia_1:_Reguli_de_programare_%C3%AEn_limbajul_C_%C8%99i_complexit%C4%83%C8%9Bi_algoritmice Accesează rezolvarea temei 1]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18563</id>
		<title>Clasa a 7-a Lecția 2: Problema selecției și stive</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18563"/>
		<updated>2026-02-06T07:26:32Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/gy3zK4E8SSo|||||start=7400&amp;amp;end=9880&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
== Problema selecției ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Problema&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Dat un șir de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; numere și o poziție &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; în acel șir să se spună ce element s-ar afla pe acea poziție dacă șirul ar fi sortat.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie șirul &amp;lt;code&amp;gt;3 9 23 13 18 8 3 10 2 10 15 21&amp;lt;/code&amp;gt; și poziția &amp;lt;code&amp;gt;k = 6&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dacă am ordona șirul am obține 2 3 3 8 9 10 10 13 15 18 21 23. Pe poziția 6 se află elementul 10, care este și răspunsul dorit.&lt;br /&gt;
&lt;br /&gt;
===Soluție forță brută===&lt;br /&gt;
&lt;br /&gt;
O soluție ce vine ușor în minte este să sortăm vectorul și apoi să afișăm elementul de la poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;. Nu este eficientă, complexitatea ei va fi &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; cu sortare prin selecție, sau &#039;&#039;O(n log n)&#039;&#039; cu sortări mai rapide.&lt;br /&gt;
&lt;br /&gt;
===Soluția folosind algoritmul Quick select===&lt;br /&gt;
&lt;br /&gt;
Aplicăm un pas de pivotare în mod repetat. Algoritmul este prezentat aici: [https://en.wikipedia.org/wiki/Quickselect quickselect].&lt;br /&gt;
&lt;br /&gt;
Ideea pivotării este următoarea: alegem un așa numit pivot. El este un element oarecare din vector. Putem să alegem elementul aflat la jumatea vectorului, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[n/2]&amp;lt;/source&amp;gt;. Sau putem să îl alegem la întâmplare, folosind o funcție ce generează numere aleatoare, cum ar fi funcția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;rand()&amp;lt;/source&amp;gt; în C (parte din biblioteca &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;stdlib.h&amp;lt;/source&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
Fie &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p&amp;lt;/source&amp;gt; indicele pivotului și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val = v[p]&amp;lt;/source&amp;gt; valoarea acelui pivot. Ne propunem să separăm vectorul în două: prima parte va conține elemente mai mici sau egale cu pivotul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val&amp;lt;/source&amp;gt;. A doua parte va conține elemente mai mari sau egale cu pivotul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Odată efecutată pivotarea vom ști în care din cele două părți se va afla elementul pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în vectorul sortat. Putem, deci, selecta acea parte, continuând algoritmul doar pentru acel subvector.&lt;br /&gt;
&lt;br /&gt;
Există mai mulți algoritmi de pivotare, toți liniari. Vă rămâne ca temă să implementați unul din ei. În linkul de pe [https://en.wikipedia.org/wiki/Quickselect Wikipedia] de mai sus găsiți algoritmul propus de Lomuto, care nu este cel mai eficient. Ca bonus, v-aș recomanda să implementați [https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme algoritmul original al lui Tony Hoare]. &lt;br /&gt;
&lt;br /&gt;
Atenție! El nu este structurat! Va trebui să îl modificați! Sau, voi aprecia și mai mult dacă găsiți propria voastră implementare :-) .&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce complexitate are acest algoritm?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom aplica în mod repetat pivotarea, ce are complexitate &#039;&#039;O(n)&#039;&#039;. Pe medie vom păstra, la fiecare pas, jumătate din vector. Complexitatea va fi, deci, &#039;&#039;O(n + n/2 + n/4 + ... + 1)&#039;&#039;. Observăm că suma nu va depăși niciodată &#039;&#039;2n&#039;&#039; (deși se va apropia de acea valoare). Complexitatea este, deci &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Atenție! Aceasta este o complexitate medie! Pe cazul cel mai rău putem să selectăm mereu o parte a vectorului care are cu un element mai puțin ca vectorul curent, ceea ce duce la o complexitate de &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; pe cazul cel mai rău.&lt;br /&gt;
&lt;br /&gt;
Aplicăm repetat pivotarea quickselect. Calcul aproximativ al complexității pe cazul mediu: este &#039;&#039;O(n)&#039;&#039;, în loc de &#039;&#039;O(n log n)&#039;&#039; dacă am fi sortat.&lt;br /&gt;
&lt;br /&gt;
Pentru cei curioși, există o îmbunătățire a algoritmului quick select ce îi schimbă complexitatea la &#039;&#039;O(n)&#039;&#039; chiar și pe cazul cel mai rău, dar ea depășește nivelul cursului nostru. Puteți citi mai multe despre medianul medianelor aici: [https://en.wikipedia.org/wiki/Median_of_medians https://en.wikipedia.org/wiki/Median_of_medians] .&lt;br /&gt;
&lt;br /&gt;
== Stive ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este un tip de date abstract?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Un tip de date abstract este un model matematic, ce privește tipul de date din punctul de vedere al utilizatorului. Astfel, el definește operații asupra datelor și comportamentul acestor operații, dar nu specifică o implementare.&lt;br /&gt;
&lt;br /&gt;
Tipurile de date abstracte separă funcționalitatea de implementare. Acest lucru este util pentru analiza complexității algoritmilor. Este de menționat faptul că unele TDA au multiple implementări, cu avantaje și dezavantaje, precum vom vedea în lecțiile următoare.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este o stivă?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Stiva (în engleză &#039;&#039;stack&#039;&#039;) este un concept simplu, pe care l-am mai folosit în trecut, fără a sublinia faptul că folosim o stivă. &lt;br /&gt;
&lt;br /&gt;
O stivă este o &#039;&#039;grămadă&#039;&#039; de obiecte ordonate după ordinea LIFO: last in, first out. Aceasta înseamnă că putem adăuga obiecte în stivă, iar atunci când le vom scoate, le vom scoate în ordine inversă față de cum le-am adăugat. Vom vedea în lecțiile următoare că, prin contrast, coada scoate obiectele în aceeași ordine în care au fost adăugate.&lt;br /&gt;
&lt;br /&gt;
Stiva este un tip de date abstract în care definim operațiile:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;push(value)&amp;lt;/source&amp;gt; - inserează o valoare dată la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;top&amp;lt;/source&amp;gt; - returnează valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pop&amp;lt;/source&amp;gt; - șterge valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;empty&amp;lt;/source&amp;gt; - returnează 1 dacă stiva este goală (poate fi implementată și în cadrul operației top).&lt;br /&gt;
&lt;br /&gt;
Pentru aceste operații există restricția menționată, anume că valorile trebuie returnate cu regula &#039;&#039;&#039;LIFO&#039;&#039;&#039; (Last In, First Out).&lt;br /&gt;
&lt;br /&gt;
Pentru a înțelege mai bine ce este o stivă, urmăriți clipul de mai jos:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.youtube.com/watch?v=I37kGX-nZEI&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Implementare: stivă de întregi pozitivi sau zero===&lt;br /&gt;
&lt;br /&gt;
În practică stiva se implementează aproape întotdeauna ca un vector căruia i se adaugă la coadă elemente:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#define MAXN 1000&lt;br /&gt;
&lt;br /&gt;
int stack[MAXN];       // stocarea valorilor propriu-zise&lt;br /&gt;
int stack_pointer = 0; // indice la prima valoare nefolosita in vector (totuna cu numărul de elemente din stiva)&lt;br /&gt;
&lt;br /&gt;
// adauga valoarea &#039;val&#039; la stiva, daca aceasta nu este plina&lt;br /&gt;
// returneaza 1 in caz de succes, 0 in caz de stiva plina&lt;br /&gt;
int push( int val ) {&lt;br /&gt;
  if ( stack_pointer &amp;amp;gt;= MAXN )  // stiva este plina?&lt;br /&gt;
    return 0;                   // returnam eroare&lt;br /&gt;
&lt;br /&gt;
  stack[stack_pointer++] = val; // adaugam valoarea in stiva&lt;br /&gt;
  return 1;                     // returnam succes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returnează valoarea aflată în virful stivei, dacă ea exista&lt;br /&gt;
// in caz contrar returneaza -1&lt;br /&gt;
int top() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0 ? stack[stack_pointer - 1] : -1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// elimina ultimul element din stiva daca se poate&lt;br /&gt;
// returneaza 1 daca operatia a reusit, 0 in caz de eroare&lt;br /&gt;
int pop() {&lt;br /&gt;
  if ( stack_pointer == 0 ) // testam cazul de eroare&lt;br /&gt;
    return 0;&lt;br /&gt;
&lt;br /&gt;
  stack_pointer--; // daca avem elemente pe stiva, il eliminam pe ultimul&lt;br /&gt;
  return 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza 1 daca stiva este goala, 0 in caz contrar&lt;br /&gt;
int empty() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* În această implementare, cele patru operații au complexitate &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* Remarcați o folosire corectă a funcțiilor: ele implementează operațiuni ce au sens separat, de sine stătător.&lt;br /&gt;
* Remarcați denumirile funcțiilor: ele sugerează operațiunea implementată.&lt;br /&gt;
* Am folosit instrucțiunea return în mijlocul funcției. Aceasta este o excepție de la regulă. Pe caz general, atunci când la intrarea într-o funcție avem de tratat cazuri particulare de ieșire imediată din funcție, putem folosi instrucțiunea return. Codul devine astfel mai clar, iar restul corpului funcției nu se deplasează spre dreapta prin indentare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Sfaturi în utilizarea stivelor pentru rezolvarea problemelor===&lt;br /&gt;
&lt;br /&gt;
* Pentru problemele noastre, stiva se va implementa cu ajutorul unui vector;&lt;br /&gt;
* Operațiile &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;top&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;empty&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pop&amp;lt;/source&amp;gt; presupun verificarea și manipularea numărului de elemente din vector. În exemplul de implementare numărul de elemente este denumit &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;stack_pointer&amp;lt;/source&amp;gt;. Dar dacă vectorul vostru denumește numărul de elemente &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;, atunci veți opera cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;;&lt;br /&gt;
* Nu este neapărată nevoie să scrieți funcții ca cele de mai sus pentru a lucra cu stive. Exemplul de mai sus își dorește să explice noțiunea de tip de date abstract care definește acele operații, iar implementarea arată cum s-ar implementa ele. Deci, ne interesează modul de funcționare al stivei, dar implementarea o putem face cum simțim că se potrivește mai bine problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 2 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C în CodeBlocks, trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/selectie Selecție]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/betisoare Bețișoare], dată la ONI 2014 clasa a 6a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/zuma Zuma], dată la.campion 2011&lt;br /&gt;
* [https://www.nerdarena.ro/problema/swap Swap], dată la ONI 2013 baraj gimnaziu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive Accesează rezolvarea temei 2]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18562</id>
		<title>Clasa a 7-a Lecția 2: Problema selecției și stive</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18562"/>
		<updated>2026-02-06T07:25:20Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/gy3zK4E8SSo|||||start=7400&amp;amp;end=9880&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
== Problema selecției ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Problema&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Dat un șir de &amp;lt;code&amp;gt;n&amp;lt;/code&amp;gt; numere și o poziție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în acel șir să se spună ce element s-ar afla pe acea poziție dacă șirul ar fi sortat.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie șirul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;3 9 23 13 18 8 3 10 2 10 15 21&amp;lt;/source&amp;gt; și poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k = 6&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dacă am ordona șirul am obține 2 3 3 8 9 10 10 13 15 18 21 23. Pe poziția 6 se află elementul 10, care este și răspunsul dorit.&lt;br /&gt;
&lt;br /&gt;
===Soluție forță brută===&lt;br /&gt;
&lt;br /&gt;
O soluție ce vine ușor în minte este să sortăm vectorul și apoi să afișăm elementul de la poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;. Nu este eficientă, complexitatea ei va fi &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; cu sortare prin selecție, sau &#039;&#039;O(n log n)&#039;&#039; cu sortări mai rapide.&lt;br /&gt;
&lt;br /&gt;
===Soluția folosind algoritmul Quick select===&lt;br /&gt;
&lt;br /&gt;
Aplicăm un pas de pivotare în mod repetat. Algoritmul este prezentat aici: [https://en.wikipedia.org/wiki/Quickselect quickselect].&lt;br /&gt;
&lt;br /&gt;
Ideea pivotării este următoarea: alegem un așa numit pivot. El este un element oarecare din vector. Putem să alegem elementul aflat la jumatea vectorului, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[n/2]&amp;lt;/source&amp;gt;. Sau putem să îl alegem la întâmplare, folosind o funcție ce generează numere aleatoare, cum ar fi funcția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;rand()&amp;lt;/source&amp;gt; în C (parte din biblioteca &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;stdlib.h&amp;lt;/source&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
Fie &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p&amp;lt;/source&amp;gt; indicele pivotului și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val = v[p]&amp;lt;/source&amp;gt; valoarea acelui pivot. Ne propunem să separăm vectorul în două: prima parte va conține elemente mai mici sau egale cu pivotul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val&amp;lt;/source&amp;gt;. A doua parte va conține elemente mai mari sau egale cu pivotul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Odată efecutată pivotarea vom ști în care din cele două părți se va afla elementul pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în vectorul sortat. Putem, deci, selecta acea parte, continuând algoritmul doar pentru acel subvector.&lt;br /&gt;
&lt;br /&gt;
Există mai mulți algoritmi de pivotare, toți liniari. Vă rămâne ca temă să implementați unul din ei. În linkul de pe [https://en.wikipedia.org/wiki/Quickselect Wikipedia] de mai sus găsiți algoritmul propus de Lomuto, care nu este cel mai eficient. Ca bonus, v-aș recomanda să implementați [https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme algoritmul original al lui Tony Hoare]. &lt;br /&gt;
&lt;br /&gt;
Atenție! El nu este structurat! Va trebui să îl modificați! Sau, voi aprecia și mai mult dacă găsiți propria voastră implementare :-) .&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce complexitate are acest algoritm?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom aplica în mod repetat pivotarea, ce are complexitate &#039;&#039;O(n)&#039;&#039;. Pe medie vom păstra, la fiecare pas, jumătate din vector. Complexitatea va fi, deci, &#039;&#039;O(n + n/2 + n/4 + ... + 1)&#039;&#039;. Observăm că suma nu va depăși niciodată &#039;&#039;2n&#039;&#039; (deși se va apropia de acea valoare). Complexitatea este, deci &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Atenție! Aceasta este o complexitate medie! Pe cazul cel mai rău putem să selectăm mereu o parte a vectorului care are cu un element mai puțin ca vectorul curent, ceea ce duce la o complexitate de &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; pe cazul cel mai rău.&lt;br /&gt;
&lt;br /&gt;
Aplicăm repetat pivotarea quickselect. Calcul aproximativ al complexității pe cazul mediu: este &#039;&#039;O(n)&#039;&#039;, în loc de &#039;&#039;O(n log n)&#039;&#039; dacă am fi sortat.&lt;br /&gt;
&lt;br /&gt;
Pentru cei curioși, există o îmbunătățire a algoritmului quick select ce îi schimbă complexitatea la &#039;&#039;O(n)&#039;&#039; chiar și pe cazul cel mai rău, dar ea depășește nivelul cursului nostru. Puteți citi mai multe despre medianul medianelor aici: [https://en.wikipedia.org/wiki/Median_of_medians https://en.wikipedia.org/wiki/Median_of_medians] .&lt;br /&gt;
&lt;br /&gt;
== Stive ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este un tip de date abstract?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Un tip de date abstract este un model matematic, ce privește tipul de date din punctul de vedere al utilizatorului. Astfel, el definește operații asupra datelor și comportamentul acestor operații, dar nu specifică o implementare.&lt;br /&gt;
&lt;br /&gt;
Tipurile de date abstracte separă funcționalitatea de implementare. Acest lucru este util pentru analiza complexității algoritmilor. Este de menționat faptul că unele TDA au multiple implementări, cu avantaje și dezavantaje, precum vom vedea în lecțiile următoare.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este o stivă?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Stiva (în engleză &#039;&#039;stack&#039;&#039;) este un concept simplu, pe care l-am mai folosit în trecut, fără a sublinia faptul că folosim o stivă. &lt;br /&gt;
&lt;br /&gt;
O stivă este o &#039;&#039;grămadă&#039;&#039; de obiecte ordonate după ordinea LIFO: last in, first out. Aceasta înseamnă că putem adăuga obiecte în stivă, iar atunci când le vom scoate, le vom scoate în ordine inversă față de cum le-am adăugat. Vom vedea în lecțiile următoare că, prin contrast, coada scoate obiectele în aceeași ordine în care au fost adăugate.&lt;br /&gt;
&lt;br /&gt;
Stiva este un tip de date abstract în care definim operațiile:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;push(value)&amp;lt;/source&amp;gt; - inserează o valoare dată la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;top&amp;lt;/source&amp;gt; - returnează valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pop&amp;lt;/source&amp;gt; - șterge valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;empty&amp;lt;/source&amp;gt; - returnează 1 dacă stiva este goală (poate fi implementată și în cadrul operației top).&lt;br /&gt;
&lt;br /&gt;
Pentru aceste operații există restricția menționată, anume că valorile trebuie returnate cu regula &#039;&#039;&#039;LIFO&#039;&#039;&#039; (Last In, First Out).&lt;br /&gt;
&lt;br /&gt;
Pentru a înțelege mai bine ce este o stivă, urmăriți clipul de mai jos:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.youtube.com/watch?v=I37kGX-nZEI&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Implementare: stivă de întregi pozitivi sau zero===&lt;br /&gt;
&lt;br /&gt;
În practică stiva se implementează aproape întotdeauna ca un vector căruia i se adaugă la coadă elemente:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#define MAXN 1000&lt;br /&gt;
&lt;br /&gt;
int stack[MAXN];       // stocarea valorilor propriu-zise&lt;br /&gt;
int stack_pointer = 0; // indice la prima valoare nefolosita in vector (totuna cu numărul de elemente din stiva)&lt;br /&gt;
&lt;br /&gt;
// adauga valoarea &#039;val&#039; la stiva, daca aceasta nu este plina&lt;br /&gt;
// returneaza 1 in caz de succes, 0 in caz de stiva plina&lt;br /&gt;
int push( int val ) {&lt;br /&gt;
  if ( stack_pointer &amp;amp;gt;= MAXN )  // stiva este plina?&lt;br /&gt;
    return 0;                   // returnam eroare&lt;br /&gt;
&lt;br /&gt;
  stack[stack_pointer++] = val; // adaugam valoarea in stiva&lt;br /&gt;
  return 1;                     // returnam succes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returnează valoarea aflată în virful stivei, dacă ea exista&lt;br /&gt;
// in caz contrar returneaza -1&lt;br /&gt;
int top() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0 ? stack[stack_pointer - 1] : -1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// elimina ultimul element din stiva daca se poate&lt;br /&gt;
// returneaza 1 daca operatia a reusit, 0 in caz de eroare&lt;br /&gt;
int pop() {&lt;br /&gt;
  if ( stack_pointer == 0 ) // testam cazul de eroare&lt;br /&gt;
    return 0;&lt;br /&gt;
&lt;br /&gt;
  stack_pointer--; // daca avem elemente pe stiva, il eliminam pe ultimul&lt;br /&gt;
  return 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza 1 daca stiva este goala, 0 in caz contrar&lt;br /&gt;
int empty() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* În această implementare, cele patru operații au complexitate &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* Remarcați o folosire corectă a funcțiilor: ele implementează operațiuni ce au sens separat, de sine stătător.&lt;br /&gt;
* Remarcați denumirile funcțiilor: ele sugerează operațiunea implementată.&lt;br /&gt;
* Am folosit instrucțiunea return în mijlocul funcției. Aceasta este o excepție de la regulă. Pe caz general, atunci când la intrarea într-o funcție avem de tratat cazuri particulare de ieșire imediată din funcție, putem folosi instrucțiunea return. Codul devine astfel mai clar, iar restul corpului funcției nu se deplasează spre dreapta prin indentare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Sfaturi în utilizarea stivelor pentru rezolvarea problemelor===&lt;br /&gt;
&lt;br /&gt;
* Pentru problemele noastre, stiva se va implementa cu ajutorul unui vector;&lt;br /&gt;
* Operațiile &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;top&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;empty&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pop&amp;lt;/source&amp;gt; presupun verificarea și manipularea numărului de elemente din vector. În exemplul de implementare numărul de elemente este denumit &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;stack_pointer&amp;lt;/source&amp;gt;. Dar dacă vectorul vostru denumește numărul de elemente &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;, atunci veți opera cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;;&lt;br /&gt;
* Nu este neapărată nevoie să scrieți funcții ca cele de mai sus pentru a lucra cu stive. Exemplul de mai sus își dorește să explice noțiunea de tip de date abstract care definește acele operații, iar implementarea arată cum s-ar implementa ele. Deci, ne interesează modul de funcționare al stivei, dar implementarea o putem face cum simțim că se potrivește mai bine problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 2 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C în CodeBlocks, trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/selectie Selecție]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/betisoare Bețișoare], dată la ONI 2014 clasa a 6a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/zuma Zuma], dată la.campion 2011&lt;br /&gt;
* [https://www.nerdarena.ro/problema/swap Swap], dată la ONI 2013 baraj gimnaziu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive Accesează rezolvarea temei 2]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18561</id>
		<title>Clasa a 7-a Lecția 2: Problema selecției și stive</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18561"/>
		<updated>2026-02-06T07:24:35Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/gy3zK4E8SSo|||||start=7400&amp;amp;end=9880&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
== Problema selecției ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Problema&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Dat un șir de &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; numere și o poziție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în acel șir să se spună ce element s-ar afla pe acea poziție dacă șirul ar fi sortat.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie șirul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;3 9 23 13 18 8 3 10 2 10 15 21&amp;lt;/source&amp;gt; și poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k = 6&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dacă am ordona șirul am obține 2 3 3 8 9 10 10 13 15 18 21 23. Pe poziția 6 se află elementul 10, care este și răspunsul dorit.&lt;br /&gt;
&lt;br /&gt;
===Soluție forță brută===&lt;br /&gt;
&lt;br /&gt;
O soluție ce vine ușor în minte este să sortăm vectorul și apoi să afișăm elementul de la poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;. Nu este eficientă, complexitatea ei va fi &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; cu sortare prin selecție, sau &#039;&#039;O(n log n)&#039;&#039; cu sortări mai rapide.&lt;br /&gt;
&lt;br /&gt;
===Soluția folosind algoritmul Quick select===&lt;br /&gt;
&lt;br /&gt;
Aplicăm un pas de pivotare în mod repetat. Algoritmul este prezentat aici: [https://en.wikipedia.org/wiki/Quickselect quickselect].&lt;br /&gt;
&lt;br /&gt;
Ideea pivotării este următoarea: alegem un așa numit pivot. El este un element oarecare din vector. Putem să alegem elementul aflat la jumatea vectorului, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[n/2]&amp;lt;/source&amp;gt;. Sau putem să îl alegem la întâmplare, folosind o funcție ce generează numere aleatoare, cum ar fi funcția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;rand()&amp;lt;/source&amp;gt; în C (parte din biblioteca &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;stdlib.h&amp;lt;/source&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
Fie &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p&amp;lt;/source&amp;gt; indicele pivotului și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val = v[p]&amp;lt;/source&amp;gt; valoarea acelui pivot. Ne propunem să separăm vectorul în două: prima parte va conține elemente mai mici sau egale cu pivotul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val&amp;lt;/source&amp;gt;. A doua parte va conține elemente mai mari sau egale cu pivotul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Odată efecutată pivotarea vom ști în care din cele două părți se va afla elementul pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în vectorul sortat. Putem, deci, selecta acea parte, continuând algoritmul doar pentru acel subvector.&lt;br /&gt;
&lt;br /&gt;
Există mai mulți algoritmi de pivotare, toți liniari. Vă rămâne ca temă să implementați unul din ei. În linkul de pe [https://en.wikipedia.org/wiki/Quickselect Wikipedia] de mai sus găsiți algoritmul propus de Lomuto, care nu este cel mai eficient. Ca bonus, v-aș recomanda să implementați [https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme algoritmul original al lui Tony Hoare]. &lt;br /&gt;
&lt;br /&gt;
Atenție! El nu este structurat! Va trebui să îl modificați! Sau, voi aprecia și mai mult dacă găsiți propria voastră implementare :-) .&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce complexitate are acest algoritm?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom aplica în mod repetat pivotarea, ce are complexitate &#039;&#039;O(n)&#039;&#039;. Pe medie vom păstra, la fiecare pas, jumătate din vector. Complexitatea va fi, deci, &#039;&#039;O(n + n/2 + n/4 + ... + 1)&#039;&#039;. Observăm că suma nu va depăși niciodată &#039;&#039;2n&#039;&#039; (deși se va apropia de acea valoare). Complexitatea este, deci &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Atenție! Aceasta este o complexitate medie! Pe cazul cel mai rău putem să selectăm mereu o parte a vectorului care are cu un element mai puțin ca vectorul curent, ceea ce duce la o complexitate de &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; pe cazul cel mai rău.&lt;br /&gt;
&lt;br /&gt;
Aplicăm repetat pivotarea quickselect. Calcul aproximativ al complexității pe cazul mediu: este &#039;&#039;O(n)&#039;&#039;, în loc de &#039;&#039;O(n log n)&#039;&#039; dacă am fi sortat.&lt;br /&gt;
&lt;br /&gt;
Pentru cei curioși, există o îmbunătățire a algoritmului quick select ce îi schimbă complexitatea la &#039;&#039;O(n)&#039;&#039; chiar și pe cazul cel mai rău, dar ea depășește nivelul cursului nostru. Puteți citi mai multe despre medianul medianelor aici: [https://en.wikipedia.org/wiki/Median_of_medians https://en.wikipedia.org/wiki/Median_of_medians] .&lt;br /&gt;
&lt;br /&gt;
== Stive ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este un tip de date abstract?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Un tip de date abstract este un model matematic, ce privește tipul de date din punctul de vedere al utilizatorului. Astfel, el definește operații asupra datelor și comportamentul acestor operații, dar nu specifică o implementare.&lt;br /&gt;
&lt;br /&gt;
Tipurile de date abstracte separă funcționalitatea de implementare. Acest lucru este util pentru analiza complexității algoritmilor. Este de menționat faptul că unele TDA au multiple implementări, cu avantaje și dezavantaje, precum vom vedea în lecțiile următoare.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este o stivă?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Stiva (în engleză &#039;&#039;stack&#039;&#039;) este un concept simplu, pe care l-am mai folosit în trecut, fără a sublinia faptul că folosim o stivă. &lt;br /&gt;
&lt;br /&gt;
O stivă este o &#039;&#039;grămadă&#039;&#039; de obiecte ordonate după ordinea LIFO: last in, first out. Aceasta înseamnă că putem adăuga obiecte în stivă, iar atunci când le vom scoate, le vom scoate în ordine inversă față de cum le-am adăugat. Vom vedea în lecțiile următoare că, prin contrast, coada scoate obiectele în aceeași ordine în care au fost adăugate.&lt;br /&gt;
&lt;br /&gt;
Stiva este un tip de date abstract în care definim operațiile:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;push(value)&amp;lt;/source&amp;gt; - inserează o valoare dată la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;top&amp;lt;/source&amp;gt; - returnează valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pop&amp;lt;/source&amp;gt; - șterge valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;empty&amp;lt;/source&amp;gt; - returnează 1 dacă stiva este goală (poate fi implementată și în cadrul operației top).&lt;br /&gt;
&lt;br /&gt;
Pentru aceste operații există restricția menționată, anume că valorile trebuie returnate cu regula &#039;&#039;&#039;LIFO&#039;&#039;&#039; (Last In, First Out).&lt;br /&gt;
&lt;br /&gt;
Pentru a înțelege mai bine ce este o stivă, urmăriți clipul de mai jos:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.youtube.com/watch?v=I37kGX-nZEI&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Implementare: stivă de întregi pozitivi sau zero===&lt;br /&gt;
&lt;br /&gt;
În practică stiva se implementează aproape întotdeauna ca un vector căruia i se adaugă la coadă elemente:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#define MAXN 1000&lt;br /&gt;
&lt;br /&gt;
int stack[MAXN];       // stocarea valorilor propriu-zise&lt;br /&gt;
int stack_pointer = 0; // indice la prima valoare nefolosita in vector (totuna cu numărul de elemente din stiva)&lt;br /&gt;
&lt;br /&gt;
// adauga valoarea &#039;val&#039; la stiva, daca aceasta nu este plina&lt;br /&gt;
// returneaza 1 in caz de succes, 0 in caz de stiva plina&lt;br /&gt;
int push( int val ) {&lt;br /&gt;
  if ( stack_pointer &amp;amp;gt;= MAXN )  // stiva este plina?&lt;br /&gt;
    return 0;                   // returnam eroare&lt;br /&gt;
&lt;br /&gt;
  stack[stack_pointer++] = val; // adaugam valoarea in stiva&lt;br /&gt;
  return 1;                     // returnam succes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returnează valoarea aflată în virful stivei, dacă ea exista&lt;br /&gt;
// in caz contrar returneaza -1&lt;br /&gt;
int top() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0 ? stack[stack_pointer - 1] : -1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// elimina ultimul element din stiva daca se poate&lt;br /&gt;
// returneaza 1 daca operatia a reusit, 0 in caz de eroare&lt;br /&gt;
int pop() {&lt;br /&gt;
  if ( stack_pointer == 0 ) // testam cazul de eroare&lt;br /&gt;
    return 0;&lt;br /&gt;
&lt;br /&gt;
  stack_pointer--; // daca avem elemente pe stiva, il eliminam pe ultimul&lt;br /&gt;
  return 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza 1 daca stiva este goala, 0 in caz contrar&lt;br /&gt;
int empty() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* În această implementare, cele patru operații au complexitate &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* Remarcați o folosire corectă a funcțiilor: ele implementează operațiuni ce au sens separat, de sine stătător.&lt;br /&gt;
* Remarcați denumirile funcțiilor: ele sugerează operațiunea implementată.&lt;br /&gt;
* Am folosit instrucțiunea return în mijlocul funcției. Aceasta este o excepție de la regulă. Pe caz general, atunci când la intrarea într-o funcție avem de tratat cazuri particulare de ieșire imediată din funcție, putem folosi instrucțiunea return. Codul devine astfel mai clar, iar restul corpului funcției nu se deplasează spre dreapta prin indentare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Sfaturi în utilizarea stivelor pentru rezolvarea problemelor===&lt;br /&gt;
&lt;br /&gt;
* Pentru problemele noastre, stiva se va implementa cu ajutorul unui vector;&lt;br /&gt;
* Operațiile &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;top&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;empty&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pop&amp;lt;/source&amp;gt; presupun verificarea și manipularea numărului de elemente din vector. În exemplul de implementare numărul de elemente este denumit &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;stack_pointer&amp;lt;/source&amp;gt;. Dar dacă vectorul vostru denumește numărul de elemente &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;, atunci veți opera cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;;&lt;br /&gt;
* Nu este neapărată nevoie să scrieți funcții ca cele de mai sus pentru a lucra cu stive. Exemplul de mai sus își dorește să explice noțiunea de tip de date abstract care definește acele operații, iar implementarea arată cum s-ar implementa ele. Deci, ne interesează modul de funcționare al stivei, dar implementarea o putem face cum simțim că se potrivește mai bine problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 2 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C în CodeBlocks, trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/selectie Selecție]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/betisoare Bețișoare], dată la ONI 2014 clasa a 6a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/zuma Zuma], dată la.campion 2011&lt;br /&gt;
* [https://www.nerdarena.ro/problema/swap Swap], dată la ONI 2013 baraj gimnaziu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive Accesează rezolvarea temei 2]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18560</id>
		<title>Clasa a 7-a Lecția 2: Problema selecției și stive</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive&amp;diff=18560"/>
		<updated>2026-02-06T07:23:30Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
{{#ev:youtube|https://youtu.be/gy3zK4E8SSo|||||start=7400&amp;amp;end=9880&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
== Problema selecției ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Problema&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Dat un șir de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; numere și o poziție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în acel șir să se spună ce element s-ar afla pe acea poziție dacă șirul ar fi sortat.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie șirul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;3 9 23 13 18 8 3 10 2 10 15 21&amp;lt;/source&amp;gt; și poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k = 6&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Dacă am ordona șirul am obține 2 3 3 8 9 10 10 13 15 18 21 23. Pe poziția 6 se află elementul 10, care este și răspunsul dorit.&lt;br /&gt;
&lt;br /&gt;
===Soluție forță brută===&lt;br /&gt;
&lt;br /&gt;
O soluție ce vine ușor în minte este să sortăm vectorul și apoi să afișăm elementul de la poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;. Nu este eficientă, complexitatea ei va fi &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; cu sortare prin selecție, sau &#039;&#039;O(n log n)&#039;&#039; cu sortări mai rapide.&lt;br /&gt;
&lt;br /&gt;
===Soluția folosind algoritmul Quick select===&lt;br /&gt;
&lt;br /&gt;
Aplicăm un pas de pivotare în mod repetat. Algoritmul este prezentat aici: [https://en.wikipedia.org/wiki/Quickselect quickselect].&lt;br /&gt;
&lt;br /&gt;
Ideea pivotării este următoarea: alegem un așa numit pivot. El este un element oarecare din vector. Putem să alegem elementul aflat la jumatea vectorului, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[n/2]&amp;lt;/source&amp;gt;. Sau putem să îl alegem la întâmplare, folosind o funcție ce generează numere aleatoare, cum ar fi funcția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;rand()&amp;lt;/source&amp;gt; în C (parte din biblioteca &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;stdlib.h&amp;lt;/source&amp;gt;). &lt;br /&gt;
&lt;br /&gt;
Fie &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p&amp;lt;/source&amp;gt; indicele pivotului și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val = v[p]&amp;lt;/source&amp;gt; valoarea acelui pivot. Ne propunem să separăm vectorul în două: prima parte va conține elemente mai mici sau egale cu pivotul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val&amp;lt;/source&amp;gt;. A doua parte va conține elemente mai mari sau egale cu pivotul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;val&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Odată efecutată pivotarea vom ști în care din cele două părți se va afla elementul pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în vectorul sortat. Putem, deci, selecta acea parte, continuând algoritmul doar pentru acel subvector.&lt;br /&gt;
&lt;br /&gt;
Există mai mulți algoritmi de pivotare, toți liniari. Vă rămâne ca temă să implementați unul din ei. În linkul de pe [https://en.wikipedia.org/wiki/Quickselect Wikipedia] de mai sus găsiți algoritmul propus de Lomuto, care nu este cel mai eficient. Ca bonus, v-aș recomanda să implementați [https://en.wikipedia.org/wiki/Quicksort#Hoare_partition_scheme algoritmul original al lui Tony Hoare]. &lt;br /&gt;
&lt;br /&gt;
Atenție! El nu este structurat! Va trebui să îl modificați! Sau, voi aprecia și mai mult dacă găsiți propria voastră implementare :-) .&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce complexitate are acest algoritm?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom aplica în mod repetat pivotarea, ce are complexitate &#039;&#039;O(n)&#039;&#039;. Pe medie vom păstra, la fiecare pas, jumătate din vector. Complexitatea va fi, deci, &#039;&#039;O(n + n/2 + n/4 + ... + 1)&#039;&#039;. Observăm că suma nu va depăși niciodată &#039;&#039;2n&#039;&#039; (deși se va apropia de acea valoare). Complexitatea este, deci &#039;&#039;O(n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Atenție! Aceasta este o complexitate medie! Pe cazul cel mai rău putem să selectăm mereu o parte a vectorului care are cu un element mai puțin ca vectorul curent, ceea ce duce la o complexitate de &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; pe cazul cel mai rău.&lt;br /&gt;
&lt;br /&gt;
Aplicăm repetat pivotarea quickselect. Calcul aproximativ al complexității pe cazul mediu: este &#039;&#039;O(n)&#039;&#039;, în loc de &#039;&#039;O(n log n)&#039;&#039; dacă am fi sortat.&lt;br /&gt;
&lt;br /&gt;
Pentru cei curioși, există o îmbunătățire a algoritmului quick select ce îi schimbă complexitatea la &#039;&#039;O(n)&#039;&#039; chiar și pe cazul cel mai rău, dar ea depășește nivelul cursului nostru. Puteți citi mai multe despre medianul medianelor aici: [https://en.wikipedia.org/wiki/Median_of_medians https://en.wikipedia.org/wiki/Median_of_medians] .&lt;br /&gt;
&lt;br /&gt;
== Stive ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este un tip de date abstract?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Un tip de date abstract este un model matematic, ce privește tipul de date din punctul de vedere al utilizatorului. Astfel, el definește operații asupra datelor și comportamentul acestor operații, dar nu specifică o implementare.&lt;br /&gt;
&lt;br /&gt;
Tipurile de date abstracte separă funcționalitatea de implementare. Acest lucru este util pentru analiza complexității algoritmilor. Este de menționat faptul că unele TDA au multiple implementări, cu avantaje și dezavantaje, precum vom vedea în lecțiile următoare.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ce este o stivă?&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Stiva (în engleză &#039;&#039;stack&#039;&#039;) este un concept simplu, pe care l-am mai folosit în trecut, fără a sublinia faptul că folosim o stivă. &lt;br /&gt;
&lt;br /&gt;
O stivă este o &#039;&#039;grămadă&#039;&#039; de obiecte ordonate după ordinea LIFO: last in, first out. Aceasta înseamnă că putem adăuga obiecte în stivă, iar atunci când le vom scoate, le vom scoate în ordine inversă față de cum le-am adăugat. Vom vedea în lecțiile următoare că, prin contrast, coada scoate obiectele în aceeași ordine în care au fost adăugate.&lt;br /&gt;
&lt;br /&gt;
Stiva este un tip de date abstract în care definim operațiile:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;push(value)&amp;lt;/source&amp;gt; - inserează o valoare dată la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;top&amp;lt;/source&amp;gt; - returnează valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pop&amp;lt;/source&amp;gt; - șterge valoarea de la sfârșitul listei;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;empty&amp;lt;/source&amp;gt; - returnează 1 dacă stiva este goală (poate fi implementată și în cadrul operației top).&lt;br /&gt;
&lt;br /&gt;
Pentru aceste operații există restricția menționată, anume că valorile trebuie returnate cu regula &#039;&#039;&#039;LIFO&#039;&#039;&#039; (Last In, First Out).&lt;br /&gt;
&lt;br /&gt;
Pentru a înțelege mai bine ce este o stivă, urmăriți clipul de mai jos:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.youtube.com/watch?v=I37kGX-nZEI&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Implementare: stivă de întregi pozitivi sau zero===&lt;br /&gt;
&lt;br /&gt;
În practică stiva se implementează aproape întotdeauna ca un vector căruia i se adaugă la coadă elemente:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#define MAXN 1000&lt;br /&gt;
&lt;br /&gt;
int stack[MAXN];       // stocarea valorilor propriu-zise&lt;br /&gt;
int stack_pointer = 0; // indice la prima valoare nefolosita in vector (totuna cu numărul de elemente din stiva)&lt;br /&gt;
&lt;br /&gt;
// adauga valoarea &#039;val&#039; la stiva, daca aceasta nu este plina&lt;br /&gt;
// returneaza 1 in caz de succes, 0 in caz de stiva plina&lt;br /&gt;
int push( int val ) {&lt;br /&gt;
  if ( stack_pointer &amp;amp;gt;= MAXN )  // stiva este plina?&lt;br /&gt;
    return 0;                   // returnam eroare&lt;br /&gt;
&lt;br /&gt;
  stack[stack_pointer++] = val; // adaugam valoarea in stiva&lt;br /&gt;
  return 1;                     // returnam succes&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returnează valoarea aflată în virful stivei, dacă ea exista&lt;br /&gt;
// in caz contrar returneaza -1&lt;br /&gt;
int top() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0 ? stack[stack_pointer - 1] : -1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// elimina ultimul element din stiva daca se poate&lt;br /&gt;
// returneaza 1 daca operatia a reusit, 0 in caz de eroare&lt;br /&gt;
int pop() {&lt;br /&gt;
  if ( stack_pointer == 0 ) // testam cazul de eroare&lt;br /&gt;
    return 0;&lt;br /&gt;
&lt;br /&gt;
  stack_pointer--; // daca avem elemente pe stiva, il eliminam pe ultimul&lt;br /&gt;
  return 1;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// returneaza 1 daca stiva este goala, 0 in caz contrar&lt;br /&gt;
int empty() {&lt;br /&gt;
  return stack_pointer &amp;amp;gt; 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observații:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* În această implementare, cele patru operații au complexitate &#039;&#039;O(1)&#039;&#039;.&lt;br /&gt;
* Remarcați o folosire corectă a funcțiilor: ele implementează operațiuni ce au sens separat, de sine stătător.&lt;br /&gt;
* Remarcați denumirile funcțiilor: ele sugerează operațiunea implementată.&lt;br /&gt;
* Am folosit instrucțiunea return în mijlocul funcției. Aceasta este o excepție de la regulă. Pe caz general, atunci când la intrarea într-o funcție avem de tratat cazuri particulare de ieșire imediată din funcție, putem folosi instrucțiunea return. Codul devine astfel mai clar, iar restul corpului funcției nu se deplasează spre dreapta prin indentare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Sfaturi în utilizarea stivelor pentru rezolvarea problemelor===&lt;br /&gt;
&lt;br /&gt;
* Pentru problemele noastre, stiva se va implementa cu ajutorul unui vector;&lt;br /&gt;
* Operațiile &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;top&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;empty&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pop&amp;lt;/source&amp;gt; presupun verificarea și manipularea numărului de elemente din vector. În exemplul de implementare numărul de elemente este denumit &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;stack_pointer&amp;lt;/source&amp;gt;. Dar dacă vectorul vostru denumește numărul de elemente &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;, atunci veți opera cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;;&lt;br /&gt;
* Nu este neapărată nevoie să scrieți funcții ca cele de mai sus pentru a lucra cu stive. Exemplul de mai sus își dorește să explice noțiunea de tip de date abstract care definește acele operații, iar implementarea arată cum s-ar implementa ele. Deci, ne interesează modul de funcționare al stivei, dar implementarea o putem face cum simțim că se potrivește mai bine problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 2 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C în CodeBlocks, trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/selectie Selecție]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/betisoare Bețișoare], dată la ONI 2014 clasa a 6a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/zuma Zuma], dată la.campion 2011&lt;br /&gt;
* [https://www.nerdarena.ro/problema/swap Swap], dată la ONI 2013 baraj gimnaziu.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_2:_Problema_selec%C8%9Biei_%C8%99i_stive Accesează rezolvarea temei 2]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_13:_Analiz%C4%83_amortizat%C4%83_(2)&amp;diff=18559</id>
		<title>Clasa a 7-a Lecția 13: Analiză amortizată (2)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_13:_Analiz%C4%83_amortizat%C4%83_(2)&amp;diff=18559"/>
		<updated>2026-02-06T07:22:23Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video curs ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/_oXoXO7EEWY&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Unific ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/unific Unific] a fost dată la OJI 2013 clasa a 7-a. Problema definește o procedură prin care două numere pot fi unificate, dacă au măcar o cifră în comun. Apoi cere să se aplice pe un vector unificări de elemente adiacente până ce nu se mai poate unifica nimic. Întotdeauna se va face prima unificare posibilă de la începutul vectorului.&lt;br /&gt;
&lt;br /&gt;
Soluția forță brută este să căutăm prima pereche unificabilă de la începutul vectorului și să o unificăm. Apoi reluăm, de la începutul vectorului. Ea este pătratică, deci &#039;&#039;&#039;va depăși timpul pe teste mari&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
O soluție mai bună este ca la momentul citirii unui număr nou să îl unificăm cu el dinainte, apoi rezultatul cu cel dinainte și tot așa până ce nu mai putem unifica. Apoi citim următorul număr și reluăm.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție? &lt;br /&gt;
&lt;br /&gt;
La prima vedere un număr nou poate fi unificat de cel mult n ori, ceea ce duce la o complexitate pătratică. Desigur că nu e așa. Deoarece fiecare număr poate fi adăugat o singură dată și eliminat o singură dată (prin unificare) rezultă că &#039;&#039;&#039;numărul de unificări este maxim 2n. Analiza amortizată ne spune că soluția este liniară în numărul de unificări&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
O parte grea este unificarea propriu zisă. Deși algoritmul este clar, există destule cazuri particulare, iar implementarea dă suficiente bătăi de cap, așa încât mulți elevi au greșit-o la olimpiadă, denumind-o &#039;&#039;problemă tractor&#039;&#039;. &lt;br /&gt;
&lt;br /&gt;
În realitate nu este chiar așa, ea putându-se implementa în circa 90 de linii de cod. &lt;br /&gt;
&lt;br /&gt;
=== Implementare soluție ===&lt;br /&gt;
&lt;br /&gt;
==== Subprograme cu parametri de ieșire ====&lt;br /&gt;
&lt;br /&gt;
În problema de față ne poate fi folositoare o funcție care returnează valori folosind parametri de ieșire. Un parametru de ieșire reprezintă o variabilă care păstrează valoarea calculată în funcție și în afara ei. Am mai vorbit despre ei, dar poate este un moment bun să reluăm.&lt;br /&gt;
&lt;br /&gt;
Știm să scriem &#039;&#039;&#039;subprograme cu parametri de intrare&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int subprogram (int variabila1, int variabila2) {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Pentru a putea avea un parametru de ieșire, suntem vom declara acest parametru ca pointer:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int subprogram (int variabila1, int *variabila2) {}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Vom avea grijă ca oriunde folosim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;*variabila2&amp;lt;/source&amp;gt; să păstram simbolul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;*&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Când chemăm subprogramul undeva, vom avea grijă să îi spunem compilatorului care este parametrul de ieșire:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
a = subprogram (var1, &amp;amp;amp;var2);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Simbolul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;&amp;amp;&amp;lt;/source&amp;gt; raportează adresa de memorie unde este stocată acea variabilă, astfel că, executarea programului va folosi adresele de memorie, nu valorile în sine din variabile. Așa se explică de ce trimitem parametrii comenzii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fscanf()&amp;lt;/source&amp;gt; cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;&amp;amp;&amp;lt;/source&amp;gt;, pentru ca funcția să poată returna variabilele modificate.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de program care folosește parametri de ieșire. Programul următor calculează suma și produsul a două numere într-un subprogram de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt;. Suma este returnată de către subprogram iar produsul de către parametrul de ieșire al subprogramului:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int suma_produs(int a, int b, int *produs) {&lt;br /&gt;
    *produs = a * b;&lt;br /&gt;
    return a + b;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main()&lt;br /&gt;
{&lt;br /&gt;
    FILE *fin, *fout;&lt;br /&gt;
    int nr1, nr2, suma, prod;&lt;br /&gt;
&lt;br /&gt;
    fin = fopen(&amp;quot;sp.in&amp;quot;, &amp;quot;r&amp;quot;);&lt;br /&gt;
    fscanf(fin, &amp;quot;%d%d&amp;quot;, &amp;amp;nr1, &amp;amp;nr2);&lt;br /&gt;
    fclose(fin);&lt;br /&gt;
&lt;br /&gt;
    suma = suma_produs(nr1, nr2, &amp;amp;prod);&lt;br /&gt;
&lt;br /&gt;
    fout = fopen(&amp;quot;sp.out&amp;quot;, &amp;quot;w&amp;quot;);&lt;br /&gt;
    fprintf(fout, &amp;quot;%d %d\n&amp;quot;, suma, prod);&lt;br /&gt;
    fclose(fout);&lt;br /&gt;
&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Urmează sugestii de implementare. Le puteți ignora dacă doriți :-)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Programul principal ====&lt;br /&gt;
&lt;br /&gt;
În programul principal:&lt;br /&gt;
&lt;br /&gt;
* vom folosi &#039;&#039;&#039;o stivă&#039;&#039;&#039; în care vom păstra atâtea doar numere care nu mai pot produce o unificare. Să numim stiva &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;num[]&amp;lt;/source&amp;gt;;&lt;br /&gt;
* vom determina &#039;&#039;&#039;cea mai folosită cifră&#039;&#039;&#039;, prin încărcarea tuturor cifrelor într-un vector de frecvență. Să îl numim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;c[]&amp;lt;/source&amp;gt;. Pe măsură ce vom citi numerele la intrare, vom prelucra cifrele acestuia în vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;c&amp;lt;/source&amp;gt;. Astfel rezolvăm punctul a al problemei;&lt;br /&gt;
* vom verifica &#039;&#039;&#039;dacă putem face o unificare&#039;&#039;&#039;. Vom verifica dacă stiva are cel puțin două elemente, și dacă da, vom apela o funcție care verifică dacă ultimele două elemente din stivă se pot unifica. Să numim această funcție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;unific()&amp;lt;/source&amp;gt;. Ea va avea antetul astfel: &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;long long unific (long long a, long long b)&amp;lt;/source&amp;gt;. Funcția ne va returna una din situațiile:&lt;br /&gt;
** fie cele două numere se pot unifica și rezultă un nou număr, caz în care scoatem ultimele două numere din stivă și adăugăm noul număr; numărul de elemente din stivă scade cu unu.&lt;br /&gt;
** fie cele două numere se pot unifica și dispar, caz în care pur și simplu scoatem ultimele două elemente din stivă; numărul de elemente din stivă scade cu 2.&lt;br /&gt;
** fie cele două numere nu se pot unifica.&lt;br /&gt;
* doar în cazul în care ultimele două numere se pot unifica și rezultă un singur număr (primul din cele trei cazuri anterioare) vom continua procedura de unificare, verificând ultimele două numere pe stivă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Subprogramul unific() ====&lt;br /&gt;
&lt;br /&gt;
Antetul programului este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
long long unific (long long a, long long b) &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Acesta va returna rezultatul unificării a două numere, a și b. Rezultatul poate avea unul dintre următoarele valori:&lt;br /&gt;
&lt;br /&gt;
* numărul rezultat prin unificare;&lt;br /&gt;
* -1, dacă dispar ambele numere;&lt;br /&gt;
* -2, dacă nu se unifică.&lt;br /&gt;
&lt;br /&gt;
Vom implementa procedura descrisă în cerință. Pentru a simplifica calculele, vom defini 2 vectori de frecvență, unul pentru fiecare număr. În &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;va[10]&amp;lt;/source&amp;gt; vom salva cifrele numărului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt; și în &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;vb[10]&amp;lt;/source&amp;gt; vom salva cifrele numărului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;b&amp;lt;/source&amp;gt;. Putem crea o altă funcție, care să calculeze acești vectori sau o putem face aici.&lt;br /&gt;
&lt;br /&gt;
De asemenea, vom avea nevoie să calculăm cea mai mare putere a lui 10, mai mică sau egală cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt;, respectiv cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;b&amp;lt;/source&amp;gt;. Să le notăm &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pa&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pb&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Vom parcurge cei doi vectori de frecvență, va și vb și vom vedea dacă găsim cel puțin o cifră comună. Dacă da, atunci trebuie să creăm noul număr. Pentru că există posibilitatea ca după unificare, ambele numere să nu mai aibă nici o cifră, vom crea noul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt; și noul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;b&amp;lt;/source&amp;gt;. Apoi vom verifica dacă oricare dintre cele două numere noi sunt diferite de 0. Dacă sunt diferite de 0, va trebui să construim numărul din nou.&lt;br /&gt;
&lt;br /&gt;
Pentru a calcula noul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt; sau noul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;b&amp;lt;/source&amp;gt;, vom crea altă funcție, să o numim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;nrnou()&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Subprogramul nrnou() ====&lt;br /&gt;
&lt;br /&gt;
Antetul programului este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
long long nrnou (long long a, long long p10, int v[], int *nrcf) &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Acesta va returna noul număr și numărul lui de cifre în parametrul de ieșire &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;*nrcf&amp;lt;/source&amp;gt;. Explicația parametrilor este următoarea:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt;: numărul din care se elimină cifrele;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;pa&amp;lt;/source&amp;gt;: puterea cea mai mare a lui 10 care încape în &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt;, inițial;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;: vectorul de frecvență al cifrelor rămase în noul număr. La apelare va fi de fapt vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;va[]&amp;lt;/source&amp;gt; sau &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;vb[]&amp;lt;/source&amp;gt;, pe care îl vom folosi pentru calcule;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;nrcf&amp;lt;/source&amp;gt;: numărul de cifre al noului număr (se calculeaza și returnează).&lt;br /&gt;
&lt;br /&gt;
Modul de calcul ar trebui să fie deja evident. Câtă vreme sunt cifre în numărul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt;, vom calcula prima cifră cu ajutorul puterii pa și vom elimina prima cifră din &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt;. Dacă cifra există în vectorul de frecvență o adăugăm la numărul nou construit. Vom actualiza numărul de cifre pentru noul număr.&lt;br /&gt;
&lt;br /&gt;
Atenție, tot aici trebuie să ajustăm noul număr când singurele cifre rămase sunt 0.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema MaxArea ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/maxarea MaxArea] a fost dată la concursul Shumen pentru juniori în 2015.&lt;br /&gt;
&lt;br /&gt;
Rezolvarea folosește o stivă, similar cu problemele [https://www.nerdarena.ro/problema/tower Tower] sau [https://www.nerdarena.ro/problema/maxp MaxP]. Vom memora o stivă de dreptunghiuri de înălțimi din ce în ce mai mari. Când adăugăm o înălțime &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;h&amp;lt;/source&amp;gt; vom închide toate dreptunghiurile de înălțimi mai mari de pe stivă. Apoi depunem pe stivă dreptunghiul curent.&lt;br /&gt;
&lt;br /&gt;
Avem două cazuri, dacă dreptunghiul curent este de înălțime strict mai mare vom crea un nou dreptunghi pe stivă de &#039;&#039;lățime 1 + lățimea însumată a dreptunghiurilor eliminate&#039;&#039;. Altfel, în caz de egalitate, vom mări lățimea dreptunghiului din vârful stivei cu același număr, &#039;&#039;1 + lătimea însumată a dreptunghiurilor eliminate&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
De remarcat că problema cere citirea a un milion de întregi de 5 cifre. Incluzând separatorul, spațiu, aceasta înseamnă circa 6MB. Soluția fiind &#039;&#039;O(n)&#039;&#039;, la fel ca și citirea, este clar că ea va fi dominată ca timp de citire. De aceea este important să nu citim cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fscanf()&amp;lt;/source&amp;gt;, ci cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fgetc()&amp;lt;/source&amp;gt; (parsing în limbajul olimpicilor).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație:&#039;&#039;&#039; spre finalul numerelor pe stivă se vor afla foarte multe înălțimi egale. O optimizare ar fi, deci, să &#039;&#039;grupăm&#039;&#039; acele înălțimi. În loc de o înălțime putem stoca o pereche &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(h, ap)&amp;lt;/source&amp;gt; unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;ap&amp;lt;/source&amp;gt; ne spune numărul de apariții ale acelei înălțimi. Când adăugăm o înălțime egală la stivă vom aduna de fapt 1 la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;ap&amp;lt;/source&amp;gt;. Astfel mărimea stivei rămâne mică.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Skyline ==&lt;br /&gt;
&lt;br /&gt;
Problema [https://www.nerdarena.ro/problema/skyline Skyline] este o problemă clasică. Cere să se găsească dreptunghiul de arie maximă într-un skyline, unde un skyline este linia lăsată de zgârie-nori. Cu alte cuvinte o secvență de dreptunghiuri aliniate cu axa &#039;&#039;Ox&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Rezolvarea folosește o stivă, similar cu problemele [https://www.nerdarena.ro/problema/tower Tower] sau [https://www.nerdarena.ro/problema/maxp MaxP]. Vom memora o stivă de dreptunghiuri de înălțimi din ce în ce mai mari. Când adăugăm un dreptunghi de înălțime &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;h&amp;lt;/source&amp;gt; vom închide toate dreptunghiurile de înălțime mai mare de pe stivă, având grijă să le înlocuim cu un dreptunghi egal cu suma lungimilor lor și de înălțime egală cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;h&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Complexitatea este &#039;&#039;O(n)&#039;&#039; prin analiză amortizată și &#039;&#039;O(n)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Temă 13  ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/unific Unific] dată la OJI 2013 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/maxarea MaxArea] dată la Shumen Juniori 2015&lt;br /&gt;
* [https://www.nerdarena.ro/problema/skyline Skyline] problemă clasică de analiză amortizată&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/dreptunghi1 Dreptunghi1] problemă clasică de analiză amortizată&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php/Clasa_a_7-a_Lec%C8%9Bia_13:_Analiz%C4%83_amortizat%C4%83_(2) Accesează rezolvarea temei 13]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_7:_Recursivitate_(1)&amp;diff=18558</id>
		<title>Clasa a 7-a Lecția 7: Recursivitate (1)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_7:_Recursivitate_(1)&amp;diff=18558"/>
		<updated>2026-02-06T07:19:45Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/n_LKos0gG0Q&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Despre recursivitate ==&lt;br /&gt;
&lt;br /&gt;
O funcție este recursivă dacă se autoapelează. A scrie o funcție recursivă necesită, inițial, o încredere în faptul că funcția funcționează. În fapt, când pornim să scriem o funcție recursivă este bine să considerăm că ea deja funcționează!&lt;br /&gt;
&lt;br /&gt;
Reguli de scriere a unei funcții recursive:&lt;br /&gt;
&lt;br /&gt;
* Înainte de a scrie cod este bine să ne clarificăm formula recurentă.&lt;br /&gt;
* Tratăm mai întâi cazurile simple, atunci când funcția poate returna o valoare fără a se autoapela.&lt;br /&gt;
* În final, tratăm cazul de recursie, considerând că funcția este deja scrisă pentru cazurile &#039;&#039;mai mici&#039;&#039;, folosindu-ne de formula recurentă găsită la început.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Iată un videoclip de prezentare cu exemple de funcții recursive, cu imagini *:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.youtube.com/watch?v=ngCos392W4w&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039; * Exemplele de cod din film sunt scrise într-un pseudocod, suficient de bine explicate astfel încât să fie ușor redactate în orice limbaj de programare, inclusiv C.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Exemple introductive ==&lt;br /&gt;
&lt;br /&gt;
=== Factorial ===&lt;br /&gt;
&lt;br /&gt;
Calcul n! Ne vom folosi de definiția recursivă: &amp;lt;math&amp;gt;n! = \begin{cases} 1, &amp;amp; \mbox{dacă }  n = 1  \\ n \cdot (n - 1)!,  &amp;amp; \mbox{dacă } n &amp;gt; 1. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int fact( int n ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return n * fact( n - 1 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Putere ===&lt;br /&gt;
Calcul a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;. Ne vom folosi de definiția recurentă: &amp;lt;math&amp;gt;a^{n} = \begin{cases} 1, &amp;amp; \mbox{dacă }  n = 0  \\ a \cdot a^{n-1},  &amp;amp; \mbox{dacă } n &amp;gt; 0. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int putere( int a, int n ) {&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return a * putere( a, n - 1 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Cmmdc ===&lt;br /&gt;
Calcul cmmdc a două numere. Ne vom folosi de definiția recursivă a lui Euclid: &amp;lt;math&amp;gt;cmmdc(a, b) = \begin{cases} a, &amp;amp; \mbox{dacă }  b = 0  \\ cmmdc(b, a \bmod b),  &amp;amp; \mbox{dacă } b &amp;gt; 0. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int cmmdc( int a, int b ) {&lt;br /&gt;
  if ( b == 0 )&lt;br /&gt;
    return a;&lt;br /&gt;
  return cmmdc( b, a % b );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Exerciții ==&lt;br /&gt;
&lt;br /&gt;
Încercați să scrieți următoarele funcții recursive.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Fibonacci ===&lt;br /&gt;
Să se scrie o funcție recursivă care, dat &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;, calculează al &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;-lea termen din șirul lui Fibonacci: &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;0 1 1 2 3 5 8 13...&amp;lt;/source&amp;gt;. Ne vom folosi de definiția recurentă: &amp;lt;math&amp;gt;fib(n) = \begin{cases} 0, &amp;amp; \mbox{dacă }  n = 1 \\ 1, &amp;amp; \mbox{dacă }  n = 2 \\ fib(n-1) + fib(n-2),  &amp;amp; \mbox{dacă } n &amp;gt; 2. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int fib( int n ) {&lt;br /&gt;
  if ( n == 1 )&lt;br /&gt;
    return 0;&lt;br /&gt;
  if ( n == 2 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  return fib( n - 1 ) + fib ( n - 2 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Suma cifrelor unui număr ===&lt;br /&gt;
Să se scrie o funcție recursivă care calculează suma cifrelor unui număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ne vom folosi de următoarea formulă recurentă: &amp;lt;math&amp;gt;suma(n) = \begin{cases} 0, &amp;amp; \mbox{dacă }  n = 0  \\ n % 10 + suma(n / 10),  &amp;amp; \mbox{dacă } n &amp;gt; 0. \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int sumac( int n ) {&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 0;&lt;br /&gt;
  return n % 10 + sumac( n / 10 );&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Putere în O(log n) ===&lt;br /&gt;
Calculați &amp;lt;tt&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt; în mod cât mai eficient (a și n numere naturale). Problema este cunoscută şi sub numele de ridicare la putere în timp logaritmic. Ideea din spatele acestei rezolvări este următoarea:&lt;br /&gt;
&lt;br /&gt;
*Dacă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; este par, atunci &amp;lt;tt&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; = a&amp;lt;sup&amp;gt;2*n/2&amp;lt;/sup&amp;gt; = (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;n/2&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
*Dacă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; este impar, atunci &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-1&amp;lt;/source&amp;gt; este par și avem &amp;lt;tt&amp;gt;a&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; = a * a&amp;lt;sup&amp;gt;n-1&amp;lt;/sup&amp;gt; =  a * a&amp;lt;sup&amp;gt;2*(n-1)/2&amp;lt;/sup&amp;gt; = a * (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;(n-1)/2&amp;lt;/sup&amp;gt; = a * (a&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&amp;lt;sup&amp;gt;n/2&amp;lt;/sup&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Rezultă formula de recurență:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;a^{n} = \begin{cases} 1, &amp;amp; \mbox{dacă }  n = 0 \\ {(a^{2})}^{n/2},  &amp;amp; \mbox{dacă } n &amp;gt;0, n % 2 = 0 \\ a \times {(a^{2})}^{n/2},  &amp;amp; \mbox{dacă } n &amp;gt; 0, n % 2 = 1 \end{cases}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
În formulele de mai sus am considerat că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;/&amp;lt;/source&amp;gt; este împărțirea întreagă din limbajul C. Se observă că indiferent de paritatea lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;, la fiecare pas al iterației putem transforma &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt; în &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a * a&amp;lt;/source&amp;gt; și apoi putem împărți &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;2&amp;lt;/source&amp;gt;. Doar în cazurile când &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; este impar vom acumula valoarea curentă a lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt; la produsul calculat. Iată soluția bazată pe această idee:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int putere( int a, int n ) {&lt;br /&gt;
  int p;&lt;br /&gt;
&lt;br /&gt;
  if ( n == 0 )&lt;br /&gt;
    return 1;&lt;br /&gt;
  p =  putere( a * a, n / 2 );&lt;br /&gt;
  if ( n % 2 ) // n impar?&lt;br /&gt;
    p *= a;&lt;br /&gt;
&lt;br /&gt;
  return p;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Afișare ===&lt;br /&gt;
Să se afișeze un șir de caractere în ordine inversă. Șirul se termină cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;&#039;\n&#039;&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ne vom folosi de următoarea idee: citim un caracter, apoi chemăm funcția recursivă pentru a afișa restul caracterelor, apoi afișăm caracterul.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void afisare( FILE *fin, FILE *fout ) {&lt;br /&gt;
  char ch;&lt;br /&gt;
&lt;br /&gt;
  ch = fgetc( fin );&lt;br /&gt;
  if ( ch != &#039;\n&#039; ) {&lt;br /&gt;
    afisare( fin, fout );&lt;br /&gt;
    fputc( ch, fout );&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Descompunere în baza 2 ===&lt;br /&gt;
Să se scrie o funcție recursivă care, dat &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;, îl afișează în baza 2. &lt;br /&gt;
&lt;br /&gt;
Similar cu exercițiul precedent, vom calcula ultima cifră a descompunerii (restul împărțirii la doi), apoi vom chema funcția cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n / 2&amp;lt;/source&amp;gt;, iar la revenire vom afișa cifra.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void baza2( int n, FILE *fout ) {&lt;br /&gt;
  int cf2;&lt;br /&gt;
&lt;br /&gt;
  if ( n &amp;gt; 0 ) {&lt;br /&gt;
    cf2 = n % 2;&lt;br /&gt;
    baza2( n / 2, fout );&lt;br /&gt;
    fprintf( fout, &amp;quot;%d&amp;quot;, cf2 );&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Interclasare vectori ordonați ===&lt;br /&gt;
Dîndu-se doi vectori ordonați să se calculeze un al treilea vector ordonat ce conține elementele lor.&lt;br /&gt;
&lt;br /&gt;
Funcția va primi ca parametri cei trei vectori și pozițiile curente în ei (inițial pozițiile de start, 0). La fiecare apel va alege un element pe care îl va copia. Apoi se va reapela cu indici actualizați (i1 și i3 sau i2 și i3).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void interclaseaza( int n1, int v1[], int n2, int v2[], int v3[],&lt;br /&gt;
                   int i1, int i2, int i3 ) {&lt;br /&gt;
  if ( i1 &amp;lt; n1 ) { // mai avem elemente in primul vector?&lt;br /&gt;
    if ( i2 &amp;lt; n2 &amp;amp;&amp;amp; v2[i2] &amp;lt; v1[i1] ) { // v2[i2] exista si e mai mic?&lt;br /&gt;
      v3[i3] = v2[i2];                  // copiem v2[i1]&lt;br /&gt;
      interclaseaza( n1, v1, n2, v2, v3, i1, i2 + 1, i3 + 1 );&lt;br /&gt;
    } else {&lt;br /&gt;
      v3[i3] = v1[i1];                  // copiem v1[i1]&lt;br /&gt;
      interclaseaza( n1, v1, n2, v2, v3, i1 + 1, i2, i3 + 1 );&lt;br /&gt;
    }&lt;br /&gt;
  } else if ( i2 &amp;lt; n2 ) { // mai avem elemente in v2? (v1 s-a terminat)&lt;br /&gt;
    v3[i3] = v2[i2];&lt;br /&gt;
    interclaseaza( n1, v1, n2, v2, v3, i1, i2 + 1, i3 + 1 );&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 6 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumacfnr Sumacfnr]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumadiv Sumadiv]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/nset Nset]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/invector Invector]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/hanoi Turnurile din Hanoi]: [http://www.puzzle.ro/ro/play_toh.htm exemplu de animație joc aici] să se scrie un program care să rezolve jocul turnurile din Hanoi (să efectueze mutările care duc la soluție).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Indicații pentru tema obligatorie ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2. Sumadiv - suma divizorilor unui număr&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Rezolvați problema [https://www.nerdarena.ro/problema/sumadiv Sumadiv]: să se scrie o funcție recursivă care să calculeze suma divizorilor unui număr. &lt;br /&gt;
&lt;br /&gt;
Ea va arăta astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
int sumd( int n, int d ) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
și va fi apelată inițial astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
sum = sumd( n, 1 );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Semnificaţia este suma divizorilor lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; care sunt mai mari sau egali cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;d&amp;lt;/source&amp;gt; şi mai mici sau egali cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n / d&amp;lt;/source&amp;gt;. Mai exact trebuie să completați funcția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;sumd&amp;lt;/source&amp;gt; din programul următor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int sumd( int n, int d ) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;sumadiv.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;sumadiv.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;%d\n&amp;quot;, sumd( n, 1 ) );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Atenţie mare la complexitatea algoritmului obţinut!&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4. invector - răsturnare vector&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Rezolvați problema [https://www.nerdarena.ro/problema/invector Invector] să se răstoarne un vector folosind o funcție recursivă. Vectorul trebuie modificat, nu doar afișat invers. Funcția va arăta astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
void inv( int primul, int ultimul, int v[] ) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
unde primul și ultimul sunt indicii de început, respectiv sfârșit care definesc subvectorul de răsturnat. Funcția va fi apelată inițial astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
inv( 0, n-1, v );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Mai exact trebuie să completați funcția inv din programul următor:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int v[100000];&lt;br /&gt;
&lt;br /&gt;
void inv( int primul, int ultimul, int v[] ) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, i;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;invector.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;n );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;v[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  inv( 0, n-1, v );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;invector.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d &amp;quot;, v[i] );&lt;br /&gt;
  fprintf( fout, &amp;quot;\n&amp;quot; );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tema opțională  ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/combinari combinări] &amp;lt;math&amp;gt;{C}_{n}^{k}&amp;lt;/math&amp;gt;&amp;lt;nowiki&amp;gt;- combinări de n luate câte k. Date numerele naturale n și k, k &amp;amp;le; n, să se afișeze toate submulțimile mulțimii {1, 2, 3, ..., n} de k elemente.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
* [https://www.nerdarena.ro/problema/v v] (ONI 2004 clasa a 7-a) Problema este modificată. Puteți lua doar 50% din punctaj. Pentru 100% &#039;&#039;&#039;nu este nevoie de parsing&#039;&#039;&#039;. Dacă introduceam cerință de parsing aș fi scăzut timpul permis.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php/Clasa_a_7-a_Lec%C8%9Bia_7:_Recursivitate_(1) Accesează rezolvarea temei 7]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_gimnaziu&amp;diff=18557</id>
		<title>Cercul de informatică gimnaziu</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_gimnaziu&amp;diff=18557"/>
		<updated>2026-02-03T15:41:46Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Aceste lecții sînt create în fața unei audiențe &amp;quot;live&amp;quot; :-). Aceasta înseamnă că au fost efectiv folosite în anul școlar 2014-2015. De aceea veți remarca uneori comentarii la teme, laude, mustrări. Nu le-am eliminat deoarece pot fi și ele  folositoare pentru instructor în desfășurarea lecției.&lt;br /&gt;
&lt;br /&gt;
== Spiritul cercului de informatică ==&lt;br /&gt;
* [[Reguli ale cercului de informatică]]: spiritul acestor lecții&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lecții gimnaziu ==&lt;br /&gt;
* [[Lecții de informatică, clasa a V-a]]&lt;br /&gt;
* [[Lecții de informatică, clasa a VI-a]]&lt;br /&gt;
* [[Clasa a 7-a|Lecții de informatică, clasa VII/VIII]]&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Link-uri utile ==&lt;br /&gt;
* [[Setări necesare în Code::Blocks]]&lt;br /&gt;
* [[Instrucțiuni de intrare/ieșire în limbajul C]]&lt;br /&gt;
* [[Testarea timpului de execuție al unui program]]&lt;br /&gt;
* [[Unelte software necesare|Download: unelte software necesare]]&lt;br /&gt;
* [[Cercul de informatică - sfaturi pentru olimpiadă | Sfaturi pentru olimpiadă]]&lt;br /&gt;
* [[Cercul de informatică: pregătirea în vacanța de vară pentru elevii de gimnaziu | Pregătirea în vacanța de vară pentru elevii de gimnaziu]]&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18556</id>
		<title>Cercul de informatică IQ Academy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18556"/>
		<updated>2026-02-03T15:40:51Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p align=right&amp;gt;&amp;lt;sup&amp;gt;&#039;&#039;No &#039;&#039;&amp;lt;code&amp;gt;break&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;continue&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;int v[1005]&amp;lt;/code&amp;gt;&#039;&#039;, no nonsense&#039;&#039;&amp;lt;/sup&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aceste lecții sînt create în fața unei audiențe &amp;quot;live&amp;quot; :-). Aceasta înseamnă că au fost efectiv folosite în diverși ani școlari. De aceea veți remarca uneori comentarii la teme, laude, mustrări. Nu le-am eliminat deoarece pot fi și ele  folositoare pentru instructor în desfășurarea lecției.&lt;br /&gt;
&lt;br /&gt;
== Cursuri IQ Academy ==&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VII-a, anul 2019-2020]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2019-2020]] - Mihai Tuțu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VI-a, anul 2018-2019]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2017-2018]] - Cristian Frâncu&lt;br /&gt;
* [[Lecții de informatică, clasa VII/VIII|Cercul de informatică, CNITV, clasa VII/VIII, anul 2014-2015]] - Cristian Frâncu&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[http://iqacademy.ro/ IQ Academy] este o inițiativă a [http://francu.org/ Fundației Frâncu pentru progres și educație] ce își propune formarea unor minți algoritmice, cu putere de abstractizare, cu “gândire computațională”, termen apărut relativ recent și susținut de oameni precum Mark Zuckerberg, creatorul Facebook. IQ Academy oferă momentan cursuri de informatică.&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18555</id>
		<title>Cercul de informatică IQ Academy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18555"/>
		<updated>2026-02-03T15:40:23Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p align=right&amp;gt;&amp;lt;sup&amp;gt;&#039;&#039;No &#039;&#039;&amp;lt;code&amp;gt;break&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;continue&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;int v[1005]&amp;lt;/code&amp;gt;&#039;&#039;, no nonsense&#039;&#039;&amp;lt;/sup&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aceste lecții sînt create în fața unei audiențe &amp;quot;live&amp;quot; :-). Aceasta înseamnă că au fost efectiv folosite în diverși ani școlari. De aceea veți remarca uneori comentarii la teme, laude, mustrări. Nu le-am eliminat deoarece pot fi și ele  folositoare pentru instructor în desfășurarea lecției.&lt;br /&gt;
&lt;br /&gt;
== Cursuri IQ Academy ==&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VII-a, anul 2019-2020]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2019-2020]] - Mihai Tuțu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VI-a, anul 2018-2019]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2017-2018]] - Cristian Frâncu&lt;br /&gt;
* [[Lecții de informatică, clasa VII/VIII|Cercul de informatică, CNITV, anul 2014-2015]] - Cristian Frâncu&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[http://iqacademy.ro/ IQ Academy] este o inițiativă a [http://francu.org/ Fundației Frâncu pentru progres și educație] ce își propune formarea unor minți algoritmice, cu putere de abstractizare, cu “gândire computațională”, termen apărut relativ recent și susținut de oameni precum Mark Zuckerberg, creatorul Facebook. IQ Academy oferă momentan cursuri de informatică.&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18554</id>
		<title>Cercul de informatică IQ Academy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18554"/>
		<updated>2026-02-03T15:38:58Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p align=right&amp;gt;&amp;lt;sup&amp;gt;&#039;&#039;No &#039;&#039;&amp;lt;code&amp;gt;break&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;continue&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;int v[1005]&amp;lt;/code&amp;gt;&#039;&#039;, no nonsense&#039;&#039;&amp;lt;/sup&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aceste lecții sînt create în fața unei audiențe &amp;quot;live&amp;quot; :-). Aceasta înseamnă că au fost efectiv folosite în diverși ani școlari. De aceea veți remarca uneori comentarii la teme, laude, mustrări. Nu le-am eliminat deoarece pot fi și ele  folositoare pentru instructor în desfășurarea lecției.&lt;br /&gt;
&lt;br /&gt;
== Cursuri IQ Academy ==&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VII-a, anul 2019-2020]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2019-2020]] - Mihai Tuțu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VI-a, anul 2018-2019]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2017-2018]] - Cristian Frâncu&lt;br /&gt;
* [[Lecții de informatică, clasa VII/VIII]], anul 2014-2015 - Cristian Frâncu&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[http://iqacademy.ro/ IQ Academy] este o inițiativă a [http://francu.org/ Fundației Frâncu pentru progres și educație] ce își propune formarea unor minți algoritmice, cu putere de abstractizare, cu “gândire computațională”, termen apărut relativ recent și susținut de oameni precum Mark Zuckerberg, creatorul Facebook. IQ Academy oferă momentan cursuri de informatică.&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18553</id>
		<title>Cercul de informatică IQ Academy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18553"/>
		<updated>2026-02-03T15:38:00Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p align=right&amp;gt;&amp;lt;sup&amp;gt;&#039;&#039;No &#039;&#039;&amp;lt;code&amp;gt;break&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;continue&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;int v[1005]&amp;lt;/code&amp;gt;&#039;&#039;, no nonsense&#039;&#039;&amp;lt;/sup&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aceste lecții sînt create în fața unei audiențe &amp;quot;live&amp;quot; :-). Aceasta înseamnă că au fost efectiv folosite în diverși ani școlari. De aceea veți remarca uneori comentarii la teme, laude, mustrări. Nu le-am eliminat deoarece pot fi și ele  folositoare pentru instructor în desfășurarea lecției.&lt;br /&gt;
&lt;br /&gt;
== Cursuri IQ Academy ==&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VII-a, anul 2019-2020]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2019-2020]] - Mihai Tuțu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VI-a, anul 2018-2019]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2017-2018]] - Cristian Frâncu&lt;br /&gt;
* [[Lecții de informatică, clasa VII/VIII]] - Cristian Frâncu&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[http://iqacademy.ro/ IQ Academy] este o inițiativă a [http://francu.org/ Fundației Frâncu pentru progres și educație] ce își propune formarea unor minți algoritmice, cu putere de abstractizare, cu “gândire computațională”, termen apărut relativ recent și susținut de oameni precum Mark Zuckerberg, creatorul Facebook. IQ Academy oferă momentan cursuri de informatică.&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18552</id>
		<title>Cercul de informatică IQ Academy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18552"/>
		<updated>2026-02-03T15:37:45Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p align=right&amp;gt;&amp;lt;sup&amp;gt;&#039;&#039;No &#039;&#039;&amp;lt;code&amp;gt;break&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;continue&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;int v[1005]&amp;lt;/code&amp;gt;&#039;&#039;, no nonsense&#039;&#039;&amp;lt;/sup&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aceste lecții sînt create în fața unei audiențe &amp;quot;live&amp;quot; :-). Aceasta înseamnă că au fost efectiv folosite în diverși ani școlari. De aceea veți remarca uneori comentarii la teme, laude, mustrări. Nu le-am eliminat deoarece pot fi și ele  folositoare pentru instructor în desfășurarea lecției.&lt;br /&gt;
&lt;br /&gt;
== Cursuri IQ Academy ==&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VII-a, anul 2019-2020]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2019-2020]] - Mihai Tuțu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VI-a, anul 2018-2019]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2017-2018]] - Cristian Frâncu&lt;br /&gt;
* [[Lecții de informatică, clasa VII/VIII]]&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[http://iqacademy.ro/ IQ Academy] este o inițiativă a [http://francu.org/ Fundației Frâncu pentru progres și educație] ce își propune formarea unor minți algoritmice, cu putere de abstractizare, cu “gândire computațională”, termen apărut relativ recent și susținut de oameni precum Mark Zuckerberg, creatorul Facebook. IQ Academy oferă momentan cursuri de informatică.&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Main_Page&amp;diff=18551</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Main_Page&amp;diff=18551"/>
		<updated>2026-02-03T15:34:20Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;__NOTOC__&lt;br /&gt;
= Algopedia =&lt;br /&gt;
&lt;br /&gt;
== [[Cercul de informatică gimnaziu]] ==&lt;br /&gt;
[[Cercul de informatică gimnaziu]] este o colecție de lecții de algoritmi și programare pentru clasele V-VIII. Lecțiile acoperă programa de olimpiadă, dar nu se opresc aici, scopul final fiind de a forma minți algoritmice și a crea informaticieni cu cunoștințe de bază complete. În acest sens evităm &amp;quot;dopajul&amp;quot; (predarea în avans a unor cunoștințe, fără a preda prerechizitele necesare, în scopul obținerii unor rezultate mai bune la olimpiadă). Lecțiile sînt împărțite astfel încît să acopere anul școlar în ritmul de o lecție pe săptămînă, circa 40-44 de lecții la fiecare clasă. Fiecare lecție durează două ore. Unele lecții sînt concursuri cu participare de acasă, ele dublînd lecția din acea săptămînă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== [[Cercul de informatică IQ Academy]] ==&lt;br /&gt;
[[Cercul de informatică IQ Academy]] este un curs oferit de [http://iqacademy.ro/ IQ Academy], o inițiativă a [http://francu.org/ Fundației Frâncu pentru progres și educație] ce își propune formarea unor minți algoritmice, cu putere de abstractizare, cu “gândire computațională”, termen apărut relativ recent și susținut de oameni precum Mark Zuckerberg, creatorul Facebook.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Cercul de informatică, colegiul Tudor Vianu, 2012-2020 ==&lt;br /&gt;
Găsiți aici detalii despre [[Cercul de informatică - Colegiul Național de Informatică Tudor Vianu]] desfășurat în perioada 2012-2020.&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Data Structures and  Algorithms ==&lt;br /&gt;
A future translation to English. Work in progress.&lt;br /&gt;
* [[Introduction to Algorithms]]&lt;br /&gt;
* [[Introductory Exercises]]&lt;br /&gt;
* [[Problems Involving Sequences]]&lt;br /&gt;
* [[Problems Involving Arrays]]&lt;br /&gt;
&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Contact ==&lt;br /&gt;
Puteți contacta creatorul acestui site, Cristian Frâncu, la [mailto:cristian@francu.com cristian@francu.com]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18550</id>
		<title>Cercul de informatică IQ Academy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Cercul_de_informatic%C4%83_IQ_Academy&amp;diff=18550"/>
		<updated>2026-02-03T15:32:30Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p align=right&amp;gt;&amp;lt;sup&amp;gt;&#039;&#039;No &#039;&#039;&amp;lt;code&amp;gt;break&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;continue&amp;lt;/code&amp;gt;&#039;&#039;, no &#039;&#039;&amp;lt;code&amp;gt;int v[1005]&amp;lt;/code&amp;gt;&#039;&#039;, no nonsense&#039;&#039;&amp;lt;/sup&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aceste lecții sînt create în fața unei audiențe &amp;quot;live&amp;quot; :-). Aceasta înseamnă că au fost efectiv folosite în diverși ani școlari. De aceea veți remarca uneori comentarii la teme, laude, mustrări. Nu le-am eliminat deoarece pot fi și ele  folositoare pentru instructor în desfășurarea lecției.&lt;br /&gt;
&lt;br /&gt;
== Cursuri IQ Academy ==&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VII-a, anul 2019-2020]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2019-2020]] - Mihai Tuțu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a VI-a, anul 2018-2019]] - Cristian Frâncu&lt;br /&gt;
* [[Cercul de informatică, IQ Academy, clasa a V-a, anul 2017-2018]] - Cristian Frâncu&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[http://iqacademy.ro/ IQ Academy] este o inițiativă a [http://francu.org/ Fundației Frâncu pentru progres și educație] ce își propune formarea unor minți algoritmice, cu putere de abstractizare, cu “gândire computațională”, termen apărut relativ recent și susținut de oameni precum Mark Zuckerberg, creatorul Facebook. IQ Academy oferă momentan cursuri de informatică.&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_V-a_lec%C8%9Bia_30_-_8_mar_2018&amp;diff=18549</id>
		<title>Clasa a V-a lecția 30 - 8 mar 2018</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_V-a_lec%C8%9Bia_30_-_8_mar_2018&amp;diff=18549"/>
		<updated>2026-02-03T15:18:57Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Comentarii rezultate faza pe sector =&lt;br /&gt;
# Felicitări tuturor! Ați ajuns pînă aici cu foarte multă muncă. &lt;br /&gt;
# Felicitări celor ce au luat 100p la prima problemă, șir, o problemă ce necesita atenție și concentrare: &#039;&#039;&#039;Armin Asgari&#039;&#039;&#039; și &#039;&#039;&#039;Alex Nicu&#039;&#039;&#039;!&lt;br /&gt;
# Felicitări negative celor ce nu au luat 100p la a doua problemă, cifre. Era o problemă ușoară, de lecția 10. Poate nu v-ați dat suficiente teste?&lt;br /&gt;
# Cei ce nu ați dat surse la prima problemă, șir: ar fi fost bine să dați o sursă care afișa un număr, la inspirație. De exemplu 2 (primul număr prim), sau 1 (primul număr prim minu unu), sau poate suma primelor două numere prime minus 1 (adică 4). 10p puncte cîștigate la concurs vă puteau avansa 10-15 locuri în clasament!&lt;br /&gt;
# Pentru cei ce ați luat 100p la a doua problemă, cifre, și care ați lucrat la prima problemă, șir, dar ați luat 0p sau 10p: nu vă demoralizați! Este o problemă grea, la care este mai greu să iei punctaje parțiale. Uitați-vă la rezultate și veți vedea că avem doar patru scoruri nenule: 10p, 80p și 90p. O mică corectură în programul vostru putea să vă transforme sursa în una de 90p. La municipiu puteți foarte bine să luați 190p.&lt;br /&gt;
# Nu uitați, important: la IQ Academy &#039;&#039;&#039;nu facem pregătire pentru olimpiadă&#039;&#039;&#039;, ci &#039;&#039;&#039;ne formăm ca informaticieni&#039;&#039;&#039;. Aceasta înseamnă că nu facem dopaj și învățăm să codăm corect. Obiceiurile proaste cu care veniți de la pregătitorii de olimpici nu se acceptă. Printre acestea se numără folosirea lui break, a stegulețelor inutile, a freopen, vectori declarați cu cîteva elemente în plus în caz că vine drăcușorul indicilor și ne încurcă, for pe post de while și invers, if-uri exclusive între ele care nu se înlănțuie unul pe else-ul celuilalt.&lt;br /&gt;
&lt;br /&gt;
= Comentarii tema 29 =&lt;br /&gt;
# Felicitări pentru comentarii (cei care mă ajută comentînd programele): &#039;&#039;&#039;Nicola&#039;&#039;&#039;, &#039;&#039;&#039;Petcu&#039;&#039;&#039;&lt;br /&gt;
# Topul pescarilor la speciale: &#039;&#039;&#039;Aizic&#039;&#039;&#039; 14 surse, &#039;&#039;&#039;Togan&#039;&#039;&#039; 8 surse, &#039;&#039;&#039;Dumitrescu&#039;&#039;&#039; 7 surse, &#039;&#039;&#039;Ipate&#039;&#039;&#039; 7 surse, &#039;&#039;&#039;Iordache&#039;&#039;&#039; 6 surse, &#039;&#039;&#039;Tatomir&#039;&#039;&#039; 6 surse, &#039;&#039;&#039;Badea&#039;&#039;&#039; 5 surse, &#039;&#039;&#039;Nicu&#039;&#039;&#039; 5 surse&lt;br /&gt;
# Speciale: v-a cam dat gata! mulți nu ați luat 100p.&lt;br /&gt;
# Speciale: mulți dintre voi nu au folosit vectori preinițializați, sau i-au folosit parțial, acolo unde era cel mai puțin nevoie de ei. Nu e de mirare că v-a dat gata problema. Acesta era scopul problemei, să exersăm vectori preinițializați, nu doar să luăm 100p.&lt;br /&gt;
&lt;br /&gt;
= Tema - rezolvări =&lt;br /&gt;
&lt;br /&gt;
Rezolvări aici [http://solpedia.francu.com/wiki/index.php/Clasa_a_V-a_lec%C8%9Bia_29_-_1_mar_2018]&lt;br /&gt;
&lt;br /&gt;
= Lecţie =&lt;br /&gt;
&amp;lt;html5media height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.algopedia.ro/video/2017-2018/2018-03-08-lectie-info-30-720p.mp4&amp;lt;/html5media&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Problemele de la olimpiada pe sector ==&lt;br /&gt;
Dragilor, cred că sînteți de acord cu mine că problemele de anul acesta au fost drăguțe și abordabile, nici prea grele, nici prea ușoare. Nu cred că puteați primi probleme mai bune de olimpiada pe sector.&lt;br /&gt;
&lt;br /&gt;
Să le discutăm.&lt;br /&gt;
&lt;br /&gt;
=== Problema șir6 ===&lt;br /&gt;
Problema [http://varena.ro/problema/sir6 șir6] a fost dată la faza pe sector 2018, clasa a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;. O voi introduce pe varena.ro imediat ce obțin testele. Ea este o problemă standard ce se poate rezolva conform manualului de informatică (unul care predă informatică, nu Scratch :-). Ea conține doar elemente învățate, pe care voi trebuie să le combinați pentru a calcula rezultatul. Algoritmul este clar, fiind aproape explicat de enunț.&lt;br /&gt;
&lt;br /&gt;
Pentru rezolvare vom citi cifrele de la intrare, păstrînd mereu penultima cifră, pentru testul de paritate. La fiecare citire de cifră va trebui să calculăm și următorul număr prim. Pentru aceasta vom porni de la numărul prim anterior, să-i spunem &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt;, și vom parcurge toate numerele &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt;+1, &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt;+2, &amp;lt;code&amp;gt;prim+3&amp;lt;/code&amp;gt;, ..., testînd primalitatea lor. Ne vom opri atunci cînd găsim primul număr prim, pe care îl vom aduna la suma cerută. Dacă ultimele două cifre din secvență au parități diferite va trebui să scădem unu din sumă.&lt;br /&gt;
&lt;br /&gt;
Unde trebuie să avem grijă?&lt;br /&gt;
&lt;br /&gt;
==== Testul de găsire număr prim ====&lt;br /&gt;
Algoritmul va avea două bucle. Cea interioară testează dacă un număr este prim. Cea exterioară va executa bucla interioară în mod repetat, pînă ce va găsi un număr prim. Pentru testul buclei exterioare veți fi tentați să folosiți stegulețe și veți scrie cod încîlcit. Pentru a nu ajunge acolo, gîndim astfel:&lt;br /&gt;
&lt;br /&gt;
* Scriem mai întîi bucla interioară, care testează dacă un număr este prim. Ea este, sper banală și bine cunoscută:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;      d = 2;&lt;br /&gt;
      while ( d * d &amp;lt;= prim &amp;amp;&amp;amp; prim % d &amp;gt; 0 )&lt;br /&gt;
        d++;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* Apoi scriem bucla interioară, fără să ne preocupăm de condiția ei de oprire:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;    while ( &amp;lt;condiție necunoscută de oprire&amp;gt; ) {&lt;br /&gt;
      prim++; // numarul anterior nu este prim, deci avansam                    &lt;br /&gt;
      d = 2;&lt;br /&gt;
      while ( d * d &amp;lt;= prim &amp;amp;&amp;amp; prim % d &amp;gt; 0 )&lt;br /&gt;
        d++;&lt;br /&gt;
    }&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* Acum:&lt;br /&gt;
** Care este condiția să ieșim din &amp;lt;code&amp;gt;while&amp;lt;/code&amp;gt;? &#039;&#039;&#039;Răspuns&#039;&#039;&#039;: să găsim un număr prim.&lt;br /&gt;
** Care este condiția ce ne spune că am găsit un număr prim? &#039;&#039;&#039;Răspuns&#039;&#039;&#039;: clasică, &amp;lt;code&amp;gt;if ( d * d &amp;gt; prim )&amp;lt;/code&amp;gt;&lt;br /&gt;
** În concluzie, deoarece condiția din while este cea de a nu fi găsit un număr prim, va trebui să o punem pe dos: &amp;lt;code&amp;gt;while ( d * d &amp;lt;= prim )&amp;lt;/code&amp;gt;&lt;br /&gt;
Rezultă codul:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;    while ( d * d &amp;lt;= prim ) {&lt;br /&gt;
      prim++; // numarul anterior nu este prim, deci avansam                    &lt;br /&gt;
      d = 2;&lt;br /&gt;
      while ( d * d &amp;lt;= prim &amp;amp;&amp;amp; prim % d &amp;gt; 0 )&lt;br /&gt;
        d++;&lt;br /&gt;
    }&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
* Ce mai rămîne de rezolvat? Pornirea: cu ce trebuie să inițializăm &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt; și &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt; pentru a ne asigura că intrăm cu bine în buclă. Să vedem:&lt;br /&gt;
** Ce semnificație exactă are &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt;? El este ultimul număr prim &amp;quot;folosit&amp;quot;. La intrarea în buclă vom avea grijă să îl incrementăm. Deoarece primul număr prim este 2, el fiind și primul ce se va aduna la sumă, trebuie să pornim cu &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt; cu unu mai puțin, adică 1. Acest lucru se va întîmpla chiar la începutul algoritmului.&lt;br /&gt;
** Dar &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt;? Unde și cu cît îl inițializăm? Observăm că imediat ce intrăm în prima buclă &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt; va fi setat la 2. Deci nu contează ce valoare va avea înainte de intrarea în buclă, cîtă vreme condiția din &amp;lt;code&amp;gt;while&amp;lt;/code&amp;gt; este adevărată. Deoarece cea mai mică valoare a lui &amp;lt;code&amp;gt;prim&amp;lt;/code&amp;gt; este 1, hai să-i dăm lui &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt; valoarea 1.&lt;br /&gt;
Rezultă codul:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;    // cautam urmatorul numar prim, pentru a il insuma                          &lt;br /&gt;
    d = 1;    // valore introdusa astfel incit sa intram in bucla               &lt;br /&gt;
    while ( d * d &amp;lt;= prim ) {&lt;br /&gt;
      prim++; // numarul anterior nu este prim, deci avansam                    &lt;br /&gt;
      d = 2;&lt;br /&gt;
      while ( d * d &amp;lt;= prim &amp;amp;&amp;amp; prim % d &amp;gt; 0 )&lt;br /&gt;
        d++;&lt;br /&gt;
    }&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Citirea cifrelor ====&lt;br /&gt;
Enunțul problemei nu vă spune cîte cifre aveți la intrare, doar că avem minim două cifre, maxim 3000. Deci, pentru a citi aceste cifre, va trebui să le citim caracter cu caracter, pînă la &#039;\n&#039;. Unii din voi încă au probleme cu astfel de citiri. Să lămurim puțin lucrurile:&lt;br /&gt;
* Șirul de la intrare e de forma: cf&amp;lt;sub&amp;gt;1&amp;lt;/sub&amp;gt; cf&amp;lt;sub&amp;gt;2&amp;lt;/sub&amp;gt; cf&amp;lt;sub&amp;gt;3&amp;lt;/sub&amp;gt; ...cf&amp;lt;sub&amp;gt;n&amp;lt;/sub&amp;gt;\n&lt;br /&gt;
* Remarcați că putem descompune elegant șirul în perechi de forma (cf, ch), unde cf este o cifră, iar ch este fie caracterul spațiu, fie caracterul \n. El va fi \n o singură dată, la final.&lt;br /&gt;
* De aceea vom avea grijă ca la fiecare iterație a buclei să citim perechi de caractere, cifră și imediat după aceea caracterul următor. Ne oprim cînd acel caracter următor este &#039;\n&#039;.&lt;br /&gt;
* O ultimă precauție: trebuie să memorăm valoarea penultimei cifre, pentru calculul parității perechilor de cifre.&lt;br /&gt;
&lt;br /&gt;
==== Testul de paritate al perechilor de cifre ====&lt;br /&gt;
Sînt convins că vă veți descurca să scădeți unu din sumă în cazul cînd paritățile cifrelor sînt diferite, folosind trei instrucțiuni &amp;lt;code&amp;gt;if&amp;lt;/code&amp;gt;. Hai să vedem cum facem fără nici un &amp;lt;code&amp;gt;if&amp;lt;/code&amp;gt;. Pentru aceasta să facem tabelul celor patru cazuri posibile, pentru a vedea cînd scădem unu. În acest tabel vom calcula și paritatea sumei cifrelor.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Penultima cifră&lt;br /&gt;
!Ultima cifră&lt;br /&gt;
!Suma&lt;br /&gt;
!Scădem unu?&lt;br /&gt;
|-&lt;br /&gt;
| pară&lt;br /&gt;
| pară&lt;br /&gt;
| pară&lt;br /&gt;
| NU&lt;br /&gt;
|-&lt;br /&gt;
| pară&lt;br /&gt;
| impară&lt;br /&gt;
| impară&lt;br /&gt;
| DA&lt;br /&gt;
|-&lt;br /&gt;
| impară&lt;br /&gt;
| pară&lt;br /&gt;
| impară&lt;br /&gt;
| DA&lt;br /&gt;
|-&lt;br /&gt;
| impară&lt;br /&gt;
| impară&lt;br /&gt;
| pară&lt;br /&gt;
| NU&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Ce observăm, imediat? Că vom scădea unu numai atunci cînd suma ultimelor două cifre este impară. Drept care putem rezolva adunarea numărului prim la sumă elegant, într-o singură linie, astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;suma = suma + prim - (ucf + cf) % 2;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Iată un program foarte scurt care ține cont de cele discutate mai sus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int suma, prim, ucf, cf, d;&lt;br /&gt;
  char ch;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;sir6.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  suma = 0; // suma de afisat la final, initial zero                            &lt;br /&gt;
  prim = 1; // pornim cu numarul din-naintea primului numar prim                &lt;br /&gt;
  ucf = fgetc( fin ) - &#039;0&#039;;  // citim prima cifra la intrare                    &lt;br /&gt;
  ch = fgetc( fin );         // citim spatiul de dupa                           &lt;br /&gt;
  while ( ch != &#039;\n&#039; ) {     // mai avem cifre?                                 &lt;br /&gt;
    cf = fgetc( fin ) - &#039;0&#039;; // citim inca o cifra                              &lt;br /&gt;
    ch = fgetc( fin );       // si caracterul de dupa ea                        &lt;br /&gt;
&lt;br /&gt;
    // cautam urmatorul numar prim, pentru a il insuma                          &lt;br /&gt;
    d = 1;    // valore introdusa astfel incit sa intram in bucla               &lt;br /&gt;
    while ( d * d &amp;lt;= prim ) {&lt;br /&gt;
      prim++; // numarul anterior nu este prim, deci avansam                    &lt;br /&gt;
      d = 2;&lt;br /&gt;
      while ( d * d &amp;lt;= prim &amp;amp;&amp;amp; prim % d &amp;gt; 0 )&lt;br /&gt;
        d++;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // la iesirea din while stim ca prim este urmatorul numar prim              &lt;br /&gt;
    suma = suma + prim - (ucf + cf) % 2; // il adunam la suma conform paritatii&lt;br /&gt;
    ucf = cf; // pastram ultima cifra                                           &lt;br /&gt;
  }&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;sir6.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;%d\n&amp;quot;, suma );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Problema cifre9 ===&lt;br /&gt;
Problema [http://varena.ro/problema/cifre9 cifre9] a fost dată la faza pe sector 2018, clasa a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;. O voi introduce pe varena.ro imediat ce obțin testele. Ca și problema șir6, ea este o problemă standard, de manual, ea conținînd doar elemente învățate, simple, de lucrul cu cifre.&lt;br /&gt;
&lt;br /&gt;
Există mai multe moduri de a aborda această problemă. Un mod este să extragem pe rînd cifrele lui &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt;, de la coadă, numărînd aparițiile lui &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; și formînd în același timp numărul de afișat. Dacă cifra extrasă este diferită de &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; o vom adăuga la numărul nou format, dacă este &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt; o vom număra și o vom adăuga la un număr ce va conține toate cifrele &amp;lt;code&amp;gt;k&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată o variantă de program:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, k, nrk, kkk, nmod, p10, cf;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;cifre9.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;k );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  nrk = 0;  // de cite ori apare cifra k in n                                   &lt;br /&gt;
  kkk = 0;  // numarul format din nrk cifre k                                   &lt;br /&gt;
  nmod = 0; // n modificat astfel incit toate cifrele k sa fie la inceput       &lt;br /&gt;
  p10 = 1;  // puterea lui 10 cu care vom adauga cifrele in nmod                &lt;br /&gt;
  while ( n &amp;gt; 0 ) {&lt;br /&gt;
    cf = n % 10; // ultima cifra a lui n                                        &lt;br /&gt;
    n /= 10;     // eliminam ultima cifra din n                                 &lt;br /&gt;
    if ( cf == k ) {      // n avea pe ultima pozitie cifra k?                  &lt;br /&gt;
      nrk++;              // adunam unu la numarul de cifre k din n             &lt;br /&gt;
      kkk = kkk * 10 + k; // adaugam cifra la kkk                               &lt;br /&gt;
    } else { // daca cifra este diferita de k                                   &lt;br /&gt;
      nmod = nmod + p10 * cf; // o adaugam la n modificat                       &lt;br /&gt;
      p10 *= 10;              // pregatim urmatoarea putere a lui 10            &lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  nmod += p10 * kkk; // adaugam kkk in fata numarului modificat                 &lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;cifre9.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;%d\n%d\n&amp;quot;, nrk, nmod ); // afisam numerele cerute             &lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Manipularea orelor și minutelor pe ceas ==&lt;br /&gt;
Deși orele și minutele sînt doar numere, anumite exerciții pot pune probleme.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Problemă&#039;&#039;&#039;: se dau doi timpi, &amp;lt;tt&amp;gt;t1&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;t2&amp;lt;/tt&amp;gt;, specificați ca ore și minute. Astfel, vom avea la intrare &amp;lt;tt&amp;gt;o1&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;m1&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;o2&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;m2&amp;lt;/tt&amp;gt;. Cîte ore și minute se află între acești doi timpi? Atenție! Dacă &amp;lt;tt&amp;gt;t2&amp;lt;/tt&amp;gt; este înaintea lui &amp;lt;tt&amp;gt;t1&amp;lt;/tt&amp;gt; se consideră că am trecut într-o nouă zi. Exemplu: dacă &amp;lt;tt&amp;gt;t1&amp;lt;/tt&amp;gt; este &amp;lt;tt&amp;gt;10:35&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;t2&amp;lt;/tt&amp;gt; este &amp;lt;tt&amp;gt;18:20&amp;lt;/tt&amp;gt; atunci vom afișa &amp;lt;tt&amp;gt;07:45&amp;lt;/tt&amp;gt;. Dacă &amp;lt;tt&amp;gt;t1&amp;lt;/tt&amp;gt; este &amp;lt;tt&amp;gt;22:25&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;t2&amp;lt;/tt&amp;gt; este &amp;lt;tt&amp;gt;20:05&amp;lt;/tt&amp;gt; vom afișa &amp;lt;tt&amp;gt;21:40&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
La prima vedere acest exercițiu pare încîlcit. Va trebui să comparăm cei doi timpi. Dacă &amp;lt;tt&amp;gt;t1&amp;lt;/tt&amp;gt; este înaintea lui &amp;lt;tt&amp;gt;t2&amp;lt;/tt&amp;gt; atunci va trebui să comparăm cîte ore avem între &amp;lt;tt&amp;gt;o1&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;o2&amp;lt;/tt&amp;gt;. Dar și aceasta va depinde de relația dintre &amp;lt;tt&amp;gt;m1&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;m2&amp;lt;/tt&amp;gt;. Dacă &amp;lt;tt&amp;gt;t1&amp;lt;/tt&amp;gt; este după &amp;lt;tt&amp;gt;t2&amp;lt;/tt&amp;gt; lucrurile se complică și mai mult: trebuie să aflăm cîte ore și minute avem pîna la finalul zilei, apoi să aflăm cîte ore și minute avem de la începutul zilei pînă la &amp;lt;tt&amp;gt;t2&amp;lt;/tt&amp;gt;, iar în final trebuie să le adunăm. Multe cazuri particulare. Cum facem să simplificăm lucrurile?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Regulă&#039;&#039;&#039;&#039;&#039;: pentru a nu ne încurca, întotdeauna vom face conversia orelor la cea mai mică unitate de măsură, fie ea minut sau secundă. În final vom face conversia inversă, de la minute, la ore și minute.&lt;br /&gt;
&lt;br /&gt;
În cazul problemei noastre, vom converti t1 și t2 la minute. Algoritmul este următorul:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;1. calculează t1 = o1 * 60 + m1&lt;br /&gt;
2. calculează t2 = o2 * 60 + m2&lt;br /&gt;
3. calculează d = t2 - t1&lt;br /&gt;
4. dacă d &amp;lt; 0 atunci&lt;br /&gt;
     4.1 calculează d = 24 * 60 - d&lt;br /&gt;
5. calculează o3 și t3 pe baza lui d, astfel:&lt;br /&gt;
     5.1 o3 = d / 60&lt;br /&gt;
     5.2 m3 = d % 60&lt;br /&gt;
6. afișează o3 și m3&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Concluzie: în problemele în care avem nevoie să lucrăm cu ore, minute și secunde, este foarte important să transformăm timpul într-un singur număr exprimat în cea mai mică unitate de timp implicată. În exemplul de mai sus această unitate este minutul. Uneori s-ar putea să fie secunda. După transformare vom face calculele cerute de problemă. În final vom transforma înapoi în ore, minute, posibil secunde, dacă problema necesită acest lucru.&lt;br /&gt;
&lt;br /&gt;
= Temă =&lt;br /&gt;
[http://varena.ro/runda/2018-03-08-clasa-5-tema-30 Tema 30]: să se rezolve următoarele probleme (program C  trimis la vianuarena):&lt;br /&gt;
* [http://varena.ro/problema/chibrituri chibrituri] dată la OJI 2013 clasa a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;&lt;br /&gt;
* [http://varena.ro/problema/ore ore] dată la concursul .campion 2011&lt;br /&gt;
* [http://varena.ro/problema/consiliu consiliu] dată la OMI Iași 2012&lt;br /&gt;
&lt;br /&gt;
Rezolvări aici [http://solpedia.francu.com/wiki/index.php/Clasa_a_V-a_lec%C8%9Bia_30_-_8_mar_2018]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18548</id>
		<title>Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18548"/>
		<updated>2026-02-03T15:11:29Z</updated>

		<summary type="html">&lt;p&gt;Cristian: /* Studiu de caz: problema bile1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/W59ZLyfMZlc&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica Two Pointers (doi pointeri) ==&lt;br /&gt;
&lt;br /&gt;
În informatica de concurs avem tehnici semi-banale cărora le dăm o denumire pentru a putea să le referim ușor într-o discuție. Este și cazul acestei metode. Deși relativ evidentă, ea are un nume 🙂 Pe scurt, &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; folosește doi indici într-un vector ce avansează pe rând, similar cu interclasarea, în căutarea rezultatului. Este o tehnică ce produce în general algoritmi liniari. Ea decurge în mod natural din căutarea binară, atunci când avem un algoritm relativ simplu ce folosește multiple căutări binare, ale căror rezultate sunt fie crescătoare fie descrescătoare.&lt;br /&gt;
&lt;br /&gt;
Să luăm câteva exemple.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Pereche de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de elemente sortate crescător, să se spună câte o perechi există de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[j] == X&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Cum am putea rezolva problema? Pentru fiecare număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; să căutăm un număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j]&amp;lt;/source&amp;gt; cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j &amp;gt; i&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[j] == X&amp;lt;/source&amp;gt;. Dacă fixăm &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; atunci &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j]&amp;lt;/source&amp;gt; poate fi determinat prin căutare binară, deoarece vectorul este ordonat crescător. Vom obține, deci, un algoritm &#039;&#039;O(N log N)&#039;&#039;, unde &#039;&#039;&#039;N&#039;&#039;&#039; este numărul de elemente din vector.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie vectorul: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
v[] = { 20, 25, 30, 35, 40, 50, 55, 65, 75 }&lt;br /&gt;
indici:  0   1   2   3   4   5   6   7   8&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X = 75&amp;lt;/source&amp;gt;. Pentru fiecare &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; vom căuta binar ultimul număr mai mic sau egal cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X - v[i]&amp;lt;/source&amp;gt;. Astfel:&lt;br /&gt;
&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 0&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 20&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X - v[i] = 55&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 6&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 55&amp;lt;/source&amp;gt; soluție. &lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 1&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 25&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;50&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 5&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 50&amp;lt;/source&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 2&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 30&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;45&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 40&amp;lt;/source&amp;gt; (ultima mai mică sau egală cu 45) nu este soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 3&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 35&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;40&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 40&amp;lt;/source&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 40&amp;lt;/source&amp;gt; ar trebui să căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;35&amp;lt;/source&amp;gt; care este mai mică decât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; așa încât ne oprim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce observăm? Că valorile lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; sunt descrescătoare. Este destul de clar că așa și trebuie să fie, nu?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație importantă&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tehnica Two Pointers&#039;&#039;&#039;: atunci când o succesiune de &#039;&#039;&#039;căutări binare&#039;&#039;&#039; într-un vector au ca rezultat o &#039;&#039;&#039;secvență monotonă&#039;&#039;&#039; de numere (crescătoare sau descrescătoare), căutările binare pot fi înlocuite cu &#039;&#039;&#039;căutări liniare&#039;&#039;&#039;. Această înlocuire și noua metodă rezultată poartă denumirea de &#039;&#039;&#039;Two Pointers&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are noul algoritm?&lt;br /&gt;
&lt;br /&gt;
La prima vedere , pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; vom căuta liniar perechea lui, ceea ce ar rezulta într-un algoritm &#039;&#039;O(N)&#039;&#039;. În realitate, indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; se va muta mereu către stânga, drept care el nu va putea parcurge mai mult de &#039;&#039;O(N)&#039;&#039; elemente în toate căutările liniare. Deci, folosind analiza amortizată, deși oricare din căutările liniare poate fi &#039;&#039;O(N)&#039;&#039;, suma tuturor acestor căutări nu poate depăși &#039;&#039;O(N)&#039;&#039;. Drept care metoda Two Pointers duce la un algoritm &#039;&#039;O(N)&#039;&#039;, superior celui cu căutare binară, care era &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Subsecvență de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de elemente nenule, să se spună câte o perechi există de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[i+1] + v[i+2] + ... + v[j] == X&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Și aici avem un algoritm relativ simplu: calculăm vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; de sume parțiale și pentru fiecare element pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; în vector vom căuta binar în vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[i-1] + X&amp;lt;/source&amp;gt;. Dacă găsim această valoare la poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; avem că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[j] - s[i-1] == X&amp;lt;/source&amp;gt; și deci suma elementelor de la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; în vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X&amp;lt;/source&amp;gt;, deci avem o soluție.&lt;br /&gt;
&lt;br /&gt;
Complexitatea acestui algoritm va fi, din nou, &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce observăm, însă? Că rezultatele căutărilor binare (indicii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt;) vor fi în ordine crescătoare, deoarece sumele parțiale sunt strict crescătoare. Putem, deci, folosi &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; pentru a căuta indicii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; prin căutare liniară: pentru fiecare avans al lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; vom avansa indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; până ce suma devine mai mare sau egală cu cea dorită, moment la care testăm egalitatea.&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(N)&#039;&#039;. Mai mult, deoarece ne interesează doar suma subsecvenței de la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; putem renunța la vectorul de sume parțiale și lucra direct pe vectorul original &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;, menținând incremental suma subsecvenței studiate.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Problema NrTri ===&lt;br /&gt;
&lt;br /&gt;
Am discutat într-o lecție trecută problema [https://www.nerdarena.ro/problema/nrtri NrTri]. Ea cere să găsiți într-un vector numărul de tripleți ce pot forma laturile unui triunghi. Aici avem o soluție cu căutare binară: după ce sortăm elementele, pentru fiecare pereche de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; vom căuta binar cel mai mare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k &amp;gt; j&amp;lt;/source&amp;gt; cu proprietatea că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[k]&amp;lt;/source&amp;gt;. Toate elementele între &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (inclusiv &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;) formează un triplet valabil cu perechea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Această soluție are complexitatea &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; log N)&#039;&#039;. Ca și în exemplele anterioare, observăm că atunci când &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; avansează căutarea binară va returna valori &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; crescătoare. Și aici putem aplica tehnica &#039;&#039;Two Pointers&#039;&#039;, avansând liniar indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Rezultă un algoritm O(N2). Observați că tehnica aceasta reduce complexitatea cu acel factor log N. În practică nu este clar că vom putea întotdeauna diferenția între căutarea binară și Two Pointers. Cu toate acestea metoda merită, deoarece codul rezultat este mai simplu și constanta de implementare scade considerabil.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tipul de date struct ==&lt;br /&gt;
&lt;br /&gt;
Până acum am învățat tipuri de date simple (&amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;char&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;long long&amp;lt;/source&amp;gt;, etc) și tipuri de date compuse (ce grupează mai multe tipuri de date simple) anume tablouri, fie ele unidimensionale (vectori), bidimensionale (matrice), sau chiar de dimensiuni mai mari. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; };&lt;br /&gt;
...&lt;br /&gt;
struct nrmare n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
sau, mai scurt (putem declara variabile la definirea tipului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Elementele declarate în interiorul unui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; se numesc membri. Putem accesa membrii unei variabile tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; cu operatorul punct. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
t += n1.cf[i] + n2.cf[i];&lt;br /&gt;
n3.cf[i] = t % 10;&lt;br /&gt;
t /= 10;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; reprezintă un alt mod de a grupa date simple într-o &#039;&#039;structură&#039;&#039;. Aceste date vor fi reprezentate în memorie la rând, una după alta.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Diferențe&#039;&#039;&#039; între tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; și tipul tablou:&lt;br /&gt;
&lt;br /&gt;
* Tipul struct poate grupa date de tipuri diferite, tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;tablou&amp;lt;/source&amp;gt; nu.&lt;br /&gt;
* Tipul struct accesează elementele sale pe bază de denumire, tipul tablou accesează elementele numeric, pe bază de indice (poziție).&lt;br /&gt;
* În consecință tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; grupează un număr relativ mic de date.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Avantaje&#039;&#039;&#039; ale tipului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Gruparea datelor duce la o ordine a codului.&lt;br /&gt;
* Codul devine mai clar și mai ușor de citit, deci o depanare mai ușoară.&lt;br /&gt;
* Folosit corect tipul struct poate duce la un acces mai localizat al memorie (&#039;&#039;cache friendly&#039;&#039;) și, deci, la o scăderea a timpului de execuție.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; probleme cu tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Orice variabilă de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; va fi completată până la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt;. Actual, acest tip este pe patru octeți, ceea ce înseamnă că un struct va ocupa un număr de octeți divizibil cu patru. Dacă vom declara un struct cu un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt; și un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;char&amp;lt;/source&amp;gt; el va ocupa 8 octeți și nu 5!&lt;br /&gt;
&lt;br /&gt;
* Un vector de structuri poate fi înlocuit întotdeauna cu mai mulți vectori, câte unul pentru fiecare membru. Decizia de a folosi struct trebuie luată în funcție de modul de accesare a membrilor în algoritmul nostru. O decizie proastă poate duce la deteriorarea timpului de execuție din motive de cache.&lt;br /&gt;
&lt;br /&gt;
* Combinația între cele două de mai sus poate fi destul de rea: un struct care ocupă mai mult, chiar dacă nu duce la o depășire de memorie, va crește necesarul de memorie ceea ce duce la creșterea timpului de execuție din motive de &#039;&#039;cache&#039;&#039;. Aceasta, combinat cu un acces prost la memorie, poate duce la o creștere semnificativă a timpului de execuție - poate chiar dublarea lui!&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie&#039;&#039;&#039;: folosiți tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; numai dacă știți exact ce faceți. Recomandarea mea este ca deocamdată să nu îl folosiți.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: problema bile1 ===&lt;br /&gt;
&lt;br /&gt;
Implementarea listelor se face în general bine folosind tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;. În mod natural o celulă a listei va conține o informație și o legătură la următoarea celulă (next). În particular am învățat acea structură de date, vectorul de liste, pe care îl foloseam pentru a evita sortarea, memorând pentru fiecare linie a unei matrice acele coloane care ne interesează, unde apar elemente interesante pe acea linie. Să luăm un caz concret.&lt;br /&gt;
&lt;br /&gt;
În problema [https://www.nerdarena.ro/problema/bile1 Bile1] se cere să simulăm căderea unor bile printr-o matrice cu obstacole. Am rezolvat-o memorând obstacolele ca liste de coloane &#039;&#039;agățate&#039;&#039; pe liniile unde apar obstacole. Pentru aceasta am folosit doi vectori. Iată soluția de atunci:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;CPP&amp;quot; line&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                   // capetele de lista&lt;br /&gt;
short col[NOBST + 1], next[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                        // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    col[i] = c;         // asezam coloana c in lista liniei l&lt;br /&gt;
    next[i] = lin[l];   // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = col[pc] - 1; // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = next[pc];   // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Este clar că atunci când accesăm o celulă a listei vom avea nevoie atât de numărul de coloană cât și de next, pentru a avansa la elementul următor. Deci poate ar fi o idee bună să le grupăm, pentru &#039;&#039;cache&#039;&#039;. Partea bună este că atât coloana cât și următorul element sunt mici, deci putem folosi tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, ceea ce înseamnă că o celulă va ocupa două elemente &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, adică patru octeți, fiind aliniată la întreg.&lt;br /&gt;
&lt;br /&gt;
Iată programul modificat să folosească tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                                    // capetele de lista&lt;br /&gt;
struct cell { short col; short next; } obst[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                            // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    // asezam coloana c in lista liniei l&lt;br /&gt;
    // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    obst[i] = (struct cell){ .col = c, .next = lin[l] }; // cream noua celula&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = obst[pc].col - 1;                   // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = obst[pc].next; // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați cum putem atribui o variabilă de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;, folosind denumirile membrilor săi: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
obst[i] = (struct cell){ .col = c, .next = lin[l] };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: numere mari ===&lt;br /&gt;
&lt;br /&gt;
Numerele mari se pot reprezenta în mod natural ca un struct ce conține numărul de cifre și vectorul de cifre, precum am văzut mai sus. Avantajul constă în gruparea datelor și în claritatea codului ce menține numarul de cifre corect - nu mai avem nevoie să returnăm în funcția de înmulțire numărul de cifre al rezultatului, ci îl setăm chiar în numărul mare trimis ca parametru.&lt;br /&gt;
&lt;br /&gt;
Atunci când trimitem un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; mare ca parametru (care ocupă mai mult de un întreg) este bine să-l trimitem ca pointer, deoarece evităm copierea. În acest caz accesul la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; se face un pic anevoios. &lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de funcție de adunare număr mic la număr mare:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += (*n).cf[i];&lt;br /&gt;
    (*n).cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; (*n).ncf )&lt;br /&gt;
    (*n).ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați că avem nevoie de două paranteze, un asterisc și un punct pentru a apela un membru al structurii trimise ca pointer. De aceea limbajul C ne oferă o alternativă mai simplă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
    // forme echivalente:&lt;br /&gt;
    (*n).cf[i];&lt;br /&gt;
&lt;br /&gt;
    // este totuna cu&lt;br /&gt;
    n-&amp;gt;cf[i];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Precum vedeți săgețica este echivalentul punctului atunci când variabila este de tip pointer la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de implementare de numere mari:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXNCF 1000&lt;br /&gt;
&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mic a din numarul mare n&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (a &amp;lt;= n)&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void scadeNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  a = -a;&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;lt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    if ( n-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n-&amp;gt;cf[i] += 10;&lt;br /&gt;
      a--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n-&amp;gt;cf[n-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mare n2 la numarul mare n1&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void adunaNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, max, t;&lt;br /&gt;
&lt;br /&gt;
  max = n1-&amp;gt;ncf &amp;lt; n2-&amp;gt;ncf ? n2-&amp;gt;ncf : n1-&amp;gt;ncf;&lt;br /&gt;
  t = 0;&lt;br /&gt;
  for ( i = 0; i &amp;lt; max; i++ ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] + n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
  }&lt;br /&gt;
  if ( t &amp;gt; 0 ) {&lt;br /&gt;
    n1-&amp;gt;cf[i] = t;&lt;br /&gt;
    max++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  n1-&amp;gt;ncf = max;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mare n2 din numarul mare n1&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (n2 &amp;lt;= n1)&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void scadeNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n2-&amp;gt;ncf || t &amp;lt; 0 ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] - n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    if ( n1-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n1-&amp;gt;cf[i] += 10;&lt;br /&gt;
      t--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n1-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n1-&amp;gt;cf[n1-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n1-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// inmulteste numarul mic a cu numarul mare n cu rezultatul in n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void inmultesteNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
  long long t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n-&amp;gt;ncf || t &amp;gt; 0 ) {&lt;br /&gt;
    t = t + a * (long long)n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// tipareste un numar mare n&lt;br /&gt;
void printN( struct nrmare *n ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( n-&amp;gt;ncf == 0 )&lt;br /&gt;
    fputc( &#039;0&#039;, stdout );&lt;br /&gt;
  else&lt;br /&gt;
    for ( i = n-&amp;gt;ncf - 1; i &amp;gt;= 0; i-- )&lt;br /&gt;
      fputc( &#039;0&#039; + n-&amp;gt;cf[i], stdout );&lt;br /&gt;
  fputc( &#039;\n&#039;, stdout );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  // n1, n2, n3 declarate global, initial zero&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 75464: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;75464 - 75464: &amp;quot; );&lt;br /&gt;
  scadeNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 834205: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 834205 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  adunaNn( &amp;amp;n2, 1024586 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586: &amp;quot; );&lt;br /&gt;
  adunaNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586 - 1024586: &amp;quot; );&lt;br /&gt;
  scadeNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 * 2043984094: &amp;quot; );&lt;br /&gt;
  inmultesteNn( &amp;amp;n1, 2043984094 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Metoda Greedy ==&lt;br /&gt;
&lt;br /&gt;
Uneori trebuie să rezolvăm probleme ce necesită să găsim o soluție minimă sau maximă. Una din cele mai simple și evidente încercări de rezolvare este ca, la fiecare pas, să luăm decizia optimă pe moment - decizie &#039;&#039;lacomă&#039;&#039; - cel mai bun lucru la momentul actual.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dat un șir de numere pozitive și negative să se calculeze suma maximă ce se poate forma cu exact &#039;&#039;K&#039;&#039; numere.&lt;br /&gt;
&lt;br /&gt;
Soluție greedy: la fiecare pas vom selecta maximul dintre numerele rămase. Vom porni cu maximul. Apoi cu al doilea maxim și așa mai departe. La fiecare pas &#039;&#039;ne lăcomim&#039;&#039; la cel mai mare număr posibil.&lt;br /&gt;
&lt;br /&gt;
Este, sper, destul de clar că în acest caz obținem soluția optimă. Însă nu întotdeauna este așa, precum vom vedea.&lt;br /&gt;
&lt;br /&gt;
Să vedem câteva exemple de algoritmi greedy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Plată cu număr minim de bancnote puteri ale lui 2 ===&lt;br /&gt;
&lt;br /&gt;
Dispunem de bancnote puteri ale lui 2. Pentru fiecare putere a lui 2 vom avea un număr de bancnote disponibile, posibil niciuna. Dat &#039;&#039;&#039;X&#039;&#039;&#039;, dorim să achităm suma &#039;&#039;&#039;X&#039;&#039;&#039; cu un număr minim de bancnote posibile. Cum procedăm?&lt;br /&gt;
&lt;br /&gt;
Vom proceda greedy: selectăm bancnotele de valoare maximă ce încap în &#039;&#039;&#039;X&#039;&#039;&#039;. Fie epuizăm numărul de bancnote de acea putere și trecem mai departe, fie ne rămân bancnote dar suma de plată devine mai mică decât 2 la acea putere. Procedăm în continuare la fel.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dispunem de următoarele bancnote: 3 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;, 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;, 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, 5 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;, 6 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; și ne dorim să achităm suma 53. Procedăm astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  Pas 1: selectăm 1 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;. Suma rămasă: 21 = 53 - 32&lt;br /&gt;
  Pas 2: selectăm 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;. Suma rămasă: 13 = 21 - 8&lt;br /&gt;
  Pas 3: selectăm 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;. Suma rămasă: 5 = 13 - 8&lt;br /&gt;
  Pas 4: selectăm 2 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;. Suma rămasă: 1 = 5 - 4&lt;br /&gt;
  Pas 5: selectăm 1 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt;. Suma rămasă: 0 = 1 - 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Astfel numărul de bancnote este 7, iar numărul este plătit astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
53 = 32 + 8 + 4 + 4 + 2 + 2 + 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sortarea prin selecție ===&lt;br /&gt;
&lt;br /&gt;
Ne putem gândi la sortarea prin selecție ca având un pas de bază greedy: la fiecare trecere prin vector selectăm în mod greedy maximul și apoi îl trecem la coadă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul minim de intervale care acoperă un interval ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o mulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; ce acoperă un alt interval mai mare să se găsească o submulţime minimă a acestei mulţimi care conţine intervale ce acoperă același interval original.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Intervalul acoperit este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;[0, 18]&amp;lt;/source&amp;gt;. Ne dorim să păstrăm cât mai puține intervale care să acopere același interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idee de soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
O idee naturală este să sortăm intervalele după punctul de început, iar apoi să le luăm în ordine, unul câte unul. Având un interval curent selectat, am putea să selectăm intervalul care începe cât mai târziu dar al cărui interval este în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
Pe exemplul de mai sus observăm că nu funcționează, deoarece am selecta intervalele 4, 1 și apoi nu am mai avea ce să selectăm în continuare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom parcurge intervalele în ordinea capătului de început, ca mai devreme. Dar vom selecta următorul interval pe acela care se &#039;&#039;&#039;închide ultimul&#039;&#039;&#039; dintre cele care au capătul stânga în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
În cazul nostru vom selecta intervalele 4, 5 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul maxim de intervale disjuncte ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o submulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; să se găsească o submulţime maximală a acestei mulţimi care conţine doar intervale disjuncte (neintersectante).&lt;br /&gt;
&lt;br /&gt;
Această problemă este totuna cu numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele (de ce?).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idei de soluţie &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Această problemă apare destul de des la olimpiade, deghizată într-o formă sau alta. Deşi pare o problemă grea, în realitate ea se rezolvă uşor. O primă idee ar fi să încercăm să ordonăm intervalele după punctul de început, apoi să le luăm în ordine, unul câte unul. Pentru fiecare interval ne întrebăm dacă îl intersectează pe cel anterior. Dacă da, îl aruncăm şi mergem mai departe. Dacă nu, îl adăugăm la submulţime şi îl reţinem ca interval ultim. Această soluţie nu funcţionează şi e destul de evident de ce: am putea avea un prim interval foarte lung, care acoperă toate celelalte intervale.&lt;br /&gt;
&lt;br /&gt;
Cu toate acestea rezolvarea nu e total greşită. În fapt, ea aproape funcţionează! Are nevoie doar de o modificare ce se impune, analizînd contraexemplul anterior: de ce nu este bun acel prim interval foarte lung? Deoarece el se termină după celelalte. Şi atunci ne putem întreba, în mod natural, ce s-ar întâmpla dacă am alege intervalul care se închide primul, nu care începe primul? Desigur că acest algoritm funcţionează. Vă las vouă să vă luptaţi cu demonstraţia, care decurge cam aşa: să presupunem că soluţia generată de acest algoritm nu e optimă. Înseamnă că există o altă soluţie cu mai multe intervale. Acea soluţie trebuie să difere undeva faţă de cea găsită de noi. Mergeţi pe această cale şi trebuie să ajungeţi la o contradicţie cum că soluţia optimă nu poate avea mai multe segmente.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Atunci vom selecta, în ordine, intervalele 3 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Soluţie ====&lt;br /&gt;
&lt;br /&gt;
Care este algoritmul, în mare?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
# Iniţial mulţimea M este vidă.&lt;br /&gt;
# Ordonează crescător intervalele după punctul de închidere.&lt;br /&gt;
# Consideră ultima închidere (care nu există încă) cu valoarea minus infinit.&lt;br /&gt;
# Pentru fiecare interval, în ordine:&lt;br /&gt;
## Dacă el conţine ultima închidere&lt;br /&gt;
### Ignoră-l.&lt;br /&gt;
## Altfel&lt;br /&gt;
### Selectează intervalul curent şi adaugă-l la mulţimea M.&lt;br /&gt;
### Setează ultima închidere pe închiderea intervalului selectat&lt;br /&gt;
# Afişează mulţimea M.&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ce se întâmplă dacă avem mai multe intervale care se termină în acelaşi punct? Atunci putem reţine intervalul cel mai mic. De ce? Deoarece este cel mai convenabil. Să presupunem că soluţia maximală conţinea un alt interval care se termina în acel punct. Atunci putem înlocui acel interval cu intervalul cel mai mic, obţinînd o altă soluţie maximală.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Considerente de implementare ====&lt;br /&gt;
&lt;br /&gt;
Implementarea directă: memorăm intervalele ca perechi, prin capetele lor. Sortăm perechile după capătul din dreapta. Apoi facem o parcurgere a perechilor. Această implementare necesită &#039;&#039;O(N log N)&#039;&#039; timp și &#039;&#039;O(N)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
Implementare relaxată: atunci când coordonatele sunt suficient de mici astfel încât să putem folosi un vector care să memoreze un întreg pentru fiecare coordonată, putem încerca o abordare mai simplă: nu memorăm intervalele. Ci, pentru fiecare interval &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;[ai, bi]&amp;lt;/source&amp;gt;, vom seta &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;început[bi] = ai&amp;lt;/source&amp;gt;. Cu alte cuvinte vom memora capetele stânga la poziția capetelor dreapta. Apoi parcurgem vectorul în ordine, acoperind segmentul de coordonate care acoperă toate intervalele, testând dacă elementul de la poziția curentă în vector este mai mic decât ultima închidere memorată. Dacă nu, atunci selectăm intervalul și mutăm ultima închidere la poziția curentă în vector. Trebuie să avem grijă la următoarele:&lt;br /&gt;
&lt;br /&gt;
* Să iniţializăm vectorul de coordonate cu o valoare distinctă de capete de interval. Matematic am putea să le iniţializăm cu minus infinit, astfel încât să nu fie selectate niciodată.&lt;br /&gt;
* Atunci când &#039;&#039;aşezăm&#039;&#039; intervalele pe vector să avem grijă ca dacă găsim deja o valoare în vector să o înlocuim numai dacă cea curentă este mai mare (intervalul este mai mic).&lt;br /&gt;
&lt;br /&gt;
Această soluţie necesită &#039;&#039;O(N + CMAX)&#039;&#039; timp şi memorie, &#039;&#039;&#039;CMAX&#039;&#039;&#039; fiind coordonata maximă. De aici rezultă când o putem folosi: atunci când &#039;&#039;&#039;CMAX&#039;&#039;&#039; este suficient de mic.&lt;br /&gt;
&lt;br /&gt;
==== Generalizare ====&lt;br /&gt;
&lt;br /&gt;
Putem generaliza această problemă: să se calculeze mulţimea maximală de dreptunghiuri neintersectante. Sau de paralelipipede. Sau de paralelipipede n-dimensionale. Cum se rezolvă aceste probleme? Din nefericire aceste probleme fac parte dintr-o clasă de probleme numite &#039;&#039;NP-hard&#039;&#039;, pentru care nu se cunoaşte o soluţie bună.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu când greedy nu funcționează: prolema rucsacului ===&lt;br /&gt;
&lt;br /&gt;
Problema rucsacului cere să &#039;&#039;umplem&#039;&#039; cât mai bine un rucsac care poate duce o greutate maximă, dispunând de obiecte de diverse greutăți.&lt;br /&gt;
&lt;br /&gt;
Exemplu: Vreau să umplu cât mai bine un rucsac de 100 și am greutăți 50, 30, 25 și 25.&lt;br /&gt;
&lt;br /&gt;
Prin metoda greedy vom alege greutățile cele mai mari, în ordine: 50 și 30, după care nu mai putem adăuga nimic. Astfel vom obține greutatea totală 80. Dacă am alege însă greutățile 50, 25 și 25 am putea umple complet rucsacul.&lt;br /&gt;
&lt;br /&gt;
De remarcat totuși că chiar și atunci când metoda greedy nu calculează optimul, un algoritm greedy bine gândit va da o soluție bună, aproape de optim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 16 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/politic Politic] dată la ONI 2007 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumax SumaX] dată la Shumen Juniori 2015&lt;br /&gt;
* [https://www.nerdarena.ro/problema/acoperire Acoperire]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/char Char] dată la ONI 2010 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Pentru doritorii să încerce și soluția problemei numărul minim de numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele puteți rezolva problema [https://www.nerdarena.ro/problema/baloane Baloane].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/startrek Star Trek] dată la OJI 2016 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy Accesează rezolvarea temei 16]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_VII-a_lec%C8%9Bia_19_-_30_ian_2020&amp;diff=18547</id>
		<title>Clasa a VII-a lecția 19 - 30 ian 2020</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_VII-a_lec%C8%9Bia_19_-_30_ian_2020&amp;diff=18547"/>
		<updated>2026-02-03T15:05:44Z</updated>

		<summary type="html">&lt;p&gt;Cristian: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Fulgerică - rezolvare =&lt;br /&gt;
Problema cere să se spună dacă dintr-un milion de numere avem maxim 1000 de valori distincte.&lt;br /&gt;
&lt;br /&gt;
== Comentarii ==&lt;br /&gt;
* Mulți ați dat soluția forță brută.&lt;br /&gt;
* Majoritatea care ați dat o soluție, forță brută sau nu, nu ați analizat, sau ați analizat parțial complexitatea în timp. Nu ați făcut înlocuiri, sau le-ați făcut dar nu ați făcut calculele. Nu ați tras o concluzie, intră în timp sau nu?&lt;br /&gt;
* După aproape doi ani de analiză la fiecare algoritm și problemă prezentată, este inacceptabil să nu fiți în stare să analizați un algoritm simplu.&lt;br /&gt;
* Mulți dintre voi nu ați făcut analiza memoriei algoritmului vostru. Intră în memorie sau nu?&lt;br /&gt;
* Unii dintre voi nu știu că un long long are 8 bytes, nu 4 bytes. Și vreți să fiți la IQ Academy? Vă admir voința, în acest caz :-)&lt;br /&gt;
* Mulți dintre voi mi-ați scris că 8&#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039;1000=8KB. Ar trebui să știți că nu este așa. 8KB=8192B. Dacă scriați &amp;quot;aproximativ&amp;quot; era OK.&lt;br /&gt;
* Concluzia dureroasă este că unii din voi nu sînt capabili să facă calcule de memorie.&lt;br /&gt;
* Din cei care ați găsit un algoritm bun, cu căutări binare și deplasări, doar &#039;&#039;&#039;Ghica&#039;&#039;&#039; s-a prins că deplasările nu sînt &#039;&#039;K&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;&#039;&#039; operații, ci &#039;&#039;K&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;&#039;&#039;/2 ceea ce înjumătățește numărul de operații.&lt;br /&gt;
&lt;br /&gt;
== Clasament ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Nr.&lt;br /&gt;
!Nume&lt;br /&gt;
!F1&lt;br /&gt;
!F2&lt;br /&gt;
!Total&lt;br /&gt;
|-&lt;br /&gt;
| 1 &lt;br /&gt;
| &#039;&#039;&#039;Ghica&#039;&#039;&#039;&lt;br /&gt;
|85&lt;br /&gt;
|95&lt;br /&gt;
|180&lt;br /&gt;
|-&lt;br /&gt;
| 1 &lt;br /&gt;
| &#039;&#039;&#039;Petrescu&#039;&#039;&#039;&lt;br /&gt;
|100&lt;br /&gt;
|80&lt;br /&gt;
|180&lt;br /&gt;
|-&lt;br /&gt;
| 3 &lt;br /&gt;
| &#039;&#039;&#039;Ipate&#039;&#039;&#039;&lt;br /&gt;
|100&lt;br /&gt;
|50&lt;br /&gt;
|150&lt;br /&gt;
|-&lt;br /&gt;
| 4 &lt;br /&gt;
| &#039;&#039;&#039;Tatomir&#039;&#039;&#039;&lt;br /&gt;
|90&lt;br /&gt;
|45&lt;br /&gt;
|135&lt;br /&gt;
|-&lt;br /&gt;
| 5 &lt;br /&gt;
| &#039;&#039;&#039;Voicu M&#039;&#039;&#039;&lt;br /&gt;
|100&lt;br /&gt;
|15&lt;br /&gt;
|115&lt;br /&gt;
|-&lt;br /&gt;
| 6 &lt;br /&gt;
| &#039;&#039;&#039;Dobre&#039;&#039;&#039;&lt;br /&gt;
|95&lt;br /&gt;
|15&lt;br /&gt;
|110&lt;br /&gt;
|-&lt;br /&gt;
| 6 &lt;br /&gt;
| &#039;&#039;&#039;Iordache&#039;&#039;&#039; &lt;br /&gt;
| 100&lt;br /&gt;
| 10&lt;br /&gt;
| 110&lt;br /&gt;
|-&lt;br /&gt;
| 6&lt;br /&gt;
| &#039;&#039;&#039;Rebengiuc&#039;&#039;&#039;&lt;br /&gt;
|10&lt;br /&gt;
|100&lt;br /&gt;
|110&lt;br /&gt;
|-&lt;br /&gt;
| 9&lt;br /&gt;
| &#039;&#039;&#039;Mușat&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|95&lt;br /&gt;
|95&lt;br /&gt;
|-&lt;br /&gt;
| 10&lt;br /&gt;
| &#039;&#039;&#039;Nicu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|90&lt;br /&gt;
|90&lt;br /&gt;
|-&lt;br /&gt;
| 11&lt;br /&gt;
| &#039;&#039;&#039;Voicu T&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|80&lt;br /&gt;
|80&lt;br /&gt;
|-&lt;br /&gt;
| 12&lt;br /&gt;
| &#039;&#039;&#039;Nicola&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|50&lt;br /&gt;
|50&lt;br /&gt;
|-&lt;br /&gt;
| 13&lt;br /&gt;
| &#039;&#039;&#039;Fares&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|45&lt;br /&gt;
|45&lt;br /&gt;
|-&lt;br /&gt;
| 14&lt;br /&gt;
| &#039;&#039;&#039;Ruginiș&#039;&#039;&#039;&lt;br /&gt;
|15&lt;br /&gt;
|20&lt;br /&gt;
|35&lt;br /&gt;
|-&lt;br /&gt;
| 15&lt;br /&gt;
| &#039;&#039;&#039;Badea&#039;&#039;&#039;&lt;br /&gt;
|10&lt;br /&gt;
|15&lt;br /&gt;
|25&lt;br /&gt;
|-&lt;br /&gt;
| 16&lt;br /&gt;
| &#039;&#039;&#039;Stancu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|20&lt;br /&gt;
|20&lt;br /&gt;
|-&lt;br /&gt;
| 17 &lt;br /&gt;
| &#039;&#039;&#039;Petcu&#039;&#039;&#039;&lt;br /&gt;
|5&lt;br /&gt;
|14&lt;br /&gt;
|19&lt;br /&gt;
|-&lt;br /&gt;
| 18&lt;br /&gt;
| &#039;&#039;&#039;Grecu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|18&lt;br /&gt;
|18&lt;br /&gt;
|-&lt;br /&gt;
| 19&lt;br /&gt;
| &#039;&#039;&#039;Ilie&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|15&lt;br /&gt;
|15&lt;br /&gt;
|-&lt;br /&gt;
| 19&lt;br /&gt;
| &#039;&#039;&#039;Marcu&#039;&#039;&#039;&lt;br /&gt;
|5&lt;br /&gt;
|10&lt;br /&gt;
|15&lt;br /&gt;
|-&lt;br /&gt;
| 19&lt;br /&gt;
| &#039;&#039;&#039;Mocanu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|15&lt;br /&gt;
|15&lt;br /&gt;
|-&lt;br /&gt;
| 19&lt;br /&gt;
| &#039;&#039;&#039;Ștefănescu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|15&lt;br /&gt;
|15&lt;br /&gt;
|-&lt;br /&gt;
| 23&lt;br /&gt;
| &#039;&#039;&#039;Aizic&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|10&lt;br /&gt;
|10&lt;br /&gt;
|-&lt;br /&gt;
| 23&lt;br /&gt;
| &#039;&#039;&#039;Asgari&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|10&lt;br /&gt;
|10&lt;br /&gt;
|-&lt;br /&gt;
| 23&lt;br /&gt;
| &#039;&#039;&#039;Cadîr&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|10&lt;br /&gt;
|10&lt;br /&gt;
|-&lt;br /&gt;
| 23&lt;br /&gt;
| &#039;&#039;&#039;Calotă&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|10&lt;br /&gt;
|10&lt;br /&gt;
|-&lt;br /&gt;
| 23&lt;br /&gt;
| &#039;&#039;&#039;Dimulescu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|10&lt;br /&gt;
|10&lt;br /&gt;
|-&lt;br /&gt;
| 23&lt;br /&gt;
| &#039;&#039;&#039;Teodorescu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|10&lt;br /&gt;
|10&lt;br /&gt;
|-&lt;br /&gt;
| 29&lt;br /&gt;
| &#039;&#039;&#039;Benescu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|5&lt;br /&gt;
|5&lt;br /&gt;
|-&lt;br /&gt;
| 29&lt;br /&gt;
| &#039;&#039;&#039;Chivu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|5&lt;br /&gt;
|5&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| &#039;&#039;&#039;Burac&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|0&lt;br /&gt;
|0&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| &#039;&#039;&#039;Cojocariu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|absent&lt;br /&gt;
|0&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| &#039;&#039;&#039;Hossu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|motivat&lt;br /&gt;
|0&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| &#039;&#039;&#039;Popescu&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|0&lt;br /&gt;
|0&lt;br /&gt;
|-&lt;br /&gt;
| 31&lt;br /&gt;
| &#039;&#039;&#039;Togan&#039;&#039;&#039;&lt;br /&gt;
|0&lt;br /&gt;
|0&lt;br /&gt;
|0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
= Tema - rezolvări =&lt;br /&gt;
== Disjoint ==&lt;br /&gt;
Problema [http://varena.ro/problema/disjoint disjoint] este educativă. Rostul ei este să vă puteţi testa implementarea algoritmului &#039;&#039;union-find&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Channels ==&lt;br /&gt;
Problema [http://varena.ro/problema/channels channels] a fost dată la concursul de la Shumen 2012, juniori.&lt;br /&gt;
&lt;br /&gt;
=== Comentarii generale ===&lt;br /&gt;
* V-ați descurcat bine, bravo! Pare că ați înțeles algoritmul union-find.&lt;br /&gt;
* Foarte mulți dintre voi ați numărat la final cîțe mulțimi disjuncte rămîn. Acest număr se putea calcula pe parcurs.&lt;br /&gt;
&lt;br /&gt;
=== Soluţia union-find ===&lt;br /&gt;
Vom parcurge muchiile în orice ordine, folosindu-le doar dacă au lăţimea cel puţin &#039;&#039;k&#039;&#039; şi dacă nu creează cicluri. De fiecare dată cînd folosim o muchie numărul de componente disjuncte scade cu unu. În final, numărul rămas minus unu este chiar răspunsul căutat. Folosind union-find complexitatea este aproximativ &#039;&#039;O(m)&#039;&#039;. Este cea mai simplă soluţie.&lt;br /&gt;
&lt;br /&gt;
Iată o soluţie bazată pe union-find (35 linii de cod efectiv):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define WSHIFT (1024*1024)&lt;br /&gt;
#define NSHIFT 1024&lt;br /&gt;
&lt;br /&gt;
int sef[1001];     // vectorul de sefi ai nodurilor&lt;br /&gt;
int canal[100000]; // vector de stocare a muchiilor: 8 biti latimea, iar&lt;br /&gt;
                   // nodurile cite 10 biti fiecare, 28 de biti total&lt;br /&gt;
&lt;br /&gt;
// algoritmul clasic de find (folosim zero ca marker pentru sef suprem)&lt;br /&gt;
int find( int nod ) {&lt;br /&gt;
  if ( sef[nod] == 0 )&lt;br /&gt;
    return nod;&lt;br /&gt;
  return sef[nod] = find( sef[nod] );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, m, i, j, w, k, c, conexe;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;channels.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;n, &amp;amp;m );&lt;br /&gt;
  for ( c = 0; c &amp;lt; m; c++ ) {&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;i, &amp;amp;j, &amp;amp;w );  // citim o muchie&lt;br /&gt;
    canal[c] = w * WSHIFT + i * NSHIFT + j; // o stocam in vector, codata&lt;br /&gt;
  }&lt;br /&gt;
  fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;k );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  conexe = n; // numarul curent de componente conexe (disjuncte)&lt;br /&gt;
  for ( c = 0; c &amp;lt; m; c++ ) // parcurgem muchiile&lt;br /&gt;
    if ( (canal[c] / WSHIFT) &amp;gt;= k ) { // daca latimea &amp;gt;= k, muchie folosibila&lt;br /&gt;
      i = find( (canal[c] / NSHIFT) % NSHIFT ); // reprezentantul primului nod&lt;br /&gt;
      j = find( canal[c] % NSHIFT );            // reprezentantul nodului 2&lt;br /&gt;
      if ( i != j ) { // daca nu fac parte din aceeasi componenta conexa&lt;br /&gt;
        sef[j] = i;   // le unim&lt;br /&gt;
        conexe--;     // numarul de componente conexe se micsoreaza&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;channels.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fprintf( fout, &amp;quot;%d\n&amp;quot;, conexe - 1 ); // atitea muchii mai avem de largit&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ce complexități avem?&lt;br /&gt;
&lt;br /&gt;
Citirea canalelor este &#039;&#039;O(m)&#039;&#039;. Apoi, pentru fiecare canal vom efectua două operații &#039;&#039;find()&#039;&#039;, care au complexitate practică &#039;&#039;O(1)&#039;&#039;. Rezultă că timpul de execuție este &#039;&#039;O(m)&#039;&#039;. În realitate inițializarea vectorului de șefi durează și ea, deci mai corect ar fi &#039;&#039;O(m + n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Dar memoria?&lt;br /&gt;
&lt;br /&gt;
În mod normal am avea nevoie doar de vectorul de șefi, adica circa 4KB. Din nefericire, deoarece &#039;&#039;k&#039;&#039; se dă la final, sîntem nevoiți să memorăm canalele. Stocarea din programul de mai jos încearcă să economisească spațiu, folosind un întreg per canal. Astfel, avem &#039;&#039;O(m + n)&#039;&#039; memorie, sau, mai precis, circa 400KB.&lt;br /&gt;
&lt;br /&gt;
Pentru cei avansați și curioși, voi discuta mai jos două soluții care ies din cadrul lecțiilor noastre.&lt;br /&gt;
&lt;br /&gt;
=== Soluţia forţă brută ===&lt;br /&gt;
Această problemă poate fi rezolvată cu forță brută! Deoarece avem suficientă memorie la dispoziție putem să memorăm graful, în ce formă dorim. Vom ignora muchiile care au lățime mai mică decît &#039;&#039;k&#039;&#039;. Pe acest graf trebuie să aflăm numărul de componente conexe. Este clar că numărul de muchii ce trebuie lărgite este numărul de componente conexe minus unu. Cum aflăm componentele conexe? Printr-o parcurgere DFS (parcurgere în adîncime, ca la flood-fill).&lt;br /&gt;
&lt;br /&gt;
Complexitatea acestei soluţii este &#039;&#039;O(m + n)&#039;&#039; dacă reprezentăm graful cu liste de adiacenţă, sau &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; cînd reprezentăm graful matricial. Ambele se vor încadra în timp.&lt;br /&gt;
&lt;br /&gt;
=== Soluţia comisiei ===&lt;br /&gt;
Se construieşte un arbore parţial de cost &#039;&#039;&#039;maxim&#039;&#039;&#039; (nu minim). Pentru aceasta folosim unul din algoritmii clasici, de exemplul algoritmul lui Kruskal. Vom sorta muchiile şi le vom parcurge după lăţime, în ordine descrescătoare. Vom alege o muchie doar atunci cînd nu creează un ciclu. Ne oprim atunci cînd arborele este complet (are &#039;&#039;n-1&#039;&#039; noduri). De asemenea contorizăm cîte muchii am ales care au lăţimea mai mică strict decît &#039;&#039;k&#039;&#039;. Acela este numărul muchiilor care trebuie lărgite.&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitatea &#039;&#039;O(m log m)&#039;&#039; pentru sortare şi apoi aproximativ &#039;&#039;O(m)&#039;&#039; pentru parcurgerea muchiilor, folosind algoritmul union-find. Deoarece lăţimile sînt mici (maxim 200) putem face o sortare mai eficientă, gen bucket sort, ceea ce reduce complexitatea sortării la &#039;&#039;O(m + k)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Soluția comisiei este cea mai complicată, ceea ce ne arată că și la case mari se mai întîmplă.&lt;br /&gt;
&lt;br /&gt;
== Bile3 ==&lt;br /&gt;
Problema [http://varena.ro/problema/bile3 bile3] a fost preluată de pe site-ul infoarena. Este o problemă de Union-Find în sens invers.&lt;br /&gt;
&lt;br /&gt;
=== Comentarii generale ===&lt;br /&gt;
* A fost o problemă ceva mai grea.&lt;br /&gt;
* Unii din voi nu au folosit vectori de direcție și au duplicat mult cod.&lt;br /&gt;
&lt;br /&gt;
= Tema opțională - rezolvări =&lt;br /&gt;
== Flota ==&lt;br /&gt;
Problema [http://varena.ro/problema/flota flota]  a fost dată la cercul de informatică, clasa a 10&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;. Ea este o aplicație poate nu chiar evidentă de union-find.&lt;br /&gt;
&lt;br /&gt;
== Ruine ==&lt;br /&gt;
Problema [http://varena.ro/problema/ruine ruine] a fost dată la cercul de informatică, clasa a 10&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;. Este o problemă clasică de Union-Find invers.&lt;br /&gt;
&lt;br /&gt;
Rezolvări aici [http://solpedia.francu.com/wiki/index.php/Clasa_a_VII-a_lec%C8%9Bia_18_-_23_ian_2020]&lt;br /&gt;
&lt;br /&gt;
= Lecţie - Tehnici de programare: divide et impera =&lt;br /&gt;
&amp;lt;html5media height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.algopedia.ro/video/2019-2020/2020-01-30-clasa-7-lectie-info-19-720p.mp4&amp;lt;/html5media&amp;gt;&lt;br /&gt;
Denumită și divide and conquer sau dezbină și stăpînește, este o tehnică cunoscută de mii de ani conducătorilor. Este o tehnică de cucerire sau menținere a puterii asupra unui grup care ar avea putere mai mare dacă s-ar uni. Ținînd acel grup dezbinat, fiecare facțiune în parte are putere mică și poate fi cucerită, sau condusă. Tehnica a fost aplicată de Cezar, Napoleon, iar, mai recent, de către Britanici în Africa și de către Stalin în Rusia. Dintre tehnicile folosite în lumea reală putem menționa:&lt;br /&gt;
&lt;br /&gt;
* Ajutorarea și promovarea celor care cooperează cu puterea, pedepsirea celor ce nu cooperează.&lt;br /&gt;
* Încurajarea cheltuielilor prostești, pentru a reduce capitalul disponibil organizării sau luptei.&lt;br /&gt;
* Încurajarea divizării oamenilor pentru a preveni formarea alianțelor.&lt;br /&gt;
* Încurajarea turnătorilor, cei care trîmbițează orice alianță posibil periculoasă.&lt;br /&gt;
* Sădirea de neîncredere între oameni.&lt;br /&gt;
&lt;br /&gt;
O parte din aceste tehnici se regăsesc și în țara noastră, precum și în majoritatea țărilor lumii, inclusiv cele dezvoltate. De exemplu, toate țările lumii folosesc un număr uriaș de taxe și impozite, fiecare din ele relativ mici. Cînd le însumăm constatăm că în jur de 90% din banii noștri se duc pe aceste taxe și impozite. Dacă ar exista o taxă unică de 90%, oamenii s-ar revolta. Astfel, tehnica de împărțire a unor taxe mari în mai multe taxe mici îi face pe oameni să nu le observe și să nu se revolte.&lt;br /&gt;
&lt;br /&gt;
Pentru exemple clasice de folosire a acestei tehnici în lumea reală vizitați pagina de [http://en.wikipedia.org/wiki/Divide_and_rule divide et impera] a wikipediei.&lt;br /&gt;
&lt;br /&gt;
== Tehnica divide et impera (divide and conquer) în informatică ==&lt;br /&gt;
În informatică divide et impera constă în împărțirea problemei în subprobleme mai mici, nesuprapuse (o tehnică în care subproblemele sînt suprapuse este programarea dinamică). Fiecare din aceste subprobleme se rezolvă separat, mai ușor, deoarece sînt mai mici. Rezultatul final se obține din combinația rezultatelor subproblemelor.&lt;br /&gt;
&lt;br /&gt;
== Tehnica divide et impera simplă (decrease and conquer) ==&lt;br /&gt;
În următoarele exemple de divide et impera problema se reduce la o singură problemă, mai mică. Este un caz degenerat, în care implementarea poate fi iterativă (nu necesită recursivitate).&lt;br /&gt;
&lt;br /&gt;
=== Algoritmul lui Euclid ===&lt;br /&gt;
Și acest binecunoscut algoritm poate fi văzut ca un exemplu de divide et impera. El reduce problema de la cmmdc(a, b) la o problemă mai mică, cmmdc(b, a mod b). Complexitate: O(log min(a, b)).&lt;br /&gt;
&lt;br /&gt;
=== Căutare binară ===&lt;br /&gt;
Precum știm, căutarea binară împarte vectorul original în două, apoi decide care din cele două jumătați ar putea să conțină elementul căutat. Apoi rezolvă problema mai mică. Complexitate: O(log n).&lt;br /&gt;
&lt;br /&gt;
=== Căutare punct fix ===&lt;br /&gt;
Se dă un vector sortat de &amp;lt;tt&amp;gt;n&amp;lt;/tt&amp;gt; elemente întregi distincte. Să se găsească dacă există indicele &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt; astfel încît &amp;lt;tt&amp;gt;&amp;lt;nowiki&amp;gt;v[i] == i&amp;lt;/nowiki&amp;gt;&amp;lt;/tt&amp;gt;. Găsiți un algoritm divide et impera care are complexitate &#039;&#039;O(log n)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Puzzle balanţă ===&lt;br /&gt;
Avem 25 de bile care arată identic, una este ceva mai ușoară. Avem la dispoziție o balanță. Găsiți bila mai ușoară din trei cîntăriri.&lt;br /&gt;
&lt;br /&gt;
=== Selecția ===&lt;br /&gt;
Complexitate: pe medie &#039;&#039;O(n)&#039;&#039;, în cel mai rău caz &#039;&#039;O(n&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Este necesară la problema antitir.&lt;br /&gt;
&lt;br /&gt;
=== Probleme vianuarena ===&lt;br /&gt;
Următoarele probleme de la vianuarena sînt de divide et impera degenerat: &lt;br /&gt;
&lt;br /&gt;
* [http://varena.ro/problema/z zparcurgere]&lt;br /&gt;
* [http://varena.ro/problema/tabela tabela]&lt;br /&gt;
* [http://varena.ro/problema/antitir antitir]&lt;br /&gt;
&lt;br /&gt;
== Tehnica divide et impera propriu zisă ==&lt;br /&gt;
În următoarele exemple de divide et impera problema se reduce la două sau mai multe probleme mai mici. Implementarea este mult mai simplă dacă folosim recursivitate.&lt;br /&gt;
&lt;br /&gt;
=== Puzzle pavare ===&lt;br /&gt;
Se dă o grilă de 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; x 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; pătrate din care lipsește un pătrat. Să se acopere cu piese formate din 3 pătrate aranjate în formă de litera L.&lt;br /&gt;
&lt;br /&gt;
[[File:pav2.jpg|frame|none|Exemplu de pavare cu L-uri]]&lt;br /&gt;
&lt;br /&gt;
Problema pare destul de grea, nu? În realitate, odată ce încercăm să folosim divide et impera ea devine destul de ușoară. Cum putem împărți problema în probleme mai mici?&lt;br /&gt;
&lt;br /&gt;
Să definim o problemă astfel:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;P(n, gol)&#039;&#039; = pavează un pătrat de latură 2&amp;lt;sup&amp;gt;n&amp;lt;/sup&amp;gt; care are un pătrățel lipsă (un gol).&lt;br /&gt;
&lt;br /&gt;
O primă idee ar fi să încercăm să împărțim pătratul în patru pătrate de latură 2&amp;lt;sup&amp;gt;n-1&amp;lt;/sup&amp;gt; și să rezolvăm acele probleme. Avem, însă, o dificultate: doar unul din pătrate va avea un pătrățel lipsă. Celelalte trei vor fi pline. Ce ne facem?&lt;br /&gt;
&lt;br /&gt;
Am putea încerca să eliminăm artificial cîte un pătrățel din celelalte trei pătrate. Cum putem face acest lucru? Cu condiția ca cele trei pătrățele să fie în formă de L și să le acoperim cu o piesă elementară. Ei bine avem cum să alegem pătratele în acest fel: le vom selecta pe cele de la centru! Astfel vom obține subproblemele:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;P(n, l, c)&#039;&#039; se descompune în:&lt;br /&gt;
    &#039;&#039;P(n-1, gol)&#039;&#039; (golul original)&lt;br /&gt;
    &#039;&#039;P(n-1, gol1)&#039;&#039;&lt;br /&gt;
    &#039;&#039;P(n-1, gol2)&#039;&#039;&lt;br /&gt;
    &#039;&#039;P(n-1, gol3)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== Mergesort ===&lt;br /&gt;
Algoritmul mergesort, sau sortare prin interclasare, funcționează astfel:&lt;br /&gt;
* Divide vectorul de sortat în două părți egale (sau aproape egale)&lt;br /&gt;
* Reapelează mergesort pe acele părți&lt;br /&gt;
* La revenire avem două jumătăți sortate, deci putem sorta vectorul interclasînd cei doi vectori în timp liniar&lt;br /&gt;
&lt;br /&gt;
Iată un algoritm:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mergesort( V[], i, j )&lt;br /&gt;
  mij = (i + j) / 2;&lt;br /&gt;
  daca i &amp;lt; mij&lt;br /&gt;
    mergesort( V[], i, mij )&lt;br /&gt;
  daca mij + 1 &amp;lt; j&lt;br /&gt;
    mergesort( V[], mij + 1, j )&lt;br /&gt;
  merge( V[], i, mij, j )&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;merge( V[], i, mij, j )&amp;lt;/tt&amp;gt; interclasează vectorii sortați &amp;lt;tt&amp;gt;V[i..mij]&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;V[mij+1..j]&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are mergesort?&lt;br /&gt;
&lt;br /&gt;
Să calculăm numărul de operații, &#039;&#039;T(N)&#039;&#039;, pentru a sorta un vector de &#039;&#039;N&#039;&#039; elemente:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;T(N) = 2 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; T(N/2) + N&#039;&#039;&lt;br /&gt;
 &#039;&#039;T(N) = 2 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; (2 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; T(N/4) + N/2) + N&#039;&#039;&lt;br /&gt;
 &#039;&#039;T(N) = 4 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; T(N/4) + 2 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; N&#039;&#039;&lt;br /&gt;
 &#039;&#039;T(N) = 4 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; (2 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; T(N/8) + N/4&#039;&#039;) + 2 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; N&#039;&#039;&lt;br /&gt;
 &#039;&#039;T(N) = 8 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; T(N/8) + 3 &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; N&#039;&#039;&lt;br /&gt;
 ...&lt;br /&gt;
 &#039;&#039;T(N) = N &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; T(N/N) + log N &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; N&#039;&#039;&lt;br /&gt;
 &#039;&#039;T(N) = N &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; T(1) + log N &#039;&#039;&#039;&amp;amp;middot;&#039;&#039;&#039; N&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Deoarece &#039;&#039;T(1)&#039;&#039; este &#039;&#039;O(1)&#039;&#039; rezultă o complexitate &#039;&#039;O(N + N log N) = O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Cîtă memorie suplimentară folosește algoritmul? &lt;br /&gt;
&lt;br /&gt;
Pentru interclasarea clasică avem nevoie de un alt vector, deci &#039;&#039;O(N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În concluzie avem complexitățile:&lt;br /&gt;
&lt;br /&gt;
* timp: &#039;&#039;O(n log n)&#039;&#039;&lt;br /&gt;
* memorie suplimentară: &#039;&#039;O(n)&#039;&#039; (aceasta e problema principală a algoritmului).&lt;br /&gt;
&lt;br /&gt;
Există variante ale algoritmului care folosesc mai puțină memorie (o idee evidentă este să folosim doar jumate din memorie deoarece în timpul interclasării putem suprascrie una din jumătăți).&lt;br /&gt;
&lt;br /&gt;
==== Aplicație mergesort - inversiunile unei permutări ====&lt;br /&gt;
O problemă care apare uneori la concursuri este următoarea: se dă o permutare a numerelor de la 1 la &#039;&#039;N&#039;&#039;. Să se numere cîte inversiuni are ea. O inversiune este o pereche de indici &#039;&#039;(i, j)&#039;&#039; astfel încît &#039;&#039;i &amp;lt; j&#039;&#039; și &#039;&#039;P[i] &amp;gt; P[j]&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În linii mari, algoritmul este următorul:&lt;br /&gt;
&lt;br /&gt;
* Sortăm prima jumate a vectorului folosind un mergesort special, care ne returnează numărul de inversiuni din vectorul de sortat.&lt;br /&gt;
* Sortăm a doua jumate a vectorului.&lt;br /&gt;
* În acest moment știm inversiunile din cele două jumătăți - le adunăm.&lt;br /&gt;
* Interclasăm cele două jumătăți. În timpul interclasării cînd selectăm un număr din prima jumătate știm că numerele deja selectate din jumătatea a doua sînt mai mici, deci putem aduna numărul lor la numărul total de inversiuni.&lt;br /&gt;
&lt;br /&gt;
=== Quicksort ===&lt;br /&gt;
Algoritmul quicksort, sau sortare rapidă, inventat în 1959 de informaticianul britanic Tony Hoare, funcționează astfel:&lt;br /&gt;
* Alege o poziție la întîmplare din vectorul de sortat. Vom denumi valoarea de la acea poziție &#039;&#039;pivot&#039;&#039;.&lt;br /&gt;
* Rearanjează vectorul astfel încît în prima parte să avem elemente mai mici sau egale cu pivotul iar în a doua parte elemente mai mari sau egale cu pivotul. Vom denumi acest pas &#039;&#039;pivotare&#039;&#039;.&lt;br /&gt;
* Reapelează quicksort pe acele două părți&lt;br /&gt;
&lt;br /&gt;
Din acest algoritm avem de lămurit pasul de pivotare. El este același ca la quickselect, prezentat într-o lecție anterioară. El procedează astfel:&lt;br /&gt;
* Pornește cu doi indici, unul la începutul vectorului și unul la final de vector, să le spunem &#039;&#039;b&#039;&#039; si &#039;&#039;e&#039;&#039;.&lt;br /&gt;
* Avansează indicele &#039;&#039;b&#039;&#039; pînă la primul element mai mare sau egal cu pivotul&lt;br /&gt;
* Devansează (merge înapoi) indicele &#039;&#039;e&#039;&#039; pînă la primul element mai mic sau egal cu pivotul&lt;br /&gt;
* Interschimbă elementele din vector de la pozițiile &#039;&#039;b&#039;&#039; și &#039;&#039;e&#039;&#039;&lt;br /&gt;
* Repetă pînă ce indicii se ating, &#039;&#039;b &amp;gt;= e&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Iată o implementare posibilă. Ea este implementarea originală a lui Hoare, transformată pentru a fi structurată:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot;&amp;gt;void myqsort( int begin, int end ) {&lt;br /&gt;
  int aux, b = begin, e = end,&lt;br /&gt;
    pivot = v[(begin + end) / 2]; // poate fi orice pozitie intre begin si end - 1&lt;br /&gt;
&lt;br /&gt;
  while (v[b] &amp;lt; pivot) // cauta primul element mai mare sau egal cu pivotul&lt;br /&gt;
    b++;&lt;br /&gt;
&lt;br /&gt;
  while (v[e] &amp;gt; pivot) // cauta primul element mai mic sau egal cu pivotul&lt;br /&gt;
    e--;&lt;br /&gt;
&lt;br /&gt;
  while( b &amp;lt; e ) { // daca indicii nu s-au atins&lt;br /&gt;
    aux = v[b];    // interschimba elementele la pozitiile b si e&lt;br /&gt;
    v[b] = v[e];&lt;br /&gt;
    v[e] = aux;&lt;br /&gt;
    &lt;br /&gt;
    do // cauta primul element mai mare sau egal cu pivotul&lt;br /&gt;
      b++;&lt;br /&gt;
    while (v[b] &amp;lt; pivot);&lt;br /&gt;
&lt;br /&gt;
    do // cauta primul element mai mic sau egal cu pivotul&lt;br /&gt;
      e--;&lt;br /&gt;
    while (v[e] &amp;gt; pivot);&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // acum [begin..e] sint mai mici sau egale cu pivotul&lt;br /&gt;
  if ( begin &amp;lt; e )&lt;br /&gt;
    myqsort(begin, e);&lt;br /&gt;
  // si [e+1..end] sint mai mari sau egale cu pivotul&lt;br /&gt;
  if ( e + 1 &amp;lt; end )&lt;br /&gt;
    myqsort(e + 1, end);&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are quicksort?&lt;br /&gt;
&lt;br /&gt;
Deoarece pivotarea este liniară, calculele sînt similare cu cele de la mergesort. Avem două diferențe:&lt;br /&gt;
* Procesarea liniară se face la început, înainte de recursie.&lt;br /&gt;
* Împărțirea vectorului nu este neapărat în două părți egale, ceea ce duce la o complexitate &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; pe cazul cel mai rău.&lt;br /&gt;
&lt;br /&gt;
Pentru cazul mediu, cînd împărțirea este în două părți aproximativ egale, rezultă aceeași complexitate &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Cîtă memorie suplimentară folosește algoritmul? &lt;br /&gt;
&lt;br /&gt;
Memoria este cea necesară stivei. E va fi, deci, proporțională cu cel mai lung lanț de apeluri recursive imbricate. Pe cazul cel mai rău el este &#039;&#039;O(N)&#039;&#039;, iar pe cazul mediu este &#039;&#039;O(log N)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Optimizare&#039;&#039;&#039;: putem reduce cazul cel mai rău astfel:&lt;br /&gt;
* Observăm că al doilea apel recursiv este recursiv la coadă.&lt;br /&gt;
* Precum știm, el nu va consuma memorie.&lt;br /&gt;
* Putem reduce memoria stivei apelînd mai întîi pentru partea mai mică ce rezultă după pivotare.&lt;br /&gt;
* În acest fel chiar și în cazul cel mai rău memoria suplimentară necesară va fi &#039;&#039;O(log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În concluzie avem complexitățile:&lt;br /&gt;
&lt;br /&gt;
* timp: &#039;&#039;O(N log N)&#039;&#039;&lt;br /&gt;
* memorie suplimentară: &#039;&#039;O(log N)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție&#039;&#039;&#039;: deși putem selecta ca pivot orice element din vector, nu putem selecta ca pivot ultimul element. Aceasta ar duce la o buclă infinită.&lt;br /&gt;
&lt;br /&gt;
==== Pivotarea Lomuto ====&lt;br /&gt;
În școli, de multe ori se predă un alt algoritm de pivotare, probabil deoarece este considerat mai simplu. Nu o vom discuta aici, deoarece este foarte ineficientă. Iată diferențele față de pivotarea Hoare:&lt;br /&gt;
* Lomuto este ceva mai simplu de reținut și ceva mai scurtă.&lt;br /&gt;
* Ambii algoritmi sînt &#039;&#039;cache friendly&#039;&#039; deoarece parcurg liniar vectorul.&lt;br /&gt;
* Lomuto face de trei ori mai multe interschimburi.&lt;br /&gt;
* Lomuto face &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; operații pe un vector sortat, Hoare doar &#039;&#039;O(N log N)&#039;&#039;&lt;br /&gt;
* Lomuto face &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039; operații pe un vector cu elemente egale, Hoare doar &#039;&#039;O(N log N)&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie&#039;&#039;&#039;: Pivotarea Lomuto este un algoritm didactic, bun pentru înțelegerea pivotării, dar este contraindicat într-o implementare practică.&lt;br /&gt;
&lt;br /&gt;
==== Randomizarea pivotului ====&lt;br /&gt;
Dacă alegem ca pivot prima poziție din vector, sau cea din mijloc, &amp;quot;adversarul&amp;quot; poate, studiind algoritmul, să găsească un vector special pe care implementarea noastră să intre pe cazul cel mai rău, &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;)&#039;&#039;. Putem lupta cu aceasta, alegînd pivotul pe o poziție aleatoare din intervalul [begin..end-1].&lt;br /&gt;
&lt;br /&gt;
==== Probleme quicksort ====&lt;br /&gt;
&#039;&#039;&#039;Atenție&#039;&#039;&#039;! Algoritmul este celebru pentru bug-urile pe care le putem introduce la implementare. Un semn mai mic transformat în mai mic sau egal poate duce la bucle infinite sau la un vector nesortat.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție&#039;&#039;&#039;: pentru olimpiadă este bine să memorați algoritmul pe din-afară.&lt;br /&gt;
&lt;br /&gt;
Ați putea spune &amp;quot;hei, dar la olimpiadă pot folosi &#039;&#039;algorithms&#039;&#039;!&amp;quot; Este opțiunea voastră, dar eu nu aș risca. Motivele împotriva folosirii funcției de sortare de bibliotecă:&lt;br /&gt;
&lt;br /&gt;
* Ea primește ca parametru o funcție de comparație. Apelul acelei funcții este lent, fiind vorba de accesul unui pointer pentru a obține adresa funcției, apoi de punerea pe stivă a parametrilor. Toate acestea se reduc la o comparație sau două în codul nostru.&lt;br /&gt;
* Funcția de bibliotecă este o cutie neagră. Ea depinde de compilator și de biblioteca de funcții a acestuia. Ea este scrisă de alți oameni. Aveți mai multă încredere în cod scris de alții, care variază în funcție de calculatorul pe care se face evaluarea?&lt;br /&gt;
* În mod uzual funcțiile de bibliotecă consumă mai multă memorie, iar uneori mult mai multă. De ce? Bună întrebare. Nu știu.&lt;br /&gt;
* Algoritmul de mai sus are 23 de linii de cod efectiv. El este simplu și eficient și poate fi adaptat să sorteze orice tip de date, cu mici modificări. De ce am risca să apelăm o funcție de bibliotecă despre care nu știm nimic, cînd în 3 minute am scris acest cod?&lt;br /&gt;
&lt;br /&gt;
=== Ziua regelui ===&lt;br /&gt;
Un rege organizează o petrecere mare de ziua lui. Petrecerea începe peste 24 de ore. Regele află că una din cele 1000 de sticle de vin pe care le va servi este otrăvită. Otrava acționează ciudat: omul care bea chiar și cea mai mică cantitate din această otravă nu are nici un simptom vreme de 24 de ore, apoi moare subit. Regele are sclavi la dispoziție pe care îi poate folosi. Lui nu îi pasă cîți sclavi mor, dar îi pasă cîți sclavi beau vin, deoarece un sclav care a băut vin nu poate fi folosit la treabă, ci trebuie izolat spre observare. Care este numărul minim de sclavi cu care regele poate afla sticla otrăvită?&lt;br /&gt;
&lt;br /&gt;
=== Probleme temă ===&lt;br /&gt;
Problemele de la temă sînt probleme de divide et impera propriu zise: &lt;br /&gt;
&lt;br /&gt;
* [http://varena.ro/problema/latin latin] dată la cercul de informatică Vianu, gimnaziu 2013&lt;br /&gt;
* [http://varena.ro/problema/pav pav] dată la Lot IS 2008&lt;br /&gt;
* [http://varena.ro/problema/forma forma] dată la olimpiada pe sector 2012, clasa a 8&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;&lt;br /&gt;
* [http://varena.ro/problema/hanoi1 hanoi1]&lt;br /&gt;
&lt;br /&gt;
= Fulgerică (test fulger) =&lt;br /&gt;
&#039;&#039;Găsiți un algoritm la următoarea problemă și explicați-l pe o foaie de hîrtie. Dacă nu vă descurcați să îl explicați puteți scrie cod. Sau formule matematice. Sau puteți scrie din toate trei. Important este ca algoritmul să se înțeleagă și să fie corect. Atenție! Spre deosebire de varena, vi se cere un algoritm corect, echivalentul unei rezolvări care să treacă toate testele. Nu gîndiți la modul &amp;quot;cum fac să iau cît mai multe puncte&amp;quot;. Nu se acceptă &amp;quot;mînăreli&amp;quot;. Voi corecta lucrările personal. Nu vă concentrați pe citire, sau scriere.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Problema Gigel ==&lt;br /&gt;
Fie următorul număr în baza 16:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;X&#039;&#039;&#039; = 123456789ABCDEF&lt;br /&gt;
&lt;br /&gt;
Gigel vă ia din acest număr un subnumăr de &#039;&#039;&#039;K&#039;&#039;&#039; cifre contigue (una după alta). Gigel fiind lacom, el va lua cel mai mare număr posibil de &#039;&#039;&#039;K&#039;&#039;&#039; cifre. De exemplu, pentru &#039;&#039;&#039;K&#039;&#039;&#039;=6 el va lua numărul ABCDEF.&lt;br /&gt;
&lt;br /&gt;
Voi vreți ca Gigel să ia un număr cît mai mic. Pentru aceasta puteți reordona cifrele numărului &#039;&#039;&#039;X&#039;&#039;&#039;. Cum îl veți reordona pentru aceasta?&lt;br /&gt;
&lt;br /&gt;
La intrare veți citi numărul &#039;&#039;&#039;K&#039;&#039;&#039; (1 &amp;amp;le; &#039;&#039;&#039;K&#039;&#039;&#039; &amp;amp;le; 14).&lt;br /&gt;
&lt;br /&gt;
Se cere să afișați două numere:&lt;br /&gt;
* Numărul minim de &#039;&#039;&#039;K&#039;&#039;&#039; cifre pe care îl poate lua Gigel.&lt;br /&gt;
* O rearanjare a lui &#039;&#039;&#039;X&#039;&#039;&#039; care duce la acel număr minim.&lt;br /&gt;
&lt;br /&gt;
=== Exemplu ===&lt;br /&gt;
Pentru &#039;&#039;&#039;K&#039;&#039;&#039;=2 putem rearanja &#039;&#039;&#039;X&#039;&#039;&#039; astfel:&lt;br /&gt;
&lt;br /&gt;
 &#039;&#039;&#039;X&#039;&#039;&#039; = 68D57E19A23BC4F&lt;br /&gt;
&lt;br /&gt;
Ceea ce duce la faptul că Gigel va lua numărul de două cifre&lt;br /&gt;
&lt;br /&gt;
 E1&lt;br /&gt;
&lt;br /&gt;
el fiind și cel mai mic număr de două cifre pe care îl poate lua din toate posibilitățile de a rearanja &#039;&#039;&#039;X&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Deci, dacă ați determinat cele două valori, veți afișa:&lt;br /&gt;
&lt;br /&gt;
 E1&lt;br /&gt;
 68D57E19A23BC4F&lt;br /&gt;
&lt;br /&gt;
=== Restricţii ===&lt;br /&gt;
* timp de execuție: 0.1s.&lt;br /&gt;
* memorie disponibilă: 64MB.&lt;br /&gt;
&lt;br /&gt;
= Temă =&lt;br /&gt;
* [http://varena.ro/runda/2020-01-30-clasa-7-tema-18 Tema 19 clasa a 7&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;]&lt;br /&gt;
** [http://varena.ro/problema/latin latin] dată la cercul de informatică Vianu, gimnaziu 2013&lt;br /&gt;
** [http://varena.ro/problema/pav pav] dată la Lot IS 2008&lt;br /&gt;
** [http://varena.ro/problema/forma forma] dată la olimpiada pe sector 2012, clasa a 8&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;Opţional&#039;&#039;&#039;: [http://varena.ro/runda/2020-01-30-clasa-7-tema-19-optionala Tema 19 clasa a 7&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt; opţională]&lt;br /&gt;
** [http://varena.ro/problema/hanoi1 hanoi1] dată la concursul .campion 2003&lt;br /&gt;
* &#039;&#039;&#039;Opţional&#039;&#039;&#039;: încercaţi să găsiţi rezolvări la problemele enunţate în lecţia aceasta, inclusiv la puzzle-uri:&lt;br /&gt;
** [http://varena.ro/problema/z zparcurgere]&lt;br /&gt;
** [http://varena.ro/problema/tabela tabela]&lt;br /&gt;
** [http://varena.ro/problema/antitir antitir]&lt;br /&gt;
&lt;br /&gt;
Rezolvări aici [http://solpedia.francu.com/wiki/index.php/Clasa_a_VII-a_lec%C8%9Bia_19_-_30_ian_2020]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18546</id>
		<title>Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18546"/>
		<updated>2026-02-03T14:55:19Z</updated>

		<summary type="html">&lt;p&gt;Cristian: /* Studiu de caz: problema bile1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/W59ZLyfMZlc&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica Two Pointers (doi pointeri) ==&lt;br /&gt;
&lt;br /&gt;
În informatica de concurs avem tehnici semi-banale cărora le dăm o denumire pentru a putea să le referim ușor într-o discuție. Este și cazul acestei metode. Deși relativ evidentă, ea are un nume 🙂 Pe scurt, &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; folosește doi indici într-un vector ce avansează pe rând, similar cu interclasarea, în căutarea rezultatului. Este o tehnică ce produce în general algoritmi liniari. Ea decurge în mod natural din căutarea binară, atunci când avem un algoritm relativ simplu ce folosește multiple căutări binare, ale căror rezultate sunt fie crescătoare fie descrescătoare.&lt;br /&gt;
&lt;br /&gt;
Să luăm câteva exemple.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Pereche de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de elemente sortate crescător, să se spună câte o perechi există de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[j] == X&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Cum am putea rezolva problema? Pentru fiecare număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; să căutăm un număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j]&amp;lt;/source&amp;gt; cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j &amp;gt; i&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[j] == X&amp;lt;/source&amp;gt;. Dacă fixăm &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; atunci &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j]&amp;lt;/source&amp;gt; poate fi determinat prin căutare binară, deoarece vectorul este ordonat crescător. Vom obține, deci, un algoritm &#039;&#039;O(N log N)&#039;&#039;, unde &#039;&#039;&#039;N&#039;&#039;&#039; este numărul de elemente din vector.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie vectorul: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
v[] = { 20, 25, 30, 35, 40, 50, 55, 65, 75 }&lt;br /&gt;
indici:  0   1   2   3   4   5   6   7   8&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X = 75&amp;lt;/source&amp;gt;. Pentru fiecare &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; vom căuta binar ultimul număr mai mic sau egal cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X - v[i]&amp;lt;/source&amp;gt;. Astfel:&lt;br /&gt;
&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 0&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 20&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X - v[i] = 55&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 6&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 55&amp;lt;/source&amp;gt; soluție. &lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 1&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 25&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;50&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 5&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 50&amp;lt;/source&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 2&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 30&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;45&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 40&amp;lt;/source&amp;gt; (ultima mai mică sau egală cu 45) nu este soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 3&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 35&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;40&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 40&amp;lt;/source&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 40&amp;lt;/source&amp;gt; ar trebui să căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;35&amp;lt;/source&amp;gt; care este mai mică decât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; așa încât ne oprim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce observăm? Că valorile lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; sunt descrescătoare. Este destul de clar că așa și trebuie să fie, nu?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație importantă&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tehnica Two Pointers&#039;&#039;&#039;: atunci când o succesiune de &#039;&#039;&#039;căutări binare&#039;&#039;&#039; într-un vector au ca rezultat o &#039;&#039;&#039;secvență monotonă&#039;&#039;&#039; de numere (crescătoare sau descrescătoare), căutările binare pot fi înlocuite cu &#039;&#039;&#039;căutări liniare&#039;&#039;&#039;. Această înlocuire și noua metodă rezultată poartă denumirea de &#039;&#039;&#039;Two Pointers&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are noul algoritm?&lt;br /&gt;
&lt;br /&gt;
La prima vedere , pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; vom căuta liniar perechea lui, ceea ce ar rezulta într-un algoritm &#039;&#039;O(N)&#039;&#039;. În realitate, indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; se va muta mereu către stânga, drept care el nu va putea parcurge mai mult de &#039;&#039;O(N)&#039;&#039; elemente în toate căutările liniare. Deci, folosind analiza amortizată, deși oricare din căutările liniare poate fi &#039;&#039;O(N)&#039;&#039;, suma tuturor acestor căutări nu poate depăși &#039;&#039;O(N)&#039;&#039;. Drept care metoda Two Pointers duce la un algoritm &#039;&#039;O(N)&#039;&#039;, superior celui cu căutare binară, care era &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Subsecvență de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de elemente nenule, să se spună câte o perechi există de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[i+1] + v[i+2] + ... + v[j] == X&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Și aici avem un algoritm relativ simplu: calculăm vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; de sume parțiale și pentru fiecare element pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; în vector vom căuta binar în vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[i-1] + X&amp;lt;/source&amp;gt;. Dacă găsim această valoare la poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; avem că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[j] - s[i-1] == X&amp;lt;/source&amp;gt; și deci suma elementelor de la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; în vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X&amp;lt;/source&amp;gt;, deci avem o soluție.&lt;br /&gt;
&lt;br /&gt;
Complexitatea acestui algoritm va fi, din nou, &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce observăm, însă? Că rezultatele căutărilor binare (indicii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt;) vor fi în ordine crescătoare, deoarece sumele parțiale sunt strict crescătoare. Putem, deci, folosi &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; pentru a căuta indicii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; prin căutare liniară: pentru fiecare avans al lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; vom avansa indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; până ce suma devine mai mare sau egală cu cea dorită, moment la care testăm egalitatea.&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(N)&#039;&#039;. Mai mult, deoarece ne interesează doar suma subsecvenței de la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; putem renunța la vectorul de sume parțiale și lucra direct pe vectorul original &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;, menținând incremental suma subsecvenței studiate.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Problema NrTri ===&lt;br /&gt;
&lt;br /&gt;
Am discutat într-o lecție trecută problema [https://www.nerdarena.ro/problema/nrtri NrTri]. Ea cere să găsiți într-un vector numărul de tripleți ce pot forma laturile unui triunghi. Aici avem o soluție cu căutare binară: după ce sortăm elementele, pentru fiecare pereche de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; vom căuta binar cel mai mare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k &amp;gt; j&amp;lt;/source&amp;gt; cu proprietatea că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[k]&amp;lt;/source&amp;gt;. Toate elementele între &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (inclusiv &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;) formează un triplet valabil cu perechea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Această soluție are complexitatea &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; log N)&#039;&#039;. Ca și în exemplele anterioare, observăm că atunci când &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; avansează căutarea binară va returna valori &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; crescătoare. Și aici putem aplica tehnica &#039;&#039;Two Pointers&#039;&#039;, avansând liniar indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Rezultă un algoritm O(N2). Observați că tehnica aceasta reduce complexitatea cu acel factor log N. În practică nu este clar că vom putea întotdeauna diferenția între căutarea binară și Two Pointers. Cu toate acestea metoda merită, deoarece codul rezultat este mai simplu și constanta de implementare scade considerabil.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tipul de date struct ==&lt;br /&gt;
&lt;br /&gt;
Până acum am învățat tipuri de date simple (&amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;char&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;long long&amp;lt;/source&amp;gt;, etc) și tipuri de date compuse (ce grupează mai multe tipuri de date simple) anume tablouri, fie ele unidimensionale (vectori), bidimensionale (matrice), sau chiar de dimensiuni mai mari. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; };&lt;br /&gt;
...&lt;br /&gt;
struct nrmare n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
sau, mai scurt (putem declara variabile la definirea tipului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Elementele declarate în interiorul unui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; se numesc membri. Putem accesa membrii unei variabile tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; cu operatorul punct. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
t += n1.cf[i] + n2.cf[i];&lt;br /&gt;
n3.cf[i] = t % 10;&lt;br /&gt;
t /= 10;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; reprezintă un alt mod de a grupa date simple într-o &#039;&#039;structură&#039;&#039;. Aceste date vor fi reprezentate în memorie la rând, una după alta.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Diferențe&#039;&#039;&#039; între tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; și tipul tablou:&lt;br /&gt;
&lt;br /&gt;
* Tipul struct poate grupa date de tipuri diferite, tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;tablou&amp;lt;/source&amp;gt; nu.&lt;br /&gt;
* Tipul struct accesează elementele sale pe bază de denumire, tipul tablou accesează elementele numeric, pe bază de indice (poziție).&lt;br /&gt;
* În consecință tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; grupează un număr relativ mic de date.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Avantaje&#039;&#039;&#039; ale tipului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Gruparea datelor duce la o ordine a codului.&lt;br /&gt;
* Codul devine mai clar și mai ușor de citit, deci o depanare mai ușoară.&lt;br /&gt;
* Folosit corect tipul struct poate duce la un acces mai localizat al memorie (&#039;&#039;cache friendly&#039;&#039;) și, deci, la o scăderea a timpului de execuție.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; probleme cu tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Orice variabilă de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; va fi completată până la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt;. Actual, acest tip este pe patru octeți, ceea ce înseamnă că un struct va ocupa un număr de octeți divizibil cu patru. Dacă vom declara un struct cu un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt; și un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;char&amp;lt;/source&amp;gt; el va ocupa 8 octeți și nu 5!&lt;br /&gt;
&lt;br /&gt;
* Un vector de structuri poate fi înlocuit întotdeauna cu mai mulți vectori, câte unul pentru fiecare membru. Decizia de a folosi struct trebuie luată în funcție de modul de accesare a membrilor în algoritmul nostru. O decizie proastă poate duce la deteriorarea timpului de execuție din motive de cache.&lt;br /&gt;
&lt;br /&gt;
* Combinația între cele două de mai sus poate fi destul de rea: un struct care ocupă mai mult, chiar dacă nu duce la o depășire de memorie, va crește necesarul de memorie ceea ce duce la creșterea timpului de execuție din motive de &#039;&#039;cache&#039;&#039;. Aceasta, combinat cu un acces prost la memorie, poate duce la o creștere semnificativă a timpului de execuție - poate chiar dublarea lui!&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie&#039;&#039;&#039;: folosiți tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; numai dacă știți exact ce faceți. Recomandarea mea este ca deocamdată să nu îl folosiți.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: problema bile1 ===&lt;br /&gt;
&lt;br /&gt;
Implementarea listelor se face în general bine folosind tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;. În mod natural o celulă a listei va conține o informație și o legătură la următoarea celulă (next). În particular am învățat acea structură de date, vectorul de liste, pe care îl foloseam pentru a evita sortarea, memorând pentru fiecare linie a unei matrice acele coloane care ne interesează, unde apar elemente interesante pe acea linie. Să luăm un caz concret.&lt;br /&gt;
&lt;br /&gt;
În problema [https://www.nerdarena.ro/problema/bile1 Bile1] se cere să simulăm căderea unor bile printr-o matrice cu obstacole. Am rezolvat-o memorând obstacolele ca liste de coloane &#039;&#039;agățate&#039;&#039; pe liniile unde apar obstacole. Pentru aceasta am folosit doi vectori. Iată soluția de atunci:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                   // capetele de lista&lt;br /&gt;
short col[NOBST + 1], next[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                        // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    col[i] = c;         // asezam coloana c in lista liniei l&lt;br /&gt;
    next[i] = lin[l];   // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = col[pc] - 1; // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = next[pc];   // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Este clar că atunci când accesăm o celulă a listei vom avea nevoie atât de numărul de coloană cât și de next, pentru a avansa la elementul următor. Deci poate ar fi o idee bună să le grupăm, pentru &#039;&#039;cache&#039;&#039;. Partea bună este că atât coloana cât și următorul element sunt mici, deci putem folosi tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, ceea ce înseamnă că o celulă va ocupa două elemente &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, adică patru octeți, fiind aliniată la întreg.&lt;br /&gt;
&lt;br /&gt;
Iată programul modificat să folosească tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                                    // capetele de lista&lt;br /&gt;
struct cell { short col; short next; } obst[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                            // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    // asezam coloana c in lista liniei l&lt;br /&gt;
    // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    obst[i] = (struct cell){ .col = c, .next = lin[l] }; // cream noua celula&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = obst[pc].col - 1;                   // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = obst[pc].next; // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați cum putem atribui o variabilă de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;, folosind denumirile membrilor săi: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
obst[i] = (struct cell){ .col = c, .next = lin[l] };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: numere mari ===&lt;br /&gt;
&lt;br /&gt;
Numerele mari se pot reprezenta în mod natural ca un struct ce conține numărul de cifre și vectorul de cifre, precum am văzut mai sus. Avantajul constă în gruparea datelor și în claritatea codului ce menține numarul de cifre corect - nu mai avem nevoie să returnăm în funcția de înmulțire numărul de cifre al rezultatului, ci îl setăm chiar în numărul mare trimis ca parametru.&lt;br /&gt;
&lt;br /&gt;
Atunci când trimitem un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; mare ca parametru (care ocupă mai mult de un întreg) este bine să-l trimitem ca pointer, deoarece evităm copierea. În acest caz accesul la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; se face un pic anevoios. &lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de funcție de adunare număr mic la număr mare:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += (*n).cf[i];&lt;br /&gt;
    (*n).cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; (*n).ncf )&lt;br /&gt;
    (*n).ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați că avem nevoie de două paranteze, un asterisc și un punct pentru a apela un membru al structurii trimise ca pointer. De aceea limbajul C ne oferă o alternativă mai simplă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
    // forme echivalente:&lt;br /&gt;
    (*n).cf[i];&lt;br /&gt;
&lt;br /&gt;
    // este totuna cu&lt;br /&gt;
    n-&amp;gt;cf[i];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Precum vedeți săgețica este echivalentul punctului atunci când variabila este de tip pointer la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de implementare de numere mari:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXNCF 1000&lt;br /&gt;
&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mic a din numarul mare n&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (a &amp;lt;= n)&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void scadeNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  a = -a;&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;lt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    if ( n-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n-&amp;gt;cf[i] += 10;&lt;br /&gt;
      a--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n-&amp;gt;cf[n-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mare n2 la numarul mare n1&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void adunaNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, max, t;&lt;br /&gt;
&lt;br /&gt;
  max = n1-&amp;gt;ncf &amp;lt; n2-&amp;gt;ncf ? n2-&amp;gt;ncf : n1-&amp;gt;ncf;&lt;br /&gt;
  t = 0;&lt;br /&gt;
  for ( i = 0; i &amp;lt; max; i++ ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] + n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
  }&lt;br /&gt;
  if ( t &amp;gt; 0 ) {&lt;br /&gt;
    n1-&amp;gt;cf[i] = t;&lt;br /&gt;
    max++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  n1-&amp;gt;ncf = max;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mare n2 din numarul mare n1&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (n2 &amp;lt;= n1)&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void scadeNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n2-&amp;gt;ncf || t &amp;lt; 0 ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] - n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    if ( n1-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n1-&amp;gt;cf[i] += 10;&lt;br /&gt;
      t--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n1-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n1-&amp;gt;cf[n1-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n1-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// inmulteste numarul mic a cu numarul mare n cu rezultatul in n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void inmultesteNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
  long long t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n-&amp;gt;ncf || t &amp;gt; 0 ) {&lt;br /&gt;
    t = t + a * (long long)n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// tipareste un numar mare n&lt;br /&gt;
void printN( struct nrmare *n ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( n-&amp;gt;ncf == 0 )&lt;br /&gt;
    fputc( &#039;0&#039;, stdout );&lt;br /&gt;
  else&lt;br /&gt;
    for ( i = n-&amp;gt;ncf - 1; i &amp;gt;= 0; i-- )&lt;br /&gt;
      fputc( &#039;0&#039; + n-&amp;gt;cf[i], stdout );&lt;br /&gt;
  fputc( &#039;\n&#039;, stdout );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  // n1, n2, n3 declarate global, initial zero&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 75464: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;75464 - 75464: &amp;quot; );&lt;br /&gt;
  scadeNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 834205: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 834205 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  adunaNn( &amp;amp;n2, 1024586 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586: &amp;quot; );&lt;br /&gt;
  adunaNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586 - 1024586: &amp;quot; );&lt;br /&gt;
  scadeNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 * 2043984094: &amp;quot; );&lt;br /&gt;
  inmultesteNn( &amp;amp;n1, 2043984094 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Metoda Greedy ==&lt;br /&gt;
&lt;br /&gt;
Uneori trebuie să rezolvăm probleme ce necesită să găsim o soluție minimă sau maximă. Una din cele mai simple și evidente încercări de rezolvare este ca, la fiecare pas, să luăm decizia optimă pe moment - decizie &#039;&#039;lacomă&#039;&#039; - cel mai bun lucru la momentul actual.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dat un șir de numere pozitive și negative să se calculeze suma maximă ce se poate forma cu exact &#039;&#039;K&#039;&#039; numere.&lt;br /&gt;
&lt;br /&gt;
Soluție greedy: la fiecare pas vom selecta maximul dintre numerele rămase. Vom porni cu maximul. Apoi cu al doilea maxim și așa mai departe. La fiecare pas &#039;&#039;ne lăcomim&#039;&#039; la cel mai mare număr posibil.&lt;br /&gt;
&lt;br /&gt;
Este, sper, destul de clar că în acest caz obținem soluția optimă. Însă nu întotdeauna este așa, precum vom vedea.&lt;br /&gt;
&lt;br /&gt;
Să vedem câteva exemple de algoritmi greedy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Plată cu număr minim de bancnote puteri ale lui 2 ===&lt;br /&gt;
&lt;br /&gt;
Dispunem de bancnote puteri ale lui 2. Pentru fiecare putere a lui 2 vom avea un număr de bancnote disponibile, posibil niciuna. Dat &#039;&#039;&#039;X&#039;&#039;&#039;, dorim să achităm suma &#039;&#039;&#039;X&#039;&#039;&#039; cu un număr minim de bancnote posibile. Cum procedăm?&lt;br /&gt;
&lt;br /&gt;
Vom proceda greedy: selectăm bancnotele de valoare maximă ce încap în &#039;&#039;&#039;X&#039;&#039;&#039;. Fie epuizăm numărul de bancnote de acea putere și trecem mai departe, fie ne rămân bancnote dar suma de plată devine mai mică decât 2 la acea putere. Procedăm în continuare la fel.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dispunem de următoarele bancnote: 3 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;, 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;, 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, 5 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;, 6 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; și ne dorim să achităm suma 53. Procedăm astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  Pas 1: selectăm 1 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;. Suma rămasă: 21 = 53 - 32&lt;br /&gt;
  Pas 2: selectăm 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;. Suma rămasă: 13 = 21 - 8&lt;br /&gt;
  Pas 3: selectăm 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;. Suma rămasă: 5 = 13 - 8&lt;br /&gt;
  Pas 4: selectăm 2 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;. Suma rămasă: 1 = 5 - 4&lt;br /&gt;
  Pas 5: selectăm 1 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt;. Suma rămasă: 0 = 1 - 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Astfel numărul de bancnote este 7, iar numărul este plătit astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
53 = 32 + 8 + 4 + 4 + 2 + 2 + 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sortarea prin selecție ===&lt;br /&gt;
&lt;br /&gt;
Ne putem gândi la sortarea prin selecție ca având un pas de bază greedy: la fiecare trecere prin vector selectăm în mod greedy maximul și apoi îl trecem la coadă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul minim de intervale care acoperă un interval ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o mulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; ce acoperă un alt interval mai mare să se găsească o submulţime minimă a acestei mulţimi care conţine intervale ce acoperă același interval original.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Intervalul acoperit este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;[0, 18]&amp;lt;/source&amp;gt;. Ne dorim să păstrăm cât mai puține intervale care să acopere același interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idee de soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
O idee naturală este să sortăm intervalele după punctul de început, iar apoi să le luăm în ordine, unul câte unul. Având un interval curent selectat, am putea să selectăm intervalul care începe cât mai târziu dar al cărui interval este în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
Pe exemplul de mai sus observăm că nu funcționează, deoarece am selecta intervalele 4, 1 și apoi nu am mai avea ce să selectăm în continuare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom parcurge intervalele în ordinea capătului de început, ca mai devreme. Dar vom selecta următorul interval pe acela care se &#039;&#039;&#039;închide ultimul&#039;&#039;&#039; dintre cele care au capătul stânga în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
În cazul nostru vom selecta intervalele 4, 5 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul maxim de intervale disjuncte ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o submulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; să se găsească o submulţime maximală a acestei mulţimi care conţine doar intervale disjuncte (neintersectante).&lt;br /&gt;
&lt;br /&gt;
Această problemă este totuna cu numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele (de ce?).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idei de soluţie &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Această problemă apare destul de des la olimpiade, deghizată într-o formă sau alta. Deşi pare o problemă grea, în realitate ea se rezolvă uşor. O primă idee ar fi să încercăm să ordonăm intervalele după punctul de început, apoi să le luăm în ordine, unul câte unul. Pentru fiecare interval ne întrebăm dacă îl intersectează pe cel anterior. Dacă da, îl aruncăm şi mergem mai departe. Dacă nu, îl adăugăm la submulţime şi îl reţinem ca interval ultim. Această soluţie nu funcţionează şi e destul de evident de ce: am putea avea un prim interval foarte lung, care acoperă toate celelalte intervale.&lt;br /&gt;
&lt;br /&gt;
Cu toate acestea rezolvarea nu e total greşită. În fapt, ea aproape funcţionează! Are nevoie doar de o modificare ce se impune, analizînd contraexemplul anterior: de ce nu este bun acel prim interval foarte lung? Deoarece el se termină după celelalte. Şi atunci ne putem întreba, în mod natural, ce s-ar întâmpla dacă am alege intervalul care se închide primul, nu care începe primul? Desigur că acest algoritm funcţionează. Vă las vouă să vă luptaţi cu demonstraţia, care decurge cam aşa: să presupunem că soluţia generată de acest algoritm nu e optimă. Înseamnă că există o altă soluţie cu mai multe intervale. Acea soluţie trebuie să difere undeva faţă de cea găsită de noi. Mergeţi pe această cale şi trebuie să ajungeţi la o contradicţie cum că soluţia optimă nu poate avea mai multe segmente.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Atunci vom selecta, în ordine, intervalele 3 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Soluţie ====&lt;br /&gt;
&lt;br /&gt;
Care este algoritmul, în mare?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
# Iniţial mulţimea M este vidă.&lt;br /&gt;
# Ordonează crescător intervalele după punctul de închidere.&lt;br /&gt;
# Consideră ultima închidere (care nu există încă) cu valoarea minus infinit.&lt;br /&gt;
# Pentru fiecare interval, în ordine:&lt;br /&gt;
## Dacă el conţine ultima închidere&lt;br /&gt;
### Ignoră-l.&lt;br /&gt;
## Altfel&lt;br /&gt;
### Selectează intervalul curent şi adaugă-l la mulţimea M.&lt;br /&gt;
### Setează ultima închidere pe închiderea intervalului selectat&lt;br /&gt;
# Afişează mulţimea M.&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ce se întâmplă dacă avem mai multe intervale care se termină în acelaşi punct? Atunci putem reţine intervalul cel mai mic. De ce? Deoarece este cel mai convenabil. Să presupunem că soluţia maximală conţinea un alt interval care se termina în acel punct. Atunci putem înlocui acel interval cu intervalul cel mai mic, obţinînd o altă soluţie maximală.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Considerente de implementare ====&lt;br /&gt;
&lt;br /&gt;
Implementarea directă: memorăm intervalele ca perechi, prin capetele lor. Sortăm perechile după capătul din dreapta. Apoi facem o parcurgere a perechilor. Această implementare necesită &#039;&#039;O(N log N)&#039;&#039; timp și &#039;&#039;O(N)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
Implementare relaxată: atunci când coordonatele sunt suficient de mici astfel încât să putem folosi un vector care să memoreze un întreg pentru fiecare coordonată, putem încerca o abordare mai simplă: nu memorăm intervalele. Ci, pentru fiecare interval &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;[ai, bi]&amp;lt;/source&amp;gt;, vom seta &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;început[bi] = ai&amp;lt;/source&amp;gt;. Cu alte cuvinte vom memora capetele stânga la poziția capetelor dreapta. Apoi parcurgem vectorul în ordine, acoperind segmentul de coordonate care acoperă toate intervalele, testând dacă elementul de la poziția curentă în vector este mai mic decât ultima închidere memorată. Dacă nu, atunci selectăm intervalul și mutăm ultima închidere la poziția curentă în vector. Trebuie să avem grijă la următoarele:&lt;br /&gt;
&lt;br /&gt;
* Să iniţializăm vectorul de coordonate cu o valoare distinctă de capete de interval. Matematic am putea să le iniţializăm cu minus infinit, astfel încât să nu fie selectate niciodată.&lt;br /&gt;
* Atunci când &#039;&#039;aşezăm&#039;&#039; intervalele pe vector să avem grijă ca dacă găsim deja o valoare în vector să o înlocuim numai dacă cea curentă este mai mare (intervalul este mai mic).&lt;br /&gt;
&lt;br /&gt;
Această soluţie necesită &#039;&#039;O(N + CMAX)&#039;&#039; timp şi memorie, &#039;&#039;&#039;CMAX&#039;&#039;&#039; fiind coordonata maximă. De aici rezultă când o putem folosi: atunci când &#039;&#039;&#039;CMAX&#039;&#039;&#039; este suficient de mic.&lt;br /&gt;
&lt;br /&gt;
==== Generalizare ====&lt;br /&gt;
&lt;br /&gt;
Putem generaliza această problemă: să se calculeze mulţimea maximală de dreptunghiuri neintersectante. Sau de paralelipipede. Sau de paralelipipede n-dimensionale. Cum se rezolvă aceste probleme? Din nefericire aceste probleme fac parte dintr-o clasă de probleme numite &#039;&#039;NP-hard&#039;&#039;, pentru care nu se cunoaşte o soluţie bună.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu când greedy nu funcționează: prolema rucsacului ===&lt;br /&gt;
&lt;br /&gt;
Problema rucsacului cere să &#039;&#039;umplem&#039;&#039; cât mai bine un rucsac care poate duce o greutate maximă, dispunând de obiecte de diverse greutăți.&lt;br /&gt;
&lt;br /&gt;
Exemplu: Vreau să umplu cât mai bine un rucsac de 100 și am greutăți 50, 30, 25 și 25.&lt;br /&gt;
&lt;br /&gt;
Prin metoda greedy vom alege greutățile cele mai mari, în ordine: 50 și 30, după care nu mai putem adăuga nimic. Astfel vom obține greutatea totală 80. Dacă am alege însă greutățile 50, 25 și 25 am putea umple complet rucsacul.&lt;br /&gt;
&lt;br /&gt;
De remarcat totuși că chiar și atunci când metoda greedy nu calculează optimul, un algoritm greedy bine gândit va da o soluție bună, aproape de optim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 16 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/politic Politic] dată la ONI 2007 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumax SumaX] dată la Shumen Juniori 2015&lt;br /&gt;
* [https://www.nerdarena.ro/problema/acoperire Acoperire]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/char Char] dată la ONI 2010 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Pentru doritorii să încerce și soluția problemei numărul minim de numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele puteți rezolva problema [https://www.nerdarena.ro/problema/baloane Baloane].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/startrek Star Trek] dată la OJI 2016 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy Accesează rezolvarea temei 16]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18545</id>
		<title>Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18545"/>
		<updated>2026-02-03T14:47:24Z</updated>

		<summary type="html">&lt;p&gt;Cristian: /* Studiu de caz: problema bile1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/W59ZLyfMZlc&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica Two Pointers (doi pointeri) ==&lt;br /&gt;
&lt;br /&gt;
În informatica de concurs avem tehnici semi-banale cărora le dăm o denumire pentru a putea să le referim ușor într-o discuție. Este și cazul acestei metode. Deși relativ evidentă, ea are un nume 🙂 Pe scurt, &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; folosește doi indici într-un vector ce avansează pe rând, similar cu interclasarea, în căutarea rezultatului. Este o tehnică ce produce în general algoritmi liniari. Ea decurge în mod natural din căutarea binară, atunci când avem un algoritm relativ simplu ce folosește multiple căutări binare, ale căror rezultate sunt fie crescătoare fie descrescătoare.&lt;br /&gt;
&lt;br /&gt;
Să luăm câteva exemple.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Pereche de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de elemente sortate crescător, să se spună câte o perechi există de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[j] == X&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Cum am putea rezolva problema? Pentru fiecare număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; să căutăm un număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j]&amp;lt;/source&amp;gt; cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j &amp;gt; i&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[j] == X&amp;lt;/source&amp;gt;. Dacă fixăm &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; atunci &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j]&amp;lt;/source&amp;gt; poate fi determinat prin căutare binară, deoarece vectorul este ordonat crescător. Vom obține, deci, un algoritm &#039;&#039;O(N log N)&#039;&#039;, unde &#039;&#039;&#039;N&#039;&#039;&#039; este numărul de elemente din vector.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie vectorul: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
v[] = { 20, 25, 30, 35, 40, 50, 55, 65, 75 }&lt;br /&gt;
indici:  0   1   2   3   4   5   6   7   8&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X = 75&amp;lt;/source&amp;gt;. Pentru fiecare &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; vom căuta binar ultimul număr mai mic sau egal cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X - v[i]&amp;lt;/source&amp;gt;. Astfel:&lt;br /&gt;
&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 0&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 20&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X - v[i] = 55&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 6&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 55&amp;lt;/source&amp;gt; soluție. &lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 1&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 25&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;50&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 5&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 50&amp;lt;/source&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 2&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 30&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;45&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 40&amp;lt;/source&amp;gt; (ultima mai mică sau egală cu 45) nu este soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 3&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 35&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;40&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 40&amp;lt;/source&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 40&amp;lt;/source&amp;gt; ar trebui să căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;35&amp;lt;/source&amp;gt; care este mai mică decât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; așa încât ne oprim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce observăm? Că valorile lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; sunt descrescătoare. Este destul de clar că așa și trebuie să fie, nu?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație importantă&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tehnica Two Pointers&#039;&#039;&#039;: atunci când o succesiune de &#039;&#039;&#039;căutări binare&#039;&#039;&#039; într-un vector au ca rezultat o &#039;&#039;&#039;secvență monotonă&#039;&#039;&#039; de numere (crescătoare sau descrescătoare), căutările binare pot fi înlocuite cu &#039;&#039;&#039;căutări liniare&#039;&#039;&#039;. Această înlocuire și noua metodă rezultată poartă denumirea de &#039;&#039;&#039;Two Pointers&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are noul algoritm?&lt;br /&gt;
&lt;br /&gt;
La prima vedere , pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; vom căuta liniar perechea lui, ceea ce ar rezulta într-un algoritm &#039;&#039;O(N)&#039;&#039;. În realitate, indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; se va muta mereu către stânga, drept care el nu va putea parcurge mai mult de &#039;&#039;O(N)&#039;&#039; elemente în toate căutările liniare. Deci, folosind analiza amortizată, deși oricare din căutările liniare poate fi &#039;&#039;O(N)&#039;&#039;, suma tuturor acestor căutări nu poate depăși &#039;&#039;O(N)&#039;&#039;. Drept care metoda Two Pointers duce la un algoritm &#039;&#039;O(N)&#039;&#039;, superior celui cu căutare binară, care era &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Subsecvență de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de elemente nenule, să se spună câte o perechi există de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[i+1] + v[i+2] + ... + v[j] == X&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Și aici avem un algoritm relativ simplu: calculăm vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; de sume parțiale și pentru fiecare element pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; în vector vom căuta binar în vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[i-1] + X&amp;lt;/source&amp;gt;. Dacă găsim această valoare la poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; avem că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[j] - s[i-1] == X&amp;lt;/source&amp;gt; și deci suma elementelor de la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; în vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X&amp;lt;/source&amp;gt;, deci avem o soluție.&lt;br /&gt;
&lt;br /&gt;
Complexitatea acestui algoritm va fi, din nou, &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce observăm, însă? Că rezultatele căutărilor binare (indicii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt;) vor fi în ordine crescătoare, deoarece sumele parțiale sunt strict crescătoare. Putem, deci, folosi &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; pentru a căuta indicii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; prin căutare liniară: pentru fiecare avans al lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; vom avansa indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; până ce suma devine mai mare sau egală cu cea dorită, moment la care testăm egalitatea.&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(N)&#039;&#039;. Mai mult, deoarece ne interesează doar suma subsecvenței de la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; putem renunța la vectorul de sume parțiale și lucra direct pe vectorul original &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;, menținând incremental suma subsecvenței studiate.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Problema NrTri ===&lt;br /&gt;
&lt;br /&gt;
Am discutat într-o lecție trecută problema [https://www.nerdarena.ro/problema/nrtri NrTri]. Ea cere să găsiți într-un vector numărul de tripleți ce pot forma laturile unui triunghi. Aici avem o soluție cu căutare binară: după ce sortăm elementele, pentru fiecare pereche de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; vom căuta binar cel mai mare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k &amp;gt; j&amp;lt;/source&amp;gt; cu proprietatea că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[k]&amp;lt;/source&amp;gt;. Toate elementele între &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (inclusiv &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;) formează un triplet valabil cu perechea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Această soluție are complexitatea &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; log N)&#039;&#039;. Ca și în exemplele anterioare, observăm că atunci când &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; avansează căutarea binară va returna valori &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; crescătoare. Și aici putem aplica tehnica &#039;&#039;Two Pointers&#039;&#039;, avansând liniar indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Rezultă un algoritm O(N2). Observați că tehnica aceasta reduce complexitatea cu acel factor log N. În practică nu este clar că vom putea întotdeauna diferenția între căutarea binară și Two Pointers. Cu toate acestea metoda merită, deoarece codul rezultat este mai simplu și constanta de implementare scade considerabil.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tipul de date struct ==&lt;br /&gt;
&lt;br /&gt;
Până acum am învățat tipuri de date simple (&amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;char&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;long long&amp;lt;/source&amp;gt;, etc) și tipuri de date compuse (ce grupează mai multe tipuri de date simple) anume tablouri, fie ele unidimensionale (vectori), bidimensionale (matrice), sau chiar de dimensiuni mai mari. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; };&lt;br /&gt;
...&lt;br /&gt;
struct nrmare n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
sau, mai scurt (putem declara variabile la definirea tipului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Elementele declarate în interiorul unui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; se numesc membri. Putem accesa membrii unei variabile tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; cu operatorul punct. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
t += n1.cf[i] + n2.cf[i];&lt;br /&gt;
n3.cf[i] = t % 10;&lt;br /&gt;
t /= 10;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; reprezintă un alt mod de a grupa date simple într-o &#039;&#039;structură&#039;&#039;. Aceste date vor fi reprezentate în memorie la rând, una după alta.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Diferențe&#039;&#039;&#039; între tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; și tipul tablou:&lt;br /&gt;
&lt;br /&gt;
* Tipul struct poate grupa date de tipuri diferite, tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;tablou&amp;lt;/source&amp;gt; nu.&lt;br /&gt;
* Tipul struct accesează elementele sale pe bază de denumire, tipul tablou accesează elementele numeric, pe bază de indice (poziție).&lt;br /&gt;
* În consecință tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; grupează un număr relativ mic de date.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Avantaje&#039;&#039;&#039; ale tipului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Gruparea datelor duce la o ordine a codului.&lt;br /&gt;
* Codul devine mai clar și mai ușor de citit, deci o depanare mai ușoară.&lt;br /&gt;
* Folosit corect tipul struct poate duce la un acces mai localizat al memorie (&#039;&#039;cache friendly&#039;&#039;) și, deci, la o scăderea a timpului de execuție.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; probleme cu tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Orice variabilă de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; va fi completată până la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt;. Actual, acest tip este pe patru octeți, ceea ce înseamnă că un struct va ocupa un număr de octeți divizibil cu patru. Dacă vom declara un struct cu un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt; și un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;char&amp;lt;/source&amp;gt; el va ocupa 8 octeți și nu 5!&lt;br /&gt;
&lt;br /&gt;
* Un vector de structuri poate fi înlocuit întotdeauna cu mai mulți vectori, câte unul pentru fiecare membru. Decizia de a folosi struct trebuie luată în funcție de modul de accesare a membrilor în algoritmul nostru. O decizie proastă poate duce la deteriorarea timpului de execuție din motive de cache.&lt;br /&gt;
&lt;br /&gt;
* Combinația între cele două de mai sus poate fi destul de rea: un struct care ocupă mai mult, chiar dacă nu duce la o depășire de memorie, va crește necesarul de memorie ceea ce duce la creșterea timpului de execuție din motive de &#039;&#039;cache&#039;&#039;. Aceasta, combinat cu un acces prost la memorie, poate duce la o creștere semnificativă a timpului de execuție - poate chiar dublarea lui!&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie&#039;&#039;&#039;: folosiți tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; numai dacă știți exact ce faceți. Recomandarea mea este ca deocamdată să nu îl folosiți.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: problema bile1 ===&lt;br /&gt;
&lt;br /&gt;
Implementarea listelor se face în general bine folosind tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;. În mod natural o celulă a listei va conține o informație și o legătură la următoarea celulă (next). În particular am învățat acea structură de date, vectorul de liste, pe care îl foloseam pentru a evita sortarea, memorând pentru fiecare linie a unei matrice acele coloane care ne interesează, unde apar elemente interesante pe acea linie. Să luăm un caz concret.&lt;br /&gt;
&lt;br /&gt;
În problema [https://www.nerdarena.ro/problema/bile1 Bile1] se cere să simulăm căderea unor bile printr-o matrice cu obstacole. Am rezolvat-o memorând obstacolele ca liste de coloane &#039;&#039;agățate&#039;&#039; pe liniile unde apar obstacole. Pentru aceasta am folosit doi vectori. Iată soluția de atunci:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot; line&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                   // capetele de lista&lt;br /&gt;
short col[NOBST + 1], next[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                        // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    col[i] = c;         // asezam coloana c in lista liniei l&lt;br /&gt;
    next[i] = lin[l];   // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = col[pc] - 1; // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = next[pc];   // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Este clar că atunci când accesăm o celulă a listei vom avea nevoie atât de numărul de coloană cât și de next, pentru a avansa la elementul următor. Deci poate ar fi o idee bună să le grupăm, pentru &#039;&#039;cache&#039;&#039;. Partea bună este că atât coloana cât și următorul element sunt mici, deci putem folosi tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, ceea ce înseamnă că o celulă va ocupa două elemente &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, adică patru octeți, fiind aliniată la întreg.&lt;br /&gt;
&lt;br /&gt;
Iată programul modificat să folosească tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                                    // capetele de lista&lt;br /&gt;
struct cell { short col; short next; } obst[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                            // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    // asezam coloana c in lista liniei l&lt;br /&gt;
    // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    obst[i] = (struct cell){ .col = c, .next = lin[l] }; // cream noua celula&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = obst[pc].col - 1;                   // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = obst[pc].next; // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați cum putem atribui o variabilă de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;, folosind denumirile membrilor săi: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
obst[i] = (struct cell){ .col = c, .next = lin[l] };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: numere mari ===&lt;br /&gt;
&lt;br /&gt;
Numerele mari se pot reprezenta în mod natural ca un struct ce conține numărul de cifre și vectorul de cifre, precum am văzut mai sus. Avantajul constă în gruparea datelor și în claritatea codului ce menține numarul de cifre corect - nu mai avem nevoie să returnăm în funcția de înmulțire numărul de cifre al rezultatului, ci îl setăm chiar în numărul mare trimis ca parametru.&lt;br /&gt;
&lt;br /&gt;
Atunci când trimitem un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; mare ca parametru (care ocupă mai mult de un întreg) este bine să-l trimitem ca pointer, deoarece evităm copierea. În acest caz accesul la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; se face un pic anevoios. &lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de funcție de adunare număr mic la număr mare:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += (*n).cf[i];&lt;br /&gt;
    (*n).cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; (*n).ncf )&lt;br /&gt;
    (*n).ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați că avem nevoie de două paranteze, un asterisc și un punct pentru a apela un membru al structurii trimise ca pointer. De aceea limbajul C ne oferă o alternativă mai simplă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
    // forme echivalente:&lt;br /&gt;
    (*n).cf[i];&lt;br /&gt;
&lt;br /&gt;
    // este totuna cu&lt;br /&gt;
    n-&amp;gt;cf[i];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Precum vedeți săgețica este echivalentul punctului atunci când variabila este de tip pointer la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de implementare de numere mari:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXNCF 1000&lt;br /&gt;
&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mic a din numarul mare n&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (a &amp;lt;= n)&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void scadeNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  a = -a;&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;lt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    if ( n-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n-&amp;gt;cf[i] += 10;&lt;br /&gt;
      a--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n-&amp;gt;cf[n-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mare n2 la numarul mare n1&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void adunaNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, max, t;&lt;br /&gt;
&lt;br /&gt;
  max = n1-&amp;gt;ncf &amp;lt; n2-&amp;gt;ncf ? n2-&amp;gt;ncf : n1-&amp;gt;ncf;&lt;br /&gt;
  t = 0;&lt;br /&gt;
  for ( i = 0; i &amp;lt; max; i++ ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] + n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
  }&lt;br /&gt;
  if ( t &amp;gt; 0 ) {&lt;br /&gt;
    n1-&amp;gt;cf[i] = t;&lt;br /&gt;
    max++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  n1-&amp;gt;ncf = max;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mare n2 din numarul mare n1&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (n2 &amp;lt;= n1)&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void scadeNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n2-&amp;gt;ncf || t &amp;lt; 0 ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] - n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    if ( n1-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n1-&amp;gt;cf[i] += 10;&lt;br /&gt;
      t--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n1-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n1-&amp;gt;cf[n1-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n1-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// inmulteste numarul mic a cu numarul mare n cu rezultatul in n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void inmultesteNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
  long long t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n-&amp;gt;ncf || t &amp;gt; 0 ) {&lt;br /&gt;
    t = t + a * (long long)n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// tipareste un numar mare n&lt;br /&gt;
void printN( struct nrmare *n ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( n-&amp;gt;ncf == 0 )&lt;br /&gt;
    fputc( &#039;0&#039;, stdout );&lt;br /&gt;
  else&lt;br /&gt;
    for ( i = n-&amp;gt;ncf - 1; i &amp;gt;= 0; i-- )&lt;br /&gt;
      fputc( &#039;0&#039; + n-&amp;gt;cf[i], stdout );&lt;br /&gt;
  fputc( &#039;\n&#039;, stdout );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  // n1, n2, n3 declarate global, initial zero&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 75464: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;75464 - 75464: &amp;quot; );&lt;br /&gt;
  scadeNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 834205: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 834205 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  adunaNn( &amp;amp;n2, 1024586 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586: &amp;quot; );&lt;br /&gt;
  adunaNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586 - 1024586: &amp;quot; );&lt;br /&gt;
  scadeNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 * 2043984094: &amp;quot; );&lt;br /&gt;
  inmultesteNn( &amp;amp;n1, 2043984094 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Metoda Greedy ==&lt;br /&gt;
&lt;br /&gt;
Uneori trebuie să rezolvăm probleme ce necesită să găsim o soluție minimă sau maximă. Una din cele mai simple și evidente încercări de rezolvare este ca, la fiecare pas, să luăm decizia optimă pe moment - decizie &#039;&#039;lacomă&#039;&#039; - cel mai bun lucru la momentul actual.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dat un șir de numere pozitive și negative să se calculeze suma maximă ce se poate forma cu exact &#039;&#039;K&#039;&#039; numere.&lt;br /&gt;
&lt;br /&gt;
Soluție greedy: la fiecare pas vom selecta maximul dintre numerele rămase. Vom porni cu maximul. Apoi cu al doilea maxim și așa mai departe. La fiecare pas &#039;&#039;ne lăcomim&#039;&#039; la cel mai mare număr posibil.&lt;br /&gt;
&lt;br /&gt;
Este, sper, destul de clar că în acest caz obținem soluția optimă. Însă nu întotdeauna este așa, precum vom vedea.&lt;br /&gt;
&lt;br /&gt;
Să vedem câteva exemple de algoritmi greedy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Plată cu număr minim de bancnote puteri ale lui 2 ===&lt;br /&gt;
&lt;br /&gt;
Dispunem de bancnote puteri ale lui 2. Pentru fiecare putere a lui 2 vom avea un număr de bancnote disponibile, posibil niciuna. Dat &#039;&#039;&#039;X&#039;&#039;&#039;, dorim să achităm suma &#039;&#039;&#039;X&#039;&#039;&#039; cu un număr minim de bancnote posibile. Cum procedăm?&lt;br /&gt;
&lt;br /&gt;
Vom proceda greedy: selectăm bancnotele de valoare maximă ce încap în &#039;&#039;&#039;X&#039;&#039;&#039;. Fie epuizăm numărul de bancnote de acea putere și trecem mai departe, fie ne rămân bancnote dar suma de plată devine mai mică decât 2 la acea putere. Procedăm în continuare la fel.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dispunem de următoarele bancnote: 3 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;, 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;, 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, 5 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;, 6 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; și ne dorim să achităm suma 53. Procedăm astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  Pas 1: selectăm 1 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;. Suma rămasă: 21 = 53 - 32&lt;br /&gt;
  Pas 2: selectăm 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;. Suma rămasă: 13 = 21 - 8&lt;br /&gt;
  Pas 3: selectăm 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;. Suma rămasă: 5 = 13 - 8&lt;br /&gt;
  Pas 4: selectăm 2 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;. Suma rămasă: 1 = 5 - 4&lt;br /&gt;
  Pas 5: selectăm 1 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt;. Suma rămasă: 0 = 1 - 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Astfel numărul de bancnote este 7, iar numărul este plătit astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
53 = 32 + 8 + 4 + 4 + 2 + 2 + 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sortarea prin selecție ===&lt;br /&gt;
&lt;br /&gt;
Ne putem gândi la sortarea prin selecție ca având un pas de bază greedy: la fiecare trecere prin vector selectăm în mod greedy maximul și apoi îl trecem la coadă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul minim de intervale care acoperă un interval ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o mulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; ce acoperă un alt interval mai mare să se găsească o submulţime minimă a acestei mulţimi care conţine intervale ce acoperă același interval original.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Intervalul acoperit este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;[0, 18]&amp;lt;/source&amp;gt;. Ne dorim să păstrăm cât mai puține intervale care să acopere același interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idee de soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
O idee naturală este să sortăm intervalele după punctul de început, iar apoi să le luăm în ordine, unul câte unul. Având un interval curent selectat, am putea să selectăm intervalul care începe cât mai târziu dar al cărui interval este în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
Pe exemplul de mai sus observăm că nu funcționează, deoarece am selecta intervalele 4, 1 și apoi nu am mai avea ce să selectăm în continuare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom parcurge intervalele în ordinea capătului de început, ca mai devreme. Dar vom selecta următorul interval pe acela care se &#039;&#039;&#039;închide ultimul&#039;&#039;&#039; dintre cele care au capătul stânga în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
În cazul nostru vom selecta intervalele 4, 5 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul maxim de intervale disjuncte ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o submulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; să se găsească o submulţime maximală a acestei mulţimi care conţine doar intervale disjuncte (neintersectante).&lt;br /&gt;
&lt;br /&gt;
Această problemă este totuna cu numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele (de ce?).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idei de soluţie &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Această problemă apare destul de des la olimpiade, deghizată într-o formă sau alta. Deşi pare o problemă grea, în realitate ea se rezolvă uşor. O primă idee ar fi să încercăm să ordonăm intervalele după punctul de început, apoi să le luăm în ordine, unul câte unul. Pentru fiecare interval ne întrebăm dacă îl intersectează pe cel anterior. Dacă da, îl aruncăm şi mergem mai departe. Dacă nu, îl adăugăm la submulţime şi îl reţinem ca interval ultim. Această soluţie nu funcţionează şi e destul de evident de ce: am putea avea un prim interval foarte lung, care acoperă toate celelalte intervale.&lt;br /&gt;
&lt;br /&gt;
Cu toate acestea rezolvarea nu e total greşită. În fapt, ea aproape funcţionează! Are nevoie doar de o modificare ce se impune, analizînd contraexemplul anterior: de ce nu este bun acel prim interval foarte lung? Deoarece el se termină după celelalte. Şi atunci ne putem întreba, în mod natural, ce s-ar întâmpla dacă am alege intervalul care se închide primul, nu care începe primul? Desigur că acest algoritm funcţionează. Vă las vouă să vă luptaţi cu demonstraţia, care decurge cam aşa: să presupunem că soluţia generată de acest algoritm nu e optimă. Înseamnă că există o altă soluţie cu mai multe intervale. Acea soluţie trebuie să difere undeva faţă de cea găsită de noi. Mergeţi pe această cale şi trebuie să ajungeţi la o contradicţie cum că soluţia optimă nu poate avea mai multe segmente.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Atunci vom selecta, în ordine, intervalele 3 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Soluţie ====&lt;br /&gt;
&lt;br /&gt;
Care este algoritmul, în mare?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
# Iniţial mulţimea M este vidă.&lt;br /&gt;
# Ordonează crescător intervalele după punctul de închidere.&lt;br /&gt;
# Consideră ultima închidere (care nu există încă) cu valoarea minus infinit.&lt;br /&gt;
# Pentru fiecare interval, în ordine:&lt;br /&gt;
## Dacă el conţine ultima închidere&lt;br /&gt;
### Ignoră-l.&lt;br /&gt;
## Altfel&lt;br /&gt;
### Selectează intervalul curent şi adaugă-l la mulţimea M.&lt;br /&gt;
### Setează ultima închidere pe închiderea intervalului selectat&lt;br /&gt;
# Afişează mulţimea M.&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ce se întâmplă dacă avem mai multe intervale care se termină în acelaşi punct? Atunci putem reţine intervalul cel mai mic. De ce? Deoarece este cel mai convenabil. Să presupunem că soluţia maximală conţinea un alt interval care se termina în acel punct. Atunci putem înlocui acel interval cu intervalul cel mai mic, obţinînd o altă soluţie maximală.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Considerente de implementare ====&lt;br /&gt;
&lt;br /&gt;
Implementarea directă: memorăm intervalele ca perechi, prin capetele lor. Sortăm perechile după capătul din dreapta. Apoi facem o parcurgere a perechilor. Această implementare necesită &#039;&#039;O(N log N)&#039;&#039; timp și &#039;&#039;O(N)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
Implementare relaxată: atunci când coordonatele sunt suficient de mici astfel încât să putem folosi un vector care să memoreze un întreg pentru fiecare coordonată, putem încerca o abordare mai simplă: nu memorăm intervalele. Ci, pentru fiecare interval &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;[ai, bi]&amp;lt;/source&amp;gt;, vom seta &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;început[bi] = ai&amp;lt;/source&amp;gt;. Cu alte cuvinte vom memora capetele stânga la poziția capetelor dreapta. Apoi parcurgem vectorul în ordine, acoperind segmentul de coordonate care acoperă toate intervalele, testând dacă elementul de la poziția curentă în vector este mai mic decât ultima închidere memorată. Dacă nu, atunci selectăm intervalul și mutăm ultima închidere la poziția curentă în vector. Trebuie să avem grijă la următoarele:&lt;br /&gt;
&lt;br /&gt;
* Să iniţializăm vectorul de coordonate cu o valoare distinctă de capete de interval. Matematic am putea să le iniţializăm cu minus infinit, astfel încât să nu fie selectate niciodată.&lt;br /&gt;
* Atunci când &#039;&#039;aşezăm&#039;&#039; intervalele pe vector să avem grijă ca dacă găsim deja o valoare în vector să o înlocuim numai dacă cea curentă este mai mare (intervalul este mai mic).&lt;br /&gt;
&lt;br /&gt;
Această soluţie necesită &#039;&#039;O(N + CMAX)&#039;&#039; timp şi memorie, &#039;&#039;&#039;CMAX&#039;&#039;&#039; fiind coordonata maximă. De aici rezultă când o putem folosi: atunci când &#039;&#039;&#039;CMAX&#039;&#039;&#039; este suficient de mic.&lt;br /&gt;
&lt;br /&gt;
==== Generalizare ====&lt;br /&gt;
&lt;br /&gt;
Putem generaliza această problemă: să se calculeze mulţimea maximală de dreptunghiuri neintersectante. Sau de paralelipipede. Sau de paralelipipede n-dimensionale. Cum se rezolvă aceste probleme? Din nefericire aceste probleme fac parte dintr-o clasă de probleme numite &#039;&#039;NP-hard&#039;&#039;, pentru care nu se cunoaşte o soluţie bună.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu când greedy nu funcționează: prolema rucsacului ===&lt;br /&gt;
&lt;br /&gt;
Problema rucsacului cere să &#039;&#039;umplem&#039;&#039; cât mai bine un rucsac care poate duce o greutate maximă, dispunând de obiecte de diverse greutăți.&lt;br /&gt;
&lt;br /&gt;
Exemplu: Vreau să umplu cât mai bine un rucsac de 100 și am greutăți 50, 30, 25 și 25.&lt;br /&gt;
&lt;br /&gt;
Prin metoda greedy vom alege greutățile cele mai mari, în ordine: 50 și 30, după care nu mai putem adăuga nimic. Astfel vom obține greutatea totală 80. Dacă am alege însă greutățile 50, 25 și 25 am putea umple complet rucsacul.&lt;br /&gt;
&lt;br /&gt;
De remarcat totuși că chiar și atunci când metoda greedy nu calculează optimul, un algoritm greedy bine gândit va da o soluție bună, aproape de optim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 16 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/politic Politic] dată la ONI 2007 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumax SumaX] dată la Shumen Juniori 2015&lt;br /&gt;
* [https://www.nerdarena.ro/problema/acoperire Acoperire]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/char Char] dată la ONI 2010 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Pentru doritorii să încerce și soluția problemei numărul minim de numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele puteți rezolva problema [https://www.nerdarena.ro/problema/baloane Baloane].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/startrek Star Trek] dată la OJI 2016 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy Accesează rezolvarea temei 16]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18544</id>
		<title>Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy&amp;diff=18544"/>
		<updated>2026-02-03T14:47:03Z</updated>

		<summary type="html">&lt;p&gt;Cristian: /* Studiu de caz: problema bile1 */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Înregistrare video lecție ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;youtube height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://youtu.be/W59ZLyfMZlc&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnica Two Pointers (doi pointeri) ==&lt;br /&gt;
&lt;br /&gt;
În informatica de concurs avem tehnici semi-banale cărora le dăm o denumire pentru a putea să le referim ușor într-o discuție. Este și cazul acestei metode. Deși relativ evidentă, ea are un nume 🙂 Pe scurt, &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; folosește doi indici într-un vector ce avansează pe rând, similar cu interclasarea, în căutarea rezultatului. Este o tehnică ce produce în general algoritmi liniari. Ea decurge în mod natural din căutarea binară, atunci când avem un algoritm relativ simplu ce folosește multiple căutări binare, ale căror rezultate sunt fie crescătoare fie descrescătoare.&lt;br /&gt;
&lt;br /&gt;
Să luăm câteva exemple.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Pereche de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de elemente sortate crescător, să se spună câte o perechi există de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[j] == X&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Cum am putea rezolva problema? Pentru fiecare număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; să căutăm un număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j]&amp;lt;/source&amp;gt; cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j &amp;gt; i&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[j] == X&amp;lt;/source&amp;gt;. Dacă fixăm &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; atunci &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j]&amp;lt;/source&amp;gt; poate fi determinat prin căutare binară, deoarece vectorul este ordonat crescător. Vom obține, deci, un algoritm &#039;&#039;O(N log N)&#039;&#039;, unde &#039;&#039;&#039;N&#039;&#039;&#039; este numărul de elemente din vector.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Fie vectorul: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
v[] = { 20, 25, 30, 35, 40, 50, 55, 65, 75 }&lt;br /&gt;
indici:  0   1   2   3   4   5   6   7   8&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X = 75&amp;lt;/source&amp;gt;. Pentru fiecare &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; vom căuta binar ultimul număr mai mic sau egal cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X - v[i]&amp;lt;/source&amp;gt;. Astfel:&lt;br /&gt;
&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 0&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 20&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X - v[i] = 55&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 6&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 55&amp;lt;/source&amp;gt; soluție. &lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 1&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 25&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;50&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 5&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 50&amp;lt;/source&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 2&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 30&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;45&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 40&amp;lt;/source&amp;gt; (ultima mai mică sau egală cu 45) nu este soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 3&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 35&amp;lt;/source&amp;gt; căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;40&amp;lt;/source&amp;gt; și găsim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[j] = 40&amp;lt;/source&amp;gt; soluție.&lt;br /&gt;
* Pentru &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i = 4&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] = 40&amp;lt;/source&amp;gt; ar trebui să căutăm valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;35&amp;lt;/source&amp;gt; care este mai mică decât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; așa încât ne oprim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Ce observăm? Că valorile lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; sunt descrescătoare. Este destul de clar că așa și trebuie să fie, nu?&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Observație importantă&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tehnica Two Pointers&#039;&#039;&#039;: atunci când o succesiune de &#039;&#039;&#039;căutări binare&#039;&#039;&#039; într-un vector au ca rezultat o &#039;&#039;&#039;secvență monotonă&#039;&#039;&#039; de numere (crescătoare sau descrescătoare), căutările binare pot fi înlocuite cu &#039;&#039;&#039;căutări liniare&#039;&#039;&#039;. Această înlocuire și noua metodă rezultată poartă denumirea de &#039;&#039;&#039;Two Pointers&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are noul algoritm?&lt;br /&gt;
&lt;br /&gt;
La prima vedere , pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i]&amp;lt;/source&amp;gt; vom căuta liniar perechea lui, ceea ce ar rezulta într-un algoritm &#039;&#039;O(N)&#039;&#039;. În realitate, indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; se va muta mereu către stânga, drept care el nu va putea parcurge mai mult de &#039;&#039;O(N)&#039;&#039; elemente în toate căutările liniare. Deci, folosind analiza amortizată, deși oricare din căutările liniare poate fi &#039;&#039;O(N)&#039;&#039;, suma tuturor acestor căutări nu poate depăși &#039;&#039;O(N)&#039;&#039;. Drept care metoda Two Pointers duce la un algoritm &#039;&#039;O(N)&#039;&#039;, superior celui cu căutare binară, care era &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=== Subsecvență de sumă X ===&lt;br /&gt;
&lt;br /&gt;
Dat un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de elemente nenule, să se spună câte o perechi există de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[i] + v[i+1] + v[i+2] + ... + v[j] == X&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Și aici avem un algoritm relativ simplu: calculăm vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; de sume parțiale și pentru fiecare element pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; în vector vom căuta binar în vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[i-1] + X&amp;lt;/source&amp;gt;. Dacă găsim această valoare la poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; avem că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[j] - s[i-1] == X&amp;lt;/source&amp;gt; și deci suma elementelor de la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; în vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;X&amp;lt;/source&amp;gt;, deci avem o soluție.&lt;br /&gt;
&lt;br /&gt;
Complexitatea acestui algoritm va fi, din nou, &#039;&#039;O(N log N)&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Ce observăm, însă? Că rezultatele căutărilor binare (indicii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt;) vor fi în ordine crescătoare, deoarece sumele parțiale sunt strict crescătoare. Putem, deci, folosi &#039;&#039;&#039;Two Pointers&#039;&#039;&#039; pentru a căuta indicii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; prin căutare liniară: pentru fiecare avans al lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; vom avansa indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; până ce suma devine mai mare sau egală cu cea dorită, moment la care testăm egalitatea.&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate &#039;&#039;O(N)&#039;&#039;. Mai mult, deoarece ne interesează doar suma subsecvenței de la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; putem renunța la vectorul de sume parțiale și lucra direct pe vectorul original &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;, menținând incremental suma subsecvenței studiate.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Problema NrTri ===&lt;br /&gt;
&lt;br /&gt;
Am discutat într-o lecție trecută problema [https://www.nerdarena.ro/problema/nrtri NrTri]. Ea cere să găsiți într-un vector numărul de tripleți ce pot forma laturile unui triunghi. Aici avem o soluție cu căutare binară: după ce sortăm elementele, pentru fiecare pereche de indici &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt; vom căuta binar cel mai mare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k &amp;gt; j&amp;lt;/source&amp;gt; cu proprietatea că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[k]&amp;lt;/source&amp;gt;. Toate elementele între &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (inclusiv &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;) formează un triplet valabil cu perechea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(i, j)&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Această soluție are complexitatea &#039;&#039;O(N&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt; log N)&#039;&#039;. Ca și în exemplele anterioare, observăm că atunci când &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; avansează căutarea binară va returna valori &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; crescătoare. Și aici putem aplica tehnica &#039;&#039;Two Pointers&#039;&#039;, avansând liniar indicele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Rezultă un algoritm O(N2). Observați că tehnica aceasta reduce complexitatea cu acel factor log N. În practică nu este clar că vom putea întotdeauna diferenția între căutarea binară și Two Pointers. Cu toate acestea metoda merită, deoarece codul rezultat este mai simplu și constanta de implementare scade considerabil.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tipul de date struct ==&lt;br /&gt;
&lt;br /&gt;
Până acum am învățat tipuri de date simple (&amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;char&amp;lt;/source&amp;gt;, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;long long&amp;lt;/source&amp;gt;, etc) și tipuri de date compuse (ce grupează mai multe tipuri de date simple) anume tablouri, fie ele unidimensionale (vectori), bidimensionale (matrice), sau chiar de dimensiuni mai mari. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; };&lt;br /&gt;
...&lt;br /&gt;
struct nrmare n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
sau, mai scurt (putem declara variabile la definirea tipului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Elementele declarate în interiorul unui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; se numesc membri. Putem accesa membrii unei variabile tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; cu operatorul punct. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
t += n1.cf[i] + n2.cf[i];&lt;br /&gt;
n3.cf[i] = t % 10;&lt;br /&gt;
t /= 10;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; reprezintă un alt mod de a grupa date simple într-o &#039;&#039;structură&#039;&#039;. Aceste date vor fi reprezentate în memorie la rând, una după alta.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Diferențe&#039;&#039;&#039; între tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; și tipul tablou:&lt;br /&gt;
&lt;br /&gt;
* Tipul struct poate grupa date de tipuri diferite, tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;tablou&amp;lt;/source&amp;gt; nu.&lt;br /&gt;
* Tipul struct accesează elementele sale pe bază de denumire, tipul tablou accesează elementele numeric, pe bază de indice (poziție).&lt;br /&gt;
* În consecință tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; grupează un număr relativ mic de date.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Avantaje&#039;&#039;&#039; ale tipului &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Gruparea datelor duce la o ordine a codului.&lt;br /&gt;
* Codul devine mai clar și mai ușor de citit, deci o depanare mai ușoară.&lt;br /&gt;
* Folosit corect tipul struct poate duce la un acces mai localizat al memorie (&#039;&#039;cache friendly&#039;&#039;) și, deci, la o scăderea a timpului de execuție.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Atenție:&#039;&#039;&#039; probleme cu tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* Orice variabilă de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; va fi completată până la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt;. Actual, acest tip este pe patru octeți, ceea ce înseamnă că un struct va ocupa un număr de octeți divizibil cu patru. Dacă vom declara un struct cu un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&amp;gt; și un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;char&amp;lt;/source&amp;gt; el va ocupa 8 octeți și nu 5!&lt;br /&gt;
&lt;br /&gt;
* Un vector de structuri poate fi înlocuit întotdeauna cu mai mulți vectori, câte unul pentru fiecare membru. Decizia de a folosi struct trebuie luată în funcție de modul de accesare a membrilor în algoritmul nostru. O decizie proastă poate duce la deteriorarea timpului de execuție din motive de cache.&lt;br /&gt;
&lt;br /&gt;
* Combinația între cele două de mai sus poate fi destul de rea: un struct care ocupă mai mult, chiar dacă nu duce la o depășire de memorie, va crește necesarul de memorie ceea ce duce la creșterea timpului de execuție din motive de &#039;&#039;cache&#039;&#039;. Aceasta, combinat cu un acces prost la memorie, poate duce la o creștere semnificativă a timpului de execuție - poate chiar dublarea lui!&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Concluzie&#039;&#039;&#039;: folosiți tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; numai dacă știți exact ce faceți. Recomandarea mea este ca deocamdată să nu îl folosiți.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: problema bile1 ===&lt;br /&gt;
&lt;br /&gt;
Implementarea listelor se face în general bine folosind tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;. În mod natural o celulă a listei va conține o informație și o legătură la următoarea celulă (next). În particular am învățat acea structură de date, vectorul de liste, pe care îl foloseam pentru a evita sortarea, memorând pentru fiecare linie a unei matrice acele coloane care ne interesează, unde apar elemente interesante pe acea linie. Să luăm un caz concret.&lt;br /&gt;
&lt;br /&gt;
În problema [https://www.nerdarena.ro/problema/bile1 Bile1] se cere să simulăm căderea unor bile printr-o matrice cu obstacole. Am rezolvat-o memorând obstacolele ca liste de coloane &#039;&#039;agățate&#039;&#039; pe liniile unde apar obstacole. Pentru aceasta am folosit doi vectori. Iată soluția de atunci:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                   // capetele de lista&lt;br /&gt;
short col[NOBST + 1], next[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                        // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    col[i] = c;         // asezam coloana c in lista liniei l&lt;br /&gt;
    next[i] = lin[l];   // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = col[pc] - 1; // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = next[pc];   // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Este clar că atunci când accesăm o celulă a listei vom avea nevoie atât de numărul de coloană cât și de next, pentru a avansa la elementul următor. Deci poate ar fi o idee bună să le grupăm, pentru &#039;&#039;cache&#039;&#039;. Partea bună este că atât coloana cât și următorul element sunt mici, deci putem folosi tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, ceea ce înseamnă că o celulă va ocupa două elemente &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, adică patru octeți, fiind aliniată la întreg.&lt;br /&gt;
&lt;br /&gt;
Iată programul modificat să folosească tipul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXN 2000&lt;br /&gt;
#define NOBST 10000&lt;br /&gt;
&lt;br /&gt;
short lin[MAXN + 1];                                    // capetele de lista&lt;br /&gt;
struct cell { short col; short next; } obst[NOBST + 1]; // celulele listelor&lt;br /&gt;
int bile[MAXN];                            // bilele ce vor fi redistribuite&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int m, n, p, i, pc, l, c;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bile1.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;m, &amp;amp;n, &amp;amp;p );&lt;br /&gt;
  for ( i = 1; i &amp;lt;= p; i++ ) {      // citire obstacole&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;l, &amp;amp;c );&lt;br /&gt;
    // asezam coloana c in lista liniei l&lt;br /&gt;
    // coloana noua vine la inceputul listei liniei l&lt;br /&gt;
    obst[i] = (struct cell){ .col = c, .next = lin[l] }; // cream noua celula&lt;br /&gt;
    lin[l] = i;&lt;br /&gt;
  }&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )        // citire bile pe prima linie&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;bile[i] );&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  // parcurgem liniile in ordine si cautam coloane cu obstacole&lt;br /&gt;
  for ( l = 1; l &amp;lt;= m; l++ ) {&lt;br /&gt;
    pc = lin[l];&lt;br /&gt;
    while ( pc &amp;gt; 0 ) { // cita vreme mai avem elemente in lista&lt;br /&gt;
      c = obst[pc].col - 1;                   // avem obstacol la coloana c&lt;br /&gt;
      bile[c-1] += bile[c] / 2 + bile[c] % 2; // n/2 + 1 in stinga&lt;br /&gt;
      bile[c+1] += bile[c] / 2;               // n/2 in dreapta&lt;br /&gt;
      bile[c] = 0;&lt;br /&gt;
      pc = obst[pc].next; // trecem la urmatorul element din lista&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bile1.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  for ( i = 0; i &amp;lt; n; i++ )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, bile[i] );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați cum putem atribui o variabilă de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;, folosind denumirile membrilor săi: &lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;C&amp;quot; line&amp;gt;&lt;br /&gt;
obst[i] = (struct cell){ .col = c, .next = lin[l] };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Studiu de caz: numere mari ===&lt;br /&gt;
&lt;br /&gt;
Numerele mari se pot reprezenta în mod natural ca un struct ce conține numărul de cifre și vectorul de cifre, precum am văzut mai sus. Avantajul constă în gruparea datelor și în claritatea codului ce menține numarul de cifre corect - nu mai avem nevoie să returnăm în funcția de înmulțire numărul de cifre al rezultatului, ci îl setăm chiar în numărul mare trimis ca parametru.&lt;br /&gt;
&lt;br /&gt;
Atunci când trimitem un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; mare ca parametru (care ocupă mai mult de un întreg) este bine să-l trimitem ca pointer, deoarece evităm copierea. În acest caz accesul la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt; se face un pic anevoios. &lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de funcție de adunare număr mic la număr mare:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += (*n).cf[i];&lt;br /&gt;
    (*n).cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; (*n).ncf )&lt;br /&gt;
    (*n).ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Remarcați că avem nevoie de două paranteze, un asterisc și un punct pentru a apela un membru al structurii trimise ca pointer. De aceea limbajul C ne oferă o alternativă mai simplă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
    // forme echivalente:&lt;br /&gt;
    (*n).cf[i];&lt;br /&gt;
&lt;br /&gt;
    // este totuna cu&lt;br /&gt;
    n-&amp;gt;cf[i];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Precum vedeți săgețica este echivalentul punctului atunci când variabila este de tip pointer la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;struct&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu de implementare de numere mari:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define MAXNCF 1000&lt;br /&gt;
&lt;br /&gt;
struct nrmare { int ncf; char cf[MAXNCF]; } n1, n2, n3;&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mic a la numarul mare n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void adunaNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;gt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mic a din numarul mare n&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (a &amp;lt;= n)&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void scadeNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  a = -a;&lt;br /&gt;
  i = 0;&lt;br /&gt;
  while ( a &amp;lt; 0 ) {&lt;br /&gt;
    a += n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = a % 10;&lt;br /&gt;
    a /= 10;&lt;br /&gt;
    if ( n-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n-&amp;gt;cf[i] += 10;&lt;br /&gt;
      a--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n-&amp;gt;cf[n-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// aduna numarul mare n2 la numarul mare n1&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void adunaNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, max, t;&lt;br /&gt;
&lt;br /&gt;
  max = n1-&amp;gt;ncf &amp;lt; n2-&amp;gt;ncf ? n2-&amp;gt;ncf : n1-&amp;gt;ncf;&lt;br /&gt;
  t = 0;&lt;br /&gt;
  for ( i = 0; i &amp;lt; max; i++ ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] + n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
  }&lt;br /&gt;
  if ( t &amp;gt; 0 ) {&lt;br /&gt;
    n1-&amp;gt;cf[i] = t;&lt;br /&gt;
    max++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  n1-&amp;gt;ncf = max;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// scade numarul mare n2 din numarul mare n1&lt;br /&gt;
// considera ca nu se ajunge la un numar mare negativ (n2 &amp;lt;= n1)&lt;br /&gt;
// n1, n2 transmise prin referinta pentru a nu fi copiate + le modificam&lt;br /&gt;
void scadeNN( struct nrmare *n1, struct nrmare *n2 ) {&lt;br /&gt;
  int i, t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n2-&amp;gt;ncf || t &amp;lt; 0 ) {&lt;br /&gt;
    t = t + n1-&amp;gt;cf[i] - n2-&amp;gt;cf[i];&lt;br /&gt;
    n1-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    if ( n1-&amp;gt;cf[i] &amp;lt; 0 ) {&lt;br /&gt;
      n1-&amp;gt;cf[i] += 10;&lt;br /&gt;
      t--;&lt;br /&gt;
    }&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  while ( n1-&amp;gt;ncf &amp;gt; 0 &amp;amp;&amp;amp; n1-&amp;gt;cf[n1-&amp;gt;ncf-1] == 0 )&lt;br /&gt;
    n1-&amp;gt;ncf--;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// inmulteste numarul mic a cu numarul mare n cu rezultatul in n&lt;br /&gt;
// n transmis prin referinta pentru a nu fi copiat + il modificam&lt;br /&gt;
void inmultesteNn( struct nrmare *n, int a ) {&lt;br /&gt;
  int i;&lt;br /&gt;
  long long t;&lt;br /&gt;
&lt;br /&gt;
  t = i = 0;&lt;br /&gt;
  while ( i &amp;lt; n-&amp;gt;ncf || t &amp;gt; 0 ) {&lt;br /&gt;
    t = t + a * (long long)n-&amp;gt;cf[i];&lt;br /&gt;
    n-&amp;gt;cf[i] = t % 10;&lt;br /&gt;
    t /= 10;&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  if ( i &amp;gt; n-&amp;gt;ncf )&lt;br /&gt;
    n-&amp;gt;ncf = i;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// tipareste un numar mare n&lt;br /&gt;
void printN( struct nrmare *n ) {&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  if ( n-&amp;gt;ncf == 0 )&lt;br /&gt;
    fputc( &#039;0&#039;, stdout );&lt;br /&gt;
  else&lt;br /&gt;
    for ( i = n-&amp;gt;ncf - 1; i &amp;gt;= 0; i-- )&lt;br /&gt;
      fputc( &#039;0&#039; + n-&amp;gt;cf[i], stdout );&lt;br /&gt;
  fputc( &#039;\n&#039;, stdout );&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main () {&lt;br /&gt;
  // n1, n2, n3 declarate global, initial zero&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 75464: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;75464 - 75464: &amp;quot; );&lt;br /&gt;
  scadeNn( &amp;amp;n1, 75464 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;0 + 834205: &amp;quot; );&lt;br /&gt;
  adunaNn( &amp;amp;n1, 834205 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  adunaNn( &amp;amp;n2, 1024586 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586: &amp;quot; );&lt;br /&gt;
  adunaNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 + 1024586 - 1024586: &amp;quot; );&lt;br /&gt;
  scadeNN( &amp;amp;n1, &amp;amp;n2 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  printf( &amp;quot;834205 * 2043984094: &amp;quot; );&lt;br /&gt;
  inmultesteNn( &amp;amp;n1, 2043984094 );&lt;br /&gt;
  printN( &amp;amp;n1 );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Metoda Greedy ==&lt;br /&gt;
&lt;br /&gt;
Uneori trebuie să rezolvăm probleme ce necesită să găsim o soluție minimă sau maximă. Una din cele mai simple și evidente încercări de rezolvare este ca, la fiecare pas, să luăm decizia optimă pe moment - decizie &#039;&#039;lacomă&#039;&#039; - cel mai bun lucru la momentul actual.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dat un șir de numere pozitive și negative să se calculeze suma maximă ce se poate forma cu exact &#039;&#039;K&#039;&#039; numere.&lt;br /&gt;
&lt;br /&gt;
Soluție greedy: la fiecare pas vom selecta maximul dintre numerele rămase. Vom porni cu maximul. Apoi cu al doilea maxim și așa mai departe. La fiecare pas &#039;&#039;ne lăcomim&#039;&#039; la cel mai mare număr posibil.&lt;br /&gt;
&lt;br /&gt;
Este, sper, destul de clar că în acest caz obținem soluția optimă. Însă nu întotdeauna este așa, precum vom vedea.&lt;br /&gt;
&lt;br /&gt;
Să vedem câteva exemple de algoritmi greedy.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Plată cu număr minim de bancnote puteri ale lui 2 ===&lt;br /&gt;
&lt;br /&gt;
Dispunem de bancnote puteri ale lui 2. Pentru fiecare putere a lui 2 vom avea un număr de bancnote disponibile, posibil niciuna. Dat &#039;&#039;&#039;X&#039;&#039;&#039;, dorim să achităm suma &#039;&#039;&#039;X&#039;&#039;&#039; cu un număr minim de bancnote posibile. Cum procedăm?&lt;br /&gt;
&lt;br /&gt;
Vom proceda greedy: selectăm bancnotele de valoare maximă ce încap în &#039;&#039;&#039;X&#039;&#039;&#039;. Fie epuizăm numărul de bancnote de acea putere și trecem mai departe, fie ne rămân bancnote dar suma de plată devine mai mică decât 2 la acea putere. Procedăm în continuare la fel.&lt;br /&gt;
&lt;br /&gt;
Exemplu: dispunem de următoarele bancnote: 3 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;, 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;, 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;, 5 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;, 6 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt; și ne dorim să achităm suma 53. Procedăm astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  Pas 1: selectăm 1 × 2&amp;lt;sup&amp;gt;5&amp;lt;/sup&amp;gt;. Suma rămasă: 21 = 53 - 32&lt;br /&gt;
  Pas 2: selectăm 1 × 2&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;. Suma rămasă: 13 = 21 - 8&lt;br /&gt;
  Pas 3: selectăm 2 × 2&amp;lt;sup&amp;gt;2&amp;lt;/sup&amp;gt;. Suma rămasă: 5 = 13 - 8&lt;br /&gt;
  Pas 4: selectăm 2 × 2&amp;lt;sup&amp;gt;1&amp;lt;/sup&amp;gt;. Suma rămasă: 1 = 5 - 4&lt;br /&gt;
  Pas 5: selectăm 1 × 2&amp;lt;sup&amp;gt;0&amp;lt;/sup&amp;gt;. Suma rămasă: 0 = 1 - 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Astfel numărul de bancnote este 7, iar numărul este plătit astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
53 = 32 + 8 + 4 + 4 + 2 + 2 + 1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sortarea prin selecție ===&lt;br /&gt;
&lt;br /&gt;
Ne putem gândi la sortarea prin selecție ca având un pas de bază greedy: la fiecare trecere prin vector selectăm în mod greedy maximul și apoi îl trecem la coadă.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul minim de intervale care acoperă un interval ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o mulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; ce acoperă un alt interval mai mare să se găsească o submulţime minimă a acestei mulţimi care conţine intervale ce acoperă același interval original.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Intervalul acoperit este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;[0, 18]&amp;lt;/source&amp;gt;. Ne dorim să păstrăm cât mai puține intervale care să acopere același interval.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idee de soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
O idee naturală este să sortăm intervalele după punctul de început, iar apoi să le luăm în ordine, unul câte unul. Având un interval curent selectat, am putea să selectăm intervalul care începe cât mai târziu dar al cărui interval este în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
Pe exemplul de mai sus observăm că nu funcționează, deoarece am selecta intervalele 4, 1 și apoi nu am mai avea ce să selectăm în continuare.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Soluție &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Vom parcurge intervalele în ordinea capătului de început, ca mai devreme. Dar vom selecta următorul interval pe acela care se &#039;&#039;&#039;închide ultimul&#039;&#039;&#039; dintre cele care au capătul stânga în interiorul intervalului curent.&lt;br /&gt;
&lt;br /&gt;
În cazul nostru vom selecta intervalele 4, 5 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Numărul maxim de intervale disjuncte ===&lt;br /&gt;
&lt;br /&gt;
Problemă: dată o submulţime de &#039;&#039;N&#039;&#039; intervale &amp;lt;code&amp;gt;[a&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;, b&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;]&amp;lt;/code&amp;gt; să se găsească o submulţime maximală a acestei mulţimi care conţine doar intervale disjuncte (neintersectante).&lt;br /&gt;
&lt;br /&gt;
Această problemă este totuna cu numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele (de ce?).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Idei de soluţie &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Această problemă apare destul de des la olimpiade, deghizată într-o formă sau alta. Deşi pare o problemă grea, în realitate ea se rezolvă uşor. O primă idee ar fi să încercăm să ordonăm intervalele după punctul de început, apoi să le luăm în ordine, unul câte unul. Pentru fiecare interval ne întrebăm dacă îl intersectează pe cel anterior. Dacă da, îl aruncăm şi mergem mai departe. Dacă nu, îl adăugăm la submulţime şi îl reţinem ca interval ultim. Această soluţie nu funcţionează şi e destul de evident de ce: am putea avea un prim interval foarte lung, care acoperă toate celelalte intervale.&lt;br /&gt;
&lt;br /&gt;
Cu toate acestea rezolvarea nu e total greşită. În fapt, ea aproape funcţionează! Are nevoie doar de o modificare ce se impune, analizînd contraexemplul anterior: de ce nu este bun acel prim interval foarte lung? Deoarece el se termină după celelalte. Şi atunci ne putem întreba, în mod natural, ce s-ar întâmpla dacă am alege intervalul care se închide primul, nu care începe primul? Desigur că acest algoritm funcţionează. Vă las vouă să vă luptaţi cu demonstraţia, care decurge cam aşa: să presupunem că soluţia generată de acest algoritm nu e optimă. Înseamnă că există o altă soluţie cu mai multe intervale. Acea soluţie trebuie să difere undeva faţă de cea găsită de noi. Mergeţi pe această cale şi trebuie să ajungeţi la o contradicţie cum că soluţia optimă nu poate avea mai multe segmente.&lt;br /&gt;
&lt;br /&gt;
[[File:Intervale-pe-dreapta-1.svg.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
Exemplu: fie intervalele&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
1: [3 8]&lt;br /&gt;
2: [9 18]&lt;br /&gt;
3: [1 5]&lt;br /&gt;
4: [0 8]&lt;br /&gt;
5: [2 10]&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Atunci vom selecta, în ordine, intervalele 3 și 2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Soluţie ====&lt;br /&gt;
&lt;br /&gt;
Care este algoritmul, în mare?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
# Iniţial mulţimea M este vidă.&lt;br /&gt;
# Ordonează crescător intervalele după punctul de închidere.&lt;br /&gt;
# Consideră ultima închidere (care nu există încă) cu valoarea minus infinit.&lt;br /&gt;
# Pentru fiecare interval, în ordine:&lt;br /&gt;
## Dacă el conţine ultima închidere&lt;br /&gt;
### Ignoră-l.&lt;br /&gt;
## Altfel&lt;br /&gt;
### Selectează intervalul curent şi adaugă-l la mulţimea M.&lt;br /&gt;
### Setează ultima închidere pe închiderea intervalului selectat&lt;br /&gt;
# Afişează mulţimea M.&lt;br /&gt;
&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Ce se întâmplă dacă avem mai multe intervale care se termină în acelaşi punct? Atunci putem reţine intervalul cel mai mic. De ce? Deoarece este cel mai convenabil. Să presupunem că soluţia maximală conţinea un alt interval care se termina în acel punct. Atunci putem înlocui acel interval cu intervalul cel mai mic, obţinînd o altă soluţie maximală.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Considerente de implementare ====&lt;br /&gt;
&lt;br /&gt;
Implementarea directă: memorăm intervalele ca perechi, prin capetele lor. Sortăm perechile după capătul din dreapta. Apoi facem o parcurgere a perechilor. Această implementare necesită &#039;&#039;O(N log N)&#039;&#039; timp și &#039;&#039;O(N)&#039;&#039; memorie.&lt;br /&gt;
&lt;br /&gt;
Implementare relaxată: atunci când coordonatele sunt suficient de mici astfel încât să putem folosi un vector care să memoreze un întreg pentru fiecare coordonată, putem încerca o abordare mai simplă: nu memorăm intervalele. Ci, pentru fiecare interval &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;[ai, bi]&amp;lt;/source&amp;gt;, vom seta &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;început[bi] = ai&amp;lt;/source&amp;gt;. Cu alte cuvinte vom memora capetele stânga la poziția capetelor dreapta. Apoi parcurgem vectorul în ordine, acoperind segmentul de coordonate care acoperă toate intervalele, testând dacă elementul de la poziția curentă în vector este mai mic decât ultima închidere memorată. Dacă nu, atunci selectăm intervalul și mutăm ultima închidere la poziția curentă în vector. Trebuie să avem grijă la următoarele:&lt;br /&gt;
&lt;br /&gt;
* Să iniţializăm vectorul de coordonate cu o valoare distinctă de capete de interval. Matematic am putea să le iniţializăm cu minus infinit, astfel încât să nu fie selectate niciodată.&lt;br /&gt;
* Atunci când &#039;&#039;aşezăm&#039;&#039; intervalele pe vector să avem grijă ca dacă găsim deja o valoare în vector să o înlocuim numai dacă cea curentă este mai mare (intervalul este mai mic).&lt;br /&gt;
&lt;br /&gt;
Această soluţie necesită &#039;&#039;O(N + CMAX)&#039;&#039; timp şi memorie, &#039;&#039;&#039;CMAX&#039;&#039;&#039; fiind coordonata maximă. De aici rezultă când o putem folosi: atunci când &#039;&#039;&#039;CMAX&#039;&#039;&#039; este suficient de mic.&lt;br /&gt;
&lt;br /&gt;
==== Generalizare ====&lt;br /&gt;
&lt;br /&gt;
Putem generaliza această problemă: să se calculeze mulţimea maximală de dreptunghiuri neintersectante. Sau de paralelipipede. Sau de paralelipipede n-dimensionale. Cum se rezolvă aceste probleme? Din nefericire aceste probleme fac parte dintr-o clasă de probleme numite &#039;&#039;NP-hard&#039;&#039;, pentru care nu se cunoaşte o soluţie bună.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Exemplu când greedy nu funcționează: prolema rucsacului ===&lt;br /&gt;
&lt;br /&gt;
Problema rucsacului cere să &#039;&#039;umplem&#039;&#039; cât mai bine un rucsac care poate duce o greutate maximă, dispunând de obiecte de diverse greutăți.&lt;br /&gt;
&lt;br /&gt;
Exemplu: Vreau să umplu cât mai bine un rucsac de 100 și am greutăți 50, 30, 25 și 25.&lt;br /&gt;
&lt;br /&gt;
Prin metoda greedy vom alege greutățile cele mai mari, în ordine: 50 și 30, după care nu mai putem adăuga nimic. Astfel vom obține greutatea totală 80. Dacă am alege însă greutățile 50, 25 și 25 am putea umple complet rucsacul.&lt;br /&gt;
&lt;br /&gt;
De remarcat totuși că chiar și atunci când metoda greedy nu calculează optimul, un algoritm greedy bine gândit va da o soluție bună, aproape de optim.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 16 ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/politic Politic] dată la ONI 2007 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sumax SumaX] dată la Shumen Juniori 2015&lt;br /&gt;
* [https://www.nerdarena.ro/problema/acoperire Acoperire]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/char Char] dată la ONI 2010 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Pentru doritorii să încerce și soluția problemei numărul minim de numărul minim de puncte ce &#039;&#039;înțeapă&#039;&#039; toate intervalele puteți rezolva problema [https://www.nerdarena.ro/problema/baloane Baloane].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema opțională ==&lt;br /&gt;
&lt;br /&gt;
Să se rezolve următoarele probleme (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://www.nerdarena.ro/problema/startrek Star Trek] dată la OJI 2016 clasa a 7-a&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_16:_Tehnica_Two_Pointers_(doi_pointeri),_tipul_de_date_struct_%C8%99i_metoda_Greedy Accesează rezolvarea temei 16]&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_V-a_lec%C8%9Bia_29_-_1_mar_2018&amp;diff=18543</id>
		<title>Clasa a V-a lecția 29 - 1 mar 2018</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_V-a_lec%C8%9Bia_29_-_1_mar_2018&amp;diff=18543"/>
		<updated>2026-02-03T14:46:15Z</updated>

		<summary type="html">&lt;p&gt;Cristian: /* Soluție fără vectori */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Comentarii tema 28 =&lt;br /&gt;
# Topul pescarilor la jimjam: &#039;&#039;&#039;Ipate&#039;&#039;&#039; 16 surse, &#039;&#039;&#039;Nicola&#039;&#039;&#039; 13 surse, &#039;&#039;&#039;Togan&#039;&#039;&#039; 13 surse, &#039;&#039;&#039;Mocanu&#039;&#039;&#039; 12 surse, &#039;&#039;&#039;Aizic&#039;&#039;&#039; 10 surse, &#039;&#039;&#039;Benescu&#039;&#039;&#039; 10 surse, &#039;&#039;&#039;Dobre&#039;&#039;&#039; 10 surse, &#039;&#039;&#039;Chivu&#039;&#039;&#039; 9 surse, &#039;&#039;&#039;Petcu&#039;&#039;&#039; 9 surse, &#039;&#039;&#039;Asgari&#039;&#039;&#039; 8 surse, &#039;&#039;&#039;Iordache&#039;&#039;&#039; 7 surse, &#039;&#039;&#039;Grecu&#039;&#039;&#039; 6 surse, &#039;&#039;&#039;Cadîr&#039;&#039;&#039; 5 surse, &#039;&#039;&#039;Dumitrescu&#039;&#039;&#039; 5 surse, &#039;&#039;&#039;Rebengiuc&#039;&#039;&#039; 5 surse,&lt;br /&gt;
# Topul indentatorilor de cartier: &#039;&#039;&#039;Cadîr&#039;&#039;&#039; în categoria: &#039;indentăm artistic, după suflet&#039;. &#039;&#039;&#039;Cojocaru&#039;&#039;&#039;, în categoria &#039;o sursă da, una ba&#039;.&lt;br /&gt;
# Topul copiatorilor: &#039;&#039;&#039;Asgari&#039;&#039;&#039;, &#039;&#039;&#039;Petcu&#039;&#039;&#039; și &#039;&#039;&#039;Voicu&#039;&#039;&#039; cîștigă competiția de surse copiate la varena, reușind să copieze nici mai mult nici mai puțin decît de la... de la Cristian Frâncu! Motto-ul lor este &#039;&#039;copiem doar de la cei mai buni!&#039;&#039; Pe locul doi se află &#039;&#039;&#039;Tatomir&#039;&#039;&#039;, cu motto-ul &#039;&#039;tata este cel mai tare!&#039;&#039;&lt;br /&gt;
# Mulți dintre voi au simțit nevoia să folosească tipul &amp;lt;code&amp;gt;long long&amp;lt;/code&amp;gt; la problema jimjam. Nu era necesar. De aceea nu este bine să învățați materia în avans, veți folosi greșit acele cunoștințe.&lt;br /&gt;
# Unii din voi au folosit vectori la numere10. Nu erau necesari.&lt;br /&gt;
# Problema numere10 cere să se calculeze numărul de divizori ai unui număr. Este o problemă care se rezolvă eficient testînd divizorii doar pînă la radical din n. Am mai discutat acest lucru cu alte ocazii. Există și rezolvarea bazată pe descompunerea în factori primi pe care o vom discuta.&lt;br /&gt;
&lt;br /&gt;
= Tema - rezolvări =&lt;br /&gt;
&lt;br /&gt;
Rezolvări aici [http://solpedia.francu.com/wiki/index.php/Clasa_a_V-a_lec%C8%9Bia_28_-_22_feb_2018]&lt;br /&gt;
&lt;br /&gt;
= Sfaturi pentru olimpiadă =&lt;br /&gt;
Iată cîteva sugestii pentru olimpiadă.&lt;br /&gt;
== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
* &#039;&#039;&#039;Odihnă&#039;&#039;&#039;: odihniți-vă cu o zi înainte. Relaxați-vă cu activitatea favorită. Mergeți la un film, jucați jocul vostru preferat, etc. Încercați să nu vă gîndiți la concurs.&lt;br /&gt;
* &#039;&#039;&#039;Ceas&#039;&#039;&#039;: aveți un ceas la voi. Nu ceasul calculatorului, al telefonului sau al smartphone-ului. Fiți conștienți de trecerea timpului, nu vă treziți din visare cînd mai este un sfert de oră. Pentru aceasta un ceas așezat pe banca de lucru este ideal. Nu uitați: toate celelalte ceasuri pot fi incorecte, sau să moară.&lt;br /&gt;
* &#039;&#039;&#039;Țineți socoteala timpului&#039;&#039;&#039;: notați ora începerii concursului pe foaie. Verificați la final că ați avut tot timpul promis. Dacă aveți probleme cu calculatorul și cereți ajutorul supraveghetorului notați timpul cînd ați fost întrerupți și apoi timpul cînd reveniți la un calculator funcțional. Aveți dreptul la o prelungire a timpului egală cu timpul pierdut.&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile&#039;&#039;&#039;: dacă vă opresc mai devreme decît e cazul, sau dacă ați pierdut zece minute din cauza unui calculator care nu a funcționat și nu vi se dau, cereți respectuos să vi se extindă timpul cu acele minute. Supraveghetorii pot uneori să uite, olimpiada este stresantă și pentru ei. Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare (formulate astfel încît răspunsul să fie &#039;DA&#039; sau &#039;NU&#039;).&lt;br /&gt;
* &#039;&#039;&#039;Low hanging fruit&#039;&#039;&#039;: rezolvați problema mai ușoară prima. Citiți toate problemele la început și hotărîți-vă care este mai ușoară. Cu ea începeți. Puteți inclusiv să rezolvați subpunctele mai ușoare primele. De exemplu puteți să rezolvați problema 1 punctul a), apoi problema 2 punctul b).&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat&#039;&#039;&#039;: intrați în sală încrezători în voi. Sînteți printre cei mai buni din țară, aveți puterea să vă clasați printre primii din țară. Propuneți-vă să luați punctaj maxim și reamintiți-vă că puteți acest lucru.&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele&#039;&#039;&#039;: atunci cînd constatați că problemele sînt grele nu vă speriați. Bucurați-vă! Este cea mai bună situație pentru voi. Aveți numai de cîștigat: fie știți să faceți problema, caz în care este perfect, căci problema fiind grea v-ați asigurat că veți fi mai sus ca ceilalți. Fie nu știți să faceți problema și atunci care este șansa ca cei mai slabi ca voi să știe să o facă? Temeți-vă de problemele ușoare, pe care știu toți să le facă. Voi le veți gîndi, ceilalți le vor picta la perfecție.&lt;br /&gt;
* &#039;&#039;&#039;Atenție&#039;&#039;&#039;: fiți foarte atenți la detalii: la numele fișierelor de intrare/ieșire; la numele sub care trebuie să salvați programul C; la setările de proiect CodeBlocks de care am vorbit: -Wall și -O2; la locul unde ați salvat proiectul, și numele proiectului în caz că aveți nevoie să le copiați în altă parte.&lt;br /&gt;
&lt;br /&gt;
=== Ce să &#039;&#039;&#039;nu&#039;&#039;&#039; faceți ===&lt;br /&gt;
* &#039;&#039;&#039;Nu îngrășați porcul în ajun&#039;&#039;&#039;: nu faceți probleme în ultima clipă . Vă veți extenua inutil. În urma stresului s-ar putea să nu reușiți să le faceți, ceea ce vă va demoraliza. Aveți nevoie de moral ridicat la concurs.&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați&#039;&#039;&#039;: panica ucide creierul. Panica vă face să gîndiți mai încet și să doriți în subconștient să ieșiți din sală. Controlați-o. Nu uitați că o problemă grea, pe care nu știți să o faceți, vă avantajează, pentru că probabil nici ceilalți nu știu, pe cînd voi aveți o șansă să luați puncte pe ea.&lt;br /&gt;
* &#039;&#039;&#039;Nu-i băgați în seamă pe cei ce se dau mari&#039;&#039;&#039;: unii elevi spun cu voce suficient de tare să fie auziți, în timpul concursului, lucruri de genul &#039;aaah, am făcut-o și pe asta, super&#039; sau &#039;am rupt, sigur iau suta&#039;, etc. Este modul lor inconștient de a vă demoraliza. De obicei ei se clasează slab. Ignorați-i. De asemenea veți vedea că unii dintre concurenți părăsesc sala mai devreme cu o oră cu un aer fericit, eventual spunînd &#039;sigur mă calific&#039; sau &#039;am făcut tot, yesss&#039;. Ignorați-i. Și ei se clasează slab. Nu vă demoralizați indiferent cîți elevi ies din sală. Lupta voastră nu este cu ei, ci cu problemele și punctele.&lt;br /&gt;
* &#039;&#039;&#039;Nu ieșiți mai devreme din sală&#039;&#039;&#039;: dacă nu ați terminat problemele continuați să vă gîndiți la o soluție. Dacă ați terminat problemele și vă plictisiți creați mai multe teste pe care să le testați. Nu uitați: nimănui nu-i pasă cît de repede ați ieșit. Nu luați puncte suplimentare pentru asta.&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați în ultima clipă un program care funcționează&#039;&#039;&#039;: riscați să nu aveți timp să-l testați și să luați zero puncte la acea problemă.&lt;br /&gt;
* &#039;&#039;&#039;Nu vă îngrijorați de părerea noastră&#039;&#039;&#039;: noi, instructorii, știm bine care este valoarea voastră. Nu avem nevoie de un rezultat la olimpiadă să aflăm. Nu o să ne vedeți vreodată supărați pe voi pentru că nu ați luat punctaj bun. Dimpotrivă, orice veți face vom fi mîndri de voi pentru cît de departe ați ajuns în numai cîteva luni. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi sîntem de partea voastră. Chiar dacă la cerc sau la clasă, cînd sîntem între noi, vă certăm sau ne mai supărăm pe voi, în momentul cînd ați ieșit din școală să vă &amp;quot;luptați&amp;quot; cu olimpiada pentru noi, sîntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
== Diferenţa dintre olimpiadă şi viaţa reală ==&lt;br /&gt;
Nu uitaţi că olimpiada este un concurs de verificare a cunoştinţelor, nimic mai mult, nimic mai puţin. Ea nu este evaluarea supremă a unui elev, la fel cum nu este nici ceva de ignorat cu dispreţ. Este cel mai mare concurs de la clasa a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;, drept pentru care îl vom trata cu respect. Dar nu ne vom spînzura de grindă, considerînd că viaţa s-a sfîrşit, dacă nu obţinem rezultatul dorit. &#039;&#039;&#039;Olimpiada nu ne defineşte ca oameni&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Acestea fiind spuse, dacă mergem la olimpiadă, ne dorim un scor cît mai bun. De aceea consider utilă următoarea analogie între arte marţiale si informatică:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;În viaţa reală&#039;&#039;&#039;: în arte marţiale învăţăm pe viaţă. Procedee noi, ce trebuie aplicate cît mai corect, perfect dacă se poate. Filozofie, mod de comportament, respect faţă de artă şi luptă. Atunci cînd mişcarea nu este perfectă, instructorul vă va corecta. Bătaia este interzisă cu excepţia cazului cînd sînteţi în legitimă apărare. Este imoral şi foarte urît să cîştigaţi avantaje în viaţa reală bătîndu-i pe cei mai slabi. Artele marţiale au ca scop îmbunătăţirea organismului.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi cînd sînteţi cu fratele mai mic după voi şi vă atacă golanii. În acel moment nu vă verifică nimeni cît de frumos aţi executat procedeul pentru a scăpa teferi. Tot ce contează este să învingeţi. Pentru aceasta vă veţi folosi de orice! Dacă găsiţi un bolovan, îl aruncaţi în adversar. O chiuvetă spartă, bună şi aceea! La fel şi la olimpiadă, tot ce contează sînt punctele. Nu cît de frumoasă este metoda folosită, nici cît de frumos aţi scris codul. Dacă puteţi lua puncte afişînd mereu &amp;quot;0&amp;quot;, o faceţi. Această practică nu ar fi acceptată nici la cercul nostru şi nici în viaţa reală. Din păcate, însă, la olimpiadă nu se punctează stilul şi perfecţionismul, ci numai punctele.&lt;br /&gt;
&lt;br /&gt;
Atenţie însă! Olimpiada este o excepţie, nu o regulă! Aşa cum în viaţa reală nu puteţi să smulgeţi chiuveta din perete pentru a o arunca în colegul de clasă, nici la cerc, la companii, sau unde se va întîmpla să rezolvaţi probleme de informatică, nu puteţi trişa, sau scrie cod ineficient.&lt;br /&gt;
&lt;br /&gt;
= Lecţie =&lt;br /&gt;
&lt;br /&gt;
Deoarece lecția a fost foarte lungă și bateria camerei video s-a descărcat discuția soluțiilor la problema interval2 nu apare pe film.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;html5media height=&amp;quot;720&amp;quot; width=&amp;quot;1280&amp;quot;&amp;gt;https://www.algopedia.ro/video/2017-2018/2018-03-01-lectie-info-29-720p.mp4&amp;lt;/html5media&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Vectori preinițializați ==&lt;br /&gt;
Să presupunem că vrem să rezolvăm următoarea problemă:&lt;br /&gt;
&lt;br /&gt;
=== Aprinse ===&lt;br /&gt;
Dispunem de un calculator de buzunar care afișează cifrele în mod clasic, folosind 7 fante luminoase aprinse sau stinse. Astfel, cele zece cifre zecimale se reprezintă astfel:&lt;br /&gt;
&lt;br /&gt;
[[file:cifre-lcd.jpg|frame|none|Cifre afișaj electronic]]&lt;br /&gt;
&lt;br /&gt;
Se cere ca, dat un număr de maximum 18 cifre la intrare să se afișeze numărul de fante aprinse necesare pentru a afișa acel număr pe ecranul calculatorului de buzunar.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemple&#039;&#039;&#039;: numărul 254 are 14 segmente aprinse (5 + 5 + 4), iar numărul 2083 are 23 de segmente (5 + 6 + 7 + 5).&lt;br /&gt;
&lt;br /&gt;
Pentru a rezolva această problemă vom afla pe rînd cifrele numărului, și pentru fiecare cifră în parte vom afla numărul său de segmente, pe care îl vom aduna la o sumă globală (variabilă acumulator). Întrebarea este &#039;cum calculăm numărul de segmente al unei cifre date&#039;?&lt;br /&gt;
&lt;br /&gt;
=== Soluție fără vectori ===&lt;br /&gt;
Putem rezolva această problemă fără a folosi vectori. Pentru fiecare cifră vom avea nouă teste &amp;lt;code&amp;gt;if&amp;lt;/code&amp;gt; pentru a decide ce număr adunăm:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;source lang=&amp;quot;C&amp;quot;&amp;gt;s = 0;&lt;br /&gt;
while ( n &amp;gt; 0 ) {&lt;br /&gt;
  cf = n % 10;&lt;br /&gt;
  n /= 10;&lt;br /&gt;
  if ( cf == 0 )&lt;br /&gt;
    s += 6;&lt;br /&gt;
  else if ( cf == 1 )&lt;br /&gt;
    s += 2;&lt;br /&gt;
  else if ( cf == 2 )&lt;br /&gt;
    s += 5;&lt;br /&gt;
  ...&lt;br /&gt;
  else if ( cf == 8 )&lt;br /&gt;
    s += 7;&lt;br /&gt;
  else&lt;br /&gt;
    s += 6;&lt;br /&gt;
}&lt;br /&gt;
printf( &amp;quot;%d&amp;quot;, s)&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Putem scurta puțin această soluție grupînd cifrele după numărul lor de segmente:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;s = 0;&lt;br /&gt;
while ( n &amp;gt; 0 ) {&lt;br /&gt;
  cf = n % 10;&lt;br /&gt;
  n /= 10;&lt;br /&gt;
  if ( cf == 0 || cf == 6 || cf == 9 )&lt;br /&gt;
    s += 6;&lt;br /&gt;
  else if ( cf == 1 )&lt;br /&gt;
    s += 2;&lt;br /&gt;
  else if ( cf == 2 || cf == 3 || cf == 5 )&lt;br /&gt;
    s += 5;&lt;br /&gt;
  else if ( cf == 4 )&lt;br /&gt;
    s += 4;&lt;br /&gt;
  else if ( cf == 7 )&lt;br /&gt;
    s += 3;&lt;br /&gt;
  else&lt;br /&gt;
    s += 7;&lt;br /&gt;
}&lt;br /&gt;
printf( &amp;quot;%d&amp;quot;, s)&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Există o soluție mai bună?&lt;br /&gt;
&lt;br /&gt;
=== Soluție cu vector ===&lt;br /&gt;
Desigur, folosind vectori. Am putea să păstrăm pentru fiecare cifră numărul ei de segmente. Vom păstra aceste numere într-un vector &amp;lt;code&amp;gt;nrseg[10]&amp;lt;/code&amp;gt;, pe care îl vom inițializa la începutul programului. În acest fel putem afla ușor cîte segmente are o cifră: dacă cifra este &amp;lt;code&amp;gt;cf&amp;lt;/code&amp;gt;, atunci numărul său de segmente este &amp;lt;code&amp;gt;nrseg[cf]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată un program posibil:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;int nrseg[10];&lt;br /&gt;
...&lt;br /&gt;
nrseg[0] = 6;&lt;br /&gt;
nrseg[1] = 2;&lt;br /&gt;
nrseg[2] = 5;&lt;br /&gt;
nrseg[3] = 5;&lt;br /&gt;
nrseg[4] = 4;&lt;br /&gt;
nrseg[5] = 5;&lt;br /&gt;
nrseg[6] = 6;&lt;br /&gt;
nrseg[7] = 3;&lt;br /&gt;
nrseg[8] = 7;&lt;br /&gt;
nrseg[9] = 6;&lt;br /&gt;
&lt;br /&gt;
s = 0;&lt;br /&gt;
while ( n &amp;gt; 0 ) {&lt;br /&gt;
  cf = n % 10;&lt;br /&gt;
  n /= 10;&lt;br /&gt;
  s += nrseg[cf];&lt;br /&gt;
}&lt;br /&gt;
printf( &amp;quot;%d&amp;quot;, s)&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Precum observați, programul nu este cu mult mai scurt. Dar de data aceasta mare parte din program este inițializarea vectorului &amp;lt;code&amp;gt;nrseg[10]&amp;lt;/code&amp;gt;, instrucțiunile de atribuire. Calculul în sine ocupă doar șase linii, corpul instrucțiuni &amp;lt;code&amp;gt;while&amp;lt;/code&amp;gt; este mult mai mic față de soluțiile anterioare.&lt;br /&gt;
&lt;br /&gt;
=== Soluție cu vector preinițializat ===&lt;br /&gt;
Pentru astfel de situații, atunci cînd ne dorim să inițializăm un întreg vector cu multiple valori, limbajul C ne ajută să scriem ceva mai puțin. Putem atribui toate cele zece valori într-o singură linie! Această atribuire se face în aceeași linie în care declarăm vectorul. Astfel, în exemplul nostru, vom atribui cele zece valori astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;int nrseg[10] = { 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 };&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Acest tip de declarație poartă denumirea de &#039;&#039;&#039;vector preinițializat&#039;&#039;&#039;. Astfel, programul nostru devine:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;int nrseg[10] = { 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 };&lt;br /&gt;
...&lt;br /&gt;
s = 0;&lt;br /&gt;
while ( n &amp;gt; 0 ) {&lt;br /&gt;
  cf = n % 10;&lt;br /&gt;
  n /= 10;&lt;br /&gt;
  s += nrseg[cf];&lt;br /&gt;
}&lt;br /&gt;
printf( &amp;quot;%d&amp;quot;, s)&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unele probleme se rezolvă ceva mai ușor folosind astfel de vectori. Acesta este și cazul problemei [http://varena.ro/problema/speciale speciale] pe care o aveți la temă.&lt;br /&gt;
&lt;br /&gt;
== Problemele date la Info Oltenia clasele 5-6 ==&lt;br /&gt;
&lt;br /&gt;
=== Problema brățara ===&lt;br /&gt;
Problema [http://varena.ro/problema/bratara brățara] a fost dată la [http://www.cnfb.ro/infooltenia2018/ Info Oltenia 2018], clasele 5-6.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int poz[100]; // poz[i] = pozitia margelei care incepe cu i&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int n, c, i, nrb, ucf, cap, lmax, start, p, u;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;bratara.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d&amp;quot;, &amp;amp;c, &amp;amp;n );&lt;br /&gt;
  nrb = 0;   // numarul de bratari ce nu pot fi extinse (punctul 1)&lt;br /&gt;
  ucf = 100; // ultimele doua cifre ale margelei anterioare&lt;br /&gt;
  cap = 0;   // pozitia capului (inceputlui) bratarii curente&lt;br /&gt;
  lmax = 1;  // lungimea bratarii circulare maxime, initial ceva imposibil&lt;br /&gt;
  start = 0; // pozitia de start a bratarii circulare maxime&lt;br /&gt;
  for ( i = 1; i &amp;lt;= n; i++ ) {&lt;br /&gt;
    fscanf( fin, &amp;quot;%d&amp;quot;, &amp;amp;p );&lt;br /&gt;
    u = p % 100;       // ultimele doua cifre&lt;br /&gt;
    while ( p &amp;gt;= 100 ) // calculam primele doua cifre&lt;br /&gt;
      p /= 10;&lt;br /&gt;
    &lt;br /&gt;
    if ( p != ucf ) {    // nu se potriveste cu margeaua din-nainte?&lt;br /&gt;
      if ( i - cap &amp;gt; 1 ) // bratara s-a incheiat, are macar doua margele?&lt;br /&gt;
        nrb++; // numaram inca o bratara ce nu poate fi extinsa (punctul 1)&lt;br /&gt;
      cap = i; // retinem noul inceput pentru bratara care tocmai incepe (cap)&lt;br /&gt;
    }&lt;br /&gt;
    if ( poz[p] &amp;lt; cap ) // daca e prima margea care incepe cu p in noua bratara&lt;br /&gt;
      poz[p] = i;       // retinem pozitia ei, i&lt;br /&gt;
&lt;br /&gt;
    if ( poz[u] &amp;gt;= cap ) { // daca avem o margea din urma care incepe cu u&lt;br /&gt;
      // am gasit o bratara circulara, testam daca e mai mare decit ce avem deja&lt;br /&gt;
      if ( i - poz[u] + 1 &amp;gt; lmax ) { // lungimea e mai mare decit maximul?&lt;br /&gt;
        lmax = i - poz[u] + 1;       // retinem lungimea&lt;br /&gt;
        start = poz[u];              // si pozitia ei de inceput&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    ucf = u;&lt;br /&gt;
  }&lt;br /&gt;
  // testam ultima bratara - caz special la punctul 1&lt;br /&gt;
  if ( cap &amp;lt; n - 1 ) // daca avem cel putin doua margele in ultima bratara&lt;br /&gt;
    nrb++;&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
&lt;br /&gt;
  fout = fopen( &amp;quot;bratara.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  if ( c == 1 )&lt;br /&gt;
    fprintf( fout, &amp;quot;%d\n&amp;quot;, nrb );&lt;br /&gt;
  else if ( lmax == 1 ) // nu am gasit nici o bratara circulara&lt;br /&gt;
    fprintf( fout, &amp;quot;-1\n&amp;quot; );&lt;br /&gt;
  else&lt;br /&gt;
    fprintf( fout, &amp;quot;%d %d %d\n&amp;quot;, lmax, start, start + lmax - 1 );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Problema interval2 ===&lt;br /&gt;
Problema [http://varena.ro/problema/interval2 interval2] a fost dată la [http://www.cnfb.ro/infooltenia2018/ Info Oltenia 2018], clasele 5-6.&lt;br /&gt;
&lt;br /&gt;
==== Soluția comisiei ====&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int q, T, i, a, b;&lt;br /&gt;
  long long A, r, p, u;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;interval2.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fout = fopen( &amp;quot;interval2.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;a, &amp;amp;b, &amp;amp;q );&lt;br /&gt;
  for ( i = 0; i &amp;lt; q; i++ ) {&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%lld&amp;quot;, &amp;amp;T, &amp;amp;A );&lt;br /&gt;
    if ( T == 1 )        // cite numere divizibile cu A in intervalul [a, b]&lt;br /&gt;
      r = b / A - (a - 1) / A;&lt;br /&gt;
    else if ( T == 2 ) { // cite perechi de numere div. cu A in interv. [a, b]&lt;br /&gt;
      r = b / A - (a - 1) / A;&lt;br /&gt;
      r = r * (r - 1) / 2;&lt;br /&gt;
    } else { // cite perechi de numere in [a, b] cu produsul &amp;gt; A&lt;br /&gt;
      r = 0;&lt;br /&gt;
      p = (A + b) / b; // p e cel mai mic nr. p cu care putem forma o pereche&lt;br /&gt;
      if ( p &amp;lt; a )     // il ajustam sa faca parte din interval&lt;br /&gt;
        p = a;&lt;br /&gt;
      for ( ; p &amp;lt; b; p++ ) {&lt;br /&gt;
        u = (A + p) / p; // cautam cel mai mic u a.i. p * u &amp;gt; A &lt;br /&gt;
        if ( u &amp;lt;= p )&lt;br /&gt;
          u = p + 1;&lt;br /&gt;
        r += b - u + 1;  // formam perechi (p u), (p u+1) ... (p b)&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    fprintf( fout, &amp;quot;%lld\n&amp;quot;, r );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Soluție îmbunătățită ====&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int q, T, i, a, b;&lt;br /&gt;
  long long A, r, p, u;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;interval2.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fout = fopen( &amp;quot;interval2.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;a, &amp;amp;b, &amp;amp;q );&lt;br /&gt;
  for ( i = 0; i &amp;lt; q; i++ ) {&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%lld&amp;quot;, &amp;amp;T, &amp;amp;A );&lt;br /&gt;
    if ( T == 1 )        // cite numere divizibile cu A in intervalul [a, b]&lt;br /&gt;
      r = b / A - (a - 1) / A;&lt;br /&gt;
    else if ( T == 2 ) { // cite perechi de numere div. cu A in interv. [a, b]&lt;br /&gt;
      r = b / A - (a - 1) / A;&lt;br /&gt;
      r = r * (r - 1) / 2;&lt;br /&gt;
    } else { // cite perechi de numere in [a, b] cu produsul &amp;gt; A&lt;br /&gt;
      r = 0;&lt;br /&gt;
      p = (A + b) / b; // cautam cel mai mic p a.i. p * b &amp;gt; A&lt;br /&gt;
      if ( p &amp;lt; a )     // daca este in afara intervalului, il ajustam&lt;br /&gt;
        p = a;&lt;br /&gt;
      if ( p &amp;lt; b ) {&lt;br /&gt;
        u = b;&lt;br /&gt;
        while ( p &amp;lt; u ) { // cita vreme nu sint consecutive&lt;br /&gt;
          r += u - p; // formam perechi (p u), (p+1 u) ... (u-1 u)&lt;br /&gt;
          u--;&lt;br /&gt;
          if ( p * u &amp;lt;= A ) // trebuie sa marim p?&lt;br /&gt;
            p++;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    fprintf( fout, &amp;quot;%lld\n&amp;quot;, r );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Soluție optimizată ====&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main() {&lt;br /&gt;
  FILE *fin, *fout;&lt;br /&gt;
  int q, T, i, a, b;&lt;br /&gt;
  long long A, r, p, u;&lt;br /&gt;
&lt;br /&gt;
  fin = fopen( &amp;quot;interval2.in&amp;quot;, &amp;quot;r&amp;quot; );&lt;br /&gt;
  fout = fopen( &amp;quot;interval2.out&amp;quot;, &amp;quot;w&amp;quot; );&lt;br /&gt;
  fscanf( fin, &amp;quot;%d%d%d&amp;quot;, &amp;amp;a, &amp;amp;b, &amp;amp;q );&lt;br /&gt;
  for ( i = 0; i &amp;lt; q; i++ ) {&lt;br /&gt;
    fscanf( fin, &amp;quot;%d%lld&amp;quot;, &amp;amp;T, &amp;amp;A );&lt;br /&gt;
    if ( T == 1 )        // cite numere divizibile cu A in intervalul [a, b]&lt;br /&gt;
      r = b / A - (a - 1) / A;&lt;br /&gt;
    else if ( T == 2 ) { // cite perechi de numere div. cu A in interv. [a, b]&lt;br /&gt;
      r = b / A - (a - 1) / A;&lt;br /&gt;
      r = r * (r - 1) / 2;&lt;br /&gt;
    } else { // cite perechi de numere in [a, b] cu produsul &amp;gt; A&lt;br /&gt;
      p = sqrtl( A ); // cautam cel mai mic p a.i. p * (p + 1) &amp;gt; A&lt;br /&gt;
      if ( p * (p + 1) &amp;lt;= A ) // p este cumva prea mic?&lt;br /&gt;
        p++;&lt;br /&gt;
      if ( p &amp;lt;= a ) {  // toate perechile sint OK&lt;br /&gt;
        r = b - a + 1; // numarul de numere in [a, b]&lt;br /&gt;
        r = r * (r - 1) / 2; // numarul de perechi din [a, b]&lt;br /&gt;
      } else if ( p &amp;gt;= b  )  // nici o pereche nu este OK&lt;br /&gt;
        r = 0;&lt;br /&gt;
      else { // p este in intervalul (a, b) deschis la ambele capete&lt;br /&gt;
        // putem forma toate perechile din [p+1, p+2, ..., b]&lt;br /&gt;
        r = (b - p - 1) * (b - p) / 2;&lt;br /&gt;
        u = p + 1;&lt;br /&gt;
        while ( p &amp;gt;= a &amp;amp;&amp;amp; u &amp;lt;= b ) {&lt;br /&gt;
          r += b - u + 1; // formam perechi (p u) (p u+1) ... (p b)&lt;br /&gt;
          p--;&lt;br /&gt;
          u++;&lt;br /&gt;
          while ( p * u &amp;lt;= A &amp;amp;&amp;amp; u &amp;lt;= b ) // marim u atit cit e nevoie&lt;br /&gt;
            u++;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    fprintf( fout, &amp;quot;%lld\n&amp;quot;, r );&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  fclose( fin );&lt;br /&gt;
  fclose( fout );&lt;br /&gt;
&lt;br /&gt;
  return 0;&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Temă =&lt;br /&gt;
[http://varena.ro/runda/2018-03-01-clasa-5-tema-29 Tema 29]: să se rezolve următoarele probleme (program C  trimis la vianuarena):&lt;br /&gt;
* [http://varena.ro/problema/speciale speciale] dată la OJI 2015 clasa a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;&lt;br /&gt;
* [http://varena.ro/problema/cuart cuarț] dată la OJI 2015 clasa a 5&amp;lt;sup&amp;gt;a&amp;lt;/sup&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Rezolvări aici [http://solpedia.francu.com/wiki/index.php/Clasa_a_V-a_lec%C8%9Bia_29_-_1_mar_2018]&lt;br /&gt;
&lt;br /&gt;
= Succes la olimpiadă! =&lt;br /&gt;
Mult succes la olimpiadă! Nu uitaţi că sînteţi cei mai buni şi că ne sînteţi dragi indiferent de rezultatul de duminică.&lt;/div&gt;</summary>
		<author><name>Cristian</name></author>
	</entry>
</feed>