<?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=Cata</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=Cata"/>
	<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php/Special:Contributions/Cata"/>
	<updated>2026-04-13T06:22:11Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.44.2</generator>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=PCP/Arbori_de_intervale&amp;diff=18538</id>
		<title>PCP/Arbori de intervale</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=PCP/Arbori_de_intervale&amp;diff=18538"/>
		<updated>2025-10-26T16:07:02Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Arborii de intervale sînt prima structură de date pe care o vom studia dintr-o clasă mai amplă: structuri de date pe vectori care pot procesa anumite operații în timp mai bun decît $\mathcal{O}(N)$. Ocazional aceste structuri se aplică și matricilor.&lt;br /&gt;
&lt;br /&gt;
Subiectele de ONI / baraj ONI / lot din anii trecuți abundă în probleme rezolvabile cu astfel de structuri:&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=PCP/Arbori_de_intervale&amp;diff=18537</id>
		<title>PCP/Arbori de intervale</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=PCP/Arbori_de_intervale&amp;diff=18537"/>
		<updated>2025-10-26T16:02:05Z</updated>

		<summary type="html">&lt;p&gt;Cata: Created page with &amp;quot;ABC&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;ABC&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Programare_cu_premeditare&amp;diff=18536</id>
		<title>Programare cu premeditare</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Programare_cu_premeditare&amp;diff=18536"/>
		<updated>2025-10-26T16:01:54Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Acesta este un curs de algoritmi și structuri de date extins cu noțiuni de inginerie software.&lt;br /&gt;
&lt;br /&gt;
Publicul-țintă sînt elevii de liceu pe care îi pasionează participarea la Olimpiada de Informatică. Dacă ați ajuns în anii anteriori la etapa națională și dacă aveți ambiția să urcați un nivel și să încercați să intrați în lotul lărgit, atunci acest curs este pentru voi.&lt;br /&gt;
&lt;br /&gt;
Materia pe care o acoperă acest curs se suprapune cu [https://cdn.sepi.ro/oni2024/Programa%20pentru%20olimpiada%20de%20informatica_gimnaziu%20si%20liceu.pdf Programa pentru Olimpiada națională de informatică] pentru liceu. Dar cursul este mai mult decît un talmeș-balmeș de algoritmi și structuri de date. Pentru acelea puteți găsi nenumărate alte resurse. Cursul acoperă toate noțiunile, dar insistă și pe latura practică a programării, pe scrierea de cod curat, lizibil și rezistent la buguri. Aceasta este o componentă extrem de subapreciată în supa culturală olimpică din România și -- cred eu -- principala sursă a scorurilor cu o singură cifră la concursuri.&lt;br /&gt;
&lt;br /&gt;
Acest curs de programare s-a născut din [https://education.nerdvana.ro/courses/cursul-de-programare-pentru-pregatire-lot-seniori-anul-2025-2026/ cercul de programare pentru performanță] pe care îl țin la [https://nerdvana.ro/ Nerdvana] începînd cu anul 2022.&lt;br /&gt;
&lt;br /&gt;
[[PCP/Arbori de intervale|Arbori de intervale]]&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Programare_cu_premeditare&amp;diff=18535</id>
		<title>Programare cu premeditare</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Programare_cu_premeditare&amp;diff=18535"/>
		<updated>2025-10-26T16:00:44Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Acesta este un curs de algoritmi și structuri de date extins cu noțiuni de inginerie software.&lt;br /&gt;
&lt;br /&gt;
Publicul-țintă sînt elevii de liceu pe care îi pasionează participarea la Olimpiada de Informatică. Dacă ați ajuns în anii anteriori la etapa națională și dacă aveți ambiția să urcați un nivel și să încercați să intrați în lotul lărgit, atunci acest curs este pentru voi.&lt;br /&gt;
&lt;br /&gt;
Materia pe care o acoperă acest curs se suprapune cu [https://cdn.sepi.ro/oni2024/Programa%20pentru%20olimpiada%20de%20informatica_gimnaziu%20si%20liceu.pdf Programa pentru Olimpiada națională de informatică] pentru liceu. Dar cursul este mai mult decît un talmeș-balmeș de algoritmi și structuri de date. Pentru acelea puteți găsi nenumărate alte resurse. Cursul acoperă toate noțiunile, dar insistă și pe latura practică a programării, pe scrierea de cod curat, lizibil și rezistent la buguri. Aceasta este o componentă extrem de subapreciată în supa culturală olimpică din România și -- cred eu -- principala sursă a scorurilor cu o singură cifră la concursuri.&lt;br /&gt;
&lt;br /&gt;
Acest curs de programare s-a născut din [https://education.nerdvana.ro/courses/cursul-de-programare-pentru-pregatire-lot-seniori-anul-2025-2026/ cercul de programare pentru performanță] pe care îl țin la [https://nerdvana.ro/ Nerdvana] începînd cu anul 2022.&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Programare_cu_premeditare&amp;diff=18534</id>
		<title>Programare cu premeditare</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Programare_cu_premeditare&amp;diff=18534"/>
		<updated>2025-10-26T15:42:37Z</updated>

		<summary type="html">&lt;p&gt;Cata: Created page with &amp;quot;Acesta este un curs de algoritmi și structuri de date extins cu noțiuni de inginerie software.  Publicul-țintă sînt elevii de liceu pe care îi pasionează participarea la Olimpiada de Informatică. Dacă ați ajuns în anii anteriori la etapa națională și dacă aveți ambiția să urcați un nivel și să încercați să intrați în lotul lărgit, atunci acest curs este pentru voi.  Materia pe care o acoperă acest curs se suprapune cu [https://cdn.sepi.ro/oni2...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Acesta este un curs de algoritmi și structuri de date extins cu noțiuni de inginerie software.&lt;br /&gt;
&lt;br /&gt;
Publicul-țintă sînt elevii de liceu pe care îi pasionează participarea la Olimpiada de Informatică. Dacă ați ajuns în anii anteriori la etapa națională și dacă aveți ambiția să urcați un nivel și să încercați să intrați în lotul lărgit, atunci acest curs este pentru voi.&lt;br /&gt;
&lt;br /&gt;
Materia pe care o acoperă acest curs se suprapune cu [https://cdn.sepi.ro/oni2024/Programa%20pentru%20olimpiada%20de%20informatica_gimnaziu%20si%20liceu.pdf Programa pentru Olimpiada națională de informatică] pentru liceu. Dar cursul este mai mult decît un talmeș-balmeș de algoritmi și structuri de date. Pentru acelea puteți găsi nenumărate alte resurse. Cursul acoperă toate noțiunile, dar insistă și pe latura practică a programării, pe scrierea de cod curat, lizibil și rezistent la buguri. Aceasta este o componentă extrem de subapreciată în supa culturală olimpică din România și -- cred eu -- principala sursă a scorurilor cu două cifre la concursuri (sau cu una singură).&lt;br /&gt;
&lt;br /&gt;
Acest curs de programare s-a născut din [https://education.nerdvana.ro/courses/cursul-de-programare-pentru-pregatire-lot-seniori-anul-2025-2026/ cercul de programare pentru performanță] pe care îl țin la [https://nerdvana.ro/ Nerdvana] începînd cu anul 2022.&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasele_9-12_lec%C8%9Bia_8_-_5_nov_2014&amp;diff=18533</id>
		<title>Clasele 9-12 lecția 8 - 5 nov 2014</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasele_9-12_lec%C8%9Bia_8_-_5_nov_2014&amp;diff=18533"/>
		<updated>2025-10-26T12:35:35Z</updated>

		<summary type="html">&lt;p&gt;Cata: /* Recursivitate inutilă */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Lecția de astăzi este mai ușoară, ca să ne dea timp să ne pregătim pentru barajele pentru Șumen ([http://varena.ro/runda/barajshumenjuniori2 juniori], [http://varena.ro/runda/barajshumenseniori1 seniori]). Aceasta nu înseamnă că este mai puțin importantă! Aștept de la voi să începeți să aplicați toate lucrurile discutate aici.&lt;br /&gt;
&lt;br /&gt;
= Obiceiuri de programare =&lt;br /&gt;
&lt;br /&gt;
== Preliminarii ==&lt;br /&gt;
&lt;br /&gt;
Sunteți cu toții pasionați de informatică și sunteți buni la algoritmică. Este fantastic, dar nu va fi suficient pe termen lung. Ușor-ușor, trebuie să începeți să vă cizelați și felul în care codați. Trebuie să vă dezvoltați capacitatea de a vă judeca o sursă după alte criterii decât strict punctajul luat la concurs. Vorbim despre criterii ca: ușurința de înțelegere a codului sau ușurința de modificare și extindere.&lt;br /&gt;
&lt;br /&gt;
Noi, profesorii, avem partea noastră de vină în felul dezlânat în care programați voi. Ne batem singuri pe spate că elevii știu algoritmi, dar nu ne pasă cum îi codează. Adevărul este că cele două merg mână în mână, mai ales apropo de ceea ce am mai spus: voi nu vedeți destul cod scris de alții. Deci o lecție pe an dedicată programării este doar un început.&lt;br /&gt;
&lt;br /&gt;
== Programarea aproximativă ==&lt;br /&gt;
&lt;br /&gt;
Banc: cică japonezii aud de Dacia -- mașină bună, fiabilă, piese ieftine. Cumpără planurile de la Dacia și încep și ei să o producă. Doar că, surpriză, de pe linia de asamblare le iese un tractor! Trimit un om la Pitești, să studieze uzina. Se uită el, se întoarce acasă, mai repară japonezii mici probleme la linia lor. Degeaba: de pe linia de asamblare ieșeau tractoare. Disperați, dau telefon în România și cer lămuriri. Românii îi liniștesc: „păi și nouă ne iese tot tractor, dar îl luăm la pilă!”&lt;br /&gt;
&lt;br /&gt;
Toate subproblemele pe care le tratăm aici sunt doar vârfuri ale icebergului. Ele reflectă o problemă mai gravă: programarea aproximativă. Prin programare aproximativă înțelegem o serie de iterații de adăugare de cod care converge către o soluție. Programul capătă formă pe măsură ce îl scriem, ca și ideea de rezolvare:&lt;br /&gt;
&lt;br /&gt;
* Mai am nevoie de un element în vector? Adaug 1 la lungimea lui.&lt;br /&gt;
* M-ar ajuta să am datele sortate? Ce idee bună, apelez un sort().&lt;br /&gt;
* Trebuie să ies prematur dintr-o buclă? Deja am scris-o folosind &#039;&#039;&#039;for&#039;&#039;&#039;, deci inserez un &#039;&#039;&#039;break&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Nu ciocăniți ici și colo, nu programați reactiv, în funcție de erorile din iterația anterioară. Programați activ, gândindu-vă bine la algoritm și la detaliile lui și încercați să-l implementați bine din prima. Nu fiți lăutari, fiți virtuozi.&lt;br /&gt;
&lt;br /&gt;
Veți vrea să mergeți la companii faimoase. Acolo oamenii se uită, încă de la interviu, la stilul vostru de codare. Iar, dacă veți fi acceptați, vă veți poticni la fiecare code review de aceleași observații. Dacă așteptați zece ani până să încercați să vă corectați, s-ar putea să fie prea târziu. Lenevirea creierului duce la o gândire încâlcită.&lt;br /&gt;
&lt;br /&gt;
== Constante ==&lt;br /&gt;
&lt;br /&gt;
Folosim în general constante pentru a face codul mai solid și mai ușor de modificat.&lt;br /&gt;
&lt;br /&gt;
=== Constantele sunt sublime, dar lipsesc cu desăvârșire ===&lt;br /&gt;
&lt;br /&gt;
Cel mai grav este să nu folosiți deloc constante. Orice repetiție, chiar și a unui singur număr, chiar și doar o dată, este o practică riscantă. Un programator bun își ordonează codul astfel încât o modificare undeva în program să nu necesite modificări în alte părți fără legătură. Acest principiu se numește [http://en.wikipedia.org/wiki/Don&#039;t_repeat_yourself Don&#039;t repeat yourself (DRY)].&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int v[100000], w[100000];&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
int max = -2000000000;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 100000&lt;br /&gt;
#define INFINITY 2000000000&lt;br /&gt;
&lt;br /&gt;
int v[MAX_N], w[MAX_N];&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
int max = -INFINITY;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Scenariu catastrofic: declarați 5 vectori de mărime 1.000. Testați programul, care trece toate testele mici. Înlocuiți 1.000 cu 100.000, dar omiteți una dintre aparițiile constantei. Trimiteți problema, care ia 0 puncte.&lt;br /&gt;
&lt;br /&gt;
=== #define N &amp;quot;vreo 100000 și 17, să fie&amp;quot; ===&lt;br /&gt;
&lt;br /&gt;
Aproape fără excepție am observat constante declarate cu „câteva” unități mai mari decât este necesar. Dacă problema specifică N ≤ 100.000, voi declarați 100.002, 100.005, 100.007. Unii mai creativi ajung la 111.111 (ce-i drept, estetica acestui număr este incontestabilă).&lt;br /&gt;
&lt;br /&gt;
De ce faceți asta? Următoarele răspunsuri nu sunt acceptate:&lt;br /&gt;
&lt;br /&gt;
* Îmi este teamă că programul are o viață proprie și că decide, de capul lui, să se reverse cu câteva elemente;&lt;br /&gt;
* Mă gândesc că biții nu sunt bine separați și că se pot păta unii de la alții;&lt;br /&gt;
* Cred că pointerul care iterează prin vector frânează greu și nu se va opri la timp;&lt;br /&gt;
* Am de gând să folosesc vectorul de la 1, deci un element deja s-a pierdut.&lt;br /&gt;
&lt;br /&gt;
Următorul răspuns este acceptat:&lt;br /&gt;
&lt;br /&gt;
* Nu am încredere în aptitudinile mele de programator și în robustețea codului pe care îl produc; vectorul este accesat și modificat în câteva locuri diferite și eu nu pot să demonstrez că mă încadrez în N elemente.&lt;br /&gt;
&lt;br /&gt;
Nu programați în dorul lelii. Fiți stăpâni pe program. Nimeni nu jelește 10 octeți irosiți, dar este vorba de un principiu.&lt;br /&gt;
&lt;br /&gt;
În plus, la matricele multidimensionale risipa se cumulează. 1.010 octeți în loc de 1.000? 1% risipă. 1.010&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt; octeți în loc de 1.000&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;? 3% risipă.&lt;br /&gt;
&lt;br /&gt;
Dacă vă plac algoritmii, veți ajunge să învățați unii tot mai complicați. Dacă nu sunteți pe deplin stăpâni nici pe aceste cărămizi de la bază, vă va fi greu să construiți peste ele.&lt;br /&gt;
&lt;br /&gt;
=== Patul lui Procust pentru constante ===&lt;br /&gt;
&lt;br /&gt;
Adesea aveți nevoie de vectori de lungimi ușor diferite. Poate un vector se termină cu o santinelă, sau poate altul are santinele la ambele capete. Poate altul are N ≤ 100.000 elemente și va mai câștiga alte K ≤ 1.000 elemente în viitor. De cele mai multe ori, voi definiți constanta în program pentru a acoperi toate aceste cazuri.&lt;br /&gt;
&lt;br /&gt;
Nu este bine, tot din principiu. Declarați o constantă egală cu cea specificată de datele problemei, apoi ajustați fiecare vector în funcție de necesități. &lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 101000&lt;br /&gt;
int v[MAX_N], w[MAX_N], x[MAX_N], y[MAX_N];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 100000&lt;br /&gt;
#define MAX_K 1000&lt;br /&gt;
&lt;br /&gt;
int v[MAX_N];&lt;br /&gt;
int w[MAX_N + 1];     // bordat cu santinelă la sfârșit&lt;br /&gt;
int x[MAX_N + 2];     // bordat cu santinelă la ambele capete&lt;br /&gt;
int y[MAX_N + MAX_K]; // Va căpăta un element la fiecare iterație&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Codul vostru va câștiga mult în eleganță și în claritate, căci scopul fiecărei variabile va deveni mai clar. Reiese și că 1 și 2 sunt constante (numărul de santinele nu depinde de mărimea intrării), dar K poate și el să varieze dacă datele problemei se schimbă.&lt;br /&gt;
&lt;br /&gt;
== Break, continue și return prematur ==&lt;br /&gt;
&lt;br /&gt;
Evităm, cu rare excepții, break, continue și return prematur pentru că fac codul mai greu de citit. Se aplică și la concurs, chiar dacă voi sunteți singurii cititori.&lt;br /&gt;
&lt;br /&gt;
=== „Să oprească proștii la stop” ===&lt;br /&gt;
&lt;br /&gt;
La semnele de stop (octogonul roșu), legea spune că trebuie să oprești complet mașina (să stea roata). În practică, mai devreme sau mai târziu toți șoferii ne dăm seama că putem să trecem pe lângă semn fără să oprim complet. Nu cunosc pe nimeni care să respecte la sânge această lege. „Rostogolirea” prin semnul de stop reduce uzura frânelor, deci are un avantaj palpabil. Dar suntem conștienți că facem o prostioară și că nu putem face asta oricând, oriunde -- în special când de pe strada cu prioritate vin mașini. Cine încalcă această lege fără discernământ se expune la accidente, potențial catastrofice.&lt;br /&gt;
&lt;br /&gt;
La fel este și cu programarea nestructurată. Scopul nu este să urmăm orbește niște legi, ci să facem codul mai ușor de înțeles și de întreținut. Prin această prismă, situațiile legitime de folosire a programării nestructurate sunt rarisime.&lt;br /&gt;
&lt;br /&gt;
=== Dar programez mai ușor așa ===&lt;br /&gt;
&lt;br /&gt;
Viața nu e ușoară. Cu toții ne străduim azi ca să ne fie bine mai încolo, pe termen lung.&lt;br /&gt;
&lt;br /&gt;
=== Dar scriu cod mai clar așa ===&lt;br /&gt;
&lt;br /&gt;
Claritatea este în ochii cititorului. Dacă vă obișnuiți să programați aproximativ, orice program neaproximativ vi se va părea straniu.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu tipic: căutarea lui &#039;&#039;x&#039;&#039; în vectorul &#039;&#039;v&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
for (int i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
  if (v[i] == x) {&lt;br /&gt;
    break;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int i = 0;&lt;br /&gt;
while ((i &amp;lt; n) &amp;amp;&amp;amp; (v[i] != x)) {&lt;br /&gt;
  i++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Codul este mai scurt și mai citibil. Versiunea cu break promite o condiție de ieșire (i &amp;gt;= n), apoi ne păcălește ieșind pe o cu totul altă condiție (v[i] == x). În versiunea structurată, ambele condiții sunt listate clar în instrucțiunea &#039;&#039;while&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Un alt exemplu: un test dacă șirul &#039;&#039;s&#039;&#039; este palindrom.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
bool palindrom(char *s){&lt;br /&gt;
  for (int i = 0; 2 * i &amp;lt; n; i++) {&lt;br /&gt;
    if (s[i] != s[n - 1 - i]) {&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
  return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
bool palindrom(char *s){&lt;br /&gt;
  int i = 0;&lt;br /&gt;
  while ((2 * i &amp;lt; n) &amp;amp;&amp;amp; (s[i] == s[n - 1 - i])) {&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
  return (s[i] == s[n - 1 - i]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Dar cum pot să scriu un switch fără break? ===&lt;br /&gt;
&lt;br /&gt;
Limbajul C a apărut prin 1972, când informaticienii abia începeau să înțeleagă diferențele între programarea structurată și cea nestructurată. Demonstrația că orice program se poate scrie [http://en.wikipedia.org/wiki/Structured_program_theorem numai cu blocuri structurate] datează din 1966. În anii următori, polemica programării structurate versus cea nestructurată a polarizat lumea informaticii. [http://en.wikipedia.org/wiki/Go_To_Statement_Considered_Harmful#Criticism_and_decline Dijkstra] era pro-structurare, pe când [http://www.scribd.com/doc/38873257/Knuth-1974-Structured-Programming-With-Go-to-Statements Knuth] a dat exemple când instrucțiunea GOTO produce cod mai citibil.&lt;br /&gt;
&lt;br /&gt;
Nu este, deci, de mirare, că limbajul C încorporează un atavism ca &#039;&#039;break&#039;&#039;. Îl vom folosi, dar doar fiindcă nu putem altfel. Nu abuzăm de el; în special, nu permitem „curgerea” dintr-o ramură într-alta. Fiecare ramură trebuie să aibă &#039;&#039;break&#039;&#039;-ul propriu.&lt;br /&gt;
&lt;br /&gt;
=== Când este acceptabil să programăm nestructurat? ===&lt;br /&gt;
&lt;br /&gt;
Iată câteva exemple:&lt;br /&gt;
&lt;br /&gt;
* Când o funcție are de testat 2-3-4 precondiții, este mai firesc să apelăm &#039;&#039;return&#039;&#039; prematur decât să scriem corpul principal al funcției sub 2-3-4 niveluri de indentare.&lt;br /&gt;
* Cazul de bază în funcțiile recursive poate fi pus la început și terminat cu &#039;&#039;return&#039;&#039;.&lt;br /&gt;
* Pentru &#039;&#039;switch&#039;&#039; folosim &#039;&#039;break&#039;&#039; căci nu putem evita asta.&lt;br /&gt;
&lt;br /&gt;
În toate situațiile folosim &#039;&#039;return&#039;&#039; sau &#039;&#039;break&#039;&#039; dintr-un &#039;&#039;if&#039;&#039;, niciodată dintr-o buclă.&lt;br /&gt;
&lt;br /&gt;
== Un program de 10 linii poate fi [http://en.wikipedia.org/wiki/Wikipedia:Too_long;_didn&#039;t_read TLDR] ==&lt;br /&gt;
&lt;br /&gt;
Ați încercat vreodată să citiți o carte tipărită la o editură de bloc? Cu margini de 1 mm, font de 8 și spațiere minusculă? Nu-i așa că e enervant?&lt;br /&gt;
&lt;br /&gt;
Și programele pot fi ilizibile. Nu veți lucra toată viața de unii singuri, iar într-o echipă lizibilitatea este la fel de importantă ca eficiența. Uneori mi se pare că scopul vostru este un program cât mai scurt. De unde și până unde?&lt;br /&gt;
&lt;br /&gt;
Încercați să deprindeți următoarele aspecte:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;O instrucțiune pe linie.&#039;&#039;&#039; Liniile scurte fac codul mai citibil.&lt;br /&gt;
* &#039;&#039;&#039;Spațierea.&#039;&#039;&#039; O linie goală este ca o virgulă. Folosiți-o acolo unde programul face trecerea între două bucăți separate logic: două funcții, două blocuri logic distincte în aceeași funcție. Nu exagerați cu spațierea. Este important ca fiecare funcție să încapă pe un singur ecran.&lt;br /&gt;
* &#039;&#039;&#039;Lungimea numelor de variabile.&#039;&#039;&#039; O literă este acceptabilă numai pentru scopuri încetățenite: &#039;&#039;i, j, k&#039;&#039; pentru variabilele de ciclu, &#039;&#039;m, n&#039;&#039; pentru dimensiunile matricei. Pentru orice altceva, alegeți nume mai clare (dar nici 3 cuvinte, căci ajungem la linii prea lungi).&lt;br /&gt;
* &#039;&#039;&#039;Goana stupidă după scurtimea codului.&#039;&#039;&#039; Două lucruri rele nu fac un lucru bun. Voi scrieți cod lung pentru că acolo converge procesul iterativ, iar pe parcurs îl scurtați mâncând acolade, spațiere și scurtând numele de variabile.&lt;br /&gt;
* &#039;&#039;&#039;Virgula în loc de acolade.&#039;&#039;&#039; Un caz particular al punctului anterior. Sunteți tentați să folosiți virgula ca să înghesuiți două instrucțiuni în același if, fără să adăugați acolade. Vă puteți păcăli singuri.&lt;br /&gt;
* &#039;&#039;&#039;Folosim for numai pentru număr cunoscut de iterații.&#039;&#039;&#039; Rezistați tentației de a deveni hackeri. Nu este o regulă unanim acceptată, dar este o idee bună. Și [http://en.wikipedia.org/wiki/For_loop Wikipedia] o menționează.&lt;br /&gt;
* &#039;&#039;&#039;Variabile cât mai locale.&#039;&#039;&#039; Dacă tot folosiți toate prostiile din C++, măcar folosiți și lucrurile bune. Declarați variabilele cât mai local (fără însă a împăna tot programul cu declarații). În special evitați variabilele globale pentru programe netriviale. Este ușor să pierdeți socoteala cine le modifică și când.&lt;br /&gt;
* &#039;&#039;&#039;Evitați freopen().&#039;&#039;&#039; Este o idee proastă să închideți &#039;&#039;&#039;stdin&#039;&#039;&#039; și în special &#039;&#039;&#039;stdout&#039;&#039;&#039;. În &#039;&#039;&#039;stdout&#039;&#039;&#039; puteți tipări mesaje de depanare pe care, dacă cumva le uitați în program, nu se întâmplă nimic rău. Dar dacă &#039;&#039;&#039;stdout&#039;&#039;&#039; și fișierul de ieșire al problemei sunt unul și același, riscați să tipăriți gunoaie în fișierul de ieșire. În plus, &#039;&#039;&#039;freopen()&#039;&#039;&#039; este o metodă foarte lipsită de considerație. Într-un program mai mare, cine știe ce bibliotecă vrea să tipărească mesaje pe ecran. Voi îi răpiți această posibilitate.&lt;br /&gt;
&lt;br /&gt;
== Recursivitate inutilă ==&lt;br /&gt;
&lt;br /&gt;
Nu vă fie frică să eliminați recursivitatea acolo unde ea nu este necesară. Cel mai comun exemplu este la problemele de programare dinamică, acolo unde, pe lângă valoarea soluției, se cere și soluția explicită.&lt;br /&gt;
&lt;br /&gt;
Exemplu: cel mai lung subșir comun între două șiruri. Presupunând că avem două șiruri, &#039;&#039;a&#039;&#039; de lungime &#039;&#039;m&#039;&#039;, și &#039;&#039;b&#039;&#039; de lungime &#039;&#039;n&#039;&#039;, și că am calculat matricea &#039;&#039;d&#039;&#039; pentru toate perechile de prefixe, soluția se calculează recursiv astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
char s[MAX_SOL];&lt;br /&gt;
&lt;br /&gt;
void solutie(int i, int j) {&lt;br /&gt;
  if (d[i][j]) {&lt;br /&gt;
    if (a[i] == b[j]) {&lt;br /&gt;
      solutie(i - 1, j - 1);&lt;br /&gt;
      s[d[i][j] - 1] = a[i];&lt;br /&gt;
    } else if (d[i][j - 1] &amp;gt; d[i - 1][j] {&lt;br /&gt;
      solutie(d[i][j - 1]);&lt;br /&gt;
    } else {&lt;br /&gt;
      solutie(d[i - 1][j]);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
...&lt;br /&gt;
solutie(m - 1, n - 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nu este rău, dar nu uitați că recursivitatea cere timp și memorie! Stiva de apeluri poate avea O(n) niveluri. Dacă spațiul sau timpul sunt o problemă, eliminați recursivitatea:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
char solutie[MAX_SOL];&lt;br /&gt;
&lt;br /&gt;
int i = m - 1, j = n - 1;&lt;br /&gt;
solutie[d[i][j]] = &#039;\0&#039;;&lt;br /&gt;
&lt;br /&gt;
while (d[i][j]) {&lt;br /&gt;
  if (a[i] == b[j]) {&lt;br /&gt;
    solutie[d[i][j] - 1] = a[i];&lt;br /&gt;
    i--;&lt;br /&gt;
    j--;&lt;br /&gt;
  } else if (d[i][j - 1] &amp;gt; d[i - 1][j] {&lt;br /&gt;
    j--;&lt;br /&gt;
  } else {&lt;br /&gt;
    i--;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Se pierde foarte puțin din claritatea codului, căci relația de recurență nu mai este susținută de apelul recursiv propriu-zis. Totuși, condițiile de recurență (din if-uri) sunt încă vizibile, iar codul necesită O(1) spațiu.&lt;br /&gt;
&lt;br /&gt;
== STL este o armă cu două tăișuri ==&lt;br /&gt;
&lt;br /&gt;
Această secțiune se aplică surselor scrise în afara concursurilor. La concurs, totul este permis. Totuși, nu uitați că omul folosește sculele cu care este obișnuit.&lt;br /&gt;
&lt;br /&gt;
De ce venim la cerc? Pentru că vrem să învățăm algoritmi, nu doar să-i apelăm. Cu algoritmii din STL programați mai repede, dar nu învățați nimic. Sunt numeroase exemplele când trebuie să înțelegeți (și să particularizați) porțiuni dintr-un algoritm: pivotarea din quicksort, construirea automatului din KMP, operațiile pe vectori de biți.&lt;br /&gt;
&lt;br /&gt;
Nu uitați că algoritmii din STL pot fi mai lenți sau mai consumatori de memorie decât cei scriși de voi. Este firesc, căci aceia sunt prea generali și adesea fac operații care nu sunt necesare pentru problema voastră. Qsort din stdlib, de exemplu, necesită aproape dublul memoriei și este mai lent. Am observat asta de mai multe ori. De exemplu, am trimis [http://varena.ro/job_detail/29271 două] [http://varena.ro/job_detail/29277 surse] la problema [http://varena.ro/problema/avioane Avioane]. A doua diferă doar prin faptul că mi-am scris propriul quicksort. Nu știu dacă și sortarea din biblioteca algorithm are această problemă.&lt;br /&gt;
&lt;br /&gt;
La problema [http://varena.ro/problema/dirty Dirty], am comparat sursa mea cu a unor concurenți care făceau cam același lucru, doar că foloseau vectori STL (și iteratori) în loc de vectori C. Diferența de timp este considerabilă. Ajungeți să parsați manual fișierul de intrare ca să câștigați timp. Care mai e câștigul? Ce luați pe mere dați pe pere.&lt;br /&gt;
&lt;br /&gt;
Nu uitați de ce lucrăm în C/C++, nu în PHP sau în Ruby: avem nevoie de viteză, iar C se pretează cel mai bine la asta.&lt;br /&gt;
&lt;br /&gt;
=== Sortarea, înger și demon ===&lt;br /&gt;
&lt;br /&gt;
Sortarea din STL merită un capitol special. Nu zic să nu o folosiți ca să câștigați 10 minute la concurs. (Dacă vă ia mai mult de 10 minute să vă implementați propria sortare, deja avem probleme mai grave). Dar dacă dați una-două fuga la sortare, riscați să pierdeți esențialul din vedere.&lt;br /&gt;
&lt;br /&gt;
* Există cel puțin 5 sortări ușor de codat, fiecare cu avantajele ei (quicksort, mergesort, heapsort, radix sort, counting sort). Fiecare dintre ele are avantaje și poate fi personalizată. Luați ca exemplu problema numărării inversiunilor dintr-o permutare, dată la [http://www.math.bas.bg/keleved/shumen2013 Șumen 2013], care se rezolvă ușor cu un merge sort adaptat. Dacă vă gândiți la sortare ca la o cutie neagră, veți uita ce posibilități aveți.&lt;br /&gt;
* Radix sort și counting sort sunt O(n)! Dacă problema se pretează la o astfel de sortare, atunci &#039;&#039;&#039;sort()&#039;&#039;&#039; nu ajută, ci dăunează.&lt;br /&gt;
* Unele probleme se pot rezolva pur și simplu fără sortare. Nu vă adormiți gândirea cu stereotipuri de genul „încep cu o sortare, să fie”.&lt;br /&gt;
&lt;br /&gt;
== Department of Redundancy Department ==&lt;br /&gt;
&lt;br /&gt;
Evitați să copiați bucăți mari de cod cu copy-paste. Îl avem pe Victor Ponta pentru asta. Dacă aveți cazuri &#039;&#039;&#039;aproape&#039;&#039;&#039; identice, dar diferite pe alocuri, este mai bine să parametrizați codul și să-l mutați într-o funcție sau chiar într-un &#039;&#039;for&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Un exemplu concret: problema [http://varena.ro/problema/avioane Avioane] cerea, pentru anumite elemente dintr-un vector, calcularea unor distanțe la stânga și la dreapta. Cu toții ați rezolvat problema pentru direcția stângă, apoi ați dublat codul și ați modificat ce era necesar ca să-l faceți să meargă și spre dreapta. Mă simt onorat că v-a plăcut problema atât de mult, încât ați rezolvat-o de două ori. :-) Iată două metode de a elimina redundanța:&lt;br /&gt;
&lt;br /&gt;
# Parametrizați tot ce se modifică între direcția stângă și cea dreaptă: punctul de pornire, cel de oprire, pasul (+/- 1). Fratele meu Cristi a pus tot codul într-un for în care singurul parametru era pasul (+/- 1). Deci se poate.&lt;br /&gt;
# Calculați toate valorile spre stânga, apoi întoarceți vectorul pe dos și recalculați valorile tot spre stânga.&lt;br /&gt;
&lt;br /&gt;
Din nou, principiul este DRY: &#039;&#039;Don&#039;t Repeat Yourself&#039;&#039;. Riscați să găsiți un bug într-una din copii și să uitați să-l reparați și în cealaltă.&lt;br /&gt;
&lt;br /&gt;
Bugurile de redundanță sunt greu de reparat pentru că avem de luptat și împotriva naturii noastre umane. Ni se pare că nu are sens să citim ambele blocuri, căci a doua e „la fel ca prima”. Nu vi s-a întâmplat niciodată să vă uitați 10 minute fără să vă dați seama ce e greșit în codul de mai jos?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
for (int i = 0; i &amp;lt; m; i++) {&lt;br /&gt;
  for (int j = 0; i &amp;lt; n; j++) {&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Dimensiunea datelor de intrare ==&lt;br /&gt;
&lt;br /&gt;
Fiți întotdeauna atenți la valorile minime și maxime pe care le poate lua o variabilă. Dacă vă obișnuiți să lucrați numai cu &#039;&#039;int&#039;&#039; și cu &#039;&#039;long long&#039;&#039;, veți uita că puteți face economii mari de memorie folosind short și char.&lt;br /&gt;
&lt;br /&gt;
Mai mult, uneori valorile încap pe 4 biți, pe 2 sau chiar pe unul singur. Toate aceste cazuri pot fi tratate ușor cu rutine de 1-2 linii. Iată o implementare pentru baza 4  (codul complet este în [[media:two-bit.cpp]]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 1000000&lt;br /&gt;
&lt;br /&gt;
unsigned char b[MAX_N / 4];&lt;br /&gt;
&lt;br /&gt;
int get(int i) {&lt;br /&gt;
  return (b[i/4] &amp;gt;&amp;gt; (2 * (i % 4))) &amp;amp; 3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void set(int i, int x) {&lt;br /&gt;
  // Clear the previous value if needed&lt;br /&gt;
  b[i/4] &amp;amp;= ~(3 &amp;lt;&amp;lt; (2 * (i % 4)));&lt;br /&gt;
  b[i/4] ^= x &amp;lt;&amp;lt; (2 * (i % 4));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Exemplu: la problema [http://varena.ro/problema/palindrom Palindrom] primeați foarte puțină memorie. Algoritmul de programare dinamică nu este dificil, dar pentru reconstituirea soluției avem nevoie de întreaga matrice, mai exact de triunghiul superior al unei matrice de dimensiuni n x n. Cum n ≤ 5.000, în aparență avem nevoie de 25 MB. În realitate, o celulă din matrice necesită un singur bit pentru stocare, deci necesarul de memorie este 5.000 x 5.000 / 2 / 8 = 1,56 MB.&lt;br /&gt;
&lt;br /&gt;
== Parsarea intrării: când ai un ciocan, totul în jur pare un cui ==&lt;br /&gt;
&lt;br /&gt;
Haideți să lămurim ceva: profesorii și propunătorii de probleme nu sunt sadici. Nu stau zile întregi să stoarcă fiecare picătură de eficiență dintr-o implementare, pentru ca apoi să vă ceară vouă să reușiți același lucru într-o oră. Limitele de timp sunt, în general, alese adăugând o marjă generoasă la o implementare curată, dar neoptimizată la sânge.&lt;br /&gt;
&lt;br /&gt;
Dacă doriți să câștigați câteva zeci de milisecunde, aveți opțiunea să parsați intrarea. Dar amintiți-vă că sunteți pe un drum greșit și undeva ați pierdut din vedere ceva mult mai important. Nu lucrați cu ciocanul. Lucrați cu bisturiul.&lt;br /&gt;
&lt;br /&gt;
Dacă totuși faceți parsarea datelor de intrare, faceți-o cu stil. Am observat codul următor (îl public anonim, dă-mi de veste dacă dorești credit).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
const int DIM = (1 &amp;lt;&amp;lt; 12);&lt;br /&gt;
char buff[DIM];&lt;br /&gt;
int poz = 0;&lt;br /&gt;
&lt;br /&gt;
void read(int &amp;amp;numar) {&lt;br /&gt;
    numar = 0;&lt;br /&gt;
    while (buff[poz] &amp;lt; &#039;0&#039; || buff[poz] &amp;gt; &#039;9&#039;)&lt;br /&gt;
        if (++poz == DIM)&lt;br /&gt;
            fread(buff, 1, DIM, stdin), poz = 0;&lt;br /&gt;
    while (&#039;0&#039; &amp;lt;= buff[poz] &amp;amp;&amp;amp; buff[poz] &amp;lt;= &#039;9&#039;) {&lt;br /&gt;
        numar = numar * 10 + buff[poz] - &#039;0&#039;;&lt;br /&gt;
        if (++poz == DIM)&lt;br /&gt;
            fread(buff, 1, DIM, stdin), poz = 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Hai să-l scriem mai elegant și mai eficient de atât. I-am adus următoarele îmbunătățiri:&lt;br /&gt;
&lt;br /&gt;
* (de stil) Funcția de citire a unui caracter trebuie separată. Separarea bufferării la un nivel abstract este un lucru bun. Această funcție amestecă citirea bufferată cu parsarea numărului.&lt;br /&gt;
* (de stil) Funcția citește în gol bufferul prima dată înainte de a citi primul bloc de pe disc. Este păcat.&lt;br /&gt;
* (de eficiență) Neapărat testați prezența unei cifre cu &#039;&#039;&#039;isdigit()&#039;&#039;&#039; din &amp;amp;lt;ctype.h&amp;amp;gt;. Codul de mai sus testează două condiții. &#039;&#039;&#039;isdigit()&#039;&#039;&#039; nu testează decât una, căci are un tabel de 256 de valori.&lt;br /&gt;
* (de eficiență) Declarați funcția / funcțiile inline. Compilatorul își poate da seama că este nevoie de asta, dar nu e garantat.&lt;br /&gt;
* (posibil de eficiență) Declarați fișierul de intrare global, ca să nu mai fie trimis ca parametru. Nu ar trebui să aibă niciun efect.&lt;br /&gt;
&lt;br /&gt;
Primele patru modificări produc codul:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define BUF_SIZE 4096&lt;br /&gt;
char buf[BUF_SIZE];&lt;br /&gt;
int pos = BUF_SIZE;&lt;br /&gt;
&lt;br /&gt;
inline char getChar(FILE *f) {&lt;br /&gt;
  if (pos == BUF_SIZE) {&lt;br /&gt;
    fread(buf, 1, BUF_SIZE, f);&lt;br /&gt;
    pos = 0;&lt;br /&gt;
  }&lt;br /&gt;
  return buf[pos++];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
inline int readInt(FILE *f) {&lt;br /&gt;
  int result = 0;&lt;br /&gt;
  char c;&lt;br /&gt;
  do {&lt;br /&gt;
    c = getChar(f);&lt;br /&gt;
  } while (!isdigit(c));&lt;br /&gt;
&lt;br /&gt;
  do {&lt;br /&gt;
    result = 10 * result + c - &#039;0&#039;;&lt;br /&gt;
    c = getChar(f);&lt;br /&gt;
  } while (isdigit(c));&lt;br /&gt;
&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am rulat un experiment cu patru versiuni de program:&lt;br /&gt;
&lt;br /&gt;
* una care citește cu fscanf&lt;br /&gt;
* una care citește cu codul de mai sus&lt;br /&gt;
* una care folosește primele patru optimizări&lt;br /&gt;
* una care folosește și a cincea optimizare&lt;br /&gt;
&lt;br /&gt;
Fișierul de intrare are 10.000.000 de numere (79 MB). Am rulat fiecare program de zece ori, chiar pe serverul Varena, am eliminat cea mai rapidă și cea mai lentă rulare și am calculat media.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! versiune || timp mediu de rulare || sursă&lt;br /&gt;
|-&lt;br /&gt;
| cu fscanf || 4.31 s || [[media:parse1.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu codul de mai sus || 0.819 s || [[media:parse2.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu primele patru optimizări || 0.366 s || [[media:parse3.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu toate optimizările || 0.355 s || [[media:parse3.cpp]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Versiunea mai elegantă nu este mai lentă, ci chiar dimpotrivă. Pentru fișiere cu mărimi mai realiste (5-6 MB), versiunea 3 este cu circa 30 ms mai rapidă decât versiunea 2.&lt;br /&gt;
&lt;br /&gt;
Nu uitați că puteți câștiga timp semnificativ, cam 50% față de versiunea cu &#039;&#039;&#039;fscanf(),&#039;&#039;&#039; folosind &#039;&#039;&#039;getc()&#039;&#039;&#039; pentru citirea caracter cu caracter. Folosim bufferul oferit de sistem. În multe situații poate fi suficient.&lt;br /&gt;
&lt;br /&gt;
Morala? Să renunți la stil și să pierzi și din viteză este un compromis tare păgubos.&lt;br /&gt;
&lt;br /&gt;
== O funcție este un contract ==&lt;br /&gt;
&lt;br /&gt;
* Despre invarianți, cutii negre, contracte și comentarii.&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasele_9-12_lec%C8%9Bia_8_-_5_nov_2014&amp;diff=18532</id>
		<title>Clasele 9-12 lecția 8 - 5 nov 2014</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasele_9-12_lec%C8%9Bia_8_-_5_nov_2014&amp;diff=18532"/>
		<updated>2025-10-26T12:34:52Z</updated>

		<summary type="html">&lt;p&gt;Cata: /* Recursivitate inutilă */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Lecția de astăzi este mai ușoară, ca să ne dea timp să ne pregătim pentru barajele pentru Șumen ([http://varena.ro/runda/barajshumenjuniori2 juniori], [http://varena.ro/runda/barajshumenseniori1 seniori]). Aceasta nu înseamnă că este mai puțin importantă! Aștept de la voi să începeți să aplicați toate lucrurile discutate aici.&lt;br /&gt;
&lt;br /&gt;
= Obiceiuri de programare =&lt;br /&gt;
&lt;br /&gt;
== Preliminarii ==&lt;br /&gt;
&lt;br /&gt;
Sunteți cu toții pasionați de informatică și sunteți buni la algoritmică. Este fantastic, dar nu va fi suficient pe termen lung. Ușor-ușor, trebuie să începeți să vă cizelați și felul în care codați. Trebuie să vă dezvoltați capacitatea de a vă judeca o sursă după alte criterii decât strict punctajul luat la concurs. Vorbim despre criterii ca: ușurința de înțelegere a codului sau ușurința de modificare și extindere.&lt;br /&gt;
&lt;br /&gt;
Noi, profesorii, avem partea noastră de vină în felul dezlânat în care programați voi. Ne batem singuri pe spate că elevii știu algoritmi, dar nu ne pasă cum îi codează. Adevărul este că cele două merg mână în mână, mai ales apropo de ceea ce am mai spus: voi nu vedeți destul cod scris de alții. Deci o lecție pe an dedicată programării este doar un început.&lt;br /&gt;
&lt;br /&gt;
== Programarea aproximativă ==&lt;br /&gt;
&lt;br /&gt;
Banc: cică japonezii aud de Dacia -- mașină bună, fiabilă, piese ieftine. Cumpără planurile de la Dacia și încep și ei să o producă. Doar că, surpriză, de pe linia de asamblare le iese un tractor! Trimit un om la Pitești, să studieze uzina. Se uită el, se întoarce acasă, mai repară japonezii mici probleme la linia lor. Degeaba: de pe linia de asamblare ieșeau tractoare. Disperați, dau telefon în România și cer lămuriri. Românii îi liniștesc: „păi și nouă ne iese tot tractor, dar îl luăm la pilă!”&lt;br /&gt;
&lt;br /&gt;
Toate subproblemele pe care le tratăm aici sunt doar vârfuri ale icebergului. Ele reflectă o problemă mai gravă: programarea aproximativă. Prin programare aproximativă înțelegem o serie de iterații de adăugare de cod care converge către o soluție. Programul capătă formă pe măsură ce îl scriem, ca și ideea de rezolvare:&lt;br /&gt;
&lt;br /&gt;
* Mai am nevoie de un element în vector? Adaug 1 la lungimea lui.&lt;br /&gt;
* M-ar ajuta să am datele sortate? Ce idee bună, apelez un sort().&lt;br /&gt;
* Trebuie să ies prematur dintr-o buclă? Deja am scris-o folosind &#039;&#039;&#039;for&#039;&#039;&#039;, deci inserez un &#039;&#039;&#039;break&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Nu ciocăniți ici și colo, nu programați reactiv, în funcție de erorile din iterația anterioară. Programați activ, gândindu-vă bine la algoritm și la detaliile lui și încercați să-l implementați bine din prima. Nu fiți lăutari, fiți virtuozi.&lt;br /&gt;
&lt;br /&gt;
Veți vrea să mergeți la companii faimoase. Acolo oamenii se uită, încă de la interviu, la stilul vostru de codare. Iar, dacă veți fi acceptați, vă veți poticni la fiecare code review de aceleași observații. Dacă așteptați zece ani până să încercați să vă corectați, s-ar putea să fie prea târziu. Lenevirea creierului duce la o gândire încâlcită.&lt;br /&gt;
&lt;br /&gt;
== Constante ==&lt;br /&gt;
&lt;br /&gt;
Folosim în general constante pentru a face codul mai solid și mai ușor de modificat.&lt;br /&gt;
&lt;br /&gt;
=== Constantele sunt sublime, dar lipsesc cu desăvârșire ===&lt;br /&gt;
&lt;br /&gt;
Cel mai grav este să nu folosiți deloc constante. Orice repetiție, chiar și a unui singur număr, chiar și doar o dată, este o practică riscantă. Un programator bun își ordonează codul astfel încât o modificare undeva în program să nu necesite modificări în alte părți fără legătură. Acest principiu se numește [http://en.wikipedia.org/wiki/Don&#039;t_repeat_yourself Don&#039;t repeat yourself (DRY)].&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int v[100000], w[100000];&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
int max = -2000000000;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 100000&lt;br /&gt;
#define INFINITY 2000000000&lt;br /&gt;
&lt;br /&gt;
int v[MAX_N], w[MAX_N];&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
int max = -INFINITY;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Scenariu catastrofic: declarați 5 vectori de mărime 1.000. Testați programul, care trece toate testele mici. Înlocuiți 1.000 cu 100.000, dar omiteți una dintre aparițiile constantei. Trimiteți problema, care ia 0 puncte.&lt;br /&gt;
&lt;br /&gt;
=== #define N &amp;quot;vreo 100000 și 17, să fie&amp;quot; ===&lt;br /&gt;
&lt;br /&gt;
Aproape fără excepție am observat constante declarate cu „câteva” unități mai mari decât este necesar. Dacă problema specifică N ≤ 100.000, voi declarați 100.002, 100.005, 100.007. Unii mai creativi ajung la 111.111 (ce-i drept, estetica acestui număr este incontestabilă).&lt;br /&gt;
&lt;br /&gt;
De ce faceți asta? Următoarele răspunsuri nu sunt acceptate:&lt;br /&gt;
&lt;br /&gt;
* Îmi este teamă că programul are o viață proprie și că decide, de capul lui, să se reverse cu câteva elemente;&lt;br /&gt;
* Mă gândesc că biții nu sunt bine separați și că se pot păta unii de la alții;&lt;br /&gt;
* Cred că pointerul care iterează prin vector frânează greu și nu se va opri la timp;&lt;br /&gt;
* Am de gând să folosesc vectorul de la 1, deci un element deja s-a pierdut.&lt;br /&gt;
&lt;br /&gt;
Următorul răspuns este acceptat:&lt;br /&gt;
&lt;br /&gt;
* Nu am încredere în aptitudinile mele de programator și în robustețea codului pe care îl produc; vectorul este accesat și modificat în câteva locuri diferite și eu nu pot să demonstrez că mă încadrez în N elemente.&lt;br /&gt;
&lt;br /&gt;
Nu programați în dorul lelii. Fiți stăpâni pe program. Nimeni nu jelește 10 octeți irosiți, dar este vorba de un principiu.&lt;br /&gt;
&lt;br /&gt;
În plus, la matricele multidimensionale risipa se cumulează. 1.010 octeți în loc de 1.000? 1% risipă. 1.010&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt; octeți în loc de 1.000&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;? 3% risipă.&lt;br /&gt;
&lt;br /&gt;
Dacă vă plac algoritmii, veți ajunge să învățați unii tot mai complicați. Dacă nu sunteți pe deplin stăpâni nici pe aceste cărămizi de la bază, vă va fi greu să construiți peste ele.&lt;br /&gt;
&lt;br /&gt;
=== Patul lui Procust pentru constante ===&lt;br /&gt;
&lt;br /&gt;
Adesea aveți nevoie de vectori de lungimi ușor diferite. Poate un vector se termină cu o santinelă, sau poate altul are santinele la ambele capete. Poate altul are N ≤ 100.000 elemente și va mai câștiga alte K ≤ 1.000 elemente în viitor. De cele mai multe ori, voi definiți constanta în program pentru a acoperi toate aceste cazuri.&lt;br /&gt;
&lt;br /&gt;
Nu este bine, tot din principiu. Declarați o constantă egală cu cea specificată de datele problemei, apoi ajustați fiecare vector în funcție de necesități. &lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 101000&lt;br /&gt;
int v[MAX_N], w[MAX_N], x[MAX_N], y[MAX_N];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 100000&lt;br /&gt;
#define MAX_K 1000&lt;br /&gt;
&lt;br /&gt;
int v[MAX_N];&lt;br /&gt;
int w[MAX_N + 1];     // bordat cu santinelă la sfârșit&lt;br /&gt;
int x[MAX_N + 2];     // bordat cu santinelă la ambele capete&lt;br /&gt;
int y[MAX_N + MAX_K]; // Va căpăta un element la fiecare iterație&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Codul vostru va câștiga mult în eleganță și în claritate, căci scopul fiecărei variabile va deveni mai clar. Reiese și că 1 și 2 sunt constante (numărul de santinele nu depinde de mărimea intrării), dar K poate și el să varieze dacă datele problemei se schimbă.&lt;br /&gt;
&lt;br /&gt;
== Break, continue și return prematur ==&lt;br /&gt;
&lt;br /&gt;
Evităm, cu rare excepții, break, continue și return prematur pentru că fac codul mai greu de citit. Se aplică și la concurs, chiar dacă voi sunteți singurii cititori.&lt;br /&gt;
&lt;br /&gt;
=== „Să oprească proștii la stop” ===&lt;br /&gt;
&lt;br /&gt;
La semnele de stop (octogonul roșu), legea spune că trebuie să oprești complet mașina (să stea roata). În practică, mai devreme sau mai târziu toți șoferii ne dăm seama că putem să trecem pe lângă semn fără să oprim complet. Nu cunosc pe nimeni care să respecte la sânge această lege. „Rostogolirea” prin semnul de stop reduce uzura frânelor, deci are un avantaj palpabil. Dar suntem conștienți că facem o prostioară și că nu putem face asta oricând, oriunde -- în special când de pe strada cu prioritate vin mașini. Cine încalcă această lege fără discernământ se expune la accidente, potențial catastrofice.&lt;br /&gt;
&lt;br /&gt;
La fel este și cu programarea nestructurată. Scopul nu este să urmăm orbește niște legi, ci să facem codul mai ușor de înțeles și de întreținut. Prin această prismă, situațiile legitime de folosire a programării nestructurate sunt rarisime.&lt;br /&gt;
&lt;br /&gt;
=== Dar programez mai ușor așa ===&lt;br /&gt;
&lt;br /&gt;
Viața nu e ușoară. Cu toții ne străduim azi ca să ne fie bine mai încolo, pe termen lung.&lt;br /&gt;
&lt;br /&gt;
=== Dar scriu cod mai clar așa ===&lt;br /&gt;
&lt;br /&gt;
Claritatea este în ochii cititorului. Dacă vă obișnuiți să programați aproximativ, orice program neaproximativ vi se va părea straniu.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu tipic: căutarea lui &#039;&#039;x&#039;&#039; în vectorul &#039;&#039;v&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
for (int i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
  if (v[i] == x) {&lt;br /&gt;
    break;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int i = 0;&lt;br /&gt;
while ((i &amp;lt; n) &amp;amp;&amp;amp; (v[i] != x)) {&lt;br /&gt;
  i++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Codul este mai scurt și mai citibil. Versiunea cu break promite o condiție de ieșire (i &amp;gt;= n), apoi ne păcălește ieșind pe o cu totul altă condiție (v[i] == x). În versiunea structurată, ambele condiții sunt listate clar în instrucțiunea &#039;&#039;while&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Un alt exemplu: un test dacă șirul &#039;&#039;s&#039;&#039; este palindrom.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
bool palindrom(char *s){&lt;br /&gt;
  for (int i = 0; 2 * i &amp;lt; n; i++) {&lt;br /&gt;
    if (s[i] != s[n - 1 - i]) {&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
  return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
bool palindrom(char *s){&lt;br /&gt;
  int i = 0;&lt;br /&gt;
  while ((2 * i &amp;lt; n) &amp;amp;&amp;amp; (s[i] == s[n - 1 - i])) {&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
  return (s[i] == s[n - 1 - i]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Dar cum pot să scriu un switch fără break? ===&lt;br /&gt;
&lt;br /&gt;
Limbajul C a apărut prin 1972, când informaticienii abia începeau să înțeleagă diferențele între programarea structurată și cea nestructurată. Demonstrația că orice program se poate scrie [http://en.wikipedia.org/wiki/Structured_program_theorem numai cu blocuri structurate] datează din 1966. În anii următori, polemica programării structurate versus cea nestructurată a polarizat lumea informaticii. [http://en.wikipedia.org/wiki/Go_To_Statement_Considered_Harmful#Criticism_and_decline Dijkstra] era pro-structurare, pe când [http://www.scribd.com/doc/38873257/Knuth-1974-Structured-Programming-With-Go-to-Statements Knuth] a dat exemple când instrucțiunea GOTO produce cod mai citibil.&lt;br /&gt;
&lt;br /&gt;
Nu este, deci, de mirare, că limbajul C încorporează un atavism ca &#039;&#039;break&#039;&#039;. Îl vom folosi, dar doar fiindcă nu putem altfel. Nu abuzăm de el; în special, nu permitem „curgerea” dintr-o ramură într-alta. Fiecare ramură trebuie să aibă &#039;&#039;break&#039;&#039;-ul propriu.&lt;br /&gt;
&lt;br /&gt;
=== Când este acceptabil să programăm nestructurat? ===&lt;br /&gt;
&lt;br /&gt;
Iată câteva exemple:&lt;br /&gt;
&lt;br /&gt;
* Când o funcție are de testat 2-3-4 precondiții, este mai firesc să apelăm &#039;&#039;return&#039;&#039; prematur decât să scriem corpul principal al funcției sub 2-3-4 niveluri de indentare.&lt;br /&gt;
* Cazul de bază în funcțiile recursive poate fi pus la început și terminat cu &#039;&#039;return&#039;&#039;.&lt;br /&gt;
* Pentru &#039;&#039;switch&#039;&#039; folosim &#039;&#039;break&#039;&#039; căci nu putem evita asta.&lt;br /&gt;
&lt;br /&gt;
În toate situațiile folosim &#039;&#039;return&#039;&#039; sau &#039;&#039;break&#039;&#039; dintr-un &#039;&#039;if&#039;&#039;, niciodată dintr-o buclă.&lt;br /&gt;
&lt;br /&gt;
== Un program de 10 linii poate fi [http://en.wikipedia.org/wiki/Wikipedia:Too_long;_didn&#039;t_read TLDR] ==&lt;br /&gt;
&lt;br /&gt;
Ați încercat vreodată să citiți o carte tipărită la o editură de bloc? Cu margini de 1 mm, font de 8 și spațiere minusculă? Nu-i așa că e enervant?&lt;br /&gt;
&lt;br /&gt;
Și programele pot fi ilizibile. Nu veți lucra toată viața de unii singuri, iar într-o echipă lizibilitatea este la fel de importantă ca eficiența. Uneori mi se pare că scopul vostru este un program cât mai scurt. De unde și până unde?&lt;br /&gt;
&lt;br /&gt;
Încercați să deprindeți următoarele aspecte:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;O instrucțiune pe linie.&#039;&#039;&#039; Liniile scurte fac codul mai citibil.&lt;br /&gt;
* &#039;&#039;&#039;Spațierea.&#039;&#039;&#039; O linie goală este ca o virgulă. Folosiți-o acolo unde programul face trecerea între două bucăți separate logic: două funcții, două blocuri logic distincte în aceeași funcție. Nu exagerați cu spațierea. Este important ca fiecare funcție să încapă pe un singur ecran.&lt;br /&gt;
* &#039;&#039;&#039;Lungimea numelor de variabile.&#039;&#039;&#039; O literă este acceptabilă numai pentru scopuri încetățenite: &#039;&#039;i, j, k&#039;&#039; pentru variabilele de ciclu, &#039;&#039;m, n&#039;&#039; pentru dimensiunile matricei. Pentru orice altceva, alegeți nume mai clare (dar nici 3 cuvinte, căci ajungem la linii prea lungi).&lt;br /&gt;
* &#039;&#039;&#039;Goana stupidă după scurtimea codului.&#039;&#039;&#039; Două lucruri rele nu fac un lucru bun. Voi scrieți cod lung pentru că acolo converge procesul iterativ, iar pe parcurs îl scurtați mâncând acolade, spațiere și scurtând numele de variabile.&lt;br /&gt;
* &#039;&#039;&#039;Virgula în loc de acolade.&#039;&#039;&#039; Un caz particular al punctului anterior. Sunteți tentați să folosiți virgula ca să înghesuiți două instrucțiuni în același if, fără să adăugați acolade. Vă puteți păcăli singuri.&lt;br /&gt;
* &#039;&#039;&#039;Folosim for numai pentru număr cunoscut de iterații.&#039;&#039;&#039; Rezistați tentației de a deveni hackeri. Nu este o regulă unanim acceptată, dar este o idee bună. Și [http://en.wikipedia.org/wiki/For_loop Wikipedia] o menționează.&lt;br /&gt;
* &#039;&#039;&#039;Variabile cât mai locale.&#039;&#039;&#039; Dacă tot folosiți toate prostiile din C++, măcar folosiți și lucrurile bune. Declarați variabilele cât mai local (fără însă a împăna tot programul cu declarații). În special evitați variabilele globale pentru programe netriviale. Este ușor să pierdeți socoteala cine le modifică și când.&lt;br /&gt;
* &#039;&#039;&#039;Evitați freopen().&#039;&#039;&#039; Este o idee proastă să închideți &#039;&#039;&#039;stdin&#039;&#039;&#039; și în special &#039;&#039;&#039;stdout&#039;&#039;&#039;. În &#039;&#039;&#039;stdout&#039;&#039;&#039; puteți tipări mesaje de depanare pe care, dacă cumva le uitați în program, nu se întâmplă nimic rău. Dar dacă &#039;&#039;&#039;stdout&#039;&#039;&#039; și fișierul de ieșire al problemei sunt unul și același, riscați să tipăriți gunoaie în fișierul de ieșire. În plus, &#039;&#039;&#039;freopen()&#039;&#039;&#039; este o metodă foarte lipsită de considerație. Într-un program mai mare, cine știe ce bibliotecă vrea să tipărească mesaje pe ecran. Voi îi răpiți această posibilitate.&lt;br /&gt;
&lt;br /&gt;
== Recursivitate inutilă ==&lt;br /&gt;
&lt;br /&gt;
Nu vă fie frică să eliminați recursivitatea acolo unde ea nu este necesară. Cel mai comun exemplu este la problemele de programare dinamică, acolo unde, pe lângă valoarea soluției, se cere și soluția explicită.&lt;br /&gt;
&lt;br /&gt;
Exemplu: cel mai lung subșir comun între două șiruri. Presupunând că avem două șiruri, &#039;&#039;a&#039;&#039; de lungime &#039;&#039;m&#039;&#039;, și &#039;&#039;b&#039;&#039; de lungime &#039;&#039;n&#039;&#039;, și că am calculat matricea &#039;&#039;d&#039;&#039; pentru toate perechile de prefixe, soluția se calculează recursiv astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char s[MAX_SOL];&lt;br /&gt;
&lt;br /&gt;
void solutie(int i, int j) {&lt;br /&gt;
  if (d[i][j]) {&lt;br /&gt;
    if (a[i] == b[j]) {&lt;br /&gt;
      solutie(i - 1, j - 1);&lt;br /&gt;
      s[d[i][j] - 1] = a[i];&lt;br /&gt;
    } else if (d[i][j - 1] &amp;gt; d[i - 1][j] {&lt;br /&gt;
      solutie(d[i][j - 1]);&lt;br /&gt;
    } else {&lt;br /&gt;
      solutie(d[i - 1][j]);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
...&lt;br /&gt;
solutie(m - 1, n - 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nu este rău, dar nu uitați că recursivitatea cere timp și memorie! Stiva de apeluri poate avea O(n) niveluri. Dacă spațiul sau timpul sunt o problemă, eliminați recursivitatea:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
char solutie[MAX_SOL];&lt;br /&gt;
&lt;br /&gt;
int i = m - 1, j = n - 1;&lt;br /&gt;
solutie[d[i][j]] = &#039;\0&#039;;&lt;br /&gt;
&lt;br /&gt;
while (d[i][j]) {&lt;br /&gt;
  if (a[i] == b[j]) {&lt;br /&gt;
    solutie[d[i][j] - 1] = a[i];&lt;br /&gt;
    i--;&lt;br /&gt;
    j--;&lt;br /&gt;
  } else if (d[i][j - 1] &amp;gt; d[i - 1][j] {&lt;br /&gt;
    j--;&lt;br /&gt;
  } else {&lt;br /&gt;
    i--;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Se pierde foarte puțin din claritatea codului, căci relația de recurență nu mai este susținută de apelul recursiv propriu-zis. Totuși, condițiile de recurență (din if-uri) sunt încă vizibile, iar codul necesită O(1) spațiu.&lt;br /&gt;
&lt;br /&gt;
== STL este o armă cu două tăișuri ==&lt;br /&gt;
&lt;br /&gt;
Această secțiune se aplică surselor scrise în afara concursurilor. La concurs, totul este permis. Totuși, nu uitați că omul folosește sculele cu care este obișnuit.&lt;br /&gt;
&lt;br /&gt;
De ce venim la cerc? Pentru că vrem să învățăm algoritmi, nu doar să-i apelăm. Cu algoritmii din STL programați mai repede, dar nu învățați nimic. Sunt numeroase exemplele când trebuie să înțelegeți (și să particularizați) porțiuni dintr-un algoritm: pivotarea din quicksort, construirea automatului din KMP, operațiile pe vectori de biți.&lt;br /&gt;
&lt;br /&gt;
Nu uitați că algoritmii din STL pot fi mai lenți sau mai consumatori de memorie decât cei scriși de voi. Este firesc, căci aceia sunt prea generali și adesea fac operații care nu sunt necesare pentru problema voastră. Qsort din stdlib, de exemplu, necesită aproape dublul memoriei și este mai lent. Am observat asta de mai multe ori. De exemplu, am trimis [http://varena.ro/job_detail/29271 două] [http://varena.ro/job_detail/29277 surse] la problema [http://varena.ro/problema/avioane Avioane]. A doua diferă doar prin faptul că mi-am scris propriul quicksort. Nu știu dacă și sortarea din biblioteca algorithm are această problemă.&lt;br /&gt;
&lt;br /&gt;
La problema [http://varena.ro/problema/dirty Dirty], am comparat sursa mea cu a unor concurenți care făceau cam același lucru, doar că foloseau vectori STL (și iteratori) în loc de vectori C. Diferența de timp este considerabilă. Ajungeți să parsați manual fișierul de intrare ca să câștigați timp. Care mai e câștigul? Ce luați pe mere dați pe pere.&lt;br /&gt;
&lt;br /&gt;
Nu uitați de ce lucrăm în C/C++, nu în PHP sau în Ruby: avem nevoie de viteză, iar C se pretează cel mai bine la asta.&lt;br /&gt;
&lt;br /&gt;
=== Sortarea, înger și demon ===&lt;br /&gt;
&lt;br /&gt;
Sortarea din STL merită un capitol special. Nu zic să nu o folosiți ca să câștigați 10 minute la concurs. (Dacă vă ia mai mult de 10 minute să vă implementați propria sortare, deja avem probleme mai grave). Dar dacă dați una-două fuga la sortare, riscați să pierdeți esențialul din vedere.&lt;br /&gt;
&lt;br /&gt;
* Există cel puțin 5 sortări ușor de codat, fiecare cu avantajele ei (quicksort, mergesort, heapsort, radix sort, counting sort). Fiecare dintre ele are avantaje și poate fi personalizată. Luați ca exemplu problema numărării inversiunilor dintr-o permutare, dată la [http://www.math.bas.bg/keleved/shumen2013 Șumen 2013], care se rezolvă ușor cu un merge sort adaptat. Dacă vă gândiți la sortare ca la o cutie neagră, veți uita ce posibilități aveți.&lt;br /&gt;
* Radix sort și counting sort sunt O(n)! Dacă problema se pretează la o astfel de sortare, atunci &#039;&#039;&#039;sort()&#039;&#039;&#039; nu ajută, ci dăunează.&lt;br /&gt;
* Unele probleme se pot rezolva pur și simplu fără sortare. Nu vă adormiți gândirea cu stereotipuri de genul „încep cu o sortare, să fie”.&lt;br /&gt;
&lt;br /&gt;
== Department of Redundancy Department ==&lt;br /&gt;
&lt;br /&gt;
Evitați să copiați bucăți mari de cod cu copy-paste. Îl avem pe Victor Ponta pentru asta. Dacă aveți cazuri &#039;&#039;&#039;aproape&#039;&#039;&#039; identice, dar diferite pe alocuri, este mai bine să parametrizați codul și să-l mutați într-o funcție sau chiar într-un &#039;&#039;for&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Un exemplu concret: problema [http://varena.ro/problema/avioane Avioane] cerea, pentru anumite elemente dintr-un vector, calcularea unor distanțe la stânga și la dreapta. Cu toții ați rezolvat problema pentru direcția stângă, apoi ați dublat codul și ați modificat ce era necesar ca să-l faceți să meargă și spre dreapta. Mă simt onorat că v-a plăcut problema atât de mult, încât ați rezolvat-o de două ori. :-) Iată două metode de a elimina redundanța:&lt;br /&gt;
&lt;br /&gt;
# Parametrizați tot ce se modifică între direcția stângă și cea dreaptă: punctul de pornire, cel de oprire, pasul (+/- 1). Fratele meu Cristi a pus tot codul într-un for în care singurul parametru era pasul (+/- 1). Deci se poate.&lt;br /&gt;
# Calculați toate valorile spre stânga, apoi întoarceți vectorul pe dos și recalculați valorile tot spre stânga.&lt;br /&gt;
&lt;br /&gt;
Din nou, principiul este DRY: &#039;&#039;Don&#039;t Repeat Yourself&#039;&#039;. Riscați să găsiți un bug într-una din copii și să uitați să-l reparați și în cealaltă.&lt;br /&gt;
&lt;br /&gt;
Bugurile de redundanță sunt greu de reparat pentru că avem de luptat și împotriva naturii noastre umane. Ni se pare că nu are sens să citim ambele blocuri, căci a doua e „la fel ca prima”. Nu vi s-a întâmplat niciodată să vă uitați 10 minute fără să vă dați seama ce e greșit în codul de mai jos?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
for (int i = 0; i &amp;lt; m; i++) {&lt;br /&gt;
  for (int j = 0; i &amp;lt; n; j++) {&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Dimensiunea datelor de intrare ==&lt;br /&gt;
&lt;br /&gt;
Fiți întotdeauna atenți la valorile minime și maxime pe care le poate lua o variabilă. Dacă vă obișnuiți să lucrați numai cu &#039;&#039;int&#039;&#039; și cu &#039;&#039;long long&#039;&#039;, veți uita că puteți face economii mari de memorie folosind short și char.&lt;br /&gt;
&lt;br /&gt;
Mai mult, uneori valorile încap pe 4 biți, pe 2 sau chiar pe unul singur. Toate aceste cazuri pot fi tratate ușor cu rutine de 1-2 linii. Iată o implementare pentru baza 4  (codul complet este în [[media:two-bit.cpp]]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 1000000&lt;br /&gt;
&lt;br /&gt;
unsigned char b[MAX_N / 4];&lt;br /&gt;
&lt;br /&gt;
int get(int i) {&lt;br /&gt;
  return (b[i/4] &amp;gt;&amp;gt; (2 * (i % 4))) &amp;amp; 3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void set(int i, int x) {&lt;br /&gt;
  // Clear the previous value if needed&lt;br /&gt;
  b[i/4] &amp;amp;= ~(3 &amp;lt;&amp;lt; (2 * (i % 4)));&lt;br /&gt;
  b[i/4] ^= x &amp;lt;&amp;lt; (2 * (i % 4));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Exemplu: la problema [http://varena.ro/problema/palindrom Palindrom] primeați foarte puțină memorie. Algoritmul de programare dinamică nu este dificil, dar pentru reconstituirea soluției avem nevoie de întreaga matrice, mai exact de triunghiul superior al unei matrice de dimensiuni n x n. Cum n ≤ 5.000, în aparență avem nevoie de 25 MB. În realitate, o celulă din matrice necesită un singur bit pentru stocare, deci necesarul de memorie este 5.000 x 5.000 / 2 / 8 = 1,56 MB.&lt;br /&gt;
&lt;br /&gt;
== Parsarea intrării: când ai un ciocan, totul în jur pare un cui ==&lt;br /&gt;
&lt;br /&gt;
Haideți să lămurim ceva: profesorii și propunătorii de probleme nu sunt sadici. Nu stau zile întregi să stoarcă fiecare picătură de eficiență dintr-o implementare, pentru ca apoi să vă ceară vouă să reușiți același lucru într-o oră. Limitele de timp sunt, în general, alese adăugând o marjă generoasă la o implementare curată, dar neoptimizată la sânge.&lt;br /&gt;
&lt;br /&gt;
Dacă doriți să câștigați câteva zeci de milisecunde, aveți opțiunea să parsați intrarea. Dar amintiți-vă că sunteți pe un drum greșit și undeva ați pierdut din vedere ceva mult mai important. Nu lucrați cu ciocanul. Lucrați cu bisturiul.&lt;br /&gt;
&lt;br /&gt;
Dacă totuși faceți parsarea datelor de intrare, faceți-o cu stil. Am observat codul următor (îl public anonim, dă-mi de veste dacă dorești credit).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
const int DIM = (1 &amp;lt;&amp;lt; 12);&lt;br /&gt;
char buff[DIM];&lt;br /&gt;
int poz = 0;&lt;br /&gt;
&lt;br /&gt;
void read(int &amp;amp;numar) {&lt;br /&gt;
    numar = 0;&lt;br /&gt;
    while (buff[poz] &amp;lt; &#039;0&#039; || buff[poz] &amp;gt; &#039;9&#039;)&lt;br /&gt;
        if (++poz == DIM)&lt;br /&gt;
            fread(buff, 1, DIM, stdin), poz = 0;&lt;br /&gt;
    while (&#039;0&#039; &amp;lt;= buff[poz] &amp;amp;&amp;amp; buff[poz] &amp;lt;= &#039;9&#039;) {&lt;br /&gt;
        numar = numar * 10 + buff[poz] - &#039;0&#039;;&lt;br /&gt;
        if (++poz == DIM)&lt;br /&gt;
            fread(buff, 1, DIM, stdin), poz = 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Hai să-l scriem mai elegant și mai eficient de atât. I-am adus următoarele îmbunătățiri:&lt;br /&gt;
&lt;br /&gt;
* (de stil) Funcția de citire a unui caracter trebuie separată. Separarea bufferării la un nivel abstract este un lucru bun. Această funcție amestecă citirea bufferată cu parsarea numărului.&lt;br /&gt;
* (de stil) Funcția citește în gol bufferul prima dată înainte de a citi primul bloc de pe disc. Este păcat.&lt;br /&gt;
* (de eficiență) Neapărat testați prezența unei cifre cu &#039;&#039;&#039;isdigit()&#039;&#039;&#039; din &amp;amp;lt;ctype.h&amp;amp;gt;. Codul de mai sus testează două condiții. &#039;&#039;&#039;isdigit()&#039;&#039;&#039; nu testează decât una, căci are un tabel de 256 de valori.&lt;br /&gt;
* (de eficiență) Declarați funcția / funcțiile inline. Compilatorul își poate da seama că este nevoie de asta, dar nu e garantat.&lt;br /&gt;
* (posibil de eficiență) Declarați fișierul de intrare global, ca să nu mai fie trimis ca parametru. Nu ar trebui să aibă niciun efect.&lt;br /&gt;
&lt;br /&gt;
Primele patru modificări produc codul:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define BUF_SIZE 4096&lt;br /&gt;
char buf[BUF_SIZE];&lt;br /&gt;
int pos = BUF_SIZE;&lt;br /&gt;
&lt;br /&gt;
inline char getChar(FILE *f) {&lt;br /&gt;
  if (pos == BUF_SIZE) {&lt;br /&gt;
    fread(buf, 1, BUF_SIZE, f);&lt;br /&gt;
    pos = 0;&lt;br /&gt;
  }&lt;br /&gt;
  return buf[pos++];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
inline int readInt(FILE *f) {&lt;br /&gt;
  int result = 0;&lt;br /&gt;
  char c;&lt;br /&gt;
  do {&lt;br /&gt;
    c = getChar(f);&lt;br /&gt;
  } while (!isdigit(c));&lt;br /&gt;
&lt;br /&gt;
  do {&lt;br /&gt;
    result = 10 * result + c - &#039;0&#039;;&lt;br /&gt;
    c = getChar(f);&lt;br /&gt;
  } while (isdigit(c));&lt;br /&gt;
&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am rulat un experiment cu patru versiuni de program:&lt;br /&gt;
&lt;br /&gt;
* una care citește cu fscanf&lt;br /&gt;
* una care citește cu codul de mai sus&lt;br /&gt;
* una care folosește primele patru optimizări&lt;br /&gt;
* una care folosește și a cincea optimizare&lt;br /&gt;
&lt;br /&gt;
Fișierul de intrare are 10.000.000 de numere (79 MB). Am rulat fiecare program de zece ori, chiar pe serverul Varena, am eliminat cea mai rapidă și cea mai lentă rulare și am calculat media.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! versiune || timp mediu de rulare || sursă&lt;br /&gt;
|-&lt;br /&gt;
| cu fscanf || 4.31 s || [[media:parse1.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu codul de mai sus || 0.819 s || [[media:parse2.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu primele patru optimizări || 0.366 s || [[media:parse3.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu toate optimizările || 0.355 s || [[media:parse3.cpp]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Versiunea mai elegantă nu este mai lentă, ci chiar dimpotrivă. Pentru fișiere cu mărimi mai realiste (5-6 MB), versiunea 3 este cu circa 30 ms mai rapidă decât versiunea 2.&lt;br /&gt;
&lt;br /&gt;
Nu uitați că puteți câștiga timp semnificativ, cam 50% față de versiunea cu &#039;&#039;&#039;fscanf(),&#039;&#039;&#039; folosind &#039;&#039;&#039;getc()&#039;&#039;&#039; pentru citirea caracter cu caracter. Folosim bufferul oferit de sistem. În multe situații poate fi suficient.&lt;br /&gt;
&lt;br /&gt;
Morala? Să renunți la stil și să pierzi și din viteză este un compromis tare păgubos.&lt;br /&gt;
&lt;br /&gt;
== O funcție este un contract ==&lt;br /&gt;
&lt;br /&gt;
* Despre invarianți, cutii negre, contracte și comentarii.&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Clasele_9-12_lec%C8%9Bia_8_-_5_nov_2014&amp;diff=18294</id>
		<title>Clasele 9-12 lecția 8 - 5 nov 2014</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Clasele_9-12_lec%C8%9Bia_8_-_5_nov_2014&amp;diff=18294"/>
		<updated>2023-09-11T06:24:51Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Lecția de astăzi este mai ușoară, ca să ne dea timp să ne pregătim pentru barajele pentru Șumen ([http://varena.ro/runda/barajshumenjuniori2 juniori], [http://varena.ro/runda/barajshumenseniori1 seniori]). Aceasta nu înseamnă că este mai puțin importantă! Aștept de la voi să începeți să aplicați toate lucrurile discutate aici.&lt;br /&gt;
&lt;br /&gt;
= Obiceiuri de programare =&lt;br /&gt;
&lt;br /&gt;
== Preliminarii ==&lt;br /&gt;
&lt;br /&gt;
Sunteți cu toții pasionați de informatică și sunteți buni la algoritmică. Este fantastic, dar nu va fi suficient pe termen lung. Ușor-ușor, trebuie să începeți să vă cizelați și felul în care codați. Trebuie să vă dezvoltați capacitatea de a vă judeca o sursă după alte criterii decât strict punctajul luat la concurs. Vorbim despre criterii ca: ușurința de înțelegere a codului sau ușurința de modificare și extindere.&lt;br /&gt;
&lt;br /&gt;
Noi, profesorii, avem partea noastră de vină în felul dezlânat în care programați voi. Ne batem singuri pe spate că elevii știu algoritmi, dar nu ne pasă cum îi codează. Adevărul este că cele două merg mână în mână, mai ales apropo de ceea ce am mai spus: voi nu vedeți destul cod scris de alții. Deci o lecție pe an dedicată programării este doar un început.&lt;br /&gt;
&lt;br /&gt;
== Programarea aproximativă ==&lt;br /&gt;
&lt;br /&gt;
Banc: cică japonezii aud de Dacia -- mașină bună, fiabilă, piese ieftine. Cumpără planurile de la Dacia și încep și ei să o producă. Doar că, surpriză, de pe linia de asamblare le iese un tractor! Trimit un om la Pitești, să studieze uzina. Se uită el, se întoarce acasă, mai repară japonezii mici probleme la linia lor. Degeaba: de pe linia de asamblare ieșeau tractoare. Disperați, dau telefon în România și cer lămuriri. Românii îi liniștesc: „păi și nouă ne iese tot tractor, dar îl luăm la pilă!”&lt;br /&gt;
&lt;br /&gt;
Toate subproblemele pe care le tratăm aici sunt doar vârfuri ale icebergului. Ele reflectă o problemă mai gravă: programarea aproximativă. Prin programare aproximativă înțelegem o serie de iterații de adăugare de cod care converge către o soluție. Programul capătă formă pe măsură ce îl scriem, ca și ideea de rezolvare:&lt;br /&gt;
&lt;br /&gt;
* Mai am nevoie de un element în vector? Adaug 1 la lungimea lui.&lt;br /&gt;
* M-ar ajuta să am datele sortate? Ce idee bună, apelez un sort().&lt;br /&gt;
* Trebuie să ies prematur dintr-o buclă? Deja am scris-o folosind &#039;&#039;&#039;for&#039;&#039;&#039;, deci inserez un &#039;&#039;&#039;break&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Nu ciocăniți ici și colo, nu programați reactiv, în funcție de erorile din iterația anterioară. Programați activ, gândindu-vă bine la algoritm și la detaliile lui și încercați să-l implementați bine din prima. Nu fiți lăutari, fiți virtuozi.&lt;br /&gt;
&lt;br /&gt;
Veți vrea să mergeți la companii faimoase. Acolo oamenii se uită, încă de la interviu, la stilul vostru de codare. Iar, dacă veți fi acceptați, vă veți poticni la fiecare code review de aceleași observații. Dacă așteptați zece ani până să încercați să vă corectați, s-ar putea să fie prea târziu. Lenevirea creierului duce la o gândire încâlcită.&lt;br /&gt;
&lt;br /&gt;
== Constante ==&lt;br /&gt;
&lt;br /&gt;
Folosim în general constante pentru a face codul mai solid și mai ușor de modificat.&lt;br /&gt;
&lt;br /&gt;
=== Constantele sunt sublime, dar lipsesc cu desăvârșire ===&lt;br /&gt;
&lt;br /&gt;
Cel mai grav este să nu folosiți deloc constante. Orice repetiție, chiar și a unui singur număr, chiar și doar o dată, este o practică riscantă. Un programator bun își ordonează codul astfel încât o modificare undeva în program să nu necesite modificări în alte părți fără legătură. Acest principiu se numește [http://en.wikipedia.org/wiki/Don&#039;t_repeat_yourself Don&#039;t repeat yourself (DRY)].&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int v[100000], w[100000];&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
int max = -2000000000;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 100000&lt;br /&gt;
#define INFINITY 2000000000&lt;br /&gt;
&lt;br /&gt;
int v[MAX_N], w[MAX_N];&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
int max = -INFINITY;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Scenariu catastrofic: declarați 5 vectori de mărime 1.000. Testați programul, care trece toate testele mici. Înlocuiți 1.000 cu 100.000, dar omiteți una dintre aparițiile constantei. Trimiteți problema, care ia 0 puncte.&lt;br /&gt;
&lt;br /&gt;
=== #define N &amp;quot;vreo 100000 și 17, să fie&amp;quot; ===&lt;br /&gt;
&lt;br /&gt;
Aproape fără excepție am observat constante declarate cu „câteva” unități mai mari decât este necesar. Dacă problema specifică N ≤ 100.000, voi declarați 100.002, 100.005, 100.007. Unii mai creativi ajung la 111.111 (ce-i drept, estetica acestui număr este incontestabilă).&lt;br /&gt;
&lt;br /&gt;
De ce faceți asta? Următoarele răspunsuri nu sunt acceptate:&lt;br /&gt;
&lt;br /&gt;
* Îmi este teamă că programul are o viață proprie și că decide, de capul lui, să se reverse cu câteva elemente;&lt;br /&gt;
* Mă gândesc că biții nu sunt bine separați și că se pot păta unii de la alții;&lt;br /&gt;
* Cred că pointerul care iterează prin vector frânează greu și nu se va opri la timp;&lt;br /&gt;
* Am de gând să folosesc vectorul de la 1, deci un element deja s-a pierdut.&lt;br /&gt;
&lt;br /&gt;
Următorul răspuns este acceptat:&lt;br /&gt;
&lt;br /&gt;
* Nu am încredere în aptitudinile mele de programator și în robustețea codului pe care îl produc; vectorul este accesat și modificat în câteva locuri diferite și eu nu pot să demonstrez că mă încadrez în N elemente.&lt;br /&gt;
&lt;br /&gt;
Nu programați în dorul lelii. Fiți stăpâni pe program. Nimeni nu jelește 10 octeți irosiți, dar este vorba de un principiu.&lt;br /&gt;
&lt;br /&gt;
În plus, la matricele multidimensionale risipa se cumulează. 1.010 octeți în loc de 1.000? 1% risipă. 1.010&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt; octeți în loc de 1.000&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;? 3% risipă.&lt;br /&gt;
&lt;br /&gt;
Dacă vă plac algoritmii, veți ajunge să învățați unii tot mai complicați. Dacă nu sunteți pe deplin stăpâni nici pe aceste cărămizi de la bază, vă va fi greu să construiți peste ele.&lt;br /&gt;
&lt;br /&gt;
=== Patul lui Procust pentru constante ===&lt;br /&gt;
&lt;br /&gt;
Adesea aveți nevoie de vectori de lungimi ușor diferite. Poate un vector se termină cu o santinelă, sau poate altul are santinele la ambele capete. Poate altul are N ≤ 100.000 elemente și va mai câștiga alte K ≤ 1.000 elemente în viitor. De cele mai multe ori, voi definiți constanta în program pentru a acoperi toate aceste cazuri.&lt;br /&gt;
&lt;br /&gt;
Nu este bine, tot din principiu. Declarați o constantă egală cu cea specificată de datele problemei, apoi ajustați fiecare vector în funcție de necesități. &lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 101000&lt;br /&gt;
int v[MAX_N], w[MAX_N], x[MAX_N], y[MAX_N];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 100000&lt;br /&gt;
#define MAX_K 1000&lt;br /&gt;
&lt;br /&gt;
int v[MAX_N];&lt;br /&gt;
int w[MAX_N + 1];     // bordat cu santinelă la sfârșit&lt;br /&gt;
int x[MAX_N + 2];     // bordat cu santinelă la ambele capete&lt;br /&gt;
int y[MAX_N + MAX_K]; // Va căpăta un element la fiecare iterație&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Codul vostru va câștiga mult în eleganță și în claritate, căci scopul fiecărei variabile va deveni mai clar. Reiese și că 1 și 2 sunt constante (numărul de santinele nu depinde de mărimea intrării), dar K poate și el să varieze dacă datele problemei se schimbă.&lt;br /&gt;
&lt;br /&gt;
== Break, continue și return prematur ==&lt;br /&gt;
&lt;br /&gt;
Evităm, cu rare excepții, break, continue și return prematur pentru că fac codul mai greu de citit. Se aplică și la concurs, chiar dacă voi sunteți singurii cititori.&lt;br /&gt;
&lt;br /&gt;
=== „Să oprească proștii la stop” ===&lt;br /&gt;
&lt;br /&gt;
La semnele de stop (octogonul roșu), legea spune că trebuie să oprești complet mașina (să stea roata). În practică, mai devreme sau mai târziu toți șoferii ne dăm seama că putem să trecem pe lângă semn fără să oprim complet. Nu cunosc pe nimeni care să respecte la sânge această lege. „Rostogolirea” prin semnul de stop reduce uzura frânelor, deci are un avantaj palpabil. Dar suntem conștienți că facem o prostioară și că nu putem face asta oricând, oriunde -- în special când de pe strada cu prioritate vin mașini. Cine încalcă această lege fără discernământ se expune la accidente, potențial catastrofice.&lt;br /&gt;
&lt;br /&gt;
La fel este și cu programarea nestructurată. Scopul nu este să urmăm orbește niște legi, ci să facem codul mai ușor de înțeles și de întreținut. Prin această prismă, situațiile legitime de folosire a programării nestructurate sunt rarisime.&lt;br /&gt;
&lt;br /&gt;
=== Dar programez mai ușor așa ===&lt;br /&gt;
&lt;br /&gt;
Viața nu e ușoară. Cu toții ne străduim azi ca să ne fie bine mai încolo, pe termen lung.&lt;br /&gt;
&lt;br /&gt;
=== Dar scriu cod mai clar așa ===&lt;br /&gt;
&lt;br /&gt;
Claritatea este în ochii cititorului. Dacă vă obișnuiți să programați aproximativ, orice program neaproximativ vi se va părea straniu.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu tipic: căutarea lui &#039;&#039;x&#039;&#039; în vectorul &#039;&#039;v&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
for (int i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
  if (v[i] == x) {&lt;br /&gt;
    break;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
int i = 0;&lt;br /&gt;
while ((i &amp;lt; n) &amp;amp;&amp;amp; (v[i] != x)) {&lt;br /&gt;
  i++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Codul este mai scurt și mai citibil. Versiunea cu break promite o condiție de ieșire (i &amp;gt;= n), apoi ne păcălește ieșind pe o cu totul altă condiție (v[i] == x). În versiunea structurată, ambele condiții sunt listate clar în instrucțiunea &#039;&#039;while&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Un alt exemplu: un test dacă șirul &#039;&#039;s&#039;&#039; este palindrom.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
bool palindrom(char *s){&lt;br /&gt;
  for (int i = 0; 2 * i &amp;lt; n; i++) {&lt;br /&gt;
    if (s[i] != s[n - 1 - i]) {&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
  return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
bool palindrom(char *s){&lt;br /&gt;
  int i = 0;&lt;br /&gt;
  while ((2 * i &amp;lt; n) &amp;amp;&amp;amp; (s[i] == s[n - 1 - i])) {&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
  return (s[i] == s[n - 1 - i]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Dar cum pot să scriu un switch fără break? ===&lt;br /&gt;
&lt;br /&gt;
Limbajul C a apărut prin 1972, când informaticienii abia începeau să înțeleagă diferențele între programarea structurată și cea nestructurată. Demonstrația că orice program se poate scrie [http://en.wikipedia.org/wiki/Structured_program_theorem numai cu blocuri structurate] datează din 1966. În anii următori, polemica programării structurate versus cea nestructurată a polarizat lumea informaticii. [http://en.wikipedia.org/wiki/Go_To_Statement_Considered_Harmful#Criticism_and_decline Dijkstra] era pro-structurare, pe când [http://www.scribd.com/doc/38873257/Knuth-1974-Structured-Programming-With-Go-to-Statements Knuth] a dat exemple când instrucțiunea GOTO produce cod mai citibil.&lt;br /&gt;
&lt;br /&gt;
Nu este, deci, de mirare, că limbajul C încorporează un atavism ca &#039;&#039;break&#039;&#039;. Îl vom folosi, dar doar fiindcă nu putem altfel. Nu abuzăm de el; în special, nu permitem „curgerea” dintr-o ramură într-alta. Fiecare ramură trebuie să aibă &#039;&#039;break&#039;&#039;-ul propriu.&lt;br /&gt;
&lt;br /&gt;
=== Când este acceptabil să programăm nestructurat? ===&lt;br /&gt;
&lt;br /&gt;
Iată câteva exemple:&lt;br /&gt;
&lt;br /&gt;
* Când o funcție are de testat 2-3-4 precondiții, este mai firesc să apelăm &#039;&#039;return&#039;&#039; prematur decât să scriem corpul principal al funcției sub 2-3-4 niveluri de indentare.&lt;br /&gt;
* Cazul de bază în funcțiile recursive poate fi pus la început și terminat cu &#039;&#039;return&#039;&#039;.&lt;br /&gt;
* Pentru &#039;&#039;switch&#039;&#039; folosim &#039;&#039;break&#039;&#039; căci nu putem evita asta.&lt;br /&gt;
&lt;br /&gt;
În toate situațiile folosim &#039;&#039;return&#039;&#039; sau &#039;&#039;break&#039;&#039; dintr-un &#039;&#039;if&#039;&#039;, niciodată dintr-o buclă.&lt;br /&gt;
&lt;br /&gt;
== Un program de 10 linii poate fi [http://en.wikipedia.org/wiki/Wikipedia:Too_long;_didn&#039;t_read TLDR] ==&lt;br /&gt;
&lt;br /&gt;
Ați încercat vreodată să citiți o carte tipărită la o editură de bloc? Cu margini de 1 mm, font de 8 și spațiere minusculă? Nu-i așa că e enervant?&lt;br /&gt;
&lt;br /&gt;
Și programele pot fi ilizibile. Nu veți lucra toată viața de unii singuri, iar într-o echipă lizibilitatea este la fel de importantă ca eficiența. Uneori mi se pare că scopul vostru este un program cât mai scurt. De unde și până unde?&lt;br /&gt;
&lt;br /&gt;
Încercați să deprindeți următoarele aspecte:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;O instrucțiune pe linie.&#039;&#039;&#039; Liniile scurte fac codul mai citibil.&lt;br /&gt;
* &#039;&#039;&#039;Spațierea.&#039;&#039;&#039; O linie goală este ca o virgulă. Folosiți-o acolo unde programul face trecerea între două bucăți separate logic: două funcții, două blocuri logic distincte în aceeași funcție. Nu exagerați cu spațierea. Este important ca fiecare funcție să încapă pe un singur ecran.&lt;br /&gt;
* &#039;&#039;&#039;Lungimea numelor de variabile.&#039;&#039;&#039; O literă este acceptabilă numai pentru scopuri încetățenite: &#039;&#039;i, j, k&#039;&#039; pentru variabilele de ciclu, &#039;&#039;m, n&#039;&#039; pentru dimensiunile matricei. Pentru orice altceva, alegeți nume mai clare (dar nici 3 cuvinte, căci ajungem la linii prea lungi).&lt;br /&gt;
* &#039;&#039;&#039;Goana stupidă după scurtimea codului.&#039;&#039;&#039; Două lucruri rele nu fac un lucru bun. Voi scrieți cod lung pentru că acolo converge procesul iterativ, iar pe parcurs îl scurtați mâncând acolade, spațiere și scurtând numele de variabile.&lt;br /&gt;
* &#039;&#039;&#039;Virgula în loc de acolade.&#039;&#039;&#039; Un caz particular al punctului anterior. Sunteți tentați să folosiți virgula ca să înghesuiți două instrucțiuni în același if, fără să adăugați acolade. Vă puteți păcăli singuri.&lt;br /&gt;
* &#039;&#039;&#039;Folosim for numai pentru număr cunoscut de iterații.&#039;&#039;&#039; Rezistați tentației de a deveni hackeri. Nu este o regulă unanim acceptată, dar este o idee bună. Și [http://en.wikipedia.org/wiki/For_loop Wikipedia] o menționează.&lt;br /&gt;
* &#039;&#039;&#039;Variabile cât mai locale.&#039;&#039;&#039; Dacă tot folosiți toate prostiile din C++, măcar folosiți și lucrurile bune. Declarați variabilele cât mai local (fără însă a împăna tot programul cu declarații). În special evitați variabilele globale pentru programe netriviale. Este ușor să pierdeți socoteala cine le modifică și când.&lt;br /&gt;
* &#039;&#039;&#039;Evitați freopen().&#039;&#039;&#039; Este o idee proastă să închideți &#039;&#039;&#039;stdin&#039;&#039;&#039; și în special &#039;&#039;&#039;stdout&#039;&#039;&#039;. În &#039;&#039;&#039;stdout&#039;&#039;&#039; puteți tipări mesaje de depanare pe care, dacă cumva le uitați în program, nu se întâmplă nimic rău. Dar dacă &#039;&#039;&#039;stdout&#039;&#039;&#039; și fișierul de ieșire al problemei sunt unul și același, riscați să tipăriți gunoaie în fișierul de ieșire. În plus, &#039;&#039;&#039;freopen()&#039;&#039;&#039; este o metodă foarte lipsită de considerație. Într-un program mai mare, cine știe ce bibliotecă vrea să tipărească mesaje pe ecran. Voi îi răpiți această posibilitate.&lt;br /&gt;
&lt;br /&gt;
== Recursivitate inutilă ==&lt;br /&gt;
&lt;br /&gt;
Nu vă fie frică să eliminați recursivitatea acolo unde ea nu este necesară. Cel mai comun exemplu este la problemele de programare dinamică, acolo unde, pe lângă valoarea soluției, se cere și soluția explicită.&lt;br /&gt;
&lt;br /&gt;
Exemplu: cel mai lung subșir comun între două șiruri. Presupunând că avem două șiruri, &#039;&#039;a&#039;&#039; de lungime &#039;&#039;m&#039;&#039;, și &#039;&#039;b&#039;&#039; de lungime &#039;&#039;n&#039;&#039;, și că am calculat matricea &#039;&#039;d&#039;&#039; pentru toate perechile de prefixe, soluția se calculează recursiv astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
char s[MAX_SOL];&lt;br /&gt;
&lt;br /&gt;
void solutie(int i, int j) {&lt;br /&gt;
  if (d[i][j]) {&lt;br /&gt;
    if (a[i] == b[j]) {&lt;br /&gt;
      solutie(i - 1, j - 1);&lt;br /&gt;
      s[d[i][j] - 1] = a[i];&lt;br /&gt;
    } else if (d[i][j - 1] &amp;gt; d[i - 1][j] {&lt;br /&gt;
      solutie(d[i][j - 1]);&lt;br /&gt;
    } else {&lt;br /&gt;
      solutie(d[i - 1][j]);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
...&lt;br /&gt;
solutie(m - 1, n - 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nu este rău, dar nu uitați că recursivitatea cere timp și memorie! Stiva de apeluri poate avea O(n) niveluri. Dacă spațiul sau timpul sunt o problemă, eliminați recursivitatea:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
char solutie[MAX_SOL];&lt;br /&gt;
&lt;br /&gt;
int i = m - 1, j = n - 1;&lt;br /&gt;
solutie[d[i][j]] = &#039;\0&#039;;&lt;br /&gt;
&lt;br /&gt;
while (d[i][j]) {&lt;br /&gt;
  if (a[i] == b[j]) {&lt;br /&gt;
    solutie[d[i][j] - 1] = a[i];&lt;br /&gt;
    i--;&lt;br /&gt;
    j--;&lt;br /&gt;
  } else if (d[i][j - 1] &amp;gt; d[i - 1][j] {&lt;br /&gt;
    j--;&lt;br /&gt;
  } else {&lt;br /&gt;
    i--;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Se pierde foarte puțin din claritatea codului, căci relația de recurență nu mai este susținută de apelul recursiv propriu-zis. Totuși, condițiile de recurență (din if-uri) sunt încă vizibile, iar codul necesită O(1) spațiu.&lt;br /&gt;
&lt;br /&gt;
== STL este o armă cu două tăișuri ==&lt;br /&gt;
&lt;br /&gt;
Această secțiune se aplică surselor scrise în afara concursurilor. La concurs, totul este permis. Totuși, nu uitați că omul folosește sculele cu care este obișnuit.&lt;br /&gt;
&lt;br /&gt;
De ce venim la cerc? Pentru că vrem să învățăm algoritmi, nu doar să-i apelăm. Cu algoritmii din STL programați mai repede, dar nu învățați nimic. Sunt numeroase exemplele când trebuie să înțelegeți (și să particularizați) porțiuni dintr-un algoritm: pivotarea din quicksort, construirea automatului din KMP, operațiile pe vectori de biți.&lt;br /&gt;
&lt;br /&gt;
Nu uitați că algoritmii din STL pot fi mai lenți sau mai consumatori de memorie decât cei scriși de voi. Este firesc, căci aceia sunt prea generali și adesea fac operații care nu sunt necesare pentru problema voastră. Qsort din stdlib, de exemplu, necesită aproape dublul memoriei și este mai lent. Am observat asta de mai multe ori. De exemplu, am trimis [http://varena.ro/job_detail/29271 două] [http://varena.ro/job_detail/29277 surse] la problema [http://varena.ro/problema/avioane Avioane]. A doua diferă doar prin faptul că mi-am scris propriul quicksort. Nu știu dacă și sortarea din biblioteca algorithm are această problemă.&lt;br /&gt;
&lt;br /&gt;
La problema [http://varena.ro/problema/dirty Dirty], am comparat sursa mea cu a unor concurenți care făceau cam același lucru, doar că foloseau vectori STL (și iteratori) în loc de vectori C. Diferența de timp este considerabilă. Ajungeți să parsați manual fișierul de intrare ca să câștigați timp. Care mai e câștigul? Ce luați pe mere dați pe pere.&lt;br /&gt;
&lt;br /&gt;
Nu uitați de ce lucrăm în C/C++, nu în PHP sau în Ruby: avem nevoie de viteză, iar C se pretează cel mai bine la asta.&lt;br /&gt;
&lt;br /&gt;
=== Sortarea, înger și demon ===&lt;br /&gt;
&lt;br /&gt;
Sortarea din STL merită un capitol special. Nu zic să nu o folosiți ca să câștigați 10 minute la concurs. (Dacă vă ia mai mult de 10 minute să vă implementați propria sortare, deja avem probleme mai grave). Dar dacă dați una-două fuga la sortare, riscați să pierdeți esențialul din vedere.&lt;br /&gt;
&lt;br /&gt;
* Există cel puțin 5 sortări ușor de codat, fiecare cu avantajele ei (quicksort, mergesort, heapsort, radix sort, counting sort). Fiecare dintre ele are avantaje și poate fi personalizată. Luați ca exemplu problema numărării inversiunilor dintr-o permutare, dată la [http://www.math.bas.bg/keleved/shumen2013 Șumen 2013], care se rezolvă ușor cu un merge sort adaptat. Dacă vă gândiți la sortare ca la o cutie neagră, veți uita ce posibilități aveți.&lt;br /&gt;
* Radix sort și counting sort sunt O(n)! Dacă problema se pretează la o astfel de sortare, atunci &#039;&#039;&#039;sort()&#039;&#039;&#039; nu ajută, ci dăunează.&lt;br /&gt;
* Unele probleme se pot rezolva pur și simplu fără sortare. Nu vă adormiți gândirea cu stereotipuri de genul „încep cu o sortare, să fie”.&lt;br /&gt;
&lt;br /&gt;
== Department of Redundancy Department ==&lt;br /&gt;
&lt;br /&gt;
Evitați să copiați bucăți mari de cod cu copy-paste. Îl avem pe Victor Ponta pentru asta. Dacă aveți cazuri &#039;&#039;&#039;aproape&#039;&#039;&#039; identice, dar diferite pe alocuri, este mai bine să parametrizați codul și să-l mutați într-o funcție sau chiar într-un &#039;&#039;for&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Un exemplu concret: problema [http://varena.ro/problema/avioane Avioane] cerea, pentru anumite elemente dintr-un vector, calcularea unor distanțe la stânga și la dreapta. Cu toții ați rezolvat problema pentru direcția stângă, apoi ați dublat codul și ați modificat ce era necesar ca să-l faceți să meargă și spre dreapta. Mă simt onorat că v-a plăcut problema atât de mult, încât ați rezolvat-o de două ori. :-) Iată două metode de a elimina redundanța:&lt;br /&gt;
&lt;br /&gt;
# Parametrizați tot ce se modifică între direcția stângă și cea dreaptă: punctul de pornire, cel de oprire, pasul (+/- 1). Fratele meu Cristi a pus tot codul într-un for în care singurul parametru era pasul (+/- 1). Deci se poate.&lt;br /&gt;
# Calculați toate valorile spre stânga, apoi întoarceți vectorul pe dos și recalculați valorile tot spre stânga.&lt;br /&gt;
&lt;br /&gt;
Din nou, principiul este DRY: &#039;&#039;Don&#039;t Repeat Yourself&#039;&#039;. Riscați să găsiți un bug într-una din copii și să uitați să-l reparați și în cealaltă.&lt;br /&gt;
&lt;br /&gt;
Bugurile de redundanță sunt greu de reparat pentru că avem de luptat și împotriva naturii noastre umane. Ni se pare că nu are sens să citim ambele blocuri, căci a doua e „la fel ca prima”. Nu vi s-a întâmplat niciodată să vă uitați 10 minute fără să vă dați seama ce e greșit în codul de mai jos?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
for (int i = 0; i &amp;lt; m; i++) {&lt;br /&gt;
  for (int j = 0; i &amp;lt; n; j++) {&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Dimensiunea datelor de intrare ==&lt;br /&gt;
&lt;br /&gt;
Fiți întotdeauna atenți la valorile minime și maxime pe care le poate lua o variabilă. Dacă vă obișnuiți să lucrați numai cu &#039;&#039;int&#039;&#039; și cu &#039;&#039;long long&#039;&#039;, veți uita că puteți face economii mari de memorie folosind short și char.&lt;br /&gt;
&lt;br /&gt;
Mai mult, uneori valorile încap pe 4 biți, pe 2 sau chiar pe unul singur. Toate aceste cazuri pot fi tratate ușor cu rutine de 1-2 linii. Iată o implementare pentru baza 4  (codul complet este în [[media:two-bit.cpp]]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define MAX_N 1000000&lt;br /&gt;
&lt;br /&gt;
unsigned char b[MAX_N / 4];&lt;br /&gt;
&lt;br /&gt;
int get(int i) {&lt;br /&gt;
  return (b[i/4] &amp;gt;&amp;gt; (2 * (i % 4))) &amp;amp; 3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void set(int i, int x) {&lt;br /&gt;
  // Clear the previous value if needed&lt;br /&gt;
  b[i/4] &amp;amp;= ~(3 &amp;lt;&amp;lt; (2 * (i % 4)));&lt;br /&gt;
  b[i/4] ^= x &amp;lt;&amp;lt; (2 * (i % 4));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Exemplu: la problema [http://varena.ro/problema/palindrom Palindrom] primeați foarte puțină memorie. Algoritmul de programare dinamică nu este dificil, dar pentru reconstituirea soluției avem nevoie de întreaga matrice, mai exact de triunghiul superior al unei matrice de dimensiuni n x n. Cum n ≤ 5.000, în aparență avem nevoie de 25 MB. În realitate, o celulă din matrice necesită un singur bit pentru stocare, deci necesarul de memorie este 5.000 x 5.000 / 2 / 8 = 1,56 MB.&lt;br /&gt;
&lt;br /&gt;
== Parsarea intrării: când ai un ciocan, totul în jur pare un cui ==&lt;br /&gt;
&lt;br /&gt;
Haideți să lămurim ceva: profesorii și propunătorii de probleme nu sunt sadici. Nu stau zile întregi să stoarcă fiecare picătură de eficiență dintr-o implementare, pentru ca apoi să vă ceară vouă să reușiți același lucru într-o oră. Limitele de timp sunt, în general, alese adăugând o marjă generoasă la o implementare curată, dar neoptimizată la sânge.&lt;br /&gt;
&lt;br /&gt;
Dacă doriți să câștigați câteva zeci de milisecunde, aveți opțiunea să parsați intrarea. Dar amintiți-vă că sunteți pe un drum greșit și undeva ați pierdut din vedere ceva mult mai important. Nu lucrați cu ciocanul. Lucrați cu bisturiul.&lt;br /&gt;
&lt;br /&gt;
Dacă totuși faceți parsarea datelor de intrare, faceți-o cu stil. Am observat codul următor (îl public anonim, dă-mi de veste dacă dorești credit).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
const int DIM = (1 &amp;lt;&amp;lt; 12);&lt;br /&gt;
char buff[DIM];&lt;br /&gt;
int poz = 0;&lt;br /&gt;
&lt;br /&gt;
void read(int &amp;amp;numar) {&lt;br /&gt;
    numar = 0;&lt;br /&gt;
    while (buff[poz] &amp;lt; &#039;0&#039; || buff[poz] &amp;gt; &#039;9&#039;)&lt;br /&gt;
        if (++poz == DIM)&lt;br /&gt;
            fread(buff, 1, DIM, stdin), poz = 0;&lt;br /&gt;
    while (&#039;0&#039; &amp;lt;= buff[poz] &amp;amp;&amp;amp; buff[poz] &amp;lt;= &#039;9&#039;) {&lt;br /&gt;
        numar = numar * 10 + buff[poz] - &#039;0&#039;;&lt;br /&gt;
        if (++poz == DIM)&lt;br /&gt;
            fread(buff, 1, DIM, stdin), poz = 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Hai să-l scriem mai elegant și mai eficient de atât. I-am adus următoarele îmbunătățiri:&lt;br /&gt;
&lt;br /&gt;
* (de stil) Funcția de citire a unui caracter trebuie separată. Separarea bufferării la un nivel abstract este un lucru bun. Această funcție amestecă citirea bufferată cu parsarea numărului.&lt;br /&gt;
* (de stil) Funcția citește în gol bufferul prima dată înainte de a citi primul bloc de pe disc. Este păcat.&lt;br /&gt;
* (de eficiență) Neapărat testați prezența unei cifre cu &#039;&#039;&#039;isdigit()&#039;&#039;&#039; din &amp;amp;lt;ctype.h&amp;amp;gt;. Codul de mai sus testează două condiții. &#039;&#039;&#039;isdigit()&#039;&#039;&#039; nu testează decât una, căci are un tabel de 256 de valori.&lt;br /&gt;
* (de eficiență) Declarați funcția / funcțiile inline. Compilatorul își poate da seama că este nevoie de asta, dar nu e garantat.&lt;br /&gt;
* (posibil de eficiență) Declarați fișierul de intrare global, ca să nu mai fie trimis ca parametru. Nu ar trebui să aibă niciun efect.&lt;br /&gt;
&lt;br /&gt;
Primele patru modificări produc codul:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
#define BUF_SIZE 4096&lt;br /&gt;
char buf[BUF_SIZE];&lt;br /&gt;
int pos = BUF_SIZE;&lt;br /&gt;
&lt;br /&gt;
inline char getChar(FILE *f) {&lt;br /&gt;
  if (pos == BUF_SIZE) {&lt;br /&gt;
    fread(buf, 1, BUF_SIZE, f);&lt;br /&gt;
    pos = 0;&lt;br /&gt;
  }&lt;br /&gt;
  return buf[pos++];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
inline int readInt(FILE *f) {&lt;br /&gt;
  int result = 0;&lt;br /&gt;
  char c;&lt;br /&gt;
  do {&lt;br /&gt;
    c = getChar(f);&lt;br /&gt;
  } while (!isdigit(c));&lt;br /&gt;
&lt;br /&gt;
  do {&lt;br /&gt;
    result = 10 * result + c - &#039;0&#039;;&lt;br /&gt;
    c = getChar(f);&lt;br /&gt;
  } while (isdigit(c));&lt;br /&gt;
&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am rulat un experiment cu patru versiuni de program:&lt;br /&gt;
&lt;br /&gt;
* una care citește cu fscanf&lt;br /&gt;
* una care citește cu codul de mai sus&lt;br /&gt;
* una care folosește primele patru optimizări&lt;br /&gt;
* una care folosește și a cincea optimizare&lt;br /&gt;
&lt;br /&gt;
Fișierul de intrare are 10.000.000 de numere (79 MB). Am rulat fiecare program de zece ori, chiar pe serverul Varena, am eliminat cea mai rapidă și cea mai lentă rulare și am calculat media.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! versiune || timp mediu de rulare || sursă&lt;br /&gt;
|-&lt;br /&gt;
| cu fscanf || 4.31 s || [[media:parse1.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu codul de mai sus || 0.819 s || [[media:parse2.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu primele patru optimizări || 0.366 s || [[media:parse3.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu toate optimizările || 0.355 s || [[media:parse3.cpp]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Versiunea mai elegantă nu este mai lentă, ci chiar dimpotrivă. Pentru fișiere cu mărimi mai realiste (5-6 MB), versiunea 3 este cu circa 30 ms mai rapidă decât versiunea 2.&lt;br /&gt;
&lt;br /&gt;
Nu uitați că puteți câștiga timp semnificativ, cam 50% față de versiunea cu &#039;&#039;&#039;fscanf(),&#039;&#039;&#039; folosind &#039;&#039;&#039;getc()&#039;&#039;&#039; pentru citirea caracter cu caracter. Folosim bufferul oferit de sistem. În multe situații poate fi suficient.&lt;br /&gt;
&lt;br /&gt;
Morala? Să renunți la stil și să pierzi și din viteză este un compromis tare păgubos.&lt;br /&gt;
&lt;br /&gt;
== O funcție este un contract ==&lt;br /&gt;
&lt;br /&gt;
* Despre invarianți, cutii negre, contracte și comentarii.&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Note_de_curs,_clasele_11-12,_21_noiembrie_2013&amp;diff=18026</id>
		<title>Note de curs, clasele 11-12, 21 noiembrie 2013</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Note_de_curs,_clasele_11-12,_21_noiembrie_2013&amp;diff=18026"/>
		<updated>2020-12-14T14:39:18Z</updated>

		<summary type="html">&lt;p&gt;Cata: înlocuiesc &amp;lt;source&amp;gt; cu &amp;lt;syntaxhighlight&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Zilele acestea m-am uitat pe multe surse de la concursurile din această toamnă. Apoi am plâns. Din lacrimile mele s-a născut lecția de azi!&lt;br /&gt;
&lt;br /&gt;
= Chestiuni de programare =&lt;br /&gt;
&lt;br /&gt;
== Preliminarii ==&lt;br /&gt;
&lt;br /&gt;
Orice lucru rău trebuie spus încadrat de două lucruri bune. Voi sunteți printre cei mai buni elevi din generația voastră și sunt onorat să lucrez cu voi. Deci luați observațiile de mai jos nu ca pe o insultă personală, ci ca pe o ocazie să vă îmbunătățiți. Gata, am început cu un lucru bun. :-)&lt;br /&gt;
&lt;br /&gt;
Noi, profesorii, avem partea noastră de vină în felul dezlânat în care programați voi. Ne batem singuri pe spate că elevii știu algoritmi, dar nu ne pasă cum îi codează. Adevărul este că cele două merg mână în mână, mai ales apropo de ceea ce am mai spus: voi nu vedeți destul cod scris de alții. Deci o lecție pe an dedicată programării este doar un început.&lt;br /&gt;
&lt;br /&gt;
== Programarea aproximativă ==&lt;br /&gt;
&lt;br /&gt;
Banc: cică japonezii aud de Dacia -- mașină bună, fiabilă, piese ieftine. Cumpără planurile de la Dacia și încep și ei să o producă. Doar că, surpriză, de pe linia de asamblare le iese un tractor! Trimit un om la Pitești, să studieze uzina. Se uită el, se întoarce acasă, mai repară japonezii mici probleme la linia lor. Degeaba: de pe linia de asamblare ieșeau tractoare. Disperați, dau telefon în România și cer lămuriri. Românii îi liniștesc: „păi și nouă ne iese tot tractor, dar îl luăm la pilă!”&lt;br /&gt;
&lt;br /&gt;
Toate subproblemele pe care le tratăm aici sunt doar vârfuri ale icebergului. Ele reflectă o problemă mai gravă: programarea aproximativă. Prin programare aproximativă înțelegem o serie de iterații de adăugare de cod care converge către o soluție. Programul capătă formă pe măsură ce îl scriem, ca și ideea de rezolvare:&lt;br /&gt;
&lt;br /&gt;
* Mai am nevoie de un element în vector? Adaug 1 la lungimea lui.&lt;br /&gt;
* M-ar ajuta să am datele sortate? Ce idee bună, apelez un sort().&lt;br /&gt;
* Trebuie să ies prematur dintr-o buclă? Deja am scris-o folosind &#039;&#039;&#039;for&#039;&#039;&#039;, deci inserez un &#039;&#039;&#039;break&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Nu ciocăniți ici și colo, nu programați reactiv, în funcție de erorile din iterația anterioară. Programați activ, gândindu-vă bine la algoritm și la detaliile lui și încercați să-l implementați bine din prima.&lt;br /&gt;
&lt;br /&gt;
Veți vrea să mergeți la companii faimoase. Acolo oamenii se uită, încă de la interviu, la stilul vostru de codare. Iar, dacă veți fi acceptați, vă veți poticni la fiecare code review de aceleași observații. Dacă așteptați zece ani până să încercați să vă corectați, s-ar putea să fie prea târziu. Lenevirea creierului duce la o gândire încâlcită.&lt;br /&gt;
&lt;br /&gt;
== Constante ==&lt;br /&gt;
&lt;br /&gt;
Folosim în general constante pentru a face codul mai solid și mai ușor de modificat.&lt;br /&gt;
&lt;br /&gt;
=== Constantele sunt sublime, dar lipsesc cu desăvârșire ===&lt;br /&gt;
&lt;br /&gt;
Cel mai grav este să nu folosiți deloc constante. Orice repetiție, chiar și a unui singur număr, chiar și doar o dată, este o practică riscantă. Un programator bun își ordonează codul astfel încât o modificare undeva în program să nu necesite modificări în alte părți fără legătură. Acest principiu se numește [http://en.wikipedia.org/wiki/Don&#039;t_repeat_yourself Don&#039;t repeat yourself (DRY)].&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int v[100000], w[100000];&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
int max = -2000000000;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#define MAX_N 100000&lt;br /&gt;
#define INFINITY 2000000000&lt;br /&gt;
&lt;br /&gt;
int v[MAX_N], w[MAX_N];&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
int max = -INFINITY;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Scenariu catastrofic: declarați 5 vectori de mărime 1.000. Testați programul, care trece toate testele mici. Înlocuiți 1.000 cu 100.000, dar omiteți una dintre aparițiile constantei. Trimiteți problema, care ia 0 puncte.&lt;br /&gt;
&lt;br /&gt;
=== #define N &amp;quot;vreo 100000 și 17, să fie&amp;quot; ===&lt;br /&gt;
&lt;br /&gt;
Aproape fără excepție am observat constante declarate cu „câteva” unități mai mari decât este necesar. Dacă problema specifică N ≤ 100.000, voi declarați 100.002, 100.005, 100.007. Unii mai creativi ajung la 111.111 (ce-i drept, estetica acestui număr este incontestabilă).&lt;br /&gt;
&lt;br /&gt;
De ce faceți asta? Următoarele răspunsuri nu sunt acceptate:&lt;br /&gt;
&lt;br /&gt;
* Îmi este teamă că programul are o viață proprie și că decide, de capul lui, să se reverse cu câteva elemente;&lt;br /&gt;
* Mă gândesc că biții nu sunt bine separați și că se pot păta unii de la alții;&lt;br /&gt;
* Cred că pointerul care iterează prin vector frânează greu și nu se va opri la timp;&lt;br /&gt;
* Am de gând să folosesc vectorul de la 1, deci un element deja s-a pierdut.&lt;br /&gt;
&lt;br /&gt;
Următorul răspuns este acceptat:&lt;br /&gt;
&lt;br /&gt;
* Nu am încredere în aptitudinile mele de programator și în robustețea codului pe care îl produc; vectorul este accesat și modificat în câteva locuri diferite și eu nu pot să demonstrez că mă încadrez în N elemente.&lt;br /&gt;
&lt;br /&gt;
Nu programați în dorul lelii. Fiți stăpâni pe program. Nimeni nu jelește 10 octeți irosiți, dar este vorba de un principiu.&lt;br /&gt;
&lt;br /&gt;
În plus, la matricele multidimensionale risipa se cumulează. 1.010 octeți în loc de 1.000? 1% risipă. 1.010&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt; octeți în loc de 1.000&amp;lt;sup&amp;gt;3&amp;lt;/sup&amp;gt;? 3% risipă.&lt;br /&gt;
&lt;br /&gt;
Dacă vă plac algoritmii, veți ajunge să învățați unii tot mai complicați. Dacă nu sunteți pe deplin stăpâni nici pe aceste cărămizi de la bază, vă va fi greu să construiți peste ele.&lt;br /&gt;
&lt;br /&gt;
=== Patul lui Procust pentru constante ===&lt;br /&gt;
&lt;br /&gt;
Adesea aveți nevoie de vectori de lungimi ușor diferite. Poate un vector se termină cu o santinelă, sau poate altul are santinele la ambele capete. Poate altul are N ≤ 100.000 elemente și va mai câștiga alte K ≤ 1.000 elemente în viitor. De cele mai multe ori, voi definiți constanta în program pentru a acoperi toate aceste cazuri.&lt;br /&gt;
&lt;br /&gt;
Nu este bine, tot din principiu. Declarați o constantă egală cu cea specificată de datele problemei, apoi ajustați fiecare vector în funcție de necesități. &lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#define MAX_N 101000&lt;br /&gt;
int v[MAX_N], w[MAX_N], x[MAX_N], y[MAX_N];&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#define MAX_N 100000&lt;br /&gt;
#define MAX_K 1000&lt;br /&gt;
&lt;br /&gt;
int v[MAX_N];&lt;br /&gt;
int w[MAX_N + 1];     // bordat cu santinelă la sfârșit&lt;br /&gt;
int x[MAX_N + 2];     // bordat cu santinelă la ambele capete&lt;br /&gt;
int y[MAX_N + MAX_K]; // Va căpăta un element la fiecare iterație&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Codul vostru va câștiga mult în eleganță și în claritate, căci scopul fiecărei variabile va deveni mai clar. Reiese și că 1 și 2 sunt constante (numărul de santinele nu depinde de mărimea intrării), dar K poate și el să varieze dacă datele problemei se schimbă.&lt;br /&gt;
&lt;br /&gt;
== Break, continue și return prematur ==&lt;br /&gt;
&lt;br /&gt;
Evităm, cu rare excepții, break, continue și return prematur pentru că fac codul mai greu de citit. Se aplică și la concurs, chiar dacă voi sunteți singurii cititori.&lt;br /&gt;
&lt;br /&gt;
=== „Să oprească proștii la stop” ===&lt;br /&gt;
&lt;br /&gt;
La semnele de stop (octogonul roșu), legea spune că trebuie să oprești complet mașina (să stea roata). În practică, mai devreme sau mai târziu toți șoferii ne dăm seama că putem să trecem pe lângă semn fără să oprim complet. Nu cunosc pe nimeni care să respecte la sânge această lege. „Rostogolirea” prin semnul de stop reduce uzura frânelor, deci are un avantaj palpabil. Dar suntem conștienți că facem o prostioară și că nu putem face asta oricând, oriunde -- în special când de pe strada cu prioritate vin mașini. Cine încalcă această lege fără discernământ se expune la accidente, potențial catastrofice.&lt;br /&gt;
&lt;br /&gt;
La fel este și cu programarea nestructurată. Scopul nu este să urmăm orbește niște legi, ci să facem codul mai ușor de înțeles și de întreținut. Prin această prismă, situațiile legitime de folosire a programării nestructurate sunt rarisime.&lt;br /&gt;
&lt;br /&gt;
=== Dar programez mai ușor așa ===&lt;br /&gt;
&lt;br /&gt;
Viața nu e ușoară. Cu toții ne străduim azi ca să ne fie bine mai încolo, pe termen lung.&lt;br /&gt;
&lt;br /&gt;
=== Dar scriu cod mai clar așa ===&lt;br /&gt;
&lt;br /&gt;
Claritatea este în ochii cititorului. Dacă vă obișnuiți să programați aproximativ, orice program neaproximativ vi se va părea straniu.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu tipic: căutarea lui &#039;&#039;x&#039;&#039; în vectorul &#039;&#039;v&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
for (int i = 0; i &amp;lt; n; i++) {&lt;br /&gt;
  if (v[i] == x) {&lt;br /&gt;
    break;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int i = 0;&lt;br /&gt;
while ((i &amp;lt; n) &amp;amp;&amp;amp; (v[i] != x)) {&lt;br /&gt;
  i++;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Codul este mai scurt și mai citibil. Versiunea cu break promite o condiție de ieșire (i &amp;gt;= n), apoi ne păcălește ieșind pe o cu totul altă condiție (v[i] == x). În versiunea structurată, ambele condiții sunt listate clar în instrucțiunea &#039;&#039;while&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Un alt exemplu: un test dacă șirul &#039;&#039;s&#039;&#039; este palindrom.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Așa nu&lt;br /&gt;
! Așa da&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
bool palindrom(char *s){&lt;br /&gt;
  for (int i = 0; 2 * i &amp;lt; n; i++) {&lt;br /&gt;
    if (s[i] != s[n - 1 - i]) {&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
  return true;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|  &amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
bool palindrom(char *s){&lt;br /&gt;
  int i = 0;&lt;br /&gt;
  while ((2 * i &amp;lt; n) &amp;amp;&amp;amp; (s[i] == s[n - 1 - i])) {&lt;br /&gt;
    i++;&lt;br /&gt;
  }&lt;br /&gt;
  return (s[i] == s[n - 1 - i]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Dar cum pot să scriu un switch fără break? ===&lt;br /&gt;
&lt;br /&gt;
Limbajul C a apărut prin 1972, când informaticienii abia începeau să înțeleagă diferențele între programarea structurată și cea nestructurată. Demonstrația că orice program se poate scrie [http://en.wikipedia.org/wiki/Structured_program_theorem numai cu blocuri structurate] datează din 1966. În anii următori, polemica programării structurate versus cea nestructurată a polarizat lumea informaticii. [http://en.wikipedia.org/wiki/Go_To_Statement_Considered_Harmful#Criticism_and_decline Dijkstra] era pro-structurare, pe când [http://www.scribd.com/doc/38873257/Knuth-1974-Structured-Programming-With-Go-to-Statements Knuth] a dat exemple când instrucțiunea GOTO produce cod mai citibil.&lt;br /&gt;
&lt;br /&gt;
Nu este, deci, de mirare, că limbajul C încorporează un atavism ca &#039;&#039;break&#039;&#039;. Îl vom folosi, dar doar fiindcă nu putem altfel. Nu abuzăm de el; în special, nu permitem „curgerea” dintr-o ramură într-alta. Fiecare ramură trebuie să aibă &#039;&#039;break&#039;&#039;-ul propriu.&lt;br /&gt;
&lt;br /&gt;
=== Când este acceptabil să programăm nestructurat? ===&lt;br /&gt;
&lt;br /&gt;
Iată câteva exemple:&lt;br /&gt;
&lt;br /&gt;
* Când o funcție are de testat 2-3-4 precondiții, este mai firesc să apelăm &#039;&#039;return&#039;&#039; prematur decât să scriem corpul principal al funcției sub 2-3-4 niveluri de indentare.&lt;br /&gt;
* Cazul de bază în funcțiile recursive poate fi pus la început și terminat cu &#039;&#039;return&#039;&#039;.&lt;br /&gt;
* Pentru &#039;&#039;switch&#039;&#039; folosim &#039;&#039;break&#039;&#039; căci nu putem evita asta.&lt;br /&gt;
&lt;br /&gt;
În toate situațiile folosim &#039;&#039;return&#039;&#039; sau &#039;&#039;break&#039;&#039; dintr-un &#039;&#039;if&#039;&#039;, niciodată dintr-o buclă.&lt;br /&gt;
&lt;br /&gt;
== Un program de 10 linii poate fi [http://en.wikipedia.org/wiki/Wikipedia:Too_long;_didn&#039;t_read TLDR] ==&lt;br /&gt;
&lt;br /&gt;
Ați încercat vreodată să citiți o carte tipărită la o editură de bloc? Cu margini de 1 mm, font de 8 și spațiere minusculă? Nu-i așa că e enervant?&lt;br /&gt;
&lt;br /&gt;
Și programele pot fi ilizibile. Nu veți lucra toată viața de unii singuri, iar într-o echipă lizibilitatea este la fel de importantă ca eficiența. Uneori mi se pare că scopul vostru este un program cât mai scurt. De unde și până unde?&lt;br /&gt;
&lt;br /&gt;
Încercați să deprindeți următoarele aspecte:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;O instrucțiune pe linie.&#039;&#039;&#039; Liniile scurte fac codul mai citibil.&lt;br /&gt;
* &#039;&#039;&#039;Spațierea.&#039;&#039;&#039; O linie goală este ca o virgulă. Folosiți-o acolo unde programul face trecerea între două bucăți separate logic: două funcții, două blocuri logic distincte în aceeași funcție. Nu exagerați cu spațierea. Este important ca fiecare funcție să încapă pe un singur ecran.&lt;br /&gt;
* &#039;&#039;&#039;Lungimea numelor de variabile.&#039;&#039;&#039; O literă este acceptabilă numai pentru scopuri încetățenite: &#039;&#039;i, j, k&#039;&#039; pentru variabilele de ciclu, &#039;&#039;m, n&#039;&#039; pentru dimensiunile matricei. Pentru orice altceva, alegeți nume mai clare (dar nici 3 cuvinte, căci ajungem la linii prea lungi).&lt;br /&gt;
* &#039;&#039;&#039;Goana stupidă după scurtimea codului.&#039;&#039;&#039; Două lucruri rele nu fac un lucru bun. Voi scrieți cod lung pentru că acolo converge procesul iterativ, iar pe parcurs îl scurtați mâncând acolade, spațiere și scurtând numele de variabile.&lt;br /&gt;
* &#039;&#039;&#039;Virgula în loc de acolade.&#039;&#039;&#039; Un caz particular al punctului anterior. Sunteți tentați să folosiți virgula ca să înghesuiți două instrucțiuni în același if, fără să adăugați acolade. Vă puteți păcăli singuri.&lt;br /&gt;
* &#039;&#039;&#039;Folosim for numai pentru număr cunoscut de iterații.&#039;&#039;&#039; Rezistați tentației de a deveni hackeri. Nu este o regulă unanim acceptată, dar este o idee bună. Și [http://en.wikipedia.org/wiki/For_loop Wikipedia] o menționează.&lt;br /&gt;
* &#039;&#039;&#039;Variabile cât mai locale.&#039;&#039;&#039; Dacă tot folosiți toate prostiile din C++, măcar folosiți și lucrurile bune. Declarați variabilele cât mai local (fără însă a împăna tot programul cu declarații). În special evitați variabilele globale pentru programe netriviale. Este ușor să pierdeți socoteala cine le modifică și când.&lt;br /&gt;
* &#039;&#039;&#039;Evitați freopen().&#039;&#039;&#039; Este o idee proastă să închideți &#039;&#039;&#039;stdin&#039;&#039;&#039; și în special &#039;&#039;&#039;stdout&#039;&#039;&#039;. În &#039;&#039;&#039;stdout&#039;&#039;&#039; puteți tipări mesaje de depanare pe care, dacă cumva le uitați în program, nu se întâmplă nimic rău. Dar dacă &#039;&#039;&#039;stdout&#039;&#039;&#039; și fișierul de ieșire al problemei sunt unul și același, riscați să tipăriți gunoaie în fișierul de ieșire. În plus, &#039;&#039;&#039;freopen()&#039;&#039;&#039; este o metodă foarte lipsită de considerație. Într-un program mai mare, cine știe ce bibliotecă vrea să tipărească mesaje pe ecran. Voi îi răpiți această posibilitate.&lt;br /&gt;
&lt;br /&gt;
== Recursivitate inutilă ==&lt;br /&gt;
&lt;br /&gt;
Nu vă fie frică să eliminați recursivitatea acolo unde ea nu este necesară. Cel mai comun exemplu este la problemele de programare dinamică, acolo unde, pe lângă valoarea soluției, se cere și soluția explicită.&lt;br /&gt;
&lt;br /&gt;
Exemplu: cel mai lung subșir comun între două șiruri. Presupunând că avem două șiruri, &#039;&#039;a&#039;&#039; de lungime &#039;&#039;m&#039;&#039;, și &#039;&#039;b&#039;&#039; de lungime &#039;&#039;n&#039;&#039;, și că am calculat matricea &#039;&#039;d&#039;&#039; pentru toate perechile de prefixe, soluția se calculează recursiv astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char s[MAX_SOL];&lt;br /&gt;
&lt;br /&gt;
void solutie(int i, int j) {&lt;br /&gt;
  if (d[i][j]) {&lt;br /&gt;
    if (a[i] == b[j]) {&lt;br /&gt;
      solutie(i - 1, j - 1);&lt;br /&gt;
      s[d[i][j] - 1] = a[i];&lt;br /&gt;
    } else if (d[i][j - 1] &amp;gt; d[i - 1][j] {&lt;br /&gt;
      solutie(d[i][j - 1]);&lt;br /&gt;
    } else {&lt;br /&gt;
      solutie(d[i - 1][j]);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
...&lt;br /&gt;
solutie(m - 1, n - 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Nu este rău, dar nu uitați că recursivitatea cere timp și memorie! Stiva de apeluri poate avea O(n) niveluri. Dacă spațiul sau timpul sunt o problemă, eliminați recursivitatea:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char solutie[MAX_SOL];&lt;br /&gt;
&lt;br /&gt;
int i = m - 1, j = n - 1;&lt;br /&gt;
solutie[d[i][j]] = &#039;\0&#039;;&lt;br /&gt;
&lt;br /&gt;
while (d[i][j]) {&lt;br /&gt;
  if (a[i] == b[j]) {&lt;br /&gt;
    solutie[d[i][j] - 1] = a[i];&lt;br /&gt;
    i--;&lt;br /&gt;
    j--;&lt;br /&gt;
  } else if (d[i][j - 1] &amp;gt; d[i - 1][j] {&lt;br /&gt;
    j--;&lt;br /&gt;
  } else {&lt;br /&gt;
    i--;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Se pierde foarte puțin din claritatea codului, căci relația de recurență nu mai este susținută de apelul recursiv propriu-zis. Totuși, condițiile de recurență (din if-uri) sunt încă vizibile, iar codul necesită O(1) spațiu.&lt;br /&gt;
&lt;br /&gt;
== STL este o armă cu două tăișuri ==&lt;br /&gt;
&lt;br /&gt;
Această secțiune se aplică surselor scrise în afara concursurilor. La concurs, totul este permis. Totuși, nu uitați că omul folosește sculele cu care este obișnuit.&lt;br /&gt;
&lt;br /&gt;
De ce venim la cerc? Pentru că vrem să învățăm algoritmi, nu doar să-i apelăm. Cu algoritmii din STL programați mai repede, dar nu învățați nimic. Sunt numeroase exemplele când trebuie să înțelegeți (și să particularizați) porțiuni dintr-un algoritm: pivotarea din quicksort, construirea automatului din KMP, operațiile pe vectori de biți.&lt;br /&gt;
&lt;br /&gt;
Nu uitați că algoritmii din STL pot fi mai lenți sau mai consumatori de memorie decât cei scriși de voi. Este firesc, căci aceia sunt prea generali și adesea fac operații care nu sunt necesare pentru problema voastră. Qsort din stdlib, de exemplu, necesită aproape dublul memoriei și este mai lent. Am observat asta de mai multe ori. De exemplu, am trimis [http://varena.ro/job_detail/29271 două] [http://varena.ro/job_detail/29277 surse] la problema [http://varena.ro/problema/avioane Avioane]. A doua diferă doar prin faptul că mi-am scris propriul quicksort. Nu știu dacă și sortarea din biblioteca algorithm are această problemă.&lt;br /&gt;
&lt;br /&gt;
La problema [http://varena.ro/problema/dirty Dirty], am comparat sursa mea cu a unor concurenți care făceau cam același lucru, doar că foloseau vectori STL (și iteratori) în loc de vectori C. Diferența de timp este considerabilă. Ajungeți să parsați manual fișierul de intrare ca să câștigați timp. Care mai e câștigul? Ce luați pe mere dați pe pere.&lt;br /&gt;
&lt;br /&gt;
Nu uitați de ce lucrăm în C/C++, nu în PHP sau în Ruby: avem nevoie de viteză, iar C se pretează cel mai bine la asta.&lt;br /&gt;
&lt;br /&gt;
=== Sortarea, înger și demon ===&lt;br /&gt;
&lt;br /&gt;
Sortarea din STL merită un capitol special. Nu zic să nu o folosiți ca să câștigați 10 minute la concurs. (Dacă vă ia mai mult de 10 minute să vă implementați propria sortare, deja avem probleme mai grave). Dar dacă dați una-două fuga la sortare, riscați să pierdeți esențialul din vedere.&lt;br /&gt;
&lt;br /&gt;
* Există cel puțin 5 sortări ușor de codat, fiecare cu avantajele ei (quicksort, mergesort, heapsort, radix sort, counting sort). Fiecare dintre ele are avantaje și poate fi personalizată. Luați ca exemplu problema numărării inversiunilor dintr-o permutare, dată la [http://www.math.bas.bg/keleved/shumen2013 Șumen 2013], care se rezolvă ușor cu un merge sort adaptat. Dacă vă gândiți la sortare ca la o cutie neagră, veți uita ce posibilități aveți.&lt;br /&gt;
* Radix sort și counting sort sunt O(n)! Dacă problema se pretează la o astfel de sortare, atunci &#039;&#039;&#039;sort()&#039;&#039;&#039; nu ajută, ci dăunează.&lt;br /&gt;
* Unele probleme se pot rezolva pur și simplu fără sortare. Nu vă adormiți gândirea cu stereotipuri de genul „încep cu o sortare, să fie”.&lt;br /&gt;
&lt;br /&gt;
== Department of Redundancy Department ==&lt;br /&gt;
&lt;br /&gt;
Evitați să copiați bucăți mari de cod cu copy-paste. Îl avem pe Victor Ponta pentru asta. Dacă aveți cazuri &#039;&#039;&#039;aproape&#039;&#039;&#039; identice, dar diferite pe alocuri, este mai bine să parametrizați codul și să-l mutați într-o funcție sau chiar într-un &#039;&#039;for&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Un exemplu concret: problema [http://varena.ro/problema/avioane Avioane] cerea, pentru anumite elemente dintr-un vector, calcularea unor distanțe la stânga și la dreapta. Cu toții ați rezolvat problema pentru direcția stângă, apoi ați dublat codul și ați modificat ce era necesar ca să-l faceți să meargă și spre dreapta. Mă simt onorat că v-a plăcut problema atât de mult, încât ați rezolvat-o de două ori. :-) Iată două metode de a elimina redundanța:&lt;br /&gt;
&lt;br /&gt;
# Parametrizați tot ce se modifică între direcția stângă și cea dreaptă: punctul de pornire, cel de oprire, pasul (+/- 1). Fratele meu Cristi a pus tot codul într-un for în care singurul parametru era pasul (+/- 1). Deci se poate.&lt;br /&gt;
# Calculați toate valorile spre stânga, apoi întoarceți vectorul pe dos și recalculați valorile tot spre stânga.&lt;br /&gt;
&lt;br /&gt;
Din nou, principiul este DRY: &#039;&#039;Don&#039;t Repeat Yourself&#039;&#039;. Riscați să găsiți un bug într-una din copii și să uitați să-l reparați și în cealaltă.&lt;br /&gt;
&lt;br /&gt;
Bugurile de redundanță sunt greu de reparat pentru că avem de luptat și împotriva naturii noastre umane. Ni se pare că nu are sens să citim ambele blocuri, căci a doua e „la fel ca prima”. Nu vi s-a întâmplat niciodată să vă uitați 10 minute fără să vă dați seama ce e greșit în codul de mai jos?&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
for (int i = 0; i &amp;lt; m; i++) {&lt;br /&gt;
  for (int j = 0; i &amp;lt; n; j++) {&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Dimensiunea datelor de intrare ==&lt;br /&gt;
&lt;br /&gt;
Fiți întotdeauna atenți la valorile minime și maxime pe care le poate lua o variabilă. Dacă vă obișnuiți să lucrați numai cu &#039;&#039;int&#039;&#039; și cu &#039;&#039;long long&#039;&#039;, veți uita că puteți face economii mari de memorie folosind short și char.&lt;br /&gt;
&lt;br /&gt;
Mai mult, uneori valorile încap pe 4 biți, pe 2 sau chiar pe unul singur. Toate aceste cazuri pot fi tratate ușor cu rutine de 1-2 linii. Iată o implementare pentru baza 4  (codul complet este în [[media:two-bit.cpp]]).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#define MAX_N 1000000&lt;br /&gt;
&lt;br /&gt;
unsigned char b[MAX_N / 4];&lt;br /&gt;
&lt;br /&gt;
int get(int i) {&lt;br /&gt;
  return (b[i/4] &amp;gt;&amp;gt; (2 * (i % 4))) &amp;amp; 3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void set(int i, int x) {&lt;br /&gt;
  // Clear the previous value if needed&lt;br /&gt;
  b[i/4] &amp;amp;= ~(3 &amp;lt;&amp;lt; (2 * (i % 4)));&lt;br /&gt;
  b[i/4] ^= x &amp;lt;&amp;lt; (2 * (i % 4));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Exemplu: la problema [http://varena.ro/problema/palindrom Palindrom] primeați foarte puțină memorie. Algoritmul de programare dinamică nu este dificil, dar pentru reconstituirea soluției avem nevoie de întreaga matrice, mai exact de triunghiul superior al unei matrice de dimensiuni n x n. Cum n ≤ 5.000, în aparență avem nevoie de 25 MB. În realitate, o celulă din matrice necesită un singur bit pentru stocare, deci necesarul de memorie este 5.000 x 5.000 / 2 / 8 = 1,56 MB.&lt;br /&gt;
&lt;br /&gt;
== Parsarea intrării: când ai un ciocan, totul în jur pare un cui ==&lt;br /&gt;
&lt;br /&gt;
Haideți să lămurim ceva: profesorii și propunătorii de probleme nu sunt sadici. Nu stau zile întregi să stoarcă fiecare picătură de eficiență dintr-o implementare, pentru ca apoi să vă ceară vouă să reușiți același lucru într-o oră. Limitele de timp sunt, în general, alese adăugând o marjă generoasă la o implementare curată, dar neoptimizată la sânge.&lt;br /&gt;
&lt;br /&gt;
Dacă doriți să câștigați câteva zeci de milisecunde, aveți opțiunea să parsați intrarea. Dar amintiți-vă că sunteți pe un drum greșit și undeva ați pierdut din vedere ceva mult mai important. Nu lucrați cu ciocanul. Lucrați cu bisturiul.&lt;br /&gt;
&lt;br /&gt;
Dacă totuși faceți parsarea datelor de intrare, faceți-o cu stil. Am observat codul următor (îl public anonim, dă-mi de veste dacă dorești credit).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
const int DIM = (1 &amp;lt;&amp;lt; 12);&lt;br /&gt;
char buff[DIM];&lt;br /&gt;
int poz = 0;&lt;br /&gt;
&lt;br /&gt;
void read(int &amp;amp;numar) {&lt;br /&gt;
    numar = 0;&lt;br /&gt;
    while (buff[poz] &amp;lt; &#039;0&#039; || buff[poz] &amp;gt; &#039;9&#039;)&lt;br /&gt;
        if (++poz == DIM)&lt;br /&gt;
            fread(buff, 1, DIM, stdin), poz = 0;&lt;br /&gt;
    while (&#039;0&#039; &amp;lt;= buff[poz] &amp;amp;&amp;amp; buff[poz] &amp;lt;= &#039;9&#039;) {&lt;br /&gt;
        numar = numar * 10 + buff[poz] - &#039;0&#039;;&lt;br /&gt;
        if (++poz == DIM)&lt;br /&gt;
            fread(buff, 1, DIM, stdin), poz = 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Hai să-l scriem mai elegant și mai eficient de atât. I-am adus următoarele îmbunătățiri:&lt;br /&gt;
&lt;br /&gt;
* (de stil) Funcția de citire a unui caracter trebuie separată. Separarea bufferării la un nivel abstract este un lucru bun. Această funcție amestecă citirea bufferată cu parsarea numărului.&lt;br /&gt;
* (de stil) Funcția citește în gol bufferul prima dată înainte de a citi primul bloc de pe disc. Este păcat.&lt;br /&gt;
* (de eficiență) Neapărat testați prezența unei cifre cu &#039;&#039;&#039;isdigit()&#039;&#039;&#039; din &amp;amp;lt;ctype.h&amp;amp;gt;. Codul de mai sus testează două condiții. &#039;&#039;&#039;isdigit()&#039;&#039;&#039; nu testează decât una, căci are un tabel de 256 de valori.&lt;br /&gt;
* (de eficiență) Declarați funcția / funcțiile inline. Compilatorul își poate da seama că este nevoie de asta, dar nu e garantat.&lt;br /&gt;
* (posibil de eficiență) Declarați fișierul de intrare global, ca să nu mai fie trimis ca parametru. Nu ar trebui să aibă niciun efect.&lt;br /&gt;
&lt;br /&gt;
Primele patru modificări produc codul:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#define BUF_SIZE 4096&lt;br /&gt;
char buf[BUF_SIZE];&lt;br /&gt;
int pos = BUF_SIZE;&lt;br /&gt;
&lt;br /&gt;
inline char getChar(FILE *f) {&lt;br /&gt;
  if (pos == BUF_SIZE) {&lt;br /&gt;
    fread(buf, 1, BUF_SIZE, f);&lt;br /&gt;
    pos = 0;&lt;br /&gt;
  }&lt;br /&gt;
  return buf[pos++];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
inline int readInt(FILE *f) {&lt;br /&gt;
  int result = 0;&lt;br /&gt;
  char c;&lt;br /&gt;
  do {&lt;br /&gt;
    c = getChar(f);&lt;br /&gt;
  } while (!isdigit(c));&lt;br /&gt;
&lt;br /&gt;
  do {&lt;br /&gt;
    result = 10 * result + c - &#039;0&#039;;&lt;br /&gt;
    c = getChar(f);&lt;br /&gt;
  } while (isdigit(c));&lt;br /&gt;
&lt;br /&gt;
  return result;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am rulat un experiment cu patru versiuni de program:&lt;br /&gt;
&lt;br /&gt;
* una care citește cu fscanf&lt;br /&gt;
* una care citește cu codul de mai sus&lt;br /&gt;
* una care folosește primele patru optimizări&lt;br /&gt;
* una care folosește și a cincea optimizare&lt;br /&gt;
&lt;br /&gt;
Fișierul de intrare are 10.000.000 de numere (79 MB). Am rulat fiecare program de zece ori, chiar pe serverul Varena, am eliminat cea mai rapidă și cea mai lentă rulare și am calculat media.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! versiune || timp mediu de rulare || sursă&lt;br /&gt;
|-&lt;br /&gt;
| cu fscanf || 4.31 s || [[media:parse1.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu codul de mai sus || 0.819 s || [[media:parse2.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu primele patru optimizări || 0.366 s || [[media:parse3.cpp]]&lt;br /&gt;
|-&lt;br /&gt;
| cu toate optimizările || 0.355 s || [[media:parse3.cpp]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Versiunea mai elegantă nu este mai lentă, ci chiar dimpotrivă. Pentru fișiere cu mărimi mai realiste (5-6 MB), versiunea 3 este cu circa 30 ms mai rapidă decât versiunea 2.&lt;br /&gt;
&lt;br /&gt;
Nu uitați că puteți câștiga timp semnificativ, cam 50% față de versiunea cu &#039;&#039;&#039;fscanf(),&#039;&#039;&#039; folosind &#039;&#039;&#039;getc()&#039;&#039;&#039; pentru citirea caracter cu caracter. Folosim bufferul oferit de sistem. În multe situații poate fi suficient.&lt;br /&gt;
&lt;br /&gt;
Morala? Să renunți la stil și să pierzi și din viteză este un compromis tare păgubos.&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Preg%C4%83tirea_unui_concurs&amp;diff=15657</id>
		<title>Pregătirea unui concurs</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Preg%C4%83tirea_unui_concurs&amp;diff=15657"/>
		<updated>2018-10-12T08:23:26Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Regulament intern al comisiei ==&lt;br /&gt;
&lt;br /&gt;
* Unii membri nu vor avea drept de vot. &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;Fără a jigni pe nimeni, nu toți dorim același lucru de la un concurs, iar asta se reflectă în voturile pe probleme. Unii vor să arate ce probleme dure pot ei să producă. Alții vor să testeze dacă concurenții cunosc un algoritm în particular. Cu un vot 100% democratic, lucrurile se vor duce mereu pe acest făgaș greșit.&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Membrii trebuie să contribuie cu volumul de muncă așteptat de la ei (estimativ 40-60 de ore).&lt;br /&gt;
&lt;br /&gt;
* Membrii trebuie să contribuie din timp (vezi calendarul), nu în ultimele 3 zile. &lt;br /&gt;
&lt;br /&gt;
* Membrii trebuie să trimită surse didactice, citibile, comentate. &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;Este foarte dăunător să propagăm conceptul (greșit) că singurul scop al unui program este să facă ce trebuie. Dacă concursul este internațional, toate numele de variabile și comentariile trebuie să fie în engleză.&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Membrii trebuie să fie disponibili pentru două întâlniri în persoană. &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;Facem excepție pentru membrii care nu locuiesc în București, dar și ei trebuie să fie disponibili pentru o videoconferință.&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Membrii trebuie să îi informeze pe restul despre progresele făcute și planuri de viitor, printr-un e-mail periodic. &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;Membrii vor primi mementouri prin e-mail pentru asta (săptămânal la început, la 3-4 zile spre final). Cei care omit să răspundă la un memento vor primi un avertisment. Cei care omit al doilea răspuns vor fi înlocuiți.&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Membrii trebuie să anunțe cât mai repede dacă își dau seama că nu mai pot contribui, din orice motive.&lt;br /&gt;
&lt;br /&gt;
* Dacă sunt autorii unei probleme, membrii trebuie să fie disponibili pe telefon sau în persoană în timpul concursului, în afară de cazul în care altcineva acceptă această răspundere.&lt;br /&gt;
&lt;br /&gt;
* Membrii trebuie să contribuie cu câteva ore și după concurs, pentru analiza finală &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;(de exemplu, pentru a analiza sursele concurenților ca să vedem cine a reușit să fenteze probleme).&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Membrii vor face un minim de efort să se descurce cu tehnologiile folosite (să-și încarce testele în mediu, să adauge surse la repository etc.). Va exista un coordonator al acestor activități, dar el nu poate face singur totul.&lt;br /&gt;
&lt;br /&gt;
== Regulament de concurs ==&lt;br /&gt;
&lt;br /&gt;
* Sursele tuturor concurenților vor fi publicate după concurs, în domeniul public. Prin înscrierea în concurs, concurenții sunt de acord să renunțe la orice formă de copyright pe sursele lor. &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;Nu neapărat toate variantele trimise vor fi publicate, dar cel puțin cea care determină scorul la fiecare problemă (ultima sau cea cu punctaj maxim, după caz).&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Tehnologii folosite ==&lt;br /&gt;
&lt;br /&gt;
=== Repository ===&lt;br /&gt;
&lt;br /&gt;
Vom folosi un repository privat pentru toate documentele (surse, teste, enunțuri). &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;Există multe variante (GitHub / GitLab / Bitbucket / instalare proprie de GitLab etc.)&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Mediul de concurs ===&lt;br /&gt;
&lt;br /&gt;
Vom folosi un mediu de concurs pe măsură ce problemele se conturează. Este probabil bine ca acesta să fie același cu cel folosit la concursul în sine. Până acum am lucrat cu [https://cms-dev.github.io/ CMS] și cu [https://csacademy.com/ CS Academy]. Beneficii:&lt;br /&gt;
&lt;br /&gt;
* Membrii comisiei își pot testa sursele devreme.&lt;br /&gt;
* Comisia poate afla ușor ce metode sunt deja implementate, ce punctaje iau și ce mai este de făcut pentru fiecare problemă.&lt;br /&gt;
** Pentru asta, sursele trimise trebuie să conțină, într-un comentariu, numele autorului și metoda de rezolvare. &lt;br /&gt;
&lt;br /&gt;
Cerințe de la mediu:&lt;br /&gt;
&lt;br /&gt;
* Să aibă opțiunea pentru feedback complet / parțial.&lt;br /&gt;
* Să permită vederea tuturor surselor trimise (nu doar ultimele N sau orice de acest gen).&lt;br /&gt;
* Să permită relativ ușor organizarea unui concurs paralel online.&lt;br /&gt;
&lt;br /&gt;
=== De analizat: pregătirea subiectelor în Polygon ===&lt;br /&gt;
&lt;br /&gt;
Dan sugerează [https://polygon.codeforces.com/ Polygon] pentru pregătirea de probleme cu mai puține greșeli.&lt;br /&gt;
&lt;br /&gt;
=== E-mail ===&lt;br /&gt;
&lt;br /&gt;
Principalul mijloc de comunicare este o listă de e-mail, care are două avantaje:&lt;br /&gt;
&lt;br /&gt;
# Este asincron. Nu presupune ore fixe.&lt;br /&gt;
# Ajunge la toți membrii simultan, spre deosebire de telefon.&lt;br /&gt;
&lt;br /&gt;
Evident, membrii pot comunica între ei pe orice canale, dar tot ce este de interes general trebuie să ajungă și pe lista de e-mail.&lt;br /&gt;
&lt;br /&gt;
Eticheta pe e-mail este:&lt;br /&gt;
&lt;br /&gt;
* Dacă un mesaj este pentru tine, răspunde în maxim 24h. Nu ține lumea în loc.&lt;br /&gt;
* Probleme separate în threaduri separate.&lt;br /&gt;
&lt;br /&gt;
== Ce înseamnă o problemă? ==&lt;br /&gt;
&lt;br /&gt;
Crearea unei probleme de concurs înseamnă mai mult decât o sursă care să ia 100p.&lt;br /&gt;
&lt;br /&gt;
În primul rând este nevoie de o &#039;&#039;&#039;idee&#039;&#039;&#039;. Acest pas durează cât durează, arta nu poate fi grăbită. Ca să facem un set bun de 6 probleme + 1-2 rezerve, realist avem nevoie de 13-15 idei.&lt;br /&gt;
&lt;br /&gt;
Punerea în practică presupune:&lt;br /&gt;
&lt;br /&gt;
* Tot ce înseamnă discuții pe e-mail despre problemă, dezbaterea algoritmilor etc.&lt;br /&gt;
* Formalizarea enunțului, pentru ca lumea să poată începe să trimită surse. Îl putem modifica ulterior, aducând la zi sursele trimise.&lt;br /&gt;
* Minim două soluții optime.&lt;br /&gt;
* Minim o soluție forță brută, dar cât mai corectă (cât mai greu de greșit).&lt;br /&gt;
* Minim două mânăreli &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;(idei ușoare sau surse foarte scurte care încearcă să obțină cât mai multe puncte).&amp;lt;/span&amp;gt;&lt;br /&gt;
* Generatorul de teste. Autorul generatorului trebuie să prezinte în mare algoritmul de generare și cel puțin o altă persoană să-l analizeze.&lt;br /&gt;
* Generarea testelor.&lt;br /&gt;
* O sursă care să facă assert că testele încărcate efectiv în mediul de concurs corespund limitelor promise în enunț.&lt;br /&gt;
* Stabilirea limitelor pentru punctaje parțiale.&lt;br /&gt;
* Implementarea surselor pentru punctaje parțiale &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;(e.g. un algoritm în O(N log^2 N) în loc de O(N log N)).&amp;lt;/span&amp;gt;&lt;br /&gt;
* Soluția în format PDF, cât mai clară.&lt;br /&gt;
* După concurs, analiza surselor concurenților pentru a vedea cine a reușit să fenteze problema. Ne interesează:&lt;br /&gt;
** cine a reușit să ia 90-100p cu o abordare diferită de a comisiei;&lt;br /&gt;
** cine a reușit să ia 40-50p sau mai mult cu o sursă foarte scurtă.&lt;br /&gt;
&lt;br /&gt;
Toate sursele trebuie să fie didactice și comentate. &amp;lt;span style=&amp;quot;color: #777&amp;quot;&amp;gt;Este jenant și needucativ să publicăm cod neinteligibil. Nu vă bazați pe altcineva să facă asta pentru voi.&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Asta înseamnă zeci de ore per problemă. Nu este o glumă. Gândiți-vă bine dacă aveți acest timp înainte de a accepta invitația în comisie. Dacă tăiem orice colțuri de la această cale, concursul va avea de suferit.&lt;br /&gt;
&lt;br /&gt;
== Calendar ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! termen&lt;br /&gt;
! responsabil&lt;br /&gt;
! activitate&lt;br /&gt;
|-&lt;br /&gt;
| 4 luni&lt;br /&gt;
| organizatorul&lt;br /&gt;
| Stabilește câte probleme dorim și la ce nivel (OJI, ONI, lot).&lt;br /&gt;
|-&lt;br /&gt;
| 4 luni&lt;br /&gt;
| organizatorul&lt;br /&gt;
| Caută membri pentru comisie. Le transmite clar așteptările de mai sus.&lt;br /&gt;
|-&lt;br /&gt;
| 3 luni&lt;br /&gt;
| comisia&lt;br /&gt;
| Componența comisiei este definitivată.&amp;lt;br/&amp;gt;&lt;br /&gt;
Membrii propun probleme pe lista de e-mail.&amp;lt;br/&amp;gt;&lt;br /&gt;
Comisia planifică întâlniri în persoană.&lt;br /&gt;
|-&lt;br /&gt;
| 3 luni&lt;br /&gt;
| organizatorul&lt;br /&gt;
| Stabilește mediul de concurs.&amp;lt;br/&amp;gt;&lt;br /&gt;
Caută sysadmin pentru mediu. Acesta are nevoie de timp să învețe sistemul.&lt;br /&gt;
|-&lt;br /&gt;
| 2 luni&lt;br /&gt;
| sysadminul&lt;br /&gt;
| Procură calculatoarele necesare pentru mediu (probabil unul singur).&amp;lt;br/&amp;gt;&lt;br /&gt;
Livrează un mediu funcțional cu conturi și parole pentru membrii comisiei.&lt;br /&gt;
|-&lt;br /&gt;
| 2 - 1 luni&lt;br /&gt;
| comisia&lt;br /&gt;
| Are două întâlniri în care dezbate problemele.&amp;lt;br&amp;gt;&lt;br /&gt;
Identifică minim 3 probleme de care este sigură, la care poate lucra (vezi secțiunea „Ce înseamnă o problemă?”).&lt;br /&gt;
|-&lt;br /&gt;
| 2 luni - 1 săptămână&lt;br /&gt;
| comisia&lt;br /&gt;
| Lucrează la implementări cu tot ce ține de aceasta.&lt;br /&gt;
|-&lt;br /&gt;
| 4 săptămâni&lt;br /&gt;
| comisia&lt;br /&gt;
| Setul de probleme este definitivat.&amp;lt;br/&amp;gt;&lt;br /&gt;
Autorii problemelor caută oameni pentru diversele faze ale implementării și escaladează din timp dacă nu găsesc.&lt;br /&gt;
|-&lt;br /&gt;
| 2 săptămâni&lt;br /&gt;
| organizatorul&lt;br /&gt;
| Compilează listele de concurenți.&lt;br /&gt;
|-&lt;br /&gt;
| 1 săptămână&lt;br /&gt;
| comisia&lt;br /&gt;
| Implementarea este finalizată.&lt;br /&gt;
|-&lt;br /&gt;
| 1 săptămână&lt;br /&gt;
| organizatorul&lt;br /&gt;
| Team leaderii colaborează pentru traduceri.&amp;lt;br/&amp;gt;&lt;br /&gt;
Neclaritățile sunt colectate și trimise comisiei.&lt;br /&gt;
|-&lt;br /&gt;
| 3-4 zile&lt;br /&gt;
| comisia&lt;br /&gt;
| Comisia corectează neclaritățile.&lt;br /&gt;
|-&lt;br /&gt;
| 3-4 zile&lt;br /&gt;
| organizatorul&lt;br /&gt;
| Team leaderii definitivează traducerile.&lt;br /&gt;
|-&lt;br /&gt;
| 3 zile&lt;br /&gt;
| sysadminul&lt;br /&gt;
| Creează conturile concurenților.&amp;lt;br/&amp;gt;&lt;br /&gt;
Creează 10 conturi generice pentru situații de urgență.&amp;lt;br/&amp;gt;&lt;br /&gt;
Dacă mediul este partajat cu alte activități (ex. Varena), sysadminul se asigură că nu există teme sau alte concursuri cu deadline în timpul concursului nostru.&lt;br /&gt;
|-&lt;br /&gt;
| 2 zile înainte&lt;br /&gt;
| organizatorul&lt;br /&gt;
| Tipărește și multiplică subiectele.&lt;br /&gt;
|-&lt;br /&gt;
| în dimineața concursului&lt;br /&gt;
| sysadminul&lt;br /&gt;
| Pornește firewall-ul. Trebuie tăiat accesul la orice în afară de mediu și alte resurse convenite anterior (cplusplus.com etc.).&lt;br /&gt;
|-&lt;br /&gt;
| după concurs&lt;br /&gt;
| comisia&lt;br /&gt;
| Analizează sursele concurenților pentru a vedea cine și cum a reușit să fenteze problemele.&lt;br /&gt;
|-&lt;br /&gt;
| după concurs&lt;br /&gt;
| comisia&lt;br /&gt;
| Publică arhiva completă cu enunțuri, soluții, teste, surse.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== TODO ==&lt;br /&gt;
&lt;br /&gt;
De adăugat la document: organizarea unui practice session cu o problemă sau (dacă concursul real include și probleme interactive) cu două probleme, din care una interactivă.&lt;br /&gt;
&lt;br /&gt;
De adăugat: organizarea unui concurs în liceu pe același hardware, pentru un pic de stress testing.&lt;br /&gt;
&lt;br /&gt;
De adăugat: cerințe mai generale pentru CMS: cîți workeri, workerii să fie echivalenți, să fie hardware decent, workerul pentru concursul online să fie echivalent cu cei de la concursul real etc.&lt;br /&gt;
&lt;br /&gt;
Numărul de workeri să țină cont și de numărul de concurenți (2018: 3 workeri Core 2 Duo la 104 concurenți).&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Psihologia_concursurilor_de_informatic%C4%83&amp;diff=14960</id>
		<title>Psihologia concursurilor de informatică</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Psihologia_concursurilor_de_informatic%C4%83&amp;diff=14960"/>
		<updated>2018-03-31T13:45:15Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;Notă 1&#039;&#039;&#039;: Există și o [https://github.com/CatalinFrancu/psycho variantă Latex] a acestei cărți.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Notă 2&#039;&#039;&#039;: Am publicat această carte în 1997 la Editura L&amp;amp;S Infomat. În 20 de ani s-au schimbat multe. Materia predată s-a schimbat mult. Concursurile s-au schimbat mult. Limbajele C și C++ au înlocuit aproape complet limbajul Pascal la concursuri. Mai ales, eu cel de acum nu mai sunt de acord cu unele principii, moduri de exprimare și stiluri de programare din această carte. Totuși, ocazional lumea îmi mai cere o copie a ei și mă simt jenat să le trimit un fișier Word (apropo de principii). De aceea, am publicat cartea online. Am păstrat tot conținutul original, făcând doar modificări minime de formatare pentru compatibilitatea cu MediaWiki (cuvântul „migălos” abia începe să descrie aceste modificări). — Cătălin, București, 2 martie 2018.&lt;br /&gt;
&lt;br /&gt;
----&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;Această carte îi este dedicată fratelui meu Cristi, căruia îi datorez o mare parte din cunoștințele mele în domeniul programării. Un gând bun pentru familia mea și pentru prietenii mei, care mi-au luat toate îndatoririle de pe umeri cât timp am scris cartea de față. Fără înțelegerea și răbdarea lor, nu mi-aș fi putut duce munca la bun sfârșit.&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* [[Psihologia concursurilor de informatică/0 Cuvânt înainte|Cuvânt înainte]]&lt;br /&gt;
* [[Psihologia concursurilor de informatică/1 Concursul de informatică|Capitolul I: Concursul de informatică (de la extaz la agonie)]]&lt;br /&gt;
* [[Psihologia concursurilor de informatică/2 Lucrul cu numere mari|Capitolul II: Lucrul cu numere mari]]&lt;br /&gt;
* [[Psihologia concursurilor de informatică/3 Lucrul cu structuri mari de date|Capitolul III: Lucrul cu structuri mari de date]]&lt;br /&gt;
* [[Psihologia concursurilor de informatică/4 Heap-uri și tabele de dispersie|Capitolul IV: Heap-uri și tabele de dispersie]]&lt;br /&gt;
* [[Psihologia concursurilor de informatică/5 Despre algoritmi exponențiali și îmbunătățirea lor|Capitolul V: Despre algoritmi exponențiali și îmbunătățirea lor]]&lt;br /&gt;
* [[Psihologia concursurilor de informatică/6 Probleme de concurs 1|Capitolul VI: Probleme de concurs (1-6)]]&lt;br /&gt;
* [[Psihologia concursurilor de informatică/6 Probleme de concurs 2|Capitolul VI: Probleme de concurs (7-12)]]&lt;br /&gt;
* [[Psihologia concursurilor de informatică/6 Probleme de concurs 3|Capitolul VI: Probleme de concurs (13-18)]]&lt;br /&gt;
* [[Psihologia concursurilor de informatică/7 Bibliografie|Bibliografie]]&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Psihologia_concursurilor_de_informatic%C4%83/6_Probleme_de_concurs_3&amp;diff=14824</id>
		<title>Psihologia concursurilor de informatică/6 Probleme de concurs 3</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Psihologia_concursurilor_de_informatic%C4%83/6_Probleme_de_concurs_3&amp;diff=14824"/>
		<updated>2018-03-12T10:55:14Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;⇦ înapoi la [[Psihologia concursurilor de informatică]]&lt;br /&gt;
&lt;br /&gt;
= Capitolul VI: Probleme de concurs (13-18) =&lt;br /&gt;
&lt;br /&gt;
== Problema 13 ==&lt;br /&gt;
&lt;br /&gt;
Probabil că orice elev care are cât de cât experiență în programare a auzit despre &#039;&#039;&#039;problema celui mai lung subșir crescător&#039;&#039;&#039;. Când este prezentată la concurs, problema e „învelită” sub diverse forme. Aici o vom formaliza din punct de vedere matematic.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Se dă un vector cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; elemente numere întregi. Se cere să se determine cel mai lung subșir crescător.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Fișierul de tip text &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; conține pe prima linie numărul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; de elemente din vector (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;≤10.000), iar pe fiecare din următoarele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; linii se află câte un element al vectorului.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: În fișierul de tip text &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; se vor lista pe prima linie lungimea &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; a celui mai lung subșir crescător, iar pe următoarele &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; linii subșirul în sine. Dacă există mai multe soluții, se va tipări una singură.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;6&amp;lt;br&amp;gt;2&amp;lt;br&amp;gt;5&amp;lt;br&amp;gt;7&amp;lt;br&amp;gt;3&amp;lt;br&amp;gt;4&amp;lt;br&amp;gt;1&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;3&amp;lt;br&amp;gt;2&amp;lt;br&amp;gt;3&amp;lt;br&amp;gt;4&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 45 minute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 5 secunde.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Începem prin a lămuri diferența dintre noțiunile de „subșir” și „subsecvență”. Fie &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[1], &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[2], ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[N],  vectorul citit. Prin &#039;&#039;&#039;subșir de lungime &#039;&#039;L&#039;&#039;&#039;&#039;&#039; al vectorului V se înțelege o succesiune nu neapărat continuă de elemente &amp;lt;math&amp;gt;V[K_1], V[K_2], \dots, V[K_L]&amp;lt;/math&amp;gt;, unde &amp;lt;math&amp;gt;K_1&amp;lt;K_2&amp;lt; ... &amp;lt;K_L&amp;lt;/math&amp;gt;. Prin &#039;&#039;&#039;subsecvență de lungime &#039;&#039;L&#039;&#039;&#039;&#039;&#039; a vectorului, începând de la poziția &#039;&#039;&#039;K&#039;&#039;&#039;, se înțelege succesiunea continuă de elemente &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;], &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+1], ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+&#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;-1].&lt;br /&gt;
&lt;br /&gt;
Rezolvarea prin metoda programării dinamice este în general cunoscută și nu vom insista asupra ei. Probabil că aflarea celui mai lung subșir crescător este punctul de plecare al oricărui elev în învățarea programării dinamice. Totuși, această metodă de rezolvare are complexitatea &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;. În continuare prezentăm o metodă mai puțin cunoscută și ceva mai dificil de implementat, dar mult mai eficientă. Ea are complexitatea &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;. Vom enunța principiul de rezolvare, urmat de o schiță a demonstrației de corectitudine.&lt;br /&gt;
&lt;br /&gt;
Fie &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; vectorul citit. Se parcurge vectorul de la stânga la dreapta și se construiesc în paralel doi vectori &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;, astfel: inițial vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; este vid. Se ia fiecare element din &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; și se suprascrie peste cel mai mic element din &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; care este strict mai mare ca el. Dacă nu există un asemenea element în &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;, cu alte cuvinte dacă elementul analizat din &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; este mai mare ca toate elementele din &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;, atunci el este adăugat la sfârșitul vectorului &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;. Concomitent, se notează în vectorul &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; poziția pe care a fost adăugat în vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; elementul din &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;. Iată cum se construiesc vectorii &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; pentru exemplul din enunț:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig68.png]]&lt;br /&gt;
&lt;br /&gt;
Lungimea &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; la care ajunge vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; la sfârșitul acestei prelucrări este tocmai lungimea celui mai lung subșir crescător al vectorului &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;. Pentru a afla exact și care sunt elementele subșirului crescător se procedează astfel: se caută ultima apariție în vectorul &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; a valorii &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;. Să spunem că ea este găsită pe poziția &amp;lt;math&amp;gt;K_L&amp;lt;/math&amp;gt;. Se caută apoi ultima apariție în vectorul &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; a valorii &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;-1, anterior poziției &amp;lt;math&amp;gt;K_L&amp;lt;/math&amp;gt;. Ea va fi pe poziția &amp;lt;math&amp;gt;K_{L-1}&amp;lt;K_{L}&amp;lt;/math&amp;gt;. Analog se caută în vectorul &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; valorile &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;-2, &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;-3, ..., 2, 1. Subșirul crescător este &amp;lt;math&amp;gt;S=(V[K_1], V[K2], \dots, V[K_L])&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
În figura de mai sus au fost hașurate elementele respective găsite în vectorul &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. Vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; are la sfârșit lungimea &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;=3, deci cel mai lung subșir crescător are trei elemente. Se caută în vectorul &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; cifrele 3, 2 și 1 și se găsesc pe pozițiile &amp;lt;math&amp;gt;K_1=1&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;K_2=4&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;K_3=5&amp;lt;/math&amp;gt;, ceea ce înseamnă că cel mai lung subșir crescător este (2, 3, 4).&lt;br /&gt;
&lt;br /&gt;
Demonstrația (care, fără a pierde din corectitudine, face apel la intuiție...) folosește aceleași notații de mai sus și are următoarele etape:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1. Vectorul &#039;&#039;Q&#039;&#039; este în permanență sortat crescător.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Demonstrația acestei afirmații se face prin inducție. După primul pas (inserarea elementului &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[1]), vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; are un singur element și este bineînțeles ordonat. Trebuie acum arătat că dacă vectorul Q este sortat după inserarea elementului &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;-1], el rămâne sortat și după inserarea lui &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;]. Într-adevăr, pentru elementul &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;] există două variante:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;] este mai mare decât toate elementele lui &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;, caz în care este adăugat la sfârșitul lui &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;, care rămâne sortat;&lt;br /&gt;
* Există &#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;&amp;gt;1 astfel încât &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;-1]≤&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;]&amp;lt;&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;], caz în care &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;] se suprascrie peste &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;]. Dar &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;]≤ &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;+1] ⇒ &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;-1]≤&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;]&amp;lt;&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;+1] și vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; rămâne sortat.&lt;br /&gt;
&lt;br /&gt;
Această deducție ne va fi utilă în calculul complexității algoritmului.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2. Odată ce au fost scrise pentru prima oară, elementele vectorului &#039;&#039;Q&#039;&#039; nu mai pot decât să scadă sau să rămână constante. Ele nu pot crește niciodată.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Afirmația este evidentă, decurgând din modul de construcție a lui &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3. Elementele din vectorul &#039;&#039;V&#039;&#039; de la poziția &amp;lt;math&amp;gt;K_{i-1}+1&amp;lt;/math&amp;gt; la poziția &amp;lt;math&amp;gt;K_{i}-1&amp;lt;/math&amp;gt; sunt fie mai mici decât &amp;lt;math&amp;gt;V[K_{i-1}]&amp;lt;/math&amp;gt;, fie mai mari decât &amp;lt;math&amp;gt;V[K_i]&amp;lt;/math&amp;gt;.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Acest lucru este natural, deoarece dacă ar exista &amp;lt;math&amp;gt;K_{i-1}&amp;lt;X&amp;lt;K_i&amp;lt;/math&amp;gt; astfel încât &amp;lt;math&amp;gt;V[K_{i-1}] \leq V[X] \leq V[K_i]&amp;lt;/math&amp;gt;, atunci șirul &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; ar putea fi extins cu elementul &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;], deci nu ar fi subșir maximal, contradicție.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4. Toate elementele care urmează în &#039;&#039;V&#039;&#039; după &amp;lt;math&amp;gt;V[K_L]&amp;lt;/math&amp;gt; sunt mai mici decât &amp;lt;math&amp;gt;V[K_L]&amp;lt;/math&amp;gt;.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Dacă ar exista &amp;lt;math&amp;gt;X&amp;gt;K_L&amp;lt;/math&amp;gt; astfel încât &amp;lt;math&amp;gt;V[X] \geq V[K_L]&amp;lt;/math&amp;gt;, atunci &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; ar putea fi extins la dreapta cu &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;], contradicție.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;5. Elementul &amp;lt;math&amp;gt;V[K_1]&amp;lt;/math&amp;gt; este suprascris peste poziția &#039;&#039;Q&#039;&#039;[1].&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Demonstrația este imediată: Dacă elementul &amp;lt;math&amp;gt;V[K_1]&amp;lt;/math&amp;gt; este inserat în &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; pe o poziție &#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;&amp;gt;1, rezultă că, în momentul tratării lui &amp;lt;math&amp;gt;V[K_1]&amp;lt;/math&amp;gt;, pe poziția &#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;-1 în vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; exista deja un număr &amp;lt;math&amp;gt;X&amp;lt;V[K_1]&amp;lt;/math&amp;gt;. Aceasta înseamnă că &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; poate fi prelungit la stânga cu &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;, deci &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; nu este un subșir crescător maxim, contradicție.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;6. Orice element &amp;lt;math&amp;gt;V[K_i]&amp;lt;/math&amp;gt; va fi suprascris în &#039;&#039;Q&#039;&#039; pe poziția următoare celei pe care a fost scris &amp;lt;math&amp;gt;V[K_{i-1}]&amp;lt;/math&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Demonstrație: Să presupunem că &amp;lt;math&amp;gt;V[K_{i-1}]&amp;lt;/math&amp;gt; a fost scris peste &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;]. Înainte de a ajunge să tratăm elementul &amp;lt;math&amp;gt;V[K_i]&amp;lt;/math&amp;gt;, a trebuit să tratăm elementele &amp;lt;math&amp;gt;V[K_{i-1}+1], V[K_{i-1}+2], \dots, V[K_{i}-1]&amp;lt;/math&amp;gt;, care, după cum s-a stabilit la punctul (3), pot fi:&lt;br /&gt;
&lt;br /&gt;
* mai mici decât &amp;lt;math&amp;gt;V[K_{i-1}]&amp;lt;/math&amp;gt;, caz în care ele vor fi scrise în vector pe poziții mai mici sau egale cu &#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;, iar &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;] va scădea, deci &amp;lt;math&amp;gt;Q[t] \leq V[K_{i-1}]&amp;lt;/math&amp;gt; (conform punctului 2);&lt;br /&gt;
* mai mari decât &amp;lt;math&amp;gt;V[K_i]&amp;lt;/math&amp;gt;, caz în care vor fi scrise în &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; pe poziții mai mari decât &#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;, așadar &amp;lt;math&amp;gt;Q[t+1]&amp;gt;V[K_i]&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Se obține lanțul de inegalități &amp;lt;math&amp;gt;Q[t] \leq V[K_{i-1}] \leq V[K_i] \leq Q[t+1]&amp;lt;/math&amp;gt;, de unde rezultă conform modului de construcție că &amp;lt;math&amp;gt;V[K_i]&amp;lt;/math&amp;gt; va fi scris peste &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;+1]. Cu aceasta, folosind și punctul (5), am demonstrat că &amp;lt;math&amp;gt;V[K_i]&amp;lt;/math&amp;gt; este scris pe poziția &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;], ∀&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;=1, 2, ..., &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;. După tratarea primelor &amp;lt;math&amp;gt;K_L&amp;lt;/math&amp;gt; elemente din &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;, vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; are lungimea &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;. Mai rămâne de văzut ce se întâmplă cu elementele &amp;lt;math&amp;gt;V[K_L+1], \dots, V[N]&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;7. Elementele care îi urmează lui &amp;lt;math&amp;gt;V[K_L]&amp;lt;/math&amp;gt; se scriu în &#039;&#039;Q&#039;&#039; pe poziții mai mici sau egale cu &#039;&#039;L&#039;&#039;.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Acest fapt este intuitiv dacă se ține cont de punctul (4). Deducem că la final vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; are lungime &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;, adică aceeași cu a lui &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;. Modul de reconstituire a lui &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; din vectorul &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; este corect. Trebuie să avem grijă ca, atunci când căutăm în vectorul &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; o apariție a valorii &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;, să o alegem pe ultima disponibilă, altfel pot apărea erori. Spre exemplu, pentru vectorii dați în exemplu, &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;=(2, 5, 7, 3, 4, 1) și &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;=(1, 2, 3, 2, 3, 1), dacă se alege prima apariție a lui 2 (pe poziția a doua), avem subșirul &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;=(2, 5, 4) care nu este crescător. Dacă însă alegem ultima apariție a lui 2 (pe poziția a patra), avem subșirul crescător maximal &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;=(2, 3, 4).&lt;br /&gt;
&lt;br /&gt;
Calculul complexității este ușor de făcut:&lt;br /&gt;
&lt;br /&gt;
* Pentru construcția vectorilor &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; sunt necesare &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; inserții în vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;, care este sortat; o inserție binară cere &amp;lt;math&amp;gt;O(\log N)&amp;lt;/math&amp;gt;, așadar complexitatea primei părți este &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* Pentru reconstituirea lui &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; se face o singură parcurgere a vectorului &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;, deci &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Complexitatea totală a algoritmului este &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#define Infinity 30000&lt;br /&gt;
typedef int Vector[10001];&lt;br /&gt;
&lt;br /&gt;
Vector V,P,Q,*S;&lt;br /&gt;
int N,Len; /* Len = Lungimea vectorului Q */&lt;br /&gt;
&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;,&amp;quot;rt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F,&amp;quot;%d&amp;quot;,&amp;amp;N);&lt;br /&gt;
  for(i=1;i&amp;lt;=N;i++) fscanf(F,&amp;quot;%d&amp;quot;,&amp;amp;V[i]);&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int Insert(int K,int Lo,int Hi)&lt;br /&gt;
{ int Mid=(Lo+Hi)/2;&lt;br /&gt;
&lt;br /&gt;
  if (Lo==Hi)&lt;br /&gt;
    { if (Hi&amp;gt;Len) Q[++Len+1]=Infinity;&lt;br /&gt;
      Q[Lo]=K;&lt;br /&gt;
      return Lo;&lt;br /&gt;
    }&lt;br /&gt;
    else if (K&amp;lt;Q[Mid]) return Insert(K,Lo,Mid);&lt;br /&gt;
                     else return Insert(K,Mid+1,Hi);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void BuildPQ(void)&lt;br /&gt;
{ int i,Place;&lt;br /&gt;
&lt;br /&gt;
  Len=0;Q[1]=Infinity;&lt;br /&gt;
  for (i=1;i&amp;lt;=N;i++)&lt;br /&gt;
    P[i]=Insert(V[i],1,Len+1);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void BuildS(void)&lt;br /&gt;
{ int i,K=N;&lt;br /&gt;
&lt;br /&gt;
  S=malloc(sizeof(*S));&lt;br /&gt;
  for (i=Len;i;i--)&lt;br /&gt;
    { while (P[K]!=i) K--;&lt;br /&gt;
      (*S)[i]=V[K];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void WriteSolution(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;output.txt&amp;quot;,&amp;quot;wt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  fprintf(F,&amp;quot;%d\n&amp;quot;,Len);&lt;br /&gt;
  for(i=1;i&amp;lt;=Len;i++) fprintf(F,&amp;quot;%d\n&amp;quot;,(*S)[i]);&lt;br /&gt;
&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  BuildPQ();&lt;br /&gt;
  BuildS();&lt;br /&gt;
  WriteSolution();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Menționăm că în sursa de mai sus se putea construi vectorul &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; peste vectorul &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;, deoarece pentru construirea lui &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; nu avem nevoie decât de elementele vectorului &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. În acest fel, programul ar fi avut nevoie numai de trei vectori, iar volumul total de date nu ar fi depășit un segment. Am preferat totuși varianta în care vectorul &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; este alocat dinamic pentru a evita confuziile.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 14 ==&lt;br /&gt;
&lt;br /&gt;
Continuăm cu două probleme foarte asemănătoare. Atât de asemănătoare, încât diferența dintre ele pare - la o privire superficială - neglijabilă. Totuși, algoritmul de rezolvare se schimbă fundamental.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; grămezi de mere trebuie împărțite la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; copii. Deoarece copiii sunt buni prieteni, trebuie ca împărțirea să se facă în mod echitabil, fiecare primind același număr de mere. Spiritul de dreptate al copiilor este atât de puternic, încât ei preferă ca unele mere să nu fie date nici unui copil, decât ca unii să primească mai multe mere ca alții. O condiție suplimentară este ca fie toate merele dintr-o grămadă să fie împărțite copiilor, fie grămada să nu mai fie împărțită deloc. Desigur, interesul este ca fiecare copil să primească un număr cât mai mare de mere.&lt;br /&gt;
&lt;br /&gt;
Să se selecteze un număr de grămezi din cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; astfel încât numărul total de mere să se dividă cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, iar suma selectată să fie maximă. Dacă există mai multe soluții, se cere una singură.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Fișierul de intrare &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; conține pe prima linie numărul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; de grămezi (1≤&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;≤100). Pe a doua linie se dau cantitățile de mere din cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; grămezi (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere naturale pozitive, toate mai mici ca 200, separate prin spații).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: În fișierul &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; se va scrie pe prima linie numărul maxim de mere găsit. Pe a doua linie se vor tipări indicii grămezilor selectate, în ordine crescătoare.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;4&amp;lt;br&amp;gt;3 2 5 7&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;12&amp;lt;br&amp;gt;1 3 4&amp;lt;/tt&amp;gt;&amp;lt;ref&amp;gt;Greșeală în original, corect: &amp;lt;tt&amp;gt;1 2 4&amp;lt;/tt&amp;gt;&amp;lt;/ref&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 45 minute - maxim o oră.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 1 secundă.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: O primă modalitate, de altfel foarte comodă, este să verificăm toate posibilitățile de a selecta grămezi. Aceasta presupune să generăm toate submulțimile mulțimii de grămezi, iar pentru fiecare submulțime să calculăm suma merelor. În felul acesta putem afla submulțimea pentru care suma merelor se divide cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; și este maximă. Din nefericire, această soluție, banal de implementat, are o complexitate exponențială, mai precis &amp;lt;math&amp;gt;O(N \times 2^N)&amp;lt;/math&amp;gt;, deoarece există &amp;lt;math&amp;gt;2^N&amp;lt;/math&amp;gt; submulțimi ale mulțimii grămezilor și pentru fiecare submulțime putem calcula suma merelor în timp liniar. Prin urmare, suntem departe de complexitatea cerută în enunț.&lt;br /&gt;
&lt;br /&gt;
Punctul de plecare pentru rezolvarea corectă a problemei este din nou principiul de optimalitate al programării dinamice. Să notăm cu &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[1], &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[2], ..., &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;] cantitățile de mere din fiecare grămadă. Să considerăm că submulțimea optimă conține în total &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; mere, iar grămada cu numărul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; face parte din ea. Fie &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; restul împărțirii lui &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;] la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, deci&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;M[N] \equiv K \pmod{N}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Atunci suma &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; - &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;] dă restul (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;) mod &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; la împărțirea prin &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, de unde deducem că&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S - M[N] \equiv N - K \pmod{N}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
De asemenea, putem afirma că &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; - &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;] este cea mai mare dintre toate sumele care se pot obține folosind grămezile 1, 2, ..., &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 și care dau același rest la împărțirea prin &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;. Demonstrația nu este grea: dacă ar exista o sumă mai mare decât &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; - &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;] congruentă cu (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;) modulo &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, am putea folosi această sumă și grămada &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;] pentru a obține o sumă &#039;&#039;&#039;&#039;&#039;S’&#039;&#039;&#039;&#039;&#039;&amp;gt;&#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; astfel încât&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S&#039; \equiv 0 \pmod{N}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Această observație ne sugerează și metoda de rezolvare a problemei. Pentru a afla care este submulțimea de sumă maximă, avem două variante:&lt;br /&gt;
&lt;br /&gt;
* Grămada &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; face parte din această submulțime, caz în care trebuie să descoperim cea mai mare sumă care se poate obține adunând grămezi dintre primele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 și care dă restul (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;) la împărțirea prin &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;;&lt;br /&gt;
* Grămada &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; nu face parte din această submulțime, caz în care trebuie să descoperim cea mai mare sumă care se poate obține adunând grămezi dintre primele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 și care se împarte exact la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Pentru că nu putem ști de la început dacă grămada a &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-a face sau nu parte din submulțimea maximală, trebuie să avem răspunsul pregătit pentru ambele situații. Mai mult, pentru a putea afla care sunt sumele maxime formate cu primele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 grămezi care dau diferite resturi (în cazul nostru 0 sau &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;) la împărțirea prin &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, trebuie să reluăm exact aceeași problemă: grămada cu numărul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 poate face sau nu parte din submulțime. În concluzie, putem formaliza problema astfel: avem nevoie să putem răspunde la toate întrebările de forma „Care este suma maximă care se poate forma cu grămezi din primele &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; astfel încât restul la împărțirea prin &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; să fie &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;?”. Vom nota răspunsul la această întrebare cu &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;] (unde 1≤&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;≤&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; și 0≤&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;&amp;lt;&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;). Răspunsurile tuturor întrebărilor se pot deci dispune într-o matrice &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;, iar scopul nostru este să-l aflăm pe &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;,0]. Am observat că pentru a-l putea afla pe &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;] avem nevoie de două valori din linia &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1 a matricei (după cum grămada &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; face sau nu parte din submulțimea optimă). Pentru a afla toate elementele liniei &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; a matricei, este deci foarte probabil să avem nevoie de întreaga linie &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1.&lt;br /&gt;
&lt;br /&gt;
Astfel, problema se reduce la a compune o linie a matricei din cea precedentă. Dacă presupunem că grămada &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; nu intră în componența submulțimii optime, atunci linia &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; este identică cu linia &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;R[P,Q] = R[P - 1, Q], \forall 0 \leq Q &amp;lt; N&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dacă presupunem că grămada &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; face parte din submulțime, atunci avem egalitatea:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;R[P,Q] = R[P - 1, (Q - M[P])\,\bmod\,N] + M[P], \forall 0 \leq Q &amp;lt; N&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Alegând dintre aceste variante pe cea care ne convine mai mult, obținem:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
R[P,Q] =  \max&lt;br /&gt;
\begin{Bmatrix}&lt;br /&gt;
R[P - 1, Q] \\&lt;br /&gt;
R[P - 1, (Q - M[P])\,\bmod\,N] + M[P]&lt;br /&gt;
\end{Bmatrix}&lt;br /&gt;
\forall 0 \leq Q &amp;lt; N&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
În felul acesta se completează fără nici un fel de probleme matricea &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;. După cum am spus, &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;,0] indică suma maximă divizibilă cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;. Mai rămâne de văzut cum se face reconstituirea soluției. De fapt, nu avem decât să parcurgem aceleași etape ale raționamentului, dar în sens invers. Respectiv: dacă &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;,0] = &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1,0], atunci deducem că grămada a &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-a nu a fost folosită pentru a se obține suma maximă și avem nevoie să obținem suma maximă divizibilă cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; din primele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 grămezi (adică &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1,0]). Dacă &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;,0] ≠ &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1,0], atunci grămada a &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-a a fost folosită și avem nevoie să obținem suma maximă de rest (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-&#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;]) modulo &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; folosind primele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 grămezi. Pe cazul general, pentru a obține suma maximă de rest &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; folosind primele &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; grămezi, avem două posibilități:&lt;br /&gt;
&lt;br /&gt;
* Dacă &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;] = &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1,&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;], atunci grămada &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; nu este folosită și trebuie să obținem același rest &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; folosind doar primele &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1 grămezi;&lt;br /&gt;
* Dacă &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;] ≠ &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1,&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;], atunci grămada &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; este folosită și trebuie să obținem restul (&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;- &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;]) modulo &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; folosind primele &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1 grămezi;&lt;br /&gt;
&lt;br /&gt;
Menționăm că programul este puțin diferit de ceea ce s-a explicat până acum, în sensul că prima linie din matricea &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039; corespunde ultimei grămezi de mere, a doua linie corespunde penultimei grămezi de mere ș.a.m.d. Cu alte cuvinte, grămezile de mere sunt procesate în ordine inversă. Am făcut acest lucru pentru a ușura procedura de aflare a soluției; se observă că prima oară se decide dacă ultima grămadă face parte din submulțime, apoi penultima etc. Deci și la găsirea soluției se generează grămezile în ordine inversă. Prin această dublă inversiune, indicii grămezilor de mere selectate vor fi listați în ordine crescătoare. Dacă am fi prelucrat grămezile de mere în ordinea lor din fișier, s-ar fi impus scrierea unei proceduri recursive pentru afișarea soluției.&lt;br /&gt;
&lt;br /&gt;
Să facem și calculul complexității acestui program. Citirea datelor se face în &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;, la fel și reconstituirea soluției. Pentru a completa o linie din matrice, avem nevoie să parcurgem linia precedentă, adică &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;. Pentru compunerea întregii matrice, timpul necesar este pătratic.&lt;br /&gt;
&lt;br /&gt;
Mai trebuie făcută o singură observație referitoare la modul de inițializare a matricei. Vom adăuga în matricea &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039; linia cu numărul 0, care va conține sumele maxime ce se pot obține fără a folosi nici o grămadă. Desigur, se poate obține numai suma 0, care dă restul 0 la împărțirea prin &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, iar alte sume nu se pot obține. Vom pune deci &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[0,0]=0 și &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[0,i]=&#039;&#039;&#039;&#039;&#039;Impossible&#039;&#039;&#039;&#039;&#039; pentru orice 1 ≤ i &amp;lt; &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, unde &#039;&#039;&#039;&#039;&#039;Impossible&#039;&#039;&#039;&#039;&#039; este o constantă specială (preferabil negativă, pentru a nu se confunda cu valorile obișnuite din matrice).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#define NMax 101&lt;br /&gt;
#define Impossible -30000&lt;br /&gt;
&lt;br /&gt;
int M[NMax], R[NMax][NMax], N;&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
{ FILE *F = fopen(&amp;quot;input.txt&amp;quot;, &amp;quot;rt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F, &amp;quot;%d\n&amp;quot;, &amp;amp;N);&lt;br /&gt;
  for (i=1; i&amp;lt;=N; fscanf(F, &amp;quot;%d&amp;quot;, &amp;amp;M[i++]));&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Share(void)&lt;br /&gt;
{ int i,j;&lt;br /&gt;
&lt;br /&gt;
  R[0][0]=0;&lt;br /&gt;
  for (i=1; i&amp;lt;N; R[0][i++]=Impossible);&lt;br /&gt;
&lt;br /&gt;
  for (i=1; i&amp;lt;=N; i++)&lt;br /&gt;
    {&lt;br /&gt;
      for (j=0; j&amp;lt;N; j++)&lt;br /&gt;
        R[i][j] = R[i-1][j];&lt;br /&gt;
      for (j=0; j&amp;lt;N; j++)&lt;br /&gt;
        if (R[i-1][j] != Impossible&lt;br /&gt;
           &amp;amp;&amp;amp; R[i-1][j] + M[N+1-i] &amp;gt; &lt;br /&gt;
              R[i][ (R[i-1][j]+M[N+1-i]) % N ])&lt;br /&gt;
         R[i][ (R[i-1][j]+M[N+1-i]) % N ] =&lt;br /&gt;
         R[i-1][j] + M[N+1-i];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void WriteSolution(void)&lt;br /&gt;
{ FILE *F = fopen(&amp;quot;output.txt&amp;quot;, &amp;quot;wt&amp;quot;);&lt;br /&gt;
  int i,j;&lt;br /&gt;
&lt;br /&gt;
  fprintf(F, &amp;quot;%d\n&amp;quot;, R[N][0]);&lt;br /&gt;
  j=0;&lt;br /&gt;
  for (i=N; i; i--)&lt;br /&gt;
    if (R[i][j] != R[i-1][j])&lt;br /&gt;
      {&lt;br /&gt;
        fprintf(F, &amp;quot;%d &amp;quot;, N+1-i);&lt;br /&gt;
        j = (j + N - M[N+1-i]%N) % N;&lt;br /&gt;
      }&lt;br /&gt;
  fprintf(F, &amp;quot;\n&amp;quot;);&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  Share();&lt;br /&gt;
  WriteSolution();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 15 ==&lt;br /&gt;
&lt;br /&gt;
Problema următoare a fost propusă la proba de baraj de la Olimpiada Națională de Informatică, Slatina 1995 și este un exemplu tipic de aplicare a principiului lui Dirichlet.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: La un SHOP din Slatina se găsesc spre vînzare P-1 (P este un număr prim) produse unicat de costuri &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;(1)$, &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;(2)$, ..., &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1)$. Nici unul din produse nu poate fi cumpărat prin plata exactă cu bancnote de &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;$. în SHOP intră un olimpic care are un număr nelimitat de bancnote de &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;$ și o singură bancnotă de &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;$ (1 ≤ &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; ≤ &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1). Ce produse trebuie să cumpere olimpicul pentru a putea plăti exact produsele cumpărate?&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Datele de intrare se dau în fișierul de intrare &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ce conține două linii:&lt;br /&gt;
&lt;br /&gt;
* pe prima linie valorile lui &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;;&lt;br /&gt;
* pe a doua linie valorile costurilor produselor.&lt;br /&gt;
&lt;br /&gt;
Ieșirea se va face în fișierul &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; unde se vor lista în ordine crescătoare indicii produselor cumpărate de olimpic.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;: &lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;5 4&amp;lt;br&amp;gt;1 3 6 7&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;1 2&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: la Slatina s-au acordat cam 90 de minute, dar 45 ar trebui să fie suficiente.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare pentru fiecare test&#039;&#039;&#039;: 1 sec.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(P)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Enunțul original nu specifica nici o limită maximă pentru valoarea lui P. Vom adăuga noi această limită, respectiv &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; &amp;lt; 10000.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Problema se reduce la a găsi un grup de obiecte pentru care suma costurilor să fie divizibilă cu &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; sau să fie congruentă cu &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039; modulo &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. Am văzut deja în problema precedentă că dispunem de o soluție &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt; pentru a găsi un număr de elemente care să se dividă cu &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. În cazul nostru, trebuie să observăm însă că nu avem &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; obiecte, ci numai &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1; în schimb, dispunem de o bancnotă suplimentară de valoare &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;. Aceste diferențe vor fi explicate mai târziu și se va vedea că ele nu schimbă cu nimic natura problemei. Diferența esențială provine din faptul că nu se mai cere ca suma numerelor să fie maximă, ca în problema precedentă. Orice combinație de numere care dau o sumă potrivită este suficientă.&lt;br /&gt;
&lt;br /&gt;
Să începem prin a explica principiul lui Dirichlet, care de altfel face apel numai la intuiție și nu necesită cunoștințe speciale de matematică. Acest principiu spune că dacă distribuim &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; obiecte în &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; cutii, atunci cel puțin într-o cutie se vor afla minim &amp;lt;math&amp;gt;\lceil N/K \rceil&amp;lt;/math&amp;gt; obiecte (aici prin &amp;lt;math&amp;gt;\lceil N/K \rceil&amp;lt;/math&amp;gt; se înțelege „cel mai mic întreg mai mare decât&amp;lt;ref&amp;gt;Greșeală în original. Corect: mai mare sau egal cu&amp;lt;/ref&amp;gt; &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;/&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;”). Demonstrația se face prin reducere la absurd: dacă în fiecare cutie s-ar afla mai puțin decât &amp;lt;math&amp;gt;\lceil N/K \rceil&amp;lt;/math&amp;gt; obiecte, atunci numărul total de obiecte ar fi mai mic decât &amp;lt;math&amp;gt;K \times \lceil N/K \rceil&amp;lt;/math&amp;gt;, adică mai mic decât &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Spre exemplu, oricum am distribui 7 obiecte în 4 cutii, putem fi siguri că cel puțin într-o cutie se vor afla minim &amp;lt;math&amp;gt;\lceil 7/4 \rceil = 2&amp;lt;/math&amp;gt; obiecte. Într-adevăr, dacă toate cutiile ar conține cel mult câte un obiect, atunci numărul total de obiecte nu ar putea fi mai mare ca 4, ceea ce este absurd.&lt;br /&gt;
&lt;br /&gt;
Să vedem acum cum s-ar putea aplica acest principiu la problema de față. Să facem notația&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S(k) = \sum_{i = 1}^{k} X(i)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
și convenția &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(0) = 0. Prin urmare, putem scrie egalitatea&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S(k_2) - S(k_1) = \sum_{i = k_1}^{k_2} X(i)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dacă găsim în vectorul &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; două valori &amp;lt;math&amp;gt;S(k_1)&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;S(k_2)&amp;lt;/math&amp;gt; care dau același rest la împărțirea prin &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;, înseamnă că diferența lor se divide cu &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;, deci șirul de obiecte &amp;lt;math&amp;gt;k_1+1, k_1+2, \dots, k_2&amp;lt;/math&amp;gt; poate constitui o soluție.&lt;br /&gt;
&lt;br /&gt;
Să presupunem pentru început că dispunem de &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; obiecte. Se pune întrebarea: ce valori poate lua restul împărțirii lui &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) prin &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;? Desigur, orice valoare între 0 și &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1. Există deci în total &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; resturi distincte. Pe de altă parte, există &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;+1 elemente în vectorul &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; (se consideră și elementul &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(0)). Începem să recunoaștem aici principiul lui Dirichlet, în care „obiectele” sunt resturile &amp;lt;math&amp;gt;S(0)\,\bmod\,P,\, S(1)\,\bmod\,P,\, \dots, S(P)\,\bmod\,P&amp;lt;/math&amp;gt;, iar cutiile sunt clasele de resturi modulo &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. Avem de distribuit P+1 obiecte în P cutii, așadar cel puțin într-o cutie se vor afla &amp;lt;math&amp;gt;\lceil (P+1)/P \rceil = 2&amp;lt;/math&amp;gt; obiecte. Prin urmare, vor exista cu siguranță doi indici &amp;lt;math&amp;gt;k_1 &amp;lt; k_2&amp;lt;/math&amp;gt; astfel încât &amp;lt;math&amp;gt;S(k_2) - S(k_1)  \equiv  0 \pmod{P}&amp;lt;/math&amp;gt;. Nu avem decât să tipărim secvența &amp;lt;math&amp;gt;k_1+1, k_1+2, ..., k_2&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Să vedem acum ce se întâmplă dacă avem numai &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1 obiecte, așa cum este cazul problemei. Atunci avem numai &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; resturi posibile, deci se poate ca toate elementele din &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; să dea resturi distincte la împărțirea prin &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. Dar în acest caz, există un indice &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; astfel încât &amp;lt;math&amp;gt;S(k) \equiv Q \pmod{N}&amp;lt;/math&amp;gt;, deci trebuie doar să tipărim secvența de indici 1, 2, ..., &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Pentru a reuni aceste două cazuri într-unul singur, putem considera expresia &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) drept un alt mod de a scrie expresia &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) - &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(0). Problema se reduce la a căuta doi indici &amp;lt;math&amp;gt;k_1 , k_2 \in \{0, 1, 2, ..., P\}&amp;lt;/math&amp;gt; astfel încât &amp;lt;math&amp;gt;(S(k_2) - S(k_1))\,\bmod\,N \in [0,Q]&amp;lt;/math&amp;gt;. Să nu uităm că trebuie să efectuăm această operație într-un timp liniar, deci nu avem voie să comparăm pur și simplu două câte două elementele vectorului &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;. Vom prezenta modul în care se pot găsi două elemente congruente modulo &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;, cazul celălalt tratându-se analog. Metoda constă în crearea unui alt vector, &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;, în care &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;) = &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039; înseamnă că suma &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;) dă restul &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; la împărțirea prin &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. Inițial, toate elementele vectorului &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; vor avea o valoare specială, eventual negativă. Apoi se parcurge vectorul &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; și pentru fiecare &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;) se efectuează operația &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;) mod &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;)=j. În momentul în care se încearcă reatribuirea unui element din &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; care are deja o valoare dată, înseamnă că am găsit cei doi indici pe care îi căutam.&lt;br /&gt;
&lt;br /&gt;
Iată un exemplu. Dacă &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;=7 și &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;=(8, 8, 2, 6, 13, 3), rezultă vectorul &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;=(8, 16, 18, 24, 37, 40). Resturile la împărțirea prin 7 sunt respectiv 1, 2, 4, 3, 2 și 5.&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig69.png]]&lt;br /&gt;
&lt;br /&gt;
După cum se vede, restul 2 poate fi obținut atât cu primele două obiecte, cât și cu primele 5, deci suma prețurilor obiectelor 3, 4 și 5 este divizibilă cu 7.&lt;br /&gt;
&lt;br /&gt;
Un ultim detaliu de implementare constă în aceea că nu este necesară memorarea vectorului &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;, ci numai a elementului curent; orice alte informații care ne trebuie la un moment dat le putem afla din vectorii &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;. Pentru memorarea elementului curent din &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;, se pornește cu valoarea 0 și la fiecare pas se adaugă valoarea elementului corespunzător din &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#define NMax 10000&lt;br /&gt;
#define None -1&lt;br /&gt;
&lt;br /&gt;
int X[NMax], L[NMax], P, Q;&lt;br /&gt;
&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
{ FILE *F = fopen(&amp;quot;input.txt&amp;quot;, &amp;quot;rt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F, &amp;quot;%d %d\n&amp;quot;, &amp;amp;P, &amp;amp;Q);&lt;br /&gt;
  for (i=1; i&amp;lt;P; fscanf(F, &amp;quot;%d&amp;quot;, &amp;amp;X[i++]));&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void FindSum(void)&lt;br /&gt;
{ long S=0;&lt;br /&gt;
  FILE *F = fopen(&amp;quot;output.txt&amp;quot;, &amp;quot;wt&amp;quot;);&lt;br /&gt;
  int i,j;&lt;br /&gt;
&lt;br /&gt;
  for (i=1, L[0]=0; i&amp;lt;P; L[i++] = None);&lt;br /&gt;
  i=0;&lt;br /&gt;
  while ( L[ (S+=X[++i]) % P ]==None &amp;amp;&amp;amp; // Restul 0&lt;br /&gt;
          L[ (S%P+P-Q) % P ]==None )    // Restul Q&lt;br /&gt;
    L[S%P]=i;&lt;br /&gt;
  for (j = 1 + ((L[S%P]!=None)? L[S%P]: L[ (S%P+P-Q) % P ]);&lt;br /&gt;
       j &amp;lt;= i; fprintf(F, &amp;quot;%d &amp;quot;, j++));&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  FindSum();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 16 ==&lt;br /&gt;
&lt;br /&gt;
Următoarele probleme aparțin categoriei de probleme pe care, dacă ne grăbim, le putem clasifica drept „ușoare”. Într-adevăr, ele au soluții vizibile și foarte la îndemână, dar și soluții mai subtile și mult mai performante. Pentru a obliga cititorul să se gândească și la aceste soluții, am ales limite pentru datele de intrare suficient de mari încât să facă nepractice rezolvările „la minut”.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: (Generarea unui arbore oarecare când i se cunosc gradele) Se dă un vector cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere întregi. Se cere să se construiască un arbore cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; noduri numerotate de la 1 la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; astfel încât gradele celor &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; noduri să fie exact numerele din vector. Dacă acest lucru nu este posibil, se va da un mesaj de eroare corespunzător.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Datele de intrare se află în fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt;. Pe prima linie se află numărul de noduri &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 10000), iar pe a doua linie se află cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere separate prin spații. Toate numerele sunt strict pozitive și mai mici ca 10000.&lt;br /&gt;
&lt;br /&gt;
Ieșirea se va face în fișierul &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;. Dacă problema are soluție, se va tipări arborele prin muchiile lui. Fiecare muchie se va lista pe câte o linie, prin nodurile adiacente separate printr-un spațiu. Dacă problema nu are soluție, se va afișa un mesaj corespunzător.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemple&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;6&amp;lt;br&amp;gt;1 2 3 2 1 1&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;1 4&amp;lt;br&amp;gt;2 5&amp;lt;br&amp;gt;3 6&amp;lt;br&amp;gt;4 6&amp;lt;br&amp;gt;5 6&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;3&amp;lt;br&amp;gt;2 2 1&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;Problema nu are solutie!&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 30 minute - 45 minute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 2-3 secunde.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;; dacă vectorul citit la intrare se presupune sortat, se cere o complexitate &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Să începem prin a ne pune întrebarea: când are problema soluție și când nu?&lt;br /&gt;
&lt;br /&gt;
Se știe că un arbore oarecare cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; noduri are &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 muchii. Fiecare din aceste muchii va contribui cu o unitate la gradele nodurilor adiacente. Deducem de aici că suma gradelor tuturor nodurilor este egală cu dublul numărului de muchii, adică, notând cu &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[1], &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[2], ..., &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;] gradele nodurilor,&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;\sum_{i = 1}^{N} G[i] = 2 \cdot (N - 1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Am aflat deci o condiție necesară pentru ca problema să aibă soluție. O a doua condiție este ca toate nodurile să aibă grade cuprinse între 1 și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1. Totuși, ținând cont de afirmația enunțului că toate numerele din vector sunt strict pozitive, rezultă că a doua condiție nu mai trebuie verificată. Iată de ce: să presupunem că am verificat prima condiție și am constatat că suma celor &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere este 2(&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1), iar unul dintre numere este cel puțin &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;. Atunci ar rezulta că suma celorlalte &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 numere este cel mult &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-2, de unde rezultă că există cel puțin un nod de grad 0, ceea ce contrazice informația din enunț. Prin urmare, numai prima condiție este importantă, cea de-a doua fiind redundantă.&lt;br /&gt;
&lt;br /&gt;
Vom demonstra că această condiție este și suficientă indicând efectiv modul de construcție a arborelui în cazul în care ea este satisfăcută. Începem prin a sorta vectorul de numere. Acest lucru era oricum de așteptat, deoarece complexitatea &amp;lt;math&amp;gt;N \log N&amp;lt;/math&amp;gt; ne-o permite. Trebuie numai să avem grijă să alegem un algoritm de sortare de complexitate &amp;lt;math&amp;gt;N \log N&amp;lt;/math&amp;gt;. Programul care urmează folosește heapsort-ul. Odată ce am sortat vectorul, trebuie să reconstituim muchiile în timp liniar, și iată cum:&lt;br /&gt;
&lt;br /&gt;
* Se poate demonstra că primele două elemente din vectorul sortat au valoarea 1. Într-adevăr, dacă toate elementele ar fi mai mari sau egale cu 2, atunci suma lor ar fi mai mare sau egală cu 2&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, ori noi știm că suma trebuie să fie 2&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-2, adică există cel puțin două elemente egale cu 1 în vector. Acest lucru rezultă imediat dacă ne gândim că orice arbore are cel puțin două frunze.&lt;br /&gt;
* Vom căuta în vector primul număr mai mare sau egal cu 2. Se pune întrebarea: există întotdeauna acest număr? Nu cumva există un arbore în care toate nodurile au grad 1? Să aplicăm condiția precedentă și să vedem ce se întâmplă. Dacă toate nodurile au grad 1, atunci suma gradelor este &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, ceea ce conduce la ecuația:&lt;br /&gt;
:&amp;lt;math&amp;gt;N = 2 \cdot (N - 1) \implies N = 2&amp;lt;/math&amp;gt;&lt;br /&gt;
* Iată deci că există un singur arbore în care toate nodurile sunt frunze, anume cel cu 2 noduri unite printr-o muchie. Vom reveni mai târziu la acest caz particular. Deocamdată presupunem că există în vector un număr mai mare ca 1, pe poziția &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; în vector. Atunci vom uni nodul 1 din arbore (care știm că are gradul 1) cu nodul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;. În felul acesta, nodul 1 și-a completat numărul necesar de vecini și poate fi neglijat pe viitor, iar &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;] va fi decrementat cu o unitate, întrucât nodul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; și-a completat unul din vecini. Astfel, problema s-a redus la un arbore cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 noduri numerotate de la 2 la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
* Vectorul &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039; este în continuare sortat, deoarece &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1] = 1 &amp;lt; G[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;] ≤ &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+1] înainte de decrementarea lui &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;], deci după decrementare vom avea &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1] = 1 ≤ &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;] &amp;lt; &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+1], adică dubla relație de ordonare se păstrează.&lt;br /&gt;
* Întrucât secvența &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[2], &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[3], ..., &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;] reprezintă gradele unui arbore, putem aplica același raționament ca mai înainte pentru a deduce că &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[2]=1. Cu ce nod vom uni nodul 2? Dacă &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;]&amp;gt;1, îl vom uni cu nodul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;. Dacă prin decrementare, &#039;&#039;&#039;&#039;&#039;G&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;] a ajuns la valoarea 1, vom trece la nodul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+1 (despre care știm că are gradul mai mare ca 1) și vom trasa muchia 2 ↔ (&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+1).&lt;br /&gt;
* Procedeul acesta se repetă până când au fost trasate &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-2 muchii. Aceasta înseamnă că a mai rămas o singură muchie de trasat. Iată deci că, mai devreme sau mai târziu, este oricum inevitabil să ajungem la cazul particular de arbore de care am amintit mai devreme. Deoarece la primul pas am unit nodul 1 cu nodul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;, la al doilea pas am unit nodul 2 cu un alt nod (&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; sau &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+1) ș.a.m.d., rezultă că în &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-2 iterații, toate nodurile de la 1 la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-2 și-au completat numărul de vecini. De aici rezultă că ultima muchie pe care o vom trasa este (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1) ↔ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;; putem să tipărim această muchie „cu ochii închiși”, fără nici un fel de teste suplimentare. Ultima muchie trasată este diferită de celelalte și necesită o operație separată de trasare din cauză că, în timp ce primele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-2 iterații uneau o frunză cu un nod intern, această ultimă iterație are de unit două frunze, deci nu are sens să mai căutăm un nod de grad mai mare ca 1.&lt;br /&gt;
&lt;br /&gt;
Aceasta este metoda de lucru. Calculul complexității este simplu: Avem nevoie doar de doi indici: Unul care marchează frunza curentă (în program el se numește pur și simplu &amp;lt;tt&amp;gt;i&amp;lt;/tt&amp;gt;) și care avansează la fiecare pas, și unul care marchează primul număr mai mare ca 1 din vector (în program se numește &amp;lt;tt&amp;gt;First&amp;lt;/tt&amp;gt;) și care se incrementează cu cel mult 1 la fiecare pas (deci de mai puțin de &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ori în total). De aici rezultă complexitatea liniară a algoritmului.&lt;br /&gt;
&lt;br /&gt;
Să vedem cum se comportă această metodă pe cazul particular al exemplului 1:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig70.png]]&lt;br /&gt;
&lt;br /&gt;
Mai trebuie remarcat că soluția nu este unică. Propunem ca temă cititorului să scrie un program care să verifice în timp &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt; dacă soluția furnizată de un alt program este corectă.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
int G[10001], N;&lt;br /&gt;
long Sum;&lt;br /&gt;
FILE *OutF;&lt;br /&gt;
&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;,&amp;quot;rt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F,&amp;quot;%d\n&amp;quot;,&amp;amp;N);&lt;br /&gt;
  for (i=1, Sum=0; i&amp;lt;=N; i++)&lt;br /&gt;
    { fscanf(F,&amp;quot;%d&amp;quot;,&amp;amp;G[i]);&lt;br /&gt;
      Sum+=G[i];&lt;br /&gt;
    }&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Sift(int K, int N)&lt;br /&gt;
/* Cerne al K-lea element dintr-un heap de N elemente */&lt;br /&gt;
{ int Son;&lt;br /&gt;
&lt;br /&gt;
  /* Alege un fiu mai mare ca tatal */&lt;br /&gt;
  if (K&amp;lt;&amp;lt;1&amp;lt;=N)&lt;br /&gt;
    { Son=K&amp;lt;&amp;lt;1;&lt;br /&gt;
      if (K&amp;lt;&amp;lt;1&amp;lt;N &amp;amp;&amp;amp; G[(K&amp;lt;&amp;lt;1)+1]&amp;gt;G[(K&amp;lt;&amp;lt;1)])&lt;br /&gt;
        Son++;&lt;br /&gt;
      if (G[Son]&amp;lt;=G[K]) Son=0;&lt;br /&gt;
    }&lt;br /&gt;
    else Son=0;&lt;br /&gt;
  while (Son)&lt;br /&gt;
    { /* Schimba G[K] cu G[Son] */&lt;br /&gt;
      G[K]=(G[K]^G[Son])^(G[Son]=G[K]);&lt;br /&gt;
      K=Son;&lt;br /&gt;
      /* Alege un alt fiu */&lt;br /&gt;
      if (K&amp;lt;&amp;lt;1&amp;lt;=N)&lt;br /&gt;
        { Son=K&amp;lt;&amp;lt;1;&lt;br /&gt;
          if (K&amp;lt;&amp;lt;1&amp;lt;N &amp;amp;&amp;amp; G[(K&amp;lt;&amp;lt;1)+1]&amp;gt;G[(K&amp;lt;&amp;lt;1)])&lt;br /&gt;
            Son++;&lt;br /&gt;
          if (G[Son]&amp;lt;=G[K]) Son=0;&lt;br /&gt;
        }&lt;br /&gt;
        else Son=0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void HeapSort(void)&lt;br /&gt;
{ int i;&lt;br /&gt;
&lt;br /&gt;
  /* Construieste heap-ul */&lt;br /&gt;
  for (i=N&amp;gt;&amp;gt;1; i;) Sift(i--,N);&lt;br /&gt;
  /* Sorteaza vectorul */&lt;br /&gt;
  for (i=N; i&amp;gt;=2;)&lt;br /&gt;
    { G[1]=(G[1]^G[i])^(G[i]=G[1]);&lt;br /&gt;
      Sift(1,--i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Match(void)&lt;br /&gt;
{ int i, First=1;&lt;br /&gt;
&lt;br /&gt;
  while (G[First]==1 &amp;amp;&amp;amp; First&amp;lt;N) First++;&lt;br /&gt;
  /* Trebuie adaugata si conditia First&amp;lt;N&lt;br /&gt;
     pentru a acoperi cazul particular N=2 */&lt;br /&gt;
  for (i=1; i&amp;lt;=N-2; i++)&lt;br /&gt;
    { fprintf(OutF,&amp;quot;%d %d\n&amp;quot;, i, First);&lt;br /&gt;
      First+=(--G[First]==1);&lt;br /&gt;
    }&lt;br /&gt;
  fprintf(OutF,&amp;quot;%d %d\n&amp;quot;, N-1, N);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  OutF=fopen(&amp;quot;output.txt&amp;quot;,&amp;quot;wt&amp;quot;);&lt;br /&gt;
  if (Sum==(N-1)&amp;lt;&amp;lt;1)&lt;br /&gt;
    { HeapSort();&lt;br /&gt;
      Match();&lt;br /&gt;
    }&lt;br /&gt;
    else fputs(&amp;quot;Problema nu are solutie!\n&amp;quot;,OutF);&lt;br /&gt;
  fclose(OutF);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 17 ==&lt;br /&gt;
&lt;br /&gt;
Iată un nou exemplu de problemă care admite două rezolvări: una evidentă, dar neeficientă și una mai puțin evidentă dar cu mult mai eficientă.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Fie &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; un vector. Arborele cartezian atașat vectorului &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; este un arbore binar care se obține astfel: Dacă vectorul &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; este vid (are 0 elemente), atunci arborele cartezian atașat lui este de asemenea vid. Altfel, se selectează elementul de valoare minimă din vector și se pune în rădăcina arborelui, iar arborii cartezieni atașați fragmentelor de vector din stânga (respectiv din dreapta) elementului minim se pun în subarborele stâng, respectiv drept al rădăcinii. Iată, de exemplu, care este arborele cartezian al următorului vector cu 8 elemente:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig71.png]]&lt;br /&gt;
&lt;br /&gt;
În figură au fost încadrate prin dreptunghiuri punctate porțiunile din stânga, respectiv din dreapta elementului minim, împreună cu subarborii atașați. Trebuie observat că arborele cartezian atașat unui vector poate să nu fie unic, în cazul în care există mai multe elemente de valoare minimă. Vom impune ca o condiție suplimentară ca elementul care va fi trecut în rădăcină să fie cel mai din stânga dintre minime (cel cu indicele cel mai mic). Astfel, arborele cartezian este unic.&lt;br /&gt;
&lt;br /&gt;
Cerința problemei este ca, dându-se un vector, să i se construiască arborele cartezian.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Fișierul de intrare &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; conține pe prima linie valoarea lui &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 10000), iar pe a doua &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere naturale mai mici ca 30000, separate prin spații.&lt;br /&gt;
&lt;br /&gt;
Ieșirea se va face în fișierul text &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; sub următoarea formă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;T_1 \quad T_2 \quad T_3 \quad \dots \quad T_N&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
unde &amp;lt;math&amp;gt;T_i&amp;lt;/math&amp;gt; este indicele în vector al elementului care este părintele lui &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;] în arborele cartezian. Dacă &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;] este rădăcina arborelui, atunci &amp;lt;math&amp;gt;T_i = 0&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;: Pentru exemplul dat mai sus, fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
8&lt;br /&gt;
8 2 4 1 5 3 6 4&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
După cum reiese din figură, tatăl elementului 8 este elementul 2, adică al doilea în vector; tatăl elementului 2 este elementul 1, adică al 4-lea în vector; tatăl elementului 5 este elementul 3, adică al 6-lea în vector ș.a.m.d. Fișierul de ieșire este deci:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
2 4 2 0 6 4 8 6&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 45 minute - 1h.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 2 secunde.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Nu întâmplător s-a impus o complexitate liniară pentru rezolvarea acestei probleme. Altfel, ea ar fi trivială în &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;, prin următoarea metodă: scriem o procedură care parcurge vectorul și caută minimul, apoi se reapelează pentru bucățile de vector aflate în stânga, respectiv în dreapta minimului. Pentru a demonstra că această variantă de rezolvare are complexitate pătratică, să ne imaginăm cum s-ar comporta ea pe cazul:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;V = (N \quad N-1 \quad N-2 \quad \dots \quad 2 \quad 1)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La primul apel, procedura ar face &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; comparații pentru a parcurge vectorul (deoarece elementul minim este ultimul în vector) și s-ar reapela pentru porțiunea din vector care cuprinde primele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 elemente. La al doilea apel, ar face &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 comparații și s-ar reapela pentru primele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-2 elemente etc. În concluzie, numărul total de comparații făcute este&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;N + (N-1) + (N-2) + \cdots + 1 = \frac{N(N + 1)}{2}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
de unde rezultă complexitatea. O problemă interesantă, pe care îi vom lăsa plăcerea cititorului să o rezolve, este de a demonstra că această versiune &#039;&#039;&#039;nu poate atinge o complexitate mai bună decât &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;&#039;&#039;&#039; și de a arăta care sunt cazurile cele mai favorabile pe care se obține această complexitate.&lt;br /&gt;
&lt;br /&gt;
A doua metodă este și ea destul de ușor de înțeles și de implementat. Ceea este mai greu de acceptat este că ea are complexitate liniară, așa cum vom încerca să explicăm la sfârșit. Iată mai întâi principiul de rezolvare: vom porni cu un arbore cartezian vid și, la fiecare pas, vom adăuga câte un element al vectorului &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; la acest arbore, astfel încât structura obținută să rămână un arbore cartezian. La al &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;-lea pas, vom adăuga elementul &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;] în arbore și vom restructura arborele în așa fel încât să obținem arborele cartezian atașat primelor &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; elemente din &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;. Trebuie să ne concentrăm atenția asupra a două lucruri:&lt;br /&gt;
&lt;br /&gt;
# Cunoscând arborele cartezian atașat primelor &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;-1 vârfuri și elementul &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;], cum se obține arborele cartezian atașat primelor &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; vârfuri?&lt;br /&gt;
# Cum reușim să actualizăm de &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ori arborele astfel încât timpul total consumat să fie liniar?&lt;br /&gt;
&lt;br /&gt;
Pentru a răspunde la prima întrebare, pe lângă vectorii &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;, mai este necesară o stivă &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;, în care vom stoca elemente ale vectorului &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;. Inițial, stiva este vidă. Atunci când un nou element &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; sosește, el va fi introdus în stivă imediat după ultimul număr din stivă care are o valoare mai mică sau egală cu &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;. Toate elementele care se aflau înainte în stivă pe poziții mai mari sau egale cu poziția pe care a fost inserat &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; vor fi eliminate din stivă, iar elementul care se afla exact pe poziția lui &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; va deveni fiul stâng al lui &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;. &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; însuși va deveni fiul drept al predecesorului său în stivă. La fiecare moment, primul element din stivă este rădăcina arborelui cartezian.&lt;br /&gt;
&lt;br /&gt;
Pentru a înțelege mai bine principiul de funcționare al stivei, să analizăm mai de aproape exemplul din enunț.&lt;br /&gt;
&lt;br /&gt;
La început stiva este vidă. Primul element din V are valoarea 8, drept care îl vom pune în stivă, iar arborele cartezian va avea un singur nod:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig72.png]]&lt;br /&gt;
&lt;br /&gt;
Următorul element sosit este 2. Acesta este mai mic decât 8, deci trebuie introdus înaintea lui în stivă. El va fi deci primul element din stivă și rădăcina arborelui cartezian la acest moment. Concomitent, 8 va fi eliminat din stivă și va deveni fiul stâng al lui 2:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig73.png]]&lt;br /&gt;
&lt;br /&gt;
Se observă că arborele obținut este tocmai arborele cartezian atașat secvenței (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[1], &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[2]). Următorul element este 4, care este mai mare decât 2, deci trebuie adăugat în vârful stivei. Nici un element nu este eliminat din stivă, iar 4 devine fiul drept al lui 2:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig74.png]]&lt;br /&gt;
&lt;br /&gt;
Următorul element sosit este 1, care este mai mic decât toate numerele din stivă. Stiva se va goli, iar numărul 2 (cel peste care se va scrie 1) va deveni fiul stâng al lui 1:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig75.png]]&lt;br /&gt;
&lt;br /&gt;
Deja arborele începe să semene cu forma sa finală. Urmează elementul 5, care va fi adăugat în stivă și „atârnat” în dreapta lui 1:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig76.png]]&lt;br /&gt;
&lt;br /&gt;
Elementul 3 este mai mic&amp;lt;ref&amp;gt;Greșeală în original.&amp;lt;/ref&amp;gt; ca 1, căruia îi va deveni fiu drept, dar mai mare&amp;lt;ref&amp;gt;Greșeală în original.&amp;lt;/ref&amp;gt; ca 5, pe care îl va elimina din stivă:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig77.png]]&lt;br /&gt;
&lt;br /&gt;
Următorul număr, 6, va fi adăugat la extremitatea dreaptă a arborelui și în vârful stivei:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig78.png]]&lt;br /&gt;
&lt;br /&gt;
În sfârșit, elementul 4 va fi fiul drept al lui 3 și îl va elimina din stivă pe 6, care îi va deveni fiu stâng:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig79.png]]&lt;br /&gt;
&lt;br /&gt;
Se observă că arborele a ajuns tocmai la forma sa corectă. Trebuie acum să ne ocupăm de un detaliu de implementare. Pentru a afla poziția pe care trebuie inserat un element în stivă avem două metode: &lt;br /&gt;
&lt;br /&gt;
# Putem să căutăm în stivă de la dreapta la stânga (ar fi mai corect spus „de la vârf spre bază”) până dăm de un element mai mic decât cel de inserat; programul folosește această metodă și îi vom discuta în final eficiența.&lt;br /&gt;
# Putem face o căutare binară în stivă, întrucât elementele din stivă au valori crescătoare de la bază spre vârf (lăsăm demonstrația acestei afirmații în seama cititorului). O căutare binară într-un vector de &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; elemente poate necesita, în cazul cel mai nefavorabil, &amp;lt;math&amp;gt;\log k&amp;lt;/math&amp;gt; comparații. În cazul cel mai nefavorabil, când vectorul &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; este sortat crescător, elementele vor fi introduse pe rând în stivă și nu vor mai fi scoase, deci la fiecare pas se vor face &amp;lt;math&amp;gt;\log k&amp;lt;/math&amp;gt; comparații, unde &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; ia valori de la 1 la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;. Complexitatea care rezultă este mai slabă decât cea cerută:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
O(\log 1 + \log 2 + \cdots + \log N) = O(\sum_{k = 1}^{N} \log k) = O(\log \prod_{k = 1}^{N} k) = O(\log N!) = O(N \cdot \log N)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Acesta este unul din puținele cazuri în care căutarea binară este mai ineficientă decât cea secvențială.&lt;br /&gt;
&lt;br /&gt;
Pentru ușurința programării, sursa C de mai jos reține în stiva &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; nu valorile elementelor, ci indicii lor în vectorul &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; (deoarece aceștia sunt ceruți pentru construcția vectorului &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#define NMax 10001&lt;br /&gt;
&lt;br /&gt;
int V[NMax], /* Vectorul */&lt;br /&gt;
    T[NMax], /* Vectorul de tati */&lt;br /&gt;
    S[NMax], /* Stiva */&lt;br /&gt;
    N;       /* Numarul de elemente */&lt;br /&gt;
&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;,&amp;quot;rt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F,&amp;quot;%d\n&amp;quot;,&amp;amp;N);&lt;br /&gt;
  for (i=1; i&amp;lt;=N;)&lt;br /&gt;
    fscanf(F, &amp;quot;%d&amp;quot;, &amp;amp;V[i++]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void BuildTree(void)&lt;br /&gt;
{ int i,k,LenS=0;&lt;br /&gt;
&lt;br /&gt;
  S[0]=0; /* Pentru ca initial T[1] sa fie 0 */&lt;br /&gt;
  for (i=1; i&amp;lt;=N; i++)&lt;br /&gt;
    { /* Cauta pozitia pe care va fi inserat V[i] */&lt;br /&gt;
      k=LenS+1;&lt;br /&gt;
      while (V[S[k-1]]&amp;gt;V[i]) k--;&lt;br /&gt;
      /* Face corecturile in S si T */&lt;br /&gt;
      T[i]=S[k-1];&lt;br /&gt;
      if (k&amp;lt;=LenS) T[S[k]]=i;&lt;br /&gt;
      /* i este ultimul element din stiva, deci... */&lt;br /&gt;
      S[LenS=k]=i;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void WriteSolution(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;output.txt&amp;quot;,&amp;quot;wt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
  for (i=1; i&amp;lt;=N;)&lt;br /&gt;
    fprintf(F,&amp;quot;%d &amp;quot;,T[i++]);&lt;br /&gt;
  fprintf(F,&amp;quot;\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  BuildTree();&lt;br /&gt;
  WriteSolution();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Acum să analizăm și complexitatea acestui algoritm. În primul rând, ea nu poate fi mai bună decât &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;, pentru că aceasta este complexitatea funcțiilor de intrare și ieșire. Procedura &amp;lt;tt&amp;gt;BuildTree&amp;lt;/tt&amp;gt; se compune dintr-un ciclu &amp;lt;tt&amp;gt;for&amp;lt;/tt&amp;gt; în care se execută patru operații în timp constant și o instrucțiune repetitivă &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;. Numărul total de operații în timp constant care se execută în procedură este prin urmare &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;. Problema este: care este numărul total maxim de evaluări ale condiției logice din bucla &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt;? Aparent, bucla while se execută de &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; ori, deci numărul total de evaluări ar fi &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;. Să aruncăm totuși o privire mai atentă.&lt;br /&gt;
&lt;br /&gt;
Fiecare evaluare a condiției din bucla &amp;lt;tt&amp;gt;while&amp;lt;/tt&amp;gt; are ca efect decrementarea lui &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; și, implicit, eliminarea unui element deja existent în stivă. Pe de altă parte, fiecare element este introdus în stivă o singură dată și deci nu poate fi eliminat din stivă decât cel mult o dată. Așadar numărul maxim de elemente ce pot fi eliminate din stivă pe parcursul executării procedurii &amp;lt;tt&amp;gt;BuildTree&amp;lt;/tt&amp;gt; este &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1, deci numărul total de evaluări ale condiției este &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;. De aici rezultă că programul are complexitate liniară.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 18 ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Se dă un vector nesortat cu elemente numere reale oarecare. Considerând că vectorul ar fi sortat, se cere să se găsească distanța maximă între două elemente consecutive ale sale, fără însă a sorta efectiv vectorul.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; conține pe prima linie numărul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; de elemente din vector (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 5000). Pe următoare linie se dau numerele separate prin spații.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: Pe ecran se va tipări un mesaj de forma:&lt;br /&gt;
&lt;br /&gt;
Distanța maximă este D&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;: Pentru fișierul de intrare cu conținutul&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
4&lt;br /&gt;
5 3.2 2 3.7&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
răspunsul trebuie să fie&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Distanța maximă este 1.5&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 30 minute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 2-3 secunde.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Desigur că primul lucru la care ne gândim este să sortăm vectorul și să îl parcurgem apoi de la stânga la dreapta, căutând distanța maximă între două elemente consecutive. Complexitatea unui asemenea algoritm este &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt;. Nici această soluție nu este rea, iar la un concurs, comisiei de corectare i-ar veni destul de greu să găsească teste care să departajeze un algoritm în &amp;lt;math&amp;gt;O(N \log N)&amp;lt;/math&amp;gt; de unul liniar, chiar și pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; = 5000. Totuși, vom arăta care este algoritmul liniar; în primul rând de dragul „artei”, iar în al doilea rând pentru că nu este cu mult mai greu de implementat decât o sortare.&lt;br /&gt;
&lt;br /&gt;
Primul lucru care trebuie făcut este găsirea maximului și a minimului din vector; să notăm aceste valori cu &amp;lt;math&amp;gt;V_{\mathit{max}}&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;V_{\mathit{min}}&amp;lt;/math&amp;gt;. Aceste operații se fac în timp liniar, eventual chiar la citirea datelor din fișier. Apoi se împarte intervalul &amp;lt;math&amp;gt;[V_{\mathit{max}}, V_{\mathit{min}}&amp;lt;/math&amp;gt;&amp;lt;ref&amp;gt;Greșeală în original.&amp;lt;/ref&amp;gt; de pe axa reală în &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 intervale egale. Iată cazul exemplului din enunț, unde &amp;lt;math&amp;gt;V_{\mathit{min}} = 2&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;V_{\mathit{max}} = 5&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig80.png]]&lt;br /&gt;
&lt;br /&gt;
Lungimea fiecărui interval va fi deci de&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
D = \frac{V_{\mathit{max}} - V_{\mathit{min}}}{N - 1} \quad&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
(în cazul nostru &amp;lt;math&amp;gt;D = 1&amp;lt;/math&amp;gt;)&lt;br /&gt;
&lt;br /&gt;
De ce s-a făcut această împărțire? Dacă notăm cu &amp;lt;math&amp;gt;D_{\mathit{max}}&amp;lt;/math&amp;gt; distanța maximă pe axă între două numere vecine, adică tocmai valoarea pe care o căutăm, se poate demonstra că &amp;lt;math&amp;gt;D_{\mathit{max}} \geq D&amp;lt;/math&amp;gt;. Într-adevăr, între cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere de pe axă se formează &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 intervale. Dacă presupunem că &amp;lt;math&amp;gt;D_{\mathit{max}} &amp;lt; D&amp;lt;/math&amp;gt;, rezultă că distanța între oricare două numere consecutive de pe axă este mai mică decât &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt;. De aici deducem că distanța dintre primul și ultimul număr, adică , este mai mică decât de (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1) ori &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt;. Dar aceasta duce la relația:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
V_{\mathit{max}} - V_{\mathit{min}} &amp;lt; (N - 1) \cdot \frac{V_{\mathit{max}} - V_{\mathit{min}}}{N - 1}&lt;br /&gt;
\implies&lt;br /&gt;
V_{\mathit{max}} - V_{\mathit{min}} &amp;lt; V_{\mathit{max}} - V_{\mathit{min}}&lt;br /&gt;
&amp;lt;/math&amp;gt;,&lt;br /&gt;
&lt;br /&gt;
relație care este absurdă; demonstrația afirmației &amp;lt;math&amp;gt;D_{\mathit{max}} \geq D&amp;lt;/math&amp;gt; este completă.&lt;br /&gt;
&lt;br /&gt;
Următorul pas pe care îl avem de făcut este să parcurgem încă o dată vectorul de numere și să aflăm pentru fiecare element căruia dintre intervalele de lungime îi aparține. Și această operație se poate face în &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;. Convenim ca dacă un număr &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; se află exact la limita dintre două intervale, adică&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
X = V_{\mathit{min}} + k \cdot D, \quad 0 \leq k &amp;lt; N&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
el să fie considerat ca aparținând intervalului din dreapta. Aceasta înseamnă că elementul de valoare &amp;lt;math&amp;gt;V_{\mathit{max}}&amp;lt;/math&amp;gt; nu aparține nici unuia din cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 intervale, ci celui de-al &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-lea interval, &amp;lt;math&amp;gt;[V_{\mathit{max}}, V_{\mathit{max}} + D]&amp;lt;/math&amp;gt;. Să vedem la ce ne ajută acest lucru. Din moment ce &amp;lt;math&amp;gt;D_{\mathit{max}} \geq D&amp;lt;/math&amp;gt;, rezultă că este imposibil ca distanța maximă să se producă între două numere din același interval, deoarece distanța în cadrul aceluiași interval nu poate atinge valoarea &amp;lt;math&amp;gt;D&amp;lt;/math&amp;gt;. Este deci obligatoriu ca distanța maximă să apară între două elemente din intervale distincte. Să urmărim în figura următoare ce alte proprietăți mai au aceste numere:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig81.png]]&lt;br /&gt;
&lt;br /&gt;
Dacă &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039; sunt valorile între care diferența este maximă, este de la sine înțeles că între ele nu mai există nici un număr, deoarece se prespune că &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039; sunt consecutive în vectorul sortat. Aceasta înseamnă însă că &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; este cel mai mare număr din intervalul său, iar numărul &#039;&#039;&#039;&#039;&#039;X’&#039;&#039;&#039;&#039;&#039; nu poate exista acolo unde a fost el figurat. Analog, &#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039; este cel mai mic număr din intervalul său, iar numărul &#039;&#039;&#039;&#039;&#039;Y’&#039;&#039;&#039;&#039;&#039; nu poate exista. De fapt, în nici unul din intervalele dintre cele care le cuprind pe &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039; nu poate exista nici un număr.&lt;br /&gt;
&lt;br /&gt;
Prin urmare, diferența maximă se poate produce numai între maximul unui interval și minimul imediat următor. Următorul pas în găsirea soluției presupune aflarea pentru fiecare din cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 intervale (sau &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; dacă îl considerăm și pe ultimul, cel care nu îl conține decât pe &amp;lt;math&amp;gt;V_{\mathit{max}}&amp;lt;/math&amp;gt;) a minimului și a maximului. Și acest pas se execută în &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;, deoarece procesarea fiecărui element din vector se reduce la numai două comparări, cu minimul și cu maximul intervalului în care se încadrează el. Vor rezulta doi vectori care în program se vor numi &#039;&#039;&#039;&#039;&#039;Lo&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Hi&#039;&#039;&#039;&#039;&#039;. Iată care sunt valorile lor pentru exemplul nostru:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig82.png]]&lt;br /&gt;
&lt;br /&gt;
Deoarece în intervalul [4,5) nu se află elemente, rezultă că elementele corespunzătoare din vectorii &#039;&#039;&#039;&#039;&#039;Lo&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Hi&#039;&#039;&#039;&#039;&#039; trebuie să aibă o valoare specială care să informeze programul asupra acestui lucru. De exemplu, sursa oferită mai jos folosește următorul artificiu: inițializează vectorul &#039;&#039;&#039;&#039;&#039;Lo&#039;&#039;&#039;&#039;&#039; cu valori foarte mari (&amp;lt;math&amp;gt;V_{\mathit{max}} + 1&amp;lt;/math&amp;gt;), astfel încât orice număr „repartizat” într-un interval să modifice această valoare. Similar, vectorul &#039;&#039;&#039;&#039;&#039;Hi&#039;&#039;&#039;&#039;&#039; este inițializat cu &amp;lt;math&amp;gt;V_{\mathit{min}} - 1&amp;lt;/math&amp;gt;. Dacă pentru un interval aceste valori se păstrează până la sfârșit, putem trage concluzia că în respectivul interval nu se află nici un număr.&lt;br /&gt;
&lt;br /&gt;
În continuare, elementele vectorilor &#039;&#039;&#039;&#039;&#039;Lo&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Hi&#039;&#039;&#039;&#039;&#039; se amestecă formând un nou vector &#039;&#039;&#039;&#039;&#039;W&#039;&#039;&#039;&#039;&#039; care de data aceasta este sortat. Sortarea este foarte ușoară, pentru că nu avem decât să așezăm numerele în ordinea &#039;&#039;&#039;&#039;&#039;Lo&#039;&#039;&#039;&#039;&#039;[1], &#039;&#039;&#039;&#039;&#039;Hi&#039;&#039;&#039;&#039;&#039;[1], &#039;&#039;&#039;&#039;&#039;Lo&#039;&#039;&#039;&#039;&#039;[2], &#039;&#039;&#039;&#039;&#039;Hi&#039;&#039;&#039;&#039;&#039;[2], ..., &#039;&#039;&#039;&#039;&#039;Lo&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;], Hi[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;]. Deși la prima vedere pare că noul vector rezultat are 2&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; elemente, de fapt el are numai &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; elemente, pentru că:&lt;br /&gt;
&lt;br /&gt;
* Dacă într-un interval &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; există un singur număr, (cazul intervalelor [2,3) și [5,6)) sau există numai numere egale, atunci &#039;&#039;&#039;&#039;&#039;Lo&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;] = &#039;&#039;&#039;&#039;&#039;Hi&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;] și este suficient să copiem în &#039;&#039;&#039;&#039;&#039;W&#039;&#039;&#039;&#039;&#039; una singură dintre aceste două valori;&lt;br /&gt;
* Dacă într-un interval nu există nici un număr, putem să nu copiem nici o valoare în vectorul &#039;&#039;&#039;&#039;&#039;W&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Astfel, construcția vectorului &#039;&#039;&#039;&#039;&#039;W&#039;&#039;&#039;&#039;&#039; se poate face în timp liniar, mai exact în &amp;lt;math&amp;gt;O(2N)&amp;lt;/math&amp;gt;. Se observă că la această construcție se poate întâmpla ca unele numere să „dispară”, adică să nu fie trecute în vectorul &#039;&#039;&#039;&#039;&#039;W&#039;&#039;&#039;&#039;&#039;. De exemplu, dacă între numerele 3.2 și 3.7 ar mai fi existat un număr, 3.5, el nu ar fi fost nici minim, nici maxim pentru intervalul său, deci nu ar fi fost copiat. Totuși, trierea în acest fel a elementelor nu afectează în nici un fel soluția. În cazul nostru, nu se întâmplă să dispară nici un element, deci &#039;&#039;&#039;&#039;&#039;W&#039;&#039;&#039;&#039;&#039; = (2, 3.2, 3.7, 5).&lt;br /&gt;
&lt;br /&gt;
După ce am construit vectorul &#039;&#039;&#039;&#039;&#039;W&#039;&#039;&#039;&#039;&#039;, nu mai avem decât să-l parcurgem de la stânga la dreapta și să tipărim diferența maximă întâlnită între două numere consecutive (repetăm, vectorul &#039;&#039;&#039;&#039;&#039;W&#039;&#039;&#039;&#039;&#039; este sortat), această ultimă etapă necesitând și ea un timp liniar. Nu se poate obține o complexitate inferioară celei liniare, întrucât citirea datelor presupune ea însăși &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; operații.&lt;br /&gt;
&lt;br /&gt;
Ca un detaliu de implementare, odată ce au fost construiți vectorii &#039;&#039;&#039;&#039;&#039;Lo&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Hi&#039;&#039;&#039;&#039;&#039;, vectorul &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; nu mai este necesar, deci putem construi chiar în el vectorul &#039;&#039;&#039;&#039;&#039;W&#039;&#039;&#039;&#039;&#039;, pentru a economisi memorie.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#define NMax 5000&lt;br /&gt;
float V[NMax+1], Lo[NMax+1], Hi[NMax+1];&lt;br /&gt;
float Delta, Max, Min;&lt;br /&gt;
int N;&lt;br /&gt;
&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
/* Citeste vectorul si afla maximul si minimul */&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;, &amp;quot;rt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F, &amp;quot;%d\n&amp;quot;, &amp;amp;N);&lt;br /&gt;
  fscanf(F, &amp;quot;%f&amp;quot;, &amp;amp;V[1]);&lt;br /&gt;
  Max=Min=V[1];&lt;br /&gt;
&lt;br /&gt;
  for (i=2; i&amp;lt;=N; i++)&lt;br /&gt;
    {&lt;br /&gt;
      fscanf(F, &amp;quot;%f&amp;quot;, &amp;amp;V[i]);&lt;br /&gt;
      if (V[i]&amp;gt;Max) Max=V[i];&lt;br /&gt;
        else if (V[i]&amp;lt;Min) Min=V[i];&lt;br /&gt;
    }&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Split(void)&lt;br /&gt;
{ int i, K;&lt;br /&gt;
&lt;br /&gt;
  Delta = (Max-Min)/(N-1);&lt;br /&gt;
  /* Se initializeaza vectorii Lo si Hi */&lt;br /&gt;
  for (i=0; i&amp;lt;=N; Lo[i]=Max+1, Hi[i++]=Min-1);&lt;br /&gt;
&lt;br /&gt;
  /* Se construiesc intervalele */&lt;br /&gt;
  for (i=1; i&amp;lt;=N; i++)&lt;br /&gt;
    {&lt;br /&gt;
      K = (V[i]-Min)/Delta;&lt;br /&gt;
      if (V[i]&amp;lt;Lo[K]) Lo[K]=V[i];&lt;br /&gt;
      if (V[i]&amp;gt;Hi[K]) Hi[K]=V[i];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Rebuild(void)&lt;br /&gt;
/* Rescrie vectorul V, pentru a economisi memorie,&lt;br /&gt;
   pastrand numai capetele intervalelor */&lt;br /&gt;
{ int i, M=0;&lt;br /&gt;
&lt;br /&gt;
  for (i=0;i&amp;lt;N; i++)&lt;br /&gt;
    {&lt;br /&gt;
      if (Lo[i] != Max+1) V[++M]=Lo[i];&lt;br /&gt;
      if (Hi[i] != Min-1 &amp;amp;&amp;amp; Hi[i] != Lo[i]) V[++M]=Hi[i];&lt;br /&gt;
    }&lt;br /&gt;
  N=M;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void FindGap(void)&lt;br /&gt;
/* Acum cautarea distantei maxime se face&lt;br /&gt;
   secvential, vectorul fiind sortat */&lt;br /&gt;
{ int i;&lt;br /&gt;
  float Gap=0;&lt;br /&gt;
&lt;br /&gt;
  for (i=2; i&amp;lt;=N; i++)&lt;br /&gt;
    if (V[i]-V[i-1] &amp;gt; Gap)&lt;br /&gt;
      Gap = V[i]-V[i-1];&lt;br /&gt;
  printf(&amp;quot;Distanta maxima este %0.3f\n&amp;quot;, Gap);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  if (Max==Min)&lt;br /&gt;
    puts(&amp;quot;0&amp;quot;);&lt;br /&gt;
  else&lt;br /&gt;
    {&lt;br /&gt;
      Split();&lt;br /&gt;
      Rebuild();&lt;br /&gt;
      FindGap();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig82.png&amp;diff=14818</id>
		<title>File:Psycho-fig82.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig82.png&amp;diff=14818"/>
		<updated>2018-03-11T09:32:51Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig81.png&amp;diff=14817</id>
		<title>File:Psycho-fig81.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig81.png&amp;diff=14817"/>
		<updated>2018-03-11T09:32:35Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig80.png&amp;diff=14816</id>
		<title>File:Psycho-fig80.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig80.png&amp;diff=14816"/>
		<updated>2018-03-11T09:32:19Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig79.png&amp;diff=14815</id>
		<title>File:Psycho-fig79.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig79.png&amp;diff=14815"/>
		<updated>2018-03-11T09:32:08Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig78.png&amp;diff=14814</id>
		<title>File:Psycho-fig78.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig78.png&amp;diff=14814"/>
		<updated>2018-03-11T09:31:55Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig77.png&amp;diff=14813</id>
		<title>File:Psycho-fig77.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig77.png&amp;diff=14813"/>
		<updated>2018-03-11T09:31:43Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig76.png&amp;diff=14812</id>
		<title>File:Psycho-fig76.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig76.png&amp;diff=14812"/>
		<updated>2018-03-11T09:31:20Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig75.png&amp;diff=14811</id>
		<title>File:Psycho-fig75.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig75.png&amp;diff=14811"/>
		<updated>2018-03-11T09:31:01Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig74.png&amp;diff=14810</id>
		<title>File:Psycho-fig74.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig74.png&amp;diff=14810"/>
		<updated>2018-03-11T09:30:47Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig73.png&amp;diff=14809</id>
		<title>File:Psycho-fig73.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig73.png&amp;diff=14809"/>
		<updated>2018-03-11T09:30:27Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig72.png&amp;diff=14808</id>
		<title>File:Psycho-fig72.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig72.png&amp;diff=14808"/>
		<updated>2018-03-11T09:30:15Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig71.png&amp;diff=14807</id>
		<title>File:Psycho-fig71.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig71.png&amp;diff=14807"/>
		<updated>2018-03-11T09:29:01Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig70.png&amp;diff=14806</id>
		<title>File:Psycho-fig70.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig70.png&amp;diff=14806"/>
		<updated>2018-03-11T09:28:42Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig69.png&amp;diff=14805</id>
		<title>File:Psycho-fig69.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig69.png&amp;diff=14805"/>
		<updated>2018-03-11T09:28:30Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig68.png&amp;diff=14804</id>
		<title>File:Psycho-fig68.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig68.png&amp;diff=14804"/>
		<updated>2018-03-11T09:28:15Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Psihologia_concursurilor_de_informatic%C4%83/6_Probleme_de_concurs_2&amp;diff=14802</id>
		<title>Psihologia concursurilor de informatică/6 Probleme de concurs 2</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Psihologia_concursurilor_de_informatic%C4%83/6_Probleme_de_concurs_2&amp;diff=14802"/>
		<updated>2018-03-09T14:38:01Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;⇦ înapoi la [[Psihologia concursurilor de informatică]]&lt;br /&gt;
&lt;br /&gt;
= Capitolul VI: Probleme de concurs (7-12) =&lt;br /&gt;
&lt;br /&gt;
== Problema 7 ==&lt;br /&gt;
&lt;br /&gt;
Problema programării unui turneu de fotbal a fost dată spre rezolvare la a III-a Balcaniadă de Informatică, Varna 1995. Vom prezenta mai întâi enunțul nemodificat al problemei, după care vom adăuga câteva detalii care o vor face mai „provocatoare”.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Una din sarcinile Ministerului Sporturilor dintr-o țară balcanică este de a organiza un campionat de fotbal cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; echipe (numerotate de la 1 la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;). Campionatul constă din &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; etape (dacă &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; este impar) sau &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 etape (dacă &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; este par); orice două echipe dispută între ele un joc și numai unul. Scrieți un program care realizează o programare a campionatului.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Numărul de echipe &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; (2 ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 24) va fi dat la intrarea standard (tastatură).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039; se va face în fișierul text &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; sub forma unui tabel cu numere întregi avînd &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; linii. Al &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;-lea element din linia &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; este numărul echipei care joacă cu echipa &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; în etapa &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039; (evident, dacă &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; joacă cu &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; în etapa &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;, atunci în tabel &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; joacă cu &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; în aceeași etapă). Dacă echipa &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; este liberă în etapa &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;, atunci al &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;-lea element al liniei &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; este zero.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemple&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;3&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;2 3 0&amp;lt;br&amp;gt;&lt;br /&gt;
1 0 3&amp;lt;br&amp;gt;&lt;br /&gt;
0 1 2&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;4&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;2 3 4&amp;lt;br&amp;gt;&lt;br /&gt;
1 4 3&amp;lt;br&amp;gt;&lt;br /&gt;
4 1 2&amp;lt;br&amp;gt;&lt;br /&gt;
3 2 1&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: circa 1h 45’&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de execuție&#039;&#039;&#039;: 33 secunde&lt;br /&gt;
&lt;br /&gt;
Iată și modificările pe care le propunem pentru a aduce cu adevărat problema la nivel de concurs:&lt;br /&gt;
&lt;br /&gt;
* Limita pentru numărul de echipe este &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 200;&lt;br /&gt;
* &#039;&#039;&#039;Timpul de implementare&#039;&#039;&#039; este de 45 minute;&lt;br /&gt;
* &#039;&#039;&#039;Timpul de execuție&#039;&#039;&#039; este de 2-3 secunde;&lt;br /&gt;
* &#039;&#039;&#039;Complexitatea cerută&#039;&#039;&#039; este &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Vom lăsa pe seama cititorului conceperea, implementarea și testarea unei rezolvări backtracking (adoptată de majoritatea în timpul concursului). De altfel, la Balcaniadă concurenții au avut la dispoziție calculatoare 486 / 50 Mhz, iar un backtracking îngrijit funcționa cam până la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; = 18 în timpul impus, ceea ce asigura cam 75% din punctajul maxim. În afară de aceasta, se mai puteau face și alte lucruri nu tocmai elegante, având în vedere că datele de ieșire nu erau foarte mari. Pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;&amp;gt;18, mulți concurenți lăsau programul să meargă până când termina (câteva minute bune sau chiar mai mult), apoi scriau rezultatele într-un fișier temporar. Matricea rezultată era inclusă ca o constantă în codul sursă. Programul care era dat comisiei de corectare se prefăcea că „se gândește” timp de câteva secunde, apoi scria pur și simplu matricea în fișierul de ieșire. Cu aceasta s-au luat punctaje foarte apropiate de maxim. Totuși, pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=24 un backtracking ar fi stat foarte mult pentru a găsi soluția. Tot timpul acesta, calculatorul era blocat, neputând fi folosit. De aceea, mulți concurenți nu au luat punctajul maxim, preferând să renunțe la ultimele teste și să rezolve celelalte probleme. Oricum, backtracking-ul este în acest caz o soluție pentru care raportul punctaj obținut / timp consumat este foarte convenabil.&lt;br /&gt;
&lt;br /&gt;
Există însă și o soluție care poate asigura un punctaj maxim fără bătăi de cap și fără să folosească „date preprocesate”. Ea are complexitatea &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt; și nu face decât o singură parcurgere a matricei de ieșire. Ce-i drept, autorul a pierdut cam trei ore pentru a o găsi și implementa în timp de concurs, compromițând aproape rezolvarea celorlalte două probleme din ziua respectivă, dar comisia de corectare a fost plăcut impresionată, programul fiind singurul care mergea instantaneu. Rămâne ca voi să alegeți între eleganță și eficiență...&lt;br /&gt;
&lt;br /&gt;
Dacă ținem cont și de restricțiile suplimentare propuse, rezolvarea backtracking nu mai este valabilă. În această situație, iată care este metoda care stă la baza rezolvării în timp pătratic. În primul rând se reduce cazul când &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; este impar la un caz când &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; este par, prin mărirea cu 1 a lui &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; și introducerea unei echipe fictive. În fiecare etapă se consideră că echipa care trebuia să joace cu echipa fictivă stă de fapt „pe bară”. Iată de exemplu cum se rezolvă cazul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=3:&lt;br /&gt;
&lt;br /&gt;
a) Se programează un campionat cu 4 echipe, echipa 4 fiind echipa fictivă:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Echipa || Etapa 1 || Etapa 2 || Etapa 3&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;1&#039;&#039;&#039; || 2 || 3 || 4&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;2&#039;&#039;&#039; || 1 || 4 || 3&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;3&#039;&#039;&#039; || 4 || 1 || 2&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;4&#039;&#039;&#039; || 3 || 2 || 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
b) Se neglijează ultima linie din tabel, meciurile echipei fictive nefiind importante:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Echipa || Etapa 1 || Etapa 2 || Etapa 3&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;1&#039;&#039;&#039; || 2 || 3 || 4&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;2&#039;&#039;&#039; || 1 || 4 || 3&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;3&#039;&#039;&#039; || 4 || 1 || 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
c) Peste tot unde apare cifra 4, ea este înlocuită cu 0:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Echipa || Etapa 1 || Etapa 2 || Etapa 3&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;1&#039;&#039;&#039; || 2 || 3 || 0&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;2&#039;&#039;&#039; || 1 || 0 || 3&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;3&#039;&#039;&#039; || 0 || 1 || 2&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Mai rămâne să vedem cum se tratează cazul când &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; este par. E destul de greu de dat o demonstrație matematică metodei care urmează; de fapt, nici nu există una, problema fiind rezolvată în timp de concurs prin inducție incompletă (adică s-a constatat cu creionul pe hârtie că metoda merge pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=4, 6, 8 și 10, apoi s-a scris programul care să facă același lucru și s-a constatat că merge și pentru valori mai mari). Să tratăm și aici cazul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=8, apoi să generalizăm procedeul.&lt;br /&gt;
&lt;br /&gt;
Vor fi șapte etape și, fără a reduce cu nimic generalitatea problemei, putem presupune că echipa 1 joacă pe rând cu echipele 2, 3, 4, 5, 6, 7 și 8. Deocamdată tabelul arată astfel:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Echipa || Etapa 1 || Etapa 2 || Etapa 3 || Etapa 4 || Etapa 5 || Etapa 6 || Etapa 7&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;1&#039;&#039;&#039; || 2 || 3 || 4 || 5 || 6 || 7 || 8&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;2&#039;&#039;&#039; || 1 ||   ||   ||   ||   ||   ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;3&#039;&#039;&#039; ||   || 1 ||   ||   ||   ||   ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;4&#039;&#039;&#039; ||   ||   || 1 ||   ||   ||   ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;5&#039;&#039;&#039; ||   ||   ||   || 1 ||   ||   ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;6&#039;&#039;&#039; ||   ||   ||   ||   || 1 ||   ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;7&#039;&#039;&#039; ||   ||   ||   ||   ||   || 1 ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;8&#039;&#039;&#039; ||   ||   ||   ||   ||   ||   || 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Echipa 2 are deja programat un meci cu echipa 1 în prima etapă și mai are de programat meciurile cu echipele 3, 4, 5, 6, 7 și 8 în celelalte etape. Așezarea se poate face oricum, cu condiția ca echipele 1 și 2 să nu își aleagă același partener în aceeași etapă. De exemplu, putem programa meciul 2-3 în etapa a 3-a, meciul 2-4 în etapa a 4-a, ..., iar meciul 2-8 în etapa a 2-a. Astfel am completat linia a doua a tabloului:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Echipa || Etapa 1 || Etapa 2 || Etapa 3 || Etapa 4 || Etapa 5 || Etapa 6 || Etapa 7&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;1&#039;&#039;&#039; || 2 || 3 || 4 || 5 || 6 || 7 || 8&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;2&#039;&#039;&#039; || 1 || 8 || 3 || 4 || 5 || 6 || 7&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;3&#039;&#039;&#039; ||   || 1 || 2 ||   ||   ||   ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;4&#039;&#039;&#039; ||   ||   || 1 || 2 ||   ||   ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;5&#039;&#039;&#039; ||   ||   ||   || 1 || 2 ||   ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;6&#039;&#039;&#039; ||   ||   ||   ||   || 1 || 2 ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;7&#039;&#039;&#039; ||   ||   ||   ||   ||   || 1 || 2&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;8&#039;&#039;&#039; ||   || 2 ||   ||   ||   ||   || 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Echipa 3 are programate meciurile cu 1 și 2 și mai are de programat meciurile cu echipele 4, 5, 6, 7 și 8. Pentru a nu crea conflicte (adică două echipe să nu-și aleagă același adversar), putem aplica același procedeu: pornim de la etapa a 5-a și completăm toate celulele goale ale liniei a 3-a cu numerele de la 4 la 8, mergând circular spre dreapta:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Echipa || Etapa 1 || Etapa 2 || Etapa 3 || Etapa 4 || Etapa 5 || Etapa 6 || Etapa 7&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;1&#039;&#039;&#039; || 2 || 3 || 4 || 5 || 6 || 7 || 8&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;2&#039;&#039;&#039; || 1 || 8 || 3 || 4 || 5 || 6 || 7&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;3&#039;&#039;&#039; || 7 || 1 || 2 || 8 || 4 || 5 || 6&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;4&#039;&#039;&#039; ||   ||   || 1 || 2 || 3 ||   ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;5&#039;&#039;&#039; ||   ||   ||   || 1 || 2 || 3 ||&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;6&#039;&#039;&#039; ||   ||   ||   ||   || 1 || 2 || 3&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;7&#039;&#039;&#039; || 3 ||   ||   ||   ||   || 1 || 2&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;8&#039;&#039;&#039; ||   || 2 ||   || 3 ||   ||   || 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Se folosește aceeași metodă pentru a completa și celelalte linii ale matricei. Pentru fiecare linie &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; (&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1):&lt;br /&gt;
&lt;br /&gt;
* Se caută pe linia &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; apariția valorii &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;-1;&lt;br /&gt;
* Se caută primul spațiu liber (mergând circular spre dreapta). Primul spațiu liber înseamnă prima etapă în care se poate programa meciul dintre echipele &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1 (trebuie ca ambele să fie libere în acea etapă). Se constată experimental că trebuie început de la a doua poziție liberă de după apariția valorii &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;-1;&lt;br /&gt;
* Se merge circular spre dreapta și, în căsuțele libere întâlnite se trec valorile &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1,    &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+2, ..., &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;. Concomitent, pe aceleași coloane ale liniilor &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1, &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+2, ..., &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; se trece valoarea &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Echipa || Etapa 1 || Etapa 2 || Etapa 3 || Etapa 4 || Etapa 5 || Etapa 6 || Etapa 7&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;1&#039;&#039;&#039; || 2 || 3 || 4 || 5 || 6 || 7 || 8&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;2&#039;&#039;&#039; || 1 || 8 || 3 || 4 || 5 || 6 || 7&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;3&#039;&#039;&#039; || 7 || 1 || 2 || 8 || 4 || 5 || 6&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;4&#039;&#039;&#039; || 6 || 7 || 1 || 2 || 3 || 8 || 5&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;5&#039;&#039;&#039; || 8 || 6 || 7 || 1 || 2 || 3 || 4&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;6&#039;&#039;&#039; || 4 || 5 || 8 || 7 || 1 || 2 || 3&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;7&#039;&#039;&#039; || 3 || 4 || 5 || 6 || 8 || 1 || 2&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;8&#039;&#039;&#039; || 5 || 2 || 6 || 3 || 7 || 4 || 1&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Fiecare linie a matricei poate fi parcursă in acest fel de maxim 2 ori (o dată pentru găsirea coloanei de start și o dată pentru completarea liniei), deci numărul total de celule vizitate este cel mult &amp;lt;math&amp;gt;2 \times N^2&amp;lt;/math&amp;gt;. Complexitatea pătratică este cea optimă, deoarece programul trebuie în orice caz să tipărească la ieșire circa &amp;lt;math&amp;gt;N^2&amp;lt;/math&amp;gt; numere.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#define NMax 200&lt;br /&gt;
unsigned char A[NMax+1][NMax+1];&lt;br /&gt;
int N,RealN;&lt;br /&gt;
&lt;br /&gt;
void FindNextFree(int Line,int *K)&lt;br /&gt;
/* Cauta (circular) urmatorul spatiu liber pe linia Line */&lt;br /&gt;
{ do *K=*K%(N-1)+1;&lt;br /&gt;
  while (A[Line][*K]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void MakeMatrix(void)&lt;br /&gt;
{ int i,j,k;&lt;br /&gt;
&lt;br /&gt;
  for (i=1;i&amp;lt;=N;i++)&lt;br /&gt;
    for (j=1;j&amp;lt;=N;j++) A[i][j]=0;&lt;br /&gt;
  for (i=1;i&amp;lt;N;i++)  /* Pentru fiecare echipa */&lt;br /&gt;
    { if (i==1)      /* Alege coloana de start */&lt;br /&gt;
        j=1;&lt;br /&gt;
        else { j=0;&lt;br /&gt;
               do j++; while (A[i][j]!=i-1);&lt;br /&gt;
               FindNextFree(i,&amp;amp;j);&lt;br /&gt;
               FindNextFree(i,&amp;amp;j);&lt;br /&gt;
             }&lt;br /&gt;
      for (k=i+1;k&amp;lt;=N;k++) /* Completeaza circular linia */&lt;br /&gt;
        { A[i][j]=k;&lt;br /&gt;
          A[k][j]=i;&lt;br /&gt;
          if (k&amp;lt;N) FindNextFree(i,&amp;amp;j);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void WriteMatrix(void)&lt;br /&gt;
{ int i,j;&lt;br /&gt;
  FILE *F=fopen(&amp;quot;output.txt&amp;quot;,&amp;quot;wt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
  for (i=1;i&amp;lt;=RealN;i++)&lt;br /&gt;
    { for (j=1;j&amp;lt;N;j++)&lt;br /&gt;
        fprintf(F,&amp;quot;%4d&amp;quot;,A[i][j]&amp;gt;RealN ? 0 : A[i][j]);&lt;br /&gt;
      fprintf(F,&amp;quot;\n&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  printf(&amp;quot;N=&amp;quot;);scanf(&amp;quot;%d&amp;quot;,&amp;amp;RealN);&lt;br /&gt;
  N=RealN+RealN%2; /* Se adauga 1 daca RealN e impar */&lt;br /&gt;
  MakeMatrix();&lt;br /&gt;
  WriteMatrix();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 8 ==&lt;br /&gt;
&lt;br /&gt;
Problema &#039;&#039;&#039;codului lui Pruffer&#039;&#039;&#039;&amp;lt;ref&amp;gt;Greșeală în original, corect: Prüfer&amp;lt;/ref&amp;gt; pentru arborii generali, mai exact cea a decodificării acestui cod, este genul de problemă care nu este excesiv de grea, dar care necesită un artificiu fără de care nu se poate ajunge la complexitatea optimă.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Un arbore general neorientat conex cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+2 vârfuri se poate codifica eficient printr-un vector cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere, astfel:&lt;br /&gt;
&lt;br /&gt;
* Numerotăm nodurile de la 1 la &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+2 într-o ordine oarecare;&lt;br /&gt;
* Eliminăm cea mai mică frunză (nod de grad 1) și adăugăm în vector numărul nodului de care ea aparținea;&lt;br /&gt;
* Reluăm procedeul pentru arborele rămas: tăiem cea mai mică frunză și adăugăm în vector numărul nodului de care ea aparținea;&lt;br /&gt;
* Repetăm procedeul până mai rămân doar două noduri.&lt;br /&gt;
&lt;br /&gt;
Vectorul rezultat se numește codificare Pruffer a arborelui dat. Iată un exemplu de construcție a codului Pruffer atașat arborelui din figura de mai jos: &lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig64.png]]&lt;br /&gt;
&lt;br /&gt;
Codul lui Pruffer se obține astfel:&lt;br /&gt;
&lt;br /&gt;
* Îl tăiem pe 3 și scriem 1;&lt;br /&gt;
* Îl tăiem pe 4 și scriem 5;&lt;br /&gt;
* Îl tăiem pe 6 și scriem 2;&lt;br /&gt;
* Îl tăiem pe 2 și scriem 5;&lt;br /&gt;
* Îl tăiem pe 5 și scriem 1;&lt;br /&gt;
&lt;br /&gt;
Deci codificarea este (1, 5, 2, 5, 1) (și mai rămâne muchia 1-7, lucru care este evident, deoarece 1 și 7 sunt singurele noduri care nu au fost tăiate).&lt;br /&gt;
&lt;br /&gt;
Așadar fiecărui arbore oarecare cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+2 noduri i se poate atașa un vector cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; componente numere naturale cuprinse între 1 și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+2. Se poate demonstra că funcția definită între cele două mulțimi (mulțimea arborilor și mulțimea vectorilor) este bijectivă. De aici rezultă două lucruri:&lt;br /&gt;
&lt;br /&gt;
# Există &amp;lt;math&amp;gt;(N+2)^N&amp;lt;/math&amp;gt; arbori generali cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+2 noduri.&lt;br /&gt;
# Codificarea Pruffer admite și decodificare (deoarece funcția de codificare este bijectivă). Tocmai aceasta este problema de rezolvat. Se dă un vector de &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere întregi, fiecare cuprins între 1 și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+2. Se cere să se tipărească cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+1 muchii ale arborelui  decodificat.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039; se face din fișierul text &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; care conține două linii. Pe prima linie se dă &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; (1 ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 10000), pe a doua cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere separate prin spații.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039; se va face în fișierul text &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;. Acesta va conține muchiile arborelui, câte una pe linie, o muchie fiind indicată prin vârfurile adiacente separate printr-un spațiu.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;5&amp;lt;br&amp;gt;1 5 2 5 1&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;5 2&amp;lt;br&amp;gt;&lt;br /&gt;
1 3&amp;lt;br&amp;gt;&lt;br /&gt;
4 5&amp;lt;br&amp;gt;&lt;br /&gt;
7 1&amp;lt;br&amp;gt;&lt;br /&gt;
6 2&amp;lt;br&amp;gt;&lt;br /&gt;
1 5&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 45 minute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 2-3 secunde.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Să pornim de la exemplul particular prezentat mai sus, urmând ca apoi să generalizăm algoritmul.&lt;br /&gt;
&lt;br /&gt;
Primim la intrare &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=5 (deducem că arborele are 7 noduri) și codificarea   (1, 5, 2, 5, 1). Primul element din vector este 1. Știm deci că, la primul pas, a fost eliminată o frunză al cărei părinte este nodul 1. Întrebarea este: ce număr purta respectiva frunză? În nici un caz 1, deoarece numărul 1 îl avea tatăl ei. Nici 2, nici 5 nu ar putea fi, deoarece aceste numere apar mai târziu în codificare, deci „mai este nevoie” de ele și nu pot fi încă eliminate. Rămân 3, 4, 6 și 7. Dintre acestea noi știm că a fost tăiată cea mai mică frunză. Este însă ușor de văzut că toate nodurile enumerate sunt frunze în arborele inițial (deoarece nu mai apar în vector, adică nici un alt nod nu mai este legat de ele), deci îl vom alege pe cel mai mic cu putință, adică pe 3. Rezultă că prima muchie tăiată a fost (1,3).&lt;br /&gt;
&lt;br /&gt;
Pe poziția a doua în codificare apare numărul 5. Ce număr ar putea avea frunza tăiată? 1 și 2 mai apar ulterior în codificare deci nu pot fi eliminate încă, 5 este chiar tatăl frunzei necunoscute, iar 3 a fost deja eliminat. Dintre 4, 6 și 7, frunza cu numărul cel mai mic este 4, deci următoarea muchie tăiată este (4,5).&lt;br /&gt;
&lt;br /&gt;
În continuare apare un 2. Cel  mai mic nod care nu mai apare în vector și nici nu a fost deja eliminat este 6, deci muchia este (6,2). Se observă că mai departe nu mai apare nici un 2 în codificare, de unde deducem că după tăierea nodului 6, nodul 2 a devenit frunză și va putea fi la rândul său eliminat. Cu un raționament analog, următoarele muchii eliminate sunt (2,5) și (5,1), iar singurele noduri rămase sunt 1 și 7. Arborele a fost reconstituit corect.&lt;br /&gt;
&lt;br /&gt;
Deci procedeul general este: pentru fiecare număr dintre cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; din vector, nodul care aparținea de el poartă cel mai mic număr care nu intervine ulterior în codificare și nu a fost tăiat deja. Astfel se generează &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; muchii. A &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+1-a muchie are drept capete ultimele noduri rămase netăiate.&lt;br /&gt;
&lt;br /&gt;
Acesta este algoritmul. Trebuie să ne ocupăm acum de partea de implementare.&lt;br /&gt;
&lt;br /&gt;
Pentru a testa la orice moment dacă un nod mai apare sau nu în vector, este bine să se creeze la citirea datelor un vector care să rețină numărul de apariții în codificare al fiecărui nod. Pentru exemplul dat, vectorul de apariții va fi &#039;&#039;&#039;&#039;&#039;Apar&#039;&#039;&#039;&#039;&#039;=(2,1,0,0,2,0,0), semnificând că 1 și 5 apar de câte două ori, 2 apare o singură dată, iar 3, 4, 6 și 7 nu apar deloc. La fiecare pas, când se „restaurează” o muchie, se decrementează poziția corespunzătoare tatălui în vectorul de apariții. Un număr nu mai apare ulterior în codificare dacă pe poziția sa din vectorul de apariții se află un 0.&lt;br /&gt;
&lt;br /&gt;
Astfel, o primă versiune de program ar putea fi:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
intrare: N, V[1..N].&lt;br /&gt;
construieste vectorul Apar[1..N+2]&lt;br /&gt;
pentru i de la 1 la N:&lt;br /&gt;
  cauta primul j pentru care Apar[j]=0 si j nu a fost taiat;&lt;br /&gt;
  scrie muchia (j,V[i]);&lt;br /&gt;
  Apar[j] = Apar[j]-1;&lt;br /&gt;
  marcheaza nodul j ca fiind taiat.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
După cum se observă, căutarea celui mai mic nod j se face în timp liniar, ceea ce înseamnă că algoritmul complet va necesita un timp pătratic. Pentru a-l reduce la o complexitate liniară, începem prin a observa că la fiecare pas alegem frunza cu numărul cel mai mic. Aceasta înseamnă că, în general, numărul frunzei alese spre a fi tăiată va crește la fiecare pas. Astfel, prima oară am tăiat nodul 4, apoi nodul 7. Singurul caz când va fi tăiată o frunză mai mică decât cea dinaintea ei este atunci când unui nod cu număr mic i se taie toate frunzele și devine el însuși o frunză, putănd fi tăiat. În exemplul nostru, nodul 2 nu putea fi eliminat de la început, deși avea un număr mic, deoarece mai avea atașată frunza 6. După eliminarea muchiei (6,2), nodul 2 a devenit frunză și a fost eliminat imediat.&lt;br /&gt;
&lt;br /&gt;
Putem deci păstra într-o variabilă (care în program se numește &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;) următoarea frunză care care trebuie eliminată. Dacă prin decrementarea numărului de apariții la pasul curent nu s-a creat nici un zero în vectorul &#039;&#039;&#039;&#039;&#039;Apar&#039;&#039;&#039;&#039;&#039;, sau s-a creat un zero, dar pe o poziție &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;&amp;gt;&#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;, atunci totul este bine și la pasul următor se va elimina nodul &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;. Dacă s-a creat un zero pe o poziție &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; mai mică decât &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;, rezultă că în arbore există acum două frunze, &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;, &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; fiind mai mică, deci prioritară. Atunci la pasul următor se va elimina frunza de pe poziția &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;, urmând ca peste doi pași să se revină la frunza &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;. Dacă prin eliminarea acestei frunze &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;  s-a creat un alt zero în vectorul de apariții, tot pe o poziție &#039;&#039;&#039;&#039;&#039;K’&#039;&#039;&#039;&#039;&#039; mai mică decât Next, se va trece mai întâi la acea poziție, urmând ca după aceea să se revină la poziția &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039; etc.&lt;br /&gt;
&lt;br /&gt;
La prima vedere, pare necesară menținerea unei stive în care să depunem numerele &#039;&#039;&#039;&#039;&#039;Next, K, K’&#039;&#039;&#039;&#039;&#039;... și să le scoatem din stivă pe măsură ce nodurile respective sunt eliminate. Acest lucru ar presupune în continuare un algoritm pătratic. Totuși nu este așa deoarece, odată ce am tăiat un nod și l-am marcat ca atare, putem fi siguri că nu ne vom mai intâlni cu el până la sfârșitul decodificării, deci nu mai este necesară stocarea lui. Există o pseudo-stivă care are înălțimea 2: frunza asupra căreia se operează curent și nodul care urmează, &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
În acest fel, numărul total de incrementări al variabilei &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039; nu depășește &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; (iar ea nu este niciodată decrementată), deci programul este rezolvat în timp liniar. Facem observația că algoritmul &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; este optim; nu poate exista unul mai bun deoarece există &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; muchii care trebuie tipărite.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#define NMax 10002&lt;br /&gt;
typedef int Vector[NMax];&lt;br /&gt;
&lt;br /&gt;
Vector V,Apar;&lt;br /&gt;
int N;&lt;br /&gt;
&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
{ int i;&lt;br /&gt;
  FILE *InF=fopen(&amp;quot;input.txt&amp;quot;,&amp;quot;rt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
  fscanf(InF,&amp;quot;%d&amp;quot;,&amp;amp;N);&lt;br /&gt;
  for (i=1;i&amp;lt;=N+2;Apar[i++]=0);&lt;br /&gt;
  for (i=1;i&amp;lt;=N;i++)&lt;br /&gt;
    { fscanf(InF,&amp;quot;%d&amp;quot;,&amp;amp;V[i]);&lt;br /&gt;
      Apar[V[i]]++;&lt;br /&gt;
    }&lt;br /&gt;
  fclose(InF);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Decode(void)&lt;br /&gt;
{ int Current=0,Next,i;&lt;br /&gt;
  FILE *OutF=fopen(&amp;quot;output.txt&amp;quot;,&amp;quot;wt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
  do; while (Apar[++Current]);    /* Se cauta prima frunza */&lt;br /&gt;
  Next=Current;&lt;br /&gt;
  for (i=1;i&amp;lt;=N;i++)&lt;br /&gt;
    { fprintf(OutF,&amp;quot;%d %d\n&amp;quot;,Current,V[i]);&lt;br /&gt;
      if (Current==Next) do; while (Apar[++Next]);&lt;br /&gt;
      /* Daca am ajuns la ultimul 0, mai caut unul */&lt;br /&gt;
      Apar[V[i]]--;&lt;br /&gt;
      Current=(V[i]&amp;lt;Next) &amp;amp;&amp;amp; (Apar[V[i]]==0) ? V[i] : Next;&lt;br /&gt;
      /* Daca exista o frunza mai mica decat Next, */&lt;br /&gt;
      /* ea este prioritara, altfel revin la Next */&lt;br /&gt;
    }&lt;br /&gt;
  fprintf(OutF,&amp;quot;%d %d\n&amp;quot;,Current,Next);&lt;br /&gt;
  fclose(OutF);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  Decode();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 9 ==&lt;br /&gt;
&lt;br /&gt;
Iată o problemă care necesită pentru reducerea complexității un artificiu asemănător celui din problema codului Pruffer. Ea a fost propusă în 1995 la concursul de selecție a echipelor României pentru IOI și CEOI. Deși cerința este puțin modificată pentru a nu ne izbi de dificultăți secundare, ideea generală de abordare este aceeași.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Președintele companiei X dorește să organizeze o petrecere cu angajații. Această companie are o structură ierarhică în formă de arbore. Fiecare angajat are asociat un număr întreg reprezentând măsura sociabilității sale. Pentru ca petrecerea să fie agreabilă pentru toți participanții, președintele dorește să facă invitațiile astfel încât:&lt;br /&gt;
&lt;br /&gt;
* el însuși să participe la petrecere;&lt;br /&gt;
* pentru nici un participant la petrecere să nu fie invitat și șeful lui direct;&lt;br /&gt;
* suma măsurilor sociabilităților invitaților să fie maximă.&lt;br /&gt;
&lt;br /&gt;
Se cere să se spună care este suma maximă a sociabilităților care se poate obține.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;&#039;&#039;Intrarea&#039;&#039;&#039;&#039;&#039; se face din fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; care conține trei linii de forma:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
N&lt;br /&gt;
T(1) T(2) ... T(N)&lt;br /&gt;
S(1) S(2) ... S(N)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
unde &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; este numărul de angajați ai companiei, inclusiv președintele (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 1000), &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) este numărul de ordine al șefului direct al lui &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; (dacă &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) = 0, atunci &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; este președintele), iar &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) este măsura sociabilității lui &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;. Valorile vectorului &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; sunt de tipul întreg.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: Pe ecran se va tipări suma maximă a sociabilităților ce se poate obține.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;: Pentru intrarea:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
7&lt;br /&gt;
2 5 2 5 0 4 2&lt;br /&gt;
2 5 3 13 8 4 3&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
pe ecran se va afișa numărul 20 (fiindcă la petrecere participă 1, 3, 5, 6 și 7).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 1h - 1h 15’.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 2-3 secunde.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;. De asemenea, menționăm că s-a specificat &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 1000 numai pentru ca programul sursă de mai jos să nu se complice și să distragă atenția asupra unor lucruri neimportante. În mod normal, limita trebuia să fie &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 10000.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Vom expune mai întâi principiul de rezolvare al problemei, apoi vom aborda detaliile de implementare.&lt;br /&gt;
&lt;br /&gt;
Algoritmul are la bază programarea dinamică și necesită o parcurgere de jos în sus a arborelui, decizia pentru fiecare nod depinzând de valorile tuturor fiilor lui. Ne propunem să aflăm două caracteristici pentru fiecare nod &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
* P(k) - suma maximă a sociabilităților care se poate obține în subarborele de rădăcină &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; în cazul în care &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; participă la petrecere;&lt;br /&gt;
* Q(k) - suma maximă a sociabilităților care se poate obține în subarborele de rădăcină &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; în cazul în care &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; nu participă la petrecere;&lt;br /&gt;
&lt;br /&gt;
Dacă reușim să determinăm aceste caracteristici, nu ne rămâne decât să-l tipărim pe &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;) (&#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039; fiind rădăcina arborelui). Într-adevăr, problema cere să se determine suma maximă a sociabilităților din întregul arbore, adică din subarborele de rădăcină &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;. În plus, se mai cere ca &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039; să participe la petrecere. Rămâne de aflat cum se stabilește relația între caracteristicile unui nod și cele ale fiilor săi.&lt;br /&gt;
&lt;br /&gt;
* Dacă angajatul &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; participă la petrecere, atunci automat nici unul din subordonații săi direcți nu participă, și obținem relația:&lt;br /&gt;
: &amp;lt;math&amp;gt;P(k) = S(k) + \sum_j Q(j)&amp;lt;/math&amp;gt;, &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039; fiu al lui &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
* Dacă angajatul &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; nu participă la petrecere, atunci subordonații săi direcți pot să participe sau nu la petrecere, după cum este mai avantajos, și obținem relația:&lt;br /&gt;
: &amp;lt;math&amp;gt;Q(k) = \sum_j \max\{P(j), Q(j)\}&amp;lt;/math&amp;gt;, &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039; fiu al lui &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Problema care se pune acum este cum să facem parcurgerea arborelui într-un mod cât mai avantajos. Pseudocodul (recursiv) sub forma sa cea mai generală este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
procedura Calcul(k, P, Q)&lt;br /&gt;
  P ← S(k), Q ← 0&lt;br /&gt;
  pentru fiecare fiu j al lui k:&lt;br /&gt;
    Calcul(j, P1, Q1)&lt;br /&gt;
    P ← P + Q1&lt;br /&gt;
    Q ← Q + max (P1, Q1)&lt;br /&gt;
  intoarce P,Q&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Soluția „de bun simț” este de a construi arborele alocat dinamic. Ar apărea însă o sumedenie de dificultăți. În primul rând, arborele este general, deci nu se cunoaște numărul maxim de fii pe care îi poate avea un nod (pe cazul cel mai defavorabil, rădăcina poate avea &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 fii). Aceasta înseamnă că legătura trebuie să fie de tip tata (fiecare nod pointează la tatăl său), ceea ce complică procedura de parcurgere: nu se poate apela procedura recursiv, dintr-un nod pentru toți fiii săi, deoarece nu se cunosc fiii, ci numai tatăl! O altă modalitate de alocare a arborelui ar fi cu doi pointeri pentru fiecare nod: unul către primul său fiu și unul către fratele său din dreapta (exemplul din enunț are reprezentate grafic mai jos cele două metode de construcție).&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig65.png]]&lt;br /&gt;
&lt;br /&gt;
Probabil că veți fi de acord cu mine că e riscant să te aventurezi la o asemenea implementare în timp de concurs, deoarece lucrul cu pointeri presupune o atenție deosebită. Greșelile sunt mai greu de observat și de multe ori duc la blocarea calculatorului, care trebuie resetat mereu, pierzându-se astfel o mulțime de timp. Trebuie deci căutată o metodă de parcurgere a arborelui care să nu necesite o alocare dinamică a memoriei. Putem încerca astfel: inițial &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;)=&#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;) și &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;)=0 pentru orice nod. Urmează acum să tratăm pe rând fiecare nod. Cum ? Știm că pentru fiecare nod &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;, numerele &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) și &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) intervin în expresia lui &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)) și &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)). Uitându-ne la formulele de mai sus, observăm că tot ce avem de făcut este să incrementăm &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)) cu &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) și &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)) cu max{&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;),&#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)}.&lt;br /&gt;
&lt;br /&gt;
Există o singură problemă: Pentru a putea folosi numerele &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) și &#039;&#039;&#039;&#039;&#039;Q&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;) trebuie să ne asigurăm că ele au fost deja calculate corect, în funcție de caracteristicile tuturor fiilor lui &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;. În cazul frunzelor, problema este rezolvată, deoarece ele nu au fii. Pentru nodurile interne, este necesar să știm câți fii au ele și câți din aceștia au fost tratați. În momentul în care toți fiii unui nod au fost tratați, poate fi tratat și nodul în sine. În program, vectorul &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039; reține numărul de fiii netratați ai fiecărui nod. La tratarea unui nod &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; se face decrementarea lui &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)). Un nod &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; poate fi ales spre tratare dacă F(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)=0. Se observă că formatul datelor de intrare permite cu ușurință construcția vectorului &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039; (numărul de fii al lui &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; este egal cu numărul de apariții al lui &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; în vectorul T).&lt;br /&gt;
&lt;br /&gt;
Un algoritm mai ușor de implementat ar fi:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
numara fiii fiecarui nod&lt;br /&gt;
repeta de N-1 ori:&lt;br /&gt;
  cauta un nod k cu F(k)=0&lt;br /&gt;
  P(T(k)) ← P(T(k))+Q(k)&lt;br /&gt;
  Q(T(K)) ← Q(T(K))+max{P(k),Q(k)}&lt;br /&gt;
  F(T(K)) ← F(T(K))-1&lt;br /&gt;
scrie P(R)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Partea delicată a acestui algoritm este căutarea unui nod &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; cu &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)=0. Dacă ea se face secvențial, pornind de fiecare dată de la primul nod și cercetând fiecare element, programul care rezultă are complexitatea &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;. Această căutare trebuie deci optimizată, și iată cum: În principiu, putem căuta nodurile cu &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)=0 mergând numai în sensul crescător al indicilor în vector. Vom folosi, ca și la problema codului lui Pruffer, două variabile: &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;. &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; este nodul tratat în prezent, iar &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039; este nodul care urmează a fi tratat la pasul următor, așadar &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039; &amp;gt; &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;. După tratarea nodului &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; și decrementarea lui &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)), pot surveni trei situații:&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;))&amp;gt;0 și în vectorul &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039; nu a apărut nici un alt element zero, caz în care nimic nu se schimbă, algoritmul continuând cu tratarea nodului &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;;&lt;br /&gt;
# &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;))=0 și &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)&amp;gt;&#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;, caz în care de asemenea se poate trata nodul &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039; (deoarece selectarea nodurilor cu &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)=0 se face în ordine crescătoare a indicilor);&lt;br /&gt;
# &#039;&#039;&#039;&#039;&#039;F&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;))=0 și &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;)&amp;lt;&#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;, caz în care se va trata mai întâi nodul &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;), urmând a se reveni apoi la nodul &#039;&#039;&#039;&#039;&#039;Next&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Pentru motivele explicate la codul lui Pruffer, acest algoritm nu necesită menținerea unei stive, iar timpul total de căutare este &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;. Facem observația că nu se poate găsi un algoritm mai bun, deoarece trebuie parcurse toate cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; noduri ale arborelui.&lt;br /&gt;
&lt;br /&gt;
Iată în încheiere cum arată arborele cu valorile P și Q atașate fiecărui nod:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig66.png]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#define NMax 1000&lt;br /&gt;
typedef long Vector[NMax+1];&lt;br /&gt;
&lt;br /&gt;
Vector T,S,P,Q,F; /* T = Vectorul de tati,&lt;br /&gt;
                     S = sociabilitatile,&lt;br /&gt;
                     P,Q = caracteristicile,&lt;br /&gt;
                     F = numarul de fii */&lt;br /&gt;
int N,Root;&lt;br /&gt;
&lt;br /&gt;
void InitData(void)&lt;br /&gt;
{ int i;&lt;br /&gt;
  FILE *InF=fopen(&amp;quot;input.txt&amp;quot;,&amp;quot;rt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
  fscanf(InF,&amp;quot;%d&amp;quot;,&amp;amp;N);&lt;br /&gt;
  for (i=1;i&amp;lt;=N+1;F[i++]=0);&lt;br /&gt;
  for (i=1;i&amp;lt;=N;i++)&lt;br /&gt;
    { fscanf(InF,&amp;quot;%d&amp;quot;,&amp;amp;T[i]);&lt;br /&gt;
      if (T[i]) F[T[i]]++; else Root=i;&lt;br /&gt;
    }&lt;br /&gt;
  for (i=1;i&amp;lt;=N;i++) fscanf(InF,&amp;quot;%d&amp;quot;,&amp;amp;S[i]);&lt;br /&gt;
  fclose(InF);&lt;br /&gt;
&lt;br /&gt;
  for (i=1;i&amp;lt;=N;P[i++]=S[i]);&lt;br /&gt;
  for (i=1;i&amp;lt;=N;Q[i++]=0);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void FindNext(int *K,int *Next)&lt;br /&gt;
{ F[T[*K]]--;&lt;br /&gt;
  F[*K]=-1;&lt;br /&gt;
  if ((F[T[*K]]&amp;gt;0) || (T[*K]&amp;gt;*Next))&lt;br /&gt;
    { *K=*Next;&lt;br /&gt;
      while (F[*Next]) (*Next)++;&lt;br /&gt;
    }&lt;br /&gt;
    else *K=T[*K];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void TraverseTree(void)&lt;br /&gt;
{ int K=0,Next,i;&lt;br /&gt;
&lt;br /&gt;
  do; while (F[++K]);&lt;br /&gt;
  Next=K; do; while (F[++Next]);&lt;br /&gt;
  for (i=1;i&amp;lt;N;i++)&lt;br /&gt;
    { P[T[K]]+=Q[K];&lt;br /&gt;
      Q[T[K]]+=(P[K]&amp;gt;Q[K]) ? P[K] : Q[K];&lt;br /&gt;
      FindNext(&amp;amp;K,&amp;amp;Next);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  InitData();&lt;br /&gt;
  TraverseTree();&lt;br /&gt;
  printf(&amp;quot;%ld\n&amp;quot;,P[Root]);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 10 ==&lt;br /&gt;
&lt;br /&gt;
Această problemă a fost dată la unul din barajele pentru selecționarea lotului restrâns al României pentru Olimpiada Internațională din 1996.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Fie un număr prim &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. Pe mulțimea {0, 1, ..., &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1} se definesc operațiile binare +, -, ×, / modulo &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;, în felul următor:&lt;br /&gt;
&lt;br /&gt;
# &#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;+&#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; modulo &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; este restul împărțirii lui &#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;+&#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; la &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. Analog pentru &#039;×&#039;.&lt;br /&gt;
# Expresia &#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;-&#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; este definită ca fiind soluția ecuației &amp;lt;math&amp;gt;b+x \equiv a \pmod{P}&amp;lt;/math&amp;gt;. Analog pentru &#039;/&#039;.&lt;br /&gt;
&lt;br /&gt;
Se știe că ecuația &amp;lt;math&amp;gt;b+x=a&amp;lt;/math&amp;gt; are întotdeauna soluție unică, iar &amp;lt;math&amp;gt;b \times x=a&amp;lt;/math&amp;gt; are soluție unică pentru orice &#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; ≠ 0. Pentru &#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; = 0, operația &#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;/&#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; nu e definită. De exemplu, dacă &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;=11, atunci 6+7=2, 6-7=10, 6*7=9, 6/7=4. Se știe că adunarea și înmulțirea modulo &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; sunt comutative, asociative, posedă elemente neutre (pe 0, respectiv pe 1), iar adunarea este distributivă față de înmulțire. În plus, pentru orice &#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039; există &#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; astfel încât &#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;+&#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039;=0; notând &#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; cu (-&#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;) avem &#039;&#039;&#039;&#039;&#039;c&#039;&#039;&#039;&#039;&#039;-&#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;=&#039;&#039;&#039;&#039;&#039;c&#039;&#039;&#039;&#039;&#039;+(-&#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;)=&#039;&#039;&#039;&#039;&#039;c&#039;&#039;&#039;&#039;&#039;+&#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; pentru orice &#039;&#039;&#039;&#039;&#039;c&#039;&#039;&#039;&#039;&#039;. De asemenea, pentru orice &#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039; ≠ 0 există &#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; astfel încât &#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039; × &#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039;=1; notând &#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039; cu (1/&#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;) avem că &#039;&#039;&#039;&#039;&#039;c&#039;&#039;&#039;&#039;&#039;/&#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;=&#039;&#039;&#039;&#039;&#039;c&#039;&#039;&#039;&#039;&#039;(1/&#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;)=&#039;&#039;&#039;&#039;&#039;c&#039;&#039;&#039;&#039;&#039; × &#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Dându-se un număr prim &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;, un întreg &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039; între 0 și &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1 și un șir de &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere, cuprinse fiecare între 0 și &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1, se cere să se introducă între elementele șirului operatorii +, -, ×, / și parantezele corespunzătoare, astfel încât să se obțină o expresie corectă a cărei valoare să fie &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039; (lucrând în aritmetica modulo &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;). În caz că acest lucru nu este posibil, se va afișa un mesaj corespunzător.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; conține două linii:&lt;br /&gt;
&lt;br /&gt;
* pe prima linie se găsesc trei numere întregi: &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;, &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;, separate prin spații (2≤&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;≤ 23, &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; prim, 1 ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 30, 0 ≤ &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039; ≤ &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1);&lt;br /&gt;
* pe următoarea linie se găsește șirul de numere ce formează expresia (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere întregi cuprinse între 0 și &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: Fișierul text &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; va conține o singură linie pe care se va găsi expresia generată sau mesajul „Nu există soluție.”. Expresia va fi parantezată complet (fiecare operator va avea o pereche de paranteze atașate).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemple&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;11 3 6&amp;lt;br&amp;gt;4 7 9&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;(4+(7/9))&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;11 3 7&amp;lt;br&amp;gt;1 1 1&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;Nu exista solutie.&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de execuție&#039;&#039;&#039; pentru un test: 1 minut.&lt;br /&gt;
&lt;br /&gt;
Modificările și completările propuse de autor sunt:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Timp de execuție&#039;&#039;&#039;: 10 secunde&lt;br /&gt;
* &#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 1h 30 minute, maxim 1h 45 minute.&lt;br /&gt;
* &#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N^3 \times P^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Problema în sine nu este foarte complicată. Ea face apel la programarea dinamică, dar este foarte asemănătoare cu una din problemele bine cunoscute de elevi - înmulțirea optimă a unui șir de matrice. Ceea ce o face mai „provocatoare” este timpul alocat implementării. De fapt, la respectiva probă au fost propuse trei probleme, cam de același nivel de dificultate, iar timpul total permis a fost de 4 ore. De asemenea, structurile de date impuse și modul de utilizare a lor sunt mai rar întâlnite.&lt;br /&gt;
&lt;br /&gt;
Ideea de la care se pornește în rezolvarea acestei probleme este următoarea: Parantezarea și completarea cu operatori a unui șir de &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; numere, &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;=(&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(2), ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;)), astfel încât să se obțină rezultatul &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039; este posibilă dacă și numai dacă există două valori &amp;lt;math&amp;gt;D_1&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;D_2&amp;lt;/math&amp;gt;, un număr întreg &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; cuprins între 1 și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 și un operator &amp;lt;math&amp;gt;\oplus \in \{ +, -, \times, / \}&amp;lt;/math&amp;gt; astfel încât următoarele condiții să fie îndeplinite simultan:&lt;br /&gt;
&lt;br /&gt;
# Este posibilă parantezarea și completarea cu operatori a șirului &#039;&#039;&#039;&#039;&#039;V’&#039;&#039;&#039;&#039;&#039;=(&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(2), ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;)) astfel încât să se obțină rezultatul &amp;lt;math&amp;gt;D_1&amp;lt;/math&amp;gt;;&lt;br /&gt;
# Este posibilă parantezarea și completarea cu operatori a șirului &#039;&#039;&#039;&#039;&#039;V’’&#039;&#039;&#039;&#039;&#039;=(&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+1), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+2), ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;)) astfel încât să se obțină rezultatul &amp;lt;math&amp;gt;D_2&amp;lt;/math&amp;gt;;&lt;br /&gt;
# &amp;lt;math&amp;gt;D_1 \oplus D_2 = D&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cu alte cuvinte, trebuie să găsim un loc în care să „spargem” expresia noastră în așa fel încât, luând două valori care se pot obține pentru partea din stânga, respectiv din dreapta, și inserând între ele operatorul potrivit, să obținem valoarea &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Pentru aflarea operatorului, a locului de împărțire a expresiei în două și a celor două valori necesare, ar fi de ajuns patru instrucțiuni repetitive for. Totuși, rămâne o singură întrebare: cum se poate verifica dacă se poate sau nu obține valoarea &amp;lt;math&amp;gt;D_1&amp;lt;/math&amp;gt; pentru subexpresia din stânga, respectiv valoarea &amp;lt;math&amp;gt;D_2&amp;lt;/math&amp;gt; pentru subexpresia din dreapta? Aceasta este o subproblemă similară cu problema în sine, dar redusă la dimensiuni mai mici. O abordare directă ar fi comodă: se scrie o procedură care efectuează cele patru instrucțiuni for și, pentru fiecare combinație posibilă de operatori și operanzi, se reapelează recursiv ca să afle dacă valorile operanzilor se pot obține. Totuși, este intuitiv că această variantă va avea o complexitate uriașă, care o face inutilizabilă.&lt;br /&gt;
&lt;br /&gt;
Motivul principal al nerentabilității acestei implementări este că ea reface de nenumărate ori exact aceleași calcule. Spre exemplu, dacă &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=4, programul va încerca să spargă vectorul (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(2), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(3), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(4)) în două părți. Există trei moduri posibile:&lt;br /&gt;
&lt;br /&gt;
* (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1)) și (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(2), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(3), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(4));    (a)&lt;br /&gt;
* (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(2)) și (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(3), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(4));    (b)&lt;br /&gt;
* (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(2), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(3)) și (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(4));    (c)&lt;br /&gt;
&lt;br /&gt;
Pentru a studia cazul (c), expresia stângă va trebui la rândul ei ruptă în două bucăți, lucru care poate fi făcut în două moduri:&lt;br /&gt;
&lt;br /&gt;
* (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1)) și (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(2), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(3));    (d)&lt;br /&gt;
* (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(2)) și (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(3));    (e)&lt;br /&gt;
&lt;br /&gt;
Se observă că deja secvența (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(2)) a fost studiată de două ori (în cazurile (b) și (e)), iar secvența (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(1)) tot de două ori (în cazurile (a) și (d)). Exemplele pot continua. Dacă însă am reuși să nu mai evaluăm de două ori aceeași secvență, complexitatea programului s-ar reduce foarte mult. Pentru aceasta, trebuie să pornim cu secvențe foarte scurte (întâi cele de un singur număr, apoi cele de două numere), și să trecem la secvențe mai lungi, bazându-ne pe faptul că secvențele mai lungi se descompun în secvențe mai scurte care au fost deja analizate. Facem mențiunea că o secvență cu un singur număr poate fi parantezată într-un singur fel (practic nu este nevoie de paranteze și operatori) și poate produce un singur rezultat, egal cu valoarea numărului. O secvență de două numere poate produce maxim patru rezultate distincte, prin folosirea pe rând a celor patru operatori disponibili.&lt;br /&gt;
&lt;br /&gt;
Pentru a stoca rezultatele obținute, vom folosi o matrice tridimensională &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039; de dimensiuni &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;x&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;x&#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;. &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;r&#039;&#039;&#039;&#039;&#039;] indică dacă există vreo parantezare corespunzătoare a secvenței (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(i), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(i+1), ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(j)) astfel încât să se obțină rezultatul &#039;&#039;&#039;&#039;&#039;r&#039;&#039;&#039;&#039;&#039;. Dacă o asemenea parantezare există, &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;r&#039;&#039;&#039;&#039;&#039;] va indica punctul în care trebuie spartă în două expresia (printr-un număr între &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;-1). Dacă nu există o asemenea parantezare, &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;r&#039;&#039;&#039;&#039;&#039;] va lua o valoare specială (0 de exemplu).&lt;br /&gt;
&lt;br /&gt;
De aici decurge modul de inițializare al matricei:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;)]=&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,    ∀ 1 ≤ &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; ≤ N&lt;br /&gt;
* &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;r&#039;&#039;&#039;&#039;&#039;]=0,    pentru orice alte valori ale lui &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;, &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;r&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Matricea se va completa pe diagonală, începând de la diagonala principală și terminând în colțul de NE. Pentru a afla toate valorile care se pot obține prin parantezarea secvenței (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1), ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;)), se va împărți această expresie în două părți disjuncte, în toate modurile posibile: (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;)) și (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1), ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;)), apoi (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1)) și (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+2), ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;)) și așa mai departe până la (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1), ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;-1)) și (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;)). Fiecăreia din părțile obținute îi va corespunde în matrice un element de forma &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;] sau &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;+1,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;], cu &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; ≤ &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; &amp;lt; &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;. În orice caz, toate elementele de această formă se vor afla în matrice dedesubtul diagonalei din care face parte elementul &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;], deci pentru secvențele respective se cunosc deja toate valorile pe care le pot lua prin parantezare și introducerea operatorilor. Tot ce avem de făcut este să combinăm în toate modurile aceste valori prin inserarea fiecăruia din cei patru operatori pentru a obține toate valorile posibile ale expresiei (&#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;), &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1), ..., &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;)).&lt;br /&gt;
&lt;br /&gt;
Dacă în final &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[1,&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;]&amp;lt;&amp;gt;0, atunci problema are soluție. Vom vedea imediat și cum se reconstituie ea. Iată modul de compunere a matricei pentru exemplul din enunț (cu deosebirea că, în figurile de mai jos, &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;] nu mai este un vector cu &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; elemente, ci o mulțime de valori între 0 și &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1, această reprezentare fiind mai comodă):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
A[1,1] = \{4\} \quad A[2,2] = \{7\} \quad A[3,3] = \{9\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
A =&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
\{4\} &amp;amp; ? &amp;amp; ? \\&lt;br /&gt;
? &amp;amp; \{7\} &amp;amp; ? \\&lt;br /&gt;
? &amp;amp; ? &amp;amp; \{9\}&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Între numerele 4 și 7 plasăm cei patru operatori și obținem:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
4 + 7 &amp;amp; \equiv 0 \pmod{11} \\&lt;br /&gt;
4 - 7 &amp;amp; \equiv 4 + 4 \equiv 8 \pmod{11} \\&lt;br /&gt;
4 \times 7 &amp;amp; \equiv 6 \pmod{11} \\&lt;br /&gt;
4 / 7 &amp;amp; \equiv 4 \times 8 \equiv 10 \pmod{11} \\&lt;br /&gt;
A[1,2] &amp;amp; = \{0, 6, 8, 10\}&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Analog se procedează pentru numerele 7 și 9:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
7 + 9 &amp;amp; \equiv 5 \pmod{11} \\&lt;br /&gt;
7 - 9 &amp;amp; \equiv 7 + 2 \equiv 9 \pmod{11} \\&lt;br /&gt;
7 \times 9 &amp;amp; \equiv 8 \pmod{11} \\&lt;br /&gt;
7 / 9 &amp;amp; \equiv 7 \times 5 \equiv 2 \pmod{11} \\&lt;br /&gt;
A[2,3] &amp;amp; = \{2, 5, 8, 9\}&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
A =&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
\{4\} &amp;amp; \{0, 6, 8, 10\} &amp;amp; ? \\&lt;br /&gt;
? &amp;amp; \{7\} &amp;amp; \{2, 5, 8, 9\} \\&lt;br /&gt;
? &amp;amp; ? &amp;amp; \{9\}&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pentru a calcula A[1,3], putem grupa termenii în două moduri: Fie primul separat și ultimii doi separat, fie primii doi separat și ultimul separat. În primul caz, ultimii doi termeni - după cum s-a văzut - pot produce patru rezultate distincte (2, 5, 8 și 9). Combinând oricare din aceste rezultate cu primul termen (4) și adăugând orice operator, vor rezulta 16 valori posibile, din care evident unele vor coincide. Analog se procedează și pentru celălalt caz:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Cazul I || Cazul II&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
4 + 2 &amp;amp; \equiv 6 \pmod{11} \\&lt;br /&gt;
4 - 2 &amp;amp; \equiv 2 \pmod{11} \\&lt;br /&gt;
4 \times 2 &amp;amp; \equiv 8 \pmod{11} \\&lt;br /&gt;
4 / 2 &amp;amp; \equiv 2 \pmod{11} \\&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
0 + 9 &amp;amp; \equiv 9 \pmod{11} \\&lt;br /&gt;
0 - 9 &amp;amp; \equiv 2 \pmod{11} \\&lt;br /&gt;
0 \times 9 &amp;amp; \equiv 0 \pmod{11} \\&lt;br /&gt;
0 / 9 &amp;amp; \equiv 0 \pmod{11} \\&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
4 + 5 &amp;amp; \equiv 9 \pmod{11} \\&lt;br /&gt;
4 - 5 &amp;amp; \equiv 10 \pmod{11} \\&lt;br /&gt;
4 \times 5 &amp;amp; \equiv 9 \pmod{11} \\&lt;br /&gt;
4 / 5 &amp;amp; \equiv 3 \pmod{11} \\&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
6 + 9 &amp;amp; \equiv 4 \pmod{11} \\&lt;br /&gt;
6 - 9 &amp;amp; \equiv 8 \pmod{11} \\&lt;br /&gt;
6 \times 9 &amp;amp; \equiv 10 \pmod{11} \\&lt;br /&gt;
6 / 9 &amp;amp; \equiv 8 \pmod{11} \\&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
4 + 8 &amp;amp; \equiv 1 \pmod{11} \\&lt;br /&gt;
4 - 8 &amp;amp; \equiv 7 \pmod{11} \\&lt;br /&gt;
4 \times 8 &amp;amp; \equiv 10 \pmod{11} \\&lt;br /&gt;
4 / 8 &amp;amp; \equiv 6 \pmod{11} \\&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
8 + 9 &amp;amp; \equiv 6 \pmod{11} \\&lt;br /&gt;
8 - 9 &amp;amp; \equiv 10 \pmod{11} \\&lt;br /&gt;
8 \times 9 &amp;amp; \equiv 6 \pmod{11} \\&lt;br /&gt;
8 / 9 &amp;amp; \equiv 7 \pmod{11} \\&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
4 + 9 &amp;amp; \equiv 2 \pmod{11} \\&lt;br /&gt;
4 - 9 &amp;amp; \equiv 6 \pmod{11} \\&lt;br /&gt;
4 \times 9 &amp;amp; \equiv 3 \pmod{11} \\&lt;br /&gt;
4 / 9 &amp;amp; \equiv 9 \pmod{11} \\&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
| &amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
10 + 9 &amp;amp; \equiv 8 \pmod{11} \\&lt;br /&gt;
10 - 9 &amp;amp; \equiv 1 \pmod{11} \\&lt;br /&gt;
10 \times 9 &amp;amp; \equiv 2 \pmod{11} \\&lt;br /&gt;
10 / 9 &amp;amp; \equiv 6 \pmod{11} \\&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
A[1,3] = \{0, 1, 2, 3, 4, 6, 7, 8, 9, 10\}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
A =&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
\{4\} &amp;amp; \{0, 6, 8, 10\} &amp;amp; \{0..4, 6..10\} \\&lt;br /&gt;
? &amp;amp; \{7\} &amp;amp; \{2, 5, 8, 9\} \\&lt;br /&gt;
? &amp;amp; ? &amp;amp; \{9\}&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Se observă că singura valoare care nu se poate obține prin parantezarea șirului (4, 7, 9) este 5. Să vedem acum și care este metoda de reconstituire a soluției. Fie &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;1&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;]=X. Dacă &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;=0, atunci nu există soluție. Dacă &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; ≠ 0, atunci &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; indică poziția din vector după care trebuie inserat semnul. Nu se indică însă ce semn trebuie inserat, nici care sunt valorile care trebuie obținute pentru partea stângă, respectiv dreaptă. De aceea, vom căuta o combinație oarecare de valori &amp;lt;math&amp;gt;D_1&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;D_2&amp;lt;/math&amp;gt; care se pot obține și un operator oarecare astfel încât &amp;lt;math&amp;gt;D_1 \oplus D_2 = D&amp;lt;/math&amp;gt;. Odată ce le găsim, vom căuta, prin aceeași metodă, o modalitate de a obține valoarea &amp;lt;math&amp;gt;D_1&amp;lt;/math&amp;gt; în partea stângă a vectorului (știm sigur că această modalitate există) și o modalitate de a obține valoarea &amp;lt;math&amp;gt;D_2&amp;lt;/math&amp;gt; în partea dreaptă a vectorului.&lt;br /&gt;
&lt;br /&gt;
Iată în continuare câteva detalii de implementare. Pentru efectuarea operațiilor matematice modulo &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; s-a scris o funcție separată, &#039;&#039;&#039;&#039;&#039;Expr&#039;&#039;&#039;&#039;&#039;, care primește două numere &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039; între 0 și &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1 și un număr &#039;&#039;&#039;&#039;&#039;Op&#039;&#039;&#039;&#039;&#039; între 1 și 4 reprezentând codificarea operatorului și întoarce un număr între 0 și &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;, reprezentând valoarea operației (&#039;&#039;&#039;&#039;&#039;X Op Y&#039;&#039;&#039;&#039;&#039;). De fapt, ar trebui ca această funcție să întoarcă un număr între 0 și &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;-1 dacă operația este posibilă și să nu întoarcă nimic dacă operația este imposibilă, respectiv dacă se încearcă o împărțire la 0. Cum acest lucru nu este posibil în Pascal, am asimilat valoarea &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; cu un cod de eroare, iar funcția va întoarce această valoare (care nu poate fi atinsă prin operații obișnuite) în cazul unei împărțiri prin 0. Mai departe, pentru a nu face un test separat dacă rezultatul funcției reprezintă o adresă valabilă în cea de-a treia dimensiune a tabloului, am preferat să supradimensionăm tabloul cu o unitate și să ignorăm tot ceea ce se scrie în coloana &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Să ne ocupăm acum de operațiile aritmetice. Adunarea, scăderea și înmulțirea se fac în timp constant. O problemă apare în cazul împărțirii, deoarece ea nu mai seamănă deloc cu cea învățată pe mulțimea &amp;lt;math&amp;gt;\mathbb{R}&amp;lt;/math&amp;gt;. Pentru a calcula &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;/&#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;, trebuie găsit acel număr &#039;&#039;&#039;&#039;&#039;Z&#039;&#039;&#039;&#039;&#039; care, înmulțit cu &#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;, să dea &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;. Acest lucru se poate face într-o primă fază prin căutare secvențială (se încearcă valoarea 0, apoi 1, apoi 2 și așa mai departe; trebuie să existe un cât deoarece &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; este prim). Tehnica are însă influențe neplăcute asupra complexității, supărătoare asupra timpului de rulare și dezastruoase asupra punctajului obținut. De aceea, este bine ca, în măsura în care timpul o permite, să se construiască o tabelă predefinită de calculare a inverșilor. Atunci, în loc să se efectueze împărțirea &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;/&#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;, se efectuează înmulțirea &amp;lt;math&amp;gt;X \times Y^{-1}&amp;lt;/math&amp;gt;. Inversul unui element depinde și de modulul ales. Programul care urmează construiește un tabel care a fost importat în programul sursă ca o constantă, matricea &amp;lt;tt&amp;gt;Invers&amp;lt;/tt&amp;gt; (&amp;lt;tt&amp;gt;Invers[A,B]&amp;lt;/tt&amp;gt; este inversul lui &#039;&#039;&#039;&#039;&#039;B&#039;&#039;&#039;&#039;&#039; modulo &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;). Liniile corespunzătoare unor numere neprime au fost totuși inserate, pentru ușurința implementării.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
program Invert;&lt;br /&gt;
const NMax=30;&lt;br /&gt;
      PMax=23;&lt;br /&gt;
var i,P:Integer;&lt;br /&gt;
&lt;br /&gt;
function Invers(P,K:Integer):Integer;&lt;br /&gt;
var i:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  i:=0;&lt;br /&gt;
  repeat Inc(i) until (K*i) mod P=1;&lt;br /&gt;
  Invers:=i;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
begin&lt;br /&gt;
  Assign(Output,&#039;invers.txt&#039;);Rewrite(Output);&lt;br /&gt;
  for P:=2 to PMax do&lt;br /&gt;
    begin&lt;br /&gt;
      Write(&#039;{&#039;,P:2,&#039;} (&#039;);&lt;br /&gt;
      for i:=1 to NMax do&lt;br /&gt;
        begin&lt;br /&gt;
          if (P in [2,3,5,7,11,13,17,19,23]) and (i&amp;lt;P)&lt;br /&gt;
            then Write(Invers(P,i):2)&lt;br /&gt;
            else Write(99);&lt;br /&gt;
          if i&amp;lt;&amp;gt;NMax then Write(&#039;,&#039;);&lt;br /&gt;
          if i=NMax div 2 then Write(#13#10&#039;      &#039;);&lt;br /&gt;
        end;&lt;br /&gt;
      WriteLn(&#039;),&#039;);&lt;br /&gt;
    end;&lt;br /&gt;
  Close(Output);&lt;br /&gt;
end.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Să analizăm în sfârșit complexitatea: Trebuie completată o matrice, deci &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt; elemente. Pentru fiecare element din matrice, secvența corespunzătoare din vector trebuie spartă în două în toate modurile posibile, deci încă &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;. Pentru fiecare descompunere, trebuie combinate în toate felurile toate valorile disponibile pentru partea stângă, respectiv dreaptă. Cum numărul de valori este &amp;lt;math&amp;gt;O(P)&amp;lt;/math&amp;gt;, rezultă o complexitate de &amp;lt;math&amp;gt;O(P^2)&amp;lt;/math&amp;gt;. Înmulțind toți acești factori rezultă o complexitate totală de &amp;lt;math&amp;gt;O(N^3 \times P^2)&amp;lt;/math&amp;gt;. Dacă nu am fi făcut împărțirea a două numere modulo P în timp constant, ci în &amp;lt;math&amp;gt;O(P)&amp;lt;/math&amp;gt;, atunci complexitatea totală ar fi fost &amp;lt;math&amp;gt;O(N^3 \times P^3)&amp;lt;/math&amp;gt;, deci timpul de rulare putea fi și de 20 de ori mai mare.&lt;br /&gt;
&lt;br /&gt;
Programul de mai jos pare îngrozitor de lung, dar, dacă avem în vedere faptul că o bună bucată o reprezintă constanta &amp;lt;tt&amp;gt;Invers&amp;lt;/tt&amp;gt;, care este generată, putem avea speranțe să-l scriem în timpul alocat.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
program ParaNT;&lt;br /&gt;
{$B-,I-,R-,S-}&lt;br /&gt;
const NMax=30;&lt;br /&gt;
      PMax=23;&lt;br /&gt;
      NoWay=0;&lt;br /&gt;
      OpNames:String[4]=&#039;+-*/&#039;;&lt;br /&gt;
      Invers:array[2..PMax,1..NMax] of Integer=&lt;br /&gt;
{ 2}(( 1,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{ 3} ( 1, 2,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{ 4} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{ 5} ( 1, 3, 2, 4,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{ 6} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{ 7} ( 1, 4, 5, 2, 3, 6,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{ 8} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{ 9} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{10} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{11} ( 1, 6, 4, 3, 9, 2, 8, 7, 5,10,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{12} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{13} ( 1, 7, 9,10, 8,11, 2, 5, 3, 4, 6,12,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{14} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{15} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{16} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{17} ( 1, 9, 6,13, 7, 3, 5,15, 2,12,14,10, 4,11, 8,&lt;br /&gt;
      16,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{18} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{19} ( 1,10,13, 5, 4,16,11,12,17, 2, 7, 8, 3,15,14,&lt;br /&gt;
       6, 9,18,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{20} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{21} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{22} (99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,&lt;br /&gt;
      99,99,99,99,99,99,99,99,99,99,99,99,99,99,99),&lt;br /&gt;
{23} ( 1,12, 8, 6,14, 4,10, 3,18, 7,21, 2,16, 5,20,&lt;br /&gt;
      13,19, 9,17,15,11,22,99,99,99,99,99,99,99,99));&lt;br /&gt;
&lt;br /&gt;
type Matrix=array[1..NMax, 1..NMax, 0..PMax] of Integer;&lt;br /&gt;
       { S-a inclus si valoarea PMax, care nu poate fi&lt;br /&gt;
         atinsa, pentru a se depozita &amp;quot;deseurile&amp;quot; }&lt;br /&gt;
     Vector=array[1..NMax] of Integer;&lt;br /&gt;
var A:Matrix;       { Matricea de calcul }&lt;br /&gt;
    V:Vector;       { Numerele }&lt;br /&gt;
    N,P,D:Integer;&lt;br /&gt;
&lt;br /&gt;
procedure ReadData;&lt;br /&gt;
var i:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  Assign(Input,&#039;input.txt&#039;);Reset(Input);&lt;br /&gt;
  ReadLn(P,N,D);&lt;br /&gt;
  for i:=1 to N do Read(V[i]);&lt;br /&gt;
  Close(Input);&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
function Expr(X,Y,Op:Integer):Integer;&lt;br /&gt;
{ Calculeaza expresia (X Op Y) unde Op=1 (&#039;+&#039;),&lt;br /&gt;
  Op=2 (&#039;-&#039;), Op=3 (&#039;*&#039;), Op=4 (&#039;/&#039;). Daca Op=4&lt;br /&gt;
  si Y=0 se returneaza valoarea P (care nu poate&lt;br /&gt;
  fi atinsa prin alte operatii corecte). }&lt;br /&gt;
begin&lt;br /&gt;
  case Op of&lt;br /&gt;
    1:Expr:=(X+Y) mod P;&lt;br /&gt;
    2:Expr:=(X+P-Y) mod P;&lt;br /&gt;
    3:Expr:=(X*Y) mod P;&lt;br /&gt;
    4:if Y=0 then Expr:=P  { = imposibil }&lt;br /&gt;
             else Expr:=(X*Invers[P,Y]) mod P&lt;br /&gt;
      { S-a creat o tabela predefinita de inversi,&lt;br /&gt;
        deoarece altfel impartirea se efectua numai&lt;br /&gt;
        in O(P) }&lt;br /&gt;
  end; {case}&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure Combine(i,j,k:Integer);&lt;br /&gt;
{ Urmeaza a se combina toate valorile posibile&lt;br /&gt;
  pentru A[i,k] si A[k+1,j] pentru a se afla&lt;br /&gt;
  toate valorile posibile pentru A[i,j] }&lt;br /&gt;
var p1,p2,Op:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  for p1:=0 to P-1 do&lt;br /&gt;
    if A[i,k,p1]&amp;lt;&amp;gt;NoWay&lt;br /&gt;
      then for p2:=0 to P-1 do&lt;br /&gt;
             if A[k+1,j,p2]&amp;lt;&amp;gt;NoWay&lt;br /&gt;
               then { Am gasit doua valori posibile&lt;br /&gt;
                      si aplicam cei patru operatori }&lt;br /&gt;
                    for Op:=1 to 4 do&lt;br /&gt;
                      A[i,j,Expr(p1,p2,Op)]:=k;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure ComposeMatrix;&lt;br /&gt;
var i,j,k,l:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  { Initializarea matricei }&lt;br /&gt;
  for i:=1 to N do&lt;br /&gt;
    for j:=1 to N do&lt;br /&gt;
      for k:=0 to P-1 do A[i,j,P]:=NoWay;&lt;br /&gt;
&lt;br /&gt;
  for i:=1 to N do&lt;br /&gt;
    A[i,i,V[i]]:=1; { sau orice &amp;lt;&amp;gt; NoWay }&lt;br /&gt;
&lt;br /&gt;
  for l:=2 to N do { Lungimea intervalelor }&lt;br /&gt;
    for i:=1 to N-l+1 do&lt;br /&gt;
      begin&lt;br /&gt;
        j:=i+l-1; { S-au fixat [i,j] capetele intervalului }&lt;br /&gt;
        for k:=i to j-1 do { Se alege locul de impartire }&lt;br /&gt;
          Combine(i,j,k);&lt;br /&gt;
      end;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure SeekValues(Lo,Hi,Mid,Value:Integer;&lt;br /&gt;
                     var v1,v2:Integer; var Op:Char);&lt;br /&gt;
{ Se stie unde e &amp;quot;sparta&amp;quot; expresia in doua; se cauta&lt;br /&gt;
  valorile care trebuie obtinute pentru partea stanga,&lt;br /&gt;
  respectiv dreapta, si pentru operator }&lt;br /&gt;
var i,j,k:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  for i:=0 to P-1 do&lt;br /&gt;
    if A[Lo,Mid,i]&amp;lt;&amp;gt;NoWay&lt;br /&gt;
      then for j:=0 to P-1 do&lt;br /&gt;
             if A[Mid+1,Hi,j]&amp;lt;&amp;gt;NoWay&lt;br /&gt;
               then for k:=1 to 4 do&lt;br /&gt;
                      if Expr(i,j,k)=Value&lt;br /&gt;
                        then begin&lt;br /&gt;
                               v1:=i;&lt;br /&gt;
                               v2:=j;&lt;br /&gt;
                               Op:=OpNames[k];&lt;br /&gt;
                               Exit;&lt;br /&gt;
                             end;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure WriteExpression(Lo,Hi,Value:Integer);&lt;br /&gt;
var v1,v2,Place:Integer;&lt;br /&gt;
    Op:Char;&lt;br /&gt;
begin&lt;br /&gt;
  if Lo=Hi&lt;br /&gt;
    then Write(V[Lo])&lt;br /&gt;
    else begin&lt;br /&gt;
           Place:=A[Lo,Hi,Value];&lt;br /&gt;
           SeekValues(Lo,Hi,Place,Value,v1,v2,Op);&lt;br /&gt;
           Write(&#039;(&#039;);&lt;br /&gt;
           WriteExpression(Lo,Place,v1);&lt;br /&gt;
           Write(Op);&lt;br /&gt;
           WriteExpression(Place+1,Hi,v2);&lt;br /&gt;
           Write(&#039;)&#039;);&lt;br /&gt;
         end;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure WriteSolution;&lt;br /&gt;
begin&lt;br /&gt;
  Assign(Output,&#039;output.txt&#039;);Rewrite(Output);&lt;br /&gt;
  if A[1,N,D]=NoWay&lt;br /&gt;
    then Write(&#039;Nu exista solutie&#039;)&lt;br /&gt;
    else WriteExpression(1,N,D);&lt;br /&gt;
  WriteLn;&lt;br /&gt;
  Close(Output);&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
begin&lt;br /&gt;
  ReadData;&lt;br /&gt;
  ComposeMatrix;&lt;br /&gt;
  WriteSolution;&lt;br /&gt;
end.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
O posibilă îmbunătățire a programului de sus ar fi să reținem în &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;r&#039;&#039;&#039;&#039;&#039;] nu numai locul unde se face secționarea expresiei, ci și operatorul introdus și eventual și valorile care trebuie obținute pe partea stângă, respectiv dreaptă. Totuși, volumul de date ar fi crescut corespunzător și ar fi devenit greu de manipulat. În schimb, în versiunea prezentă, programul merge puțin mai lent, dar nesesizabil. Să vedem de ce. Complexitatea reconstituirii expresiei în sine se află astfel: avem de reconstituit &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; operatori. Pentru fiecare din ei, trebuie să căutăm valorile stângă și dreaptă și operatorul în sine, deci &amp;lt;math&amp;gt;O(4 \times P^2)&amp;lt;/math&amp;gt;. Complexitatea totală a reconstituirii datelor este &amp;lt;math&amp;gt;O(N \times P^2)&amp;lt;/math&amp;gt;, adică oricum mult mai mică față de cea a compunerii matricei. Este preferabil să nu complicăm structurile de date și codul scris, mai ales că diferența ca timp de rulare este infimă.&lt;br /&gt;
&lt;br /&gt;
Propunem ca temă cititorului o versiune a acestei probleme, în care nu se va mai lucra în inelul &amp;lt;math&amp;gt;\mathbf{Z_P}&amp;lt;/math&amp;gt;, ci într-un grup cu elementele &#039;&#039;&#039;&#039;&#039;a&#039;&#039;&#039;&#039;&#039;, &#039;&#039;&#039;&#039;&#039;b&#039;&#039;&#039;&#039;&#039;, &#039;&#039;&#039;&#039;&#039;c&#039;&#039;&#039;&#039;&#039;, &#039;&#039;&#039;&#039;&#039;d&#039;&#039;&#039;&#039;&#039;, ..., pentru care se cunoaște tabela de compoziție. În acest caz avem un singur operator &amp;lt;math&amp;gt;\oplus&amp;lt;/math&amp;gt;, iar cerința este să se parantezeze expresia &amp;lt;math&amp;gt;x_1 \oplus x_2 \oplus \cdots \oplus x_k&amp;lt;/math&amp;gt; astfel încât rezultatul să fie &#039;&#039;&#039;&#039;&#039;y&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 11 ==&lt;br /&gt;
&lt;br /&gt;
Problema celui mai lung prefix a fost dată la a VIII-a Olimpiadă Internațională de Informatică, Veszprem 1996. Iată enunțul nemodificat al problemei:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Structura unor compuși biologici este reprezentată prin succesiunea constituenților lor. Acești constituenți sunt notați cu litere mari. Biologii sunt interesați să descompună o secvență lungă în altele mai scurte, numite primitive. Spunem că o secvență &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; poate fi compusă dintr-un set de primitive &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; dacă există &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; primitive &#039;&#039;&#039;&#039;&#039;p1&#039;&#039;&#039;&#039;&#039;, ..., &#039;&#039;&#039;&#039;&#039;pN&#039;&#039;&#039;&#039;&#039; în &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; astfel încât concatenarea &#039;&#039;&#039;&#039;&#039;p1p2...pN&#039;&#039;&#039;&#039;&#039; a primitivelor să fie egală cu &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;. Aceeași primitivă poate interveni de mai multe ori în concatenare și nu trebuie neapărat ca toate primitivele să fie prezente.&lt;br /&gt;
&lt;br /&gt;
Primele &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039; caractere din &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; se numesc prefixul lui &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; de lungime &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;. Scrieți un program care primește la intrare un set de primitive &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; și o secvență de constituenți &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;. Programul trebuie sa afle lungimea celui mai lung prefix al lui &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039; care se poate compune din primitive din &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Datele de intrare&#039;&#039;&#039; apar în două fișiere. Fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; descrie setul de primitive &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;, iar fișierul &amp;lt;tt&amp;gt;DATA.TXT&amp;lt;/tt&amp;gt; conține secvența de examinat. Pe prima linie din &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; se află &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, numărul de primitive din &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; (1 ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 100). Fiecare primitivă se dă pe două linii consecutive: pe prima lungimea &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; a primitivei (1 ≤ &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; ≤ 20), iar pe a doua un șir de litere mari de lungime &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;. Toate cele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; primitive sunt distincte.&lt;br /&gt;
&lt;br /&gt;
Fiecare linie din fișierul DATA.TXT conține o literă mare pe prima poziție. El se termină cu o linie conținând un punct (&#039;.&#039;). Lungimea secvenței este cuprinsă între 1 si 500.000.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: Pe ecran se va tipări lungimea celui mai lung prefix din &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039; care poate fi compus din primitive din &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;5&amp;lt;br&amp;gt;&lt;br /&gt;
1&amp;lt;br&amp;gt;&lt;br /&gt;
A&amp;lt;br&amp;gt;&lt;br /&gt;
2&amp;lt;br&amp;gt;&lt;br /&gt;
AB&amp;lt;br&amp;gt;&lt;br /&gt;
3&amp;lt;br&amp;gt;&lt;br /&gt;
BBC&amp;lt;br&amp;gt;&lt;br /&gt;
2&amp;lt;br&amp;gt;&lt;br /&gt;
CA&amp;lt;br&amp;gt;&lt;br /&gt;
2&amp;lt;br&amp;gt;&lt;br /&gt;
BA&amp;lt;br&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;A&amp;lt;br&amp;gt;&lt;br /&gt;
B&amp;lt;br&amp;gt;&lt;br /&gt;
A&amp;lt;br&amp;gt;&lt;br /&gt;
B&amp;lt;br&amp;gt;&lt;br /&gt;
A&amp;lt;br&amp;gt;&lt;br /&gt;
C&amp;lt;br&amp;gt;&lt;br /&gt;
A&amp;lt;br&amp;gt;&lt;br /&gt;
B&amp;lt;br&amp;gt;&lt;br /&gt;
A&amp;lt;br&amp;gt;&lt;br /&gt;
A&amp;lt;br&amp;gt;&lt;br /&gt;
B&amp;lt;br&amp;gt;&lt;br /&gt;
C&amp;lt;br&amp;gt;&lt;br /&gt;
B&amp;lt;br&amp;gt;&lt;br /&gt;
.&amp;lt;br&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Pe ecran se va tipări numărul 11.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 30 secunde pentru un test.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 1h.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(S \times L \times N)&amp;lt;/math&amp;gt;, unde S este lungimea secvenței.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Menționăm de la început că datele problemei sunt supradimensionate. Nici unul din cele zece teste cu care au fost verificate programele nu a depășit în realitate 12 primitive. În schimb, fișierul &amp;lt;tt&amp;gt;DATA.TXT&amp;lt;/tt&amp;gt; a fost unic pentru toate testele și a conținut o secvență de lungime 500.000.&lt;br /&gt;
&lt;br /&gt;
Problema se rezolvă și în acest caz prin reducerea ei la una similară, dar cu date de intrare mai mici. Respectiv, un prefix &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; al lui &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039; se poate descompune în primitive dacă există o primitivă &amp;lt;math&amp;gt;p_i&amp;lt;/math&amp;gt; astfel încât &amp;lt;math&amp;gt;S=S&#039;+p_i&amp;lt;/math&amp;gt; și &#039;&#039;&#039;&#039;&#039;S’&#039;&#039;&#039;&#039;&#039; se poate descompune în primitive. Am redus împărțirea în primitive a lui &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; la despărțirea în primitive a lui &#039;&#039;&#039;&#039;&#039;S’&#039;&#039;&#039;&#039;&#039;, care are o lungime mai mică decât &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;. O primă modalitate, pur teoretică, de a rezolva problema, este să reținem toate prefixele lui &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039; care se pot descompune în primitive; în felul acesta, putem studia prefixe din ce în ce mai lungi, bazându-ne pe prefixe mai scurte deja studiate. Totuși, este imposibil să ținem minte toate prefixele lui &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;, deoarece lungimea medie a unui prefix poate atinge 250.000 caractere.&lt;br /&gt;
&lt;br /&gt;
O îmbunătățire care poate fi adusă acestui algoritm este următoarea: deoarece toate prefixele aparțin aceleiași secvențe de constituenți, &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;, este suficient să reținem în întregime secvența &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039; și, pentru fiecare prefix ce se poate descompune în primitive, păstrăm doar lungimea sa. Făcând abstracție de limitările de memorie, putem crea un vector &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; cu &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; variabile booleene (unde &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;≤500.000), iar &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;] va indica dacă subșirul de lungime &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; din &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039; se poate descompune în primitive. &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;] va primi valoarea &amp;lt;tt&amp;gt;True&amp;lt;/tt&amp;gt; dacă și numai dacă există o primitivă &#039;&#039;&#039;&#039;&#039;p&#039;&#039;&#039;&#039;&#039; în &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; de lungime &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; astfel încât să fie îndeplinite simultan condițiile:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;-&#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;]=&amp;lt;tt&amp;gt;True&amp;lt;/tt&amp;gt;;&lt;br /&gt;
* secvența de caractere &amp;lt;tt&amp;gt;T[i-L+1]T[i-L+2]...T[i]&amp;lt;/tt&amp;gt; este egală cu &#039;&#039;&#039;&#039;&#039;p&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Iată o primă variantă (în pseudocod) a algoritmului:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
citeste primitivele&lt;br /&gt;
citeste T&lt;br /&gt;
pentru i de la 1 la S&lt;br /&gt;
  daca exista o primitiva p astfel incat&lt;br /&gt;
    V[i-L] si T[i-L+1]T[i-L+2]...T[i]=p&lt;br /&gt;
    atunci V[i]←True&lt;br /&gt;
    altfel V[i]←False&lt;br /&gt;
cauta cel mai mare i pentru care V[i]=True&lt;br /&gt;
scrie i&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Chiar și în acest caz, apare o problemă, deoarece avem nevoie de doi vectori, unul de caractere și altul de variabile booleene, ambii de lungime maxim 500.000. Necesarul de memorie este deci cam de 1MB. Sigur, pentru calculatoarele de astăzi această sumă este ușor de alocat, dar problema admite oricum o soluție la fel de rapidă și mult mai economică. Iată care este principiul:&lt;br /&gt;
&lt;br /&gt;
Pentru a vedea dacă un prefix &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039; de lungime &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; se poate descompune în primitive, noi avem nevoie să cunoaștem dacă prefixele de lungime mai mică se pot descompune. Dar avem oare nevoie de toate prefixele? Nu, deoarece noi vom concatena unul din prefixele de lungime mai mică cu o primitivă pentru a obține noul prefix &#039;&#039;&#039;&#039;&#039;S&#039;&#039;&#039;&#039;&#039;. Însă primitivele au lungime de maxim 20 caractere. Așadar, noi nu trebuie să cunoaștem decât dacă prefixele de lungime &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;-1, &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;-2, ..., &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039;-20 se pot descompune; restul nu ne interesează. În felul acesta am eliminat vectorul &#039;&#039;&#039;&#039;&#039;V&#039;&#039;&#039;&#039;&#039; și l-am redus la un vector de numai 20 de elemente (care în program se numește &amp;lt;tt&amp;gt;CanGet&amp;lt;/tt&amp;gt;). La fiecare moment, când se prelucrează un nou caracter din secvența de constituenți &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;, primul element din &amp;lt;tt&amp;gt;CanGet&amp;lt;/tt&amp;gt; se pierde (deoarece informația pe care el o stochează este învechită), iar următoarele 19 elemente se deplasează spre stânga cu câte o poziție. Al 20-lea element, care acum a rămas disponibil, va fi calculat la pasul curent.&lt;br /&gt;
&lt;br /&gt;
O altă modificare pornește de la observația că, datorită aceleiași limitări a lungimii primitivelor la 20 de caractere, nu avem nevoie nici măcar să reținem întregul vector &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;, ci numai ultimele 20 de litere ale lui. La fiecare pas, litera cea mai „veche” din &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039; (adică de indice minim) se va pierde, iar la celelalte 19 litere se va adăuga litera nou citită. Avem așadar nevoie doar de un string de 20 de caractere, pe care în program l-am numit &amp;lt;tt&amp;gt;Last&amp;lt;/tt&amp;gt;. Pentru a deplasa spre stânga vectorii &amp;lt;tt&amp;gt;CanGet&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;Last&amp;lt;/tt&amp;gt;, se pot folosi fie atribuirile succesive, fie rutinele de acces direct la memorie.&lt;br /&gt;
&lt;br /&gt;
Deoarece prefixele care se pot descompune sunt identificate în ordinea crescătoare a lungimii, ultimul asemenea prefix găsit este tocmai cel de lungime maximă. Dar pentru că, în momentul în care găsim un prefix, nu putem ști dinainte că el este ultimul, trebuie să reținem într-o variabilă lungimea celui mai lung prefix găsit până la momentul respectiv, variabilă pe care o actualizăm de fiecare dată când găsim un nou prefix.&lt;br /&gt;
&lt;br /&gt;
În felul acesta, am reușit să reducem memoria folosită aproape la strictul necesar, adică numai la dicționarul de primitive și la doi vectori de câte douăzeci de caractere. Se recomandă totuși să se aloce un buffer cât mai mare pentru citirea datelor din fișierul &amp;lt;tt&amp;gt;DATA.TXT&amp;lt;/tt&amp;gt; pentru mărirea vitezei de citire. Repartizarea buffer-ului se face cu procedura Pascal &amp;lt;tt&amp;gt;SetTextBuf&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
O optimizare care nu a fost inclusă în program, fiind lăsată ca temă cititorului, este următoarea: dacă la un moment dat, în timpul examinării secvenței de constituenți, este întâlnit un șir de cel puțin 20 de prefixe consecutive, din care nici unul nu se poate descompune în primitive, atunci nici mai departe nu vom mai întâlni vreun prefix care să se poată descompune. Explicați de ce. Această optimizare poate să nu aducă uneori nimic nou în evoluția programului, dar alteori poate să reducă la zero timpul de rulare.&lt;br /&gt;
&lt;br /&gt;
Prezentăm mai jos codul sursă al programului. A fost preferat limbajul Pascal, deoarece pune la dispoziție rutine mai comode de manevrare a șirurilor de caractere.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
program LongestPrefix;&lt;br /&gt;
{$B-,D-,I-,R-,S-}&lt;br /&gt;
const NMax=100;&lt;br /&gt;
      LMax=20;&lt;br /&gt;
type Str20=String[LMax+1];&lt;br /&gt;
     LexType=array[1..NMax] of Str20;&lt;br /&gt;
     BooleanVector=array[1..LMax+1] of Boolean;&lt;br /&gt;
     BufferType=array[1..62000] of Byte;&lt;br /&gt;
var Lex:LexType;     { Dictionarul de primitive }&lt;br /&gt;
    N:Integer;       { Numarul de primitive }&lt;br /&gt;
    Biggest:LongInt; { Lungimea maxima }&lt;br /&gt;
    Buf:BufferType;  { Buffer de intrare }&lt;br /&gt;
&lt;br /&gt;
procedure ReadPrimitives;&lt;br /&gt;
var i,Len:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  Assign(Input,&#039;input.txt&#039;);&lt;br /&gt;
  Reset(Input);&lt;br /&gt;
  ReadLn(N);&lt;br /&gt;
  for i:=1 to N do&lt;br /&gt;
    begin&lt;br /&gt;
      ReadLn(Len);&lt;br /&gt;
      ReadLn(Lex[i]);&lt;br /&gt;
    end;&lt;br /&gt;
  Close(Input);&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure Decompose;&lt;br /&gt;
var Last:Str20;  { Ultimele 20 de caractere citite }&lt;br /&gt;
    CanGet:BooleanVector;&lt;br /&gt;
     { CanGet[i] indica daca Last[i] poate fi&lt;br /&gt;
       ultimul caracter al unei descompuneri }&lt;br /&gt;
    i,L:Integer;&lt;br /&gt;
    Current:LongInt; { Lungimea curenta }&lt;br /&gt;
begin&lt;br /&gt;
  Assign(Input,&#039;data.txt&#039;);&lt;br /&gt;
  SetTextBuf(Input,Buf,SizeOf(Buf));&lt;br /&gt;
  Reset(Input);  { Deschide fisierul cu un buffer atasat }&lt;br /&gt;
&lt;br /&gt;
  Biggest:=0;&lt;br /&gt;
  Last[0]:=Chr(LMax+1);&lt;br /&gt;
  FillChar(Last,LMax,&#039;@&#039;);&lt;br /&gt;
  FillChar(CanGet,LMax,True);&lt;br /&gt;
  Current:=0;  { Deocamdata nu s-a citit nimic }&lt;br /&gt;
&lt;br /&gt;
  ReadLn(Last[LMax+1]); { Citeste primul caracter }&lt;br /&gt;
  repeat&lt;br /&gt;
    Inc(Current);&lt;br /&gt;
    i:=0;&lt;br /&gt;
    { Cauta o primitiva potrivita }&lt;br /&gt;
    repeat&lt;br /&gt;
      Inc(i);&lt;br /&gt;
      L:=Length(Lex[i]);&lt;br /&gt;
      CanGet[LMax+1]:=CanGet[LMax+1-L]&lt;br /&gt;
        and (Copy(Last,LMax+2-L,L)=Lex[i]);&lt;br /&gt;
    until CanGet[LMax+1] or (i=N);&lt;br /&gt;
    { Am gasit o primitiva? }&lt;br /&gt;
    if CanGet[LMax+1]&lt;br /&gt;
      then Biggest:=Current;&lt;br /&gt;
    { Deplaseaza spre stanga CanGet si Last }&lt;br /&gt;
    Move(CanGet[2],CanGet[1],LMax);&lt;br /&gt;
    Move(Last[2],Last[1],LMax);&lt;br /&gt;
    { Avanseaza la urmatorul caracter }&lt;br /&gt;
    ReadLn(Last[LMax+1]);&lt;br /&gt;
  until Last[LMax+1]=&#039;.&#039;;&lt;br /&gt;
&lt;br /&gt;
  Close(Input);&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure WriteSolution;&lt;br /&gt;
begin&lt;br /&gt;
  Assign(Output,&#039;output.txt&#039;);&lt;br /&gt;
  Rewrite(Output);&lt;br /&gt;
  WriteLn(Biggest);&lt;br /&gt;
  Close(Output);&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
begin&lt;br /&gt;
  ReadPrimitives;&lt;br /&gt;
  Decompose;&lt;br /&gt;
  WriteSolution;&lt;br /&gt;
end.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 12 ==&lt;br /&gt;
&lt;br /&gt;
Această problemă a fost propusă la a IV-a Balcaniadă de Informatică, Nicosia 1996.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Grupul de rock U2 va da un concert în Nicosia. Un grup de &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 200 fani U2 așteaptă la coadă în scopul de a cumpăra bilete de la singura caserie deschisă. Fiecare persoană vrea să cumpere numai un bilet, iar casierul poate vinde unei persoane cel mult două bilete.&lt;br /&gt;
&lt;br /&gt;
Casierul folosește &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;] unități de timp pentru a servi al &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;-lea fan (1 ≤ &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; ≤ N). Este posibil totuși ca doi fani așezați la coadă unul după altul (de exemplu, al &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;-lea și al &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;+1-lea) să convină ca numai unul din ei să rămână la coadă, iar celălalt să plece, dacă timpul &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;] (1 ≤ &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039; ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1) în care casierul servește al &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;-lea și al &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;+1-lea fan este mai mic decât &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;]+&#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;+1]. Deci, pentru a minimiza timpul de lucru al casierului, fiecare persoană din coadă încearcă să negocieze cu predecesorul și cu succesorul său, ceea ce va duce în final la o servire mai rapidă.&lt;br /&gt;
&lt;br /&gt;
Fiind date numerele întregi pozitive &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;] (1 ≤ &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;) și &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;] (1 ≤ &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039; ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1), se cere să se minimizeze timpul total al casierului. Acest lucru va fi realizat grupând într-un mod optim perechi de persoane consecutive. Atenție! Nu este necesar ca un anumit fan să se cupleze neapărat cu predecesorul sau cu succesorul său.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: În fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt;, datele de intrare sunt date pe trei linii:&lt;br /&gt;
&lt;br /&gt;
* prima linie conține numărul întreg &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;;&lt;br /&gt;
* a doua linie conține &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; întregi: valorile &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;], separate prin câte un spațiu;&lt;br /&gt;
* a doua linie conține &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 întregi: valorile &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;], separate de asemenea prin câte un spațiu;&lt;br /&gt;
&lt;br /&gt;
Ieșirea se va face în fișierul &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;, astfel:&lt;br /&gt;
&lt;br /&gt;
* prima linie conține un întreg care reprezintă timpul total (minim) al casierului;&lt;br /&gt;
* pe fiecare din următoarele linii se află un singur număr sau două numere separate prin caracterul &#039;+&#039;. Mai exact, fiecare linie conține numărul &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; dacă al &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;-lea fan este servit singur, sau &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+(&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1) dacă cei doi fani sunt serviți ca o pereche.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;7&amp;lt;br&amp;gt;&lt;br /&gt;
5 4 3 2 1 4 4&amp;lt;br&amp;gt;&lt;br /&gt;
7 3 4 2 2 4&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;14&amp;lt;br&amp;gt;&lt;br /&gt;
1&amp;lt;br&amp;gt;&lt;br /&gt;
2+3&amp;lt;br&amp;gt;&lt;br /&gt;
4+5&amp;lt;br&amp;gt;&lt;br /&gt;
6+7&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 15 secunde pentru un test.&lt;br /&gt;
&lt;br /&gt;
Acesta este enunțul în forma lui de la Nicosia. Iată acum și completările „din studio”:&lt;br /&gt;
&lt;br /&gt;
* Numărul de fani este &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 5000;&lt;br /&gt;
* &#039;&#039;&#039;Complexitatea cerută&#039;&#039;&#039; este &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;;&lt;br /&gt;
* &#039;&#039;&#039;Timpul de rulare&#039;&#039;&#039; este de o secundă.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: O primă metodă de rezolvare o vom lăsa în seama cititorului, întrucât ea este foarte asemănătoare cu rezolvarea problemei înmulțirii optime a unui șir de matrice. Ideea de pornire este de a defini o matrice &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039; cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; linii și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; coloane, în care &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;] reprezintă timpul minim în care pot fi serviți fanii &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;, &#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;+1, ..., &#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;-1, &#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;. Scopul este de a afla valoarea &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;[1,&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;].&lt;br /&gt;
&lt;br /&gt;
După cum se știe, însă, înmulțirea optimă a unui șir de matrice se poate determina în timp &amp;lt;math&amp;gt;O(N^3)&amp;lt;/math&amp;gt;. Pentru condițiile inițiale ale problemei (&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 200), metoda se încadrează în timp. Ea putea fi deci folosită la concurs, pe câtă vreme dacă se impun condițiile suplimentare, rezolvarea în timp cubic nu mai dă rezultate. Iată ideea de rezolvare a problemei în timp liniar.&lt;br /&gt;
&lt;br /&gt;
Vom denumi o &#039;&#039;&#039;cuplare de ordin &#039;&#039;K&#039;&#039;&#039;&#039;&#039; modul în care primii &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; fani sunt serviți câte unul sau câte doi. Fiecare cuplare are atașat un cost, respectiv timpul consumat de casier pentru a servi toți fanii. Dintre toate cuplările de ordin &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;, cele pentru care costul este minim (în cazul general pot fi mai multe) se vor numi &#039;&#039;&#039;cuplări optime de ordinul &#039;&#039;K&#039;&#039;&#039;&#039;&#039;. Cerința problemei exprimată cu noua terminologie este: să se găsească o cuplare optimă de ordinul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Să presupunem acum că am găsit cumva această cuplare optimă de ordinul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, pe care o vom nota cu &amp;lt;math&amp;gt;C_N&amp;lt;/math&amp;gt;. Dacă în această cuplare al &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-lea fan este servit de unul singur, atunci modul de servire al primilor &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1 fani reprezintă o cuplare optimă de ordinul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1, pe care o vom nota cu &amp;lt;math&amp;gt;C_{K-1}&amp;lt;/math&amp;gt;. Demonstrația nu este grea: dacă fanii 1, 2, ..., &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1 nu ar fi cuplați în mod optim în cadrul lui &amp;lt;math&amp;gt;C_N&amp;lt;/math&amp;gt;, atunci ar exista o cuplare a lor &amp;lt;math&amp;gt;C&#039;_{K-1}&amp;lt;/math&amp;gt; de cost mai mic decât &amp;lt;math&amp;gt;C_{K-1}&amp;lt;/math&amp;gt;. Dar această cuplare mai bună ar putea fi folosită pentru a obține o cuplare mai bună a tuturor celor &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; fani (servind primii &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1 fani conform cuplării &amp;lt;math&amp;gt;C&#039;_{K-1}&amp;lt;/math&amp;gt;, iar pe ceilalți conform cuplării &amp;lt;math&amp;gt;C_N&amp;lt;/math&amp;gt;). S-ar obține astfel o cuplare &amp;lt;math&amp;gt;C&#039;_{N}&amp;lt;/math&amp;gt; de cost mai mic decât &amp;lt;math&amp;gt;C_N&amp;lt;/math&amp;gt;, ceea ce este absurd, deoarece am presupus &amp;lt;math&amp;gt;C_N&amp;lt;/math&amp;gt; ca fiind optimă.&lt;br /&gt;
&lt;br /&gt;
În mod absolut identic se poate demonstra că dacă &amp;lt;math&amp;gt;C_N&amp;lt;/math&amp;gt; este o cuplare optimă de ordinul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; în care fanii &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;+1 sunt serviți împreună, atunci modul de servire al primilor &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1 fani reprezintă o cuplare optimă de ordinul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1. Recunoaștem în aceste afirmații principiul programării dinamice: optimul global presupune optime locale. De aici deducem că, pentru a realiza o cuplare optimă a primilor &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; fani avem nevoie de câte o cuplare optimă pentru primii &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1 fani, respectiv pentru primii &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-2 fani, urmând ca apoi să aflăm care este costul minim al unei cuplări de ordin &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;, atât în ipoteza că fanul al &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-lea este servit singur, cât și în ipoteza că el este servit împreună cu al &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1-lea fan.&lt;br /&gt;
&lt;br /&gt;
Vom crea așadar doi vectori &amp;lt;math&amp;gt;T_1&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;T_2&amp;lt;/math&amp;gt;, ambii cu câte &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; elemente, în care:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;T_1[K]&amp;lt;/math&amp;gt; este timpul minim în care pot fi serviți fanii 1, 2, ..., &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;, astfel încât fanul al &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-lea să rămână singur;&lt;br /&gt;
* &amp;lt;math&amp;gt;T_2[K]&amp;lt;/math&amp;gt; este timpul minim în care pot fi serviți fanii 1, 2, ..., &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;, astfel încât fanii &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1 să fie cuplați.&lt;br /&gt;
&lt;br /&gt;
Datele inițiale pe care le putem trece în cei doi vectori sunt:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;T_1[1] = T[1]&amp;lt;/math&amp;gt;&lt;br /&gt;
* &amp;lt;math&amp;gt;T_2[1] = \infty&amp;lt;/math&amp;gt; (un singur fan nu poate fi cuplat cu nimeni)&lt;br /&gt;
* &amp;lt;math&amp;gt;T_1[2] = T[1]+T[2]&amp;lt;/math&amp;gt; (dacă al doilea fan rămâne singur, atunci și primul rămâne singur)&lt;br /&gt;
* &amp;lt;math&amp;gt;T_2[2] = R[1]&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Relațiile de recurență se deduc fără prea multă bătaie de cap:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;T_1[K] = T[K] + \min(T_1[K-1], T_2[K-1])&amp;lt;/math&amp;gt; (dacă fanul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; rămâne singur, atunci el este servit în timpul &#039;&#039;&#039;&#039;&#039;T&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;], iar fanul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1 se va cupla cu &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-2 sau va rămâne singur, după cum este mai convenabil);&lt;br /&gt;
* &amp;lt;math&amp;gt;T_2[K] = R[K-1] + \min(T_1[K-2], T_2[K-2])&amp;lt;/math&amp;gt; (dacă fanul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039; se cuplează cu &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1, atunci ei sunt serviți în timpul &#039;&#039;&#039;&#039;&#039;R&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-1], iar fanul &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-2 se va cupla cu &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;-3 sau va rămâne singur, după cum este mai convenabil);&lt;br /&gt;
&lt;br /&gt;
Putem deci să completăm vectorii de la stânga la dreapta. Odată ce am făcut aceasta, costul minim al cuplării de ordinul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; este &amp;lt;math&amp;gt;\min(T_1[N], T_2[N])&amp;lt;/math&amp;gt;. Pentru a reconstitui și așezarea fanilor, vom proceda recurent, astfel: dacă &amp;lt;math&amp;gt;T_2[N]&amp;lt;T_1[N]&amp;lt;/math&amp;gt;, înseamnă că este mai avantajos ca fanii &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 să fie cuplați și reluăm reconstituirea pentru fanii 1, 2, ..., &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-2. În caz contrar, înseamnă că este mai avantajos ca fanul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; să rămână singur și reluăm reconstituirea pentru fanii 1, 2, .., &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1. De exemplu, iată modul în care se completează vectorii &amp;lt;math&amp;gt;T_1&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;T_2&amp;lt;/math&amp;gt; pentru exemplul din enunț:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig67.png]]&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;T_2[7]&amp;lt;T_1[7] \implies&amp;lt;/math&amp;gt; Fanii 6 și 7 sunt cuplați. Reconstituim așezarea primilor 5 fani.&lt;br /&gt;
* &amp;lt;math&amp;gt;T_2[5]&amp;lt;T_1[5] \implies&amp;lt;/math&amp;gt; Fanii 4 și 5 sunt cuplați. Reconstituim așezarea primilor 3 fani.&lt;br /&gt;
* &amp;lt;math&amp;gt;T_2[3]&amp;lt;T_1[3] \implies&amp;lt;/math&amp;gt; Fanii 2 și 3 sunt cuplați, iar primul fan este singur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#define NMax 5001&lt;br /&gt;
typedef unsigned IntVector[NMax];&lt;br /&gt;
typedef long LongVector[NMax];&lt;br /&gt;
&lt;br /&gt;
IntVector T, R;&lt;br /&gt;
LongVector T1, T2;&lt;br /&gt;
int N;&lt;br /&gt;
FILE *OutF;&lt;br /&gt;
&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;, &amp;quot;rt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F,&amp;quot;%d\n&amp;quot;, &amp;amp;N);&lt;br /&gt;
  for (i=1; i&amp;lt;=N;)&lt;br /&gt;
    fscanf(F,&amp;quot;%d&amp;quot;, &amp;amp;T[i++]);&lt;br /&gt;
  for (i=1; i&amp;lt;N;)&lt;br /&gt;
    fscanf(F,&amp;quot;%d&amp;quot;, &amp;amp;R[i++]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
long Min(long X, long Y)&lt;br /&gt;
{&lt;br /&gt;
  return X&amp;lt;Y? X : Y;&lt;br /&gt;
}&lt;br /&gt;
void Match(void)&lt;br /&gt;
{ int i;&lt;br /&gt;
  T1[1]=T[1]; T2[1]=0xFFFF; /* =Infinit */&lt;br /&gt;
  T1[2]=T[1]+T[2]; T2[2]=R[1];&lt;br /&gt;
  for (i=3; i&amp;lt;=N; i++)&lt;br /&gt;
    {&lt;br /&gt;
      T1[i]=T[i]+Min(T1[i-1],T2[i-1]);&lt;br /&gt;
      T2[i]=R[i-1]+Min(T1[i-2],T2[i-2]);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void GoBack(int K)&lt;br /&gt;
{&lt;br /&gt;
  if (K)&lt;br /&gt;
    if (T1[K]&amp;lt;T2[K])&lt;br /&gt;
      { GoBack(K-1);&lt;br /&gt;
        fprintf(OutF,&amp;quot;%d\n&amp;quot;,K);&lt;br /&gt;
      }&lt;br /&gt;
      else { GoBack(K-2);&lt;br /&gt;
             fprintf(OutF,&amp;quot;%d+%d\n&amp;quot;,K-1,K);&lt;br /&gt;
           }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void WriteSolution(void)&lt;br /&gt;
{&lt;br /&gt;
  OutF=fopen(&amp;quot;output.txt&amp;quot;, &amp;quot;wt&amp;quot;);&lt;br /&gt;
  fprintf(OutF,&amp;quot;%d\n&amp;quot;,Min(T1[N],T2[N]));&lt;br /&gt;
  GoBack(N);&lt;br /&gt;
  fclose(OutF);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  Match();&lt;br /&gt;
  WriteSolution();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig67.png&amp;diff=14793</id>
		<title>File:Psycho-fig67.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig67.png&amp;diff=14793"/>
		<updated>2018-03-09T12:37:05Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig66.png&amp;diff=14792</id>
		<title>File:Psycho-fig66.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig66.png&amp;diff=14792"/>
		<updated>2018-03-09T12:36:54Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig65.png&amp;diff=14791</id>
		<title>File:Psycho-fig65.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig65.png&amp;diff=14791"/>
		<updated>2018-03-09T12:36:42Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig64.png&amp;diff=14790</id>
		<title>File:Psycho-fig64.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig64.png&amp;diff=14790"/>
		<updated>2018-03-09T12:36:27Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=Psihologia_concursurilor_de_informatic%C4%83/6_Probleme_de_concurs_1&amp;diff=14789</id>
		<title>Psihologia concursurilor de informatică/6 Probleme de concurs 1</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=Psihologia_concursurilor_de_informatic%C4%83/6_Probleme_de_concurs_1&amp;diff=14789"/>
		<updated>2018-03-09T12:32:21Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;⇦ înapoi la [[Psihologia concursurilor de informatică]]&lt;br /&gt;
&lt;br /&gt;
= Capitolul VI: Probleme de concurs (1-6) =&lt;br /&gt;
&lt;br /&gt;
== Problema 1 ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Se consideră următorul joc: Pe o tablă liniară cu 2&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+1 căsuțe sunt dispuse &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; bile albe (în primele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; căsuțe) și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; bile negre (în ultimele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; căsuțe), căsuța din mijloc fiind liberă. Bilele albe se pot mișca numai spre dreapta, iar cele negre numai spre stânga. Mutările posibile sunt:&lt;br /&gt;
&lt;br /&gt;
# O bilă albă se poate deplasa o căsuță spre dreapta, numai dacă aceasta este liberă;&lt;br /&gt;
# O bilă albă poate sări peste bila aflată imediat în dreapta ei (indiferent de culoarea acesteia), așezându-se în căsuța de dincolo de ea, numai dacă aceasta este liberă;&lt;br /&gt;
# O bilă neagră se poate deplasa o căsuță spre stânga, numai dacă aceasta este liberă;&lt;br /&gt;
# O bilă neagră poate sări peste bila aflată imediat în stânga ei (indiferent de culoarea acesteia), așezându-se în căsuța de dincolo de ea, numai dacă aceasta este liberă.&lt;br /&gt;
&lt;br /&gt;
Trebuie schimbat locul bilelor albe cu cele negre. Se mai cere în plus ca prima mutare să fie făcută cu o bilă albă.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: De la tastatură se citește numărul &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 1000.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: În fișierul &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; se vor tipări două linii terminate cu &amp;lt;tt&amp;gt;&amp;lt;Enter&amp;gt;&amp;lt;/tt&amp;gt;. Pe prima se va tipări numărul de mutări efectuate, iar pe a doua o succesiune de cifre cuprinse între 1 și 4, nedespărțite prin spații, corespunzătoare mutărilor ce trebuie făcute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemple&#039;&#039;&#039;:&lt;br /&gt;
* &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=1  ⇒  Ieșirea &amp;quot;141&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=2  ⇒  Ieșirea &amp;quot;14322341&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 1h.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: 10 secunde pentru un test.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: La prima vedere, problema pare să se preteze la o rezolvare în timp exponențial, prin metoda „Branch and Bound”. Un neajuns al enunțului pare să fie faptul că nu se specifică dacă numărul de mutări efectuate trebuie sau nu să fie minim. Pentru a ne lămuri, să privim în detaliu soluțiile pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=1 și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=2:&lt;br /&gt;
&lt;br /&gt;
* Pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; = 1, toate mutările sunt forțate ((a) - se mută bila albă, (b) - se sare cu cea neagră peste ea, (c) - se mută din nou bila albă); trebuie remarcat că după mutările (a) și (b) se obțin două configurații simetrice una în raport cu cealaltă (oglindite).&lt;br /&gt;
* Pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; = 2, se poate începe sărind cu bila albă de la margine peste cealaltă, dar această mutare ar duce la blocarea jocului. Este deci obligatoriu să se înceapă prin a împinge bila albă centrală (a). Următoarea mutare este forțată ((b) - se sare cu bila neagră peste cea albă), apoi toate mutările sunt obligate (în sensul că dacă la orice pas se face altă mutare decât cea care conduce la soluție, jocul se blochează în câteva mutări): (c) - se împinge bila neagră, (d), (e) - se sare de două ori cu bilele albe, (f) - se împinge bila neagră, (g) - se sare cu bila neagră, (h) - se împinge bila albă. Trebuie din nou remarcat că după mutările (c) și (e) se obțin două configurații simetrice.&lt;br /&gt;
&lt;br /&gt;
Așadar în ambele cazuri, soluția este unică. De fapt, există două soluții asemănătoare, una dacă se începe cu o mutare a bilei albe și una dacă se începe cu o mutare a bilei negre. Fiindcă enunțul impune ca prima mutare să se facă cu o bilă albă, soluția este unică. Se mai observă și că, atât pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=1 cât și pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=2 șirul de mutări este simetric. Pentru a indica efectiv modul de determinare a soluției (care va sugera și ideea de scriere a programului) și pentru a explica observațiile de mai sus, să generalizăm observațiile făcute pentru un &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; oarecare.&lt;br /&gt;
&lt;br /&gt;
* Configurația inițială este:&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig32.png]]&lt;br /&gt;
&lt;br /&gt;
* Se împinge bila albă și se sare cu cea neagră peste ea (șirul de mutări 14):&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig33.png]]&lt;br /&gt;
&lt;br /&gt;
* Se împinge bila neagră și se sare de două ori cu cele albe peste ea (șirul de mutări 322):&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig34.png]]&lt;br /&gt;
&lt;br /&gt;
* Se împinge bila albă și se sare de trei ori cu cele negre peste ea (șirul de mutări 1444):&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig35.png]]&lt;br /&gt;
&lt;br /&gt;
* Se împinge bila neagră și se sare de patru ori cu cele albe peste ea (șirul de mutări 32222):&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig36.png]]&lt;br /&gt;
&lt;br /&gt;
: ...&lt;br /&gt;
&lt;br /&gt;
* Se împinge bila albă (mutarea 1)&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig37.png]]&lt;br /&gt;
&lt;br /&gt;
* Se sare de &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ori cu cele negre peste ea (șirul de mutări 44..44):&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig38.png]]&lt;br /&gt;
&lt;br /&gt;
Ultimele două configurații sunt simetrice. În acest moment șirul de mutări se inversează:&lt;br /&gt;
&lt;br /&gt;
* Se împinge bila albă (mutarea 1):&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig39.png]]&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
* Se sare de patru ori cu bilele albe și se împinge bila neagră (șirul de mutări 22223):&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig40.png]]&lt;br /&gt;
&lt;br /&gt;
* Se sare de trei ori cu bilele negre și se împinge bila albă (șirul de mutări 4441):&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig41.png]]&lt;br /&gt;
&lt;br /&gt;
* Se sare de două ori cu bilele albe și se împinge bila neagră (șirul de mutări 223):&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig42.png]]&lt;br /&gt;
&lt;br /&gt;
* Se sare cu bila neagră și se împinge bila albă (șirul de mutări 41), obținându-se configurația finală:&lt;br /&gt;
&lt;br /&gt;
: [[File:Psycho-fig43.png]]&lt;br /&gt;
&lt;br /&gt;
În concluzie, șirul de mutări este: o împingere - un salt - o împingere - două salturi - o împingere - trei salturi - ... - o împingere - &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 salturi - o împingere - &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; salturi - o împingere - &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 salturi - ... - o împingere - trei salturi - o împingere - două salturi - o împingere - un salt - o împingere, culorile alternând la fiecare pas.&lt;br /&gt;
&lt;br /&gt;
Pentru a calcula numărul de mutări, putem să le numărăm pe măsură ce le efectuăm, dar deoarece se cere afișarea mai întâi a numărului de mutări și după aceea a mutărilor în sine, trebuie fie să stocăm toate mutările în memorie, fie să lucrăm cu fișiere temporare, ambele variante putând duce la complicații nedorite. Din fericire, numărul de mutări se poate calcula cu ușurință astfel: fiecare piesă albă trebuie mutată în medie cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; pași către dreapta și fiecare piesă neagră trebuie mutată cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; pași către stânga. Deci numărul total de pași este &amp;lt;math&amp;gt;2N(N+1).&amp;lt;/math&amp;gt; Din secvența generală de mutări expusă mai sus se observă că nu se fac decât 2&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; împingeri de piese (mutări de un singur pas), restul fiind salturi (mutări de câte doi pași). Deci numărul de mutări este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;2N + \frac{2N(N + 1) - 2N}{2} = 2N + N^2 = N(N + 2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
De aici deducem că nu există un algoritm mai bun decât &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;, deoarece numărul de mutări este &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;. Propunem ca temă cititorului să demonstreze că nu există decât două succesiuni de mutări care rezolvă problema, din care una începe cu mutarea unei piese albe, iar cealaltă este oglindirea ei și începe cu mutarea unei piese negre, deci nu poate constitui o soluție corectă. Demonstrația începe prin a arăta că sunt necesare cel puțin 2&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; împingeri de piese. Această demonstrație explică de ce nu se cere un număr minim de mutări în enunț - cerința nu ar avea sens întrucât soluția este oricum unică.  Acestea fiind zise, programul arată astfel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
int N;&lt;br /&gt;
FILE *OutF;&lt;br /&gt;
&lt;br /&gt;
void Jump(int Level, char A, char B)&lt;br /&gt;
{ int i;&lt;br /&gt;
  putc(A,OutF);&lt;br /&gt;
  for (i=1;i&amp;lt;=Level;i++) putc(B,OutF);&lt;br /&gt;
  if (Level&amp;lt;N)&lt;br /&gt;
    {&lt;br /&gt;
      Jump(Level+1,&#039;1&#039;+&#039;2&#039;-A,&#039;4&#039;+&#039;3&#039;-B);&lt;br /&gt;
      for (i=1;i&amp;lt;=Level;i++) putc(B,OutF);&lt;br /&gt;
    }&lt;br /&gt;
  putc(A,OutF);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  printf(&amp;quot;N=&amp;quot;);scanf(&amp;quot;%d&amp;quot;,&amp;amp;N);&lt;br /&gt;
  OutF=fopen(&amp;quot;output.txt&amp;quot;,&amp;quot;wt&amp;quot;);&lt;br /&gt;
  fprintf(OutF,&amp;quot;%ld\n&amp;quot;,(long)N*(N+2));&lt;br /&gt;
  Jump(1,&#039;1&#039;,&#039;4&#039;);&lt;br /&gt;
  fprintf(OutF,&amp;quot;\n&amp;quot;);&lt;br /&gt;
  fclose(OutF);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 2 ==&lt;br /&gt;
&lt;br /&gt;
Problema următoare a fost propusă la a VI-a Olimpiadă Internațională de Informatică, Stockholm 1994. Este și ea un bun exemplu de situație în care putem cădea în plasa unei rezolvări „Branch and Bound” atunci când nu este cazul.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Se dă o configurație de 3 x 3 ceasuri, fiecare având un singur indicator care poate arăta numai punctele cardinale (adică orele 3, 6, 9 și 12). Asupra acestor ceasuri se poate acționa în nouă moduri distincte, fiecare acțiune însemnând mișcarea limbilor unui anumit grup de ceasuri în sens orar cu 90°. În figura de mai jos se dă un exemplu de configurație inițială a ceasurilor și se arată care sunt cele nouă tipuri de mutări (pentru fiecare tip de mutare se mișcă numai ceasurile reprezentate hașurat).&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig44.png]]&lt;br /&gt;
&lt;br /&gt;
Se cere ca, într-un număr minim de mutări, să aducem toate indicatoarele la ora 12.&lt;br /&gt;
&lt;br /&gt;
Intrarea se face din fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt;, care conține configurația inițială sub forma unei matrice 3 x 3. Pentru fiecare ceas se specifică câte o cifră: 0 = ora 12, 1 = ora 3, 2 = ora 6, 3 = ora 9.&lt;br /&gt;
&lt;br /&gt;
Ieșirea se va face în fișierul &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; sub forma unui șir de numere între 1 și 9, pe un singur rând, separate prin spațiu, reprezentând șirul de mutări care aduc ceasurile în starea finală. Se cere o singură soluție.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;: Pentru figura de mai sus, fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; este&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
3 3 0&lt;br /&gt;
2 2 2&lt;br /&gt;
2 1 2&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
iar fișierul &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; ar putea fi:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
5 8 4 9&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 1h - 1h 15min.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de rulare&#039;&#039;&#039;: o secundă.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(1)&amp;lt;/math&amp;gt; (timp constant).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Din câte am văzut pe la concursuri, peste jumătate din elevi s-ar apuca direct să implementeze o rezolvare Branch and Bound la această problemă, fără să-și mai bată capul prea mult. Există argumente în favoarea acestei inițiative:&lt;br /&gt;
&lt;br /&gt;
* Mulți preferă să nu mai piardă timpul căutând o altă soluție, mai ales că problema seamănă mult cu „Lampa lui Dario Uri” (care de fapt este exact problema ceasurilor, dar în care ceasurile au doar două stări în loc de patru). În plus, se știe că pe cazul general al unei table &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; x &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, cele două probleme nu admit rezolvări polinomiale și atunci cea mai sigură soluție este prin tehnica Branch and Bound.&lt;br /&gt;
* De asemenea, se observă că numărul total de configurații posibile pentru o tablă cu 9 ceasuri este de &amp;lt;math&amp;gt;4^9&amp;lt;/math&amp;gt;, adică aproximativ un sfert de milion. Un algoritm Branch and Bound ar furniza așadar o soluție în timp rezonabil. Raționamentul multor elevi este „decât să pierd timpul căutând o soluție mai bună, fără să am certitudinea că o voi găsi, mai bine folosesc timpul implementând un Branch care măcar știu sigur că merge”.&lt;br /&gt;
* Problema cere o soluție într-un număr minim de pași, lucru care îi cam descurajează pe cei care încă ar vrea să caute alte rezolvări. „Alte rezolvări” înseamnă de obicei un Greedy comod de implementat, iar asupra rezolvărilor Greedy se poartă întotdeauna discuții interminabile pe culoarele sălilor de concurs referitor la „cât de bune sunt” (adică în cât la sută din cazuri furnizează soluția optimă).&lt;br /&gt;
&lt;br /&gt;
Se pierd însă din vedere unele lucruri esențiale. În primul rând, tabla nu este de &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; x &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, ci are dimensiuni fixate, 3 x 3. În al doilea rând, implementarea unui Branch and Bound în timp de concurs este o aventură nu tocmai ușor de dus la bun sfârșit (personal mi-a fost frică să o încerc vreodată). În sfârșit, după cum se va vedea mai jos, problema șirului minim de transformări este o pseudo-problemă, deoarece soluția simplă este oricum unică.&lt;br /&gt;
&lt;br /&gt;
Ce se înțelege prin „soluție simplă”? Să remarcăm două lucruri:&lt;br /&gt;
&lt;br /&gt;
# Aplicarea de patru ori a aceleiași mutări nu schimbă nimic în configurația ceasurilor. Într-adevăr, mutarea va afecta de fiecare dată același grup de ceasuri, iar aplicarea de patru ori va roti fiecare indicator cu 360°, adică îl va aduce în poziția inițială. Din acest motiv, toate afirmațiile făcute în cele ce urmează vor fi valabile în algebra modulo 4.&lt;br /&gt;
# Ordinea în care se aplică transformările nu contează.&lt;br /&gt;
&lt;br /&gt;
În consecință, prin „soluție simplă” se înțelege un șir de mutări ordonat crescător în care nici o mutare nu apare de mai mult de trei ori. Să demonstrăm acum că soluția simplă este unică.&lt;br /&gt;
&lt;br /&gt;
Fie &amp;lt;math&amp;gt;A \in M_3(\mathbb{Z}_4)&amp;lt;/math&amp;gt; matricea citită de la intrare, unde &amp;lt;math&amp;gt;a_{i,j}&amp;lt;/math&amp;gt; arată de câte ori a fost rotit ceasul &amp;lt;math&amp;gt;C_{i,j}&amp;lt;/math&amp;gt; peste ora 12. Fie matricea &amp;lt;math&amp;gt;B \in M_3(\mathbb{Z}_4),&lt;br /&gt;
 b_{i,j}=4 - a_{i,j}&amp;lt;/math&amp;gt;. Matricea &#039;&#039;&#039;&#039;&#039;B&#039;&#039;&#039;&#039;&#039; arată de câte ori mai trebuie rotit fiecare ceas până la ora 12. O soluție înseamnă a efectua fiecare din cele 9 mutări de un număr de ori, &amp;lt;math&amp;gt;p_1, p_2, \dots, p_9&amp;lt;/math&amp;gt;. Cum afectează aceste mutări ceasurile? Se poate deduce ușor:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Ceasul || Tipurile de mutări care îl afectează&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;C_{1,1}&amp;lt;/math&amp;gt; || 1, 2, 4&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;C_{1,2}&amp;lt;/math&amp;gt; || 1, 2, 3, 5&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;C_{1,3}&amp;lt;/math&amp;gt; || 2, 3, 6&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;C_{2,1}&amp;lt;/math&amp;gt; || 1, 4, 5, 7&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;C_{2,2}&amp;lt;/math&amp;gt; || 1, 3, 5, 7, 9&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;C_{2,3}&amp;lt;/math&amp;gt; || 3, 5, 6, 9&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;C_{3,1}&amp;lt;/math&amp;gt; || 4, 7, 8&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;C_{3,2}&amp;lt;/math&amp;gt; || 5, 7, 8, 9&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;math&amp;gt;C_{3,3}&amp;lt;/math&amp;gt; || 6, 8, 9&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Se obține deci un sistem de 9 ecuații cu 9 necunoscute:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
P = &lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
p_1 + p_2 + p_4 &amp;amp; p_1 + p_2 + p_3 + p_5 &amp;amp; p_2 + p_3 + p_6 \\&lt;br /&gt;
p_1 + p_4 + p_5 + p_7 &amp;amp; p_1 + p_3 + p_5 + p_7 + p_9 &amp;amp; p_3 + p_5 + p_6 + p_9 \\&lt;br /&gt;
p_4 + p_7 + p_8 &amp;amp; p_5 + p_7 + p_8 + p_9 &amp;amp; p_6 + p_8 + p_9 \\&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
\equiv B&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Să presupunem că acest sistem admite două soluții &amp;lt;math&amp;gt;p_1, p_2, \dots, p_9&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;q_1, q_2, \dots, q_9&amp;lt;/math&amp;gt;. Atunci &amp;lt;math&amp;gt;P \equiv B \pmod{4}&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;Q \equiv B \pmod{4}&amp;lt;/math&amp;gt;, deci &amp;lt;math&amp;gt;P \equiv Q \pmod{4}&amp;lt;/math&amp;gt; și, prin diferite combinații liniare ale celor 9 ecuații se deduce &amp;lt;math&amp;gt;p_1 \equiv q_1&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;p_2 \equiv q_2&amp;lt;/math&amp;gt;, ..., &amp;lt;math&amp;gt;p_9 \equiv q_9 \pmod{4}&amp;lt;/math&amp;gt;, adică cele două soluții sunt echivalente.&lt;br /&gt;
&lt;br /&gt;
Odată ce am demonstrat că soluția este unică, algoritmul de găsire a ei este foarte simplu: găsim o soluție oarecare, o ordonăm crescător și eliminăm orice grup de 4 mutări identice. Pentru a găsi o soluție oarecare, avem nevoie de niște mutări predefinite care să miște un singur ceas cu o singură poziție înainte, fără a afecta celelalte ceasuri. Aceste mutări vor fi reținute sub forma unui vector cu 9 componente, fiecare componentă indicând de câte ori se efectuează fiecare din cele 9 tipuri de mutări. Deoarece avem nevoie de 9 asemenea mutări predefinite, câte una pentru fiecare ceas, rezultatul va fi o matrice predefinită. De exemplu, pentru a determina secvența de mutări care rotește ceasul &amp;lt;math&amp;gt;C_{1,1}&amp;lt;/math&amp;gt; cu o poziție, trebuie rezolvat sistemul&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
p_1 + p_2 + p_4 &amp;amp; p_1 + p_2 + p_3 + p_5 &amp;amp; p_2 + p_3 + p_6 \\&lt;br /&gt;
p_1 + p_4 + p_5 + p_7 &amp;amp; p_1 + p_3 + p_5 + p_7 + p_9 &amp;amp; p_3 + p_5 + p_6 + p_9 \\&lt;br /&gt;
p_4 + p_7 + p_8 &amp;amp; p_5 + p_7 + p_8 + p_9 &amp;amp; p_6 + p_8 + p_9 \\&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
\equiv&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
1 &amp;amp; 0 &amp;amp; 0 \\&lt;br /&gt;
0 &amp;amp; 0 &amp;amp; 0 \\&lt;br /&gt;
0 &amp;amp; 0 &amp;amp; 0&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
lucru care nu este foarte ușor, dar se poate duce la bun sfârșit în timp de concurs. Soluția este &amp;lt;math&amp;gt;p_1=3, p_2=3, p_3=3, p_4=3, p_5=3, p_6=2, p_7=3, p_8=2, p_9=0&amp;lt;/math&amp;gt;, adică mutarea 1 trebuie efectuată de trei ori, mutarea 2 de trei ori ș.a.m.d. Se obține prima linie din matricea predefinită, (3, 3, 3, 3, 3, 2, 3, 2, 0). Mai trebuie rezolvate propriu-zis sistemele de ecuații pentru ceasurile &amp;lt;math&amp;gt;C_{1,2}&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;C_{2,2}&amp;lt;/math&amp;gt;, soluțiile celorlalte sisteme decurgând ușor prin simetrie. Soluțiile apar în textul sursă.&lt;br /&gt;
&lt;br /&gt;
Odată determinate aceste șiruri elementare de mutări, vom lua pe rând fiecare ceas, vom aplica șirul elementar corespunzător de atâtea ori cât e nevoie pentru a-l aduce la ora 12 și vom aduna modulo 4 toate mutările făcute. Vectorul sumă care rezultă este tocmai soluția noastră.&lt;br /&gt;
&lt;br /&gt;
Pentru exemplul din enunț, folosind constantele din programul sursă, obținem:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
B =&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
1 &amp;amp; 1 &amp;amp; 0 \\&lt;br /&gt;
2 &amp;amp; 2 &amp;amp; 2 \\&lt;br /&gt;
2 &amp;amp; 3 &amp;amp; 2&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
și&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| 1 x (3,3,3,3,3,2,3,2,0) = || (3,3,3,3,3,2,3,2,0) +&lt;br /&gt;
|-&lt;br /&gt;
| 1 x (2,3,2,3,2,3,1,0,1) = || (2,3,2,3,2,3,1,0,1)&lt;br /&gt;
|-&lt;br /&gt;
| 0 x (3,3,3,2,3,3,0,2,3) = || (0,0,0,0,0,0,0,0,0)&lt;br /&gt;
|-&lt;br /&gt;
| 2 x (2,3,1,3,2,0,2,3,1) = || (0,2,2,2,0,0,0,2,2)&lt;br /&gt;
|-&lt;br /&gt;
| 2 x (2,3,2,3,1,3,2,3,2) = || (0,2,0,2,2,2,0,2,0)&lt;br /&gt;
|-&lt;br /&gt;
| 2 x (1,3,2,0,2,3,1,3,2) = || (2,2,0,0,0,2,2,2,0)&lt;br /&gt;
|-&lt;br /&gt;
| 2 x (3,2,0,3,3,2,3,3,3) = || (2,0,0,2,2,0,2,2,2)&lt;br /&gt;
|-&lt;br /&gt;
| 3 x (1,0,1,3,2,3,2,3,2) = || (3,0,3,1,2,1,2,1,2)&lt;br /&gt;
|-&lt;br /&gt;
| 2 x (0,2,3,2,3,3,3,3,3) = || (0,0,2,0,2,2,2,2,2)&lt;br /&gt;
|-&lt;br /&gt;
|                           || ------------------------&lt;br /&gt;
|-&lt;br /&gt;
|                           || (0,0,0,1,1,0,0,1,1)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Prin urmare soluția simplă a exemplului este: 4 5 8 9.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
typedef int Matrix[9][9];&lt;br /&gt;
typedef int Vector[9];&lt;br /&gt;
const Matrix A=&lt;br /&gt;
  {{3,3,3,3,3,2,3,2,0},  // Mutarile care misca ceasul C11&lt;br /&gt;
   {2,3,2,3,2,3,1,0,1},  // .&lt;br /&gt;
   {3,3,3,2,3,3,0,2,3},  // .&lt;br /&gt;
   {2,3,1,3,2,0,2,3,1},  // .&lt;br /&gt;
   {2,3,2,3,1,3,2,3,2},  // .&lt;br /&gt;
   {1,3,2,0,2,3,1,3,2},  // .&lt;br /&gt;
   {3,2,0,3,3,2,3,3,3},  // .&lt;br /&gt;
   {1,0,1,3,2,3,2,3,2},  // .&lt;br /&gt;
   {0,2,3,2,3,3,3,3,3}}; // Mutarile care misca ceasul C33&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;,&amp;quot;rt&amp;quot;);&lt;br /&gt;
  Vector V={0,0,0,0,0,0,0,0,0}; // Vectorul suma&lt;br /&gt;
  int i,j,k;&lt;br /&gt;
&lt;br /&gt;
  for (i=0;i&amp;lt;=8;i++)&lt;br /&gt;
    { fscanf(F,&amp;quot;%d&amp;quot;,&amp;amp;k);&lt;br /&gt;
      for (j=0;j&amp;lt;=8;j++)&lt;br /&gt;
        V[j]=(V[j]+(4-k)*A[i][j])%4;&lt;br /&gt;
    }&lt;br /&gt;
  fclose(F);&lt;br /&gt;
&lt;br /&gt;
  F=fopen(&amp;quot;output.txt&amp;quot;,&amp;quot;wt&amp;quot;);&lt;br /&gt;
  for(i=0;i&amp;lt;=8;i++)&lt;br /&gt;
    for(j=1;j&amp;lt;=V[i];j++)&lt;br /&gt;
      fprintf(F,&amp;quot;%d &amp;quot;,i+1);&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 3 ==&lt;br /&gt;
&lt;br /&gt;
Problema de mai jos este un exemplu de situație în care căutarea exhaustivă a soluției este cea mai bună alegere. Ea a fost propusă spre rezolvare la a VIII-a Olimpiadă Internațională de Informatică, Veszprem, Ungaria 1996.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Văzând succesul cubului său magic, Rubik a inventat versiunea plană a jocului, numit „pătrate magice”. Se folosește o tablă compusă din 8 pătrate de dimensiuni egale. Cele opt pătrate au culori distincte, codificate prin numere de la 1 la 8, ca în figura următoare:&lt;br /&gt;
&lt;br /&gt;
[[File:psycho-fig45.png]]&lt;br /&gt;
&lt;br /&gt;
Configurația tablei se poate reprezenta într-un vector cu 8 elemente citind cele opt pătrate, începând din colțul din stânga sus și mergând în sens orar. De exemplu, configurația din figură se reprezintă prin vectorul (1, 2, 3, 4, 5, 6, 7, 8). Aceasta este configurația inițială a tablei.&lt;br /&gt;
&lt;br /&gt;
Unei configurații i se pot aplica trei transformări elementare, identificate prin literele „A”, „B” și „C”:&lt;br /&gt;
&lt;br /&gt;
* „A”  schimbă între ele cele două linii ale tablei;&lt;br /&gt;
* „B”  rotește circular spre dreapta întregul dreptunghi (cu o poziție);&lt;br /&gt;
* „C”  rotește în sens orar cele patru pătrate centrale (cu o poziție);&lt;br /&gt;
&lt;br /&gt;
Efectele transformărilor elementare asupra configurației inițiale sunt reprezentate în figura de mai jos:&lt;br /&gt;
&lt;br /&gt;
[[File:psycho-fig46.png]]&lt;br /&gt;
&lt;br /&gt;
Din configurația inițială se poate ajunge în orice configurație folosind numai combinații de tranformări elementare. Trebuie să scrieți un program care calculează o secvență de transformări elementare care să aducă tabla de la configurația inițială la o anumită configurație finală cerută.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; conține 8 întregi pe aceeași linie, separați prin spații, descriind configurația finală.&lt;br /&gt;
&lt;br /&gt;
Ieșirea se va face în fișierul &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;. Pe prima linie a acestuia se va tipări lungimea &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; a secvenței de transformări, iar pe fiecare din următoarele &#039;&#039;&#039;&#039;&#039;L&#039;&#039;&#039;&#039;&#039; linii se va tipări câte un caracter  „A”, „B” sau „C”, corespunzător mutărilor care trebuie efectuate.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;2 6 8 4 5 7 3 1&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;7&amp;lt;br&amp;gt;B&amp;lt;br&amp;gt;C&amp;lt;br&amp;gt;A&amp;lt;br&amp;gt;B&amp;lt;br&amp;gt;C&amp;lt;br&amp;gt;C&amp;lt;br&amp;gt;B&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp limită pentru un test&#039;&#039;&#039;: 20 secunde.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 1h 30’ - 1h 45’&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
# La concurs s-au acordat, pentru fiecare test, două puncte dacă se furniza o soluție și încă două dacă lungimea ei nu depășea 300 de mutări.&lt;br /&gt;
# Concurenților li s-a furnizat un program auxiliar, &amp;lt;tt&amp;gt;MTOOL.EXE&amp;lt;/tt&amp;gt;, cu care se puteau verifica soluțiile furnizate.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Și la această problemă se întrevăd două abordări, ca și în problema ceasurilor: una bazată pe mutări predefinite, iar cealaltă pe o căutare exhaustivă a soluției. De data aceasta însă, prima este neinspirată. Să le analizăm pe rând pe fiecare, plecând de la următoarele considerente:&lt;br /&gt;
&lt;br /&gt;
* Dacă se aplică de două ori la rând mutarea A, tabla rămâne nemodificată;&lt;br /&gt;
* Dacă se aplică de patru ori consecutiv una din mutările B sau C, tabla rămâne nemodificată;&lt;br /&gt;
* Ordinea în care se efectuează mutările contează.&lt;br /&gt;
&lt;br /&gt;
Soluția pe care autorul a prezentat-o la concurs avea predefinite mai multe mutări care schimbau între ele oricare două pătrate vecine de pe tablă. Mergând din aproape în aproape, fiecare pătrat era adus în poziția corespunzătoare. Spre exemplu, succesiunea de mutări predefinite care duceau la configurația din exemplu este:&lt;br /&gt;
&lt;br /&gt;
[[File:psycho-fig47.png]]&lt;br /&gt;
&lt;br /&gt;
Această soluție funcționează instantaneu și este relativ ușor de implementat. Ea are însă defectul că soluția furnizată este extrem de lungă, ajungând frecvent la 500 de mutări. Din cele zece teste date, numai trei s-au încadrat în limita de 300 de mutări. Iată mai jos și sursa Pascal prezentată la concurs, care a câștigat numai 26 din cele 40 de puncte acordate pentru problemă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
program Magic;&lt;br /&gt;
{$B-,I-,R-,S-}&lt;br /&gt;
{ Tabla este 1 2 3 4&lt;br /&gt;
             A B C D }&lt;br /&gt;
const r12=&#039;BCBBB&#039;; { Roteste in sens orar coloanele 12 }&lt;br /&gt;
      r23=&#039;C&#039;;     { Roteste in sens orar coloanele 23 }&lt;br /&gt;
      r34=&#039;BBBCB&#039;; { Roteste in sens orar coloanele 34 }&lt;br /&gt;
      r14=&#039;BBCBB&#039;; { Roteste in sens orar coloanele 41 }&lt;br /&gt;
&lt;br /&gt;
      plBCD=r12+r23+r34+r14; { Permuta patratele BCD }&lt;br /&gt;
      PL234=plBCD+&#039;A&#039;+plBCD+&#039;A&#039;; { Permuta coloanele 234 }&lt;br /&gt;
&lt;br /&gt;
      { SXY schimba intre ele coloanele X si Y }&lt;br /&gt;
      S14=&#039;B&#039;+PL234;&lt;br /&gt;
      S12=&#039;BBB&#039;+S14+&#039;B&#039;;&lt;br /&gt;
      S23=&#039;BB&#039;+S14+&#039;BB&#039;;&lt;br /&gt;
      S34=&#039;B&#039;+S14+&#039;BBB&#039;;&lt;br /&gt;
      S13=r12+r12+r23+r23+r12+r12;&lt;br /&gt;
      S24=&#039;B&#039;+S13+&#039;BBB&#039;;&lt;br /&gt;
&lt;br /&gt;
      { RevXY schimba intre ele patratele vecine X si Y }&lt;br /&gt;
      RevC3=plBCD+r23+r12+r12+r34+r34+&#039;BBB&#039;+plBCD+&#039;A&#039;;&lt;br /&gt;
      RevD4=&#039;BBB&#039;+RevC3+&#039;B&#039;;&lt;br /&gt;
      RevB2=&#039;B&#039;+RevC3+&#039;BBB&#039;;&lt;br /&gt;
      RevA1=&#039;BB&#039;+RevC3+&#039;BB&#039;;&lt;br /&gt;
&lt;br /&gt;
      Rev23=&#039;C&#039;+RevC3+&#039;CCC&#039;;&lt;br /&gt;
      Rev12=&#039;B&#039;+Rev23+&#039;BBB&#039;;&lt;br /&gt;
      Rev34=&#039;BBB&#039;+Rev23+&#039;B&#039;;&lt;br /&gt;
      Rev14=&#039;BB&#039;+Rev23+&#039;BB&#039;;&lt;br /&gt;
&lt;br /&gt;
      RevAB=&#039;A&#039;+Rev12+&#039;A&#039;;&lt;br /&gt;
      RevBC=&#039;A&#039;+Rev23+&#039;A&#039;;&lt;br /&gt;
      RevCD=&#039;A&#039;+Rev34+&#039;A&#039;;&lt;br /&gt;
      RevAD=&#039;A&#039;+Rev14+&#039;A&#039;;&lt;br /&gt;
&lt;br /&gt;
type Matrix=array[1..2,1..4] of Integer;&lt;br /&gt;
     Vector=array[1..60000] of Char;&lt;br /&gt;
var A,B:Matrix;&lt;br /&gt;
    V:Vector;&lt;br /&gt;
    N:Integer;&lt;br /&gt;
&lt;br /&gt;
procedure MakeAMatrix;&lt;br /&gt;
begin&lt;br /&gt;
  A[1,1]:=1;&lt;br /&gt;
  A[1,2]:=2;&lt;br /&gt;
  A[1,3]:=3;&lt;br /&gt;
  A[1,4]:=4;&lt;br /&gt;
  A[2,1]:=8;&lt;br /&gt;
  A[2,2]:=7;&lt;br /&gt;
  A[2,3]:=6;&lt;br /&gt;
  A[2,4]:=5;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure ReadBMatrix;&lt;br /&gt;
begin&lt;br /&gt;
  Assign(Input,&#039;input.txt&#039;);&lt;br /&gt;
  Reset(Input);&lt;br /&gt;
  Read(B[1,1]);&lt;br /&gt;
  Read(B[1,2]);&lt;br /&gt;
  Read(B[1,3]);&lt;br /&gt;
  Read(B[1,4]);&lt;br /&gt;
  Read(B[2,4]);&lt;br /&gt;
  Read(B[2,3]);&lt;br /&gt;
  Read(B[2,2]);&lt;br /&gt;
  Read(B[2,1]);&lt;br /&gt;
  Close(Input);&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure AddString(S:String);&lt;br /&gt;
{ Adauga o secventa la sirul-solutie }&lt;br /&gt;
var i:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  for i:=1 to Length(S) do&lt;br /&gt;
    begin&lt;br /&gt;
      Inc(N);&lt;br /&gt;
      V[N]:=S[i];&lt;br /&gt;
    end;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure FindElement(K:Integer;var X,Y:Integer);&lt;br /&gt;
{ Cauta un element intr-o permutare }&lt;br /&gt;
var i,j:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  for i:=1 to 2 do&lt;br /&gt;
    for j:=1 to 4 do&lt;br /&gt;
      if A[i,j]=K then begin&lt;br /&gt;
                         X:=i;&lt;br /&gt;
                         Y:=j;&lt;br /&gt;
                         Exit;&lt;br /&gt;
                       end;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure Switch(var X,Y:Integer);&lt;br /&gt;
{ Schimba intre ele doua numere }&lt;br /&gt;
var IAux:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  IAux:=X;X:=Y;Y:=IAux;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure Process;&lt;br /&gt;
{ Transforma pozitia in pozitia B prin schimbari&lt;br /&gt;
  repetate ale elementelor vecine }&lt;br /&gt;
var i,j,k,l,m:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  for j:=1 to 4 do&lt;br /&gt;
    for i:=1 to 2 do&lt;br /&gt;
      begin&lt;br /&gt;
        FindElement(B[i,j],k,l);&lt;br /&gt;
        { Gaseste elementul care trebuie adus&lt;br /&gt;
          pe pozitia (i,j) }&lt;br /&gt;
        if k&amp;lt;&amp;gt;i then begin&lt;br /&gt;
                       { Il aduce pe linia corecta }&lt;br /&gt;
                       case l of&lt;br /&gt;
                         1:AddString(RevA1);&lt;br /&gt;
                         2:AddString(RevB2);&lt;br /&gt;
                         3:AddString(RevC3);&lt;br /&gt;
                         4:AddString(RevD4);&lt;br /&gt;
                       end; {case}&lt;br /&gt;
                       Switch(A[k,l],A[i,l]);&lt;br /&gt;
                       k:=i;&lt;br /&gt;
                     end;&lt;br /&gt;
        for m:=l downto j+1 do&lt;br /&gt;
          { Il aduce pe coloana corecta }&lt;br /&gt;
          begin&lt;br /&gt;
            if k=1&lt;br /&gt;
              then case m of&lt;br /&gt;
                     2:AddString(Rev12);&lt;br /&gt;
                     3:AddString(Rev23);&lt;br /&gt;
                     4:AddString(Rev34);&lt;br /&gt;
                   end&lt;br /&gt;
              else case m of&lt;br /&gt;
                     2:AddString(RevAB);&lt;br /&gt;
                     3:AddString(RevBC);&lt;br /&gt;
                     4:AddString(RevCD);&lt;br /&gt;
                   end;&lt;br /&gt;
            Switch(A[k,m],A[k,m-1]);&lt;br /&gt;
          end;&lt;br /&gt;
      end;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure Cut(K,D:Integer);&lt;br /&gt;
{ Taie din vectorul V D pozitii incepand cu K }&lt;br /&gt;
var i:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  for i:=K to N-D do&lt;br /&gt;
    V[i]:=V[i+D];&lt;br /&gt;
  Dec(N,D);&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure Reduce;&lt;br /&gt;
{ Reduce secventele de mutari identice }&lt;br /&gt;
var i:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  i:=1;&lt;br /&gt;
  repeat&lt;br /&gt;
    case V[i] of&lt;br /&gt;
      &#039;A&#039;:if (i&amp;lt;=N-1) and (V[i+1]=&#039;A&#039;)&lt;br /&gt;
            then Cut(i,2)&lt;br /&gt;
            else Inc(i);&lt;br /&gt;
      &#039;B&#039;:if (i&amp;lt;=N-3) and (V[i+1]=&#039;B&#039;)&lt;br /&gt;
            and (V[i+2]=&#039;B&#039;) and (V[i+3]=&#039;B&#039;)&lt;br /&gt;
            then Cut(i,4)&lt;br /&gt;
            else Inc(i);&lt;br /&gt;
      &#039;C&#039;:if (i&amp;lt;=N-3) and (V[i+1]=&#039;C&#039;)&lt;br /&gt;
            and (V[i+2]=&#039;C&#039;) and (V[i+3]=&#039;C&#039;)&lt;br /&gt;
            then Cut(i,4)&lt;br /&gt;
            else Inc(i);&lt;br /&gt;
    end; {case}&lt;br /&gt;
  until i=N;&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
procedure WriteSolution;&lt;br /&gt;
var i:Integer;&lt;br /&gt;
begin&lt;br /&gt;
  Assign(Output,&#039;output.txt&#039;);&lt;br /&gt;
  Rewrite(Output);&lt;br /&gt;
  WriteLn(N);&lt;br /&gt;
  for i:=1 to N do WriteLn(V[i]);&lt;br /&gt;
  Close(Output);&lt;br /&gt;
end;&lt;br /&gt;
&lt;br /&gt;
begin&lt;br /&gt;
  N:=0;&lt;br /&gt;
  MakeAMatrix;&lt;br /&gt;
  ReadBMatrix;&lt;br /&gt;
  Process;&lt;br /&gt;
  Reduce;&lt;br /&gt;
  WriteSolution;&lt;br /&gt;
end.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Singura soluție pare deci a fi una de tipul Branch and Bound, care nu este tocmai la îndemână. Cu toate acestea, numărul total de configurații posibile ale tablei este de numai 8! = 40320. Într-adevăr, fiecare poziție de pe tablă se reprezintă printr-o permutare a mulțimii {1,2,3,4,5,6,7,8}. Se poate face deci cu ușurință o căutare exhaustivă a soluției. Aceasta simplifică mult structurile de date folosite (implementarea Branch &amp;amp; Bound folosește structuri destul de încâlcite). În plus, practica arată că se poate ajunge în orice configurație în mai puțin de 25 de mutări.&lt;br /&gt;
&lt;br /&gt;
Algoritmul de căutare este cunoscut sub numele de algoritmul lui Lee și are la bază următoarea idee: Se pornește cu configurația inițială, care este depusă într-o coadă. La fiecare pas se extrage prima configurație disponibilă din coadă, se efectuează pe rând fiecare din cele trei mutări și se obțin trei succesori. Aceștia sunt adăugați la sfârșitul cozii, dacă nu există deja în coadă. Acest pas se numește &#039;&#039;&#039;expandare&#039;&#039;&#039;. Expandarea continuă până când elementul selectat spre expandare este tocmai configurația finală.&lt;br /&gt;
&lt;br /&gt;
Figura următoare indică modul de expandare a cozii, cu mențiunea că printr-o succesiune de litere ne-am referit la configurația care se obține efectuând mutările respective:&lt;br /&gt;
&lt;br /&gt;
[[File:psycho-fig48.png]]&lt;br /&gt;
&lt;br /&gt;
Se observă că, la pasul 2, în coadă au fost adăugate doar configurațiile „AB” și „AC”, iar configurația „AA” nu, deoarece prin efectuarea de două ori a mutării „A” se revine la configurația inițială, care a fost deja expandată. De asemenea, la pasul 3, după expandarea configurației „B” au fost adăugate în coadă numai configurațiile „BB” și „BC”, deoarece configurația „BA” este echivalentă cu configurația „AB”, aflată deja în listă.&lt;br /&gt;
&lt;br /&gt;
Pseudocodul algoritmului este:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
citeste datele de intrare&lt;br /&gt;
initializeaza coada cu configuratia initiala&lt;br /&gt;
cat timp primul element al cozii nu este configuratia finala:&lt;br /&gt;
  expandeaza primul element al cozii&lt;br /&gt;
  pentru i=[A,B,C]&lt;br /&gt;
    daca succesorul i nu a fost deja pus in coada atunci&lt;br /&gt;
      adauga succesorul i in coada&lt;br /&gt;
  sterge primul element al cozii&lt;br /&gt;
reconstituie sirul de mutari&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Algoritmul de mai sus garantează și găsirea soluției optime (în număr minim de mutări). Rămân de lămurit două lucruri: (1) Cum ne dăm seama dacă o configurație există deja în coadă și (2) cum se face reconstituirea soluției.&lt;br /&gt;
&lt;br /&gt;
Pentru a afla dacă o configurație mai există în listă, cea mai simplă metodă ar fi o căutare secvențială a listei. Totuși, această versiune ar fi extrem de lentă, deoarece coada atinge rapid dimensiuni respectabile (de ordinul miilor de elemente). În plus, un element al listei ar reține configurația propriu-zisă (un vector cu opt elemente), ceea ce ar duce la un consum ridicat de memorie. Testul de egalitate a doi vectori ar fi și el costisitor din punct de vedere al timpului.&lt;br /&gt;
&lt;br /&gt;
Există însă o altă metodă mai simplă. Am demonstrat că numărul de configurații posibile ale tablei este 8! = 40320.  Dacă am putea găsi o funcție bijectivă &amp;lt;math&amp;gt;H: \mathbf{P_8} \to \{0, 1, \dots, 40319\}&amp;lt;/math&amp;gt;, unde &amp;lt;math&amp;gt;\mathbf{P_8}&amp;lt;/math&amp;gt; este mulțimea permutărilor de 8 elemente, atunci ar fi suficient un vector caracteristic cu 40320 elemente. De îndată ce introducem în coadă o nouă configurație &#039;&#039;&#039;&#039;&#039;K&#039;&#039;&#039;&#039;&#039;, nu avem decât să bifăm elementul corespunzător din vectorul caracteristic. Înainte de a adăuga o configurație în coadă, testăm dacă nu cumva elementul corespunzător ei a fost deja bifat, semn că nodul a mai fost vizitat.&lt;br /&gt;
&lt;br /&gt;
Cum se construiește funcția &#039;&#039;&#039;&#039;&#039;H&#039;&#039;&#039;&#039;&#039;? Pentru orice permutare &amp;lt;math&amp;gt;p \in \mathbf{P_8}&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;H(p)&amp;lt;/math&amp;gt; este poziția lui &#039;&#039;&#039;&#039;&#039;p&#039;&#039;&#039;&#039;&#039; în ordonarea lexicografică a lui &amp;lt;math&amp;gt;\mathbf{P_8}&amp;lt;/math&amp;gt; (începând de la 0):&lt;br /&gt;
&lt;br /&gt;
: H(1, 2, 3, 4, 5, 6, 7, 8) = 0&amp;lt;br&amp;gt;&lt;br /&gt;
: H(1, 2, 3, 4, 5, 6, 8, 7) = 1&amp;lt;br&amp;gt;&lt;br /&gt;
: H(1, 2, 3, 4, 5, 7, 6, 8) = 2&amp;lt;br&amp;gt;&lt;br /&gt;
: ....................................&amp;lt;br&amp;gt;&lt;br /&gt;
: H(8, 7, 6, 5, 4, 3, 1, 2) = 40318&amp;lt;br&amp;gt;&lt;br /&gt;
: H(8, 7, 6, 5, 4, 3, 2, 1) = 40319&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Se observă că primele 7! = 5040 elemente din ordonare au pe prima poziție un 1, următoarele 5040 au pe prima poziție un 2 etc. De asemenea, dintre elementele care au pe prima poziție un 1, primele 6! = 720 au pe a doua poziție un 2, următoarele 720 au pe a doua poziție un 3 etc.&lt;br /&gt;
&lt;br /&gt;
Să calculăm de exemplu &#039;&#039;&#039;&#039;&#039;H&#039;&#039;&#039;&#039;&#039;(2, 6, 8, 4, 5, 7, 3, 1). Prima cifră a permutării este 2, deci se adaugă 7! = 5040. Rămân cifrele 1, 3, 4, 5, 6, 7 și 8. A doua cifră a permutării este 6, a cincea ca valoare dintre cifrele rămase, deci se adaugă 4 x 6! = 2880. Rămân cifrele 1, 3, 4, 5, 7 și 8 etc. Se aplică procedeul până la ultima cifră și rezultă:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Cifre rămase || Permutarea || Valoarea adăugată&lt;br /&gt;
|-&lt;br /&gt;
| 1, 2, 3, 4, 5, 6, 7, 8&lt;br /&gt;
| 2&lt;br /&gt;
| 1 x 7! = 5040&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 4, 5, 6, 7, 8&lt;br /&gt;
| 6&lt;br /&gt;
| 4 x 6! = 2880&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 4, 5, 7, 8&lt;br /&gt;
| 8&lt;br /&gt;
| 5 x 5! = 600&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 4, 5, 7&lt;br /&gt;
| 4&lt;br /&gt;
| 2 x 4! = 48&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 5, 7&lt;br /&gt;
| 5&lt;br /&gt;
| 2 x 3! = 12&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 7&lt;br /&gt;
| 7&lt;br /&gt;
| 2 x 2! = 4&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3&lt;br /&gt;
| 3&lt;br /&gt;
| 1 x 1! = 1&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 1&lt;br /&gt;
| 0 x 0! = 0&lt;br /&gt;
|-&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
| &#039;&#039;&#039;&#039;&#039;H&#039;&#039;&#039;&#039;&#039;(&#039;&#039;&#039;&#039;&#039;p&#039;&#039;&#039;&#039;&#039;) = 8585&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Reciproc se construiește permutarea când i se cunoaște valoarea atașată:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Cifre nefolosite || H(&#039;&#039;&#039;&#039;&#039;p&#039;&#039;&#039;&#039;&#039;) || || Cifra selectată ||&lt;br /&gt;
|-&lt;br /&gt;
| 1, 2, 3, 4, 5, 6, 7, 8&lt;br /&gt;
| 8585&lt;br /&gt;
| 8585 div 7! = 1&lt;br /&gt;
| 2&lt;br /&gt;
| 8585 mod 7! = 3545&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 4, 5, 6, 7, 8&lt;br /&gt;
| 3545&lt;br /&gt;
| 3545 div 6! = 4&lt;br /&gt;
| 6&lt;br /&gt;
| 3545 mod 6! = 665&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 4, 5, 7, 8&lt;br /&gt;
| 665&lt;br /&gt;
| 665 div 5! = 5&lt;br /&gt;
| 8&lt;br /&gt;
| 665 mod 5! = 65&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 4, 5, 7&lt;br /&gt;
| 65&lt;br /&gt;
| 65 div 4! =2&lt;br /&gt;
| 4&lt;br /&gt;
| 65 mod 4! = 17&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 5, 7&lt;br /&gt;
| 17&lt;br /&gt;
| 17 div 3! = 2&lt;br /&gt;
| 5&lt;br /&gt;
| 17 mod 3! = 5&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3, 7&lt;br /&gt;
| 5&lt;br /&gt;
| 5 div 2! = 2&lt;br /&gt;
| 7&lt;br /&gt;
| 5 div 2! = 1&lt;br /&gt;
|-&lt;br /&gt;
| 1, 3&lt;br /&gt;
| 1&lt;br /&gt;
| 1 div 1!=1&lt;br /&gt;
| 3&lt;br /&gt;
| 1 mod 1! = 0&lt;br /&gt;
|-&lt;br /&gt;
| 1&lt;br /&gt;
| 0 &lt;br /&gt;
| 0 div 0!=0&lt;br /&gt;
| 1&lt;br /&gt;
| &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Rezultă &#039;&#039;&#039;&#039;&#039;p&#039;&#039;&#039;&#039;&#039;=(2, 6, 8, 4, 5, 7, 3, 1).&lt;br /&gt;
&lt;br /&gt;
Această metodă de căutare are și avantajul că în listă se va ține un singur număr pe doi octeți, făcându-se economie de memorie. Expandarea unui nod constă din trei pași:&lt;br /&gt;
&lt;br /&gt;
# Se extrage primul număr din listă și se reconstituie configurația atașată;&lt;br /&gt;
# Se fac cele trei mutări, obținându-se trei succesori;&lt;br /&gt;
# Pentru fiecare succesor se calculează funcția &#039;&#039;&#039;&#039;&#039;H&#039;&#039;&#039;&#039;&#039; și dacă configurația nu este găsită în listă, este adăugată.&lt;br /&gt;
&lt;br /&gt;
Pentru a face reconstituirea soluției avem nevoie de date suplimentare. Respectiv, vectorul caracteristic atașat permutărilor nu va mai reține doar dacă o poziție a fost „văzută” sau nu, ci și poziția din care ea provine (prin valoarea funcției &#039;&#039;&#039;&#039;&#039;H&#039;&#039;&#039;&#039;&#039;). Trebuie de asemenea reținut tipul mutării (A, B sau C) prin care s-a ajuns în acea configurație. Cei doi vectori se numesc &amp;lt;tt&amp;gt;Father&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;MoveKind&amp;lt;/tt&amp;gt;. Inițial, toate elementele vectorului &amp;lt;tt&amp;gt;Father&amp;lt;/tt&amp;gt; au eticheta „Unknown”, semnificând că nodurile nu au fost încă vizitate, cu excepția elementului atașat configurației inițiale, care poartă eticheta specială „Root” (rădăcină). &lt;br /&gt;
&lt;br /&gt;
Pseudocodul pentru expandarea unui nod arată cam așa:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
K ← primul numar din coada&lt;br /&gt;
P ← H-1(K)&lt;br /&gt;
afla cei trei succesori QA, QB, QC&lt;br /&gt;
pentru i=A,B,C&lt;br /&gt;
  daca Father[H(Qi)]=Unknown atunci&lt;br /&gt;
    Father[H(Qi)] ← K&lt;br /&gt;
    MoveKind[H(Qi)] ← i&lt;br /&gt;
    adauga H(Qi) in coada&lt;br /&gt;
sterge K din coada&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reconstituirea soluției se face recursiv: se pornește de la configurația finală și se merge înapoi (folosind informația din vectorul &amp;lt;tt&amp;gt;Father&amp;lt;/tt&amp;gt;) până la configurația inițială, măsurându-se astfel numărul de mutări. La revenire se tipăresc toate mutările efectuate (folosind informația din vectorul &amp;lt;tt&amp;gt;MoveKind&amp;lt;/tt&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#define Unknown 0xFFFF&lt;br /&gt;
#define Root 0xFFFE&lt;br /&gt;
&lt;br /&gt;
typedef huge unsigned Vector[40320];&lt;br /&gt;
typedef char CharVector[40320];&lt;br /&gt;
typedef int Perm[8];&lt;br /&gt;
typedef struct list { unsigned X; struct list * Next; } List;&lt;br /&gt;
&lt;br /&gt;
Perm StartPerm={1,2,3,4,5,6,7,8}, EndPerm;&lt;br /&gt;
const Perm Moves[3]=&lt;br /&gt;
   {{7,6,5,4,3,2,1,0},&lt;br /&gt;
    {3,0,1,2,5,6,7,4},&lt;br /&gt;
    {0,6,1,3,4,2,5,7}};&lt;br /&gt;
/* Cele trei tipuri de mutari */&lt;br /&gt;
Vector Father; /* Legaturile de tip tata */&lt;br /&gt;
CharVector MoveKind;&lt;br /&gt;
unsigned StartValue,EndValue;&lt;br /&gt;
/* Valorile atasate configuratiilor initiala si finala */&lt;br /&gt;
List *Head, *Tail; /* Coada de expandat */&lt;br /&gt;
&lt;br /&gt;
/**** Bijectia care asociaza un numar unei permutari ****/&lt;br /&gt;
&lt;br /&gt;
unsigned Perm2Int(Perm P)&lt;br /&gt;
{ int i,j,k,Fact=5040;&lt;br /&gt;
  unsigned Sum=0;&lt;br /&gt;
&lt;br /&gt;
  for (i=0;i&amp;lt;=6;i++)&lt;br /&gt;
    { k=P[i]-1;&lt;br /&gt;
      for (j=0;j&amp;lt;i;j++)&lt;br /&gt;
        if (P[j]&amp;lt;P[i]) k--;&lt;br /&gt;
      Sum+=k*Fact;&lt;br /&gt;
      Fact/=(7-i);&lt;br /&gt;
    }&lt;br /&gt;
  return Sum;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Int2Perm(unsigned Sum, Perm P)&lt;br /&gt;
{ int i,j,k,Order,Fact=5040;&lt;br /&gt;
  Perm Used={0,0,0,0,0,0,0,0};&lt;br /&gt;
&lt;br /&gt;
  for (i=0;i&amp;lt;=7;i++)&lt;br /&gt;
    { Order=Sum/Fact;&lt;br /&gt;
      j=-1;&lt;br /&gt;
      for (k=0;k&amp;lt;=Order;k++)&lt;br /&gt;
        do j++; while (Used[j]);&lt;br /&gt;
      Used[j]=1;&lt;br /&gt;
      P[i]=j+1;&lt;br /&gt;
      Sum%=Fact;&lt;br /&gt;
      if (i!=7) Fact/=(7-i);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**** Lucrul cu liste ****/&lt;br /&gt;
&lt;br /&gt;
void InitList(void)&lt;br /&gt;
{&lt;br /&gt;
  Head=Tail=malloc(sizeof(List));&lt;br /&gt;
  Tail-&amp;gt;X=StartValue;&lt;br /&gt;
  Tail-&amp;gt;Next=NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void AddToTail(unsigned K)&lt;br /&gt;
{&lt;br /&gt;
  Tail-&amp;gt;Next=malloc(sizeof(List));&lt;br /&gt;
  Tail=Tail-&amp;gt;Next;&lt;br /&gt;
  Tail-&amp;gt;X=K;&lt;br /&gt;
  Tail-&amp;gt;Next=NULL;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Behead(void)&lt;br /&gt;
/* Sterge capul listei */&lt;br /&gt;
{ List *LCor=Head;&lt;br /&gt;
&lt;br /&gt;
  Head=Head-&amp;gt;Next;&lt;br /&gt;
  free(LCor);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**** Cautarea propriu-zisa ****/&lt;br /&gt;
&lt;br /&gt;
void MakeMove(Perm P,Perm Q,int Kind)&lt;br /&gt;
/* Kind = 0, 1 sau 2 */&lt;br /&gt;
{ int i;&lt;br /&gt;
  for (i=0;i&amp;lt;=7;i++)&lt;br /&gt;
    Q[i]=P[Moves[Kind][i]];&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Expand(void)&lt;br /&gt;
{ List *LCor;&lt;br /&gt;
  Perm P1,P2;&lt;br /&gt;
  unsigned i,XSon,Done;&lt;br /&gt;
&lt;br /&gt;
  InitList();&lt;br /&gt;
  do {&lt;br /&gt;
    Int2Perm(Head-&amp;gt;X,P1);&lt;br /&gt;
    for(i=0;i&amp;lt;=2;i++)&lt;br /&gt;
      { MakeMove(P1,P2,i);&lt;br /&gt;
        XSon=Perm2Int(P2);&lt;br /&gt;
        if (Father[XSon]==Unknown)&lt;br /&gt;
          { Father[XSon]=Head-&amp;gt;X;&lt;br /&gt;
            MoveKind[XSon]=i+65;&lt;br /&gt;
            AddToTail(XSon);&lt;br /&gt;
          }&lt;br /&gt;
      }&lt;br /&gt;
    Done=(Head-&amp;gt;X==EndValue);&lt;br /&gt;
    Behead(); }&lt;br /&gt;
  while (!Done);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Intrarea si iesirea */&lt;br /&gt;
&lt;br /&gt;
void InitData(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;,&amp;quot;rt&amp;quot;);&lt;br /&gt;
  unsigned i;&lt;br /&gt;
&lt;br /&gt;
  for (i=0;i&amp;lt;=7;i++) fscanf(F,&amp;quot;%d&amp;quot;,&amp;amp;EndPerm[i]);&lt;br /&gt;
  fclose(F);&lt;br /&gt;
  StartValue=Perm2Int(StartPerm);&lt;br /&gt;
  EndValue=Perm2Int(EndPerm);&lt;br /&gt;
  for (i=0;i&amp;lt;40320;) Father[i++]=Unknown;&lt;br /&gt;
  Father[StartValue]=Root;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void WriteMove(FILE *F,unsigned K,int Len)&lt;br /&gt;
{&lt;br /&gt;
  if (K!=StartValue)&lt;br /&gt;
    { WriteMove(F,Father[K],Len+1);&lt;br /&gt;
      fprintf(F,&amp;quot;%c\n&amp;quot;,MoveKind[K]);&lt;br /&gt;
    }&lt;br /&gt;
    else fprintf(F,&amp;quot;%d\n&amp;quot;,Len);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Restore(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;output.txt&amp;quot;,&amp;quot;wt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
  WriteMove(F,EndValue,0);&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  InitData();&lt;br /&gt;
  Expand();&lt;br /&gt;
  Restore();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 4 ==&lt;br /&gt;
&lt;br /&gt;
Continuăm cu o problemă care a fost de asemenea dată spre rezolvare la a VIII-a Olimpiadă Internațională de Informatică, Veszprem 1996. Problema în sine nu a fost foarte grea și mulți elevi au luat punctaj maxim. Totuși, enunțul permite unele modificări interesante care practic schimbă cu totul problema.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: Să considerăm următorul joc de două persoane. Tabla de joc constă într-o secvență de întregi pozitivi. Cei doi jucători mută pe rând. Mutarea fiecărui jucător constă în alegerea unui număr de la unul din cele două capete ale secvenței. Numărul ales este șters de pe tablă. Jocul se termină când toate numerele au fost selectate. Primul jucător câștigă dacă suma numerelor alese de el este mai mare sau egală cu cea a numerelor alese de cel de-al doilea jucător. În caz contrar, câștigă al doilea jucător.&lt;br /&gt;
&lt;br /&gt;
Dacă tabla conține inițial un număr par de elemente, atunci primul jucător are o strategie de câștig. Trebuie să scrieți un program care implementează strategia cu care primul jucător câștigă jocul. Răspunsurile celui de-al doilea jucător sunt date de un program rezident. Cei doi jucători comunică prin trei proceduri ale modulului &amp;lt;tt&amp;gt;Play&amp;lt;/tt&amp;gt; care v-a fost pus la dispoziție. Procedurile sunt &amp;lt;tt&amp;gt;StartGame&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;MyMove&amp;lt;/tt&amp;gt; și &amp;lt;tt&amp;gt;YourMove&amp;lt;/tt&amp;gt;. Primul jucător începe jocul apelând procedura fără parametri &amp;lt;tt&amp;gt;StartGame&amp;lt;/tt&amp;gt;. Dacă alege numărul de la capătul din stânga, el va apela procedura &amp;lt;tt&amp;gt;MyMove(&#039;L&#039;)&amp;lt;/tt&amp;gt;. Analog, apelul de procedură &amp;lt;tt&amp;gt;MyMove(&#039;R&#039;)&amp;lt;/tt&amp;gt; trimite un mesaj celui de-al doilea jucător prin care îl informează că a ales numărul de la capătul din dreapta. Cel de-al doilea jucător, deci computerul, mută imediat, iar primul jucător poate afla mutarea acestuia executând procedura &amp;lt;tt&amp;gt;YourMove(C)&amp;lt;/tt&amp;gt;, unde &amp;lt;tt&amp;gt;C&amp;lt;/tt&amp;gt; este o variabilă de tip &amp;lt;tt&amp;gt;Char&amp;lt;/tt&amp;gt; (în C/C++ apelul este &amp;lt;tt&amp;gt;YourMove(&amp;amp;C)&amp;lt;/tt&amp;gt;). Valoarea lui &amp;lt;tt&amp;gt;C&amp;lt;/tt&amp;gt; este &amp;lt;tt&amp;gt;&#039;L&#039;&amp;lt;/tt&amp;gt; sau &amp;lt;tt&amp;gt;&#039;R&#039;&amp;lt;/tt&amp;gt;, după cum numărul ales este de la capătul din stânga sau din dreapta.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Prima linie din fișierul &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; conține dimensiunea inițială &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; a tablei. &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; este par și 2 ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 100. Următoarele &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; linii conțin fiecare câte un număr, reprezentând conținutul tablei de la stânga la dreapta. Fiecare număr este cel mult 200.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: Când jocul se termină, programul trebuie să scrie rezultatul final în fișierul &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;. Fișierul conține două numere pe prima linie, reprezentând suma numerelor alese de primul, respectiv de cel de-al doilea jucător. Programul trebuie să joace un joc corect și ieșirea trebuie să corespundă jocului jucat.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;6&amp;lt;br&amp;gt;4&amp;lt;br&amp;gt;7&amp;lt;br&amp;gt;2&amp;lt;br&amp;gt;9&amp;lt;br&amp;gt;5&amp;lt;br&amp;gt;2&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;15 14&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp limită de execuție&#039;&#039;&#039;: 20 secunde pentru un test.&lt;br /&gt;
&lt;br /&gt;
Acesta a fost enunțul original, la care va trebui să facem câteva modificări, în parte deoarece nu putem folosi modulul &amp;lt;tt&amp;gt;Play&amp;lt;/tt&amp;gt;, în parte pentru a face problema mai restrictivă:&lt;br /&gt;
&lt;br /&gt;
* Mutările vor fi anunțate pe ecran prin tipărirea unui caracter &amp;lt;tt&amp;gt;&#039;L&#039;&amp;lt;/tt&amp;gt; sau &amp;lt;tt&amp;gt;&#039;R&#039;&amp;lt;/tt&amp;gt;;&lt;br /&gt;
* Mutările celui de-al doilea jucător vor fi comunicate de un partener uman, prin introducerea de la tastatură a unui caracter &amp;lt;tt&amp;gt;&#039;L&#039;&amp;lt;/tt&amp;gt; sau &amp;lt;tt&amp;gt;&#039;R&#039;&amp;lt;/tt&amp;gt;;&lt;br /&gt;
* Rezultatul final se va tipări pe ecran, sub aceeași formă (pereche de numere).&lt;br /&gt;
* Timpul de gândire pentru fiecare mutare trebuie să fie cât mai mic (practic răspunsul să fie instantaneu);&lt;br /&gt;
* &#039;&#039;&#039;Complexitatea totală&#039;&#039;&#039; a calculelor efectuate să fie &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;Timpul de implementare&#039;&#039;&#039; a fost cam de 1h 40 min. Propunem reducerea lui la 30 minute.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Este ușor de demonstrat că o rezolvare „greedy” a problemei (la fiecare mutare jucătorul 1 alege numărul mai mare) nu atrage întotdeauna câștigul. Iată un contraexemplu:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig49.png]]&lt;br /&gt;
&lt;br /&gt;
La prima mutare, jucătorul 1 poate să aleagă fie numărul 7, fie numărul 2. Dacă se va „lăcomi” la 7, jucătorul 2 va lua numărul 10 și inevitabil va câștiga. Soluția pentru primul jucător este să ia numărul 2, apoi, indiferent de ce va juca partenerul său, va putea lua numărul 10 și va câștiga.&lt;br /&gt;
&lt;br /&gt;
Iată o soluție izbitor de simplă de complexitate &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt;: La citirea datelor se face suma elementelor aflate pe poziții pare și a celor aflate pe poziții impare. Să presupunem că suma elementelor de ordin par este mai mare sau egală cu cea a elementelor de ordin impar (cazul invers se tratează analog). Atunci, dacă primul jucător ar putea să aleagă toate elementele de ordin par (care sunt într-adevăr &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;/2, adică atâtea câte are el dreptul să aleagă), ar câștiga jocul. Jucătorul 1 poate începe jocul prin a lua primul sau ultimul element din secvență, deci îl va alege pe ultimul, care are număr de ordine par. Al doilea jucător are de ales între primul și al &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1-lea element, ambele având număr de ordine impar. Indiferent ce variantă o va adopta, primul jucător va avea din nou acces la un element de pe o poziție pară. Dacă jucătorul 2 alege elementul din stânga (primul), atunci jucătorul 1 va putea lua elementul de după el (al doilea), iar dacă jucătorul 2 alege elementul din dreapta (al &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1-lea), atunci jucătorul 1 va putea lua elementul dinaintea el (al &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-2-lea). Deci primul jucător nu are altceva de făcut decât să repete mutările făcute de cel de-al doilea. Să privim de exemplu desfășurarea jocului pe tabla dată în enunț:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig50.png]]&lt;br /&gt;
&lt;br /&gt;
Programul în sine nici nu are nevoie să mai rețină vectorul de numere în memorie, din moment ce primul jucător nu are altceva de făcut decât să imite mutările celui de-al doilea. Un calcul al sumelor la citirea datelor este suficient. Complexitatea &amp;lt;math&amp;gt;O(N)&amp;lt;/math&amp;gt; este optimă, deoarece vectorul trebuie parcurs cel puțin o dată pentru citirea configurației inițiale a tablei.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;,&amp;quot;rt&amp;quot;);&lt;br /&gt;
  int SEven,SOdd,N,i,K;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F,&amp;quot;%d\n&amp;quot;,&amp;amp;N);&lt;br /&gt;
  for (i=1, SEven=SOdd=0; i&amp;lt;=N; i++)&lt;br /&gt;
    { fscanf(F, &amp;quot;%d\n&amp;quot;, &amp;amp;K);&lt;br /&gt;
      if (i&amp;amp;1) SOdd+=K;&lt;br /&gt;
        else SEven+=K;&lt;br /&gt;
    }&lt;br /&gt;
  fclose(F);&lt;br /&gt;
&lt;br /&gt;
  printf(&amp;quot;Mutarea mea: %c\n&amp;quot;, SEven&amp;gt;=SOdd ? &#039;R&#039; : &#039;L&#039;);&lt;br /&gt;
  for (i=1; i&amp;lt;N/2; i++)&lt;br /&gt;
    { printf(&amp;quot;Mutarea dvs. (L/R) ? &amp;quot;);&lt;br /&gt;
      printf(&amp;quot;Mutarea mea: %c\n&amp;quot;, getchar());&lt;br /&gt;
      getchar(); /* Caracterul newline */&lt;br /&gt;
    }&lt;br /&gt;
  printf(&amp;quot;Mutarea dvs. (L/R) ? &amp;quot;);&lt;br /&gt;
  getchar();&lt;br /&gt;
&lt;br /&gt;
  if (SEven&amp;gt;=SOdd)&lt;br /&gt;
    printf(&amp;quot;%d %d\n&amp;quot;, SEven, SOdd);&lt;br /&gt;
    else printf(&amp;quot;%d %d\n&amp;quot;, SOdd, SEven);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
O a doua variantă a enunțului aduce unele condiții suplimentare:&lt;br /&gt;
&lt;br /&gt;
* Se cere să se tipărească numai diferența maximă de scor pe care o poate obține primul jucător, considerând că ambii parteneri joacă perfect;&lt;br /&gt;
* &#039;&#039;&#039;Complexitatea cerută&#039;&#039;&#039; este &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;Timpul de implementare&#039;&#039;&#039; este de 45 minute, maxim 1h.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Trebuie mai întâi să lămurim ce se înțelege prin „joc perfect”. Jucătorul 1 are întotdeauna victoria la îndemână (metoda este arătată mai sus), dar nu la orice scor. Jucătorul 2 urmărește să minimizeze diferența de scor. Fie &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039; diferența de scor cu care se termină un joc. &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039; poate lua diferite valori pentru aceeași configurație inițială a tablei, în funcție de mutările făcute de cei doi jucători. Fie &amp;lt;math&amp;gt;D_{MAX}&amp;lt;/math&amp;gt; diferența maximă de scor pe care o poate obține primul jucător indiferent de mutările celui de-al doilea. Exact această valoare trebuie aflată. &amp;lt;math&amp;gt;D_{MAX}&amp;lt;/math&amp;gt; nu este propriu-zis o diferență maximă. Jucătorul 1 poate să câștige și la diferențe mai mari decât &amp;lt;math&amp;gt;D_{MAX}&amp;lt;/math&amp;gt;, dar trebuie ca jucătorul 2 să-l „ajute”. Să reluăm exemplul cu 4 numere:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig51.png]]&lt;br /&gt;
&lt;br /&gt;
În acest caz, primul jucător are asigurat scorul 12-8 (deci diferența 4). Pentru aceasta, el începe prin a lua numărul 2, apoi, orice ar replica celălalt, va lua numărul 10, jucătorului 2 revenindu-i așadar numerele 1 și 7. El poate obține și scorul 17-3 (jucătorul 1 ia numărul 7, celălalt ia 2, jucătorul 1 ia 10, iar celălalt ia 1), dar aceasta se întâmplă numai dacă jucătorul 2 face o greșeală. După cum am arătat mai sus, dacă primul jucător începe luând numărul 7, el pierde în mod normal partida. Iată deci că în acest caz &amp;lt;math&amp;gt;D_{MAX}=4&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Pentru a putea afla diferența maximă de scor, este bine să privim mereu în adâncime. Există patru variante în care ambii parteneri pot face câte o mutare: &lt;br /&gt;
&lt;br /&gt;
# Ambii jucători aleg numere din partea stângă;&lt;br /&gt;
# Ambii aleg numere din partea dreaptă;&lt;br /&gt;
# Primul jucător alege numărul din stânga, iar celălalt pe cel din dreapta;&lt;br /&gt;
# Primul jucător alege numărul din dreapta, iar celălalt pe cel din stânga;&lt;br /&gt;
&lt;br /&gt;
În urma oricărei variante de mutare, secvența se scurtează cu două elemente. Dacă am putea cunoaște dinainte care este rezultatul jocului pentru fiecare din secvențele scurte, am putea să decidem care variantă de joc este cea mai convenabilă pentru secvența inițială, ținând cont și de modul de joc al jucătorului al doilea. Tocmai de aici vine și ideea de rezolvare. Să notăm cu &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[1], &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[2], ..., &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;] secvența citită la intrare. Vom construi o matrice &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039; cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; linii și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; coloane, unde &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;] este diferența maximă pe care o poate obține jucătorul 1 pentru secvența &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;], &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1], ...,  &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;]. Bineînțeles, sunt luate în considerare numai secvențele de lungime pară. Scopul nostru este să-l aflăm pe &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;[1,&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;].&lt;br /&gt;
&lt;br /&gt;
Elementele matricei pe care le putem afla fără multă bătaie de cap sunt &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;[1,2], &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;[2,3], ..., &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1,&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;]. Într-adevăr, dintr-o secvență de numai două numere, primului jucător îi revine cel mai mare, iar celui de-al doilea - cel mai mic. Așadar&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;D[i,i + 1] = |A[i] - A[i + 1]|&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cum calculăm &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;] dacă cunoaștem valorile matricei &#039;&#039;&#039;&#039;&#039;D&#039;&#039;&#039;&#039;&#039; pentru toate subsecvențele incluse în secvența &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;], &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;+1], ...,  &#039;&#039;&#039;&#039;&#039;A&#039;&#039;&#039;&#039;&#039;[&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;] ? După cum am mai spus, avem patru variante:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig52.png]]&lt;br /&gt;
&lt;br /&gt;
Trebuie să ținem minte că, dacă primul jucător optează să-l aleagă pe A[i] (una din primele două variante), atunci jucătorul 2 va juca în așa fel încât pierderea să fie minimă, iar scorul final va fi &amp;lt;math&amp;gt;\min(R_1,R_2)&amp;lt;/math&amp;gt;. Dacă jucătorul 1 alege varianta 3 sau 4, scorul final va fi &amp;lt;math&amp;gt;\min(R_3,R_4)&amp;lt;/math&amp;gt;. Dar jucătorul 1 este primul la mutare, deci va alege varianta care îi maximizează profitul. Rezultatul este&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;D[i,j] = \max(\min(R_1, R_2), \min(R_3, R_4))&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
adică&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
D[i,j] = \max( &amp;amp; A[i] + \min(D[i + 2, j] - A[i + 1], D[i + 1, j - 1] - A[j]), \\&lt;br /&gt;
               &amp;amp; A[j] + \min(D[i + 1, j - 1] - A[i], D[i, j - 2] - A[j - 1]))&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Matricea D se completează pe diagonală, pornind de la diagonala principală și mergând până în colțul de N-E. Iată cum arată matricea atașată datelor de intrare din enunț:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
D =&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
X &amp;amp; 3 &amp;amp; X &amp;amp; 10 &amp;amp; X &amp;amp; 7 \\&lt;br /&gt;
X &amp;amp; X &amp;amp; 5 &amp;amp; X &amp;amp; 9 &amp;amp; X \\&lt;br /&gt;
X &amp;amp; X &amp;amp; X &amp;amp; 7 &amp;amp; X &amp;amp; 4 \\&lt;br /&gt;
X &amp;amp; X &amp;amp; X &amp;amp; X &amp;amp; 4 &amp;amp; X \\&lt;br /&gt;
X &amp;amp; X &amp;amp; X &amp;amp; X &amp;amp; X &amp;amp; 3 \\&lt;br /&gt;
X &amp;amp; X &amp;amp; X &amp;amp; X &amp;amp; X &amp;amp; X&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pentru exemplul din enunț, răspunsul este deci &amp;lt;math&amp;gt;D_{MAX}=7&amp;lt;/math&amp;gt;. Cum elementele matricei sunt parcurse cel mult o dată, rezultă o complexitate de &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;stdlib.h&amp;gt;&lt;br /&gt;
#define NMax 101&lt;br /&gt;
&lt;br /&gt;
int D[NMax][NMax], A[NMax], N;&lt;br /&gt;
&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;,&amp;quot;rt&amp;quot;);&lt;br /&gt;
  int i;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F,&amp;quot;%d\n&amp;quot;,&amp;amp;N);&lt;br /&gt;
  for (i=1; i&amp;lt;=N;)&lt;br /&gt;
    fscanf(F, &amp;quot;%d\n&amp;quot;, &amp;amp;A[i++]);&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int Min(int A, int B)&lt;br /&gt;
{&lt;br /&gt;
  return A&amp;lt;B ? A : B;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int Max(int A, int B)&lt;br /&gt;
{&lt;br /&gt;
  return A&amp;gt;B ? A : B;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void FindMax(void)&lt;br /&gt;
{ int i,j,k;&lt;br /&gt;
&lt;br /&gt;
  for (i=1;i&amp;lt;N;i++)&lt;br /&gt;
    D[i][i+1]=abs(A[i]-A[i+1]);&lt;br /&gt;
  for (k=3;k&amp;lt;=N-1;k++)&lt;br /&gt;
    for (i=1;i+k&amp;lt;=N;i++)&lt;br /&gt;
      { j=i+k;&lt;br /&gt;
        D[i][j]=Max(A[i]+Min(D[i+2][j]-A[i+1],&lt;br /&gt;
                             D[i+1][j-1]-A[j]),&lt;br /&gt;
                    A[j]+Min(D[i+1][j-1]-A[i],&lt;br /&gt;
                             D[i][j-2]-A[j-1]));&lt;br /&gt;
      }&lt;br /&gt;
  printf(&amp;quot;Diferenta maxima este %d\n&amp;quot;,D[1][N]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  FindMax();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Programul prezentat mai sus poate fi optimizat, dacă timpul o permite și dacă acest lucru este necesar. Lăsăm cititorul să încerce să rezolve aceeași problemă folosind o cantitatate de memorie direct proporțională cu &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 5 ==&lt;br /&gt;
&lt;br /&gt;
Această problemă a fost propusă la Olimpiada Națională de Informatică, Slatina 1995, la clasa a XI-a. Pe atunci programarea dinamică era o tehnică de programare destul de puțin cunoscută de către majoritatea elevilor.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: O regiune deșertică este reprezentată printr-un tablou de dimensiuni &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;x&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; (1 ≤ &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039; ≤ 100, 1 ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 100). Elementele tabloului sunt numere naturale mai mici ca 255, reprezentând diferențele de altitudine față de nivelul mării (cota 0). Să se stabilească:&lt;br /&gt;
&lt;br /&gt;
a) Un traseu pentru a traversa deșertul de la nord la sud (de la linia 1 la linia &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;), astfel:&lt;br /&gt;
&lt;br /&gt;
* Se pornește dintr-un punct al liniei 1;&lt;br /&gt;
* Deplasarea se poate face în una din direcțiile: E, SE, S, SV, V;&lt;br /&gt;
* Suma diferențelor de nivel (la urcare și la coborâre) trebuie să fie minimă.&lt;br /&gt;
&lt;br /&gt;
b) Un traseu pentru a traversa deșertul de la nord la sud în condițiile punctului (a), la care se adaugă condiția:&lt;br /&gt;
&lt;br /&gt;
* Lungimea traseului să fie minimă.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Fișierul de intrare &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; conține un singur set de date cu următoarea structură:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
| linia 1: || &#039;&#039;&#039;&#039;&#039;M N&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
| linia 2 ... linia &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039;+1: || elementele tabloului (pe linii) separate prin spații&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: Fișerul de ieșire &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt; va conține rezultatele în următorul format:&lt;br /&gt;
&lt;br /&gt;
(a)&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;suma diferențelor de nivel&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
TRASEU: &amp;lt;math&amp;gt;(i_1,j_1)\to(i_2,j_2)\to\cdots\to(i_k,j_k)&amp;lt;/math&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
(b)&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;suma diferențelor de nivel&amp;gt; &amp;lt;lungime traseu&amp;gt;&amp;lt;br&amp;gt;&lt;br /&gt;
TRASEU: &amp;lt;math&amp;gt;(i_1,j_1)\to(i_2,j_2)\to\cdots\to(i_p,j_p)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
unde &amp;lt;math&amp;gt;i_x&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;j_x&amp;lt;/math&amp;gt; sunt linia și coloana fiecărei celule vizitate.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
{|class=&amp;quot;wikitable&lt;br /&gt;
! &amp;lt;tt&amp;gt;INPUT.TXT&amp;lt;/tt&amp;gt; ||  &amp;lt;tt&amp;gt;OUTPUT.TXT&amp;lt;/tt&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tt&amp;gt;4 4&amp;lt;br&amp;gt;&lt;br /&gt;
10 7 2 5&amp;lt;br&amp;gt;&lt;br /&gt;
13 20 25 3&amp;lt;br&amp;gt;&lt;br /&gt;
2 4 2 20&amp;lt;br&amp;gt;&lt;br /&gt;
5 10 9 11&amp;lt;br&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
| &amp;lt;tt&amp;gt;(a)&amp;lt;br&amp;gt;&lt;br /&gt;
5&amp;lt;br&amp;gt;&lt;br /&gt;
TRASEU: (1,3)-&amp;gt;(2,4)-&amp;gt;(3,3)-&amp;gt;(3,2)-&amp;gt;(4,1)&amp;lt;br&amp;gt;&lt;br /&gt;
(b)&amp;lt;br&amp;gt;&lt;br /&gt;
9 3&amp;lt;br&amp;gt;&lt;br /&gt;
TRASEU: (1,3)-&amp;gt;(2,4)-&amp;gt;(3,3)-&amp;gt;(4,2)&amp;lt;br&amp;gt;&amp;lt;/tt&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Acesta a fost enunțul original. Iată acum completările propuse și o precizare importantă:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Timpul de implementare&#039;&#039;&#039;: 45 minute - 1h (la concurs a fost cam 1h 30 min);&lt;br /&gt;
* &#039;&#039;&#039;Timpul de rulare&#039;&#039;&#039;: 2-3 secunde;&lt;br /&gt;
* &#039;&#039;&#039;Complexitatea cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;;&lt;br /&gt;
* La punctul (b), condiția nou adăugată este mai puternică decât cea de la punctul (a). Cu alte cuvinte, în primul rând contează lungimea drumului și abia apoi, dintre toate drumurile de lungime minimă, trebuie ales cel pentru care suma denivelărilor este minimă. Pentru a vă convinge că ordinea în care sunt impuse condițiile este importantă, să privim exemplul de mai sus. Dacă este mai importantă minimizarea sumei denivelărilor, atunci minimul este 5, iar drumul este soluția de la punctul (a). Dacă este mai importantă minimizarea lungimii drumului, atunci lungimea minimă este 3, iar din toate drumurile de lungime 3, cel mai puțin costisitor este cel indicat la punctul (b).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Vom lăsa punctul (b) al acestei probleme în seama cititorului, întrucât el nu este altceva decât o simplificare a punctului (a). Să ne ocupăm acum de punctul (a). Vom numi efort diferența de altitudine (în modul) la deplasarea cu un pas. Scopul este deci găsirea unor drumuri de efort total minim. Matricea de altitudini o vom nota cu &amp;lt;tt&amp;gt;Alt&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
O primă posibilitate de abordare a problemei este „greedy”, dar aceasta nu e cea mai fericită alegere, chiar dacă este una comodă. Ideea de bază este următoarea: Încercăm să pornim din colțul de NV și să ne deplasăm la fiecare pas pe acea direcție pentru care efortul este minim, până ajungem la ultima linie. Apoi pornim din a doua coloană a primei linii și aplicăm aceeași tactică, apoi din a treia coloană și așa mai departe până la colțul de NE. În final tipărim soluția cea mai bună găsită. Iată însă un exemplu pe care această metodă dă greș:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
Alt=&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
2 &amp;amp; 1 &amp;amp; 2 \\&lt;br /&gt;
10 &amp;amp; 1 &amp;amp; 10 \\&lt;br /&gt;
10 &amp;amp; 10 &amp;amp; 10&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pe această matrice, algoritmul greedy va găsi traseele (1,1) → (2,2) → (3,2) de efort total 10, (1,2) → (2,2) → (3,2) de efort total 9 și (3,1) → (2,2) → (3,2) de efort total 10. Așadar, rezultatul optim ar fi 9, ceea ce este fals deoarece alegerea traseului (1,1) → (2,1) → (3,1) ar duce la un efort total de 8, deci mai mic.&lt;br /&gt;
&lt;br /&gt;
Motivul pentru care acest algoritm nu funcționează cum trebuie este că el nu privește în perspectivă. În cazul de mai sus, coborârea în „văile” de altitudine 1 era o primă mutare tentantă, dar fără nici un rezultat, deoarece până la urmă tot era necesară suirea la altitudinea 10. Soluția corectă este ca, pentru a afla efortul minim cu care se poate ajunge la o locație oarecare, să analizăm toate drumurile care duc la acea locație. Dacă am cunoaște efortul minim cu care se poate ajunge la fiecare din vecinii din E, NE, N, NV, și V ai unei celule, atunci putem cu ușurință, pe baza unor comparații, să deducem din ce parte este cel mai avantajos să venim în respectiva celulă și cu ce efort minim.&lt;br /&gt;
&lt;br /&gt;
Mai concret, vom construi o matrice cu aceleași dimensiuni ca și matricea &amp;lt;tt&amp;gt;Alt&amp;lt;/tt&amp;gt;, pe care o vom denumi &amp;lt;tt&amp;gt;Eff&amp;lt;/tt&amp;gt;. În această matrice, &amp;lt;tt&amp;gt;Eff[i,j]&amp;lt;/tt&amp;gt; reprezintă efortul minim necesar pentru a ajunge de pe un punct oarecare de pe linia 1 în celula (&#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039;). Deducem că &amp;lt;tt&amp;gt;Eff[1,j]&amp;lt;/tt&amp;gt;=0, ∀ 1 ≤ &#039;&#039;&#039;&#039;&#039;j&#039;&#039;&#039;&#039;&#039; ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;. Noi trebuie să completăm matricea &amp;lt;tt&amp;gt;Eff&amp;lt;/tt&amp;gt;, apoi să căutăm minimul dintre toate elementele de pe linia &#039;&#039;&#039;&#039;&#039;M&#039;&#039;&#039;&#039;&#039; (care este chiar efortul minim căutat) și să reconstituim traseul de urmat.&lt;br /&gt;
&lt;br /&gt;
Ca să vedem cum anume se face completarea matricei, facem mai întâi observația că, odată ce am ajuns pe o linie, putem fie să coborâm direct pe linia imediat inferioară, fie să ne deplasăm câțiva pași numai spre stânga sau numai spre dreapta, apoi să coborâm pe linia următoare. În orice locație (&#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;) a matricei putem veni dinspre E, NE, N, NV, sau V. Pentru acești cinci vecini presupunem deja calculate eforturile minime necesare, respectiv &amp;lt;tt&amp;gt;Eff[X,Y+1], Eff[X-1,Y+1], Eff[X-1,Y], Eff[X-1,Y-1], Eff[X,Y-1]&amp;lt;/tt&amp;gt;. Atunci, în funcție de direcția din care venim, efortul depus până la punctul (&#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;) va fi:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| dinspre est:       || &amp;lt;tt&amp;gt;Eff[X,Y+1]+ |Alt[X,Y+1] - Alt[X,Y]|&amp;lt;/tt&amp;gt;; || (1)&lt;br /&gt;
|-&lt;br /&gt;
| dinspre nord-est:  || &amp;lt;tt&amp;gt;Eff[X-1,Y+1]+ |Alt[X-1,Y+1] - Alt[X,Y]|&amp;lt;/tt&amp;gt;; || (2)&lt;br /&gt;
|-&lt;br /&gt;
| dinspre nord:      || &amp;lt;tt&amp;gt;Eff[X-1,Y]+ |Alt[X-1,Y] - Alt[X,Y]|&amp;lt;/tt&amp;gt;; || (3)&lt;br /&gt;
|-&lt;br /&gt;
| dinspre nord-vest: || &amp;lt;tt&amp;gt;Eff[X-1,Y-1]+ |Alt[X-1,Y-1] - Alt[X,Y]|&amp;lt;/tt&amp;gt;; ||  (4)&lt;br /&gt;
|-&lt;br /&gt;
| dinspre vest:      || &amp;lt;tt&amp;gt;Eff[X,Y-1]+ |Alt[X,Y-1] - Alt[X,Y]|;&amp;lt;/tt&amp;gt; ||  (5)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
În principiu, nu avem decât să calculăm minimul dintre aceste expresii ca să aflăm valoarea lui &amp;lt;tt&amp;gt;Eff[X,Y]&amp;lt;/tt&amp;gt;. În felul acesta, matricea &amp;lt;tt&amp;gt;Eff&amp;lt;/tt&amp;gt; se va completa pe linie, de sus în jos. Totuși, apare o problemă: pentru a-l afla pe &amp;lt;/tt&amp;gt;Eff[X,Y]&amp;lt;/tt&amp;gt; avem nevoie de &amp;lt;tt&amp;gt;Eff[X,Y-1]&amp;lt;/tt&amp;gt; (dacă ne deplasăm spre est), iar pentru a-l afla pe &amp;lt;tt&amp;gt;Eff[X,Y-1]&amp;lt;/tt&amp;gt; avem nevoie de &amp;lt;tt&amp;gt;Eff[X,Y]&amp;lt;/tt&amp;gt; (dacă ne deplasăm spre vest)! Bineînțeles, avem sentimentul că ne învârtim după propria coadă. Totuși, dezlegarea nu e complicată, ținând cont de observația făcută mai sus, că pe aceeași linie deplasarea se face într-o singură direcție. Este suficient să parcurgem fiecare linie de două ori: prima oară o parcurgem de la stânga la dreapta, în ipoteza că deplasarea pe linia respectivă se face spre est, apoi încă o dată de la dreapta la stânga, în ipoteza că deplasarea pe linia respectivă se face spre vest. La prima parcurgere, vom minimiza efortul pentru fiecare căsuță cu expresia (5), iar la a doua - cu expresia (1). Minimizarea cu expresiile (2), (3) și (4) se poate face la oricare din parcurgeri, deoarece elementele liniei superioare nu se mai modifică.&lt;br /&gt;
&lt;br /&gt;
După cum am spus, efortul minim se obține căutând minimul de pe ultima linie a matricei &amp;lt;tt&amp;gt;Eff&amp;lt;/tt&amp;gt; (aceasta deoarece nu contează în ce punct de pe ultima linie este sosirea). Punctul în  care se atinge acest minim este tocmai punctul de sosire. Reconstituirea efectivă a drumului se face în sens invers: se pleacă din  punctul de sosire &amp;lt;math&amp;gt;(i_k,j_k)&amp;lt;/math&amp;gt; și se caută un punct vecin lui pe una din cele cinci direcții permise, &amp;lt;math&amp;gt;(i_{k-1},j_{k-1})&amp;lt;/math&amp;gt; astfel încât&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\mathit{Eff}[i_k,j_k] = \mathit{Eff}[i_{k-1},j_{k-1}] + |Alt[i_k,j_k] - Alt[i_{k-1},j_{k-1}]|&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cu alte cuvinte, se testează pentru care din expresiile (1) - (5) se verifică egalitatea. Se reia, recursiv, același procedeu pentru locația &amp;lt;math&amp;gt;(i_{k-1},j_{k-1})&amp;lt;/math&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Iată cum se completează matricea &amp;lt;tt&amp;gt;Eff&amp;lt;/tt&amp;gt; pentru exemplul dat și cum se reconstituie drumul:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
Alt =&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
10 &amp;amp;  7 &amp;amp;  2 &amp;amp;  5 \\&lt;br /&gt;
13 &amp;amp; 20 &amp;amp; 25 &amp;amp;  3 \\&lt;br /&gt;
 2 &amp;amp;  4 &amp;amp;  2 &amp;amp; 20 \\&lt;br /&gt;
 5 &amp;amp; 10 &amp;amp;  9 &amp;amp; 11&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\mathit{Eff} =&lt;br /&gt;
\begin{pmatrix}&lt;br /&gt;
 0 &amp;amp;  0 &amp;amp;  0 &amp;amp;  0 \\&lt;br /&gt;
 3 &amp;amp; 10 &amp;amp; 15 &amp;amp;  1 \\&lt;br /&gt;
 6 &amp;amp;  4 &amp;amp;  2 &amp;amp; 18 \\&lt;br /&gt;
 5 &amp;amp; 10 &amp;amp;  9 &amp;amp; 11&lt;br /&gt;
\end{pmatrix}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Minimul de pe linia a 4-a a matricei &amp;lt;tt&amp;gt;Eff&amp;lt;/tt&amp;gt; este &amp;lt;tt&amp;gt;Eff[4,1]=5&amp;lt;/tt&amp;gt;, deci sosirea se face în colțul de SV. Din ce parte am ajuns aici ? Se testează toți vecinii și se constată că &amp;lt;tt&amp;gt;Eff[4,1] = Eff[3,2] + |Alt[4,1] - Alt[3,2]|&amp;lt;/tt&amp;gt;, deci s-a venit de la locația (3,2). Apoi se constată că:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;tt&amp;gt;Eff[3,2] = Eff[3,3] + |Alt[3,2] - Alt[3,3]|&amp;lt;/tt&amp;gt;;&lt;br /&gt;
* &amp;lt;tt&amp;gt;Eff[3,3] = Eff[2,4] + |Alt[3,3] - Alt[2,4]|&amp;lt;/tt&amp;gt;;&lt;br /&gt;
* &amp;lt;tt&amp;gt;Eff[2,4] = Eff[1,3] + |Alt[2,4] - Alt[1,3]|&amp;lt;/tt&amp;gt;;&lt;br /&gt;
&lt;br /&gt;
Din aceste relații rezultă că traseul urmat este (1,3) → (2,4) → (3,3) → (3,2) → (4,1).&lt;br /&gt;
&lt;br /&gt;
Pentru o mai mare ușurință a implementării, se vor adăuga două coloane fictive la matricea &amp;lt;tt&amp;gt;Alt&amp;lt;/tt&amp;gt;: coloanele 0 și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+1. Facem acest lucru pentru a ne putea referi la celula (&#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;-1) atunci când (&#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;) este o celulă din prima coloană (respectiv la celula (&#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;+1) atunci când (&#039;&#039;&#039;&#039;&#039;X&#039;&#039;&#039;&#039;&#039;,&#039;&#039;&#039;&#039;&#039;Y&#039;&#039;&#039;&#039;&#039;) este o celulă de pe ultima coloană) fără a primi un mesaj de eroare. Trebuie însă să fim atenți ca nu cumva noile coloane adăugate să perturbe datele de ieșire și să rezulte că traseul optim trece prin coloana 0 sau &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;+1. Pentru a scăpa de grija celulelor de pe aceste două coloane și a ne asigura că ele nu vor putea fi selectate pentru traseul optim, le vom atribui altitudini foarte mari. Deoarece diferența maximă de nivel la fiecare pas este 255, rezultă că efortul total maxim ce se poate obține este 255 x 99 = 25.245. Așadar, o altitudine a coloanelor laterale de 30.000 este suficientă.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;math.h&amp;gt;&lt;br /&gt;
#define NMax 101&lt;br /&gt;
#define Infinity 30000&lt;br /&gt;
typedef int Matrix[NMax][NMax+1];&lt;br /&gt;
&lt;br /&gt;
Matrix Alt, Eff;&lt;br /&gt;
int M, N;&lt;br /&gt;
FILE *OutF;&lt;br /&gt;
&lt;br /&gt;
void ReadData(void)&lt;br /&gt;
{ FILE *F=fopen(&amp;quot;input.txt&amp;quot;, &amp;quot;rt&amp;quot;);&lt;br /&gt;
  int i,j;&lt;br /&gt;
&lt;br /&gt;
  fscanf(F, &amp;quot;%d %d\n&amp;quot;, &amp;amp;M, &amp;amp;N);&lt;br /&gt;
  for (i=1; i&amp;lt;=M; i++)&lt;br /&gt;
    for (j=1; j&amp;lt;=N; j++)&lt;br /&gt;
      fscanf(F, &amp;quot;%d&amp;quot;, &amp;amp;Alt[i][j]);&lt;br /&gt;
  fclose(F);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Optimize(int X1, int Y1, int X2, int Y2)&lt;br /&gt;
/* Testeaza daca in (X1,Y1) se poate ajunge&lt;br /&gt;
   cu efort mai mic dinspre (X2,Y2) */&lt;br /&gt;
{&lt;br /&gt;
  if (Eff[X2][Y2]+abs(Alt[X1][Y1]-Alt[X2][Y2])&amp;lt;Eff[X1][Y1])&lt;br /&gt;
    Eff[X1][Y1]=Eff[X2][Y2]+abs(Alt[X1][Y1]-Alt[X2][Y2]);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Traverse(void)&lt;br /&gt;
{ int i,j;&lt;br /&gt;
&lt;br /&gt;
  for (j=1; j&amp;lt;=N;) Eff[1][j++]=0;&lt;br /&gt;
  for (i=1; i&amp;lt;=M; i++)&lt;br /&gt;
    Eff[i][0]=Eff[i][N+1]=Infinity; /* Bordeaza matricea */&lt;br /&gt;
  for (i=2; i&amp;lt;=M; i++)&lt;br /&gt;
    {&lt;br /&gt;
      for (j=1; j&amp;lt;=N; j++)&lt;br /&gt;
        {&lt;br /&gt;
          Eff[i][j]=Infinity;&lt;br /&gt;
          Optimize(i, j, i-1, j);       /* De la N  */&lt;br /&gt;
          Optimize(i, j, i-1, j-1);     /* De la NV */&lt;br /&gt;
          Optimize(i, j, i-1, j+1);     /* De la NE */&lt;br /&gt;
          Optimize(i, j, i, j-1);       /* De la V  */&lt;br /&gt;
        }&lt;br /&gt;
      for (j=N; j; j--)&lt;br /&gt;
        Optimize(i, j, i, j+1);         /* De la E  */&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void GoBack(int X, int Y)&lt;br /&gt;
/* Reconstituie drumul */&lt;br /&gt;
{&lt;br /&gt;
  if (X&amp;gt;1)&lt;br /&gt;
    if (Eff[X][Y]==Eff[X][Y-1]&lt;br /&gt;
                   +abs(Alt[X][Y-1]-Alt[X][Y]))&lt;br /&gt;
    GoBack(X, Y-1);&lt;br /&gt;
    else if (Eff[X][Y]==Eff[X-1][Y-1]&lt;br /&gt;
                        +abs(Alt[X-1][Y-1]-Alt[X][Y]))&lt;br /&gt;
    GoBack(X-1, Y-1);&lt;br /&gt;
    else if (Eff[X][Y]==Eff[X-1][Y]&lt;br /&gt;
                        +abs(Alt[X-1][Y]-Alt[X][Y]))&lt;br /&gt;
    GoBack(X-1, Y);&lt;br /&gt;
    else if (Eff[X][Y]==Eff[X-1][Y+1]&lt;br /&gt;
                        +abs(Alt[X-1][Y+1]-Alt[X][Y]))&lt;br /&gt;
    GoBack(X-1, Y+1);&lt;br /&gt;
    else if (Eff[X][Y]==Eff[X][Y+1]&lt;br /&gt;
                        +abs(Alt[X][Y+1]-Alt[X][Y]))&lt;br /&gt;
    GoBack(X, Y+1);&lt;br /&gt;
  if (X&amp;gt;1) fprintf(OutF, &amp;quot;-&amp;gt;&amp;quot;);&lt;br /&gt;
  fprintf(OutF,&amp;quot;(%d,%d)&amp;quot;, X, Y);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void WriteSolution(void)&lt;br /&gt;
{ int j,k;&lt;br /&gt;
&lt;br /&gt;
  OutF=fopen(&amp;quot;output.txt&amp;quot;, &amp;quot;wt&amp;quot;);&lt;br /&gt;
  /* Cauta punctul de sosire */&lt;br /&gt;
  fputs(&amp;quot;(a)\n&amp;quot;,OutF);&lt;br /&gt;
  for (j=2, k=1; j&amp;lt;=N; j++)&lt;br /&gt;
    if (Eff[M][j]&amp;lt;Eff[M][k]) k=j;&lt;br /&gt;
  fprintf(OutF, &amp;quot;%d\n&amp;quot;, Eff[M][k]);&lt;br /&gt;
  fputs(&amp;quot;TRASEU: &amp;quot;,OutF);&lt;br /&gt;
  GoBack(M, k);&lt;br /&gt;
  fprintf(OutF,&amp;quot;\n&amp;quot;);&lt;br /&gt;
  fclose(OutF);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  ReadData();&lt;br /&gt;
  Traverse();&lt;br /&gt;
  WriteSolution();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Problema 6 ==&lt;br /&gt;
&lt;br /&gt;
Propunem în continuare o problemă care s-a dat la Olimpiada Națională de Informatică, Suceava 1996, la clasa a XII-a. Menționăm că un singur concurent a reușit să o ducă la bun sfârșit în timpul concursului. Problema se numește „Cartierul Enicbo”.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;ENUNȚ&#039;&#039;&#039;: În orașul Acopan s-a construit un nou cartier. Noul cartier are patru bulevarde paralele și un număr de &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; străzi perpendiculare pe ele. Există deci în total 4&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; intersecții. Furgoneta oficiului poștal trebuie să distribuie poșta în fiecare zi; în acest scop, furgoneta pleacă de la oficiul poștal aflat la intersecția bulevardului 1 cu strada 1 și, urmând rețeaua stradală, trece exact o dată prin fiecare intersecție astfel încât să încheie traseul în punctul de plecare.&lt;br /&gt;
&lt;br /&gt;
Conducerea oficiului poștal roagă participanții la olimpiadă să o ajute să afle în câte moduri distincte se poate stabili traseul furgonetei.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Intrarea&#039;&#039;&#039;: Programul va citi de la tastatură valoarea lui &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; (2 ≤ &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; ≤ 200).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ieșirea&#039;&#039;&#039;: Pe ecran se va afișa soluția (numărul de trasee distincte pentru valoarea respectivă a lui &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Exemplu&#039;&#039;&#039;: Pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=3 există 4 soluții (se citește de la tastatură numărul 3 și se afișează pe ecran numărul 4). Iată soluțiile efective:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig53.png]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de execuție&#039;&#039;&#039;: 30 secunde pentru un text&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Timp de implementare&#039;&#039;&#039;: 1h 30 min.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Complexitate cerută&#039;&#039;&#039;: &amp;lt;math&amp;gt;O(N^2)&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;REZOLVARE&#039;&#039;&#039;: Primul lucru care ne vine în gând este „se cere numărul de cicluri hamiltoniene într-un graf, deci problema e exponențială”. Rezolvarea backtracking nu e deloc greu de implementat, dar nu are nici o șansă să meargă pentru valori mari ale lui &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;. Afirmația de mai sus este corectă, dar incompletă; din această cauză concluzia este falsă. Se scapă din vedere faptul că graful nu este oarecare, ci are un aspect foarte particular.&lt;br /&gt;
&lt;br /&gt;
Și în această problemă vom încerca să utilizăm soluțiile locale (pentru valori mici ale lui &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;) pentru aflarea soluției globale. Respectiv, vom rezolva problema pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=2, apoi o vom extinde pentru  &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=3, 4 și așa mai departe. Pentru început, însă, încercăm să simplificăm enunțul, reducând problema la una echivalentă, dar mai simplă.&lt;br /&gt;
&lt;br /&gt;
Să considerăm o posibilă soluție pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=5:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig54.png]]&lt;br /&gt;
&lt;br /&gt;
În loc să lucrăm cu segmente în această rețea, vom lucra cu ochiuri. Furgoneta parcurge un ciclu, deci închide în circuitul ei un număr de ochiuri. Am marcat aceste ochiuri cu un „X” în figura de mai sus. Așadar, oricărui drum al furgonetei i se poate atașa o matrice cu 3 linii și &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 coloane, în care unele celule sunt bifate cu „X”, iar altele nu. Să vedem în primul rând care este corespondența între numărul de circuite hamiltoniene și numărul de matrice de acest tip.&lt;br /&gt;
&lt;br /&gt;
Se observă că pentru orice circuit există un altul căruia îi este atașată aceeași matrice. Circuitul pereche este tocmai circuitul parcurs în sens invers, care închide în interior aceleași ochiuri de rețea:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig55.png]]&lt;br /&gt;
&lt;br /&gt;
Acest lucru se întâmplă deoarece transformarea graf-matrice ignoră sensul de parcurgere a circuitului hamiltonian. De aici rezultă că pentru a calcula numărul de circuite hamiltoniene trebuie să calculăm numărul de matrice și să-l înmulțim cu 2.&lt;br /&gt;
&lt;br /&gt;
În continuare, să analizăm câteva proprietăți ale matricelor în discuție.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1.&#039;&#039;&#039; Elementele bifate cu „X” în matrice formează o singură figură conexă.&lt;br /&gt;
&lt;br /&gt;
Demonstrație: dacă figura nu ar fi conexă, adică dacă ar exista mai multe figuri, ele nu ar putea fi înconjurate de furgonetă într-un singur drum. De remarcat că toate pătratele înconjurate de furgonetă trebuie bifate cu X, deci furgoneta nu poate înconjura pătrate nebifate. De aceea, traseul de mai jos (care prezintă o porțiune oarecare de circuit) este imposibil.&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig56.png]]&lt;br /&gt;
&lt;br /&gt;
Conexitatea se referă numai la vecinătatea pe latură, nu și pe colț. Spre exemplu, figura de mai jos este incorectă, deoarece, pentru a o înconjura, furgoneta trebuie să treacă de două ori prin punctul încercuit:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig57.png]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;2.&#039;&#039;&#039; Elementele bifate cu „X” formează o structură aciclică.&lt;br /&gt;
&lt;br /&gt;
Demonstrație: dacă structura ar fi ciclică, ar rezulta că elementele bifate cu „X” închid între ele elemente nebifate, pe care furgoneta însă nu poate să le ocolească. Iată un exemplu de ciclicitate:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig58.png]]&lt;br /&gt;
&lt;br /&gt;
Practic, situația de mai sus obligă furgoneta să facă două drumuri: unul pe exterior și unul în jurul ochiului marcat cu „?”.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;3.&#039;&#039;&#039; Nici un nod interior al rețelei nu poate avea toate cele patru ochiuri vecine marcate cu „X”.&lt;br /&gt;
&lt;br /&gt;
Demonstrație: dacă ar exista un asemenea nod, el nu ar putea fi parcurs de furgonetă, deci ciclul nu ar mai fi hamiltonian. Este cazul nodului încercuit în figura următoare:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig59.png]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;4.&#039;&#039;&#039; Structura elementelor bifate cu „X” în cadrul matricei este arborescentă.&lt;br /&gt;
&lt;br /&gt;
Demonstrația rezultă imediat din punctele anterioare: figura este conexă și aciclică.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;5.&#039;&#039;&#039; Numărul de celule bifate este P = 2&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1.&lt;br /&gt;
&lt;br /&gt;
Demonstrația se face prin inducție matematică. Să presupunem că structura noastră ar avea un singur pătrat bifat. Atunci structura ar avea patru laturi „la vedere”. Traseul furgonetei care ocolește structura ar avea patru laturi. O structură de două pătrate (desigur lipite) va avea șase laturi la vedere:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig60.png]]&lt;br /&gt;
&lt;br /&gt;
Să ne imaginăm acum că orice structură cu &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; pătrate are &amp;lt;math&amp;gt;S_k&amp;lt;/math&amp;gt; laturi la vedere. Trebuie să demonstrăm că toate structurile cu &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;+1 pătrate au același număr de laturi la vedere și să aflăm efectiv acest număr, &amp;lt;math&amp;gt;S_{k+1}&amp;lt;/math&amp;gt;. Cel de-al &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;+1-lea pătrat trebuie alipit la structura deja existentă în așa fel încât să nu se închidă nici un ciclu. El se va lipi deci de o latură la vedere a unui pătrat din structură. În acest fel, va dispărea o latură la vedere, dar vor apărea trei în loc. Numărul de laturi la vedere va crește prin urmare cu 2. Această cifră nu depinde de locul în care este alipit al k+1-lea pătrat, nici de forma structurii deja existente, deci am demonstrat că toate structurile arborescente cu k pătrate au același număr de laturi la vedere. Pentru a afla efectiv acest număr, pornim de la relațiile recurente stabilite prin inducție și eliminăm recurența:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
S_{k + 1} &amp;amp; = S_k + 2 \\&lt;br /&gt;
S_1 &amp;amp; = 4&lt;br /&gt;
\end{align}&lt;br /&gt;
\Biggr\}&lt;br /&gt;
\implies S_k = 2k + 2&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Deoarece numărul total de noduri al rețelei este de 4&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;, rezultă că structura noastră trebuie să aibă 4&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039; laturi la vedere. Notând cu &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039; numărul de pătrate bifate din matrice și rezolvând ecuația de mai jos, rezultă valoarea lui &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
2P + 2 = 4N \implies P = 2N - 1 = 2(N - 1) + 1&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Cum numărul de coloane al matricei este &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1, deducem că în medie pe fiecare coloană se vor afla două pătrate bifate, cu excepția uneia pe care se vor afla trei pătrate bifate. La nivel local, proprietatea este de asemenea respectată: numărul de pătrate bifate din primele &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; coloane ale matricei este 2&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;, existând posibilitatea să mai fie un pătrat suplimentar. De exemplu, în figura dată mai sus pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=5, în primele două coloane se află patru elemente „X”, deci o medie de două pătrate pe fiecare coloană. În primele trei coloane există șapte elemente „X”, adică o medie de două pătrate pe coloană și un surplus de un pătrat. Lăsăm ca temă cititorului să demonstreze că, în primele &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; coloane există întotdeauna fie 2&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;, fie 2&#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;+1 pătrate. Orice număr mai mare duce la ciclicitatea figurii, orice număr mai mic duce la neconexitatea ei.&lt;br /&gt;
&lt;br /&gt;
Pe fiecare coloană există opt combinații posibile de elemente bifate și nebifate, pe care le vom codifica cu numere de la 0 la 7, conform unei numărători binare:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig61.png]]&lt;br /&gt;
&lt;br /&gt;
Să vedem acum care dintre aceste combinații rămân valabile. O coloană de tipul 0 nu poate exista, deoarece ea ar „rupe” matricea în două bucăți separate, deci proprietatea de conexitate nu ar fi respectată.&lt;br /&gt;
&lt;br /&gt;
Dacă pe coloana &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; se află o combinație de tipul 3, ce s-ar putea afla pe coloana &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;+1 ?&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig62.png]]&lt;br /&gt;
&lt;br /&gt;
Pentru ca punctul A să se afle pe traseul furgonetei, este obligatoriu să bifăm pătratul de sub el. Pentru a menține conexitatea figurii apărute, trebuie bifat și pătratul din centrul coloanei &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;+1. Cea de-a treia celulă a coloanei &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;+1 nu poate fi bifată, deoarece punctul B ar fi înconjurat din patru părți de celule bifate, lucru care s-a stabilit că este imposibil. Se vede că singura combinație posibilă pentru coloana &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;+1 este 6. Ce combinație putem pune pe coloana k+2 ? Printr-un raționament analog, deducem că numai combinația 3:&lt;br /&gt;
&lt;br /&gt;
[[File:Psycho-fig63.png]]&lt;br /&gt;
&lt;br /&gt;
Iată că, pentru a putea respecta condițiile de corectitudine a matricei, am fi nevoiți să continuăm la nesfârșit cu coloane cu combinațiile 3-6-3-6 etc. Deci niciuna din aceste combinații nu poate apărea în matrice.&lt;br /&gt;
&lt;br /&gt;
În continuare, vom defini mai multe șiruri de forma &amp;lt;math&amp;gt;S_k(i,t)&amp;lt;/math&amp;gt;, unde:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; este numărul unei coloane;&lt;br /&gt;
* &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039; este un număr de combinație (respectiv 1, 2, 4, 5 sau 7);&lt;br /&gt;
* &#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039; este un număr care poate avea valoarea 0 sau 1.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;S_k(i,t)&amp;lt;/math&amp;gt; semnifică „numărul de matrice (corecte) cu k coloane astfel încât pe coloana cu numărul &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; să se afle combinația &#039;&#039;&#039;&#039;&#039;i&#039;&#039;&#039;&#039;&#039;, iar surplusul de pătrate bifate peste media de două pătrate pe fiecare coloană să fie &#039;&#039;&#039;&#039;&#039;t&#039;&#039;&#039;&#039;&#039;”. De exemplu, &amp;lt;math&amp;gt;S_7(5,1)&amp;lt;/math&amp;gt; reprezintă numărul de matrice corecte (care respectă regulile de construcție) cu 7 coloane, astfel încât pe ultima coloană să se afle combinația 5 și să existe un surplus de 1 pătrat (adică numărul total de pătrate să fie 2 x 7 + 1 = 15).&lt;br /&gt;
&lt;br /&gt;
Facem observația că pe a &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1-a coloană se pot afla doar combinațiile 5 sau 7 (pentru a acoperi colțurile de NE și SE ale grafului), iar surplusul de pătrate trebuie să fie 1 (deoarece în &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 coloane trebuie să se afle &#039;&#039;&#039;&#039;&#039;P&#039;&#039;&#039;&#039;&#039;=2(&#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1)+1 pătrate bifate). Deci scopul nostru este să calculăm suma &amp;lt;math&amp;gt;S_{N-1}(5,1) + S_{N-1}(7,1)&amp;lt;/math&amp;gt; și să o înmulțim cu 2 ca să aflăm numărul de cicluri hamiltoniene.&lt;br /&gt;
&lt;br /&gt;
De asemenea, remarcăm că șirurile &amp;lt;math&amp;gt;S_k(1,1)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;S_k(2,1)&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;S_k(4,1)&amp;lt;/math&amp;gt; nu sunt definite. Aceasta deoarece combinațiile 1, 2 și 4 au un singur pătrat bifat pe coloană, adică mai puțin decât media de două pătrate. Este imposibil ca după adăugarea unei asemenea coloane să mai existe un surplus. (deoarece ar rezulta că în primele &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;-1 coloane exista un surplus de două pătrate). La polul opus, șirul &amp;lt;math&amp;gt;S_k(7,0)&amp;lt;/math&amp;gt; nu este definit, deoarece combinația 7 are toată coloana bifată, adică peste medie, deci nu se poate să nu apară un surplus de pătrate bifate.&lt;br /&gt;
&lt;br /&gt;
Mai trebuie stabilite formulele de recurență între șirurile &amp;lt;math&amp;gt;S_k(1,0)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;S_k(2,0)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;S_k(4,0)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;S_k(5,0)&amp;lt;/math&amp;gt;, &amp;lt;math&amp;gt;S_k(5,1)&amp;lt;/math&amp;gt; și &amp;lt;math&amp;gt;S_k(7,1)&amp;lt;/math&amp;gt;. Termenii inițiali ai recurenței sunt:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;math&amp;gt;S_1(1,0)=0&amp;lt;/math&amp;gt; deoarece matricea nu poate începe cu combinația 1&lt;br /&gt;
* &amp;lt;math&amp;gt;S_1(2,0)=0&amp;lt;/math&amp;gt; deoarece matricea nu poate începe cu combinația 2&lt;br /&gt;
* &amp;lt;math&amp;gt;S_1(4,0)=0&amp;lt;/math&amp;gt; deoarece matricea nu poate începe cu combinația 4&lt;br /&gt;
* &amp;lt;math&amp;gt;S_1(5,0)=1&amp;lt;/math&amp;gt; deoarece există o singură matrice de o coloană cu combinația 5&lt;br /&gt;
* &amp;lt;math&amp;gt;S_1(5,1)=0&amp;lt;/math&amp;gt; deoarece combinația 5 are două pătrate, deci nu există surplus&lt;br /&gt;
* &amp;lt;math&amp;gt;S_1(7,1)=1&amp;lt;/math&amp;gt; deoarece există o singură matrice de o coloană cu combinația 7&lt;br /&gt;
&lt;br /&gt;
Pentru a stabili relația de recurență pentru șirul &amp;lt;math&amp;gt;S_k(1,0)&amp;lt;/math&amp;gt;, ne întrebăm: cărei coloane îi poate urma coloana &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; de tip 1 astfel încât să nu mai existe surplus ? Dacă observăm că pe coloana &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039; avem un singur element bifat (deci sub medie), rezultă că pe coloana &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;-1 exista un surplus de un pătrat. Deci coloana &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;-1 putea fi de tipul 5 sau 7, acestea fiind singurele tipuri de coloană după care poate exista un surplus. Rezultă formula:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
S_k(1,0)=S_{k-1}(5,1)+S_{k-1}(7,1)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Printr-o simetrie perfectă se calculează aceeași formulă și pentru șirul &amp;lt;math&amp;gt;S_k(4,0)&amp;lt;/math&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
S_k(4,0)=S_{k-1}(5,1)+S_{k-1}(7,1)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La șirul S_k(2,0), mai trebuie făcută observația că o coloană de tip 2 nu poate urma unei coloane de tip 5, deoarece se strică conexitatea figurii. Rezultă:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
S_k(2,0)=S_{k-1}(7,1)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Șirul &amp;lt;math&amp;gt;S_k(5,0)&amp;lt;/math&amp;gt; provine din adăugarea unei coloane de tipul 5 după o coloană de tipul 1, 4 sau 5. Coloana &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;-1 nu poate fi de tipul 2 deoarece figura rezultată nu este conexă, nici de tipul 7 deoarece ar rezulta că în primele &#039;&#039;&#039;&#039;&#039;k&#039;&#039;&#039;&#039;&#039;-2 coloane media de celule bifate este mai mică decât 2.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
S_k(5,0)=S_{k-1}(1,0)+S_{k-1}(4,0)+S_{k-1}(5,0)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Șirul &amp;lt;math&amp;gt;S_k(5,1)&amp;lt;/math&amp;gt; provine din adăugarea unei coloane de tipul 5 după o coloană de tipul 5 sau 7, deoarece tipul de coloană 5 are două pătrate bifate, deci conservă surplusul:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
S_k(5,1)=S_{k-1}(5,1)+S_{k-1}(7,1)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
În sfârșit, o coloană de tip 7 poate urma oricărui tip de coloană pentru care surplusul este 0, adică:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
S_k(7,1)=S_{k-1}(1,0)+S_{k-1}(2,0)+S_{k-1}(4,0)+S_{k-1}(5,0)&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Acestea sunt formulele de recurență. Rezultatul care trebuie afișat pe ecran este &amp;lt;math&amp;gt;2[S_{N-1}(5,1)+S_{N-1}(7,1)]&amp;lt;/math&amp;gt;, deoarece după &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;-1 coloane surplusul trebuie să fie 1, iar colțurile matricii trebuie să fie bifate. Se observă că &amp;lt;math&amp;gt;S_k(1,0)=S_k(4,0)=S_k(5,1)&amp;lt;/math&amp;gt;. Practic, problema se reduce la trei șiruri. Notăm:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{align}&lt;br /&gt;
A_k &amp;amp; = S_k(5,0) \\&lt;br /&gt;
B_k &amp;amp; = S_k(1,0) = S_k(4,0) = S_k(5,1) \\&lt;br /&gt;
C_k &amp;amp; = S_k(7,1) \implies S_k(2,0)=C_{k-1}&lt;br /&gt;
\end{align}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
De aici rezultă grupul de relații:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{cases}&lt;br /&gt;
A_k &amp;amp; = A_{k - 1} + 2B_{k - 1} \\&lt;br /&gt;
B_k &amp;amp; = B_{k - 1} + C_{k - 1} \\&lt;br /&gt;
C_k &amp;amp; = A_{k - 1} + 2B_{k - 1} + C_{k - 2} = A_k + C_{k - 2}&lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
și&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\begin{cases}&lt;br /&gt;
A_1 &amp;amp; = 1 \\&lt;br /&gt;
B_1 &amp;amp; = 0 \\&lt;br /&gt;
C_1 &amp;amp; = 1&lt;br /&gt;
\end{cases}&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Noi avem nevoie de valoarea &lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
2(B_{N-1} + C_{N-1}) = 2B_N&lt;br /&gt;
&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Programul de mai jos nu face decât să implementeze calculul acestor șiruri recurente. Trebuie avut grijă însă cu reprezentarea internă a numerelor, deoarece pentru &#039;&#039;&#039;&#039;&#039;N&#039;&#039;&#039;&#039;&#039;=200 valorile ajung la 81 de cifre. Este deci necesară reprezentarea numerelor ca șiruri de cifre.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;stdio.h&amp;gt;&lt;br /&gt;
#include &amp;lt;mem.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
typedef int Huge[85];&lt;br /&gt;
Huge A,B,C,C2,HTemp;&lt;br /&gt;
int N,k;&lt;br /&gt;
&lt;br /&gt;
void Atrib(Huge H, int V)&lt;br /&gt;
/* H &amp;lt;- V */&lt;br /&gt;
{&lt;br /&gt;
  memset(H,0,sizeof(Huge));&lt;br /&gt;
  H[0]=1;&lt;br /&gt;
  H[1]=V;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void Add(Huge A, Huge B)&lt;br /&gt;
/* A &amp;lt;- A+B */&lt;br /&gt;
{ int i,T=0;&lt;br /&gt;
&lt;br /&gt;
  if (B[0]&amp;gt;A[0])&lt;br /&gt;
    { for (i=A[0]+1;i&amp;lt;=B[0];) A[i++]=0;&lt;br /&gt;
      A[0]=B[0];&lt;br /&gt;
    }&lt;br /&gt;
    else for (i=B[0]+1;i&amp;lt;=A[0];) B[i++]=0;&lt;br /&gt;
  for (i=1;i&amp;lt;=A[0];i++)&lt;br /&gt;
    { A[i]+=B[i]+T;&lt;br /&gt;
      T=A[i]/10;&lt;br /&gt;
      A[i]%=10;&lt;br /&gt;
    }&lt;br /&gt;
  if (T) A[++A[0]]=T;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void WriteHuge(Huge H)&lt;br /&gt;
{ int i;&lt;br /&gt;
&lt;br /&gt;
  for (i=H[0];i;printf(&amp;quot;%d&amp;quot;,H[i--]));&lt;br /&gt;
  printf(&amp;quot;\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void main(void)&lt;br /&gt;
{&lt;br /&gt;
  printf(&amp;quot;N=&amp;quot;);scanf(&amp;quot;%d&amp;quot;,&amp;amp;N);&lt;br /&gt;
  Atrib(A,1);&lt;br /&gt;
  Atrib(B,0);&lt;br /&gt;
  Atrib(C,1);&lt;br /&gt;
  Atrib(C2,0);&lt;br /&gt;
  for (k=2;k&amp;lt;=N;k++)&lt;br /&gt;
    { memmove(HTemp,C,sizeof(Huge));&lt;br /&gt;
      Add(A,B);Add(A,B);  /* A(k) = A(k-1) + 2*B(k-1) */&lt;br /&gt;
      Add(B,C);           /* B(k) = B(k-1) + C(k-1)   */&lt;br /&gt;
      memmove(C,A,sizeof(Huge));&lt;br /&gt;
      Add(C,C2);          /* C(k) = A(k) + C(k-2)     */&lt;br /&gt;
      memmove(C2,HTemp,sizeof(Huge)); /* noul C(K-2) */&lt;br /&gt;
    }&lt;br /&gt;
  Add(B,B);               /* Rezultatul este 2*B(n)   */&lt;br /&gt;
  WriteHuge(B);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig63.png&amp;diff=14742</id>
		<title>File:Psycho-fig63.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig63.png&amp;diff=14742"/>
		<updated>2018-03-07T13:28:11Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig62.png&amp;diff=14741</id>
		<title>File:Psycho-fig62.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig62.png&amp;diff=14741"/>
		<updated>2018-03-07T13:27:59Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig61.png&amp;diff=14740</id>
		<title>File:Psycho-fig61.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig61.png&amp;diff=14740"/>
		<updated>2018-03-07T13:27:47Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig60.png&amp;diff=14739</id>
		<title>File:Psycho-fig60.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig60.png&amp;diff=14739"/>
		<updated>2018-03-07T13:27:11Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig59.png&amp;diff=14738</id>
		<title>File:Psycho-fig59.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig59.png&amp;diff=14738"/>
		<updated>2018-03-07T13:27:00Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig58.png&amp;diff=14737</id>
		<title>File:Psycho-fig58.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig58.png&amp;diff=14737"/>
		<updated>2018-03-07T13:26:17Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig57.png&amp;diff=14736</id>
		<title>File:Psycho-fig57.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig57.png&amp;diff=14736"/>
		<updated>2018-03-07T13:26:03Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig56.png&amp;diff=14735</id>
		<title>File:Psycho-fig56.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig56.png&amp;diff=14735"/>
		<updated>2018-03-07T13:25:55Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig55.png&amp;diff=14734</id>
		<title>File:Psycho-fig55.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig55.png&amp;diff=14734"/>
		<updated>2018-03-07T13:25:43Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig54.png&amp;diff=14733</id>
		<title>File:Psycho-fig54.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig54.png&amp;diff=14733"/>
		<updated>2018-03-07T13:25:32Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig53.png&amp;diff=14732</id>
		<title>File:Psycho-fig53.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig53.png&amp;diff=14732"/>
		<updated>2018-03-07T13:25:23Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig52.png&amp;diff=14731</id>
		<title>File:Psycho-fig52.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig52.png&amp;diff=14731"/>
		<updated>2018-03-07T13:25:11Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig51.png&amp;diff=14730</id>
		<title>File:Psycho-fig51.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig51.png&amp;diff=14730"/>
		<updated>2018-03-07T13:24:59Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig50.png&amp;diff=14729</id>
		<title>File:Psycho-fig50.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig50.png&amp;diff=14729"/>
		<updated>2018-03-07T13:24:16Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig49.png&amp;diff=14728</id>
		<title>File:Psycho-fig49.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig49.png&amp;diff=14728"/>
		<updated>2018-03-07T13:24:05Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig48.png&amp;diff=14727</id>
		<title>File:Psycho-fig48.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig48.png&amp;diff=14727"/>
		<updated>2018-03-07T13:23:53Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
	<entry>
		<id>https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig47.png&amp;diff=14726</id>
		<title>File:Psycho-fig47.png</title>
		<link rel="alternate" type="text/html" href="https://www.algopedia.ro/wiki/index.php?title=File:Psycho-fig47.png&amp;diff=14726"/>
		<updated>2018-03-07T13:23:44Z</updated>

		<summary type="html">&lt;p&gt;Cata: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Cata</name></author>
	</entry>
</feed>