<?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=Mihai</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=Mihai"/>
	<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php/Special:Contributions/Mihai"/>
	<updated>2026-04-12T17:01:47Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.44.2</generator>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_5:_Precalculare&amp;diff=18530</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=18530"/>
		<updated>2025-06-11T13:43:18Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;while&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;isalpha()&amp;lt;/source&amp;gt; (testare literă) și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;isalnum()&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;ciur[]&amp;lt;/source&amp;gt;, care, pentru fiecare număr &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p&amp;lt;/source&amp;gt;, calculează &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;ciur[p]&amp;lt;/source&amp;gt; care ne spune dacă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p&amp;lt;/source&amp;gt; este prim?&lt;br /&gt;
&lt;br /&gt;
Cum am calcula primalitatea tuturor numerelor până la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; prim. O estimare a numerelor prime până la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;&lt;br /&gt;
* Suma divizorilor primi (unici sau neunici) ai tuturor numerelor până la &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;
* Numărul de divizori ai tuturor numerelor până la &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;
* Suma divizorilor tuturor numerelor până la &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;
* Descompunerea în factori primi a tuturor numerelor până la &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;
* Numărul de numere prime cu fiecare din numerele până la &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;
&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; şi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&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;s[i]&amp;lt;/source&amp;gt; este suma elementelor de la 0 până la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; (inclusiv). El se mai numeşte şi vectorul sumei prefixelor lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;. Construcția acestui vector se poate face liniar deoarece avem relaţia &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[i] = s[i-1] + v[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; şi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt; este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[j] - s[i-1]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[l][c] = s[l][c-1] + s[l-1][c] - s[l-1][c-1] + m[l][c]&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s&amp;lt;/source&amp;gt; este matricea pe care o precalculăm;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S = s[l2][c2] - s[l2][c1-1] - s[l1-1][c2] + s[l1-1][c1-1]&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&amp;gt; este suma căutată;&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;x &amp;amp; (x - 1)&amp;lt;/source&amp;gt;. Complexitate &#039;&#039;O(nr. biţi)&#039;&#039;&lt;br /&gt;
* Cu precalculare: ţinem un vector în care elementul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[x]&amp;lt;/source&amp;gt; este numărul de biţi 1 din reprezentarea în baza 2 a lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;x&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente, iniţial zero. O operaţiune pe acest vector constă în incrementarea fiecărui element între indicii &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; şi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;j&amp;lt;/source&amp;gt;. Se dau &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m&amp;lt;/source&amp;gt; operaţiuni. O operaţiune constă în incrementarea a maxim n elemente din vector, deci avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n x m&amp;lt;/source&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;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 ale acestui vector constituie chiar rezolvarea problemei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Avem, deci: &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[i] = v[0] + v[1] + ... + v[i]&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Înseamnă 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; se va aduna la toate sumele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[k], s[k+1], ..., s[n]&amp;lt;/source&amp;gt;. Ceea ce înseamnă că dacă 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 &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; avem valoarea unu, acea valoare se va propaga în toate elementele lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; începând cu 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;.&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&amp;gt; sumele parțiale ale lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;. Vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[i] = 1&amp;lt;/source&amp;gt; ci &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;s[i]++&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int&amp;lt;/source&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>Mihai</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=18529</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=18529"/>
		<updated>2025-06-11T13:39:56Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;h[x]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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>Mihai</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=18528</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=18528"/>
		<updated>2025-06-11T13:39:42Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Înregistrare video curs */&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;
== 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;h[x]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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>Mihai</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=18527</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=18527"/>
		<updated>2025-06-11T13:38:37Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Înregistrare video curs */&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;loop=1}}&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;h[x]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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>Mihai</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=18526</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=18526"/>
		<updated>2025-06-11T11:07:18Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; care memorează elementele listei (numere întregi de exemplu) și un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[]&amp;lt;/source&amp;gt;, care memorează indicele următorului element din listă. &lt;br /&gt;
&lt;br /&gt;
Perechea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(v[i], next[i])&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; avem celula cu elementul 45, ca mai jos. Dorim să eliminăm celula care apare în listă după celula &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt;, cea cu elementul 10, aflat 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;. 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;-lea element în listă va trebui să parcurgem primele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; elemente, de la începutul listei. Să presupunem că prima celulă din listă este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;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;
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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; într-un vector și apoi să avansăm &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; elemente și să afișăm elementul curent, iar apoi să îl setăm pe zero. Reluăm căutarea de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; copii în cerc îi vom număra de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k/i&amp;lt;/source&amp;gt; ori. Restul elementelor fiind zero, vom face &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k/i&amp;lt;/source&amp;gt; parcurgeri ale întregului vector, deci &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n·k/i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;log n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;. Putem optimiza algoritmul numărând până la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k % i&amp;lt;/source&amp;gt;, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;b[n]&amp;lt;/source&amp;gt; care memorează linia cu bile,n fiind numărul de coloane;&lt;br /&gt;
* un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;liste[m]&amp;lt;/source&amp;gt; care pentru fiecare linie pointează la o listă de coloane pe care se află obstacole, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m&amp;lt;/source&amp;gt; fiind numărul de linii;&lt;br /&gt;
* doi vectori &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;col[p]&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[p]&amp;lt;/source&amp;gt; care memorează coloanele obstacolelor și următorul obstacol pe linie, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(l, c)&amp;lt;/source&amp;gt; unde s-ar afla textul (înainte de a citi acel text!) și adăugând coloana &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;c&amp;lt;/source&amp;gt; la lista liniei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;l&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;prim[]&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;ultim[]&amp;lt;/source&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>Mihai</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=18525</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=18525"/>
		<updated>2025-06-11T11:06:17Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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>Mihai</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=18524</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=18524"/>
		<updated>2025-06-11T11:05:01Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Înregistrare video lecție */&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=5760&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt; care memorează elementele listei (numere întregi de exemplu) și un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[]&amp;lt;/source&amp;gt;, care memorează indicele următorului element din listă. &lt;br /&gt;
&lt;br /&gt;
Perechea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(v[i], next[i])&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; avem celula cu elementul 45, ca mai jos. Dorim să eliminăm celula care apare în listă după celula &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt;, cea cu elementul 10, aflat 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;. 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;-lea element în listă va trebui să parcurgem primele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; elemente, de la începutul listei. Să presupunem că prima celulă din listă este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;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;
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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; într-un vector și apoi să avansăm &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; elemente și să afișăm elementul curent, iar apoi să îl setăm pe zero. Reluăm căutarea de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; copii în cerc îi vom număra de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k/i&amp;lt;/source&amp;gt; ori. Restul elementelor fiind zero, vom face &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k/i&amp;lt;/source&amp;gt; parcurgeri ale întregului vector, deci &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n·k/i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;log n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;. Putem optimiza algoritmul numărând până la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k % i&amp;lt;/source&amp;gt;, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;b[n]&amp;lt;/source&amp;gt; care memorează linia cu bile,n fiind numărul de coloane;&lt;br /&gt;
* un vector &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;liste[m]&amp;lt;/source&amp;gt; care pentru fiecare linie pointează la o listă de coloane pe care se află obstacole, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m&amp;lt;/source&amp;gt; fiind numărul de linii;&lt;br /&gt;
* doi vectori &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;col[p]&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;next[p]&amp;lt;/source&amp;gt; care memorează coloanele obstacolelor și următorul obstacol pe linie, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;(l, c)&amp;lt;/source&amp;gt; unde s-ar afla textul (înainte de a citi acel text!) și adăugând coloana &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;c&amp;lt;/source&amp;gt; la lista liniei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;l&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;prim[]&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;ultim[]&amp;lt;/source&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>Mihai</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=18523</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=18523"/>
		<updated>2025-06-11T10:59:50Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Înregistrare video lecție */&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=7203&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>Mihai</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=18522</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=18522"/>
		<updated>2025-06-11T10:54:36Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Înregistrare video lecție */&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a = a + y;&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int a = 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int v[10010]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;else&amp;lt;/source&amp;gt; urmează un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;if&amp;lt;/source&amp;gt; atunci instrucțiunea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;if&amp;lt;/source&amp;gt; se pune imediat după &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;else&amp;lt;/source&amp;gt; pe aceeași linie, iar corpul de instrucțiuni aparținând lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;else&amp;lt;/source&amp;gt; se indentează cu două spații, nu cu patru. Astfel, în cazul unor instrucțiuni de genul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;if ... else if ... else if ...&amp;lt;/source&amp;gt; se evită migrarea codului către dreapta prin indentări inutile.&lt;br /&gt;
* Fără &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;break / continue / return / goto&amp;lt;/source&amp;gt; în interiorul buclelor. Ele distrug programarea structurată și, implicit, zeci de ani de experiență a omenirii.&lt;br /&gt;
* Folosim &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;for&amp;lt;/source&amp;gt; pentru bucle cu număr cunoscut de pași, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;while&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;for&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n--&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;int main()&amp;lt;/source&amp;gt; și folosiți &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;return 0&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fscanf&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fopen&amp;lt;/source&amp;gt;, iar la final obligatoriu le închideți cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fclose&amp;lt;/source&amp;gt;. În nici un caz nu folosiți &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;freopen&amp;lt;/source&amp;gt;. &lt;br /&gt;
* Folosiți doar funcțiile de intrare/ieșire C, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fscanf / fprintf / fgetc / fputc / fgets&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;sort&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;qsort&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; paranteze de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n1&amp;lt;/source&amp;gt;, respectiv &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n2&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;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;n!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;main()&amp;lt;/source&amp;gt; și am folosit funcții de intrare ieșire precum &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fscanf()&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fprintf()&amp;lt;/source&amp;gt;, precum și funcții matematice din bibliotecile standard precum &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;sqrt()&amp;lt;/source&amp;gt; sau &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;abs()&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;min()&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;
// 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;void&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt; este o variabilă nou creată la intrarea în funcție în care se copiază valoarea variabilei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;x&amp;lt;/source&amp;gt;. Funcția modifică (incrementează) valoarea lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;a&amp;lt;/source&amp;gt;, dar aceasta nu influențează valoarea lui &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;x&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;main()&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;main()&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;main()&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;citire()&amp;lt;/source&amp;gt; sau &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;solve()&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;contor&amp;lt;/source&amp;gt; sau &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;stegulet&amp;lt;/source&amp;gt;. Așa cum stegulețul ar fi bine să se numească &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;terminat&amp;lt;/source&amp;gt; sau &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fara_zero&amp;lt;/source&amp;gt;, la fel și funcțiile trebuie denumite sugestiv. Pentru cele două funcții de mai sus nume bune ar fi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;citesteVectorInaltimi()&amp;lt;/source&amp;gt; sau &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;calculSumePartiale()&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;fgetc()&amp;lt;/source&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>Mihai</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=18521</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=18521"/>
		<updated>2025-06-10T15:30:02Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m[i] = m[j]&amp;lt;/source&amp;gt;, în caz contrar &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m[i] ≠ m[j]&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Inițial fiecare număr va avea propria lui mulțime, adică &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m[i] = i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m[i]&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;
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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;m[]&amp;lt;/source&amp;gt;) în care elementul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;sef[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;sef[i] = i&amp;lt;/source&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>Mihai</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=18520</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=18520"/>
		<updated>2025-06-10T15:19:54Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Created page with &amp;quot;== Înregistrare video lecție ==  {{#ev:youtube|https://youtu.be/QCaiWycJljs|||||start=8250&amp;amp;end=8320&amp;amp;loop=1}}   == Problema Disjoint ==  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:  * 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ă m...&amp;quot;&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 i să memorăm mulțimea din care face el parte. Astfel, m[i] va conține numărul mulțimii lui i. Dacă i și j fac parate din aceeași mulțime atunci m[i] = m[j], în caz contrar m[i] ≠ m[j].&lt;br /&gt;
&lt;br /&gt;
Inițial fiecare număr va avea propria lui mulțime, adică m[i] = i.&lt;br /&gt;
&lt;br /&gt;
==== Operația Find ====&lt;br /&gt;
&lt;br /&gt;
Precum am spus, find(i) ne va da un identificator unic al mulțimii elementului i. În cazul nostru el va fi un număr între 0 și N-1. Cum o implementăm? Returnând chiar m[i]!&lt;br /&gt;
&lt;br /&gt;
int find( int i ) {&lt;br /&gt;
  return m[i];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această operație?&lt;br /&gt;
&lt;br /&gt;
Desigur O(1).&lt;br /&gt;
&lt;br /&gt;
==== Operația Union ====&lt;br /&gt;
&lt;br /&gt;
Operația union(i, j) trebuie să reunească mulțimile din care fac parte elementele i, respectiv j. Cum o implementăm? Suprascriind identificatorul mulțimii lui j cu identificatorul mulțimii lui i, peste tot unde apare el.&lt;br /&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 &lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această operație?&lt;br /&gt;
&lt;br /&gt;
Desigur O(N).&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: O(1) timp&lt;br /&gt;
&lt;br /&gt;
Union: O(N) timp&lt;br /&gt;
&lt;br /&gt;
Memorie necesară: O(N)&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 j, să îl actualizăm doar pe al lui j. Astfel, fiecare element între 0 și N-1 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 m[]) în care elementul sef[i] este numărul din mulţimea 0..N-1 care este şeful lui i. 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 i este şef suprem atunci sef[i] = i. O alternativă la această reprezentare, dacă &amp;quot;sacrificăm&amp;quot; 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 i și ale lui j vom nota că șeful mulțimii i devine șeful șefului mulțimii j.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Operația Find ====&lt;br /&gt;
&lt;br /&gt;
Precum am spus, find(i) va returna șeful mulțimii din care face parte i. Cum îl găsim? Mergând la șeful lui i, apoi la șeful șefului lui i și tot așa până la șeful mulțimii. Iată o implementare recursivă simplă:&lt;br /&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;
&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 O(N).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Operația Union ====&lt;br /&gt;
&lt;br /&gt;
Operația union(i, j) trebuie să reunească mulțimile din care fac parte elementele i, respectiv j. Cum o implementăm? Suprascriind identificatorul mulțimii lui j cu identificatorul mulțimii lui i, peste tot unde apare el.&lt;br /&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;
&lt;br /&gt;
Ce complexitate are această operație?&lt;br /&gt;
&lt;br /&gt;
Ea apelează de două ori find(), iar apoi face O(1) 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: O(N) timp&lt;br /&gt;
&lt;br /&gt;
Union: O(1) timp + complexitatea lui Find&lt;br /&gt;
&lt;br /&gt;
Memorie necesară: O(N)&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 O(log N) 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 i la j 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 i, 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 find.&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;
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;
&lt;br /&gt;
&lt;br /&gt;
==== Operația Union ====&lt;br /&gt;
&lt;br /&gt;
Operația union(i, j) trebuie să reunească mulțimile din care fac parte elementele i, respectiv j. Cum o implementăm? Suprascriind identificatorul mulțimii lui j cu identificatorul mulțimii lui i, peste tot unde apare el.&lt;br /&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;
&lt;br /&gt;
Ce complexitate au aceste operații?&lt;br /&gt;
&lt;br /&gt;
În această implementare, ele sînt O(log N) 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: O(log N) timp&lt;br /&gt;
&lt;br /&gt;
Union: O(log N) timp&lt;br /&gt;
&lt;br /&gt;
Memorie necesară: O(N)&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 O(α(N)), unde α(N) 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 partiționare, 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;
&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;
&lt;br /&gt;
Notăm răspunsurile la interogările de conexitate.&lt;br /&gt;
&lt;br /&gt;
Tipărim aceste răspunsuri în ordine inversă.&lt;br /&gt;
&lt;br /&gt;
Exemple de astfel de probleme: bile3, 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>Mihai</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=18519</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=18519"/>
		<updated>2025-06-10T15:00:43Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Problema Poteci */&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short int&amp;lt;/source&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>Mihai</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=18518</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=18518"/>
		<updated>2025-06-10T14:21:11Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short int&amp;lt;/source&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;
&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>Mihai</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=18517</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=18517"/>
		<updated>2025-06-10T13:27:35Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Created page with &amp;quot;== Înregistrare video rezolvarea temei ==  {{#ev:youtube|https://www.youtube.com/watch?v=KdHv9T7Pp98|||||start=80&amp;amp;end=10280&amp;amp;loop=1}}   În continuare vom studia exemple de probleme de programare dinamică și probleme suprapuse date la olimpiadă.  == Problema Faleza ==  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...&amp;quot;&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 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;
&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 (1, 1) la elementul (i, j) vom avea formula de recurență:&lt;br /&gt;
&lt;br /&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}\)&lt;br /&gt;
&lt;br /&gt;
În final vom alege punctul din matrice (l, c) cu proprietatea:&lt;br /&gt;
&lt;br /&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])}\)&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 O(L·C). 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 short int, 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;
Observație: 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 O(L·C) 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 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;
&lt;br /&gt;
== Problema Optim ==&lt;br /&gt;
&lt;br /&gt;
Problema optim a fost dată la ONI 2012 clasa a 8a. 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 K înmulțiri între N 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 O(Comb(N, K)). 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ă MAX[i][j] este optim și are m înmulțiri în coadă, atunci și MAX[i-m-1][j-m] este optim, el fiind format din suma produselor anterioare, care dacă nu ar fi optimă am putea construi o sumă mai mare în MAX[i][j].&lt;br /&gt;
&lt;br /&gt;
Astfel, formula de recurență este:&lt;br /&gt;
&lt;br /&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}\)&lt;br /&gt;
&lt;br /&gt;
Răspunsul la cerință va fi: MAX[N][K].&lt;br /&gt;
&lt;br /&gt;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Ea este O(N·K2). Î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 N x K, deci O(N·K), 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;
Indice012345Valoare203-17-4&lt;br /&gt;
&lt;br /&gt;
Calculul expresiilor maxime, pe linii, de la stânga la dreapta:&lt;br /&gt;
&lt;br /&gt;
Înmulțiri0123N&lt;br /&gt;
u&lt;br /&gt;
m&lt;br /&gt;
e&lt;br /&gt;
r&lt;br /&gt;
e021202530342204119975753386&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema Joc6 ==&lt;br /&gt;
&lt;br /&gt;
Problema 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;
&amp;quot;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.&amp;quot;&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;
&lt;br /&gt;
Vom încerca să cuplăm secvențe astfel:&lt;br /&gt;
&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;
&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 O(n + p). Pentru parcurgerea secvențelor avem nevoie de O(p) pași. Deoarece avem k jucători complexitatea totală este O(k·(n + p).&lt;br /&gt;
&lt;br /&gt;
Dar memoria? Din cauza vectorului de frecvență vom avea nevoie de O(n + p) 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 b 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 i există la intrare, atunci o adăugăm la secvența anterioară ce începe cu bila i+1.&lt;br /&gt;
&lt;br /&gt;
Dacă bila i 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 i+1.&lt;br /&gt;
&lt;br /&gt;
Formula de recurență este:&lt;br /&gt;
&lt;br /&gt;
\(S[b][i] = \begin{cases} 0, &amp;amp; \mbox{dacă } i &amp;gt; n \mbox{ sau } b S[][] pe coloane vom avea nevoie de O(b) memorie, deoarece vom stoca doar o linie, dar O(b) este practic constant. Memorie totală necesară: O(n + b).&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;
Bile20640&lt;br /&gt;
&lt;br /&gt;
Atunci vom calcula tabelul următor:&lt;br /&gt;
&lt;br /&gt;
Bile123456010101Z&lt;br /&gt;
e&lt;br /&gt;
r&lt;br /&gt;
o&lt;br /&gt;
u&lt;br /&gt;
r&lt;br /&gt;
i0020406126410662612101066&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 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;
&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;
&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;
&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;
&lt;br /&gt;
Subproblema N7 se calculează pe baza subproblemelor S și N7, similar cu Kadane: putem continua 7-lea existent, sau să începem unul nou.&lt;br /&gt;
&lt;br /&gt;
Subproblema Z se calculează pe baza subproblemelor D și N7, similar cu Kadane: putem continua Z-ul existent, sau să începem unul nou.&lt;br /&gt;
&lt;br /&gt;
Maximul din subproblema Z 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 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 Nerdarena după tagul &amp;quot;programare dinamică&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 26 ==&lt;br /&gt;
Tema 27 presupune să se rezolve următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
optim&lt;br /&gt;
&lt;br /&gt;
joc6 &lt;br /&gt;
&lt;br /&gt;
faleza&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 26 opțională == &lt;br /&gt;
&lt;br /&gt;
poteci&lt;br /&gt;
&lt;br /&gt;
zmax&lt;br /&gt;
&lt;br /&gt;
flori2&lt;/div&gt;</summary>
		<author><name>Mihai</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=18516</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=18516"/>
		<updated>2025-06-10T12:28:02Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; optimă?&lt;br /&gt;
&lt;br /&gt;
Nu neapărat! Este posibil să găsim o soluție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; cu valoare mai mare, dar pe care să nu o putem folosi în construcția problemei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i-1][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[N][G]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[N][G]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;D[m][n]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;MAX[i]&amp;lt;/source&amp;gt; ca fiind suma tuturor &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;MAX[j]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[N][K]&amp;lt;/source&amp;gt;, elementul din colțul din dreapta-jos al matricei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[N][S]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[2][10]&amp;lt;/source&amp;gt;.&lt;br /&gt;
* Vom calcula câte numere încep cu 2 și au suma cifrelor 11. Vom folosi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[2][9]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[2][6]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[M][N]&amp;lt;/source&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>Mihai</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=18515</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=18515"/>
		<updated>2025-06-10T11:51:41Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; optimă?&lt;br /&gt;
&lt;br /&gt;
Nu neapărat! Este posibil să găsim o soluție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; cu valoare mai mare, dar pe care să nu o putem folosi în construcția problemei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i-1][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[N][G]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[N][G]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;D[m][n]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;MAX[i]&amp;lt;/source&amp;gt; ca fiind suma tuturor &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;MAX[j]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[N][K]&amp;lt;/source&amp;gt;, elementul din colțul din dreapta-jos al matricei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[N][S]&amp;lt;/source&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 N cifre au suma S?&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[2][10]&amp;lt;/source&amp;gt;.&lt;br /&gt;
* Vom calcula câte numere încep cu 2 și au suma cifrelor 11. Vom folosi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[2][9]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;NSOL[2][6]&amp;lt;/source&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 x sau y. 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 k 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 ș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 N ca sumă de M numere nenule. Ordinea termenilor contează.&lt;br /&gt;
&lt;br /&gt;
Vom avea o recurență similară cu cea de la 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 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;
&lt;br /&gt;
Al doilea termen este numărul de secvențe care încep cu 2.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Ultimul termen este numărul de secvențe care încep cu S-M+1, aceasta fiind cel mai mare număr pentru care cifrele rămase mai pot forma suma M.&lt;br /&gt;
&lt;br /&gt;
La fel ca la 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 NSOL[M][N].&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;
&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 M.&lt;br /&gt;
&lt;br /&gt;
Astfel, problema se simplifică dacă:&lt;br /&gt;
&lt;br /&gt;
Considerați că numerele pot fi nule&lt;br /&gt;
&lt;br /&gt;
Reduceți suma cerută, N, înlocuind-o cu N-M.&lt;br /&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 O(M·(N-M)) ca timp și memorie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 26 - cercul de informatică pentru performanță clasa a șaptea ==&lt;br /&gt;
Tema 26 presupune să se rezolve următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
rucsac1 ca aplicație la problema rucsacului.&lt;br /&gt;
&lt;br /&gt;
șiruri2 ca aplicație la distanța edit.&lt;br /&gt;
&lt;br /&gt;
rucsac ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
cifreco dată la ONI 2012 baraj gimnaziu, ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
Tema opțională&lt;br /&gt;
&lt;br /&gt;
Tema 26 opțională presupune să rezolvați următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
pdm ca aplicație la înmulțirea optimală de matrice&lt;br /&gt;
&lt;br /&gt;
cutii problemă simpatică&lt;/div&gt;</summary>
		<author><name>Mihai</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=18514</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=18514"/>
		<updated>2025-06-10T11:38:02Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; optimă?&lt;br /&gt;
&lt;br /&gt;
Nu neapărat! Este posibil să găsim o soluție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; cu valoare mai mare, dar pe care să nu o putem folosi în construcția problemei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i-1][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[N][G]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[N][G]&amp;lt;/source&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 K cu S: KITTEN → SITTEN&lt;br /&gt;
# Înlocuim E cu I: SITTEN → SITTIN&lt;br /&gt;
# Adăugăm la final un G: SITTIN → SITTING&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;D[m][n]&amp;lt;/source&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;
Exemplu: subsecvența crescătoare de lungime maximă&lt;br /&gt;
&lt;br /&gt;
Să luăm exemplul de data trecută: 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ță: L[i] = max(L[j]+1), j &amp;gt; i și V[j] ≥ V[i]&lt;br /&gt;
&lt;br /&gt;
Vom calcula și MAX[i] ca fiind suma tuturor MAX[j] pentru acei indecși j unde relația de maxim este satisfăcută.&lt;br /&gt;
&lt;br /&gt;
Exemplu:&lt;br /&gt;
&lt;br /&gt;
V0841221061419513311715L6453534243323221MAX4722232172312111&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;
Explicație: elementul curent se poate atașa oricărei secvențe maximale anterior găsite. Dacă o secvență anterioară avea k posibilități de formare, atunci și noile secvențe formate vor fi tot în număr de k. 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;
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 &amp;quot;transfera&amp;quot; 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;
== Probleme suprapuse (numărare soluții neoptimale) ==&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;
Rucsac&lt;br /&gt;
&lt;br /&gt;
Atenție: veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema 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 K, numărul dat.&lt;br /&gt;
&lt;br /&gt;
Exemplu&lt;br /&gt;
&lt;br /&gt;
Fie numerele: 8 3 6 5 2 și numărul dat K = 11.&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;
Soluție&lt;br /&gt;
&lt;br /&gt;
Observație importantă:&lt;br /&gt;
&lt;br /&gt;
Nu putem folosi definiția anterioară a problemei, deoarece ea admite soluții care nu &amp;quot;umplu&amp;quot; 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 g. Problema se transformă în există un mod de a forma o greutate g cu primele i 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;
\( 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} \)&lt;br /&gt;
&lt;br /&gt;
unde or(x, y) este 1 dacă măcar unul dintre x, y este 1, sau zero în caz contrar.&lt;br /&gt;
&lt;br /&gt;
Pe ultima linie a matricei V vom avea 1 pentru greutățile ce se pot obține.&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;
\( 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} \) &lt;br /&gt;
&lt;br /&gt;
Numărul de moduri în care putem alege numere de sumă K este NSOL[N][K], elementul din colțul din dreapta-jos al matricei NSOL.&lt;br /&gt;
&lt;br /&gt;
Exemplu&lt;br /&gt;
&lt;br /&gt;
Pentru exemplul anterior, avem numerele: 8 3 6 5 2&lt;br /&gt;
&lt;br /&gt;
și numărul dat K = 11.&lt;br /&gt;
&lt;br /&gt;
Vom calcula tabelul:&lt;br /&gt;
&lt;br /&gt;
01234567891011010000000000011000000010002100100001001310010010110141001011021025101102113123&lt;br /&gt;
&lt;br /&gt;
Avem, deci, trei moduri de a obține suma 11.&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 O(N·K) timp și O(K) memorie, dacă memorăm doar o linie a matricei.&lt;br /&gt;
&lt;br /&gt;
Cifreco&lt;br /&gt;
&lt;br /&gt;
Atenție: veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema 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 x și y, să se găsească cîte numere există între aceste valori cu proprietățile:&lt;br /&gt;
&lt;br /&gt;
Au N cifre, tot atîtea cifre cîte au și numerele x și y.&lt;br /&gt;
&lt;br /&gt;
Suma cifrelor lor este N+8 (la fel ca și x și y).&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 N cifre au suma cifrelor N+8? Cu alte cuvinte nu vom ține cont de valorile x și y.&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 N cifre? Punînd pe prima poziție toate cifrele permise și apoi formând un număr cu N-1 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;
&lt;br /&gt;
Al doilea termen este numărul de numere care încep cu 2&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Ultimul termen este numărul de numere care încep cu S - N + 1, aceasta fiind cea mai mare cifră pentru care cifrele rămase mai pot forma suma N-1.&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 NSOL[N][S]:&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 O(1) la întrebări de forma:&lt;br /&gt;
&lt;br /&gt;
Câte numere de N cifre au suma S?&lt;br /&gt;
&lt;br /&gt;
Acum revenim la problema noastră. Trebuie să calculăm câte numere de N cifre au suma N+8 între x și y. Cum o rezolvăm?&lt;br /&gt;
&lt;br /&gt;
Ne propunem să calculăm câte numere sunt mai mici sau egale cu k. Fie, de exemplu, k = 623. 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 NSOL[2][10].&lt;br /&gt;
&lt;br /&gt;
Vom calcula câte numere încep cu 2 și au suma cifrelor 11. Vom folosi NSOL[2][9].&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Vom calcula câte numere încep cu 5 și au suma cifrelor 11. Vom folosi NSOL[2][6].&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;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Calculul tabelului este O(N·Σ) - 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 x sau y. Este, deci O(suma cifrelor) = O(N) - un număr și mai mic, circa 54.&lt;br /&gt;
&lt;br /&gt;
Aveți această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Atenție! Calculul numărului de numere mai mici sau egale cu k 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;
Șir2&lt;br /&gt;
&lt;br /&gt;
Problema ș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 N ca sumă de M numere nenule. Ordinea termenilor contează.&lt;br /&gt;
&lt;br /&gt;
Vom avea o recurență similară cu cea de la 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 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;
&lt;br /&gt;
Al doilea termen este numărul de secvențe care încep cu 2.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Ultimul termen este numărul de secvențe care încep cu S-M+1, aceasta fiind cel mai mare număr pentru care cifrele rămase mai pot forma suma M.&lt;br /&gt;
&lt;br /&gt;
La fel ca la 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 NSOL[M][N].&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;
&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 M.&lt;br /&gt;
&lt;br /&gt;
Astfel, problema se simplifică dacă:&lt;br /&gt;
&lt;br /&gt;
Considerați că numerele pot fi nule&lt;br /&gt;
&lt;br /&gt;
Reduceți suma cerută, N, înlocuind-o cu N-M.&lt;br /&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 O(M·(N-M)) ca timp și memorie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 26 - cercul de informatică pentru performanță clasa a șaptea ==&lt;br /&gt;
Tema 26 presupune să se rezolve următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
rucsac1 ca aplicație la problema rucsacului.&lt;br /&gt;
&lt;br /&gt;
șiruri2 ca aplicație la distanța edit.&lt;br /&gt;
&lt;br /&gt;
rucsac ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
cifreco dată la ONI 2012 baraj gimnaziu, ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
Tema opțională&lt;br /&gt;
&lt;br /&gt;
Tema 26 opțională presupune să rezolvați următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
pdm ca aplicație la înmulțirea optimală de matrice&lt;br /&gt;
&lt;br /&gt;
cutii problemă simpatică&lt;/div&gt;</summary>
		<author><name>Mihai</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=18513</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=18513"/>
		<updated>2025-06-10T11:33:56Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; optimă?&lt;br /&gt;
&lt;br /&gt;
Nu neapărat! Este posibil să găsim o soluție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; cu valoare mai mare, dar pe care să nu o putem folosi în construcția problemei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;
Construim formula de recurență. Pentru a calcula &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i-1][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[N][G]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;V[N][G]&amp;lt;/source&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 X în Y este 3. De exemplu:&lt;br /&gt;
&lt;br /&gt;
# Înlocuim K cu S: KITTEN → SITTEN&lt;br /&gt;
# Înlocuim E cu I: SITTEN → SITTIN&lt;br /&gt;
# Adăugăm la final un G: SITTIN → SITTING&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;
\( \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} \)&lt;br /&gt;
&lt;br /&gt;
Distanța edit între cele două șiruri este D(m, n).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Analiză ====&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate O(m·n), unde m și n sunt lungimile șirurilor. Memoria folosită este O(min(m, n)) 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 O(m·n), precum vom vedea mai jos.&lt;br /&gt;
&lt;br /&gt;
Există un algoritm care reduce necesarul de memorie la O(min(m, n)) 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 D[m][n] ș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;
&lt;br /&gt;
Direcție spre sus înseamnă o ștergere de caracter.&lt;br /&gt;
&lt;br /&gt;
Direcția diagonală înseamnă:&lt;br /&gt;
&lt;br /&gt;
O suprascriere dacă caracterele respective sunt diferite.&lt;br /&gt;
&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 N 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:&lt;br /&gt;
&lt;br /&gt;
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;
Exemplu: subsecvența crescătoare de lungime maximă&lt;br /&gt;
&lt;br /&gt;
Să luăm exemplul de data trecută: 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ță: L[i] = max(L[j]+1), j &amp;gt; i și V[j] ≥ V[i]&lt;br /&gt;
&lt;br /&gt;
Vom calcula și MAX[i] ca fiind suma tuturor MAX[j] pentru acei indecși j unde relația de maxim este satisfăcută.&lt;br /&gt;
&lt;br /&gt;
Exemplu:&lt;br /&gt;
&lt;br /&gt;
V0841221061419513311715L6453534243323221MAX4722232172312111&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;
Explicație: elementul curent se poate atașa oricărei secvențe maximale anterior găsite. Dacă o secvență anterioară avea k posibilități de formare, atunci și noile secvențe formate vor fi tot în număr de k. 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;
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 &amp;quot;transfera&amp;quot; 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;
== Probleme suprapuse (numărare soluții neoptimale) ==&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;
Rucsac&lt;br /&gt;
&lt;br /&gt;
Atenție: veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema 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 K, numărul dat.&lt;br /&gt;
&lt;br /&gt;
Exemplu&lt;br /&gt;
&lt;br /&gt;
Fie numerele: 8 3 6 5 2 și numărul dat K = 11.&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;
Soluție&lt;br /&gt;
&lt;br /&gt;
Observație importantă:&lt;br /&gt;
&lt;br /&gt;
Nu putem folosi definiția anterioară a problemei, deoarece ea admite soluții care nu &amp;quot;umplu&amp;quot; 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 g. Problema se transformă în există un mod de a forma o greutate g cu primele i 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;
\( 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} \)&lt;br /&gt;
&lt;br /&gt;
unde or(x, y) este 1 dacă măcar unul dintre x, y este 1, sau zero în caz contrar.&lt;br /&gt;
&lt;br /&gt;
Pe ultima linie a matricei V vom avea 1 pentru greutățile ce se pot obține.&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;
\( 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} \) &lt;br /&gt;
&lt;br /&gt;
Numărul de moduri în care putem alege numere de sumă K este NSOL[N][K], elementul din colțul din dreapta-jos al matricei NSOL.&lt;br /&gt;
&lt;br /&gt;
Exemplu&lt;br /&gt;
&lt;br /&gt;
Pentru exemplul anterior, avem numerele: 8 3 6 5 2&lt;br /&gt;
&lt;br /&gt;
și numărul dat K = 11.&lt;br /&gt;
&lt;br /&gt;
Vom calcula tabelul:&lt;br /&gt;
&lt;br /&gt;
01234567891011010000000000011000000010002100100001001310010010110141001011021025101102113123&lt;br /&gt;
&lt;br /&gt;
Avem, deci, trei moduri de a obține suma 11.&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 O(N·K) timp și O(K) memorie, dacă memorăm doar o linie a matricei.&lt;br /&gt;
&lt;br /&gt;
Cifreco&lt;br /&gt;
&lt;br /&gt;
Atenție: veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema 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 x și y, să se găsească cîte numere există între aceste valori cu proprietățile:&lt;br /&gt;
&lt;br /&gt;
Au N cifre, tot atîtea cifre cîte au și numerele x și y.&lt;br /&gt;
&lt;br /&gt;
Suma cifrelor lor este N+8 (la fel ca și x și y).&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 N cifre au suma cifrelor N+8? Cu alte cuvinte nu vom ține cont de valorile x și y.&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 N cifre? Punînd pe prima poziție toate cifrele permise și apoi formând un număr cu N-1 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;
&lt;br /&gt;
Al doilea termen este numărul de numere care încep cu 2&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Ultimul termen este numărul de numere care încep cu S - N + 1, aceasta fiind cea mai mare cifră pentru care cifrele rămase mai pot forma suma N-1.&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 NSOL[N][S]:&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 O(1) la întrebări de forma:&lt;br /&gt;
&lt;br /&gt;
Câte numere de N cifre au suma S?&lt;br /&gt;
&lt;br /&gt;
Acum revenim la problema noastră. Trebuie să calculăm câte numere de N cifre au suma N+8 între x și y. Cum o rezolvăm?&lt;br /&gt;
&lt;br /&gt;
Ne propunem să calculăm câte numere sunt mai mici sau egale cu k. Fie, de exemplu, k = 623. 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 NSOL[2][10].&lt;br /&gt;
&lt;br /&gt;
Vom calcula câte numere încep cu 2 și au suma cifrelor 11. Vom folosi NSOL[2][9].&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Vom calcula câte numere încep cu 5 și au suma cifrelor 11. Vom folosi NSOL[2][6].&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;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Calculul tabelului este O(N·Σ) - 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 x sau y. Este, deci O(suma cifrelor) = O(N) - un număr și mai mic, circa 54.&lt;br /&gt;
&lt;br /&gt;
Aveți această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Atenție! Calculul numărului de numere mai mici sau egale cu k 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;
Șir2&lt;br /&gt;
&lt;br /&gt;
Problema ș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 N ca sumă de M numere nenule. Ordinea termenilor contează.&lt;br /&gt;
&lt;br /&gt;
Vom avea o recurență similară cu cea de la 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 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;
&lt;br /&gt;
Al doilea termen este numărul de secvențe care încep cu 2.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Ultimul termen este numărul de secvențe care încep cu S-M+1, aceasta fiind cel mai mare număr pentru care cifrele rămase mai pot forma suma M.&lt;br /&gt;
&lt;br /&gt;
La fel ca la 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 NSOL[M][N].&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;
&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 M.&lt;br /&gt;
&lt;br /&gt;
Astfel, problema se simplifică dacă:&lt;br /&gt;
&lt;br /&gt;
Considerați că numerele pot fi nule&lt;br /&gt;
&lt;br /&gt;
Reduceți suma cerută, N, înlocuind-o cu N-M.&lt;br /&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 O(M·(N-M)) ca timp și memorie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 26 - cercul de informatică pentru performanță clasa a șaptea ==&lt;br /&gt;
Tema 26 presupune să se rezolve următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
rucsac1 ca aplicație la problema rucsacului.&lt;br /&gt;
&lt;br /&gt;
șiruri2 ca aplicație la distanța edit.&lt;br /&gt;
&lt;br /&gt;
rucsac ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
cifreco dată la ONI 2012 baraj gimnaziu, ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
Tema opțională&lt;br /&gt;
&lt;br /&gt;
Tema 26 opțională presupune să rezolvați următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
pdm ca aplicație la înmulțirea optimală de matrice&lt;br /&gt;
&lt;br /&gt;
cutii problemă simpatică&lt;/div&gt;</summary>
		<author><name>Mihai</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=18512</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=18512"/>
		<updated>2025-06-10T11:33:05Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; optimă?&lt;br /&gt;
&lt;br /&gt;
Nu neapărat! Este posibil să găsim o soluție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; cu valoare mai mare, dar pe care să nu o putem folosi în construcția problemei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/tt&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;
Construim formula de recurență. Pentru a calcula &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i-1][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i-1][g-G&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;] + Pi&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[N][G]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;V[N][G]&amp;lt;/source&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 X în Y este 3. De exemplu:&lt;br /&gt;
&lt;br /&gt;
# Înlocuim K cu S: KITTEN → SITTEN&lt;br /&gt;
# Înlocuim E cu I: SITTEN → SITTIN&lt;br /&gt;
# Adăugăm la final un G: SITTIN → SITTING&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;
\( \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} \)&lt;br /&gt;
&lt;br /&gt;
Distanța edit între cele două șiruri este D(m, n).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Analiză ====&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate O(m·n), unde m și n sunt lungimile șirurilor. Memoria folosită este O(min(m, n)) 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 O(m·n), precum vom vedea mai jos.&lt;br /&gt;
&lt;br /&gt;
Există un algoritm care reduce necesarul de memorie la O(min(m, n)) 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 D[m][n] ș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;
&lt;br /&gt;
Direcție spre sus înseamnă o ștergere de caracter.&lt;br /&gt;
&lt;br /&gt;
Direcția diagonală înseamnă:&lt;br /&gt;
&lt;br /&gt;
O suprascriere dacă caracterele respective sunt diferite.&lt;br /&gt;
&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 N 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:&lt;br /&gt;
&lt;br /&gt;
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;
Exemplu: subsecvența crescătoare de lungime maximă&lt;br /&gt;
&lt;br /&gt;
Să luăm exemplul de data trecută: 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ță: L[i] = max(L[j]+1), j &amp;gt; i și V[j] ≥ V[i]&lt;br /&gt;
&lt;br /&gt;
Vom calcula și MAX[i] ca fiind suma tuturor MAX[j] pentru acei indecși j unde relația de maxim este satisfăcută.&lt;br /&gt;
&lt;br /&gt;
Exemplu:&lt;br /&gt;
&lt;br /&gt;
V0841221061419513311715L6453534243323221MAX4722232172312111&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;
Explicație: elementul curent se poate atașa oricărei secvențe maximale anterior găsite. Dacă o secvență anterioară avea k posibilități de formare, atunci și noile secvențe formate vor fi tot în număr de k. 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;
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 &amp;quot;transfera&amp;quot; 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;
== Probleme suprapuse (numărare soluții neoptimale) ==&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;
Rucsac&lt;br /&gt;
&lt;br /&gt;
Atenție: veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema 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 K, numărul dat.&lt;br /&gt;
&lt;br /&gt;
Exemplu&lt;br /&gt;
&lt;br /&gt;
Fie numerele: 8 3 6 5 2 și numărul dat K = 11.&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;
Soluție&lt;br /&gt;
&lt;br /&gt;
Observație importantă:&lt;br /&gt;
&lt;br /&gt;
Nu putem folosi definiția anterioară a problemei, deoarece ea admite soluții care nu &amp;quot;umplu&amp;quot; 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 g. Problema se transformă în există un mod de a forma o greutate g cu primele i 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;
\( 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} \)&lt;br /&gt;
&lt;br /&gt;
unde or(x, y) este 1 dacă măcar unul dintre x, y este 1, sau zero în caz contrar.&lt;br /&gt;
&lt;br /&gt;
Pe ultima linie a matricei V vom avea 1 pentru greutățile ce se pot obține.&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;
\( 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} \) &lt;br /&gt;
&lt;br /&gt;
Numărul de moduri în care putem alege numere de sumă K este NSOL[N][K], elementul din colțul din dreapta-jos al matricei NSOL.&lt;br /&gt;
&lt;br /&gt;
Exemplu&lt;br /&gt;
&lt;br /&gt;
Pentru exemplul anterior, avem numerele: 8 3 6 5 2&lt;br /&gt;
&lt;br /&gt;
și numărul dat K = 11.&lt;br /&gt;
&lt;br /&gt;
Vom calcula tabelul:&lt;br /&gt;
&lt;br /&gt;
01234567891011010000000000011000000010002100100001001310010010110141001011021025101102113123&lt;br /&gt;
&lt;br /&gt;
Avem, deci, trei moduri de a obține suma 11.&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 O(N·K) timp și O(K) memorie, dacă memorăm doar o linie a matricei.&lt;br /&gt;
&lt;br /&gt;
Cifreco&lt;br /&gt;
&lt;br /&gt;
Atenție: veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema 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 x și y, să se găsească cîte numere există între aceste valori cu proprietățile:&lt;br /&gt;
&lt;br /&gt;
Au N cifre, tot atîtea cifre cîte au și numerele x și y.&lt;br /&gt;
&lt;br /&gt;
Suma cifrelor lor este N+8 (la fel ca și x și y).&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 N cifre au suma cifrelor N+8? Cu alte cuvinte nu vom ține cont de valorile x și y.&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 N cifre? Punînd pe prima poziție toate cifrele permise și apoi formând un număr cu N-1 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;
&lt;br /&gt;
Al doilea termen este numărul de numere care încep cu 2&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Ultimul termen este numărul de numere care încep cu S - N + 1, aceasta fiind cea mai mare cifră pentru care cifrele rămase mai pot forma suma N-1.&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 NSOL[N][S]:&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 O(1) la întrebări de forma:&lt;br /&gt;
&lt;br /&gt;
Câte numere de N cifre au suma S?&lt;br /&gt;
&lt;br /&gt;
Acum revenim la problema noastră. Trebuie să calculăm câte numere de N cifre au suma N+8 între x și y. Cum o rezolvăm?&lt;br /&gt;
&lt;br /&gt;
Ne propunem să calculăm câte numere sunt mai mici sau egale cu k. Fie, de exemplu, k = 623. 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 NSOL[2][10].&lt;br /&gt;
&lt;br /&gt;
Vom calcula câte numere încep cu 2 și au suma cifrelor 11. Vom folosi NSOL[2][9].&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Vom calcula câte numere încep cu 5 și au suma cifrelor 11. Vom folosi NSOL[2][6].&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;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Calculul tabelului este O(N·Σ) - 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 x sau y. Este, deci O(suma cifrelor) = O(N) - un număr și mai mic, circa 54.&lt;br /&gt;
&lt;br /&gt;
Aveți această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Atenție! Calculul numărului de numere mai mici sau egale cu k 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;
Șir2&lt;br /&gt;
&lt;br /&gt;
Problema ș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 N ca sumă de M numere nenule. Ordinea termenilor contează.&lt;br /&gt;
&lt;br /&gt;
Vom avea o recurență similară cu cea de la 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 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;
&lt;br /&gt;
Al doilea termen este numărul de secvențe care încep cu 2.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Ultimul termen este numărul de secvențe care încep cu S-M+1, aceasta fiind cel mai mare număr pentru care cifrele rămase mai pot forma suma M.&lt;br /&gt;
&lt;br /&gt;
La fel ca la 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 NSOL[M][N].&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;
&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 M.&lt;br /&gt;
&lt;br /&gt;
Astfel, problema se simplifică dacă:&lt;br /&gt;
&lt;br /&gt;
Considerați că numerele pot fi nule&lt;br /&gt;
&lt;br /&gt;
Reduceți suma cerută, N, înlocuind-o cu N-M.&lt;br /&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 O(M·(N-M)) ca timp și memorie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 26 - cercul de informatică pentru performanță clasa a șaptea ==&lt;br /&gt;
Tema 26 presupune să se rezolve următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
rucsac1 ca aplicație la problema rucsacului.&lt;br /&gt;
&lt;br /&gt;
șiruri2 ca aplicație la distanța edit.&lt;br /&gt;
&lt;br /&gt;
rucsac ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
cifreco dată la ONI 2012 baraj gimnaziu, ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
Tema opțională&lt;br /&gt;
&lt;br /&gt;
Tema 26 opțională presupune să rezolvați următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
pdm ca aplicație la înmulțirea optimală de matrice&lt;br /&gt;
&lt;br /&gt;
cutii problemă simpatică&lt;/div&gt;</summary>
		<author><name>Mihai</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=18511</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=18511"/>
		<updated>2025-06-10T11:30:40Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Created page with &amp;quot;== Înregistrare video lecție ==  {{#ev:youtube|https://youtu.be/z4433kBdq2A|||||start=5580&amp;amp;end=9780&amp;amp;loop=1}}   == Problema rucsacului ==  &amp;#039;&amp;#039;&amp;#039;Atenție:&amp;#039;&amp;#039;&amp;#039; veți avea la temă ca aplicație problema [https://www.nerdarena.ro/problema/rucsac1 Rucsac1].  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ă m...&amp;quot;&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; optimă?&lt;br /&gt;
&lt;br /&gt;
Nu neapărat! Este posibil să găsim o soluție &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k]&amp;lt;/source&amp;gt; cu valoare mai mare, dar pe care să nu o putem folosi în construcția problemei &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă, &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g-G&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;]&amp;lt;/source&amp;gt; optimă?&lt;br /&gt;
&lt;br /&gt;
Răspunsul este: nu, deoarece s-ar putea ca optimul să includă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[g]&amp;lt;/tt&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&amp;gt; proprietatea de optimalitate a subproblemelor?&lt;br /&gt;
&lt;br /&gt;
Să considerăm o soluție optimă &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[k][g-G&amp;lt;sub&amp;gt;k&amp;lt;/sub&amp;gt;]&amp;lt;/tt&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/tt&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;
Construim formula de recurență. Pentru a calcula &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i-1][g]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i-1][g-G&amp;lt;sub&amp;gt;i&amp;lt;/sub&amp;gt;] + Pi&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[N][G]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;v[]&amp;lt;/source&amp;gt;V[N][G]&amp;lt;/source&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 X în Y este 3. De exemplu:&lt;br /&gt;
&lt;br /&gt;
# Înlocuim K cu S: KITTEN → SITTEN&lt;br /&gt;
# Înlocuim E cu I: SITTEN → SITTIN&lt;br /&gt;
# Adăugăm la final un G: SITTIN → SITTING&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;
\( \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} \)&lt;br /&gt;
&lt;br /&gt;
Distanța edit între cele două șiruri este D(m, n).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==== Analiză ====&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate O(m·n), unde m și n sunt lungimile șirurilor. Memoria folosită este O(min(m, n)) 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 O(m·n), precum vom vedea mai jos.&lt;br /&gt;
&lt;br /&gt;
Există un algoritm care reduce necesarul de memorie la O(min(m, n)) 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 D[m][n] ș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;
&lt;br /&gt;
Direcție spre sus înseamnă o ștergere de caracter.&lt;br /&gt;
&lt;br /&gt;
Direcția diagonală înseamnă:&lt;br /&gt;
&lt;br /&gt;
O suprascriere dacă caracterele respective sunt diferite.&lt;br /&gt;
&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 N 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:&lt;br /&gt;
&lt;br /&gt;
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;
Exemplu: subsecvența crescătoare de lungime maximă&lt;br /&gt;
&lt;br /&gt;
Să luăm exemplul de data trecută: 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ță: L[i] = max(L[j]+1), j &amp;gt; i și V[j] ≥ V[i]&lt;br /&gt;
&lt;br /&gt;
Vom calcula și MAX[i] ca fiind suma tuturor MAX[j] pentru acei indecși j unde relația de maxim este satisfăcută.&lt;br /&gt;
&lt;br /&gt;
Exemplu:&lt;br /&gt;
&lt;br /&gt;
V0841221061419513311715L6453534243323221MAX4722232172312111&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;
Explicație: elementul curent se poate atașa oricărei secvențe maximale anterior găsite. Dacă o secvență anterioară avea k posibilități de formare, atunci și noile secvențe formate vor fi tot în număr de k. 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;
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 &amp;quot;transfera&amp;quot; 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;
== Probleme suprapuse (numărare soluții neoptimale) ==&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;
Rucsac&lt;br /&gt;
&lt;br /&gt;
Atenție: veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema 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 K, numărul dat.&lt;br /&gt;
&lt;br /&gt;
Exemplu&lt;br /&gt;
&lt;br /&gt;
Fie numerele: 8 3 6 5 2 și numărul dat K = 11.&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;
Soluție&lt;br /&gt;
&lt;br /&gt;
Observație importantă:&lt;br /&gt;
&lt;br /&gt;
Nu putem folosi definiția anterioară a problemei, deoarece ea admite soluții care nu &amp;quot;umplu&amp;quot; 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 g. Problema se transformă în există un mod de a forma o greutate g cu primele i 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;
\( 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} \)&lt;br /&gt;
&lt;br /&gt;
unde or(x, y) este 1 dacă măcar unul dintre x, y este 1, sau zero în caz contrar.&lt;br /&gt;
&lt;br /&gt;
Pe ultima linie a matricei V vom avea 1 pentru greutățile ce se pot obține.&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;
\( 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} \) &lt;br /&gt;
&lt;br /&gt;
Numărul de moduri în care putem alege numere de sumă K este NSOL[N][K], elementul din colțul din dreapta-jos al matricei NSOL.&lt;br /&gt;
&lt;br /&gt;
Exemplu&lt;br /&gt;
&lt;br /&gt;
Pentru exemplul anterior, avem numerele: 8 3 6 5 2&lt;br /&gt;
&lt;br /&gt;
și numărul dat K = 11.&lt;br /&gt;
&lt;br /&gt;
Vom calcula tabelul:&lt;br /&gt;
&lt;br /&gt;
01234567891011010000000000011000000010002100100001001310010010110141001011021025101102113123&lt;br /&gt;
&lt;br /&gt;
Avem, deci, trei moduri de a obține suma 11.&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 O(N·K) timp și O(K) memorie, dacă memorăm doar o linie a matricei.&lt;br /&gt;
&lt;br /&gt;
Cifreco&lt;br /&gt;
&lt;br /&gt;
Atenție: veți avea această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Problema 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 x și y, să se găsească cîte numere există între aceste valori cu proprietățile:&lt;br /&gt;
&lt;br /&gt;
Au N cifre, tot atîtea cifre cîte au și numerele x și y.&lt;br /&gt;
&lt;br /&gt;
Suma cifrelor lor este N+8 (la fel ca și x și y).&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 N cifre au suma cifrelor N+8? Cu alte cuvinte nu vom ține cont de valorile x și y.&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 N cifre? Punînd pe prima poziție toate cifrele permise și apoi formând un număr cu N-1 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;
&lt;br /&gt;
Al doilea termen este numărul de numere care încep cu 2&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Ultimul termen este numărul de numere care încep cu S - N + 1, aceasta fiind cea mai mare cifră pentru care cifrele rămase mai pot forma suma N-1.&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 NSOL[N][S]:&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 O(1) la întrebări de forma:&lt;br /&gt;
&lt;br /&gt;
Câte numere de N cifre au suma S?&lt;br /&gt;
&lt;br /&gt;
Acum revenim la problema noastră. Trebuie să calculăm câte numere de N cifre au suma N+8 între x și y. Cum o rezolvăm?&lt;br /&gt;
&lt;br /&gt;
Ne propunem să calculăm câte numere sunt mai mici sau egale cu k. Fie, de exemplu, k = 623. 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 NSOL[2][10].&lt;br /&gt;
&lt;br /&gt;
Vom calcula câte numere încep cu 2 și au suma cifrelor 11. Vom folosi NSOL[2][9].&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Vom calcula câte numere încep cu 5 și au suma cifrelor 11. Vom folosi NSOL[2][6].&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;
Ce complexitate are această soluție?&lt;br /&gt;
&lt;br /&gt;
Calculul tabelului este O(N·Σ) - 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 x sau y. Este, deci O(suma cifrelor) = O(N) - un număr și mai mic, circa 54.&lt;br /&gt;
&lt;br /&gt;
Aveți această problemă la temă.&lt;br /&gt;
&lt;br /&gt;
Atenție! Calculul numărului de numere mai mici sau egale cu k 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;
Șir2&lt;br /&gt;
&lt;br /&gt;
Problema ș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 N ca sumă de M numere nenule. Ordinea termenilor contează.&lt;br /&gt;
&lt;br /&gt;
Vom avea o recurență similară cu cea de la 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 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;
&lt;br /&gt;
Al doilea termen este numărul de secvențe care încep cu 2.&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
Ultimul termen este numărul de secvențe care încep cu S-M+1, aceasta fiind cel mai mare număr pentru care cifrele rămase mai pot forma suma M.&lt;br /&gt;
&lt;br /&gt;
La fel ca la 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 NSOL[M][N].&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;
&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 M.&lt;br /&gt;
&lt;br /&gt;
Astfel, problema se simplifică dacă:&lt;br /&gt;
&lt;br /&gt;
Considerați că numerele pot fi nule&lt;br /&gt;
&lt;br /&gt;
Reduceți suma cerută, N, înlocuind-o cu N-M.&lt;br /&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 O(M·(N-M)) ca timp și memorie.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tema 26 - cercul de informatică pentru performanță clasa a șaptea ==&lt;br /&gt;
Tema 26 presupune să se rezolve următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
rucsac1 ca aplicație la problema rucsacului.&lt;br /&gt;
&lt;br /&gt;
șiruri2 ca aplicație la distanța edit.&lt;br /&gt;
&lt;br /&gt;
rucsac ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
cifreco dată la ONI 2012 baraj gimnaziu, ca aplicație de numărare cu probleme suprapuse&lt;br /&gt;
&lt;br /&gt;
Tema opțională&lt;br /&gt;
&lt;br /&gt;
Tema 26 opțională presupune să rezolvați următoarele probleme (program C trimis la NerdArena):&lt;br /&gt;
&lt;br /&gt;
pdm ca aplicație la înmulțirea optimală de matrice&lt;br /&gt;
&lt;br /&gt;
cutii problemă simpatică&lt;/div&gt;</summary>
		<author><name>Mihai</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=18510</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=18510"/>
		<updated>2025-06-10T10:41:35Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, circa 8KB și de un vector de 4000 de perechi de elemente de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;unsigned int&amp;lt;/source&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>Mihai</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=18509</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=18509"/>
		<updated>2025-06-10T10:39:02Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Created page with &amp;quot;== Înregistrare video lecție ==  {{#ev:youtube|https://youtu.be/z4433kBdq2A|||||start=1&amp;amp;end=3540&amp;amp;loop=1}}    == Problema Parking ==  Problema [https://www.nerdarena.ro/problema/parking Parking] a fost dată la OJI 2024 Clasa a 7-a.  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. Da...&amp;quot;&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&amp;gt;, circa 8KB și de un vector de 4000 de perechi de elemente de tip &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;short&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;unsigned int&amp;lt;/source&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;/div&gt;</summary>
		<author><name>Mihai</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=18508</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=18508"/>
		<updated>2025-06-10T10:04:32Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Tema 24 */&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;/source&amp;gt; de la coadă la cap, căutând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;/source&amp;gt; cel mai mare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[j]&amp;lt;/source&amp;gt; de după el &amp;lt;tt&amp;gt;(j &amp;gt; i)&amp;lt;/tt&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[j] ≥ V[i]&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[15]&amp;lt;/source&amp;gt;, apoi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[14]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i] &amp;gt;= V[6]&amp;lt;/source&amp;gt;. Dintre ele vom alege poziția unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;/source&amp;gt; este maximă. Astfel vom găsi că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[9] = 9&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[9] = 3&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[6]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&amp;gt; calculând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; maximul dintre &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1] + V[i]&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt;. Observăm că, în realitate, vom compara &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1]&amp;lt;/source&amp;gt; cu zero.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; cu aceeași semnificație. Acest &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; se poate calcula cu cunoștințe de nivel mic: sume parțiale. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[]&amp;lt;/source&amp;gt;, sumele parțiale pe vector putem spune că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i] = P[i] - P[k]&amp;lt;/source&amp;gt;. Să ne uităm puțin la această expresie: cine variază în ea? &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&amp;gt;. Este evident că dorim să alegem un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[5][5]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&amp;gt; pe linii, folosind formula de recurență.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[n][m]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[10][9]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[m][n]&amp;lt;/source&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>Mihai</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=18507</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=18507"/>
		<updated>2025-06-10T10:02:42Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Tema 24 */&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;/source&amp;gt; de la coadă la cap, căutând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;/source&amp;gt; cel mai mare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[j]&amp;lt;/source&amp;gt; de după el &amp;lt;tt&amp;gt;(j &amp;gt; i)&amp;lt;/tt&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[j] ≥ V[i]&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[15]&amp;lt;/source&amp;gt;, apoi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[14]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i] &amp;gt;= V[6]&amp;lt;/source&amp;gt;. Dintre ele vom alege poziția unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;/source&amp;gt; este maximă. Astfel vom găsi că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[9] = 9&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[9] = 3&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[6]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&amp;gt; calculând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; maximul dintre &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1] + V[i]&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt;. Observăm că, în realitate, vom compara &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1]&amp;lt;/source&amp;gt; cu zero.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; cu aceeași semnificație. Acest &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; se poate calcula cu cunoștințe de nivel mic: sume parțiale. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[]&amp;lt;/source&amp;gt;, sumele parțiale pe vector putem spune că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i] = P[i] - P[k]&amp;lt;/source&amp;gt;. Să ne uităm puțin la această expresie: cine variază în ea? &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&amp;gt;. Este evident că dorim să alegem un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[5][5]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&amp;gt; pe linii, folosind formula de recurență.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[n][m]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[10][9]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[m][n]&amp;lt;/source&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 Subsecvența comun maximal]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cmlsc Subsecvența comună maximală]&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>Mihai</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=18506</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=18506"/>
		<updated>2025-06-10T09:23:15Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;/source&amp;gt; de la coadă la cap, căutând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;/source&amp;gt; cel mai mare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[j]&amp;lt;/source&amp;gt; de după el &amp;lt;tt&amp;gt;(j &amp;gt; i)&amp;lt;/tt&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[j] ≥ V[i]&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[15]&amp;lt;/source&amp;gt;, apoi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[14]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i] &amp;gt;= V[6]&amp;lt;/source&amp;gt;. Dintre ele vom alege poziția unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;/source&amp;gt; este maximă. Astfel vom găsi că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[9] = 9&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[9] = 3&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[6]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&amp;gt; calculând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; maximul dintre &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1] + V[i]&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt;. Observăm că, în realitate, vom compara &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1]&amp;lt;/source&amp;gt; cu zero.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; cu aceeași semnificație. Acest &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; se poate calcula cu cunoștințe de nivel mic: sume parțiale. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[]&amp;lt;/source&amp;gt;, sumele parțiale pe vector putem spune că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i] = P[i] - P[k]&amp;lt;/source&amp;gt;. Să ne uităm puțin la această expresie: cine variază în ea? &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&amp;gt;. Este evident că dorim să alegem un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[5][5]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&amp;gt; pe linii, folosind formula de recurență.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[n][m]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[10][9]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[m][n]&amp;lt;/source&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 Subșirul de sumă maximă]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sclm Subșirul comun maximal]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cmlsc Subsecvența comună maximală]&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>Mihai</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=18505</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=18505"/>
		<updated>2025-06-10T09:20:50Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt; de la coadă la cap, căutând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;/source&amp;gt; cel mai mare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[j]&amp;lt;/source&amp;gt; de după el &amp;lt;tt&amp;gt;(j &amp;gt; i)&amp;lt;/tt&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[j] ≥ V[i]&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[15]&amp;lt;/source&amp;gt;, apoi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[14]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i] &amp;gt;= V[6]&amp;lt;/source&amp;gt;. Dintre ele vom alege poziția unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;/source&amp;gt; este maximă. Astfel vom găsi că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[9] = 9&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[9] = 3&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[6]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&amp;gt; calculând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; maximul dintre &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1] + V[i]&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt;. Observăm că, în realitate, vom compara &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1]&amp;lt;/source&amp;gt; cu zero.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; cu aceeași semnificație. Acest &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; se poate calcula cu cunoștințe de nivel mic: sume parțiale. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[]&amp;lt;/source&amp;gt;, sumele parțiale pe vector putem spune că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i] = P[i] - P[k]&amp;lt;/source&amp;gt;. Să ne uităm puțin la această expresie: cine variază în ea? &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&amp;gt;. Este evident că dorim să alegem un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[5][5]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&amp;gt; pe linii, folosind formula de recurență.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[n][m]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[10][9]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[m][n]&amp;lt;/source&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 Subșirul de sumă maximă]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sclm Subșirul comun maximal]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cmlsc Subsecvența comună maximală]&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>Mihai</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=18504</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=18504"/>
		<updated>2025-06-10T09:18:27Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt; de la coadă la cap, căutând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt; cel mai mare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[j]&amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt; de după el &amp;lt;tt&amp;gt;(j &amp;gt; i)&amp;lt;/tt&amp;gt; astfel încât &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[j] ≥ V[i]&amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[15]&amp;lt;/source&amp;gt;, apoi &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[14]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i] &amp;gt;= V[6]&amp;lt;/source&amp;gt;. Dintre ele vom alege poziția unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[i]&amp;lt;/source&amp;gt; este maximă. Astfel vom găsi că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[9] = 9&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[9] = 3&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;L[6]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&amp;gt; calculând pentru fiecare element &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; maximul dintre &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1] + V[i]&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;V[i]&amp;lt;/source&amp;gt;. Observăm că, în realitate, vom compara &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i-1]&amp;lt;/source&amp;gt; cu zero.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; cu aceeași semnificație. Acest &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&amp;gt; se poate calcula cu cunoștințe de nivel mic: sume parțiale. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[]&amp;lt;/source&amp;gt;, sumele parțiale pe vector putem spune că &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;S[i] = P[i] - P[k]&amp;lt;/source&amp;gt;. Să ne uităm puțin la această expresie: cine variază în ea? &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[i]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&amp;gt;. Este evident că dorim să alegem un &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;P[k]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[5][5]&amp;lt;/source&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;
Si,j = (i1, j1) (i2, j2) ... (ik, jk)&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M&amp;lt;/source&amp;gt; pe linii, folosind formula de recurență.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[n][m]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[10][9]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;M[m][n]&amp;lt;/source&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 Subșirul de sumă maximă]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/sclm Subșirul comun maximal]&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cmlsc Subsecvența comună maximală]&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>Mihai</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=18503</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=18503"/>
		<updated>2025-06-08T21:10:21Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Created page with &amp;quot;== Înregistrare video lecție ==  {{#ev:youtube|https://www.youtube.com/watch?v=a0wFdh8DInQ|||||start=7600&amp;amp;end=11100&amp;amp;loop=1}}   == Conceptul de programare dinamică == Introducere  Wikipedia definește 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 suprapunere a subproblemelor și de substructură optimală. Atunci când s...&amp;quot;&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;
Introducere&lt;br /&gt;
&lt;br /&gt;
Wikipedia definește 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 suprapunere a subproblemelor și de substructură optimală. 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;
Substructura optimală î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 u la orașul v are proprietatea de substructură optimală: să luăm orice oraș intermediar w pe drumul cel mai scurt, d. Dacă d este cu adevărat drumul cel mai scurt, atunci drumul d1 de la u la w și d2 de la w la v sunt cu adevărat cele mai scurte drumuri între orașele corespunzătoare. Dacă drumul de la u la w 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 d, de la u la v, 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 Bellman-Ford și 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 programare divide et impera (divide and conquer). De aceea quicksort și mergesort nu sunt clasificate ca probleme de programare dinamică.&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 putem calcula soluția finală pe baza unor subprobleme care sunt la rândul lor optimale? 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;
== Subsecvența crescătoare de lungime maximă ==&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;
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;
i1, i2, i3, …, ik&lt;br /&gt;
&lt;br /&gt;
Astfel&lt;br /&gt;
&lt;br /&gt;
L[i1] = k&lt;br /&gt;
&lt;br /&gt;
Este secvența i2, ..., ik optimală? Cu alte cuvinte lungimea secvenței optime care începe la i2 este k-1? Răspunsul este da, secvența care începe la i2, anume i2, ..., ik este o secvență maximă, deoarece altfel am putea-o înlocui cu o secvență mai lungă, ceea ce ar duce la o subsecvență L[i1] 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 L de la coadă la cap, căutând pentru fiecare element L[i] cel mai mare element L[j] de după el (j &amp;gt; i) astfel încât V[j] ≥ V[i].&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul L.&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: L[15], apoi L[14] și așa mai departe. Când ajungem la calculul lui L[6] avem următoarea situație:&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 (7, 8, 9, ...). Le vom lua în considerare doar pe acelea în care V[i] &amp;gt;= V[6]. Dintre ele vom alege poziția unde L[i] este maximă. Astfel vom găsi că V[9] = 9 și L[9] = 3. 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 L[6] - cea mai lungă secvență crescătoare care începe la poziția 6.&lt;br /&gt;
&lt;br /&gt;
Analiză&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate O(n2), unde n este lungimea secvenței. Memoria folosită este O(n).&lt;br /&gt;
&lt;br /&gt;
Există și un algoritm mai rapid, de complexitate O(n log n), bazat pe căutare binară a valorilor de început.&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, N[]. El memorează pentru fiecare indice i un alt indice j care urmează în secvență după j. 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;
== 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;
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;
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 i și j. 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 j-1, 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 i avem două variante: fie continuăm secvența anterioară, care se termină în i-1, fie începem una nouă, care pornește în i. 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 S calculînd pentru fiecare element S[i] maximul dintre S[i-1] + V[i] și V[i]. Observăm că, în realitate, vom compara S[i-1] cu zero.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este maximul din vectorul S.&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;
V12140-461-39S1226 = &lt;br /&gt;
max(14, 12 + 14)26 = &lt;br /&gt;
max(0, 0 + 26)22 = &lt;br /&gt;
max(-4, -4 + 26)83 = &lt;br /&gt;
max(61, 61 + 22)44 = &lt;br /&gt;
max(-39, -39 + 83)&lt;br /&gt;
&lt;br /&gt;
Analiză&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate O(n), unde n este lungimea șirului. Memoria folosită este O(1), deoarece nu avem nevoie să stocăm elementele, ci doar ultima sumă calculată.&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;
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 i să calculăm S[i] cu aceeași semnificație. Acest S[i] se poate calcula cu cunoștințe de nivel mic: sume parțiale. S[i] va fi suma unei secvențe de la k la i, cu k ≤ i. Deci, dacă vom calcula P[], sumele parțiale pe vector putem spune că S[i] = P[i] - P[k]. Să ne uităm puțin la această expresie: cine variază în ea? P[i] este constant, este suma elementelor până la i. Putem varia doar P[k]. Este evident că dorim să alegem un P[k] cât mai mic, nu?&lt;br /&gt;
&lt;br /&gt;
Algoritmul este unul banal: calculează sume parțiale, avansând i. Suma minimă care se termină în i 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 K sau suma maximă de lungime cel mult K.&lt;br /&gt;
&lt;br /&gt;
== Subșirul comun maximal al două șiruri ==&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;
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;
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 i în șirul X și la o poziție j în șirul Y. Ș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 i, respectiv j. Atunci este subșirul M[i-1][j-1] la rândul lui optimal? Cu alte cuvinte subșirul mai scurt cu unu, care se termină la pozițiile anterioare în X, respectiv Y, 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;
\( M[i][j] =\begin{cases}M[i-1][j-1]+1, &amp;amp; \text{dacă X[i]=Y[j]} \\ 0, &amp;amp; \text{în caz contrar} \end{cases} \)&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 M 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;
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 M[5][5] care arată astfel:&lt;br /&gt;
&lt;br /&gt;
CBABAA00101B01020A00203B01030C10000&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;
Analiză&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate O(m·n), unde m și n sunt lungimile șirurilor. Memoria folosită este, la prima vedere, O(m·n). Î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 O(min(m, n)).&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;
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 i și j 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 i - max + 1 și în al doilea șir ca fiind j - max + 1.&lt;br /&gt;
&lt;br /&gt;
[mathjax]&lt;br /&gt;
&lt;br /&gt;
== Subsecvența comună maximală a două șiruri ==&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;
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;
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 i, respectiv j. Ea va trece prin anumite perechi (ik, jk), strict crescătoare, cu proprietatea că X[ik] = Y[jk]:&lt;br /&gt;
&lt;br /&gt;
Si,j = (i1, j1) (i2, j2) ... (ik, jk)&lt;br /&gt;
&lt;br /&gt;
cu proprietatea că:&lt;br /&gt;
&lt;br /&gt;
Xi1Xi2... Xik-1Xik = Yj1Yj2... Yjk-1Yjk&lt;br /&gt;
&lt;br /&gt;
Atunci este lungimea M[ik-1][jk-1] la rândul ei optimală? Cu alte cuvinte subsecvența mai scurtă cu unu, care se termină la poziția ik-1 în X, respectiv poziția jk-1 în Y, 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;
\( M[i][j] =\begin{cases}0, &amp;amp; \text{dacă i = 0 sau j = 0} \\ M[i-1][j-1]+1, &amp;amp; \text{dacă X[i]=Y[j]} \\ max(M[i-1][j], M[i][j-1]), &amp;amp; \text{dacă X[i] ≠ Y[j]} \end{cases} \)&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;
Egalitate&lt;br /&gt;
&lt;br /&gt;
Prefixele X[1..i] și Y[1..j] 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 X[1..i-1] și Y[1..j-1]. La lungimea ei vom aduna unu.&lt;br /&gt;
&lt;br /&gt;
Diferență&lt;br /&gt;
&lt;br /&gt;
Prefixele X[1..i] și Y[1..j] 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;
Cazul 1: subsecvența optimă se termină în R. În acest caz ea nu se poate termina și în K. Deci putem elimina ultimul caracter din Y și calcula M[i][j-1].&lt;br /&gt;
&lt;br /&gt;
Cazul 2: subsecvența optimă nu se termină în R. În acest caz putem elimina acest R și calcula M[i-1][j].&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 M pe linii, folosind formula de recurență.&lt;br /&gt;
&lt;br /&gt;
Soluția problemei este M[n][m].&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 M[10][9] care arată astfel:&lt;br /&gt;
&lt;br /&gt;
CMEFPGHRKA000000000C111111111D111111111E112222222G112223333I112223333K112223334M122223334P122233334R122233344&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;
Analiză&lt;br /&gt;
&lt;br /&gt;
Acest algoritm are complexitate O(m · n), unde m și n sunt lungimile șirurilor. Memoria folosită este O(min(m,n)) 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 O(m · n), precum vom vedea mai jos.&lt;br /&gt;
&lt;br /&gt;
Există un algoritm care reduce necesarul de memorie la O(min(m, n)).&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 M[m][n] ș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 25 - cercul de informatică pentru performanță clasa a șaptea ==&lt;br /&gt;
Tema 25 presupune să se rezolve următoarele probleme (program C trimis la nerdarena):&lt;br /&gt;
&lt;br /&gt;
dreptunghiuri pentru subsecvența crescătoare maximală. Atenţie: puteți pica teste cu TLE, este în regulă.&lt;br /&gt;
&lt;br /&gt;
subșirul de sumă maximă&lt;br /&gt;
&lt;br /&gt;
subșirul comun maximal&lt;br /&gt;
&lt;br /&gt;
subsecvența comună maximală&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18502</id>
		<title>Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18502"/>
		<updated>2025-06-08T20:24:28Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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=11100&amp;amp;loop=1}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Ceas:&#039;&#039;&#039; aveți un ceas la voi. Nu ceasul calculatorului 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;
&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. &#039;&#039;Acest sfat nu mai este atât de valabil de când concursul se desfășoară online. Nu veți putea cere zece minute pierdute, deci încercați să faceți ceva în acele minute, de exemplu să vă gândiți la o altă problemă, pe hârtie. Dacă totuși pierdeți timp semnificativ, cum ar fi 30 minute, atunci aveți baze să faceți contestație ulterior. Cel mai probabil nu veți câștiga puncte, dar e posibil să facă o excepție dacă punctajul vostru e la limită.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile:&#039;&#039;&#039; Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare. Încercați să formulați cât mai clar întrebările! Sunt preferabile cele al căror răspuns este &#039;&#039;DA&#039;&#039; sau &#039;&#039;NU&#039;&#039;. Puteți da și exemple în întrebările voastre, &#039;&#039;dacă intrarea ar fi așa atunci răspund așa, sau așa?&#039;&#039;. Dacă exemplul este prea relevant este posibil să nu vă răspundă, dar nu aveți nimic de pierdut.&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat:&#039;&#039;&#039; intrați în sală încrezători în voi. Sunteț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;
&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele:&#039;&#039;&#039; atunci când constatați că problemele sunt 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, consumând timp, ceilalți le vor picta la perfecție.&lt;br /&gt;
&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: &#039;&#039;&#039;-Wall&#039;&#039;&#039; și &#039;&#039;&#039;-O2&#039;&#039;&#039;; la locul unde ați salvat proiectul, și numele proiectului în caz că aveți nevoie să le copiați în altă parte, dacă, de exemplu, vă schimbă calculatorul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ce să &amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;NU&amp;lt;/span&amp;gt; faceți ===&lt;br /&gt;
&lt;br /&gt;
* Nu îngrășați porcul în ajun: 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați: panica ucide creierul&#039;&#039;&#039;. 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;
&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;&#039;aaah, am făcut-o și pe asta, super&#039;&#039; sau &#039;&#039;am rupt, sigur iau suta&#039;&#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;&#039;sigur mă calific&#039;&#039; sau &#039;&#039;am făcut bine, yesss&#039;&#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;
&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ă nu mai aveți idei, creați mai multe teste pe care să le testați. Puteți scrie programe care să vă genereze teste. Puteți cere la CMS să vi se evalueze acele teste. 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați și trimiteț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ă. În principiu punctajul este maximul din toate sursele trimise. Nu riscați, dacă schimbă regula în ultima clipă? N-ar fi prima oară.&lt;br /&gt;
&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. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi suntem de partea voastră. Chiar dacă la cerc sau la clasă, când suntem î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ă &#039;&#039;luptați&#039;&#039; cu olimpiada suntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Diferenţa dintre olimpiadă şi viaţa reală ===&lt;br /&gt;
&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 7a, 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. Lupta este interzisă cu excepţia cazului când sunteţ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. La fel și informatica, în viața reală trebuie să inventăm un algoritm cât mai bun și să îl implementăm cât mai frumos, mai precis, fără a repeta cod, cât mai clar de citit și de alții, cu comentarii, etc.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi când sunteţ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ă sunt 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;
&#039;&#039;&#039;Atenţie însă!&#039;&#039;&#039; 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;
&lt;br /&gt;
== Tema 28 ==&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/foto1 Foto1] dată la OJI 2020 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cursuri Cursuri] dată la OJI 2017, clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/pseudocomp Pseudocomp] dată la ONI 2011 clasa a 8-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_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83 Accesează rezolvarea temei 28]&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18501</id>
		<title>Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18501"/>
		<updated>2025-06-08T20:18:06Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{#evt:&lt;br /&gt;
service=youtube&lt;br /&gt;
|id=https://www.youtube.com/watch?v=eAORm-8b1Eg&lt;br /&gt;
|alignment=right&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Ceas:&#039;&#039;&#039; aveți un ceas la voi. Nu ceasul calculatorului 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;
&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. &#039;&#039;Acest sfat nu mai este atât de valabil de când concursul se desfășoară online. Nu veți putea cere zece minute pierdute, deci încercați să faceți ceva în acele minute, de exemplu să vă gândiți la o altă problemă, pe hârtie. Dacă totuși pierdeți timp semnificativ, cum ar fi 30 minute, atunci aveți baze să faceți contestație ulterior. Cel mai probabil nu veți câștiga puncte, dar e posibil să facă o excepție dacă punctajul vostru e la limită.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile:&#039;&#039;&#039; Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare. Încercați să formulați cât mai clar întrebările! Sunt preferabile cele al căror răspuns este &#039;&#039;DA&#039;&#039; sau &#039;&#039;NU&#039;&#039;. Puteți da și exemple în întrebările voastre, &#039;&#039;dacă intrarea ar fi așa atunci răspund așa, sau așa?&#039;&#039;. Dacă exemplul este prea relevant este posibil să nu vă răspundă, dar nu aveți nimic de pierdut.&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat:&#039;&#039;&#039; intrați în sală încrezători în voi. Sunteț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;
&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele:&#039;&#039;&#039; atunci când constatați că problemele sunt 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, consumând timp, ceilalți le vor picta la perfecție.&lt;br /&gt;
&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: &#039;&#039;&#039;-Wall&#039;&#039;&#039; și &#039;&#039;&#039;-O2&#039;&#039;&#039;; la locul unde ați salvat proiectul, și numele proiectului în caz că aveți nevoie să le copiați în altă parte, dacă, de exemplu, vă schimbă calculatorul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ce să &amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;NU&amp;lt;/span&amp;gt; faceți ===&lt;br /&gt;
&lt;br /&gt;
* Nu îngrășați porcul în ajun: 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați: panica ucide creierul&#039;&#039;&#039;. 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;
&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;&#039;aaah, am făcut-o și pe asta, super&#039;&#039; sau &#039;&#039;am rupt, sigur iau suta&#039;&#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;&#039;sigur mă calific&#039;&#039; sau &#039;&#039;am făcut bine, yesss&#039;&#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;
&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ă nu mai aveți idei, creați mai multe teste pe care să le testați. Puteți scrie programe care să vă genereze teste. Puteți cere la CMS să vi se evalueze acele teste. 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați și trimiteț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ă. În principiu punctajul este maximul din toate sursele trimise. Nu riscați, dacă schimbă regula în ultima clipă? N-ar fi prima oară.&lt;br /&gt;
&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. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi suntem de partea voastră. Chiar dacă la cerc sau la clasă, când suntem î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ă &#039;&#039;luptați&#039;&#039; cu olimpiada suntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Diferenţa dintre olimpiadă şi viaţa reală ===&lt;br /&gt;
&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 7a, 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. Lupta este interzisă cu excepţia cazului când sunteţ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. La fel și informatica, în viața reală trebuie să inventăm un algoritm cât mai bun și să îl implementăm cât mai frumos, mai precis, fără a repeta cod, cât mai clar de citit și de alții, cu comentarii, etc.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi când sunteţ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ă sunt 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;
&#039;&#039;&#039;Atenţie însă!&#039;&#039;&#039; 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;
&lt;br /&gt;
== Tema 28 ==&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/foto1 Foto1] dată la OJI 2020 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cursuri Cursuri] dată la OJI 2017, clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/pseudocomp Pseudocomp] dată la ONI 2011 clasa a 8-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_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83 Accesează rezolvarea temei 28]&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18500</id>
		<title>Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18500"/>
		<updated>2025-06-08T20:17:04Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Ceas:&#039;&#039;&#039; aveți un ceas la voi. Nu ceasul calculatorului 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;
&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. &#039;&#039;Acest sfat nu mai este atât de valabil de când concursul se desfășoară online. Nu veți putea cere zece minute pierdute, deci încercați să faceți ceva în acele minute, de exemplu să vă gândiți la o altă problemă, pe hârtie. Dacă totuși pierdeți timp semnificativ, cum ar fi 30 minute, atunci aveți baze să faceți contestație ulterior. Cel mai probabil nu veți câștiga puncte, dar e posibil să facă o excepție dacă punctajul vostru e la limită.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile:&#039;&#039;&#039; Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare. Încercați să formulați cât mai clar întrebările! Sunt preferabile cele al căror răspuns este &#039;&#039;DA&#039;&#039; sau &#039;&#039;NU&#039;&#039;. Puteți da și exemple în întrebările voastre, &#039;&#039;dacă intrarea ar fi așa atunci răspund așa, sau așa?&#039;&#039;. Dacă exemplul este prea relevant este posibil să nu vă răspundă, dar nu aveți nimic de pierdut.&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat:&#039;&#039;&#039; intrați în sală încrezători în voi. Sunteț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;
&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele:&#039;&#039;&#039; atunci când constatați că problemele sunt 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, consumând timp, ceilalți le vor picta la perfecție.&lt;br /&gt;
&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: &#039;&#039;&#039;-Wall&#039;&#039;&#039; și &#039;&#039;&#039;-O2&#039;&#039;&#039;; la locul unde ați salvat proiectul, și numele proiectului în caz că aveți nevoie să le copiați în altă parte, dacă, de exemplu, vă schimbă calculatorul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ce să &amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;NU&amp;lt;/span&amp;gt; faceți ===&lt;br /&gt;
&lt;br /&gt;
* Nu îngrășați porcul în ajun: 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați: panica ucide creierul&#039;&#039;&#039;. 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;
&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;&#039;aaah, am făcut-o și pe asta, super&#039;&#039; sau &#039;&#039;am rupt, sigur iau suta&#039;&#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;&#039;sigur mă calific&#039;&#039; sau &#039;&#039;am făcut bine, yesss&#039;&#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;
&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ă nu mai aveți idei, creați mai multe teste pe care să le testați. Puteți scrie programe care să vă genereze teste. Puteți cere la CMS să vi se evalueze acele teste. 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați și trimiteț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ă. În principiu punctajul este maximul din toate sursele trimise. Nu riscați, dacă schimbă regula în ultima clipă? N-ar fi prima oară.&lt;br /&gt;
&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. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi suntem de partea voastră. Chiar dacă la cerc sau la clasă, când suntem î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ă &#039;&#039;luptați&#039;&#039; cu olimpiada suntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Diferenţa dintre olimpiadă şi viaţa reală ===&lt;br /&gt;
&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 7a, 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. Lupta este interzisă cu excepţia cazului când sunteţ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. La fel și informatica, în viața reală trebuie să inventăm un algoritm cât mai bun și să îl implementăm cât mai frumos, mai precis, fără a repeta cod, cât mai clar de citit și de alții, cu comentarii, etc.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi când sunteţ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ă sunt 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;
&#039;&#039;&#039;Atenţie însă!&#039;&#039;&#039; 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;
&lt;br /&gt;
== Tema 28 ==&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/foto1 Foto1] dată la OJI 2020 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cursuri Cursuri] dată la OJI 2017, clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/pseudocomp Pseudocomp] dată la ONI 2011 clasa a 8-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_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83 Accesează rezolvarea temei 28]&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18499</id>
		<title>Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18499"/>
		<updated>2025-06-08T20:11:08Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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/a0wFdh8DInQ?start=11100&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Ceas:&#039;&#039;&#039; aveți un ceas la voi. Nu ceasul calculatorului 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;
&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. &#039;&#039;Acest sfat nu mai este atât de valabil de când concursul se desfășoară online. Nu veți putea cere zece minute pierdute, deci încercați să faceți ceva în acele minute, de exemplu să vă gândiți la o altă problemă, pe hârtie. Dacă totuși pierdeți timp semnificativ, cum ar fi 30 minute, atunci aveți baze să faceți contestație ulterior. Cel mai probabil nu veți câștiga puncte, dar e posibil să facă o excepție dacă punctajul vostru e la limită.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile:&#039;&#039;&#039; Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare. Încercați să formulați cât mai clar întrebările! Sunt preferabile cele al căror răspuns este &#039;&#039;DA&#039;&#039; sau &#039;&#039;NU&#039;&#039;. Puteți da și exemple în întrebările voastre, &#039;&#039;dacă intrarea ar fi așa atunci răspund așa, sau așa?&#039;&#039;. Dacă exemplul este prea relevant este posibil să nu vă răspundă, dar nu aveți nimic de pierdut.&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat:&#039;&#039;&#039; intrați în sală încrezători în voi. Sunteț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;
&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele:&#039;&#039;&#039; atunci când constatați că problemele sunt 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, consumând timp, ceilalți le vor picta la perfecție.&lt;br /&gt;
&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: &#039;&#039;&#039;-Wall&#039;&#039;&#039; și &#039;&#039;&#039;-O2&#039;&#039;&#039;; la locul unde ați salvat proiectul, și numele proiectului în caz că aveți nevoie să le copiați în altă parte, dacă, de exemplu, vă schimbă calculatorul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ce să &amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;NU&amp;lt;/span&amp;gt; faceți ===&lt;br /&gt;
&lt;br /&gt;
* Nu îngrășați porcul în ajun: 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați: panica ucide creierul&#039;&#039;&#039;. 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;
&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;&#039;aaah, am făcut-o și pe asta, super&#039;&#039; sau &#039;&#039;am rupt, sigur iau suta&#039;&#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;&#039;sigur mă calific&#039;&#039; sau &#039;&#039;am făcut bine, yesss&#039;&#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;
&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ă nu mai aveți idei, creați mai multe teste pe care să le testați. Puteți scrie programe care să vă genereze teste. Puteți cere la CMS să vi se evalueze acele teste. 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați și trimiteț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ă. În principiu punctajul este maximul din toate sursele trimise. Nu riscați, dacă schimbă regula în ultima clipă? N-ar fi prima oară.&lt;br /&gt;
&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. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi suntem de partea voastră. Chiar dacă la cerc sau la clasă, când suntem î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ă &#039;&#039;luptați&#039;&#039; cu olimpiada suntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Diferenţa dintre olimpiadă şi viaţa reală ===&lt;br /&gt;
&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 7a, 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. Lupta este interzisă cu excepţia cazului când sunteţ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. La fel și informatica, în viața reală trebuie să inventăm un algoritm cât mai bun și să îl implementăm cât mai frumos, mai precis, fără a repeta cod, cât mai clar de citit și de alții, cu comentarii, etc.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi când sunteţ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ă sunt 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;
&#039;&#039;&#039;Atenţie însă!&#039;&#039;&#039; 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;
&lt;br /&gt;
== Tema 28 ==&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/foto1 Foto1] dată la OJI 2020 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cursuri Cursuri] dată la OJI 2017, clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/pseudocomp Pseudocomp] dată la ONI 2011 clasa a 8-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_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83 Accesează rezolvarea temei 28]&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18498</id>
		<title>Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18498"/>
		<updated>2025-06-08T20:08:59Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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/a0wFdh8DInQ?start=1&amp;amp;end=7207&amp;lt;/youtube&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Ceas:&#039;&#039;&#039; aveți un ceas la voi. Nu ceasul calculatorului 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;
&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. &#039;&#039;Acest sfat nu mai este atât de valabil de când concursul se desfășoară online. Nu veți putea cere zece minute pierdute, deci încercați să faceți ceva în acele minute, de exemplu să vă gândiți la o altă problemă, pe hârtie. Dacă totuși pierdeți timp semnificativ, cum ar fi 30 minute, atunci aveți baze să faceți contestație ulterior. Cel mai probabil nu veți câștiga puncte, dar e posibil să facă o excepție dacă punctajul vostru e la limită.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile:&#039;&#039;&#039; Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare. Încercați să formulați cât mai clar întrebările! Sunt preferabile cele al căror răspuns este &#039;&#039;DA&#039;&#039; sau &#039;&#039;NU&#039;&#039;. Puteți da și exemple în întrebările voastre, &#039;&#039;dacă intrarea ar fi așa atunci răspund așa, sau așa?&#039;&#039;. Dacă exemplul este prea relevant este posibil să nu vă răspundă, dar nu aveți nimic de pierdut.&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat:&#039;&#039;&#039; intrați în sală încrezători în voi. Sunteț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;
&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele:&#039;&#039;&#039; atunci când constatați că problemele sunt 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, consumând timp, ceilalți le vor picta la perfecție.&lt;br /&gt;
&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: &#039;&#039;&#039;-Wall&#039;&#039;&#039; și &#039;&#039;&#039;-O2&#039;&#039;&#039;; la locul unde ați salvat proiectul, și numele proiectului în caz că aveți nevoie să le copiați în altă parte, dacă, de exemplu, vă schimbă calculatorul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ce să &amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;NU&amp;lt;/span&amp;gt; faceți ===&lt;br /&gt;
&lt;br /&gt;
* Nu îngrășați porcul în ajun: 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați: panica ucide creierul&#039;&#039;&#039;. 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;
&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;&#039;aaah, am făcut-o și pe asta, super&#039;&#039; sau &#039;&#039;am rupt, sigur iau suta&#039;&#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;&#039;sigur mă calific&#039;&#039; sau &#039;&#039;am făcut bine, yesss&#039;&#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;
&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ă nu mai aveți idei, creați mai multe teste pe care să le testați. Puteți scrie programe care să vă genereze teste. Puteți cere la CMS să vi se evalueze acele teste. 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați și trimiteț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ă. În principiu punctajul este maximul din toate sursele trimise. Nu riscați, dacă schimbă regula în ultima clipă? N-ar fi prima oară.&lt;br /&gt;
&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. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi suntem de partea voastră. Chiar dacă la cerc sau la clasă, când suntem î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ă &#039;&#039;luptați&#039;&#039; cu olimpiada suntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Diferenţa dintre olimpiadă şi viaţa reală ===&lt;br /&gt;
&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 7a, 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. Lupta este interzisă cu excepţia cazului când sunteţ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. La fel și informatica, în viața reală trebuie să inventăm un algoritm cât mai bun și să îl implementăm cât mai frumos, mai precis, fără a repeta cod, cât mai clar de citit și de alții, cu comentarii, etc.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi când sunteţ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ă sunt 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;
&#039;&#039;&#039;Atenţie însă!&#039;&#039;&#039; 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;
&lt;br /&gt;
== Tema 28 ==&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/foto1 Foto1] dată la OJI 2020 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cursuri Cursuri] dată la OJI 2017, clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/pseudocomp Pseudocomp] dată la ONI 2011 clasa a 8-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_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83 Accesează rezolvarea temei 28]&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18497</id>
		<title>Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18497"/>
		<updated>2025-06-08T19:58:45Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Ceas:&#039;&#039;&#039; aveți un ceas la voi. Nu ceasul calculatorului 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;
&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. &#039;&#039;Acest sfat nu mai este atât de valabil de când concursul se desfășoară online. Nu veți putea cere zece minute pierdute, deci încercați să faceți ceva în acele minute, de exemplu să vă gândiți la o altă problemă, pe hârtie. Dacă totuși pierdeți timp semnificativ, cum ar fi 30 minute, atunci aveți baze să faceți contestație ulterior. Cel mai probabil nu veți câștiga puncte, dar e posibil să facă o excepție dacă punctajul vostru e la limită.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile:&#039;&#039;&#039; Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare. Încercați să formulați cât mai clar întrebările! Sunt preferabile cele al căror răspuns este &#039;&#039;DA&#039;&#039; sau &#039;&#039;NU&#039;&#039;. Puteți da și exemple în întrebările voastre, &#039;&#039;dacă intrarea ar fi așa atunci răspund așa, sau așa?&#039;&#039;. Dacă exemplul este prea relevant este posibil să nu vă răspundă, dar nu aveți nimic de pierdut.&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat:&#039;&#039;&#039; intrați în sală încrezători în voi. Sunteț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;
&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele:&#039;&#039;&#039; atunci când constatați că problemele sunt 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, consumând timp, ceilalți le vor picta la perfecție.&lt;br /&gt;
&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: &#039;&#039;&#039;-Wall&#039;&#039;&#039; și &#039;&#039;&#039;-O2&#039;&#039;&#039;; la locul unde ați salvat proiectul, și numele proiectului în caz că aveți nevoie să le copiați în altă parte, dacă, de exemplu, vă schimbă calculatorul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ce să &amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;NU&amp;lt;/span&amp;gt; faceți ===&lt;br /&gt;
&lt;br /&gt;
* Nu îngrășați porcul în ajun: 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați: panica ucide creierul&#039;&#039;&#039;. 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;
&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;&#039;aaah, am făcut-o și pe asta, super&#039;&#039; sau &#039;&#039;am rupt, sigur iau suta&#039;&#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;&#039;sigur mă calific&#039;&#039; sau &#039;&#039;am făcut bine, yesss&#039;&#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;
&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ă nu mai aveți idei, creați mai multe teste pe care să le testați. Puteți scrie programe care să vă genereze teste. Puteți cere la CMS să vi se evalueze acele teste. 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați și trimiteț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ă. În principiu punctajul este maximul din toate sursele trimise. Nu riscați, dacă schimbă regula în ultima clipă? N-ar fi prima oară.&lt;br /&gt;
&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. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi suntem de partea voastră. Chiar dacă la cerc sau la clasă, când suntem î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ă &#039;&#039;luptați&#039;&#039; cu olimpiada suntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Diferenţa dintre olimpiadă şi viaţa reală ===&lt;br /&gt;
&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 7a, 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. Lupta este interzisă cu excepţia cazului când sunteţ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. La fel și informatica, în viața reală trebuie să inventăm un algoritm cât mai bun și să îl implementăm cât mai frumos, mai precis, fără a repeta cod, cât mai clar de citit și de alții, cu comentarii, etc.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi când sunteţ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ă sunt 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;
&#039;&#039;&#039;Atenţie însă!&#039;&#039;&#039; 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;
&lt;br /&gt;
== Tema 28 ==&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/foto1 Foto1] dată la OJI 2020 clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/cursuri Cursuri] dată la OJI 2017, clasa a 7-a&lt;br /&gt;
* [https://www.nerdarena.ro/problema/pseudocomp Pseudocomp] dată la ONI 2011 clasa a 8-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_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83 Accesează rezolvarea temei 28]&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18496</id>
		<title>Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18496"/>
		<updated>2025-06-08T19:52:55Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Ceas:&#039;&#039;&#039; aveți un ceas la voi. Nu ceasul calculatorului 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;
&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. Acest sfat nu mai este atât de valabil de când concursul se desfășoară online. Nu veți putea cere zece minute pierdute, deci încercați să faceți ceva în acele minute, de exemplu să vă gândiți la o altă problemă, pe hârtie. Dacă totuși pierdeți timp semnificativ, cum ar fi 30 minute, atunci aveți baze să faceți contestație ulterior. Cel mai probabil nu veți câștiga puncte, dar e posibil să facă o excepție dacă punctajul vostru e la limită.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile:&#039;&#039;&#039; Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare. Încercați să formulați cât mai clar întrebările! Sunt preferabile cele al căror răspuns este DA sau NU. Puteți da și exemple în întrebările voastre, &amp;quot;dacă intrarea ar fi așa atunci răspund așa, sau așa?&amp;quot;. Dacă exemplul este prea relevant este posibil să nu vă răspundă, dar nu aveți nimic de pierdut.&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat:&#039;&#039;&#039; intrați în sală încrezători în voi. Sunteț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;
&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele:&#039;&#039;&#039; atunci când constatați că problemele sunt 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, consumând timp, ceilalți le vor picta la perfecție.&lt;br /&gt;
&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, dacă, de exemplu, vă schimbă calculatorul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ce să &amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;NU&amp;lt;/span&amp;gt; faceți ===&lt;br /&gt;
&lt;br /&gt;
* Nu îngrășați porcul în ajun: 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați: panica ucide creierul&#039;&#039;&#039;. 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;
&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 bine, 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;
&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ă nu mai aveți idei, creați mai multe teste pe care să le testați. Puteți scrie programe care să vă genereze teste. Puteți cere la CMS să vi se evalueze acele teste. 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați și trimiteț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ă. În principiu punctajul este maximul din toate sursele trimise. Nu riscați, dacă schimbă regula în ultima clipă? N-ar fi prima oară.&lt;br /&gt;
&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. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi suntem de partea voastră. Chiar dacă la cerc sau la clasă, când suntem î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 suntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Diferenţa dintre olimpiadă şi viaţa reală ===&lt;br /&gt;
&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 7a, 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. Olimpiada nu ne defineşte ca oameni.&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. Lupta este interzisă cu excepţia cazului când sunteţ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. La fel și informatica, în viața reală trebuie să inventăm un algoritm cât mai bun și să îl implementăm cât mai frumos, mai precis, fără a repeta cod, cât mai clar de citit și de alții, cu comentarii, etc.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi când sunteţ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ă sunt 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;
&#039;&#039;&#039;Atenţie însă!&#039;&#039;&#039; 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;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18495</id>
		<title>Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18495"/>
		<updated>2025-06-08T19:52:47Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Ce să NU&amp;lt;/span faceți */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Ceas:&#039;&#039;&#039; aveți un ceas la voi. Nu ceasul calculatorului 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;
&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. Acest sfat nu mai este atât de valabil de când concursul se desfășoară online. Nu veți putea cere zece minute pierdute, deci încercați să faceți ceva în acele minute, de exemplu să vă gândiți la o altă problemă, pe hârtie. Dacă totuși pierdeți timp semnificativ, cum ar fi 30 minute, atunci aveți baze să faceți contestație ulterior. Cel mai probabil nu veți câștiga puncte, dar e posibil să facă o excepție dacă punctajul vostru e la limită.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile:&#039;&#039;&#039; Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare. Încercați să formulați cât mai clar întrebările! Sunt preferabile cele al căror răspuns este DA sau NU. Puteți da și exemple în întrebările voastre, &amp;quot;dacă intrarea ar fi așa atunci răspund așa, sau așa?&amp;quot;. Dacă exemplul este prea relevant este posibil să nu vă răspundă, dar nu aveți nimic de pierdut.&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat:&#039;&#039;&#039; intrați în sală încrezători în voi. Sunteț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;
&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele:&#039;&#039;&#039; atunci când constatați că problemele sunt 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, consumând timp, ceilalți le vor picta la perfecție.&lt;br /&gt;
&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, dacă, de exemplu, vă schimbă calculatorul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ce să &amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;NU&amp;lt;/span&amp;gt; faceți ===&lt;br /&gt;
&lt;br /&gt;
* Nu îngrășați porcul în ajun: 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați: panica ucide creierul&#039;&#039;&#039;. 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;
&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 bine, 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;
&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ă nu mai aveți idei, creați mai multe teste pe care să le testați. Puteți scrie programe care să vă genereze teste. Puteți cere la CMS să vi se evalueze acele teste. 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați și trimiteț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ă. În principiu punctajul este maximul din toate sursele trimise. Nu riscați, dacă schimbă regula în ultima clipă? N-ar fi prima oară.&lt;br /&gt;
&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. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi suntem de partea voastră. Chiar dacă la cerc sau la clasă, când suntem î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 suntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
=== Diferenţa dintre olimpiadă şi viaţa reală ===&lt;br /&gt;
&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 7a, 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. Olimpiada nu ne defineşte ca oameni.&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. Lupta este interzisă cu excepţia cazului când sunteţ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. La fel și informatica, în viața reală trebuie să inventăm un algoritm cât mai bun și să îl implementăm cât mai frumos, mai precis, fără a repeta cod, cât mai clar de citit și de alții, cu comentarii, etc.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi când sunteţ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ă sunt 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;
&#039;&#039;&#039;Atenţie însă!&#039;&#039;&#039; 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;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18494</id>
		<title>Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_28:_Sfaturi_pentru_concursuri_/_olimpiad%C4%83&amp;diff=18494"/>
		<updated>2025-06-08T19:52:37Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Created page with &amp;quot;== Ce să faceți / ce să nu faceți ==  === Ce să faceți ===  * &amp;#039;&amp;#039;&amp;#039;Odihnă:&amp;#039;&amp;#039;&amp;#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.  * &amp;#039;&amp;#039;&amp;#039;Ceas:&amp;#039;&amp;#039;&amp;#039; aveți un ceas la voi. Nu ceasul calculatorului 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...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Ce să faceți / ce să nu faceți ==&lt;br /&gt;
&lt;br /&gt;
=== Ce să faceți ===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Ceas:&#039;&#039;&#039; aveți un ceas la voi. Nu ceasul calculatorului 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;
&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. Acest sfat nu mai este atât de valabil de când concursul se desfășoară online. Nu veți putea cere zece minute pierdute, deci încercați să faceți ceva în acele minute, de exemplu să vă gândiți la o altă problemă, pe hârtie. Dacă totuși pierdeți timp semnificativ, cum ar fi 30 minute, atunci aveți baze să faceți contestație ulterior. Cel mai probabil nu veți câștiga puncte, dar e posibil să facă o excepție dacă punctajul vostru e la limită.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Cereți-vă drepturile:&#039;&#039;&#039; Dacă nu înțelegeți textul unei probleme, după ce l-ați citit cu mare atenție, puneți întrebări pentru clarificare. Încercați să formulați cât mai clar întrebările! Sunt preferabile cele al căror răspuns este DA sau NU. Puteți da și exemple în întrebările voastre, &amp;quot;dacă intrarea ar fi așa atunci răspund așa, sau așa?&amp;quot;. Dacă exemplul este prea relevant este posibil să nu vă răspundă, dar nu aveți nimic de pierdut.&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;Moral ridicat:&#039;&#039;&#039; intrați în sală încrezători în voi. Sunteț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;
&lt;br /&gt;
* &#039;&#039;&#039;Probleme grele:&#039;&#039;&#039; atunci când constatați că problemele sunt 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, consumând timp, ceilalți le vor picta la perfecție.&lt;br /&gt;
&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, dacă, de exemplu, vă schimbă calculatorul.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ce să &amp;lt;span style=&amp;quot;color:red&amp;quot;&amp;gt;NU&amp;lt;/span faceți ===&lt;br /&gt;
&lt;br /&gt;
* Nu îngrășați porcul în ajun: 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu vă panicați: panica ucide creierul&#039;&#039;&#039;. 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;
&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 bine, 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;
&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ă nu mai aveți idei, creați mai multe teste pe care să le testați. Puteți scrie programe care să vă genereze teste. Puteți cere la CMS să vi se evalueze acele teste. 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;
&lt;br /&gt;
* &#039;&#039;&#039;Nu modificați și trimiteț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ă. În principiu punctajul este maximul din toate sursele trimise. Nu riscați, dacă schimbă regula în ultima clipă? N-ar fi prima oară.&lt;br /&gt;
&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. Grija voastră trebuie să fie rezolvarea problemelor, nu noi. Noi suntem de partea voastră. Chiar dacă la cerc sau la clasă, când suntem î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 suntem de aceeași parte a baricadei.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Diferenţa dintre olimpiadă şi viaţa reală ===&lt;br /&gt;
&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 7a, 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. Olimpiada nu ne defineşte ca oameni.&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. Lupta este interzisă cu excepţia cazului când sunteţ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. La fel și informatica, în viața reală trebuie să inventăm un algoritm cât mai bun și să îl implementăm cât mai frumos, mai precis, fără a repeta cod, cât mai clar de citit și de alții, cu comentarii, etc.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;La olimpiadă&#039;&#039;&#039;: olimpiada este ca legitima apărare. Ca şi când sunteţ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ă sunt 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;
&#039;&#039;&#039;Atenţie însă!&#039;&#039;&#039; 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;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18493</id>
		<title>Clasa a 7-a</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18493"/>
		<updated>2025-06-08T19:48:51Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[Clasa a 7-a Lecția 1: Reguli de programare în limbajul C și complexități algoritmice]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 2: Problema selecției și stive]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 3: Liste (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 4: Liste (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 5: Precalculare]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 6: Evaluare (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 7: Recursivitate (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 8: Recursivitate (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 9: Evaluare (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 10: Fill recursiv (flood fill)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 11: Numere mari, exponențiere rapidă, element majoritar]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 12: Analiză amortizată (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 13: Analiză amortizată (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 14: Evaluare (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 15: Citire / scriere rapidă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 17: Evaluare (4)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 18: Divide et impera, mergesort, quicksort]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 19: Cozi și algoritmul lui Lee]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 20: Evaluare (5)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 21: Cozi duble și maximul în fereastră glisantă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale prin algoritmi de tip succesor: submulțimi, permutări, combinări, aranjamente, next permutation]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 23: Evaluare (6)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 24: Programare dinamică (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 25: Programare dinamică (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 26: Programare dinamică (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 27: Algoritmul Union-Find]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 28: Sfaturi pentru concursuri / olimpiadă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 29: Discutarea problemelor de la OJI 2024]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 30: Discutarea problemelor de la ONI 2024]]&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_23:_Evaluare_(6)&amp;diff=18492</id>
		<title>Clasa a 7-a Lecția 23: Evaluare (6)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_23:_Evaluare_(6)&amp;diff=18492"/>
		<updated>2025-06-08T16:24:31Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Testați-vă cunoștințele și rezolvați următoarele probleme în decurs a trei ore (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://nerdarena.ro/problema/coada Coada]&lt;br /&gt;
* [https://nerdarena.ro/problema/plop Plop]&lt;br /&gt;
* [https://nerdarena.ro/problema/nraprime1 NrAPrime1]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_23:_Evaluare_(6) Accesează rezolvarea problemelor de la lecția 23]&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18491</id>
		<title>Clasa a 7-a</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18491"/>
		<updated>2025-06-08T14:57:09Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[Clasa a 7-a Lecția 1: Reguli de programare în limbajul C și complexități algoritmice]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 2: Problema selecției și stive]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 3: Liste (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 4: Liste (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 5: Precalculare]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 6: Evaluare (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 7: Recursivitate (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 8: Recursivitate (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 9: Evaluare (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 10: Fill recursiv (flood fill)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 11: Numere mari, exponențiere rapidă, element majoritar]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 12: Analiză amortizată (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 13: Analiză amortizată (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 14: Evaluare (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 15: Citire / scriere rapidă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 17: Evaluare (4)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 18: Divide et impera, mergesort, quicksort]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 19: Cozi și algoritmul lui Lee]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 20: Evaluare (5)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 21: Cozi duble și maximul în fereastră glisantă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale prin algoritmi de tip succesor: submulțimi, permutări, combinări, aranjamente, next permutation]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 23: Evaluare (6)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 24: Programare dinamică (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 25: Programare dinamică (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 26: Programare dinamică (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 27: Algoritmul Union-Find]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 28: Discutarea problemelor de la OJI 2024]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 29: Discutarea problemelor de la ONI 2024]]&lt;/div&gt;</summary>
		<author><name>Mihai</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=18490</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=18490"/>
		<updated>2025-06-08T14:56:59Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Mihai moved page Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale prin algoritmi de tip succesor: submulțimi,permutări, combinări, aranjamente, next permutation to Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale prin algoritmi de tip succesor: submulțimi, permutări, combinări, aranjamente, next permutation without leaving a redirect&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&amp;gt; elemente sunt definite ca numărul de moduri de a aranja în șir cele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[1]&amp;lt;/source&amp;gt; este 2. Avansăm pe poziția 2, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[2]&amp;lt;/source&amp;gt; este 5. Avansăm pe poziția 5, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[5] = 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[3]&amp;lt;/source&amp;gt; este 4, avansăm pe poziția 4. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[4]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;1-n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; se va mări cu unu atunci când cifra de pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (dinspre dreapta spre stânga) se va reprezenta în baza &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;
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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; cifre este &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;
* Numărul de permutări de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente este &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;
* 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Iată un exemplu de program care generează toate codurile în baza factorial pe &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;folosit[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&amp;gt;-lea 0 în vectorul de frecvență (al &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Să se afișeze numărul ei de ordine (în ordine lexicografică), &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;, cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție:&#039;&#039;&#039; vom calcula numărul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k = 11&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
La implementare nu vom avea nevoie să memorăm cifrele în baza &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&amp;gt;. Pe măsură ce le calculăm vom calcula direct &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;t!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-2&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; să poată fi mărit este să putem adăuga după el &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-i&amp;lt;/source&amp;gt; elemente. Pentru aceasta 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; putem pune ca element maxim valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n - (k - i) = n -k + i&amp;lt;/source&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>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_23:_Evaluare_(6)&amp;diff=18487</id>
		<title>Clasa a 7-a Lecția 23: Evaluare (6)</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_23:_Evaluare_(6)&amp;diff=18487"/>
		<updated>2025-06-08T14:21:39Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Created page with &amp;quot;Testați-vă cunoștințele și rezolvați următoarele probleme în decurs a trei ore (program C trimis la [https://www.nerdarena.ro/ NerdArena]):  * [https://nerdarena.ro/problema/coada Coada] * [https://nerdarena.ro/problema/plop Plop] * [https://nerdarena.ro/problema/nraprime1 NrPrime1]   [http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_23:_Evaluare_(6) Accesează rezolvarea problemelor de la lecția 23]&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Testați-vă cunoștințele și rezolvați următoarele probleme în decurs a trei ore (program C trimis la [https://www.nerdarena.ro/ NerdArena]):&lt;br /&gt;
&lt;br /&gt;
* [https://nerdarena.ro/problema/coada Coada]&lt;br /&gt;
* [https://nerdarena.ro/problema/plop Plop]&lt;br /&gt;
* [https://nerdarena.ro/problema/nraprime1 NrPrime1]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[http://solpedia.francu.com/wiki/index.php?title=Clasa_a_7-a_Lec%C8%9Bia_23:_Evaluare_(6) Accesează rezolvarea problemelor de la lecția 23]&lt;/div&gt;</summary>
		<author><name>Mihai</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=18486</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=18486"/>
		<updated>2025-06-08T14:21:12Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Tema opțională */&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&amp;gt; elemente sunt definite ca numărul de moduri de a aranja în șir cele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[1]&amp;lt;/source&amp;gt; este 2. Avansăm pe poziția 2, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[2]&amp;lt;/source&amp;gt; este 5. Avansăm pe poziția 5, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[5] = 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[3]&amp;lt;/source&amp;gt; este 4, avansăm pe poziția 4. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[4]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;1-n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; se va mări cu unu atunci când cifra de pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (dinspre dreapta spre stânga) se va reprezenta în baza &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;
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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; cifre este &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;
* Numărul de permutări de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente este &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;
* 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Iată un exemplu de program care generează toate codurile în baza factorial pe &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;folosit[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&amp;gt;-lea 0 în vectorul de frecvență (al &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Să se afișeze numărul ei de ordine (în ordine lexicografică), &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;, cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție:&#039;&#039;&#039; vom calcula numărul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k = 11&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
La implementare nu vom avea nevoie să memorăm cifrele în baza &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&amp;gt;. Pe măsură ce le calculăm vom calcula direct &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;t!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-2&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; să poată fi mărit este să putem adăuga după el &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-i&amp;lt;/source&amp;gt; elemente. Pentru aceasta 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; putem pune ca element maxim valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n - (k - i) = n -k + i&amp;lt;/source&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>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18485</id>
		<title>Clasa a 7-a</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18485"/>
		<updated>2025-06-08T14:19:09Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[Clasa a 7-a Lecția 1: Reguli de programare în limbajul C și complexități algoritmice]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 2: Problema selecției și stive]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 3: Liste (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 4: Liste (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 5: Precalculare]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 6: Evaluare (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 7: Recursivitate (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 8: Recursivitate (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 9: Evaluare (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 10: Fill recursiv (flood fill)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 11: Numere mari, exponențiere rapidă, element majoritar]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 12: Analiză amortizată (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 13: Analiză amortizată (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 14: Evaluare (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 15: Citire / scriere rapidă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 17: Evaluare (4)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 18: Divide et impera, mergesort, quicksort]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 19: Cozi și algoritmul lui Lee]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 20: Evaluare (5)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 21: Cozi duble și maximul în fereastră glisantă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale prin algoritmi de tip succesor: submulțimi,permutări, combinări, aranjamente, next permutation]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 23: Evaluare (6)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 24: Programare dinamică (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 25: Programare dinamică (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 26: Programare dinamică (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 27: Algoritmul Union-Find]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 28: Discutarea problemelor de la OJI 2024]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 29: Discutarea problemelor de la ONI 2024]]&lt;/div&gt;</summary>
		<author><name>Mihai</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=18484</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=18484"/>
		<updated>2025-06-08T14:18:58Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Mihai moved page Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale: submulțimi, permutări, combinări, aranjamente, next permutation to Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale prin algoritmi de tip succesor: submulțimi,permutări, combinări, aranjamente, next permutation without leaving a redirect&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&amp;gt; elemente sunt definite ca numărul de moduri de a aranja în șir cele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[1]&amp;lt;/source&amp;gt; este 2. Avansăm pe poziția 2, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[2]&amp;lt;/source&amp;gt; este 5. Avansăm pe poziția 5, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[5] = 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[3]&amp;lt;/source&amp;gt; este 4, avansăm pe poziția 4. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[4]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;1-n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; se va mări cu unu atunci când cifra de pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (dinspre dreapta spre stânga) se va reprezenta în baza &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;
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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; cifre este &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;
* Numărul de permutări de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente este &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;
* 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Iată un exemplu de program care generează toate codurile în baza factorial pe &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;folosit[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&amp;gt;-lea 0 în vectorul de frecvență (al &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Să se afișeze numărul ei de ordine (în ordine lexicografică), &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;, cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție:&#039;&#039;&#039; vom calcula numărul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k = 11&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
La implementare nu vom avea nevoie să memorăm cifrele în baza &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&amp;gt;. Pe măsură ce le calculăm vom calcula direct &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;t!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-2&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; să poată fi mărit este să putem adăuga după el &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-i&amp;lt;/source&amp;gt; elemente. Pentru aceasta 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; putem pune ca element maxim valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n - (k - i) = n -k + i&amp;lt;/source&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&amp;amp;action=edit&amp;amp;redlink=1 Accesează rezolvarea temei 22]&lt;/div&gt;</summary>
		<author><name>Mihai</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=18483</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=18483"/>
		<updated>2025-06-08T14:17:29Z</updated>

		<summary type="html">&lt;p&gt;Mihai: /* Tema opțională */&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&amp;gt; elemente sunt definite ca numărul de moduri de a aranja în șir cele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[1]&amp;lt;/source&amp;gt; este 2. Avansăm pe poziția 2, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[2]&amp;lt;/source&amp;gt; este 5. Avansăm pe poziția 5, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[5] = 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[3]&amp;lt;/source&amp;gt; este 4, avansăm pe poziția 4. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[4]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;1-n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; se va mări cu unu atunci când cifra de pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (dinspre dreapta spre stânga) se va reprezenta în baza &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;
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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; cifre este &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;
* Numărul de permutări de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente este &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;
* 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Iată un exemplu de program care generează toate codurile în baza factorial pe &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;folosit[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&amp;gt;-lea 0 în vectorul de frecvență (al &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Să se afișeze numărul ei de ordine (în ordine lexicografică), &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;, cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție:&#039;&#039;&#039; vom calcula numărul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k = 11&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
La implementare nu vom avea nevoie să memorăm cifrele în baza &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&amp;gt;. Pe măsură ce le calculăm vom calcula direct &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;t!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-2&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; să poată fi mărit este să putem adăuga după el &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-i&amp;lt;/source&amp;gt; elemente. Pentru aceasta 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; putem pune ca element maxim valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n - (k - i) = n -k + i&amp;lt;/source&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&amp;amp;action=edit&amp;amp;redlink=1 Accesează rezolvarea temei 22]&lt;/div&gt;</summary>
		<author><name>Mihai</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=18482</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=18482"/>
		<updated>2025-06-08T14:14:55Z</updated>

		<summary type="html">&lt;p&gt;Mihai: Created page with &amp;quot;== Înregistrare video lecție ==  &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;   == Permutări ==  Permutările de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&amp;gt; elemente sunt definite ca numărul de moduri de a aranja în șir cele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&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:    P(N) = N!   === Permutările ca funcții de reara...&amp;quot;&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&amp;gt; elemente sunt definite ca numărul de moduri de a aranja în șir cele &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;N&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[1]&amp;lt;/source&amp;gt; este 2. Avansăm pe poziția 2, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[2]&amp;lt;/source&amp;gt; este 5. Avansăm pe poziția 5, unde &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[5] = 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[3]&amp;lt;/source&amp;gt; este 4, avansăm pe poziția 4. &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;p[4]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;1-n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; se va mări cu unu atunci când cifra de pe poziția &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (dinspre dreapta spre stânga) se va reprezenta în baza &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;
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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; cifre este &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;
* Numărul de permutări de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente este &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;
* 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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Iată un exemplu de program care generează toate codurile în baza factorial pe &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;folosit[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&amp;gt;-lea 0 în vectorul de frecvență (al &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;cf&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt;. Să se afișeze numărul ei de ordine (în ordine lexicografică), &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;, cuprins între 0 și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n! - 1&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Soluție:&#039;&#039;&#039; vom calcula numărul &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; în baza factorial cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k = 11&amp;lt;/source&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
La implementare nu vom avea nevoie să memorăm cifrele în baza &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n!&amp;lt;/source&amp;gt;. Pe măsură ce le calculăm vom calcula direct &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; elemente în vectorul de frecvență. Avem &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n = 4&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;t!&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; (cu &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n-2&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;i&amp;lt;/source&amp;gt; să poată fi mărit este să putem adăuga după el &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-i&amp;lt;/source&amp;gt; elemente. Pentru aceasta 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; putem pune ca element maxim valoarea &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n - (k - i) = n -k + i&amp;lt;/source&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;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18481</id>
		<title>Clasa a 7-a</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18481"/>
		<updated>2025-06-08T10:48:17Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[Clasa a 7-a Lecția 1: Reguli de programare în limbajul C și complexități algoritmice]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 2: Problema selecției și stive]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 3: Liste (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 4: Liste (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 5: Precalculare]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 6: Evaluare (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 7: Recursivitate (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 8: Recursivitate (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 9: Evaluare (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 10: Fill recursiv (flood fill)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 11: Numere mari, exponențiere rapidă, element majoritar]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 12: Analiză amortizată (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 13: Analiză amortizată (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 14: Evaluare (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 15: Citire / scriere rapidă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 17: Evaluare (4)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 18: Divide et impera, mergesort, quicksort]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 19: Cozi și algoritmul lui Lee]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 20: Evaluare (5)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 21: Cozi duble și maximul în fereastră glisantă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale: submulțimi, permutări, combinări, aranjamente, next permutation]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 23: Evaluare (6)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 24: Programare dinamică (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 25: Programare dinamică (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 26: Programare dinamică (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 27: Algoritmul Union-Find]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 28: Discutarea problemelor de la OJI 2024]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 29: Discutarea problemelor de la ONI 2024]]&lt;/div&gt;</summary>
		<author><name>Mihai</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18479</id>
		<title>Clasa a 7-a</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasa_a_7-a&amp;diff=18479"/>
		<updated>2025-06-08T10:41:44Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;* [[Clasa a 7-a Lecția 1: Reguli de programare în limbajul C și complexități algoritmice]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 2: Problema selecției și stive]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 3: Liste (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 4: Liste (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 5: Precalculare]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 6: Evaluare (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 7: Recursivitate (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 8: Recursivitate (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 9: Evaluare (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 10: Fill recursiv (flood fill)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 11: Numere mari, exponențiere rapidă, element majoritar]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 12: Analiză amortizată (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 13: Analiză amortizată (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 14: Evaluare (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 15: Citire / scriere rapidă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 16: Tehnica Two Pointers (doi pointeri), tipul de date struct și metoda Greedy]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 17: Evaluare (4)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 18: Divide et impera, mergesort, quicksort]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 19: Cozi și algoritmul lui Lee]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 20: Evaluare (5)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 21: Cozi duble și maximul în fereastră glisantă]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 22: Generarea elementelor combinatoriale prin algoritmi de tip succesor: submulțimi, permutări, combinări, aranjamente, next permutation]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 23: Evaluare (6)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 24: Programare dinamică (1)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 25: Programare dinamică (2)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 26: Programare dinamică (3)]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 27: Algoritmul Union-Find]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 28: Discutarea problemelor de la OJI 2024]]&lt;br /&gt;
* [[Clasa a 7-a Lecția 29: Discutarea problemelor de la ONI 2024]]&lt;/div&gt;</summary>
		<author><name>Mihai</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=18473</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=18473"/>
		<updated>2025-06-03T14:02:44Z</updated>

		<summary type="html">&lt;p&gt;Mihai: &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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;primul&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;ultimul&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;primul&amp;lt;/source&amp;gt; și &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;ultim&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n – k + 1&amp;lt;/source&amp;gt; subsecvențe de &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; numere consecutive în șir (denumite și &#039;&#039;ferestre&#039;&#039; de lungime &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt;). Putem vedea aceste ferestre ca pe o singură fereastră, deplasabilă, care ne dă acces la &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k-1&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;n&amp;lt;/source&amp;gt; este numărul de elemente citit la intrare.&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; este mărimea ferestrei glisante.&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;deq[]&amp;lt;/source&amp;gt; este coada dublă ce memorează &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&amp;gt; elemente, dar va fi dimensionată la următoarea putere a lui 2.&lt;br /&gt;
* &amp;lt;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;rasp[]&amp;lt;/source&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;rasp[]&amp;lt;/source&amp;gt; este maximul din fereastra ce începe la 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;.&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;source lang=&amp;quot;C&amp;quot; enclose=&amp;quot;none&amp;quot;&amp;gt;k&amp;lt;/source&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>Mihai</name></author>
	</entry>
</feed>