Sunteți pe pagina 1din 542

Informatică

Olimpiada - cls9

2023-3
PROBLEME DE INFORMATICĂ
date la olimpiade

ONI
ı̂n

**** **** 2022 2021 2020


2019 2018 2017 2016 2015
2014 2013 2012 2011 2010
2009 2008 2007 2006 2005
2004 2033 2002 **** ****

Dacă ştii să rezolvi problemele date ı̂n ultimii 10-20 de ani
atunci vei şti să rezolvi problemele care se vor da ı̂n acest an!

... draft (ciornă) ...


*** Nobody is perfect ***

Adrian Răbâea, Ph.D.

https://www.scribd.com/user/552245048/Adi-Rabaea

2023-3-13
Dedication

I would like to dedicate this book ...

a ”to myself” ...


1
2
in a time when ...
3
I will not be able ... ”to be”
That is because ...
4
”When I Die Nobody Will Remember Me”

a to people impacted by the book


a to my nephew Adam.

( in ascending order! )

1
https://www.femalefirst.co.uk/books/carol-lynne-fighter-1034048.html
2
https://otiliaromea.bandcamp.com/track/dor-de-el
3
https://en.wikipedia.org/wiki/To_be,_or_not_to_be
4
https://www.youtube.com/watch?v=eMtcDkSh7fU

ii
Prefaţă

Stilul acestor cărţi este ... ca şi cum aş vorbi cu nepoţii mei (şi chiar cu mine ı̂nsumi!) ... ı̂ncercând
ı̂mpreună să găsim o rezolvare cât mai bună pentru o problemă dată la olimpiadă.
Ideea, de a scrie aceste culegeri de probleme date la olimpiadele de informatică, a apărut acum
câţiva ani când am ı̂ntrebat un student (care nu reuşea să rezolve nişte probleme foarte simple):
”Ce te faci dacă un elev, care ştie că eşti student şi că studiezi şi informatică, te roagă, din când ı̂n
când, să-l ajuţi să rezolve câte o problemă de informatică dată la gimnaziu la olimpiadă, sau pur
şi simplu ca temă de casă, şi tu, aproape de fiecare dată, nu ı̂l poţi ajuta? Eu cred că nu este prea
bine şi poate că ... te faci ... de râs!” Pe vremea mea (!), când eram elev de gimnaziu, un student
era ca un fel de ... zeu! Cu trecerea anilor am ı̂nţeles că nu este chiar aşa! Şi ı̂ncă ceva: nu am
reuşit să ı̂nţeleg de ce, atunci când cineva nu poate să rezolve corect o problemă de informatică
de clasa a 6-a, dată la olimpiada de informatică sau ca temă de casă, foloseşte această scuză: ”eu
nu am făcut informatică ı̂n liceu!” şi acest cineva este ”zeul” sau ”zeiţa” despre care vorbeam!.
5
Sunt convins că este important să studiem cu atenţie cât mai multe probleme rezolvate! . Cred
că sunt utile şi primele versiuni ale acestor cărţi ... ı̂n care sunt prezentate numai enunţurile şi
indicaţiile ”oficiale” de rezolvare. Acestea se găsesc ı̂n multe locuri; aici ı̂ncerc să le pun pe ”toate
la un loc”! Fiecare urcă spre vârf ... cât poate! Sunt multe poteci care duc spre vârf iar această
carte este ... una dintre ele!
Limbajul de programare se alege ı̂n funcţie de problema pe care o avem de rezolvat. Cu nişte
ani ı̂n urmă alegerea era mai simplă: dacă era o problemă de calcul se alegea Fortran iar dacă era
o problemă de prelucrarea masivă a datelor atunci se alegea Cobol. Acum alegerea este ceva mai
6 7
dificilă! :-) Vezi, de exemplu, IOI2020 şi IOI2019 , IOI2015 .
Cred că, de cele mai multe ori, este foarte greu să gândim ”simplu” şi să nu ”ne complicăm”
atunci când cautăm o rezolvare pentru o problemă dată la olimpiadă. Acesta este motivul pentru
care vom analiza cu foarte mare atenţie atât exemplele date ı̂n enunţurile problemelor cât şi
”restricţiile” care apar acolo (ele sigur ”ascund” ceva interesant din punct de vedere al algoritmului
8
de rezolvare!) .
Am ı̂nceput câteva cărţi (pentru clasele de liceu) cu mai mulţi ani ı̂n urmă, pentru perioada
2000-2007 ([29] - [33]), cu anii ı̂n ordine crescătoare!). A urmat o pauză de câţiva ani (destul de
mulţi!). Am observat că acele cursuri s-au ”ı̂mprăştiat” un pic ”pe net” ([48] - [56])! Încerc acum
să ajung acolo unde am rămas ... plecând mereu din prezent ... până când nu va mai fi posibil ...
aşa că, de această dată, anii sunt ı̂n ordine ... descrescătoare! :-)
”Codurile sursă” sunt cele ”oficiale” (publicate pe site-urile olimpiadelor) sau publicate pe alte
site-uri (dacă mi s-a părut că sunt utile şi se poate ı̂nvăţa câte ceva din ele).
Pentru liceu perioada acoperită este de ”azi” (până când va exista acest ”azi” pentru mine!)
până ı̂n anul 2000 (aveam deja perioada 2000-2007!).
Pentru gimnaziu perioada acoperită este de ”azi” până ı̂n anul 2010 (nu am prea mult timp
9
disponibil şi, oricum, calculatoarele folosite la olimpiade ı̂nainte de 2010 erau ceva mai ’slabe’ şi

... restricţiile de memorie, din enunţurile problemelor, par ’ciudate’ acum!).


În perioada 2017-2020 cele mai puternice calculatoare din lume au fost: ı̂n noiembrie 2017
10
ı̂n China, ı̂n noiembrie 2019 ı̂n SUA şi ı̂n iunie 2020 ı̂n Japonia. În iunie 2022 ”Frontier”
5
Se poate observa din ”Coduri sursă” că orice problemă are numeroase soluţii, atât ca algoritmi de rezolvare
cât şi ca stil de programare! Studiind aceste coduri ... avem ce ı̂nvăţa ... deşi uneori pare că ’se trage cu tunul’ ...
6
IOI2019 şi IOI2020 au a permis utilizarea limbajelor de programare C++ şi Java
7
IOI2015 a permis utilizarea limbajelor de programare C++, Java, Pascal, Python şi Rubi (...)
8
Vezi cele 5 secunde pentru Timp maxim de executare/test din problema ”avârcolaci” - ONI2014 clasa a 11-a
9
https://en.wikipedia.org/wiki/Computer
10
https://www.top500.org/lists/top500/2022/06/

iii
11
depăşeşte pragul ”exascale”! (1.102 Exaflop/s). ”Peta” a fost depăşit ı̂n 2008, ”tera” ı̂n 1997,
12
”giga” ı̂n 1972. . Pentru ce a fost mai ı̂nainte, vezi https://en.wikipedia.org/wiki/Li
st_of_fastest_computers.
13
O mică observaţie: ı̂n 2017 a fost prima ediţie a olimpiadei EJOI ı̂n Bulgaria şi ... tot
14
ı̂n Bulgaria a fost şi prima ediţie a olimpiadei IOI ı̂n 1989. Dar ... prima ediţie a olimpiadei
15
IMO (International Mathematical Olympiad) a fost ı̂n România ı̂n 1959. Tot ı̂n România s-au
ţinut ediţiile din anii 1960, 1969, 1978, 1999 şi 2018. Prima ediţie a olimpiadei BOI (Balkan
Olympiad in Informatics) a fost ı̂n România ı̂n 1993 la Constanţa. Prima ediţie a olimpiadei
CEOI (Central-European Olympiad in Informatics) a fost ı̂n România ı̂n 1994 la Cluj-Napoca.
Revenind la ... “culegerile noastre” ... mai departe, probabil, va urma completarea unor
informaţii ı̂n ”Rezolvări detaliate” ... pentru unele probleme numai (tot din cauza lipsei timpului
necesar pentru toate!). Prioritate vor avea problemele de gimnaziu (nu pentru că sunt mai ’uşoare’
ci pentru că ... elevii de liceu se descurcă şi singuri!). Acum, ı̂n martie 2022, am ı̂nceput şi
redactarea unei culegeri de probleme date la bacalaureat ı̂n ultimii câţiva ani (câţi voi putea!).
Îmi aduc aminte că exista o interesantă vorbă de duh printre programatorii din generaţia mea:
”nu se trage cu tunul ı̂ntr-o muscă” . Sensul este: nu se scrie un cod complicat dacă se
poate scrie un cod simplu şi clar! Asta ı̂ncerc eu ı̂n ”Rezolvări detaliate”.
Vom ı̂ncerca, ı̂mpreună, şi câteva probleme de ... IOI ... dar asta este o treabă ... nu prea
uşoară! Cred totuşi că este mai bine să prezint numai enunţuri ale problemelor date la IOI ı̂n
ultimii câţiva ani! (asta aşa, ca să vedem cum sunt problemele la acest nivel!). Cei care ajung
acolo sau vor să ajungă acolo (la IOI) sigur nu au nevoie de ajutorul meu! Se descurcă singuri!
”ALGORITMI utili la olimpiadele de informatică”, separat pentru gimnaziu şi liceu, sper să
fie de folos, aşa cum cred că sunt [1] - [28], [34] - [47], [57] - [83], ... şi multe alte cărţi şi site-uri!.
Ar fi interesant să descoperim noi ı̂nşine cât mai mulţi algoritmi ... ı̂n loc să-i ı̂nvăţăm pur şi
simplu!
O altă mică observaţie: ce am strâns şi am scris ı̂n aceste cărţi
se adresează celor interesaţi de aceste teme! Nu cârcotaşilor! Sunt
evidente sursele ”de pe net” (şi locurile ı̂n care au fost folosite) aşa
că nu sunt necesare ”ghilimele anti-plagiat”, referinţe şi precizări
suplimentare la fiecare pas!
Şi un ultim gând: criticile şi sfaturile sunt utile dacă au valoare!
Dacă sunt numai aşa ... cum critică lumea la un meci de fotbal ...
sau cum, pe bancă ı̂n parc, ”ı̂şi dă cu părerea” despre rezolvarea
problemelor economice ale ţării ... atunci ... !!!
16
”I’m only responsible for what I say, not for what you understand.”
Adrese interesante (rezultatele elevilor români):
https://stats.ioinformatics.org/halloffame/
https://stats.ioinformatics.org/tasks/
http://stats.ioinformatics.org/results/ROU
Adresele acestor cursuri:
https://www.scribd.com/user/550183580/Adrian-Rabaea
https://www.scribd.com/user/552245048/Adi-Rabaea
https://drive.google.com/drive/folders/1hC5PZuslCdS95sl37SW46H-qy59GRDGZ
Adrese utile (programe şcolare):
http://programe.ise.ro/Portals/1/Curriculum/Progr_Lic/TH/Informatica_teoretic_vocatio
nal_intensiv_clasa%20a%20IX-a.pdf
http://programe.ise.ro/Portals/1/Curriculum/Progr_Lic/TH/Informatica_teoretic_vocatio
nal_intensiv_clasa%20a%20X_a.pdf
http://programe.ise.ro/Portals/1/Curriculum/Progr_Lic/TH/Informatica_teoretic_vocatio
nal_intensiv_clasa%20a%20XI-a.pdf

Bistriţa, 13th March 2023 Adrian Răbâea


11
https://en.wikipedia.org/wiki/Metric_prefix/
12
https://en.wikipedia.org/wiki/Computer_performance_by_orders_of_magnitude
13
https://ejoi.org/about/
14
https://stats.ioinformatics.org/olympiads/
15
https://en.wikipedia.org/wiki/International_Mathematical_Olympiad
16
https://www.facebook.com/johnwayne/photos/a.156450431041410/2645523435467418/?type=3
”Acknowledgements”

17
”I want to thank God most of all because without God I wouldn’t be able to do any of this.”

Bistriţa, 13th March 2023

Adrian R.

17
I.d.k.: ”I don’t know who the author is.”

v
Despre autor

18
nume: Răbâea Aurel-Adrian, 18.03.1953 - ...

telefon: +40 728 zz ll aa +40 363 xx 25 xx


email: adrian1803@gmail.com
Lector universitar - Universitatea Tehnică din Cluj Napoca - Centrul Universitar Nord din Baia
Mare, Facultatea de Ştiinţe, Str. Victoriei, nr. 76, Baia Mare, România, (pensionat: 01.10.2018)
https://stiinte.utcluj.ro/departamente.html
Discipline predate (1992-2018):
Algoritmi şi structuri de date, Algoritmi ı̂n teoria opţiunilor financiare, Bazele matematice
ale calculatoarelor, Bazele tehnologiei informaţiei, Birotică, Capitole speciale de inteligenţă
artificială, Capitole speciale de teoria algoritmilor, Calcul paralel, Informatică economică,
Instruire asistată de calculator, Limbaje de programare; Programare orientată pe obiecte,
Programare procedurală, Structuri de date.
Studii doctorale ı̂n informatică economică - Diplomă de doctor (1997-2002):
Instituţia: Academia de Studii Economice, Bucureşti;
19
Titlul tezei: Algoritmi paraleli şi aplicaţii pe maşini virtual paralele
20
Conducător ştiinţific: Prof. dr. ing. Gheorghe Dodescu
Teme studiate: utilizarea algoritmilor paraleli ı̂n teoria opţiunilor financiare
Studii de specializare ı̂n informatică - Certificat anul V - ’master’ (1978-1979):
Instituţia: Facultatea de matematică şi informatică, Bucureşti;
Titlul tezei: Probleme la limită pentru procese cu creşteri independente şi aplicaţii ı̂n teoria
aşteptării
21
Conducător ştiinţific: Prof. dr. Constantin Tudor
Studii universitare de licenţă ı̂n informatică - Diplomă de licenţă (1974-1978):
Instituţia: Facultatea de matematică şi informatică, Bucureşti;
Titlul tezei: Metode de comparaţie multiplă ı̂n analiza dispersională
22
Conducător ştiinţific: Prof. dr. Ion Văduva
Locuri de muncă: (1979-2018):
- (2018-2009) Universitatea Tehnică din Cluj-Napoca, Centrul Universitar Nord din Baia-
Mare, Facultatea de Ştiinţe, Departamentul de Matematică-Informatică
- (2009-1992) Universitatea ”Ovidius” din Constanţa, Facultatea de Matematică şi Infor-
23
matică, Departamentul de Informatică
24
- (1992-1979) Centrul de Informatică şi organizare CINOR, Bucureşti
25
Olimpiade: (fiind elev la Liceul Militar ”Dimitrie Cantemir” - Breaza, PH)
- 1971: Olimpiada Naţională de matematică: participare (fără rezultat notabil)
- 1970: Olimpiada Naţională de matematică: participare (fără rezultat notabil)
https://scholar.google.com/citations?user=-sSE_1wAAAAJ&hl=en
https://www.scopus.com/authid/detail.uri?origin=resultslist&authorId=56122389200&zone=
http://www.facebook.com/adrian.rabaea
18
https://dmi.cunbm.utcluj.ro/?page_id=2
19
http://opac.biblioteca.ase.ro/opac/bibliographic_view/149021
20
http://www.ionivan.ro/2015-PERSONALITATI/Dodescu.htm
21
http://old.fmi.unibuc.ro/ro/prezentare/promotii/promotia1978informatica_10ani/
22
https://ro.wikipedia.org/wiki/Ion_V%C4%83duva
23
https://fmi.univ-ovidius.ro/
24
https://www.cinor.ro/index.htm
25
https://www.cantemircml.ro/

vi
Cuprins

Prefaţă iii

Cuprins vii

Lista figurilor xv

Lista tabelelor xvii

Lista programelor xviii

1 ONI 2022 1
1.1 colibri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 geogra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.2.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3 schi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
1.3.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

2 ONI 2021 12
2.1 Le Mans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.1.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2 Oposumi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3 ELHC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

3 ONI 2020 - suspendat !!! 19

4 ONI 2019 20
4.1 amat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.2 Comun . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.3 pro3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42

vii
4.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
4.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.4 Cub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.5 fibofrac . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
4.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.6 telefon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
4.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

5 ONI 2018 71
5.1 bazaf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
5.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.2 mexitate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
5.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.3 plaja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
5.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
5.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.4 bsrec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
5.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
5.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.5 numinum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
5.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
5.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
5.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
5.6 rosii mici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
5.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
5.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

6 ONI 2017 107


6.1 arhipelag . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
6.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
6.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
6.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.2 mirror . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.3 okcpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
6.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
6.4 adlic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
6.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
6.5 bomboane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
6.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
6.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
6.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
6.6 orase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144
6.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
6.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
6.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149

7 ONI 2016 150


7.1 civilizatie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
7.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
7.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
7.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
7.2 cmmdc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
7.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
7.3 livada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
7.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
7.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
7.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
7.4 dif2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
7.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
7.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
7.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
7.5 leduri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
7.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
7.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
7.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
7.6 omogene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
7.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
7.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
7.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178

8 ONI 2015 179


8.1 cub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
8.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
8.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
8.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
8.2 risc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
8.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
8.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
8.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
8.3 roboti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 202
8.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
8.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
8.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
8.4 casa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
8.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
8.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
8.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
8.5 lenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
8.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
8.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
8.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
8.6 sipet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
8.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
8.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
8.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
9 ONI 2014 230
9.1 harta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
9.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
9.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
9.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
9.2 qvect . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
9.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
9.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
9.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
9.3 tg . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
9.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
9.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
9.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
9.4 progresie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
9.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
9.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
9.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
9.5 reflex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268
9.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
9.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
9.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
9.6 traseu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276
9.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277
9.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
9.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284

10 ONI 2013 285


10.1 aranjare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
10.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
10.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
10.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
10.2 gradina . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287
10.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
10.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
10.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
10.3 split . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
10.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
10.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
10.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
10.4 momente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292
10.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
10.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294
10.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
10.5 secvente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295
10.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
10.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
10.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
10.6 spider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298
10.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
10.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
10.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302

11 ONI 2012 303


11.1 7segmente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
11.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
11.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
11.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
11.2 copaci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
11.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
11.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
11.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
11.3 intersecţii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 312
11.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
11.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
11.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
11.4 palindrom . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 317
11.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318
11.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
11.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
11.5 sstabil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
11.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
11.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
11.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
11.6 unuzero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
11.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
11.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
11.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 333

12 ONI 2011 334


12.1 poligon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334
12.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
12.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
12.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
12.2 stalpi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336
12.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
12.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
12.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
12.3 tort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 340
12.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
12.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
12.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
12.4 ape . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
12.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
12.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
12.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
12.5 ec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
12.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
12.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
12.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348
12.6 furnici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348
12.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349
12.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
12.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351

13 ONI 2010 352


13.1 cern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352
13.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
13.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
13.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
13.2 cmmmc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
13.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357
13.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
13.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
13.3 simetric . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
13.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
13.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
13.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
13.4 pesti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
13.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
13.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
13.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
13.5 plaja . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
13.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
13.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
13.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
13.6 tango . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
13.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
13.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
13.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374

14 ONI 2009 375


14.1 joc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
14.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
14.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
14.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
14.2 perspic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 377
14.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
14.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
14.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
14.3 rafturi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381
14.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
14.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
14.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
14.4 br . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
14.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
14.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
14.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
14.5 origami . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
14.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
14.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
14.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
14.6 patrate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
14.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
14.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
14.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391

15 ONI 2008 392


15.1 ab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392
15.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
15.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
15.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394
15.2 iepuras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394
15.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
15.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
15.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
15.3 palind . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 398
15.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
15.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
15.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
15.4 auto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402
15.4.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
15.4.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
15.4.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
15.5 div . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405
15.5.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
15.5.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
15.5.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
15.6 teatru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
15.6.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408
15.6.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408
15.6.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411
16 OJI 2007 412
16.1 Cartele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412
16.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
16.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
16.1.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415
16.2 Paritate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
16.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
16.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
16.2.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421

17 OJI 2006 423


17.1 Flori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423
17.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423
17.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424
17.1.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425
17.2 Pluton . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427
17.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428
17.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
17.2.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434

18 OJI 2005 437


18.1 Numere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437
18.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437
18.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 438
18.1.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 438
18.2 MaxD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439
18.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
18.2.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
18.2.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440

19 OJI 2004 443


19.1 Expresie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
19.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 443
19.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
19.1.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445
19.2 Reactivi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448
19.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449
19.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 450
19.2.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454

20 OJI 2003 456


20.1 Text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 456
20.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457
20.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457
20.1.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 458
20.2 Numere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460
20.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460
20.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461
20.2.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462

21 OJI 2002 464


21.1 Poarta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 464
21.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
21.1.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
21.1.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
21.2 Mouse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
21.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
21.2.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
21.2.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
Appendix A ”Instalare” C++ 471
A.1 Kit OJI 2017 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471
A.1.1 Code::Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472
A.1.2 Folder de lucru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
A.1.3 Utilizare Code::Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
A.1.4 Setări Code::Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478
A.1.5 Multe surse ı̂n Code Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
A.2 winlibs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
A.2.1 GCC şi MinGW-w64 pentru Windows . . . . . . . . . . . . . . . . . . . . . 480
A.2.2 PATH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
A.2.3 CodeBlocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 484

Appendix B Exponenţiere rapidă 494


B.1 Analogie baza 2 cu baza 10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 494
B.2 Notaţii, relaţii şi formule . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495
B.3 Pregătire pentru scrierea codului! . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495
B.4 Codul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496
B.5 Chiar este rapidă? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499
B.6 Rezumat intuitiv! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500

Appendix C Căutare binară 502


C.1 Mijlocul = ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
C.2 Poziţie oarecare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503
C.2.1 Varianta iterativă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503
C.2.2 Varianta recursivă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504
C.3 Poziţia din stânga . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
C.3.1 Varianta iterativă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
C.3.2 Varianta recursivă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506
C.4 Poziţia din dreapta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507
C.4.1 Varianta iterativă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507
C.4.2 Varianta recursivă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509

Appendix D ”Vecini” ... 510


D.1 Direcţiile N, E, S, V . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510

Index 512

Bibliografie 514

Lista autorilor 517


Lista figurilor

4.1 amat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

8.1 lenes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218

9.1 reflex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270


9.2 traseu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278

16.1 Cartele1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412


16.2 Cartele2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415

21.1 Poarta . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465


21.2 Mouse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467

A.1 Fişierele din Kit OJI 2017 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471


A.2 CodeBlocks & C++ Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472
A.3 Ce conţine C:¯ OJI ¯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 472
A.4 Folder de lucru . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
A.5 New -¿ Text document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474
A.6 Schimbare nume fişier şi nume extensie fişier . . . . . . . . . . . . . . . . . . . . . . 474
A.7 Confirmare schimbare extensie ı̂n .cpp . . . . . . . . . . . . . . . . . . . . . . . . . 475
A.8 Pregătit pentru Code::Blocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
A.9 Pregătit pentru a scrie cod de program C++ ı̂n Code::Blocks . . . . . . . . . . . . 475
A.10 Primul cod de program C++ ı̂n Code::Blocks . . . . . . . . . . . . . . . . . . . . . 476
A.11 Build - compilare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 476
A.12 0 error(s), 0 warning(s) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
A.13 Run - execuţie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 477
A.14 Executat corect: a făcut “nimic” . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478
A.15 Settings  % Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478
A.16 Toolchain executables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479
A.17 Unde sunt acele programe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479
A.18 Multe surse ı̂n Code Blocks - setări . . . . . . . . . . . . . . . . . . . . . . . . . . . 480
A.19 Multe surse in Code Blocks - exemple . . . . . . . . . . . . . . . . . . . . . . . . . 480
A.20 mingw64 pe D: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 481
A.21 search path . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482
A.22 System properties –¿ Advanced . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482
A.23 Environment Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 483
A.24 Edit Environment Variables –¿ New . . . . . . . . . . . . . . . . . . . . . . . . . . 483
A.25 Calea şi versiunea pentru gcc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 484
A.26 Settings –¿ Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485
A.27 Toolchain executables –¿ Auto-detect . . . . . . . . . . . . . . . . . . . . . . . . . . 485
A.28 New –¿ Text Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 486
A.29 New text Document.txt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 486
A.30 Schimbare nume şi extensie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 486
A.31 Moore apps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487
A.32 Look for another app . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487
A.33 Cale pentru codeblocks.exe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488
A.34 Selectare codeblocks.exe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488
A.35 Editare test01.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489
A.36 Compilare test01 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489
A.37 Mesaje după compilare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490

xv
A.38 Execuţie test01 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490
A.39 Rezultat execuţie test01 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491
A.40 Fişiere apărute după compilare! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491
A.41 Creare test02.cpp gol! + ¡dublu click¿ sau ¡Enter¿ . . . . . . . . . . . . . . . . . . 491
A.42 Lista programelor de utilizat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492
A.43 Selectare Code::Blocks IDE pentru fişierele .cpp . . . . . . . . . . . . . . . . . . 492
A.44 Editare+Compilare+Execuţie pentru test02 . . . . . . . . . . . . . . . . . . . . . 492
A.45 Selectare tab ce conţine test01.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . 493

B.1 Analogie B2 cu B10 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 494

C.1 căutare binară: mijlocul zonei ı̂n vector . . . . . . . . . . . . . . . . . . . . . . . . 502

D.1 ”vecini” ı̂n matrice şi sistem Oxy . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510


D.2 Toţi ”pereţii” sunt la N sau la V ı̂n matricea ”bordată” . . . . . . . . . . . . . . . 510
D.3 Toţi ”pereţii” sunt codificaţi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511
Lista tabelelor

xvii
Lista programelor

4.1.1 alex-nnlog.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.1.2 alex-nqlog.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.1.3 amat eugen.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.1.4 brut.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
4.1.5 brut IQ 9000 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.1.6 chiorean amat.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4.1.7 solution.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
4.1.8 sursa test.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2.1 comun back.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.2.2 comun chiorean.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
4.2.3 comun eugen.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.2.4 comun io.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
4.2.5 comun nlog sub.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.2.6 comun sol.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.2.7 sol nlog.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
4.3.1 pro3 AT1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.3.2 pro3 CC1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4.3.3 pro3 CC2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.3.4 pro3 LB1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
4.4.1 cub chiorean.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.4.2 cub chiorean brut.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
4.4.3 cub chiorean n3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
4.4.4 cub100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
4.4.5 cub100 int.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.5.1 fibofrac CC1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
4.5.2 fibofrac CC2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.5.3 fibofrac CC3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
4.5.4 fibofrac CC4.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
4.5.5 fibofrac CC5.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
4.5.6 fibofrac TC1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
4.6.1 alex.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
4.6.2 telefon-adrian-100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.6.3 telefon-bicsi-100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
4.6.4 tudor telefon v2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
5.1.1 1 bazaf.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.1.2 2 bazaf.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
5.1.3 3 bazaf.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
5.1.4 4 bazaf.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
5.2.1 mexitate1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.2.2 mexitate2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.3.1 plaja N+K.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
5.3.2 plaja ternara.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
5.3.3 plaja1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
5.3.4 plaja2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
5.3.5 plaja3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.3.6 plaja4.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
5.4.1 bsrec.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
5.4.2 bsrec2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
5.5.1 1 numinum 100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98

xviii
5.5.2 2 numinum 100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
5.6.1 rosiimici.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.6.2 rosiimici2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
6.1.1 arhipelag 100p 1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
6.1.2 arhipelag 100p 2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
6.2.1 mirror 100p 1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.2.2 mirror 100p 2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
6.2.3 mirror 100p 3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
6.2.4 mirror 100p 4.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
6.2.5 mirror 100p 5.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
6.2.6 mirror 100p 6.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
6.3.1 okcpp 97p.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.3.2 okcpp 100p 1 doar linux.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
6.3.3 okcpp 100p 2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
6.4.1 adlic 100p 1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
6.4.2 adlic 100p 2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
6.4.3 adlic 100p 3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
6.5.1 bomboane 100p 1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140
6.5.2 bomboane 100p 2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
6.5.3 bomboane 100p 3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
6.6.1 orase 100p.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
7.1.1 civilizatie BICSI.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
7.1.2 civilizatie100p.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
7.2.1 cmmdc BICSI.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
7.3.1 livada dan.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
7.3.2 Livada100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
7.4.1 dif2 bicsi.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165
7.4.2 dif2 eric.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
7.4.3 dif2 N log2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
7.5.1 leduri bicsi.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
7.5.2 leduri eric.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171
7.6.1 omogene eric.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
7.6.2 omogene n 4.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
7.6.3 omogeneMG.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
8.1.1 cubul cp1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
8.1.2 cubul cp2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
8.1.3 cubul gcc 2matrici.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
8.1.4 cubul gcc umplere ciur.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
8.1.5 cubul mot e.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
8.2.1 risc.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
8.2.2 risc n2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198
8.2.3 risc nlogn.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
8.3.1 roboti aib.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204
8.3.2 roboti dp.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
8.3.3 roboti greedy.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
8.4.1 casa.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
8.4.2 casa slow.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
8.4.3 casa2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
8.5.1 lenes.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218
8.5.2 lenes2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
8.5.3 lenes3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
8.6.1 sipet.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
9.1.1 harta.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
9.1.2 harta Adriana.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234
9.1.3 harta brut 1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
9.1.4 harta brut 2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
9.1.5 harta eugen.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
9.1.6 harta pit.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
9.1.7 harta1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 242
9.2.1 qvect eugen bf.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 246
9.2.2 qvect eugen fs.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 247
9.2.3 qvect eugen std.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
9.2.4 qvect inter.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
9.2.5 qvect mink.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
9.2.6 qvect qsort.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
9.2.7 qvect qsortscanf.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
9.2.8 qvect vs.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
9.3.1 tg 100p.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260
9.3.2 tg on.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
9.3.3 tg on v2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
9.3.4 tg on2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
9.3.5 tg onsqrtn.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
9.4.1 CC2 progresie.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
9.4.2 CC3 progresie.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265
9.4.3 CM progresie.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
9.4.4 EN progresie.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
9.4.5 PIT progresie.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
9.4.6 SP progresie.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
9.5.1 reflex eugen.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
9.5.2 reflex LS brut.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271
9.5.3 reflex LS Euclid 100p.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272
9.5.4 reflex LS med.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
9.5.5 reflex LS Sr 100p.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
9.5.6 reflex vs.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 275
9.6.1 traseu carmen nerec.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278
9.6.2 traseu carmen rec.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
9.6.3 traseu eugen.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
9.6.4 traseu vspit.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
10.1.1 aranjare.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
10.2.1 gradina.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288
10.3.1 split.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
10.4.1 momente.pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294
10.5.1 secvente.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
10.6.1 spider.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 300
11.1.1 PIT 7segmente.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 304
11.1.2 CC 7segmente.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305
11.1.3 RH 7segmente.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 306
11.2.1 PIT copaci.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309
11.2.2 RH copaci.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
11.2.3 VI copaci.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
11.3.1 PIT1 intersectii.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
11.3.2 PIT2 intersectii.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
11.3.3 brut intersectii.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
11.3.4 GM intersectii.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
11.3.5 VI intersectii.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
11.4.1 CTpalindrom.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
11.4.2 RHpalindrom.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
11.4.3 VIpalindrom.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
11.5.1 PRIVsstabil 1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327
11.5.2 PRIVsstabil 2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
11.5.3 VI sstabil.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
11.6.1 GMunuab 100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
11.6.2 GMunuab back.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
11.6.3 GMunuab n patrat.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
11.6.4 VI unuzero.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
12.1.1 poligon.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
12.2.1 stalpi.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
12.3.1 tort.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
12.4.1 ape.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
12.5.1 ec.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346
12.6.1 furnici.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
13.1.1 cern100v1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353
13.1.2 cern100v2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355
13.2.1 cmmmc60.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358
13.2.2 cmmmc100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
13.3.1 simetric20.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
13.3.2 simetric40.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
13.3.3 simetric100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
13.4.1 pesti.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
13.5.1 plaja.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
13.6.1 tango.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
14.1.1 joc.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
14.2.1 perspic.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
14.3.1 rafturi.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
14.4.1 br.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
14.5.1 origami.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
14.6.1 patrate.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
15.1.1 ab.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
15.2.1 iepuras.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 396
15.3.1 palind.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
15.3.2 palindvxn.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 400
15.3.3 palindvxv.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
15.4.1 auto.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
15.4.2 autonk.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404
15.5.1 div rares.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406
15.6.1 teatru.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 408
15.6.2 teatru n2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
15.6.3 teatru n3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410
16.1.1 carteleC.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
16.1.2 cartele1.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415
16.1.3 cartele2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 416
16.1.4 cartele3.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
16.2.1 PARITATE.C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420
16.2.2 Paritate.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421
17.1.1 FLORI.CPP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424
17.1.2 flori1.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425
17.1.3 flori2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426
17.2.1 PLUTONC.C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429
17.2.2 PLUTCARM.CPP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431
17.2.3 PLUTRADU.CPP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 433
17.2.4 pluton1.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434
17.2.5 pluton2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435
18.1.1 numere.pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 438
18.1.2 numere.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 438
18.2.1 MaxD1.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440
18.2.2 MaxD2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441
19.1.1 EXP.PAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444
19.1.2 Expresie1.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445
19.1.3 Expresie2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446
19.1.4 Expresie3.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447
19.2.1 REACT QS.PAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 450
19.2.2 REACTIVI.C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452
19.2.3 reactivp.pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 453
19.2.4 reactivi.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 454
20.1.1 TEXT.CPP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457
20.1.2 text.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 459
20.2.1 numere.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461
20.2.2 numere.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 462
21.1.1 poarta.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 465
21.2.1 mouce.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 467
B.4.1exponentiere rapida1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 496
B.4.2exponentiere rapida2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497
B.4.3exponentiere rapida3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 498
B.4.4exponentiere rapida MOD.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 498
B.5.1exponentiere naiva MOD.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499
B.5.2exponentiere rapida MOD.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500
B.6.1secventa cod.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501
C.2.1cautare binara-v1-iterativ.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503
C.2.2cautare binara-v1-recursiv.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 504
C.3.1cautare binara-v2-iterativ.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 505
C.3.2cautare binara-v2-recursiv.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506
C.4.1cautare binara-v3-iterativ.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507
C.4.2cautare binara-v3-recursiv.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 509
Capitolul 1

ONI 2022

1.1 colibri
Problema 1 - Colibri 100 de puncte
Se dau N triplete de numere naturale ai , bi , ci , unde ci j 0 şi 1 & i & N , fiecare reprezentând
câte un număr raţional qi egal cu:
ai
1 bi
ci
Cerinţe

Găsiţi un subşir nevid al şirului q1 , q2 , ..., qN al cărui produs al valorilor să fie maxim posibil.

Date de intrare

Fişierul de intrare colibri.in conţine pe prima linie numărul N .


Următoarele N linii descriu cele N triplete: pe linia i se află numerele naturale ai , bi , ci ,
separate prin spaţii.

Date de ieşire

Pe prima linie a fişierului de ieşire colibri.out se află un şir de N cifre. Cifra i, unde 1 & i & N ,
este 1 dacă şi numai dacă qi este selectat ı̂n subşirul soluţie, altfel este 0. Cifrele şirului nu se vor
separa prin spaţii.

Restricţii şi precizări

ˆ 1 & N & 50 000;


ˆ 0 & ai , bi & 1 000 000, oricare ar fi 1 & i & N ;
ˆ 1 & ci & 1 000 000, oricare ar fi 1 & i & N ;
ˆ Dacă există mai multe soluţii, atunci se acceptă orice soluţie corectă;
ˆ Spunem că un şir x este subşir al unui şir y dacă şi numai dacă x se poate obţine din y
eliminând o parte din elementele lui y (inclusiv nici unul) fără a schimba ordinea relativă a
elementelor rămase.

# Punctaj Restricţii
1 30 N & 19 şi ai , bi , ci & 9
2 20 N & 19
3 20 ai , bi , ci & 9
4 30 Fără restricţii suplimentare

1
CAPITOLUL 1. ONI 2022 1.1. COLIBRI 2

Exemple:

colibri.in colibri.out
5 01001
001
242
477
123
032

Explicaţie:

În exemplu N 5, q1 10 , q2 45 , q3 77 , q4  32 , q2 32 .
Produsul maxim posibil este egal cu 3. Acesta se poate obţine luând fie subşirul constând din
numerele q2 şi q5 , fie luând subşirul format din numerele q2 , q3 şi q5 . Cu alte cuvinte, şi răspunsul
01101 este corect.

1.1.1 Indicaţii de rezolvare

Propusă de: stud. Alexandru Petrescu, Universitatea din Oxford


Pregătită de: stud. Andrei Arhire, Universitatea A.I. Cuza, Iaşi
Editorial redactat de: stud. Andrei Arhire, Universitatea A.I. Cuza, Iaşi
Soluţia 1. Vom ı̂ncerca să găsim subşirul de termeni cu cel mai mare produs presupunând că
produsul aparţine intervalului [1,+). În cazul ı̂n care nu există un astfel de subşir vom căuta ı̂n
mod similar pentru intervalele 0, 1, 0, 0 şi ™, 0.

ˆ O variantă de subşir care este soluţie şi are produsul ı̂n 1, ™ conţine toate fracţiile
pozitive supraunitare şi pozitive echiunitare. De asemenea conţine şi cele mai mici 2 K
fracţii negative cu menţiunea că cele mai mari două dintre acestea formează un produs mai
mare sau egal cu 1.
ˆ În continuare ştim că nu exista soluţie ı̂n 1, ™ şi ı̂ncercăm să găsim una ı̂n (0, 1). Dacă
există atunci conţine cel mult 2 fracţii. Fie este formată din cea mai mare fracţie pozitivă,
fie este formată din cele mai mici 2 fracţii negative.
ˆ Cel mai mare produs al unui subşir este 0 atunci când nu exista nicio fracţie pozitivă, există
cel mult o fracţie negativă şi cel puţin o fracţie cu rezultatul 0.
ˆ Produsul aparţine intervalului ™, 1 când există o singură fracţie cu valoare negativă.

Plecând de la aceste observaţii problema se poate rezolva ı̂n O N log N .


Mai ı̂ntâi se verifică dacă produsul este cel mult 0. Se sortează fracţiile după rezultat şi se
menţin 2 pointeri, fiecare la un capăt. Cel din capătul stâng se va deplasa cu câte 2 poziţii spre
dreapta cât timp produsul fracţiei indicate cu cel al fracţiei următoare este cel puţin 1 şi ambele
fracţii sunt negative, iar cel din capătul drept se va deplasa cu câte o poziţie spre stânga cât timp
fracţia indicată este cel puţin 1.
Dacă ı̂n urma operaţiilor ambii pointeri nu se află ı̂n poziţiile iniţiale atunci nu există soluţie
cu produs ı̂n [1,+). Altfel subşirul este format din primele două sau ultima fracţie.
Soluţia 2.

Pentru a rezolva problema vom ı̂mparţi fracţiile ı̂n 7 categorii ı̂n funcţie de rezultat.
Mai departe ne vom referi prin
ˆ Categoria I la mulţimea fracţiilor cu rezultatul ı̂n ™, 1
ˆ Categoria II la mulţimea fracţiilor cu rezultatul ı̂n 1, 1
CAPITOLUL 1. ONI 2022 1.2. GEOGRA 3

ˆ Categoria III la mulţimea fracţiilor cu rezultatul ı̂n 1, 0


ˆ Categoria IV la mulţimea fracţiilor cu rezultatul ı̂n 0, 0
ˆ Categoria V la mulţimea fracţiilor cu rezultatul ı̂n 0, 1
ˆ Categoria VI la mulţimea fracţiilor cu rezultatul ı̂n 1, 1
ˆ Categoria VII la mulţimea fracţiilor cu rezultatul ı̂n 1, ™

O idee de rezolvare este să considerăm fiecare din cele 7 intervale ı̂n ordine descrescătoare şi să
verificăm dacă există un subşir de fracţii care ı̂nmulţite dau o valoare ı̂n intervalul la care ne
referim.

ˆ 1, ™. Pentru a obţine un produs mai mare ca 1 e nevoie ca cel puţin una din următoarele
propoziţii sa fie adevărate:
– există o fracţie ı̂n VII;
– există două fracţii ı̂n I;
– există o fracţie ı̂n I şi una ı̂n II;
– există o fracţie ı̂n I şi una ı̂n III care ı̂nmulţite dau mai mult decât 1.
Pentru a obţine valoarea maximă se aleg ı̂n această ordine:
– toate fracţiile din VII.
– cele mai mici fracţii din mulţimea I două câte două. Astfel, după alegere, ı̂n I fie nu
rămâne nimic (dacă ¶I ¶ este număr par), fie rămâne cea mai mare fracţie (¶I ¶ este
impar).
– dacă a rămas o fracţie ı̂n I şi există o fracţie ı̂n II atunci ambele sunt alese.
– dacă a rămas o fracţie ı̂n I şi există o fracţie ı̂n III al căror produs este % 1 atunci
ambele sunt alese.
ˆ 1, 1. Din moment ce suntem aici ı̂nseamnă că nu există un subşir ale cărui fracţii să
dea un produs % 1. Pentru a obţine un produs egal cu 1 e nevoie ca cel puţin una din
următoarele sa fie adevărată:
– există o fracţie ı̂n VI;
– există 2 fracţii ı̂n II;
– există o fracţie ı̂n I şi una ı̂n III care ı̂nmulţite dau 1.
ˆ 0, 1. Din moment ce suntem aici ı̂nseamnă că nu există un subşir ale cărui fracţii să
dea un produs ' 1. Pentru a obţine un produs din 0, 1 e nevoie ca cel puţin una din
următoarele sa fie adevărată:
– există o fracţie ı̂n V ;
– există o fracţie ı̂n II şi una ı̂n III;
– există o fracţie ı̂n I şi una ı̂n III care ı̂nmulţite aparţin 0, 1;
– există 2 fracţii ı̂n III.
Se alege subşirul care obţine cel mai mare produs raportat la cele 4 cazuri de mai sus.
ˆ 0, 0. Dacă există cel puţin o fracţie cu numărătorul 0 atunci răspunsul este 0.
ˆ 1, 0 sau 1, 1 sau ™, 1. Dacă suntem ı̂n acestă situaţie atunci cu siguranţă
testul conţine o singură fracţie şi aceasta este negativă. Prin urmare aceasta reprezintă şi
soluţia problemei.

1.1.2 *Cod sursă

1.1.3 *Rezolvare detaliată

1.2 geogra
Problema 2 - Geogra 100 de puncte
Pasionaţi de geografie, Alex şi Răzvan joacă online Geoguessr.
Harta lumii este alcătuită din N locaţii numerotate de la 1 la N , fiecare desemnând un punct ı̂n
plan de coordonate Xi , Yi . Alex a studiat atent toate cele N locaţii şi a determinat o listă de L
CAPITOLUL 1. ONI 2022 1.2. GEOGRA 4

caracteristici de interes pentru locaţiile date, numerotate de la 1 la L. De exemplu, caracteristica


1 ar putea fi ”se află respectiva locaţie ı̂n Europa?”, iar caracteristica 2 ar putea fi ”se vorbeşte
limba spaniolă ı̂n locaţia respectivă?”, şi aşa mai departe.
Pentru fiecare locaţie i şi fiecare caracteristică j se consideră cunoscută valoarea Zi,j " r0, 1x
cu proprietatea că Zi,j 1 dacă şi numai dacă locaţia i are caracteristica j. De exemplu, dacă
locaţia 1 se află ı̂n Asia şi acolo se vorbeşte limba spaniolă, atunci am avea Z1,1 0 şi Z1,2 1.
Un joc de Geoguessr este format din Q runde. La o rundă, calculatorul alege o locaţie i din
cele N , fără a o dezvălui lui Alex. În schimb, Alex primeşte câteva ilustraţii cu locul respectiv,
scopul lui Alex fiind acela de a descoperi locaţia i.
Analizând atent doar ilustraţiile, Alex poate determina pentru orice caracteristică j dacă
locaţia i o are sau nu. Totuşi, timpul pentru fiecare rundă fiind limitat, Alex nu va reuşi mereu să
afle ı̂n timp util valorile Zi,j pentru toate caracteristicile j, ci doar pentru unele. Aşadar, dorind
să fie cât mai eficient, Alex şi-a format următoarea strategie de joc, moştenită de la Bunicul lui:
prima dată Alex va afla valoarea Zi,T1 , apoi, dacă a mai rămas timp, Alex va afla valoarea Zi,T2 ,
apoi valoarea Zi,T3 , şi aşa mai departe până când expiră timpul alocat rundei.
În funcţie de limita de timp a rundei (care poate varia de la o rundă la alta), cu această
strategie este posibil ca Alex să afle mai multe sau mai puţine informaţii despre locaţie, inclusiv
niciuna (adică nicio valoare Zi,j descoperită). Vom nota cu R1 , R2 , ..., RL informaţiile descoperite
de Alex până la finalul rundei. Mai exact, Rj Zi,j dacă Alex a apucat să afle dacă locaţia i are
caracteristica j ı̂n timp util, sau Rj 1 altfel.
Atenţie! Strategia lui Alex, reprezentată de şirul T1 , T2 , ..., TL , este aceeaşi pentru toate cele
Q runde.
Şirul T1 , T2 , ..., TL este format din valori distincte şi T1 , T2 , ..., TL " r1, 2, .., Lx.

Cerinţe

La finalul unei runde este posibil ca mai multe locaţii din cele N să corespundă cu şirul R de
informaţii aflate de Alex, aşa că acesta ı̂şi pune două ı̂ntrebări existenţiale:
1. Care este numărul K de locaţii dintre cele N care respectă şirul R de informaţii aflate pe
parcursul rundei? Spunem că o locaţie i respectă şirul R dacă pentru orice caracteristică j avem
ca Rj Zi,j sau Rj 1.
2. Se dă câte o valoare Wi pentru fiecare locaţie i din cele N , 1 & i & N . Considerând locaţiile
care respectă şirul R, fie acestea 1 & i1 , i2 , ..., iK & N , pentru un punct P de coordonate ı̂ntregi
A, B  din plan, nu neapărat unul corespunzător unei locaţii dintre cele N , definim

dP Wi1 ¶A  Xi1 ¶  ¶B  Yi1 ¶  Wi2 ¶A  Xi2 ¶  ¶B  Yi2 ¶  ...  WiK ¶A  XiK ¶  ¶B  YiK ¶.

Care este punctul P din plan pentru care dP este minim? Dacă sunt mai multe astfel de
puncte P , se cere cel cu A minim. Dacă ı̂ncă este egalitate, atunci se cere cel cu B minim.
Se dă un număr C " r1, 2x. Pentru C 1 să se afişeze răspunsul la prima ı̂ntrebare a lui Alex
pentru fiecare din cele Q runde. Pentru C 2 să se afişeze răspunsul la a doua ı̂ntrebare a lui
Alex pentru fiecare din cele Q runde.

Date de intrare

Pe prima linie se află numărul natural C, reprezentând cerinţa care trebuie rezolvată.
Pe cea de-a doua linie se află numerele naturale N şi L, reprezentând numărul locaţiilor şi,
respectiv, numărul caracteristicilor studiate de Alex.
Urmează descrierile celor N locaţii, ı̂n ordine de la 1 la N , fiecare pe câte doua linii. Descrierea
unei locaţii i se face după cum urmează:

ˆ Pe prima linie se află numerele naturale Xi şi Yi . Dacă C 2, ı̂n continuarea acestor doua
valori se află numărul natural Wi . Valorile de pe linie sunt separate prin spaţii.
ˆ Pe a doua linie se afla Zi,1 , Zi,2 , ..., Zi,L , separate prin spaţii.

Pe următoarea linie se află numărul natural Q, reprezentând numărul de runde din cadrul
jocului.
Urmează descrierile celor Q runde, ı̂n ordine de la 1 la Q, fiecare pe câte o linie. Descrierea
unei runde i se face prin şirul de valori R1 , R2 , ..., RL găsit de Alex ı̂n runda respectivă, valorile
fiind separate prin spaţii.
CAPITOLUL 1. ONI 2022 1.2. GEOGRA 5

Date de ieşire

Se vor afişa Q linii. Dacă C 1, linia i va conţine numărul locaţiilor care respectă şirul R
de informaţii aflate de Alex ı̂n runda i. Dacă C 2, linia i va conţine doua numere naturale A
şi B, reprezentând coordonatele punctului P căutat pentru care dP este minim ı̂n runda i, unde
1 & i & N.

Restricţii şi precizări

ˆ 1 & N, Q & 100 000;


ˆ 1 & L & 30;
1 & Xi , Yi & 10 , oricare ar fi 1 & i & N ;
9
ˆ
ˆ 1 & Wi & 10 000, oricare ar fi 1 & i & N ;
ˆ Zi,j " r0, 1x, oricare ar fi 1 & i & N , 1 & j & L;
ˆ Rj " r1, 0, 1x, oricare ar fi 1 & j & L;
ˆ 1 & K & N , pentru fiecare rundă din cele Q;
ˆ Nu există două locaţii care desemnează acelaşi punct din plan;
ˆ Se garantează că Alex respectă aceeaşi strategie, reprezentată de şirul T1 , T2 , ..., TL , ı̂n fiecare
din cele Q runde.

# Punctaj Restricţii
1 9 C 1 şi 1 & N, Q, L, Xi , Yi & 10
2 11 C 1 şi Ti i pentru 1 & i & L
3 17 C 1
4 9 C 2 şi 1 & N, Q, L, Xi , Yi , Wi &i , Yi & 10 000
6 7 C 2 şi 1 & N, Q & 400 şi 1 & Xi , Yi & 250 000
7 7 C 2 şi 1 & N, Q & 1 000
8 12 C 2 şi Wi 1
9 21 C 2

Exemple:

geogra.in geogra.out
1 1
74 3
14 7
1101 4
31 2
1110
72
0100
97
1010
39
0000
56
0010
44
1100
5
1010
-1 0 -1 0
-1 -1 -1 -1
-1 1 -1 -1
1 1 -1 0
CAPITOLUL 1. ONI 2022 1.2. GEOGRA 6

2 9 7
74 3 7
141 5 2
1101 7 2
311 3 1
1110
725
0100
971
1010
392
0000
561
0010
441
1100
5
1010
-1 0 -1 0
-1 -1 -1 -1
-1 1 -1 -1
1 1 -1 0

Explicaţii:

În cazul acestor exemple, strategia lui Alex este T1 2, T2 4, T3 1, T4 3.


Spre exemplu, ı̂n runda 2 Alex află mai ı̂ntâi valoarea R2 , apoi valoarea R4 , dar, din cauza
faptului că rămâne fără timp, nu mai reuşeşte să afle nici valoarea R1 şi nici valoarea R3 .
Pentru runda 1, locaţia care respectă şirul R găsit de Alex este 4. Pentru C 2, punctul cerut
este P 9, 7, iar dP 0.
Pentru runda 2, locaţiile care respectă şirul R găsit de Alex sunt 4, 5, 6. Pentru C 2, punctul
cerut este P 3, 7, iar dP 13.
Pentru runda 3, locaţiile care respectă şirul R găsit de Alex sunt 1, 2, 3, 4, 5, 6, 7. Pentru
C 2, punctul cerut este P 5, 2, iar dP 53.
Pentru runda 4, locaţiile care respectă şirul R găsit de Alex sunt 1, 2, 3, 7. Pentru C 2,
punctul cerut este P 7, 2, iar dP 18.
Pentru runda 5, locaţiile care respectă şirul R găsit de Alex sunt 2, 7. Pentru C 2, punctul
cerut este P 3, 1, iar dP 4.
CAPITOLUL 1. ONI 2022 1.2. GEOGRA 7

1.2.1 Indicaţii de rezolvare

Propusă de: stud. Gheorghe Liviu Armand, Universitatea din Bucureş ti


Pregătită de: stud. Gheorghe Liviu Armand, Universitatea din Bucureş ti
Editorial redactat de: stud. Gheorghe Liviu Armand, Universitatea din Bucureş ti
Pentru a rezolva problema, trebuie ca mai ı̂ntâi sa calculăm şirul T .
Ordonăm şirurile R găsite de Alex ı̂n fiecare runda crescător după numărul de valori diferite
de 1 din cadrul fiecărui şir. Întrucât ştim că Alex respectă şirul T ı̂n fiecare rundă, putem să
deducem un şir T posibil doar uitându-ne la şirurile R ı̂n ordinea stabilită anterior. Astfel, când
vedem că numărul de valori diferite de 1 diferă de la un şir R la următorul şir R din ordinea
stabilită, ne putem da seama că poziţiile pe care ı̂n primul sir sunt valori de 1 şi ı̂n al doilea
şir sunt valori diferite de 1 vor urma ı̂n şirul T după poziţiile pe care ı̂n primul sir sunt valori
diferite de 1.
Pentru a determina un şir T posibil este ı̂ndeajuns ca de fiecare dată când găsim astfel de
poziţii să le adaugăm ı̂n şirul T ı̂n ordinea ı̂n care le găsim. Şirul T obţinut nu este mereu unic şi
este posibil să difere de cel al lui Alex, ı̂nsă soluţia nu este afectată de acest fapt, după cum vom
vedea ı̂n continuare.
Pe baza acestui şir T obţinut, putem rearanja valorile din fiecare sir R şi Zi . Astfel, valorile
din fiecare sir vor fi ı̂n ordinea ı̂n care Alex le va afla.
Pentru a putea răspunde eficient la ı̂ntrebări, vom construi L  1 vectori ı̂n care vom reţine
cele N locaţii. Al j-lea vector dintre aceştia, 0 & j & L, va conţine cele N locaţii ordonate după
numărul format din primii j biţi din cadrul şirului Zi asociat fiecărei locaţii.
Pentru fiecare ı̂ntrebare, ne vom uita ı̂n al nr-lea vector dintre cei L  1 formaţi anterior,
unde nr reprezintă numărul valorilor diferite de 1 din şirul R asociat rundei respective. În acest
vector, putem căuta binar cea mai din stânga şi cea mai din dreapta apariţie a numărului format
din biţii ce reprezinta primele nr valori din cadrul şirului R din runda actuala. Astfel, vom obţine
un interval nevid ce reprezinta toate locaţiile care respectă şirul R aflat de Alex ı̂n runda actuala.
Cerinţa 1 - 37 de puncte
Pentru a rezolva aceasta cerinţa, trebuie doar să afişăm lungimea intervalului găsit anterior.
Cerinţa 2 - 63 de puncte
Pentru a rezolva această cerinţa, mai ı̂ntâi trebuie să observăm că putem calcula A independent
de B.
Pentru a calcula A, trebuie să ne uităm la coordonatele X ale locaţiilor din intervalul găsit
26
anterior. Dacă valorile W ale locaţiilor din interval ar fi 1, atunci A ar fi valoarea mediană a
coordonatelor X ale locaţiilor din interval. În cazul ı̂n care şirul are 2 mediane, atunci se alege
valoarea mai mica, ı̂ntrucât A trebuie sa fie minim.
Dacă A ar fi o valoare mai mică decât cea mai mică mediană sau mai mare decât cea mai mare
mediană, atunci dp ar creşte.
Pe cazul general, unde valorile W ale locaţiilor din interval nu sunt toate egale cu 1, ne putem
imagina că fiecare locaţie apare de W -ul ei ori. Astfel, răspunsul ar fi tot mediana acestui nou (şi
mult mai lung) şir, adică a sumW ©2  sumW %2 valoare X in ordine crescătoare, unde sumW
reprezintă suma valorilor W ale locaţiilor din interval. Deci, dacă ne-am ordona după X locaţiile
din intervalul găsit cu cele doua căutări binare, atunci A va fi coordonata X a primei locaţii ı̂n
care suma valorilor W a locaţiilor de dinaintea ei ı̂n ordinea stabilita devine mai mare sau egala
cu sumW ©2  sumW %2.
Pentru a reuşi să calculăm acest lucru, vom ordona locaţiile din interval după coordonata X,
vom face sume parţiale după W şi vom parcurge locaţiile din interval până găsim valoarea căutată.
Pentru a calcula B se procedează asemănător, singura diferenţa fiind că B trebuie calculat ı̂n
funcţie de coordonatele Y ale locaţiilor din intervalul găsit ı̂n runda actuală.
Pentru a obţine 100 de puncte, trebuie să calculăm o singură dată rezultatul din runde care
au acelaşi şir R. Astfel, fiecare interval găsit pentru un sir R va fi parcurs o singură data.
26
valoarea mediană este valoarea termenului din mijloc ı̂n şirul valorilor sortate; dacă sunt doi termeni, la mijoc,
se face media aritmetică a lor sau se alege unul dintre ei, ca aici!
CAPITOLUL 1. ONI 2022 1.3. SCHI 8

Întrucât un şir R cu un anumit număr de valori diferite de 1 determină un interval disjunct de


intervalul determinat de un şir R diferit cu acelaşi număr de valori diferite de 1, fiecare element
din cei L  1 vectori făcuţi anterior va fi parcurs o singura dată ı̂n total.

1.2.2 *Cod sursă

1.2.3 *Rezolvare detaliată

1.3 schi
Problema 3 - Schi 100 de puncte
Ultimul sezon rece a fost unul cu multa zăpadă căzută ı̂n zona montană, prin urmare magazinul
de articole sportive din staţiunea de schi Semenic a avut vânzări mari, mai ales de clăpari (bocanci
speciali pentru schiat). Acum fiind sfârşit de sezon, angajaţii magazinului constată că le-au rămas
N cutii goale ı̂n care au fost ambalate perechile de clăpari vândute. Aceste cutii au forma unui
paralelipiped dreptunghic la care faţa superioară constituie capacul. Capacul şi (evident) faţa
opusă capacului sunt de formă pătrată, iar ı̂nălţimea este una oarecare, ea depinzând de modelul
de clăpari. La unele cutii capacul este prezent, la altele capacul a dispărut.
Aceste cutii trebuie predate unei firme de reciclare, dar până ajunge maşina pentru reciclat
ı̂n Semenic, cutiile trebuie depozitate. Prin urmare unul dintre angajaţii magazinului primeşte
sarcina să depoziteze cutiile punându-le una peste alta sub forma unui turn. Angajatul ia cutiile
ı̂n ordinea ı̂n care le găseşte ı̂n magazin şi le pune una peste alta. Pentru a asigura o oarecare
stabilitate a turnului, el le aşează astfel ı̂ncât axele care unesc centrul capacului cu centrul feţei
opuse capacului de la fiecare cutie să fie coliniare. Cutiile sunt astfel lăsate pe rând să cadă de
la o ı̂nălţime suficient de mare, cu feţele laterale perpendiculare pe podea şi paralele cu cele ale
cutiilor deja plasate, până când ı̂ntâlnesc una dintre aceste cutii sau podeaua. În ceea ce priveşte
aşezarea cutiilor care au capacul lipsă, angajatul le aşează fie cu golul format prin lipsa capacului
ı̂n sus, fie ı̂n jos.

Cerinţe

Cunoscând N (numărul de cutii din magazin), ordinea ı̂n care cutiile sunt aşezate ı̂n turn,
iar pentru fiecare cutie latura capacului, ı̂nălţimea cutiei şi dacă are capac sau, ı̂n cazul ı̂n care
capacul lipseşte, dacă cutia este aşezată cu golul ı̂n sus sau cu golul ı̂n jos, determinaţi:
1. ı̂nălţimea turnului astfel format;
2. numărul de cutii ale căror feţe laterale sunt vizibile dacă se priveşte turnul din lateral.

Date de intrare

Pe prima linie a fişierului de intrare schi.in se află numărul C, număr care poate fi 1 sau 2 şi
reprezintă cerinţa care trebuie rezolvată.
Pe ce de-a doua linie se află numărul natural N, reprezentând numărul de cutii care sunt aşezate
ı̂n turn.
Pe fiecare dintre următoarele N linii se află câte trei valori L, H şi M , separate prin câte un
spaţiu.
Valorile de pe linia i  2 (1 & i & N ) reprezintă informaţii referitoare la cea de-a i-a cutie
adăugată ı̂n turn, şi anume:

ˆ L - latura capacului cutiei;


ˆ H - ı̂nălţimea cutiei;
ˆ M - o valoare egală cu 0, 1 sau 2, având următoarea semnificaţie:
– M 0 - cutia are capacul lipsă şi este aşezată cu golul ı̂n jos;
– M 1 - cutia are capac;
– M 2 - cutia are capacul lipsă şi este aşezată cu golul ı̂n sus.

Date de ieşire
CAPITOLUL 1. ONI 2022 1.3. SCHI 9

# Punctaj Restricţii
1 12 C 1 şi N & 2000
2 12 C 2 şi N & 2000
3 8 C 1 şi nu avem nicio cutie aşezată cu golul ı̂n sus
4 8 C 2 şi nu avem nicio cutie aşezată cu golul ı̂n sus
5 8 C 1 şi nu avem nicio cutie aşezată cu golul ı̂n jos
6 8 C 2 şi nu avem nicio cutie aşezată cu golul ı̂n jos
7 22 C 1
8 22 C 2

Dacă C este 1, fişierul de ieşire schi.out va conţine pe prima linie răspunsul pentru cerinţa 1
(ı̂nălţimea turnului format).
Dacă C este 2, fişierul de ieşire schi.out va conţine pe prima linie răspunsul pentru cerinţa 2
(numărul de cutii ale căror feţe laterale sunt vizibile dacă se priveşte turnul din lateral).

Restricţii şi precizări

ˆ 1 & N & 200 000.


9
ˆ Dimensiunile cutiilor sunt numere naturale din intervalul 1, 10 .
ˆ Laturile capacelor cutiilor sunt distincte două câte două.
ˆ Grosimea pereţilor care formează feţele cutiilor (inclusiv capacul) este neglijabilă.
ˆ Construcţia turnului se realizează pe o podea plană de dimensiuni infinite.

Exemple:

schi.in schi.out
1 13
5
20 3 2
371
911
11 5 1
12 6 0
2 3
5
20 3 2
371
911
11 5 1
12 6 0

Explicaţii:

Înălţimea turnului de cutii este 13. Privind din lateral sunt vizibile 3 cutii, şi anume cele care
au capacele cu laturile 20, 3 şi 12.
CAPITOLUL 1. ONI 2022 1.3. SCHI 10

1.3.1 Indicaţii de rezolvare

Propusă de: prof. Stelian Ciurea, Universitatea ”Lucian Blaga”, Sibiu


Pregătită de: stud. Ioan-Bogdan Iordache, Universitatea din Bucureşti
Editorial redactat de: stud. Ioan-Bogdan Iordache, Universitatea din Bucureşti
Soluţie ı̂n complexitate pătratică - 24 de puncte. Cutiile sunt procesate ı̂n ordinea ı̂n
care sunt adăugate ı̂n turn. Notăm cutiile cu c1 , c2 , ..., cN .
Fie două cutii ci , cj cu i $ j. Considerăm că ci a fost deja plasată ı̂n turn şi cunoaştem topi
ı̂nălţimea la care se află faţa superioară a acestei cutii (eventual ı̂nălţimea la care se află faţa
corespunzătoare capacului lipsă dacă ci este o cutie de tipul 2, ”cu golul ı̂n sus”). Ştiind topi şi
implicit poziţia cutiei ci ı̂n spaţiu, putem determina o restricţie pentru topj , considerând că nu
mai există alte cutii ı̂n afară de ci care să ı̂ntrerupă căderea lui cj.
Se disting mai multe cazuri ı̂n funcţie de tipul şi orientarea cutiilor Mi , Mj  şi diferenţa dintre
dimensiunile laturilor capacelor Li , Lj . Vom arăta mai jos câteva cazuri mai interesante:

ˆ dacă Mi 2, Mj 1 şi Li $ Lj , obţinem restricţia topj ' topi  Hj


ˆ dacă Mi 2, Mj 1 şi Li % Lj , obţinem restricţia topj ' topi  Hi  Hj
ˆ dacă Mi 1, Mj 0 şi Li $ Lj , obţinem restricţia topj ' topi

De asemenea, cutiei cj i se mai impune o restricţie şi de către podea: topj ' Hj .
Calculând pentru cutia cj restricţiile impuse de toate cutiile c1 , c2 , ..., cj 1 şi de către podea,
putem afla topj ca fiind maximul dintre acestea.
Înălţimea turnului va fi ı̂n final maxrtop1 , top2 , ..., topN x.
Pentru a determina care cutii sunt vizibile din lateral, vom defini ”intervalul de vizibilitate” al
unei cutii intervalul de ı̂nălţimi la care acea cutie este vizibilă (evident ne va interesa pentru câte
cutii acest interval are lungime nenulă).
La ı̂nceput, pentru toate cutiile de forma ci iniţializăm intervalul de vizibilitate cu topi 
Hi , topi  (intervalul de ı̂nălţimi pe care ı̂l ocupă cutia ci ). Cutia ci poate fi ascunsă/acoperită
parţial sau total doar de cutii cj , pentru care Lj % Li . Aşadar, ne vom uita la astfel de cutii cj ,
care fie nu acoperă ı̂n niciun fel cutia ci , fie o acoperă total, deci deja putem stabili că ci nu este
vizibilă din lateral, fie parţial, caz ı̂n care cj acoperă fie un prefix, fie un sufix al intervalului de
vizibilitate al lui ci .
Matematic, dacă notăm vizi intervalul de vizibilitate al lui ci calculat până la un pas interme-
diar şi dorim să actualizăm acest interval pe baza unei cutii cj cu Lj % Li , realizăm diferenţa de
intervale/mulţimi: vizi  vizi topj  Hj , topj . Este uşor de observat din procesul de plasare al
cutiilor, că această diferenţă va rezulta mereu ı̂ntr-un interval.
2
Rezolvând astfel ambele cerinţe ale problemei, obţinem complexitatea O N .

Soluţie ı̂n complexitate liniară - 100 de puncte. Pentru a obţine 100 de puncte, va fi
nevoie de o rafinare a soluţiei de mai sus.
Pentru a rezolva prima cerinţă vom ı̂ncerca să calculăm din nou topi pentru fiecare cutie ci .
Observaţia care va reduce complexitatea este că nu avem nevoie să calculăm restricţiile impuse de
c1 , ..., ci1 , cutiei ci , ci doar de către o parte dintre ele.
Notăm ci1 , ci2 , ..., cik (1 & i1 $ i2 $ ... $ ik $ i) cutiile care ar putea ı̂ntrerupe căderea lui ci .
Observăm ca acest şir de cutii este descrescător după L: dacă am avea cij şi cij1 cu Lij $ Lij1 ,
cutia cij ar fi inutilă (oricum ar cădea, ci se va lovi mai ı̂ntâi de cij1 , nu de cij ).
O altă observaţie utilă este că pentru două cutii cij şi cij1 cu Lij % Lij1 % Li , restricţia
impusă de cij1 cutiei ci va fi cel puţin la fel de mare ca restricţia impusă de cij . Cu alte cuvinte,
ı̂n subşirul descrescător de cutii ci1 , ci2 , ..., cik , pentru a determina topi vom calcula restricţiile
doar cu un sufix al acestui subşir (cutiile cu L mai mic decât Li , şi cutia cu L minim mai mare
decât Li ).
După ce am calculat topi , eliminăm din subşir sufixul format din cutii cu L mai mic decât
Li şi adăugăm cutia ci la finalul subşirului. Astfel per total numărul de restricţii calculat pentru
toate cutiile este direct proporţional cu numărul de ştergeri din acest subşir, iar din moment ce o
cutie este adăugată şi ştearsă din subşir cel mult o dată, complexitatea va fi liniară.
Pentru a determina vizibilitatea cutiilor, vom folosi noţiunea de interval de vizibilitate descrisă
mai sus. Vom actualiza aceste intervale pentru toate cutiile mai ı̂ntâi considerând doar acoperirile
CAPITOLUL 1. ONI 2022 1.3. SCHI 11

realizate de cutiile de tipul 2 (cu golul ı̂n sus) şi apoi separat cele realizate de cutii de tipul 0 (cu
golul ı̂n jos).
Vom explica ı̂n continuare procesul pentru acoperirile produse de cutii de tipul 2. Vom procesa
cutiile ı̂n ordinea ı̂n care au fost adăugate ı̂n turn. În momentul de faţă suntem la cutia ci de un
tip oarecare şi vrem să vedem ce cutii de tipul 2 o acoperă parţial/total. Evident aceste cutii, ca
să poată acoperi ci , trebuiau adăugate ı̂nainte şi să aibă L mai mare decât Li .
Dintre cutiile de tip 2 care respectă aceste condiţii, ne interesează top-ul maxim (extremitatea
superioară maximă) a acestor cutii care poate acoperi un prefix al intervalului de vizibilitate
pentru ci .
O observaţie care ne va duce la soluţia dorită este că dacă după o cutie de tip 2 se adaugă la
un pas ulterior o cutie cu L mai mare (de orice tip), cutia de tip 2 nu va mai acoperi pe nimeni
ı̂ncepând cu acest moment.
De aceea, la pasul i putem menţine subşirul de cutii de tip 2: ci1 , ci2 , ..., cik (1 & i1 $ i2 $ ... $
ik $ i) cu proprietatea că Li1 % Li2 % ... % Lik . Când procesăm cutia ci eliminăm din subşir toate
cutiile cu L mai mic decât Li (acestea nu pot acoperi ci şi nicio altă cutie adăugată ı̂n viitor).
Presupunem că rămânem acum cu k cutii ı̂n subşir, pentru a determina ce prefix din intervalul
de vizibilitate al lui ci ar fi acoperit, trebuie să determinăm maxrtopi1 , topi2 , ..., topik x. Acesta
nu este altceva decât un maxim pe prefix pentru subşirul nostru, pe care ı̂l putem menţine ı̂n
complexitate constantă atunci când modificăm subşirul. După acest pas, ı̂n cazul ı̂n care ci este
de tipul 2 o adăugăm la finalul subşirului şi continuăm cu cutia i  1.
Pentru determinarea acoperirilor produse de cutii de tipul 0 (cu golul ı̂n jos) strategia este
similară, ı̂nsă cutiile vor trebui parcurse ı̂n ordinea inversă a adăugării lor ı̂n turn (”de sus ı̂n
jos”).
În final, complexitatea obţinută pentru ambele cerinţe este O N .

1.3.2 *Cod sursă

1.3.3 *Rezolvare detaliată


Capitolul 2

ONI 2021

2.1 Le Mans
Problema 1 - Le Mans 100 de puncte
Ne aflăm ı̂nainte de ı̂nceputul faimoasei curse de anduranţă de la Le Mans. După cum bine
stiţi, ı̂ntr-o cursă de anduranţă maşina care a parcurs cea mai mare distanţă pe parcursul cursei
este considerată câştigătoare.
Anul acesta Federaţia Internaţională de Automobilism (FIA) a făcut câteva schimbări majore
cu privire la desfăşurarea cursei. Anul acesta cursa va dura exact T secunde şi vor participa N
echipe, fiecare echipă având câte o maşină, iar fiecare maşină poate pleca de pe oricare dintre cele
M poziţii din grila de start.
De asemenea, FIA a impus câteva reguli care au nemulţumit echipele participante:
ˆ Fiecare maşină este obligată să se deplaseze cu o viteză constantă pe parcursul ı̂ntregii curse.
Astfel, a i-a maşină se va deplasa cu viteza de v i metri pe secundă.
ˆ Dacă o maşină pleacă de pe o poziţie j din grila de start, aceasta se află la o distanţă de
pj  metri după linia de start, iar această distanţă este luată ı̂n considerare ca o distanţă
deja parcursă ı̂n cadrul cursei.

Cerinţe

Ca semn de protest asupra noului regulament, echipele au hotărât să se aşeze ı̂n grila de start
astfel ı̂ncât diferenţa maximă dintre distanţele parcurse de oricare două maşini să fie cât mai mică
posibil.

Date de intrare

Pe prima linie din fişierul lemans.in se vor afla 3 numere:


ˆ T - durata cursei exprimată ı̂n secunde,

ˆ N - numărul de maşini,

ˆ M - numărul de poziţii de start din grilă.

Pe a doua linie se află N numere separate prin câte un spaţiu, reprezentând şirul v de viteze
ale maşinilor.
Pe a treia linie se află M numere separate prin câte un spaţiu, reprezentând şirul p - distanţele
faţă de linia de start a poziţiilor de start din grilă.

Date de ieşire

Fişierul lemans.out va conţine pe prima linie un singur număr, reprezentând valoarea minimă
posibilă a diferenţei maxime dintre distanţele parcurse de oricare două maşini. Pe a doua linie se
vor afla N numere ı̂ntre 1 şi M separate prin câte un spaţiu, al i-lea număr reprezentând poziţia
de start din grilă a maşinii cu numărul i.

Restricţii şi precizări

12
CAPITOLUL 2. ONI 2021 2.1. LE MANS 13

ˆ 2 & N, M & 103 ,


ˆ 1&T & 103 ,
ˆ 1 & v i & 10 i " r1, 2, ..., N x,
6

ˆ 1 & pi & 10 " r1, 2, ..., M x,


9
i, j
ˆ Două sau mai multe maşini pot porni de pe aceeaşi poziţie din grila de start,

ˆ În grilă pot exista şi poziţii neocupate de o maşină,

ˆ Pot exista mai multe distribuţii ale maşinilor pe grila de start, ce oferă o soluţie optimă. Se
acceptă orice soluţie corectă.
ˆ Subtask 1 - 8 puncte - M 1,
ˆ Subtask 2 - 9 puncte - M 2,
ˆ Subtask 3 - 10 puncte - N, M & 7,
ˆ Subtask 4 - 19 puncte - v i & 10 şi pi & 10 ,
3 6

ˆ Subtask 5 - 23 de puncte - N, M & 100,


ˆ Subtask 6 - 31 de puncte - nu există restricţii suplimentare.

Exemple:

lemans.in lemans.out Explicaţii


543 5 Distanţa minimă posibilă este 5 şi se poate obţine astfel:
2345 3122 a Maşina 1 pleacă de pe poziţia 3, deci va parcurge p3  v 1 5
7 1 11 11  2 5 21 metri;
a Maşina 2 pleacă de pe poziţia 1, deci va parcurge p1  v 2 5
7  3 5 22 metri;
a Maşina 3 pleacă de pe poziţia 2, deci va parcurge p2  v 3 5
1  4 5 21 metri;
a Maşina 4 pleacă de pe poziţia 2, deci va parcurge p2  v 4 5
1  5 5 26 metri.

Timp maxim de executare/test: 0.2 secunde


Memorie: total 256 MB

2.1.1 Indicaţii de rezolvare

Propunător: Tulbă-Lecu Theodor-Gabriel, Universitatea Politehnica din Bucureşti


Soluţie pentru cazul M 1 - 8 puncte
În cazul ı̂n care M 1, atunci problema se reduce la a găsi diferenţa maximă dintre vitezele
a oricăror 2 maşini, astfel răspunsul va fi dif fmax T .
Soluie pentru cazul M 2 - 9 puncte
Pentru M 2, avem ı̂n vedere cea mai lentă Ml şi cea mai rapidă Mr maşină, şi respectiv cea
mai din spate P1 şi cea mai din faţă P2 poziţie de start.
Distingem 2 cazuri:

1. Ml şi Mr ı̂ncep de pe aceeaşi poziţie, caz ı̂n care putem să alegem toate maşinile de pe acea
poziţie.
2. Ml ı̂ncepe de pe poziţia P2 şi Mr ı̂ncepe de pe poziţia P1 . Atunci, pentru celelalte maşini
alegem poziţiile astfel ı̂ncât distanţa parcursă plecând de la acea poziţie să fie cât mai aproape
de intervalul dat de distanţele parcurse de maşinile Ml şi Mr .
CAPITOLUL 2. ONI 2021 2.2. OPOSUMI 14

Soluţie pentru cazul M, N & 7 - 10 puncte


Pentru N şi M suficient de mici, putem implementa un algoritm de tip backtracking pentru a
ı̂ncerca toate modurile de a aşeza maşinile pe grila de start.
N
Complexitate temporală: O M 
Soluţie pentru cazul N, M & 100 - 23 de puncte
Putem să fixăm o maşină pe o poziţie şi impunem ca aceasta să fie maşina care va parcurge
cea mai mică distanţă, pe care o vom nota cu Dmin . Acum putem parcurge toate celelalte maşini
şi vom alege pentru fiecare maşină cea mai mică poziţie de start astfel ı̂ncât aceasta să termine
ı̂naintea ultimei maşini.
Putem implementa acest lucru sortând poziţiile de start şi căutând binar pentru fiecare maşină
i, poziţia px minimă astfel ı̂ncât v i T  px ' Dmin .
Complexitate temporală: O N M M log M 
O altă implementare a acestei soluţii este să sortăm atât maşinile cât şi poziţiile de start
şi să găsim soluţiile folosind doi pointeri, unul pentru maşini şi unul pentru poziţii. Putem face
acest lucru deoarece o maşină mai rapidă va avea o poziţie de start mai mică sau egală cu maşina
precedentă ı̂n vectorul sortat.
Complexitate temporală: O N M N  M 
Soluţie pentru cazul v i & 103, pi & 10 - 19 puncte
6

Pentru a obţine o soluţie mai bună vom face următoarea construcţie:


6
Întrucât valoarea maximă a distanţei parcurse de o maşină, Dmax , este mai mică decât 2 10 ,
putem construi un vector de apariţii, să ı̂l numim pos. Vom adăuga pentru fiecare pereche maşină
i şi poziţie de start j - pe posv i T  pj , perechea i, j .
Soluţia optimă este intervalul de lungime minimă a, b astfel ı̂ncât, ı̂n cadrul tuturor perechilor
din vectorul de apariţie pos cuprinse ı̂ntre a şi b, fiecare maşină să apară cel puţin o singură dată.
Acest lucru se poate realiza uşor folosind doi pointeri pentru capetele intervalului şi un vector
de frecvenţă pentru a ştii de câte ori apare o maşină ı̂n interval.
Complexitate temporală: O N M  Dmax 
Soluţie oficială - 100 de puncte
Vom proceda la fel ca la soluţia anterioară, dar de data aceasta vom ı̂nlocui vectorul de
frecvenţă cu un vector de tuple (v i T  pj , i, j), pe care ı̂l vom sorta crescător după distanţa
parcursă.
Astfel complexitatea temporală finală este: O N M log M N 

2.1.2 *Cod sursă

2.1.3 *Rezolvare detaliată

2.2 Oposumi
Problema 2 - Oposumi 100 de puncte
O familie de oposumi are o vizuină cu N niveluri şi N N  1©2 camere dispuse ı̂n formă
de matrice triunghiulară cu N linii. În fiecare cameră poate locui un singur oposum. Vizuina a
fost săpată ı̂n pământ de către oposumi, iar nivelul 1 (cel mai de sus) este cel mai apropiat de
suprafaţa solului. Pe fiecare nivel I se află I camere. Dacă avem I $ J, atunci nivelul I va fi
poziţionat mai sus decât nivelul J, adică nivelul I va fi mai aproape de suprafaţa solului decât
nivelul J. În familia de oposumi se află exact N N  1©2 membri cu vârste cuprinse ı̂ntre 1 şi
N N  1©2, vârste distincte. Regula de bază ı̂n vizuina familiei de oposumi este următoarea:
ı̂n camera de pe linia I şi coloana J trebuie să locuiască un oposum mai tânăr decât ı̂n camerele
de pe poziţiile I  1, J  respectiv I  1, J  1. Un oposum de vârsta X se consideră mai tânăr
decât un oposum de vârsta Y dacă X $ Y . Fiecare oposum vrea să ştie care e cel mai de sus nivel
pe care se poate poziţiona. Din păcate, ei nu au lăbuţele făcute să programeze, aşa că membrii
familiei de oposumi vă cer vouă ajutorul.
CAPITOLUL 2. ONI 2021 2.2. OPOSUMI 15

Dându-se numărul natural N ei vă cer să răspundeţi la două ı̂ntrebări:

Cerinţe

Cerinţa 1 (50 de puncte)


Pentru fiecare oposum să se afle nivelul cel mai de sus (cel mai aproapiat de suprafaţa solului)
pe care se poate afla respectând regulile de vârstă.
Cerinţa 2 (50 de puncte)
Pentru un oposum dat de vârsta K să se afişeze matricea astfel ı̂ncât oposumul să stea ı̂ntr-o
cameră pe un nivel cât mai de sus respectând regulile de vârstă.

Date de intrare

Pe prima linie a fiş ierului de intrare oposumi.in se găseşte numărul T ce poate avea valoarea
1 sau 2 astfel ı̂ncât:
ˆ Dacă T are valoarea 1, atunci se cere rezolvarea cerinţei 1, iar ı̂n continuare se va regăsi
numărul natural N reprezentând numărul de niveluri ale vizuinii.
ˆ Dacă T are valoarea 2, atunci se cere rezolvarea cerinţei 2, iar ı̂n continuare se va regăsi
numărul natural N reprezentând numărul de niveluri ale vizuinii, urmat de numărul natural
K ce reprezintă vârsta oposumului ce se doreşte poziţionat pe un nivel cât mai de sus.

Date de ieşire

În fişierul de ieşire oposumi.out se cere să se tipărească:


ˆ Pentru T 1 se va afişa un şir de N N  1©2 numere, unde cel de al I-lea număr reprezintă
cel mai de sus nivel pe care se poate afla oposumul de vârstă I.
ˆ Pentru T 2 se vor afişa N linii, reprezentând modul ı̂n care sunt aşezaţi oposumii ı̂n vizuină
ı̂n funcţie de vârstă, astfel ca oposumul de vârsta K să fie poziţionat pe un nivel cât mai
de sus posibil. Pe linia I se vor afişa I numere separate prin câte un spaţiu reprezentând
vârstele oposumilor aşezaţi pe nivelul I.

Restricţii şi precizări

ˆ 1&N & 1000,


ˆ 1&K &N N  1©2,
ˆ Pentru cerinţa T 2 soluţia nu este unică. Se acceptă orice soluţie corectă,
ˆ Testele nu sunt grupate.

Exemple:

oposumi.in oposumi.out Explicaţii


13 122233 Cel mai de sus nivel la care poate locui un oposum este:
a Oposumul de vârsta 1 poate locui la nivelul 1 .
a Fiecare oposum cu vârsta 2, 3 sau 4 ar putea locui la nivelul 2.
a Oposumii care au vârsta 5 sau 6 vor locui la nivelul 3.
247 1 Cel mai de sus nivel, unde poate locui oposumul cu vârsta de
23 K 7 ani, este nivelul 3, iar o aşezare posibilă a oposumilor ı̂n
457 camere este prezentată alăturat.
6 8 10 9

Timp maxim de executare/test: 0.2 secunde


Memorie: total 256 MB
CAPITOLUL 2. ONI 2021 2.3. ELHC 16

2.2.1 Indicaţii de rezolvare

Propunător: studentă Mihaela Cişmaru, Universitatea Politehnica Bucureşti, Facultatea de


automatică şi calculatoare.
Soluţie cerinţa 1
Se parcurge matricea triunghiulară ı̂n următorul fel: se parcurge coloana 1 de sus in jos, se
parcurge coloana 2 de sus ı̂n jos ... se parcurge coloana N de sus ı̂n jos. La fiecare pas hotărâm
care este poziţia cea mai ı̂naltă: poziţia curentă sau prima poziţie de pe următoarea coloană şi
notăm ı̂nălţimea maximă a celor două ca fiind cel mai de sus loc unde se poate situa oposumul ce
urmează să fie aşezat.
Soluţie cerinţa 2
Se aşează elementele de la 1 la K  1 astfel: se aşează primii N oposumi pe coloana 1 de sus ı̂n
jos, următorii N  1 oposumi pe coloana 2 de sus ı̂n jos ... şi aşa mai departe. Când am terminat
de pus cei k  1 oposumi, punem oposumul rămas pe cea mai de sus poziţie liberă, aceasta fiind
fie poziţia ce urmează, fie prima poziţie de pe următoarea coloană. După ce am aşezat oposumul
k, continuăm să aşezăm ı̂n acelaşi mod oposumii rămaşi.

2.2.2 *Cod sursă

2.2.3 *Rezolvare detaliată

2.3 ELHC
Problema 3 - ELHC 100 de puncte
După şase ani de lucru, Charles a terminat de curăţat instalaţiile pentru producerea negrului
de fum din Copşa Mică. Pentru a se ţine departe de mesele de Blackjack, el s-a angajat la CERN,
unde va lucra la noul accelerator de particule numit Even Larger Hadron Collider (ELHC).
ELHC are forma unui tunel circular cu o circumferinţă de P kilometri, P fiind un număr prim.
De-a lungul tunelului sunt plasaţi P senzori numerotaţi de la 0 la P  1, distanţa dintre doi senzori
consecutivi fiind de exact 1 kilometru.
Un experiment efectuat ı̂n ELHC constă ı̂n studierea unei particule de tip G, 1 & G $ P .
Dacă această particulă este ridicată la nivelul de energie k şi este lansată din dreptul senzorului
k
0 ı̂n direct, ia senzorului 1, ea va parcurge exact G kilometri prin tunel şi apoi se va dezintegra,
declanşând ı̂n acel moment senzorul s ı̂n dreptul căruia are loc dezintegrarea particulei.
Se consideră că experimentul are date complete dacă, lansând P  1 particule de tip G ridicate
la toate nivelurile de energie k de la 1 la P  1, este posibil să declanşăm toţi senzorii s numerotaţi
cu valori ı̂ntre 1 şi P  1, adică toţi senzorii din tunel mai puţin senzorul 0.

Cerinţe

Dându-se T perechi de numere G şi P , determinaţi dacă experimentul pentru studierea par-
ticulei de tip G ı̂ntr-un tunel de circumferinţă P produce date complete.

Date de intrare

Fişierul de intrare elhc.in conţine pe prima linie un număr T , reprezentând numărul de ex-
perimente care vor fi efectuate. Pe fiecare din următoarele T linii se află câte două numere G
şi P separate printr-un spaţiu, reprezentând efectuarea unui experiment cu o particulă de tip G
ı̂ntr-un tunel de circumferinţă P .

Date de ieşire

În fişierul de ieşire elhc.out se va afla o singură linie cu T biţi scrişi unul după altul, adică
fără spaţii ı̂ntre ei. Al i-lea bit este 1 dacă pentru cel de-al i-lea experiment putem obţine date
complete, şi 0 ı̂n caz contrar.
CAPITOLUL 2. ONI 2021 2.3. ELHC 17

Restricţii şi precizări

ˆ 1&T & 1 000,


ˆ 1&G$P $ 1 000 000 000,
ˆ P este un număr prim,
ˆ Subtask 1 - 7 puncte - P & 100,
ˆ Subtask 2 - 14 puncte - P & 10 000,
ˆ Subtask 3 - 53 de puncte - P & 1 000 000,
ˆ Subtask 4 - 26 de puncte - nu există restricţii suplimentare.

Exemple:

elhc.in elhc.out Explicaţii


6 110100 Fişierul de intrare conţine T 6experimente.
23 A doua particulă are tipul 3 şi va fi lansată printr-un tunel de circumferinţă
35 5, cu 5 senzori numerotaţi de la 0 la 4. Ridicată la nivelurile de energie 1, 2,
27 3, respectiv 4, şi lansată de fiecare dată din dreptul senzorului 0, particula
37 va călători 3, 9, 27, respectiv 81 de kilometri şi va declanşa senzorii 3, 4, 2,
3 11 respectiv 1. Aceştia sunt toţi senzorii pe care trebuie să-i declanşăm, prin
5 11 urmare experimentul produce date valide, deci al doilea bit din şirul afişat
este 1.
A treia particulă are tipul 2 şi va fi lansată printr-un tunel de circumferinţă
7. Ridicată la nivelurile de energie 1, 2, 3, 4, 5, respectiv 6, şi lansată de
fiecare dată din dreptul senzorului 0, particula va declanşa senzorii 2, 4, 1,
2, 4, respectiv 1. Deoarece nu declanşăm senzorii 3, 5 şi 6, experimentul nu
are date complete, deci al treilea bit din şirul afişat este 0.

Timp maxim de executare/test: 0.15 secunde


Memorie: total 256 MB din care pentru stivă 2 MB

2.3.1 Indicaţii de rezolvare

Propunători:
Ştefania Ionescu, University of Zurich,
Alexandru Petrescu, University of Oxford (Keble College),
Vlad Gavrilă, University of Cambridge (Churchill College)
Autori editorial: Vlad Gavrilă, Ştefan Manolache
Soluţie parţială - 7 puncte, respectiv 21 de puncte
Prima observaţ ie necesară este că o particulă G lansată la nivelul de energie k ı̂ntr-un tunel
k
de circumferinţă P va activa senzorul G moduloP .
Astfel, prima abordare este să trecem prin puterile lui GmoduloP şi să marcăm ı̂ntr-un vector
de apariţie care dintre senzorii 1, 2, ..., P  1 au fost activaţi.
2
Putem face asta calculând fiecare putere individual, liniar, ı̂n O T P , pentru a rezolva
problema pentru 7 puncte, sau să calculăm ı̂n O T P  toate puterile, pentru a rezolva problema
pentru 21 de puncte.
Soluţie parţială - 74 puncte
A doua observaţie importantă este că valorile senzorilor pe care ı̂i activăm sunt periodice.
k1
Dacă G  1 moduloP , atunci G
k
 G, Gk2  G2, etc. Aşadar, odată ce activăm senzorul 1,
incrementând nivelul k de energie, suntem siguri că am activat toţi senzorii pe care puteam să-i
activăm.
P 1
Mai mult, avem garanţia că G  1moduloP , folosind Mica Teoremă a lui Fermat. Astfel,
P 1
dacă G este prima putere a lui G egală cu 1moduloP , ı̂nseamnă că până atunci nu am ı̂ntâlnit
CAPITOLUL 2. ONI 2021 2.3. ELHC 18

2 3 P 1
nicio repetiţie, deci toate valorile 1, 2, ..., P  1 sunt atinse de catre G, G , G , G (nu neapărat
ı̂n ordinea aceasta). În schimb, dacă există o putere a lui G mai mică, egală cu 1moduloP , atunci
nu este posibil să atingem toate numerele dorite, din cauza periodicităţii.
Aşadar, problema noastră se reduce la: un experiment cu o particulă G ı̂ntr-un tunel de
lungime P are date complete  G
P 1
este cea mai mică putere a lui G egală cu 1moduloP .
A treia observaţie de care avem nevoie este că, dacă k este cel mai mic exponent pentru care
G  1 moduloP , atunci k ¶P  1. Această afirmaţie se poate demostra uşor prin reducere la
k

absurd: dacă k este cel mai mic exponent pentru care G  1 moduloP , dar k j P  1, atunci
k

restul r al ı̂mpărţirii lui P  1 la k ar fi un exponent mai mic decât k pentru care G  1moduloP ,
r

fapt ce reiese din periodicitatea valorilor puterilor lui G moduloP .


Aşadar, tot ce trebuie să facem e să verificăm, pentru toţi Ó
divizorii d ai lui P  1, dacă
G k 1 moduloP . Putem afla divizorii lui P ı̂n complexitate O P , iar dacă folosim un algoritm
d

de exponenţiere rapidă, putem obţine puterile lui G corespunzătoare divizorilor lui P  1 ı̂n
O log P ˜ D P  1, (unde D nÓ  este numărul divizorilor lui n, care ı̂n medie este log N şi
niciodată nu este mai mare de 2 ˜ n). Ó
Astfel, complexitatea finală a soluţiei pentru 74 de puncte este O T ˜ P  log P ˜ D P  1.
Soluţie completă
Pornind de la soluţia precedentă, observăm că este suficient să verificăm doar puterile
P  1 P  1 P 1
p1 K1 , px K2 , ... p Kx ,
x

unde p1 , p2 , ..., px sunt factorii primi ai lui P  1.


Este suficient să verificăm doar aceste puteri pentru că orice divizor d al lui P  1 mai mic decât
P  1 este garantat să fie, de asemenea, un divizor al unuia dintre aceste numere. Astfel, dacă
există k, cel mai mic divizor pentru care G  1moduloP , atunci din periodicitate vom obţine şi
k

că pentru orice multiplu de k, m nk, G  1moduloP .


m

Cum cel puţin unul dintre numerele K1 , K2 , ..., Kx este un multiplu al lui k, obţinem faptul
că pentru a confirma existenţa lui k este suficient să verificăm doar dacă există un y pentru care
G y  1moduloP .
K

Deoarece numărul de factori primi al unui număr creşte asimptotic cu O  logloglogP P , obţinem
Ó log P
2
complexitatea finală O T ˜  P  log log P
(păstrând acelaşi algoritm de factorizare a lui P , dar
şi exponenţierea rapidă pentru obţinerea puterilor lui K din soluţia precedentă).
Altă optimizare, nenecesară pentru obţinerea punctajului maxim, este să folosim Ciurul lui
Eratostene pentru a precalcula toate numerele prime mai mici decât valoarea maximă a lui P ,
pentru a optimiza obţinerea divizorilor fiecărui P din fişierul de intrare.

2.3.2 *Cod sursă

2.3.3 *Rezolvare detaliată


Capitolul 3

ONI 2020 - suspendat !!!

... ... ... ...

19
Capitolul 4

ONI 2019

4.1 amat
Problema 1 - amat 100 de puncte
Pasionat de informatică şi de puzzle-uri, Dorel a construit o matrice A de dimensiunea N  M
lipind mai multe piese dreptunghiulare de diferite dimensiuni. Fiecare piesă este compusă din
elemente de dimensiunea 1  1 şi reţin o aceeaşi valoare (vezi exemplele). Matricea rezultată nu
are spaţii libere, iar piesele din care este compusă nu se suprapun. Nu există două piese cu aceeaşi
valoare.
Deşi iniţial părea că acest design este unul inedit, nu a durat mult până când Dorel s-a plictisit.
Astfel, acum el doreşte să ”upgradeze” matricea construită. Dorel alege o submatrice delimitată
de coordonatele x1, y1 - colţul stânga-sus, x2, y2 - colţul dreapta-jos (1 & x1 & x2 & N ,
1 & y1 & y2 & M ), unde creşte toate valorile elementelor submatricei cu valoarea V .
Dorel efectuează ı̂n ordine Q operaţii de upgrade, operaţii numerotate de la 1 la Q. La
finalizarea celor Q operaţii de upgrade, toate elementele din matrice au valoarea mai mare sau
egală cu K. După o operaţie de upgrade, structura iniţială a matricei se modifică.

Cerinţe

Cum priceperea lui Dorel este proverbială, trebuie să ı̂l ajutaţi ı̂n rezolvarea următoarelor
cerinţe:
1) determinarea coordonatelor piesei cu număr maxim de elemente ı̂nainte ca Dorel să efectueze
operaţiile de upgrade;
2) determinarea numărului minim de operaţii de upgrade după care toate elementele matricei
au valoarea mai mare sau egală cu K.

Date de intrare

Datele de intrare se citesc din fişierul amat.in, care are următoarea structură:
a pe prima linie se află numărul natural C, care poate fi egal cu 1 sau 2, ı̂n funcţie de cerinţa
ce trebuie rezolvată;
a pe linia următoare se află două numerele naturale N şi M cu semnificaţia din enunţ;
a pe următoarele N linii se găsesc elementele matricei A.
a dacă C 2 atunci fişierul de intrare mai conţine:
- pe linia N  2 numerele naturale Q K cu semnificaţiile din enunţ;
- pe următoarele Q linii descrierea submatricelor asupra cărora se efectuează operaţii de up-
grade de forma: x1 y1 x2 y2 V

Date de ieşire

Datele de ieşire se vor scrie ı̂n fişierul amat.out, astfel:


Dacă C 1 se vor scrie, separate prin spaţiu patru numere naturale nenule x1 y1 x2 y2 ce
reprezintă coordonatele colţului stânga-sus, respectiv colţului dreapta-jos unde este plasată piesa
cu număr maxim de elemente ı̂nainte de upgrade. Dacă există mai multe astfel de piese, atunci
vor fi scrise coordonatele piesei pentru care coordonatele colţului stânga-sus are valoarea liniei cea
mai mare, iar la linii egale, se va alege piesa cu coordonata coloanei cea mai mare.
Dacă C 2 se va scrie numărul natural nenul N R ce reprezintă numărului minim de operaţii
de upgrade după care toate elementele matricei au valoarea mai mare sau egală cu K.

20
CAPITOLUL 4. ONI 2019 4.1. AMAT 21

Restricţii şi precizări

a 2 & N, M & 1000; 1 & Q & 100000; 1 & V & 1000


a 1000 & elementele matricei A ı̂nainte de upgrade & 1000
a Operaţiile de upgrade se efectuează obligatoriu ı̂n ordinea citirii
a Pentru teste ı̂n valoare de 30 de puncte, C 1
a Pentru teste ı̂n valoare de 30 de puncte, C 2 şi N, M, Q & 250
a Pentru teste ı̂n valoare de 50 de puncte, C 2 şi Q & 4000
a Pentru teste ı̂n valoare de 70 de puncte, C 2.

Exemple

Figura 4.1: amat

amat.in amat.out Explicaţii


2 2 Se rezolvă cerinţa 2.
46 Matricea iniţială construită este cea prezentată mai sus.
11132 2 Dorel efectuează 3 operaţii de upgrade.
11132 2 Matricea obţinută după efectuarea primului upgrade:
64442 2 666322
64445 7 666322
36 11 9 9 4 2 2
11335 644457
12465 Matricea obţinută după efectuarea celui de-al doilea upgrade:
41431 6 11 11 8 7 7
6 11 11 8 7 7
11 14 14 9 7 7
6 9 9 9 10 12
Matricea obţinută după efectuarea celui de-al treilea upgrade:
6 11 11 8 7 7
6 11 11 8 7 7
11 14 14 9 7 7
7 10 10 9 10 12
La final tuturor operaţiilor de upgrade, matricea are toate valorile
mai mari sau egale cu 6.
Se observă că sunt suficiente primele două operaţii de upgrade pentru
că toate elementele matricei să fie mai mari sau egale cu 6.

Timp maxim de executare/test: Windows: 1.5 secunde, Linux: 0.5 secunde


Memorie: total 64 MB
Dimensiune maximă a sursei: 10 KB

4.1.1 Indicaţii de rezolvare


Prof. Eugen Nodea - Colegiul Naţional ”Tudor Vladimirescu”, Tg-Jiu

Soluţie cerinţa 1 30p


CAPITOLUL 4. ONI 2019 4.1. AMAT 22

Folosim un vector de frecvenţe. Cum indexarea vectorului pleacă de la 0, pentru determinarea


frecvenţei de apariţii a elementelor negative vom deplasa intervalul valorilor de intrare:
1000, 1000 % 0, 2000;
Complexitatea acestei soluţii este O N M  şi ar trebui să obţină 30 de puncte.
Soluţie cerinţa 2 20p
Pentru fiecare operaţie ı̂n parte, vom aduna valoarea operaţiei pe fiecare element al submatricei.
La fiecare pas, verificăm dacă există un element din matrice strict mai mic decât K. În caz contrar,
ne oprim.
Această soluţie are complexitate O N M Q şi ar trebui să obţină minimum 20 de puncte.
Soluţie cerinţa 2 50p
Deoarece valorile matricei pot doar să crească după fiecare operaţie, se observă că răspunsul
poate fi găsit folosind căutare binară.
Pentru a verifica dacă, după un număr de operaţii, valorile matricei sunt toate mai mari sau
egale cu K, vom calcula efectiv matricea după simularea operaţiilor.
Să considerăm fiecare linie independent. Fiecare operaţie de upgrade va consta ı̂n O(N) operaţii
de adăugare de interval pentru fiecare vector linie. Pentru a simula operaţiile de adăugare mai
rapid, ne putem folosi de ideea că trebuie doar să aflăm valorile finale.
Să considerăm o matrice B unde B i, 1 A i, 1 şi B i, j  A i, j   A i, j  1. O operaţie
de adăugare cu valoarea x pe intervalul a, b pe vectorul-linie A i, ˜ se poate simula rapid pe
matricea B prin operaţiile B i, a x şi B i, b  1 x. La final, putem reconstitui matricea
A calculând sumele parţiale pentru fiecare vector-linie B i, ˜.
Această soluţie are complexitate O Q  M N log Q şi ar trebui să obţină minimum 50 de
puncte.
Soluţie cerinţa 2 70p
Pentru a obţine punctajul maxim alocat cerinţei 2, vom extinde ideea de mai sus pentru cazul
bidimensional.
Astfel, vom crea o matrice C astfel ı̂ncât C i, j  A i, j   A i  1, j   A i, j  1  A i  1, j  1.
În acest caz, o operaţie de adăugare cu valoarea x pe submatricea i1, j1, i2, j2 se va simula prin
operaţiile C i1, j1 x C i1, j2  1 x C i2  1, j1 x şi C i2  1, j2  1 x.
În final, pentru a reconstitui matricea A, vom calcula sumele parţiale 2D pe matricea C.
Această soluţie are complexitate O Q  M N log Q şi ar trebui să obţină 70 de puncte.

4.1.2 Cod sursă

Listing 4.1.1: alex-nnlog.cpp


#include <iostream>
#include <fstream>
#include <assert.h>

#define x1 first.first
#define y1 first.second

#define x2 second.first
#define y2 second.second.first

#define val second.second.second

using namespace std;

ifstream fin("amat.in");
ofstream fout("amat.out");

int init[1002][1002], dp[1002][1002];

pair< pair<int,int>, pair<int, pair<int,int> > > q[250001];

int qq,k,n,m;

int main()
{
int optiune;
fin >> optiune >> n >> m;
CAPITOLUL 4. ONI 2019 4.1. AMAT 23

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
fin >> init[i][j];

fin >> qq >> k;

for(int i = 1; i <= qq; i++)


fin >> q[i].x1 >> q[i].y1 >> q[i].x2 >> q[i].y2 >> q[i].val;

int st = 1;
int dr = qq;
int rasp = 0;
while(st <= dr)
{
int mij = (st + dr) / 2;
int ok = 1;
for(int i = 1; i <= mij; i++)
dp[q[i].x1][ q[i].y1 ] += q[i].val,
dp[q[i].x2 + 1][ q[i].y2 + 1] += q[i].val,
dp[q[i].x1][ q[i].y2 + 1 ] -= q[i].val,
dp[q[i].x2 + 1][ q[i].y1] -= q[i].val;

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
{
dp[i][j] += dp[i][j - 1] + dp[i - 1][j] - dp[i - 1][j - 1];
if(dp[i][j] + init[i][j] < k)
ok = 0;
}

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
dp[i][j] = 0;

if(ok)
rasp = mij, dr = mij - 1;
else
st = mij + 1;
}

fout << rasp << ’\n’;


return 0;
}

Listing 4.1.2: alex-nqlog.cpp


#include <iostream>
#include <fstream>
#include <assert.h>

#define x1 first.first
#define y1 first.second

#define x2 second.first
#define y2 second.second.first

#define val second.second.second

using namespace std;

ifstream fin("amat.in");
ofstream fout("amat.out");

int init[1002][1002], dp[1002][1002];

pair< pair<int,int>, pair<int, pair<int,int> > > q[250001];

int qq,k,n,m;

int main()
{
int optiune;
fin >> optiune >> n >> m;
CAPITOLUL 4. ONI 2019 4.1. AMAT 24

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
fin >> init[i][j];

fin >> qq >> k;

for(int i = 1; i <= qq; i++)


fin >> q[i].x1 >> q[i].y1 >> q[i].x2 >> q[i].y2 >> q[i].val;

int st = 1;
int dr = qq;
int rasp = 0;
while(st <= dr)
{
int mij = (st + dr) / 2;
int ok = 1;
for(int i = 1; i <= mij; i++)
for(int j = q[i].x1; j <= q[i].x2; j++)
dp[j][ q[i].y1 ] += q[i].val,
dp[j][ q[i].y2 + 1] -= q[i].val;

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
{
dp[i][j] += dp[i][j - 1];
if(dp[i][j] + init[i][j] < k)
ok = 0;
}

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
dp[i][j] = 0;

if(ok)
rasp = mij, dr = mij - 1;
else
st = mij + 1;
}

fout << rasp << ’\n’;


return 0;
}

Listing 4.1.3: amat eugen.cpp


#include <bits/stdc++.h>

using namespace std;

ifstream f("amat.in");
ofstream g("amat.out");

struct submatrix
{
int x1, y1, x2, y2;
int k;
} ap[2003];

struct qry
{
int x1, y1, x2, y2, w;
} Q[100001];

const int NM = 1002;

int a[NM][NM], A[NM][NM];


int B[NM][NM];
int n, m, q, c, K;

bool verif(int nr)


{
int x1, y1, x2, y2, w, x;

memset(A, 0, sizeof(A));
CAPITOLUL 4. ONI 2019 4.1. AMAT 25

for(int i = 1; i <= nr; ++i)


{
x1 = Q[i].x1; y1 = Q[i].y1;
x2 = Q[i].x2; y2 = Q[i].y2;
w = Q[i].w;
A[x1][y1] += w;
A[x1][y2 + 1] -= w;
A[x2 + 1][y1] -= w;
A[x2 + 1][y2 + 1] += w;
}

for(int i = 1; i <= n; ++i)


for(int j = 1; j <= m; ++j)
{
A[i][j] = A[i][j] + A[i-1][j] + A[i][j-1] - A[i-1][j-1];
x = A[i][j] + a[i][j];
if (x < K) return 0;
}

return 1;
}

int main()
{
int i, j, x, x1, y1, x2, y2;

f >> c >> n >> m;


for(i = 1; i <= n; ++i)
for(j = 1; j <= m; ++j)
f >> a[i][j];

if (c == 1)
{
for(i = 0; i <= 2000; ++i)
ap[i].y1 = ap[i].x1 = 2003;

for(i = 1; i <= n; ++i)


for(j = 1; j <= m; ++j)
{
x = a[i][j];
x += 1000;
ap[x].x1 = min(ap[x].x1, i);
ap[x].y1 = min(ap[x].y1, j);
ap[x].x2 = max(ap[x].x2, i);
ap[x].y2 = max(ap[x].y2, j);
ap[x].k++;
}

int Max_arie = 0;
for(i = 0; i <= 2000; ++i)
if (ap[i].k > Max_arie)
{
Max_arie = ap[i].k;
x1 = ap[i].x1;
x2 = ap[i].x2;
y1 = ap[i].y1;
y2 = ap[i].y2;
}
else
if (ap[i].k == Max_arie)
{
if (ap[i].x1 > x1 || ap[i].x1 == x1 && ap[i].y1 > y1)
{
x1 = ap[i].x1;
x2 = ap[i].x2;
y1 = ap[i].y1;
y2 = ap[i].y2;
}
}

g << x1 << " " << y1 << " " << x2 << " " << y2 << "\n";

}
else
{
CAPITOLUL 4. ONI 2019 4.1. AMAT 26

f >> q >> K;
for(i = 1; i <= q; ++i)
{
f >> x1 >> y1 >> x2 >> y2 >> x;
Q[i] = {x1, y1, x2, y2, x};
}

i = 1, j = q;
while (i <= j)
{
int mij = (i+j) >> 1;
int k = verif(mij);
if (k)
j = mij - 1;
else
i = mij + 1;
}

g << i << "\n";


}

return 0;
}

Listing 4.1.4: brut.cpp


#include <iostream>
#include <fstream>
#include <assert.h>

#define x1 first.first
#define y1 first.second

#define x2 second.first
#define y2 second.second.first

#define val second.second.second

using namespace std;

ifstream fin("amat.in");
ofstream fout("amat.out");

int init[1002][1002], dp[1002][1002];

pair< pair<int,int>, pair<int, pair<int,int> > > q[250001];

int qq,k,n,m;

int main()
{
int optiune;
fin >> optiune >> n >> m;

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
fin >> init[i][j];

fin >> qq >> k;

for(int i = 1; i <= qq; i++)


fin >> q[i].x1 >> q[i].y1 >> q[i].x2 >> q[i].y2 >> q[i].val;

int rasp = 0;
for(int mij = 1; mij <= qq; mij++)
{
int ok = 1;

for(int i = q[mij].x1; i <= q[mij].x2; i++)


for(int j = q[mij].y1; j <= q[mij].y2; j++)
init[i][j] += q[mij].val;

for(int i = 1; i <= n && ok; i++)


for(int j = 1; j <= m && ok; j++)
{
CAPITOLUL 4. ONI 2019 4.1. AMAT 27

if(init[i][j] < k)
ok = 0;
}

if(ok)
{
fout << mij << ’\n’;
return 0;
}
}

return 0;
}

Listing 4.1.5: brut IQ 9000


#include <iostream>
#include <fstream>
#include <assert.h>

#define x1 first.first
#define y1 first.second

#define x2 second.first
#define y2 second.second.first

#define val second.second.second

using namespace std;

ifstream fin("amat.in");
ofstream fout("amat.out");

int init[1002][1002], dp[1002][1002];

pair< pair<int,int>, pair<int, pair<int,int> > > q[250001];

int qq,k,n,m;
int bad;

int main()
{
int optiune;
fin >> optiune >> n >> m;

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
fin >> init[i][j];

fin >> qq >> k;

for(int i = 1; i <= qq; i++)


fin >> q[i].x1 >> q[i].y1 >> q[i].x2 >> q[i].y2 >> q[i].val;

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
if(init[i][j] < k)
bad++;

int rasp = 0;
for(int mij = 1; mij <= qq; mij++)
{
for(int i = q[mij].x1; i <= q[mij].x2; i++)
for(int j = q[mij].y1; j <= q[mij].y2; j++)
{
if(init[i][j] < k && init[i][j] + q[mij].val >= k )
bad--;
init[i][j] += q[mij].val;
}

if(bad == 0)
{
fout << mij << ’\n’;
return 0;
}
CAPITOLUL 4. ONI 2019 4.1. AMAT 28

return 0;
}

Listing 4.1.6: chiorean amat.cpp


// student Tudor Chiorean - Universitatea Tehnica din Cluj - 100 puncte

#include <bits/stdc++.h>

using namespace std;

#define NMAX 1005


#define VALMAX 1000000000
#define VALMIN -1000

ifstream fin("amat.in");
ofstream fout("amat.out");

struct query
{
int x1, y1, x2, y2, v;
};

struct subtaskAnswer
{
int x1, y1, x2, y2;
};

int n, m, i, j, q, k, c;
int mat[NMAX + 5][NMAX + 5], aux[NMAX + 5][NMAX + 5];
query queries[100000 + 5];

void DEBUG_endmatrix()
{
for (i = 1 ; i <= n ; i++)
{
for (j = 1 ; j <= m ; j++)
{
cout << aux[i][j] << ’ ’;
}
cout << ’\n’;
}
}

void subtask()
{
int crt, cnt, maxCnt = 0, i, j, lin, col;
subtaskAnswer subAns;

for (i = 1 ; i <= n ; i++)


{
for (j = 1 ; j <= m ; j++)
{
if (aux[i][j] == 1) continue;

crt = mat[i][j];
cnt = 0;
for (lin = i ; lin <= n ; lin++)
{
if (mat[lin][j] != crt) break;
for (col = j ; col <= m ; col++)
{
if (mat[lin][col] != crt) break;
aux[lin][col] = 1;

cnt++;
}
}

if (cnt >= maxCnt)


{
subAns.x1 = i;
subAns.y1 = j;
CAPITOLUL 4. ONI 2019 4.1. AMAT 29

subAns.x2 = lin - 1;
subAns.y2 = col - 1;

maxCnt = cnt;
}
}
}

fout << subAns.x1 << ’ ’ << subAns.y1 << ’ ’ <<


subAns.x2 << ’ ’ << subAns.y2 << ’\n’;
}

int solve(int nrQ)


{
int val;
memset(aux, 0, sizeof(aux));

for (i = 1 ; i <= nrQ ; i++)


{
val = queries[i].v;
aux[queries[i].x1][queries[i].y1] += val;
aux[queries[i].x1][queries[i].y2 + 1] += -val;
aux[queries[i].x2 + 1][queries[i].y1] += -val;
aux[queries[i].x2 + 1][queries[i].y2 + 1] += val;
}

for (i = 1 ; i <= n ; i++)


for (j = 1 ; j <= m ; j++)
aux[i][j] += aux[i][j - 1];

for (j = 1 ; j <= m ; j++)


for (i = 1 ; i <= n ; i++)
aux[i][j] += aux[i - 1][j];

/**
cout << nrQ << ’\n’;
DEBUG_endmatrix();
cout << "-----------------\n";
*/

for (i = 1 ; i <= n ; i++)


for (j = 1 ; j <= m ; j++)
if (aux[i][j] + mat[i][j] < k) return 0;

return 1;
}

void maintask()
{
int ls = 1, ld = q, mij, best = q;

while (ls <= ld)


{
mij = (ls + ld) / 2;

if (solve(mij))
{
best = mij;
ld = mij - 1;
}
else
{
ls = mij + 1;
}
}

fout << best;


}

int main()
{
fin >> c;
fin >> n >> m;

assert(c == 1 || c == 2);
assert(2 <= n && n <= NMAX);
CAPITOLUL 4. ONI 2019 4.1. AMAT 30

assert(2 <= m && m <= NMAX);

for (i = 1 ; i <= n ; i++)


for (j = 1 ; j <= m ; j++)
{
fin >> mat[i][j];

assert(VALMIN <= mat[i][j] && mat[i][j] <= 1000);


}

if (c == 1)
{
subtask();
}
else
{
fin >> q >> k;
assert(VALMIN <= k && k <= VALMAX);
assert(1 < q && q <= 100000);

for (i = 1 ; i <= q ; i++)


{
fin >> queries[i].x1 >> queries[i].y1 >>
queries[i].x2 >> queries[i].y2 >> queries[i].v;

assert(1 <= queries[i].x1 && queries[i].x1 <= n);


assert(1 <= queries[i].y1 && queries[i].y1 <= m);
assert(1 <= queries[i].x2 && queries[i].x2 <= n);
assert(1 <= queries[i].y2 && queries[i].y2 <= m);
assert(queries[i].x1 <= queries[i].x2 &&
queries[i].y1 <= queries[i].y2);
}

maintask();
}

return 0;
}

Listing 4.1.7: solution.cpp


#include <bits/stdc++.h>

using namespace std;

int main()
{
ifstream cin("amat.in");
ofstream cout("amat.out");

int task; cin >> task;


int n, m; cin >> n >> m;

vector<vector<int>> a(n, vector<int>(m));

for (int i = 0; i < n; ++i)


{
for (int j = 0; j < m; ++j)
{
cin >> a[i][j];
}
}

if (task == 1)
{
const int kMax = 5000;
vector<int> lt(kMax, -1), rt(kMax, -1), up(kMax, -1), dw(kMax, -1);

for (int i = 0; i < n; ++i)


{
for (int j = 0; j < m; ++j)
{
int val = a[i][j] + 2000;
if (lt[val] == -1 || lt[val] > j) lt[val] = j;
if (rt[val] == -1 || rt[val] < j) rt[val] = j;
CAPITOLUL 4. ONI 2019 4.1. AMAT 31

if (up[val] == -1 || up[val] > i) up[val] = i;


if (dw[val] == -1 || dw[val] < i) dw[val] = i;
}
}

tuple<int, int, int, int> best = make_tuple(-1, -1, -1, -1);

for (int i = 0; i < kMax; ++i)


{
tuple<int, int, int, int> now = make_tuple(
(rt[i] - lt[i] + 1) * (dw[i] - up[i] + 1), up[i], lt[i], i);

best = max(best, now);


}

int col = get<3>(best);


cout << up[col] + 1 << " " << lt[col] + 1 << " " <<
dw[col] + 1 << " " << rt[col] + 1 << endl;

}
else
{
int q, k; cin >> q >> k;

vector<tuple<int, int, int, int, int>> ops;

for (int i = 0; i < q; ++i)


{
int u, l, d, r, x; cin >> u >> l >> d >> r >> x;
ops.emplace_back(u, l, d, r, x);
}

int sol = 0;
for (int step = 1, adv = 1; step; adv ? step *= 2 : step /= 2)
{
if (sol + step > q)
{
adv = 0;
}
else
{
vector<vector<int>> mars(n + 1, vector<int>(m + 1, 0));

for (int i = sol; i < sol + step; ++i)


{
int u, l, d, r, x; tie(u, l, d, r, x) = ops[i];
mars[u - 1][l - 1] += x;
mars[u - 1][r] -= x;
mars[d][l - 1] -= x;
mars[d][r] += x;
}

bool bad = false;


for (int i = 0; i < n; ++i)
{
for (int j = 0; j < m; ++j)
{
if (i > 0) mars[i][j] += mars[i - 1][j];
if (j > 0) mars[i][j] += mars[i][j - 1];
if (i > 0 && j > 0) mars[i][j] -= mars[i - 1][j - 1];

if (mars[i][j] + a[i][j] < k)


{
bad = true;
}
}
}
/*
cerr << sol + step << endl;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j)
cerr << a[i][j] + mars[i][j] << " ";
cerr << endl;
}
*/
if (!bad)
CAPITOLUL 4. ONI 2019 4.1. AMAT 32

{
adv = 0;
continue;
}

for (int i = 0; i < n; ++i)


{
for (int j = 0; j < m; ++j)
{
a[i][j] += mars[i][j];
}
}

sol += step;
}
}

assert(sol < q);


cout << sol + 1 << endl;
}

return 0;
}

Listing 4.1.8: sursa test.cpp


#include <bits/stdc++.h>

using namespace std;

#define NMAX 1005


#define VALMAX 1000000000
#define VALMIN -1000

ifstream fin("amat.in");
ofstream fout("amat.out");

struct query
{
int x1, y1, x2, y2, v;
};

struct subtaskAnswer
{
int x1, y1, x2, y2;
};

int n, m, i, j, q, k, c;
int mat[NMAX + 5][NMAX + 5], aux[NMAX + 5][NMAX + 5];
query queries[100000 + 5];

void DEBUG_endmatrix()
{
for (i = 1 ; i <= n ; i++)
{
for (j = 1 ; j <= m ; j++)
{
cout << aux[i][j] << ’ ’;
}
cout << ’\n’;
}
}

void subtask()
{
int crt, cnt, maxCnt, i, j, lin, col;
subtaskAnswer subAns;

for (i = 1 ; i <= n ; i++)


{
for (j = 1 ; j <= m ; j++)
{
if (aux[i][j] == 1) continue;

crt = mat[i][j];
CAPITOLUL 4. ONI 2019 4.1. AMAT 33

cnt = 0;
for (lin = i ; lin <= n ; lin++)
{
if (mat[lin][j] != crt) break;
for (col = j ; col <= m ; col++)
{
if (mat[lin][col] != crt) break;
aux[lin][col] = 1;

cnt++;
}
}

if (cnt >= maxCnt)


{
subAns.x1 = i;
subAns.y1 = j;
subAns.x2 = lin - 1;
subAns.y2 = col - 1;

maxCnt = cnt;
}
}
}

fout << subAns.x1 << ’ ’ << subAns.y1 << ’ ’ <<


subAns.x2 << ’ ’ << subAns.y2 << ’\n’;
}

int solve(int nrQ)


{
int val;
memset(aux, 0, sizeof(aux));

for (i = 1 ; i <= nrQ ; i++)


{
val = queries[i].v;
aux[queries[i].x1][queries[i].y1] += val;
aux[queries[i].x1][queries[i].y2 + 1] += -val;
aux[queries[i].x2 + 1][queries[i].y1] += -val;
aux[queries[i].x2 + 1][queries[i].y2 + 1] += val;
}

for (i = 1 ; i <= n ; i++)


for (j = 1 ; j <= m ; j++)
aux[i][j] += aux[i][j - 1];

for (j = 1 ; j <= m ; j++)


for (i = 1 ; i <= n ; i++)
aux[i][j] += aux[i - 1][j];

/**
cout << nrQ << ’\n’;
DEBUG_endmatrix();
cout << "-----------------\n";
*/

for (i = 1 ; i <= n ; i++)


for (j = 1 ; j <= m ; j++)
if (aux[i][j] + mat[i][j] < k) return 0;

return 1;
}

void maintask()
{
int ls = 1, ld = q, mij, best = q;
while (ls <= ld)
{
mij = (ls + ld) / 2;

if (solve(mij))
{
best = mij;
ld = mij - 1;
}
CAPITOLUL 4. ONI 2019 4.2. COMUN 34

else
{
ls = mij + 1;
}
}

fout << best;


}

int main()
{
fin >> c;
fin >> n >> m;

assert(c == 1 || c == 2);
assert(2 <= n && n <= NMAX);
assert(2 <= m && m <= NMAX);

for (i = 1 ; i <= n ; i++)


for (j = 1 ; j <= m ; j++)
{
fin >> mat[i][j];

assert(VALMIN <= mat[i][j] && mat[i][j] <= 1000);


}

if (c == 1)
{
subtask();
}
else
{
fin >> q >> k;
assert(VALMIN <= k && k <= VALMAX);
assert(1 < q && q <= 100000);

for (i = 1 ; i <= q ; i++)


{
fin >> queries[i].x1 >> queries[i].y1 >>
queries[i].x2 >> queries[i].y2 >> queries[i].v;

assert(1 <= queries[i].x1 && queries[i].x1 <= n);


assert(1 <= queries[i].y1 && queries[i].y1 <= m);
assert(1 <= queries[i].x2 && queries[i].x2 <= n);
assert(1 <= queries[i].y2 && queries[i].y2 <= m);
assert(queries[i].x1 <= queries[i].x2 &&
queries[i].y1 <= queries[i].y2);
}

maintask();
}

return 0;
}

4.1.3 *Rezolvare detaliată

4.2 Comun
Problema 2 - Comun 100 de puncte
Tocmai ai primit un şir v de K numere naturale nenule distincte. Plecând de la acest şir, te-ai
gândit să construieşti un şir w de N numere naturale distincte, astfel ı̂ncât un număr x este ı̂n
şirul w dacă şi numai dacă exista iniţial ı̂n şirul v sau se pot alege cel puţin două numere din şirul
v astfel ı̂ncât x este cel mai mare divizor comun al acelor numere.
De exemplu, dacă v r4, 6, 7x atunci w r1, 2, 4, 6, 7x.
Uimit de proprietăţile matematice frumoase ale noului şir w, ai uitat din păcate şirul original
v de la care ai pornit.
CAPITOLUL 4. ONI 2019 4.2. COMUN 35

Cerinţe

Dându-se şirul w, să se găsească un şir posibil iniţial v având un număr minim de elemente.

Date de intrare

Fişierul de intrare comun.in conţine pe prima linie un număr natural N . Pe cea de-a doua
linie se află N numere naturale nenule distincte, ı̂n ordine strict crescătoare, reprezentând
şirul w.

Date de ieşire

Fişierul de ieşire comun.out va conţine pe prima linie numărul minim K de elemente ale
şirului v. Pe cea de-a doua linie se vor afla K numere naturale distincte, ı̂n ordine strict
crescătoare, reprezentând şirul propriu-zis.

Restricţii şi precizări

a Toate valorile din fişierul de intrare sunt numere naturale nenule mai mici sau egale cu
100000.
a Pentru teste ı̂n valoare de 15 puncte, toate valorile din fişierul de intrare sunt mai mici sau
egale cu 20.
a Pentru teste ı̂n valoare de 50 de puncte, toate valorile din fişierul de intrare sunt mai mici
sau egale cu 2000.
a Se garantează că există măcar o soluţie.
a Dacă există mai multe şiruri iniţiale cu număr minim de elemente, oricare este acceptat.

Exemple

comun.in comun.out Explicaţii


5 3 1 = cmmdc(6, 7) = cmmdc(4, 6, 7).
12467 467 2 = cmmdc(4, 6).
Se poate demonstra că orice alt şir cu proprietatea cerută are
măcar 3 elemente.
4 4 Nu există niciun şir de mai puţin de 4 elemente cu proprietatea
2 4 8 16 2 4 8 16 cerută.

Timp maxim de executare/test: Windows: 0.5 secunde, Linux: 0.5 secunde


Memorie: total 64 MB
Dimensiune maximă a sursei: 10 KB

4.2.1 Indicaţii de rezolvare


Stud. Lucian Bicsi - Universitatea din Bucureşti

Soluţie 15p
Există o multitudine de soluţii care se ı̂ncadrează ı̂n aceste limite. ı̂n continuare va fi prezentată
una dintre posibilele soluţii.
O primă observaţie este că cel mai mare număr se regăseşte mereu ı̂n şir, deoarece nu se poate
obţine aplicând c.m.m.d.c. asupra altor numere. Mai mult, dacă un număr din w este cel mai
mare divizor comun al altor numere din w, nu are sens să ı̂l includem ı̂n soluţie, deoarece orice
număr pe care l-ar putea genera x ı̂l putem genera, ı̂n schimb, cu numerele care ı̂l generează.
Din aceste considerente, se poate deduce că soluţia de lungime minimă este unică. Un algoritm
pentru a o calcula este următorul: dacă există un număr x care este c.m.m.d.c. al altor numere
ı̂ncă neeliminate, ı̂l eliminăm. Algoritmul se va opri atunci când nu se mai pot elimina numere.
N
O implementare naivă a acestui algoritm are complexitate O 2 N  şi ar trebui să obţină
minimum 15 puncte. ı̂n continuare vom optimiza algoritmul pentru a rula mai rapid.
Soluţie 50p
Observaţia cheie este că, ı̂n cadrul raţionamentului de mai sus, este suficient să considerăm
doar perechi de câte două numere.
CAPITOLUL 4. ONI 2019 4.2. COMUN 36

Un algoritm este următorul: pentru fiecare pereche x, y  de numere distincte din şirul de
intrare, calculăm d cmmdc x, y  şi ı̂l ştergem din şir. ştergerea se poate face folosind un vector
caracteristic.
2
O astfel de soluţie are complexitate O N log V  (unde V este valoarea maximă din şirul de
intrare) şi ar trebui să obţină minimum 50 de puncte.
Soluţie 100p
O altă modalitate de a optimiza algoritmul menţionat anterior este că, pentru a ne decide
dacă un număr trebuie eliminat sau nu, este de ajuns să calculăm cel mai mare divizor comun al
multiplilor săi din şir.
În acest caz, putem menţine şirul ı̂ntr-un vector caracteristic şi să verificăm fiecare număr
folosind un algoritm foarte asemănător ciurului lui Eratostene.
2
Această soluţie, deşi la prima vedere are complexitate O N  V log V , se poate demonstra
că complexitatea este de fapt O N  V log V . Demonstraţia este lăsată ca exerciţiu. O astfel
de soluţie ar trebui să obţină punctajul maxim.
Soluţie alternativă
Se poate demonstra că, ı̂n loc să considerăm toţi multiplii unui număr x, putem considera doar
perechile de multipli ı̂n care unul dintre cele două este cel mai mic multiplu al lui x din şir.
În acest caz, complexitatea soluţiei este tot O N  V log V , din aceleaşi considerente ca şi
soluţia precedentă.

4.2.2 Cod sursă

Listing 4.2.1: comun back.cpp


#include <bits/stdc++.h>

using namespace std;

int gcd(int a, int b)


{
while (b)
{
int r = a % b;
a = b;
b = r;
}
return a;
}

int main()
{
ifstream cin("comun.in");
ofstream cout("comun.out");

int n;
cin >> n;

vector<int> v(n);

int maxx = 0;
for (int i = 0; i < n; ++i)
{
cin >> v[i];
maxx = max(maxx, v[i]);
}

long long ap = 0;
for (auto x : v)
ap |= (1LL << (x - 1));

vector<int> calc(ap + 1, 0);

for (int i = 1; i <= ap; ++i)


{
for (int j = 0; j < maxx; ++j)
{
if (i & (1LL << j))
CAPITOLUL 4. ONI 2019 4.2. COMUN 37

calc[i] = gcd(calc[i], j + 1);


}
--calc[i];
}

for (int found = 1; found >= 0; --found)


{
for (int msk = 1; msk <= ap; ++msk)
{
if ((msk & ap) != msk) continue;
int gc = calc[msk];
if ((ap & (1LL << gc)) && !(msk & (1LL << gc)))
{
ap ˆ= (1LL << gc);
found = 1;
break;
}
}
}

vector<int> ans;

for (int i = 0; i < maxx; ++i)


{
if (ap & (1 << i))
ans.push_back(i + 1);
}

cout << ans.size() << endl;

for (auto x : ans)


cout << x << " ";

cout << endl;

return 0;
}

Listing 4.2.2: comun chiorean.cpp


#include <bits/stdc++.h>

using namespace std;

#define NMAX 250005

ifstream fin("comun.in");
ofstream fout("comun.out");

int n, i, j, x, valmax, g_c_d;


int v[NMAX];

int main()
{
fin >> n;
for (i = 1 ; i <= n ; i++)
{
fin >> x;
v[x] = 1;
valmax = max(valmax, x);
}

for (i = valmax - 1 ; i > 0 ; i--)


{
if (!v[i] || i + i > valmax) continue;

g_c_d = 0;

for (j = i + i ; j <= valmax ; j += i)


if (v[j] == 1)
{
if (g_c_d == 0)
g_c_d = j;
else
g_c_d = __gcd(g_c_d, j);
CAPITOLUL 4. ONI 2019 4.2. COMUN 38

if (g_c_d == i)
v[i] = 0, n--;
}

fout << n << ’\n’;


for (i = 1 ; i <= valmax ; i++)
{
if (v[i] == 1) fout << i << ’ ’;
}

return 0;
}

Listing 4.2.3: comun eugen.cpp


#include <bits/stdc++.h>

using namespace std;

const int N = 250000;

int n;
int a[N + 5], ap[N + 5];

vector <int> v;

inline int cmmdc(int x, int y)


{
if(x == -1) return y;
while(y > 0){
int r = x % y;
x = y; y = r;
}
return x;
}

int main()
{
freopen("comun.in", "r", stdin);
freopen("comun.out", "w", stdout);

scanf("%d", &n);
for(int i = 1; i <= n ; ++i)
scanf("%d", &a[i]), ++ap[a[i]]; ///citesc sirul si vad ce numere
///apar in el

for(int i = 1; i <= n ; ++i){


///un numar a[i] apare in sirul final doar daca cmmdc-ul tuturor
///multiplilor lui
///care apar in sirul initial e diferit de a[i]
int nr = 0, cm = -1;
for(int j = 2; j * a[i] <= N ; ++j)
if(ap[j * a[i]]) cm = cmmdc(cm, j * a[i]);

if(cm != a[i]) v.push_back(a[i]);


}

printf("%d\n", v.size());
for(auto it : v)
printf("%d ", it);

return 0;
}

Listing 4.2.4: comun io.cpp


#include <bits/stdc++.h>

using namespace std;

int gcd(int a, int b)


{
CAPITOLUL 4. ONI 2019 4.2. COMUN 39

while (b)
{
int r = a % b;
a = b;
b = r;
}
return a;
}

int main()
{
ifstream cin("comun.in");
ofstream cout("comun.out");

int n; cin >> n;

vector<int> v(n);

int maxx = 0;
for (auto& x : v)
{
cin >> x;
maxx = max(maxx, x);
}

vector<bool> w(maxx + 1);


for (auto x : v)
w[x] = true;

for (int x = maxx; x >= 1; --x)


{
if (w[x] == false) continue;

for (int pos = 0; pos < (int)v.size(); ++pos)


{
int y = v[pos];
int gc = gcd(x, y);
if (gc < x && gc < y)
w[gc] = false;
}
}

v.clear();

for (int i = 1; i <= maxx; ++i)


if (w[i])
v.push_back(i);

cout << v.size() << endl;

for (auto x : v)
cout << x << " ";
cout << endl;

return 0;
}

Listing 4.2.5: comun nlog sub.cpp


#include <bits/stdc++.h>

using namespace std;

int gcd(int a, int b)


{
while (a && b)
{
if (a > b) a -= b;
else b -= a;
}
return a + b;
}

vector<bool> Sparsify(vector<bool> vals)


{
CAPITOLUL 4. ONI 2019 4.2. COMUN 40

for (int i = 1; i < (int)vals.size(); ++i)


{
if (!vals[i]) continue;
int all_gcd = 0;
for (int j = i + i; j < (int)vals.size(); j += i)
{
if (vals[j])
all_gcd = all_gcd ? gcd(all_gcd, j % all_gcd) : j;
}
if (all_gcd == i)
vals[i] = false;
}
return vals;
}

int main()
{
ifstream cin("comun.in");
ofstream cout("comun.out");

int n;
cin >> n;

vector<bool> v(100001, false);

for (int i = 0; i < n; ++i)


{
int x; cin >> x; v[x] = true;
}

v = Sparsify(v);
vector<int> out;

for (int i = 0; i < (int)v.size(); ++i)


if (v[i])
out.push_back(i);

cout << out.size() << endl;


for (auto x : out)
cout << x << " ";
cout << endl;

return 0;
}

Listing 4.2.6: comun sol.cpp


#include <bits/stdc++.h>

using namespace std;

int gcd(int a, int b)


{
while (b)
{
int r = a % b;
a = b;
b = r;
}
return a;
}

vector<bool> Sparsify(vector<bool> vals)


{
for (int i = 1; i < (int)vals.size(); ++i)
{
if (!vals[i]) continue;
int all_gcd = 0;

for (int j = i + i; j < (int)vals.size(); j += i)


{
if (vals[j])
all_gcd = gcd(all_gcd, j);
}
CAPITOLUL 4. ONI 2019 4.2. COMUN 41

if (all_gcd == i)
vals[i] = false;
}
return vals;
}

int main()
{
ifstream cin("comun.in");
ofstream cout("comun.out");

int n;
cin >> n;

vector<bool> v(100001, false);

for (int i = 0; i < n; ++i)


{
int x; cin >> x; v[x] = true;
}

v = Sparsify(v);
vector<int> out;

for (int i = 0; i < (int)v.size(); ++i)


if (v[i])
out.push_back(i);

cout << out.size() << endl;


for (auto x : out)
cout << x << " ";
cout << endl;

return 0;
}

Listing 4.2.7: sol nlog.cpp


#include <bits/stdc++.h>

using namespace std;

int gcd(int a, int b)


{
while (b)
{
int r = a % b;
a = b;
b = r;
}
return a;
}

vector<bool> Sparsify(vector<bool> vals)


{
for (int i = 1; i < (int)vals.size(); ++i)
{
if (!vals[i]) continue;
int all_gcd = 0;

for (int j = i + i; j < (int)vals.size(); j += i)


{
if (vals[j])
all_gcd = gcd(all_gcd, j);
}

if (all_gcd == i)
vals[i] = false;
}
return vals;
}

int main()
{
ifstream cin("comun.in");
CAPITOLUL 4. ONI 2019 4.3. PRO3 42

ofstream cout("comun.out");

int n;
cin >> n;
vector<bool> v(100001, false);

for (int i = 0; i < n; ++i)


{
int x; cin >> x; v[x] = true;
}

v = Sparsify(v);
vector<int> out;
for (int i = 0; i < (int)v.size(); ++i)
if (v[i])
out.push_back(i);

cout << out.size() << endl;


for (auto x : out)
cout << x << " ";
cout << endl;

return 0;
}

4.2.3 *Rezolvare detaliată

4.3 pro3
Problema 3 - pro3 100 de puncte
Se consideră 3 progresii aritmetice de numere naturale nenule.
Notăm cu Pi , 1 & i & 3, mulţimile formate cu elementele progresiei i.
Fie P P1 < P2 < P3 reuniunea mulţimilor P1 , P2 , P3 .

Cerinţe

Să se determine cardinalul mulţimii P .

Date de intrare

Fişierul de intrare pro3.in conţine 3 linii.


Pe linia i, 1 & i & 3 se vor găsi câte 3 numere naturale ai , ri , ni , separate prin câte un spaţiu,
ce reprezintă ı̂n această ordine primul termen, raţia şi numărul de termeni ai progresiei P i.

Date de ieşire

Fişierul de ieşire pro3.out va conţine pe prima linie cardinalul mulţimii P .

Restricţii şi precizări

Pentru teste ı̂n valoare de 40 puncte, 0 $ ai , ri & 10 şi 0 $ ni & 10 , 1 & i & 3
2 6
a
Pentru teste ı̂n valoare de 72 puncte, 0 $ ai , ri & 10 şi 0 $ ni & 10 , 1 & i & 3
2 9
a
Pentru teste ı̂n valoare de 100 puncte, 0 $ ai , ri & 10 şi 0 $ ni & 10 , 1 & i & 3
6 9
a

Exemple

pro3.in pro3.out Explicaţii


CAPITOLUL 4. ONI 2019 4.3. PRO3 43

2 2 10 24 Prima progresie are primul termen 2, raţia 2 şi 10 termeni.


348 A doua progresie are primul termen 3, raţia 4 şi 8 termeni.
1 3 12 A treia progresie are primul termen 1, raţia 3 şi 12 termeni.
Aşadar:
P1 r2, 4, 6, 8, 10, 12, 14, 16, 18, 20x
P2 r3, 7, 11, 15, 19, 23, 27, 31x
P3 r1, 4, 7, 10, 13, 16, 19, 22, 25, 28, 31, 34x
Reuniunea termenilor celor trei progresii este mulţimea
P r1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 22, 23,
, 25, 27, 28, 31, 34x şi cardinalul mulţimii P este 24.

Timp maxim de executare/test: Windows: 0.5 secunde, Linux: 0.5 secunde


Memorie: total 64 MB
Dimensiune maximă a sursei: 10 KB

4.3.1 Indicaţii de rezolvare


prof. Ciprian Cheşcă - Liceul Tehnologic ”Grigore C. Moisil” Buzău

Soluţie 20p
Se poate utiliza un vector de frecvenţă pentru a determina dacă un număr natural cuprins
ı̂ntre 1 şi cel mai mare element dintre cele 3 progresii face parte din măcar o progresie.
Soluţia necesită un spaţiu de memorie foarte mare şi nu poate fi utilizată pentru valori mari ale
numărului de termeni ale unei progresii, dar va obţine aproximativ 20  30 de puncte, ı̂n funcţie
de diverse optimizări de memorie.
Soluţie 40p
Se poate realiza o interclasare concomitentă a celor 3 şiruri, fără a mai construi un vector cu
rezultatele reuniuni. La fiecare pas al interclasării se va determina elementul minim al celor trei
progresii şi se va incrementa indicele corespunzător.
După ce unul dintre cele 2 şiruri se va termina” se va testa care dintre şiruri s-a terminat şi se
va acţiona asemănător pe două şiruri apoi pe un singur şir.
Această soluţie are complexitate O n1  n2  n3 şi ar trebui să obţină minimum 40 de puncte.
Soluţie 72p
Observaţia cheie este că elementele comune a două sau mai multe progresii aritmetice, dacă
există, formează tot o progresie aritmetică.
Astfel, observăm că este mai uşor şi mai eficient să calculăm elementele comune a două/trei
progresii şi să folosim principiul includerii şi excluderii pentru a calcula răspunsul.
Deoarece ı̂n acest caz termenii iniţiali şi raţiile progresiilor sunt cel mult egale cu 100, se pot
găsi primii doi termeni ai progresiei comune (şi, implicit, raţia acesteia), verificând pe rând valori
candidat, ı̂n ordine crescătoare. Dacă există termeni comuni, se poate demonstra că primul dintre
ei şi raţia nu pot depăşi 2 ˜ 1003.
3 3
Această soluţie are complexitate O r1  r2  r3 a1  a2  a3  şi ar trebui să obţină
minimum 72 de puncte.
Soluţie 100p
ı̂n soluţia anterioară ne-am bazat pe faptul că primul termen şi raţia progresiei comune a două
progresii a1  Xr1 şi a2  Xr2 sunt mai mici sau egale cu a1  r1  a2  r2 . ı̂n loc să verificăm
fiecare candidat pe rând, vom itera doar prin elementele progresiei 1. Astfel, numărul de paşi este
O a1  r1  a2  r2 © a1  r1  O a2  r2 . Este esenţial că această complexitate nu depinde
de parametrii progresiei 1, astfel că intersecţia celor trei progresii se poate calcula intersectând
progresiv progresia 1 cu progresia 2 şi rezultatul cu progresia 3.
Complexitatea acestei soluţii este O r1  r2  r3  a1  a2  a3  şi ar trebui să obţină 100 de
puncte.

4.3.2 Cod sursă


CAPITOLUL 4. ONI 2019 4.3. PRO3 44

Listing 4.3.1: pro3 AT1.cpp


// student Alex Turdean - Universitatea Tehnica Cluj
#include <iostream>
#include <fstream>
#include <assert.h>

using namespace std;

ifstream fin("pro3.in");
ofstream fout("pro3.out");

long long rez, a[4], n[4],r[4];

long long gcd(int x, int y)


{
int r = x%y;
while(r)
{
x = y;
y = r;
r = x%y;
}
return y;
}

long long inter2(int x, int y)


{
long long maxi = min(a[x] + (n[x] - 1) * r[x], a[y] + (n[y] - 1) * r[y]);
for(int i = 0; i < 1000000; i++)
{
long long termen = a[x] + i * r[x];
if(termen > maxi)
return 0;
if( (termen - a[y]) % r[y] == 0 && termen >= a[y])
{
return (maxi - termen) / (r[x] * r[y] / gcd(r[y], r[x])) + 1;
}
}
return 0;
}

long long inter3()


{
long long maxi = min(a[1] + (n[1] - 1) * r[1], a[2] + (n[2] - 1) * r[2]);
long long termen;
long long ratie = r[1] * r[2] / gcd(r[1], r[2]);
int i;

for(i = 0; i < 1000000; i++)


{
termen = a[1] + i * r[1];
if(termen > maxi)
return 0;
if( (termen - a[2]) % r[2] == 0 )
{
break;
}
}

maxi = min(maxi, a[3] + (n[3] - 1) * r[3]);

if(termen > maxi || i == 1000000)


return 0;

for(i = 0; i < 1000000; i++)


{
if(i)
termen += ratie;
if(termen > maxi)
return 0;
if( (termen - a[3]) % r[3] == 0 )
{
return (maxi - termen) / (ratie * r[3] / gcd(r[3], ratie)) + 1;
}
}
CAPITOLUL 4. ONI 2019 4.3. PRO3 45

return 0;
}

int main()
{
for(int i = 1; i <= 3; i++)
fin >> a[i] >> r[i] >> n[i];
fout << n[1] + n[2] + n[3] - inter2(1, 2) - inter2(1, 3) -
inter2(2, 3) + inter3() << ’\n’;

return 0;
}

Listing 4.3.2: pro3 CC1.cpp


// prof. Chesca Ciprian
// teoria numerelor cu simplificarea ratiei

#include <fstream>

using namespace std;

long long cmmdc(long long a, long long b)


{
long long d=a,i=b,r=d%i;
while (r)
{
d=i;
i=r;
r=d%i;
}
return i;
}

int main()
{
ifstream f("pro3.in");
ofstream g("pro3.out");

long long a1,r1,a2,r2,n1,n2,a3,r3,n3,a12,a13,a23,


r12,r13,r23,n12,n13,n23,r123,a123,n123;
long long an1,an2,an3,an12;

long long i,j,t,x0,y0,d;

f >> a1 >> r1 >> n1;


f >> a2 >> r2 >> n2;
f >> a3 >> r3 >> n3;

an1 = a1 + r1*(n1 - 1);


an2 = a2 + r2*(n2 - 1);
an3 = a3 + r3*(n3 - 1);

//-------------------------------------------------------------------
// determin cate elemente sunt comune sirurilor 1 si 2
// ecuatia r1*x - r2*y = (a2 - r2) - (a1 - r1)
// ecuatia are solutii daca cmmdc(r1,r2) divide (a2 - r2) - (a1 - r1)

d=cmmdc(r1,r2);
t = (a2 - r2) - (a1 - r1);
if (t%d==0) // ecuatia are solutii
{
//determin primul element comun celor doua siruri
i=1;j=1;x0=0;y0=0;
while (i<=n1 && j<=n2)
{
if (a1+r1*(i-1) < a2+r2*(j-1)) i++;
if (a1+r1*(i-1) > a2+r2*(j-1)) j++;
if (a1+r1*(i-1) == a2+r2*(j-1)) {x0 = i; y0 = j; break;}
}

if (x0 == 0) {a12 = 0; r12 = 0; n12 = 0;}


else
{
CAPITOLUL 4. ONI 2019 4.3. PRO3 46

a12 = a1 + r1*(x0 - 1);


r12 = (r1*r2)/d;

if (an1 < an2)


n12 = (an1 - a12)/r12 + 1;
else
n12 = (an2 - a12)/r12 + 1;

an12 = a12 + r12*(n12 - 1);


//g<< x0<<" "<< y0 <<"\n";
}
}
else // ecuatia nu are solutii
{a12 = 0;r12 = 0;n12 = 0;}

//-------------------------------------------------------------------
// determin cate elemente sunt comune sirurilor 1 si 3
// ecuatia r1*x - r3*y = (a3 - r3) - (a1 - r1)
// ecuatia are solutii daca cmmdc(r1,r3) divide (a3 - r3) - (a1 - r1)

d=cmmdc(r1,r3);
t = (a3 - r3) - (a1 - r1);
if (t%d==0) // ecuatia are solutii
{
//determin primul element comun celor doua siruri
i=1;j=1;x0=0;y0=0;
while (i<=n1 && j<=n3)
{
if (a1+r1*(i-1) < a3+r3*(j-1)) i++;
if (a1+r1*(i-1) > a3+r3*(j-1)) j++;
if (a1+r1*(i-1) == a3+r3*(j-1)) {x0 = i; y0 = j; break;}
}
if (x0 == 0) {a13 = 0; r13 = 0; n13 = 0;}
else
{
a13 = a1 + r1*(x0 - 1);
r13 = (r1*r3)/d;
if (an1 < an3)
n13 = (an1 - a13)/r13 + 1;
else
n13 = (an3 - a13)/r13 + 1;

//g<< x0<<" "<< y0 <<"\n";


}
}
else // ecuatia nu are solutii
{a13 = 0;r13 = 0;n13 = 0;}

//-------------------------------------------------------------------
// determin cate elemente sunt comune sirurilor 2 si 3
// ecuatia r2*x - r3*y = (a3 - r3) - (a2 - r2)
// ecuatia are solutii daca cmmdc(r2,r3) divide (a3 - r3) - (a2 - r2)

d=cmmdc(r2,r3);
t = (a3 - r3) - (a2 - r2);
if (t%d==0) // ecuatia are solutii
{
//determin primul element comun celor doua siruri
i=1;j=1;x0=0;y0=0;
while (i<=n2 && j<=n3)
{
if (a2+r2*(i-1) < a3+r3*(j-1)) i++;
if (a2+r2*(i-1) > a3+r3*(j-1)) j++;
if (a2+r2*(i-1) == a3+r3*(j-1)) {x0 = i; y0 = j;break;}
}
if (x0 == 0) {a23 = 0; r23 = 0; n23 = 0;}
else
{
a23 = a2 + r2*(x0 - 1);
r23 = (r2*r3)/d;
if (an2 < an3)
n23 = (an2 - a23)/r23 + 1;
else
n23 = (an3 - a23)/r23 + 1;

//g<< x0<<" "<< y0 <<"\n";


CAPITOLUL 4. ONI 2019 4.3. PRO3 47

}
}
else // ecuatia nu are solutii
{a23 = 0;r23 = 0;n23 = 0;}

//---------------------------------------------------
// determin cate elemente comune au cele 3 siruri
if (n12 && n23 && n13)
{
// prima progresie este a12,r12,n12 si a doua este a3,r3,n3
d=cmmdc(r12,r3);
t = (a3 - r3) - (a12 - r12);

if (t%d==0) // ecuatia are solutii


{
//determin primul element comun celor doua progresii
i=1;j=1;x0=0;y0=0;
while (i<=n12 && j<=n3)
{
if (a12+r12*(i-1) < a3+r3*(j-1)) i++;
if (a12+r12*(i-1) > a3+r3*(j-1)) j++;
if (a12+r12*(i-1) == a3+r3*(j-1)) {x0 = i; y0 = j; break;}
}

if (x0 == 0) {a123 = 0; r123 = 0; n123 = 0;}


else
{
// g << a12 << " " << r12 << " " << x0 <<" " << a123 << "\n";
a123 = a12 + r12*(x0 - 1);
r123 = (r12*r3)/d;
if (an12 < an3)
n123 = (an12 - a123)/r123 + 1;
else
n123 = (an3 - a123)/r123 + 1;
//g<< x0<<" "<< y0 <<"\n";
}
}
else
{a123 = 0; r123 = 0; n123 = 0;}
}
else
{a123 = 0; r123 = 0; n123 = 0;}

// aplic PINEX
//g<< " ------------------------------------------"<<"\n";

g << n1 + n2 + n3 - n12 - n13 - n23 + n123;

f.close();
g.close();

return 0;
}

Listing 4.3.3: pro3 CC2.cpp


// prof. Chesca Ciprian
// Teoria numerelor

#include <fstream>
#define int long long

using namespace std;

struct pro
{
long long a,r,n,an;
};

long long cmmdc(long long a, long long b)


{
long long d=a,i=b,r=d%i;
while (r)
{
CAPITOLUL 4. ONI 2019 4.3. PRO3 48

d=i;
i=r;
r=d%i;
}
return i;
}

pro progresie(pro x, pro y)


{
// determin cate elemente sunt comune progresiilor x si y
// ecuatia r1*x - r2*y = (a2 - r2) - (a1 - r1)
// ecuatia are solutii daca cmmdc(r1,r2) divide (a2 - r2) - (a1 - r1)
// ratia progresie comune este r1*r2/cmmdc(r1,r2)

pro z;

long long d,t,i,j,x0;

d=cmmdc(x.r,y.r);
t = (y.a - y.r) - (x.a - x.r);
if (t%d==0) // ecuatia are solutii
{
//determin primul element comun celor doua siruri
i=1;j=1;x0=0;
while (i<=x.n && j<=y.n)
{
if (x.a+x.r*(i-1) < y.a+y.r*(j-1)) i++;
if (x.a+x.r*(i-1) > y.a+y.r*(j-1)) j++;
if (x.a+x.r*(i-1) == y.a+y.r*(j-1)) {x0 = i; break;}
}

if (x0 == 0) {z.a = 0; z.r = 0; z.n = 0;}


else
{
z.a = x.a + x.r*(x0 - 1);
z.r = (x.r*y.r)/d;

x.an=x.a+x.r*(x.n-1);
y.an=y.a+y.r*(y.n-1);

if (x.an < y.an)


z.n = (x.an - z.a)/z.r + 1;
else
z.n = (y.an - z.a)/z.r + 1;

z.an = z.a + z.r*(z.n - 1);


}
}
else // ecuatia nu are solutii
{z.a = 0;z.r = 0;z.n = 0;}

return z;
}

int32_t main()
{
ifstream f("pro3.in");
ofstream g("pro3.out");

pro x,y,z,xy,xz,yz,xyz;

f >> x.a >> x.r >> x.n;


f >> y.a >> y.r >> y.n;
f >> z.a >> z.r >> z.n;

xy = progresie(x,y);
xz = progresie(x,z);
yz = progresie(y,z);
xyz = progresie(xy,z);

// aplic PINEX
g << x.n + y.n + z.n - xy.n - xz.n - yz.n + xyz.n;

f.close();
g.close();
CAPITOLUL 4. ONI 2019 4.3. PRO3 49

return 0;
}

Listing 4.3.4: pro3 LB1.cpp


// student Lucian Bicsi - Universitatea din Bucuresti - 100 puncte

#include <bits/stdc++.h>

using namespace std;

struct Prog
{
long long a, r, n;
};

long long gcd(long long a, long long b)


{
while (b)
{
long long r = a % b;
a = b;
b = r;
}
return a;
}

Prog Merge(Prog p1, Prog p2)


{
if (p1.n == 0) return p1;
long long new_r = p1.r * p2.r / gcd(p1.r, p2.r);
for (int i = 0; i < p2.a + p2.r && i < p1.n; ++i)
{
long long x = p1.a + p1.r * i;
x -= p2.a;
if (x % p2.r == 0)
{
x /= p2.r;
if (x >= 0 && x < p2.n)
{
long long maxx = min(p1.a + p1.r * (p1.n - 1),
p2.a + p2.r * (p2.n - 1));
maxx -= p1.a + p1.r * i;
if (maxx < 0) break;
maxx /= new_r;
return {p1.a + p1.r * i, new_r, maxx + 1};
}
}
}
return {0, 0, 0};
}

ostream& operator<<(ostream& out, Prog prog)


{
out << "(" << prog.a << " + " << prog.r << " x " << prog.n << ")";
return out;
};

long long Calc(vector<Prog> progs)


{
// for (auto x : progs) cerr << x << " ";
Prog ret{0, 1, (long long)2e18};

for (auto prog : progs)


{
ret = Merge(ret, prog);
}
// cerr << " -> " << ret << endl;
return ret.n;
}

int main()
{
ifstream cin("pro3.in");
ofstream cout("pro3.out");
CAPITOLUL 4. ONI 2019 4.4. CUB 50

auto read = [&](){


Prog ret;
cin >> ret.a >> ret.r >> ret.n;
return ret;
};

Prog a = read(), b = read(), c = read();

cout << Calc({a}) + Calc({b}) + Calc({c}) -


Calc({a, b}) - Calc({a, c}) - Calc({b, c})
+ Calc({a, b, c}) << endl;

return 0;
}

4.3.3 *Rezolvare detaliată

4.4 Cub
Problema 4 - Cub 100 de puncte
Ionel are de rezolvat o nouă problemă. El trebuie să construiască un şir de N numere naturale.
Numerele din şir pot avea ca divizori primi doar numere prime de o cifră. După construirea şirului,
Ionel a constatat că există subsecvenţe ı̂n şir pentru care produsul elementelor este cubul unui
număr natural.
Cerinţe
Ionel vrea să determine numărul subsecvenţelor din şirul construit care au produsul elementelor
o valoare ce este cubul unui număr natural.
Date de intrare
Fişierul de intrare cub.in va conţine pe prima linie numărul natural N , iar pe linia următoare
se vor afla N numere naturale separate prin câte un spaţiu, elementele şirului construit de Ionel.

Date de ieşire
Fişierul de ieşire cub.out va conţine pe prima linie un număr natural reprezentând numărul
subsecvenţelor din şirul construit care au produsul elementelor egal cu o valoare ce este cubul unui
număr natural.
Restricţii şi precizări
a N şi elemente şirului sunt numere naturale din intervalul 2, 1000000
a Prin subsecvenţă a unui şir se ı̂nţelege o succesiune de unul sau mai mulţi termeni din şir
aflaţi pe poziţii consecutive.
a Pentru teste ı̂n valoare de 20 de puncte, N & 1000.
a Pentru teste ı̂n valoare de 40 de puncte, N & 10000.

Exemple
cub.in cub.out Explicaţii
8 6 Sunt 6 subsecvenţe ı̂n şir cu produsul elementelor egal cu o val-
15 3 5 15 7 63 21 125 oare care este cubul unui număr natural:
15 3 5 15
7 63 21
125
15 3 5 15 7 63 21
7 63 21 125
15 3 5 15 7 63 21 125
Timp maxim de executare/test: Windows: 0.6 secunde, Linux: 0.3 secunde
Memorie: total 64 MB
Dimensiune maximă a sursei: 10 KB
CAPITOLUL 4. ONI 2019 4.4. CUB 51

4.4.1 Indicaţii de rezolvare


Prof. Gh. Manolache - Colegiul Naţional de Informatică, Piatra Neamţ

Vom calcula exponenţii valorilor 2, 3, 5 şi 7 din descompunerea ı̂n factori primi a fiecărui
număr din şir, precum şi exponenţii cumulaţi crescător. ı̂n funcţie de restul ı̂mpărţirii la 3 al
sumei exponenţilor cumulaţi se atribuie un cod de la 0 la 80 pentru fiecare suma cumulată până
la poziţia curentă din şir. Două poziţii cu acelaşi cod determină o subsecvenţă cu proprietatea
cerută, deci se vor număra pentru fiecare cod câte perechi de poziţii cu acelaşi cod există. Pentru
fiecare k poziţii cu acelaşi cod, numărul subsecvenţelor existente va avea valoarea k ˜ k  1©2,
fapt uşor de demonstrat. Pentru punctaj maxim, se utilizează un algoritm de complexitate O n.

4.4.2 Cod sursă

Listing 4.4.1: cub chiorean.cpp


#include <bits/stdc++.h>

using namespace std;

ifstream fin("cub.in");
ofstream fout("cub.out");

int i, j, nr, n, x, mul, crtState[4], states[100];


long long sol;
const int divs[] = {2, 3, 5, 7};

int convert()
{
return crtState[3] * 3 * 3 * 3 + crtState[2] * 3 * 3 +
crtState[1] * 3 + crtState[0];
}

int main()
{
fin >> n;
for (i = 1 ; i <= n ; i++)
{
fin >> x;

for (j = 0 ; j < 4 ; j++)


{
nr = 0;
while (x % divs[j] == 0)
{
nr++;
x /= divs[j];
crtState[j]++;
}

crtState[j] %= 3;
}

states[convert()]++;
}
states[0]++;

for (i = 0 ; i < 81 ; i++)


sol += states[i] * (states[i] - 1) / 2;

fout << sol;

return 0;
}

Listing 4.4.2: cub chiorean brut.cpp


#include <bits/stdc++.h>

using namespace std;


CAPITOLUL 4. ONI 2019 4.4. CUB 52

ifstream fin("cub.in");
ofstream fout("cub.out");

int i, j, nr, n, x, mul, crtState[4], v[1000000];


long long sol;
const int divs[] = {2, 3, 5, 7};

int convert()
{
return crtState[3] * 3 * 3 * 3 + crtState[2] * 3 * 3 +
crtState[1] * 3 + crtState[0];
}

int main()
{
fin >> n;
for (i = 1 ; i <= n ; i++)
{
fin >> x;

for (j = 0 ; j < 4 ; j++)


{
nr = 0;
while (x % divs[j] == 0)
{
nr++;
x /= divs[j];
crtState[j]++;
}

crtState[j] %= 3;
}

v[i] = convert();
}

for (i = 0 ; i < n ; i++)


for (j = i + 1 ; j <= n ; j++)
if (v[i] == v[j])
sol++;

fout << sol;

return 0;
}

Listing 4.4.3: cub chiorean n3.cpp


/**
Chiorean Tudor-Octavian
cub O(Nˆ3)
*/
#include <bits/stdc++.h>

using namespace std;

ifstream fin("cub.in");
ofstream fout("cub.out");

int i, j, k, t, ok, nr, n, x, mul, state[4][1000000], crtState[4];


long long sol;
const int divs[] = {2, 3, 5, 7};

int main()
{
fin >> n;
for (i = 1 ; i <= n ; i++)
{
fin >> x;

for (j = 0 ; j < 4 ; j++)


{
nr = 0;
while (x % divs[j] == 0)
CAPITOLUL 4. ONI 2019 4.4. CUB 53

{
nr++;
x /= divs[j];
state[j][i]++;
}

state[j][i] %= 3;
}
}

for (i = 1 ; i < n ; i++)


for (j = i ; j <= n ; j++)
{
for (k = i ; k <= j ; k++)
{
for (t = 0 ; t < 4 ; t++)
{
crtState[t] += state[t][k];
}
ok = 1;
}

for (t = 0 ; t < 4 ; t++)


{
if (crtState[t] % 3 != 0)
{
ok = 0;
break;
}
}
sol += ok;

for (t = 0 ; t < 4 ; t++)


{
crtState[t] = 0;
}
}

fout << sol;

return 0;
}

Listing 4.4.4: cub100.cpp


#include <fstream>

using namespace std;

ifstream f("cub.in");
ofstream g("cub.out");

unsigned long long e2,e3,e5,e7,v[1000002];


unsigned long long cod,sol,viz[82],x;
unsigned long long n,i,j,k,r,i1,j1,k1,r1,d[22],t[15],c[11],s[10],p,w;
unsigned long long ex2[1000002],ex3[1000002],ex5[1000002],ex7[1000002];

int main()
{
d[0]=1;
i=0;
while(d[i]<=1000000)
{
i++;
d[i]=d[i-1]*2;
}

i--;
t[0]=1;
j=0;
while(t[j]<=1000000)
{
j++;
t[j]=t[j-1]*3;
}
CAPITOLUL 4. ONI 2019 4.4. CUB 54

j--;
c[0]=1;
k=0;
while(c[k]<=1000000)
{
k++;
c[k]=c[k-1]*5;
}
k--;
s[0]=1;r=0;
while(s[r]<=1000000)
{
r++;
s[r]=s[r-1]*7;
}

r--;
//g<<"i2="<<i<<" i3="<<j<<" i5="<<k<<" i7="<<r<<"\n"; //19 12 8 7
// calculez numerele mai mici decat 1000000 ce sunt divizibile
// doar cu 2, 3 ,5 sau 7
for(i1=0;i1<=i;i1++)
for(j1=0;j1<=j;j1++)
for(k1=0;k1<=k;k1++)
for(r1=0;r1<=r;r1++)
{
p=d[i1]*t[j1]*c[k1]*s[r1];
if(p<=1000000)
{
ex2[p]=i1;
ex3[p]=j1;
ex5[p]=k1;
ex7[p]=r1;
//w++;
}
}

//g<<"nr numere ="<<w<<"\n";// 1273


// citesc numerele si formez exponentii cumulati
// ai lui 2, 3 ,5 si 7
e2=0;e3=0;e5=0;e7=0;
f>>n;
for(i=1;i<=n;i++)
f>>v[i];
for(i=1;i<=n;i++)
{
x=v[i]; ;
e2=(e2+ex2[x])%3;
e3=(e3+ex3[x])%3;
e5=(e5+ex5[x])%3;
e7=(e7+ex7[x])%3;
cod=e2*27+e3*9+e5*3+e7;//scriu in baza 3
viz[cod]++;
}

sol=viz[0];
for(i=0;i<=80;i++)
sol=sol+viz[i]*(viz[i]-1)/2;

g<<sol<<’\n’ ;
g.close();
return 0;
}

Listing 4.4.5: cub100 int.cpp


#include <fstream>

using namespace std;

ifstream f("cub.in");
ofstream g("cub.out");

int e2,e3,e5,e7,v[1000002];
int cod,viz[82],x;
CAPITOLUL 4. ONI 2019 4.4. CUB 55

int n,i,j,k,r,i1,j1,k1,r1,d[22],t[15],c[11],s[10];
unsigned long long p,sol;
int ex2[1000002],ex3[1000002],ex5[1000002],ex7[1000002];

int main()
{
d[0]=1;
i=0;
while(d[i]<=1000000)
{
i++;
d[i]=d[i-1]*2;
}

i--;
t[0]=1;
j=0;
while(t[j]<=1000000)
{
j++;
t[j]=t[j-1]*3;
}

j--;
c[0]=1;
k=0;
while(c[k]<=1000000)
{
k++;
c[k]=c[k-1]*5;
}

k--;
s[0]=1;
r=0;
while(s[r]<=1000000)
{
r++;
s[r]=s[r-1]*7;
}
r--;
//g<<"i2="<<i<<" i3="<<j<<" i5="<<k<<" i7="<<r<<"\n"; //19 12 8 7
// calculez numerele mai mici decat 1000000 ce sunt divizibile
// doar cu 2, 3 ,5 sau 7
for(i1=0;i1<=i;i1++)
for(j1=0;j1<=j;j1++)
for(k1=0;k1<=k;k1++)
for(r1=0;r1<=r;r1++)
{
p=1ll*d[i1]*t[j1]*c[k1]*s[r1];
if(p<=1000000)
{
ex2[p]=i1;
ex3[p]=j1;
ex5[p]=k1;
ex7[p]=r1;
//w++;
}
}
//g<<"nr numere ="<<w<<"\n";// 1273
// citesc numerele si formez exponentii cumulati
// ai lui 2, 3 ,5 si 7

e2=0; e3=0; e5=0; e7=0;


f>>n;
for(i=1;i<=n;i++)
f>>v[i];

for(i=1;i<=n;i++)
{
x=v[i]; ;
e2=(e2+ex2[x])%3;
e3=(e3+ex3[x])%3;
e5=(e5+ex5[x])%3;
e7=(e7+ex7[x])%3;
cod=e2*27+e3*9+e5*3+e7;//scriu in baza 3
CAPITOLUL 4. ONI 2019 4.5. FIBOFRAC 56

viz[cod]++;
}

sol=viz[0];
for(i=0;i<=80;i++)
sol=sol+viz[i]*(viz[i]-1)/2;

g<<sol<<’\n’ ;
g.close();
return 0;
}

4.4.3 *Rezolvare detaliată

4.5 fibofrac
Problema 5 - fibofrac 100de puncte
Fie şirul Fibonacci dat prin F1 1, F2 1 şi relaţia de recurenţă Fk Fk1  Fk2 , k ' 3.
Se consideră un număr natural N .

Cerinţe

Să se scrie un program care determină numărul F al fracţiilor diferite ireductibile subunitare,
ce se pot forma utilizând primii N termeni ai şirului Fibonacci.

Date de intrare

Fişierul de intrare fibofrac.in conţine pe prima linie numărul N .

Date de ieşire

Fişierul de ieşire fibofrac.out va conţine pe prima linie numărul F ,cu semnificaţia de mai sus.

Restricţii şi precizări

a Pentru teste ı̂n valoare de 24 puncte, 0 $ N $ 80


a Pentru teste ı̂n valoare de 40 puncte, 0 $ N $ 1101
a Pentru teste ı̂n valoare de 56 puncte, 0 $ N $ 50001
a Pentru teste ı̂n valoare de 100 puncte, 0 $ N $ 1000000
a Două fracţii ireductibile a©b şi c©d sunt diferite dacă a j c sau b j d.
0&F $2
63
a

Exemple

fibofrac.in fibofrac.out Explicaţii


7 14 N=7; Primii 7 termeni ai şirului Fibonacci sunt: 1, 1, 2, 3, 5, 8,
13 Se pot forma 14 fracţii diferite ireductibile subunitare:
1 1 1 1
, , , , 1 , 2, 2, 2 , 3, 3, 3 , 5, 5 , 8 .
2 3 5 8 13 3 5 13 5 8 13 8 13 13
2019 1547722 Se pot forma 1547722 fracţii diferite ireductibile subunitare uti-
lizând primii 2019 termeni ai şirului Fibonacci.
500000 94988288219 Se pot forma 94988288219 fracţii diferite ireductibile subunitare
utilizând primii 500000 termeni ai şirului Fibonacci.

Timp maxim de executare/test: Windows: 0.1 secunde, Linux: 0.1 secunde


Memorie: total 64 MB
Dimensiune maximă a sursei: 10 KB
CAPITOLUL 4. ONI 2019 4.5. FIBOFRAC 57

4.5.1 Indicaţii de rezolvare


Prof. Cheşcă Ciprian - Liceul Tehnologic Grigore C. Moisil” Buzău

Soluţia 1
Generăm termenii şirului Fibonacci şi formăm toate perechile posibile F a©F b cu F a $ F b.
Pentru fiecare pereche calculăm F a, F b şi contorizăm pentru acele perechi care au (Fa, Fb) = 1.
Deoarece termenii şirului Fibonacci cresc repede se obţin erori pentru N % 80. Soluţia are ordin de
2
complexitate O n  şi obţine aproximativ 24 puncte. Pentru obţinerea unor puncte suplimentare
s-ar putea lucra cu numere mari.
Soluţia 2
Dacă o fracţie F a©F b este ireductibilă atunci F a, F b este 1. Se cunoaşte ı̂nsă că F a, F b =
F a, b. Cum F a, F b trebuie să fie 1, rezultă că F a, b poate fi F 1 sau F 2, pentru că F 1 1
şi F 2 1. Aşadar contorizăm toate perechile i, j cu i $ j pentru care i, j  este 1 sau 2. Soluţia
obţine aproximativ 40 puncte.
Soluţia 3
În plus faţă de consideraţiile de la varianta anterioară, se ştie că numărul de perechi ordonate
x & y  care au x, y  d şi x & y & M este φ 1  φ 2  φ 3  ...  φ M ©d, unde M este un
număr natural iar φ t reprezintă numărul de numere mai mici decât t şi prime cu t (indicatorul
lui Euler).
În cazul nostru d poate fi 1 sau 2, de unde se obţine expresia:
2 φ 1  φ 2  φ 3  ...  φ N ©2  φ N ©2  1  φ N ©2  2  ...  φ N 
Din acest număr trebuie scăzut numărul fracţiile identice cu cele numărate anterior, deoarece
au fost numărate de două ori fracţiile de tipul 1©F a, având ı̂n vedere că primii 2 termeni ai şirului
Fibonacci sunt 1. Mai trebuie deasemenea scăzute şi fracţiile 1©1 şi 2©2 care nu sunt subunitare.
Soluţia obţine 100 puncte şi are complexitate O N log N .

4.5.2 Cod sursă

Listing 4.5.1: fibofrac CC1.cpp


// Brute force
// O(nˆ2)

#include <fstream>

using namespace std;

long long fib[1000001];

long long cmmdc(long long a, long long b)


{
long long d=a,i=b,r=d%i;
while (r)
{
d=i;
i=r;
r=d%i;
}
return i;
}

int main()
{
long long i,j,n,sol;

ifstream f("fibofrac.in");
ofstream g("fibofrac.out");

f >> n;

fib[1]=1;fib[2]=1;
for(i=3;i<=n;i++)
fib[i]=fib[i-1]+fib[i-2];

sol=0;
CAPITOLUL 4. ONI 2019 4.5. FIBOFRAC 58

for(i=2;i<n;i++)
for(j=i+1;j<=n;j++)
if (cmmdc(fib[i],fib[j])==1) sol++;
g<<sol<<" ";

f.close();
g.close();

return 0;
}

Listing 4.5.2: fibofrac CC2.cpp


// prof. Chesca Ciprian
// Complexitate O(nlog(n))

#include <fstream>
#define nmax 1000003

using namespace std;

int n,i,j;

long long T=0;


int fi[nmax];

ifstream f("fibofrac.in");
ofstream g("fibofrac.out");

int main()
{

f>>n;

// calculez fi(i)
for (i=1;i<=n;i++)
fi[i] = i-1;

fi[1]=1;
for (i=2;i<=n;i++)
for (j=i+i;j<=n;j+=i)
fi[j] -= fi[i];

for(i=1;i<=n/2;i++)
T+=2*fi[i];

for(i=n/2+1;i<=n;i++)
T+=fi[i];

g<<T - (n-2) - 3<<"\n";

f.close();
g.close();

return 0;
}

Listing 4.5.3: fibofrac CC3.cpp


// Brute force
// O(nˆ2) fara generarea termenilor sirului Fibonacci

#include <fstream>

using namespace std;

long long cmmdc(long long a, long long b)


{
long long d=a,i=b,r=d%i;
while (r)
{
d=i;
i=r;
r=d%i;
CAPITOLUL 4. ONI 2019 4.5. FIBOFRAC 59

}
return i;
}

int main()
{
long long i,j,n,sol;
ifstream f("fibofrac.in");
ofstream g("fibofrac.out");
f >> n;

sol=0;
for(i=2;i<n;i++)
for(j=i+1;j<=n;j++)
if (cmmdc(i,j)==1 || cmmdc(i,j)==2) sol++;
g<<sol<<" ";

f.close();
g.close();

return 0;
}

Listing 4.5.4: fibofrac CC4.cpp


// prof. Chesca Ciprian
// O(nsqrt(n))

#include <fstream>
#define nmax 1000003

using namespace std;

long long T=0;


int fi[nmax],t;

char v[nmax];
int prime[nmax/5],nr=0;
int b[nmax/5],nf=0;

// Ciurul lui Eratostene


void ciur(int n)
{
int i, j;
for (i = 2; i <= n; ++i)
{
if (v[i] == 0)
{
prime[++nr]=i;
for (j = i + i; j <= n; j += i)
{
v[j] = 1;
}
}
}
}

//descompun in factori primi


void desc(int n)
{
int i=1,j=0,f;
while (n>1)
{
f=0;
while (n%prime[i]==0)
{
f++;
n=n/prime[i];
}

if (f)
{
b[++j]=prime[i];
}
else
CAPITOLUL 4. ONI 2019 4.5. FIBOFRAC 60

i++;
}
nf=j;
}

int main()
{
ifstream f("fibofrac.in");
ofstream g("fibofrac.out");

int n,i,j;
f>>n;
ciur(n);

// calculez fi(i) in O(sqrt(n))


for(i=1;i<=n;i++)
{
fi[i]=i;
desc(i);
for(j=1;j<=nf;j++)
{
fi[i]=fi[i]/b[j];
fi[i]=fi[i]*(b[j]-1);
}
}

for(i=1;i<=n/2;i++)
T+=2*fi[i];

for(i=n/2+1;i<=n;i++)
T+=fi[i];

g<<T - (n-2) - 3<<"\n";

f.close();
g.close();

return 0;
}

Listing 4.5.5: fibofrac CC5.cpp


// prof. Chesca Ciprian
// Complexitate O(nlog(n))

#include <fstream>
#include <cassert>
#define nmax 1000003

using namespace std;

int n,i,j;

long long T=0;


int fi[nmax];

ifstream f("fibofrac.in");
ofstream g("fibofrac.out");

int main()
{
f>>n;
assert(n>0 && n < 10000001);

// calculez fi(i)
for (i=1;i<=n;i++)
fi[i] = i-1;

fi[1]=1;
for (i=2;i<=n;i++)
for (j=i+i;j<=n;j+=i)
fi[j] -= fi[i];

for(i=1;i<=n/2;i++)
T+=2*fi[i];
CAPITOLUL 4. ONI 2019 4.6. TELEFON 61

for(i=n/2+1;i<=n;i++)
T+=fi[i];

g<<T - (n-2) - 3<<"\n";

f.close();
g.close();

return 0;
}

Listing 4.5.6: fibofrac TC1.cpp


// student Tudor Chiorean - Universitatea Tehnica din Cluj
// 100 puncte

#include <bits/stdc++.h>
using namespace std;

#define NMAX 1000005

ifstream fin("fibofrac.in");
ofstream fout("fibofrac.out");

int phi[NMAX], n, i, j;
long long ans;

int main()
{
fin >> n;
for (int i = 1; i <= n; ++i)
phi[i] = i-1;
for (int i = 2; i <= n; ++i)
for (int j = 2 * i; j <= n; j += i)
phi[j] -= phi[i];

for (i = 1 ; i <= n ; i++)


ans += ((i <= (n / 2)) ? 2 : 1 ) * phi[i];

ans -= n - 1;

fout << ans;

return 0;
}

4.5.3 *Rezolvare detaliată

4.6 telefon
Problema 6 - telefon 100 de puncte
Dorel, plictisit de puzzle-ul pe care l-a upgradat ieri, a decis să meargă afară cu ceilalţi copii.
El ı̂i priveşte pe cei N copii cum joacă telefonul fără fir”.
Jocul decurge ı̂n felul următor:
- Iniţial, copiii se aşază pe axa Ox, copilul i la distanţa Xi metri faţă de origine.
- Copilul cel mai aproape de origine alege un cuvânt secret şi ı̂l transmite celui din dreapta lui;
cel din dreapta lui ı̂l transmite următorului şi aşa mai departe până se ajunge la ultimul copil.
Pentru a transmite cuvântul, fiecare copil trebuie să meargă până la copilul din dreapta lui.
Toţi copiii se deplasează cu viteza constantă de 1 metru/secundă.
Cu toate acestea, pentru a evita deplasarea fiecare copil dispune de un dispozitiv de tip walkie-
talkie ce permite transmiterea unui cuvânt mai departe. Toate staţiile walkie-talkie au o rază de
acţiune R, setată la ı̂nceputul unei runde de joc (exprimată ı̂n metri) ce nu poate fi modificată
pe parcursul jocului. Staţiile sunt conectate la aceeaşi sursă de alimentare care are B unităţi de
energie.
CAPITOLUL 4. ONI 2019 4.6. TELEFON 62

ı̂n funcţie de raza de acţiune setată, copiii pot sau nu să folosească sistemul walkie-talkie pentru
a nu se mai deplasa. Mai exact, dacă un copil ar trebui să parcurgă o distanţă mai mică sau egală
cu R ca să transmită cuvântul celui din dreapta sa şi bateria sursei are cel puţin R unităţi de
energie rămase, acesta poate folosi sistemul walkie-talkie ca să transmită instantaneu cuvântul
secret, iar bateria se va descărca cu R unităţi de energie. Cu toate acestea, chiar şi cu sistemul
walkie-talkie, un copil nu are voie să transmită mesajul decât primului copil situat ı̂n dreapta lui.
Copiii doresc ca jocul să se termine cât mai repede, aşa că vor seta o rază de acţiune convenabilă
şi vor alege să folosească sau nu sistemul walkie-talkie, pentru a minimiza timpul necesar ca toţi
cei N copii să afle cuvântul secret.
Dorel doreşte să se alăture jocului, aşa că ı̂n a doua parte a jocului va intra şi el ı̂n rând. Dorel
se va aşeza pe axa Ox, undeva ı̂ntre primul şi ultimul copil, la o anumită distanţă de origine unde
nu se află un alt copil.

Cerinţe

1. Care este durata minimă a jocului, dacă Dorel nu ia parte la joc?


2. Care este durata minimă a jocului, dacă Dorel ia parte la joc şi se poziţionează ı̂n mod
optim pentru a minimiza durata jocului?

Date de intrare

Fişierul de intrare telefon.in conţine pe prima linie două numere naturale N şi B cu
semnificaţia din enunţ. Pe cea de-a doua linie se află N numere naturale nenule distincte Xi ,
ı̂n ordine strict crescătoare, unde Xi reprezintă distanţa copilului i faţă de origine, 1 & i & N .

Date de ieşire

Fişierul de ieşire telefon.out va conţine două numere naturale C1 C2, separate printr-un
spaţiu, unde C1 reprezintă răspunsul la cerinţa 1 iar C2 răspunsul la cerinţa 2.

Restricţii şi precizări

a 2N 105
a 1B109, 1Xi109
a Se garantează că Dorel are cel puţin o poziţie liberă pe care se poate aşeza
a Un copil poate alege ı̂ntre a se deplasa sau a folosi walkie-talkie pentru a transmite un mesaj
a Copiii pot seta o noua rază de acţiune a walkie-talkie când Dorel intră ı̂n joc
a Pentru prima cerinţă se acordă 40 puncte
a Pentru a doua cerinţă se acordă 60 puncte
a Pentru teste ı̂n valoare de 15 puncte N, B & 10
2

a Pentru alte teste ı̂n valoare de 35 puncte N & 10 , B & 10


3 4

a Pentru alte teste ı̂n valoare de 20 puncte N & 10 , B & 10


5 5

a Pentru alte teste ı̂n valoare de 30 puncte N & 10 , B & 10


5 9

Exemple

telefon.in telefon.out Explicaţii


CAPITOLUL 4. ONI 2019 4.6. TELEFON 63

6 15 86 N=6, B=15
7 9 12 16 21 27 X[1-6]=[7,9,12,16,21,27]

1.Dacă Dorel nu participă la joc atunci copiii vor alege raza


de acţiune R=5 şi al 2-lea, al 3-lea şi al 4-lea copil vor folosi
sistemul de comunicare. ı̂n consecinţă durata jocului va fi (9-
7)+(27-21)= 2+6 = 8
2.Dacă Dorel participă la joc se va poziţiona la distanţa 26 faţă
de origine.
ı̂n această situaţie copiii vor alege raza de acţiune R tot 5 şi
al 3-lea, al 4-lea şi al 5-lea copil vor folosi sistemul de comuni-
care. ı̂n consecinţă durata jocului va fi (9-7)+(12-9)+(27-26)
= 2+3+1 = 6

Timp maxim de executare/test: Windows: 1.0 secunde, Linus: 0.2 secunse


Memorie: total 64 MB
Dimensiune maximă a sursei: 10 KB

4.6.1 Indicaţii de rezolvare


student Alex Turdean - Universitatea Tehnică din Cluj Napoca

Pentru a rezolva cerinţa 1 e suficient să considerăm că R poate lua valori doar din mulţimea
distanţelor dintre oricare 2 copii.
Pentru a rezolva a 2-a cerinţă trebuie să observăm că R poate să nu aparţină din mulţimea
distanţelor dintre oricare 2 copii. De asemenea, trebuie să ı̂l aşezăm pe Dorel astfel ı̂ncât să
spargem o distanţă ı̂n două bucăţi. Mai exact, noi am vrea o distanţă X mai mare decât R (raza
noastra curentă) pe care să o spărgem” ı̂n 2 bucăţi, de lungimi R respectiv X  R. Mereu vom
folosi distanţa de lungime R dar dacă ne uităm mai atent observăm ca există cazuri ı̂n care am
dori să folosim şi o bucată de lungime X  R aşa că vom căuta cea mai mare distanţa X cu
proprietatea că X & 2R (pentru a maximiza X  R). Pentru a nu căuta liniar acest X putem
menţine un pointer ı̂n faţă.
Soluţie - 15 puncte
Se observă că pentru o valoare fixată a lui R strategia optimă este să luăm cele mai mari
distanţe mai mici sau egale cu R. Astfel, prima dată vom calcula distanţele ı̂ntre oricare 2 copii
consecutivi şi le vom sorta crescător. Având acest vector sortat, pentru fiecare valoare a lui R ı̂n
intervalul 1, B  căutăm cea mai mare distanţă mai mică sau egală cu R şi vom verifica cât de
mult ne putem extinde ı̂n stânga acestei distanţe (ı̂n limita bateriei) pentru a minimiza cât mai
mult durata jocului.
2
Această soluţie are complexitate O N ˜ B 
Soluţie - 50 puncte
Pentru a optimiza soluţia anterioară observăm că pentru o valoare fixată a lui R este suficientă
o singură parcurge a vectorului de distanţe folosind 2 pointeri. Pointerul din dreapta ne spune cea
mai mare distanţă mai mică sau egală cu R iar pointerul din stânga cat de mult ne putem extinde.
De fiecare dată când incrementam pointerul din dreapta vom verifica dacă dapăşim limita bateriei,
caz ı̂n care vom incrementa şi pointerul din stânga.
Complexitate: O N ˜ B 
Soluţie - 70 puncte
Optimizând ı̂n continuare algoritmul, observăm că nu trebuie să iteram prin toate valorile pe
care le poate lua R. În schimb, vom ı̂ncepe cu R 1 şi vom incrementa R ı̂n funcţie de distanţa
maximă pe care o considerăm la momentul actual(pointerul din dreapta).
Pentru a 2-a cerinta este necesar sa testam toate valorile pe care le poate lua R si nu doar din
multimea distantelor.
Complexitate: O N ˜ log N   B 
CAPITOLUL 4. ONI 2019 4.6. TELEFON 64

Soluţie - 100 puncte


Continuând soluţia anterioară, algoritmul optim nu va lua ı̂n consider-
are pentru R toate valorile din intervalul 1, B  ci doar valorile din mulţimea
r1, 2, 3, 4, 5, ...., sqrt B , B, B ©2, B ©3, B ©4, B ©5, .., B ©sqrt B x. Astfel reducând cardinalul
mulţimii de valori pentru R de la B la 2 ˜ sqrt B .
Complexitate: O N ˜ log N   sqrt B 
Soluţie alternativă - 100 puncte
O mică optimizare a soluţiei anterioare constă ı̂n observaţia că nu sunt necesare decât cei mai
mari N candidaţi dintre cei 2sqrt B  (ı̂n esenţă, B, B ©2, B ©3, ..., B ©N ). Astfel se obţine o soluţie
de complexitate O N log N , independentă de B.

4.6.2 Cod sursă

Listing 4.6.1: alex.cpp


#include <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
#define int long long

using namespace std;

vector<long long> v,dist,vals;

long long total,n,m,current,best,nokid;

int32_t main()
{
ifstream cin("telefon.in");
ofstream cout("telefon.out");

cin >> n >> m;


for(int i = 1; i <= n; i++)
{
int a;
cin >> a;
v.push_back(a);
}

for(int i = 0; i < v.size() - 1; i++)


{
dist.push_back(v[i + 1] - v[i]);
vals.push_back(v[i + 1] - v[i]);
total += v[i + 1] - v[i];
}

sort(dist.begin(), dist.end());

int i;
for(i = 1; i*i <= m; i++)
vals.push_back(i),vals.push_back(m/i);

vals.push_back(i), vals.push_back(i + 1);

sort(vals.begin(), vals.end());

int first = 0;
int semnal = 0;
int second = 0;
int extra = 0;
current = dist[0];

while(first < dist.size())


{
while(semnal + 1 < vals.size() && vals[semnal] < dist[first])
semnal++;

if(vals[semnal] < dist[first])


break;
CAPITOLUL 4. ONI 2019 4.6. TELEFON 65

long long delta = vals[semnal];

while((first - second + 1) * delta > m)


current -= dist[second], second++;

while(extra+1 < dist.size() && dist[extra+1] - delta <= delta)


extra++;

if(extra == first && extra + 1 < dist.size())


extra++;

int aux = second;


long long size = (first-second+1) * delta;
if(extra != first)
{
long long z = current;
long long plus = dist[extra] - delta;

if(size + delta <= m)


size += delta, z += delta;
else
if(aux <= first)
z += delta - dist[aux], aux++;

if(size + delta <= m)


size += plus, z += delta;
else
if(aux <= first && dist[aux] < plus)
z += plus - dist[aux];

best = max(z, best);


}

nokid = max(current, nokid);


best = max(current, best);
if(first + 1 == dist.size())
first++;
else
if(semnal+1 < vals.size() && vals[semnal+1] < dist[first+1])
semnal++;
else
first++,current+= dist[first];
}

long long z = 0;
cout<<max(z,total-nokid) << ’ ’;
cout<<max(z,total-best) << ’\n’;
}

Listing 4.6.2: telefon-adrian-100.cpp


#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <climits>

#define int int64_t

using namespace std;

int32_t main()
{
ifstream cin("telefon.in");
ofstream cout("telefon.out");

int N, B; cin >> N >> B;

vector<int> X(N);

for (int i = 0; i < N; ++i)


{
cin >> X[i];
}
CAPITOLUL 4. ONI 2019 4.6. TELEFON 66

for (int i = N - 1; i > 0; --i)


X[i] -= X[i - 1];

X.erase(X.begin());
--N;

sort(X.begin(), X.end());

vector<int> partial(X.size());

for (int i = 0; i < int(X.size()); ++i)


{
partial[i] += X[i];
if (i > 0)
partial[i] += partial[i - 1];
}

int answer = numeric_limits<int>::max();

for (int i = 0; i < int(X.size()); ++i)


{
// this is max
int can_take = B / X[i];
int need = partial.back() - partial[i];
if (can_take <= i)
need += partial[i - can_take];
answer = min(answer, need);
}

cout << answer << " ";

answer = numeric_limits<int>::max();
N = X.size();
for (int cover = N + 1, end = -1, magic = 0; cover > 0; --cover)
{
int max_cover = B / cover;
// 3 ways
// we cover cover of already existant values
// we cover cover - 1 of already existent values + some part
// of the biggest
// we cover cover - 2 of already + the biggest <= 2 * max_cover

while (end + 1 < N && X[end + 1] <= max_cover)


{
++end;
}

if (end == -1)
continue;

while (magic + 1 < N && X[magic + 1] <= 2 * max_cover)


++magic;

// ignore Dorel
int now = partial[N - 1] - partial[end];
if (end - cover >= 0)
now += partial[end - cover];

answer = min(answer, now);

// Dorel once
if (cover > 0)
{
now = partial[N - 1] - partial[end];
if (end - (cover - 1) >= 0)
now += partial[end - (cover - 1)];

if (end < N - 1)
now -= min(max_cover, X[N - 1]);

answer = min(answer, now);


}

// Dorel twice
if (cover > 1)
CAPITOLUL 4. ONI 2019 4.6. TELEFON 67

{
now = partial[N - 1] - partial[end];
if (end - (cover - 2) >= 0)
now += partial[end - (cover - 2)];

if (end < magic)


now -= X[magic];

answer = min(answer, now);


}
}

cout << answer << "\n";


}

Listing 4.6.3: telefon-bicsi-100.cpp


#include <bits/stdc++.h>

#define int long long

using namespace std;

pair<long long, long long> Solve(int v, vector<int> dists, set<string> opts)


{
int n = dists.size();
sort(dists.begin(), dists.end());
long long sum = 0;
for (auto x : dists)
sum += x;

int magic = sqrt(v) + 2;


vector<int> cands = dists;

if (!opts.count("cands_are_dists"))
{
cands.clear();
for (int i = 1; i <= magic && i <= v; ++i)
{
cands.push_back(i);
cands.push_back(v / i);
}

sort(cands.rbegin(), cands.rend());

for (auto& x : cands)


x = v / x;
}
else
{
for (auto x : dists)
if (x > 1)
cands.push_back(x / 2);

sort(cands.begin(), cands.end());
}

vector<long long> bests(3, 0);

long long now = 0;


int l = 0, r = 0, p = -1;

for (auto delta : cands)


{
// bests[0] = bests[1] = bests[2] = 0;

int phones = v / delta;


while (r < n && dists[r] <= delta)
{
now += dists[r];
++r;
}

while (r - l > phones)


CAPITOLUL 4. ONI 2019 4.6. TELEFON 68

{
now -= dists[l];
++l;
}

bests[0] = max(bests[0], now);


if (phones >= 1 && r < n)
{
long long xnow = now + delta;
if (r - l == phones) xnow -= dists[l];
bests[1] = max(bests[1], xnow);
}

while (p + 1 < n && dists[p + 1] <= 2 * delta)


++p;

if (phones >= 2 && p >= r)


{
long long xnow = now + dists[p];
if (r - l >= phones - 1) xnow -= dists[l];
if (r - l == phones) xnow -= dists[l + 1];
bests[2] = max(bests[2], xnow);
}

/*
for (auto x : dists) cerr << x << " "; cerr << endl;
cerr << "delta: " << delta << endl;
cerr << "(phones: " << phones << ")" << endl;
cerr << "bests: " << bests[0] << " " << bests[1]
<< " " << bests[2] << endl;
cerr << endl;
*/
}

long long ans = 0;

ans = max(ans, bests[0]);


ans = max(ans, bests[1]);
ans = max(ans, bests[2]);

return make_pair(sum - bests[0], sum - ans);


}

int32_t main()
{
ifstream cin("telefon.in");
ofstream cout("telefon.out");

int n, v; cin >> n >> v;


int last = -1;

vector<int> dists;

for (int i = 0; i < n; ++i)


{
int x; cin >> x;
if (i) dists.push_back(x - last);
last = x;
}

auto p = Solve(v, dists, {});


cout << p.first << " " << p.second << endl;

return 0;
}

Listing 4.6.4: tudor telefon v2.cpp


#include <bits/stdc++.h>

using namespace std;

#define int long long

ifstream fin("telefon.in");
CAPITOLUL 4. ONI 2019 4.6. TELEFON 69

ofstream fout("telefon.out");

int n, b, i, x, y, total;

vector<int> d, signals;

void subtask()
{
int ls = 0, ld = 0, best = 0, saved = 0;
while (ld < d.size() && d[ld] <= b)
{
saved += d[ld++];
while ( (ld - ls) * d[ld - 1] > b )
saved -= d[ls++];

best = max(best, saved);


}

fout << total - best << ’ ’;


}

void solve()
{
int ls = 0, ld = 0, split = 0, bonus[3], saved = 0, best = 0;

for (int k = 0 ; k < signals.size() ; k++) {


int signalSize = signals[k];

while (ld < d.size() && d[ld] <= signals[k])


ld++; /// Fixate ld
ls = max(0LL, ld - (b / signals[k])); /// Fixate ls

split = ld + 1;
while (split < d.size() && d[split] <= 2 * signals[k])
split++; /// Fixate split

bonus[0] = bonus[1] = 0;
if (split > 0 && ld != d.size())
{
bonus[0] = min(signals[k], d[split - 1]);
bonus[1] = max(d[split - 1] - signals[k], 0LL);
if (bonus[1] > signalSize) bonus[1] = 0;
}

saved = 0;
int spaceLeft = b / signalSize - (ld - ls);
if (spaceLeft <= 0)
{
for (int r = ls ; r < ld ; r++)
{
if (r - ls == 0 || r - ls == 1)
saved += (bonus[r - ls] > d[r] ? bonus[r - ls] : d[r]);
else
saved += d[r];
}
}
else if (spaceLeft == 1)
{
for (int r = ls ; r < ld ; r++)
{
if (r - ls == 0)
saved += (bonus[1] > d[r] ? bonus[1] : d[r]);
else
saved += d[r];
}

saved += bonus[0];
}
else
{
for (int r = ls ; r < ld ; r++)
saved += d[r];

saved += bonus[0] + bonus[1];


}
CAPITOLUL 4. ONI 2019 4.6. TELEFON 70

best = max(best, saved);


}

fout << total - best;


}

int32_t main()
{
fin >> n >> b;
fin >> x;

for (i = 2 ; i <= n ; i++)


{
fin >> y;
d.push_back(y - x);
total += y - x;

x = y;
}

sort(d.begin(), d.end());
for (i = 0 ; i < d.size() ; i++)
{
cout << d[i] << ’ ’;
}

for (i = 1 ; i <= sqrt(b) ; i++)


signals.push_back(i);

for (i = int32_t(sqrt(b)) ; i >= 1 ; i--)


signals.push_back(b / i);

subtask();
solve();

return 0;
}

4.6.3 *Rezolvare detaliată


Capitolul 5

ONI 2018

5.1 bazaf
Problema 1 - bazaf 100 de puncte
În matematică factorialul unui număr natural nenul K este notat cu K! şi este egal cu produsul
numerelor naturale nenule mai mici sau egale cu K.
Exemple: 1! 1; 2! 1 2 2; 3! 1 2 3 6, ... , K! 1 2 3 ... K.
Orice număr natural N poate fi descompus cu ajutorul numerelor factoriale astfel:

N 1!f1  2!f2  3!f3  ...  m!fm

unde coeficienţii fi , cu 1 & i & m sunt numere naturale şi ı̂n plus fm j 0;
Exemple: 20 1!20; 20 1!6  2!4  3!1; 20 1!0  2!1  3!3;
Dintre toate aceste descompuneri posibile există o singură descompunere, numită descom-
punere ı̂n bază factorială care respectă suplimentar condiţiile 0 & fi & i, cu 1 & i $ m şi
0 $ fm & m.
Exemple: 6 1!0  2!0  3!1; 17 1!1  2!2  3!2; 119 1!1  2!2  3!3  4!4;

Cerinţe

1. Să se determine descompunerea ı̂n bază factorială a unui număr natural X dat.
2. Cunoscând o descompunere oarecare a unui număr natural Y să se determine descompunerea
ı̂n bază factorială a acestuia.

Date de intrare

Fişierul de intrare este bazaf.in


Acesta conţine pe primul rând un număr natural V care poate avea doar valorile 1 sau 2 cu
următoarea semnificaţie:
a dacă valoarea lui V este 1, pe a doua linie a fişierului de intrare se găseşte un număr natural
X cu semnificaţia de mai sus;
a dacă valoarea lui V este 2, pe a doua linie a fişierului de intrare se găseşte o descompunere
a unui număr Y sub forma unui şir de valori naturale ı̂n care primul termen este m, urmat de m
valori fi , care respectă condiţiile fi ' 0, cu 1 & i $ m şi fm j 0, despărţite prin câte un spaţiu, cu
semnificaţia de mai sus

Date de ieşire

Fişierul de ieşire este bazaf.out


Dacă valoarea V este 1 atunci fişierul de ieşire va conţine descompunerea ı̂n baza factorială a
numărului X iar dacă valoarea V este 2 atunci fişierul de ieşire va conţine descompunerea ı̂n baza
factorială a numărului Y .
Descompunerea ı̂n bază factorială presupune scrierea ı̂n fişierul de ieşire a unei singure linii
sub forma unui şir de valori naturale ı̂n care primul termen este m, urmat de m valori fi , care
respectă condiţiile 0 & fi & i, cu 1 & i $ m şi 0 $ fm & m, despărţite prin câte un spaţiu, având
semnificaţia de mai sus.

Restricţii şi precizări

71
CAPITOLUL 5. ONI 2018 5.1. BAZAF 72

2 & X & 10
15
a
a 1 $ m & 100 000
a 0 & fi & 10
9

a Pentru rezolvarea corectă a primei cerinţă se va acorda 30% din punctaj, iar pentru cea de-a
doua cerinţă se va acorda 70% din punctaj.

Exemple:

bazaf.in bazaf.out Explicaţii


1 3122 V 1, deci se rezolvă doar prima cerinţă
17 X 17
Descompunerea numărului X 17 ı̂n bază factorială conţine 3
termeni şi este formată din coeficienţii 1, 2, 2 (17 = 1! 1 + 2!
2 + 3! 2);
2 2 10 5 3013 V 2, deci se rezolvă doar a doua cerinţă
Descompunerea 2 10 5 este o descompunere cu 2 termeni având
coeficienţii 10, 5 şi corespunde numărului Y 20.
Descompunerea ı̂n bază factorială a numărului
Y = 20 va fi 3 0 1 3 (20 = 1! 0 + 2! 1 + 3! 3)
Timp maxim de executare/test: 0.8 secunde
Memorie: total 128 MB din care pentru stivă 32 MB
Dimensiune maximă a sursei: 10 KB

5.1.1 Indicaţii de rezolvare

Prof. Cheşcă Ciprian - Liceul Tehnologic ”Grigore C. Moisil” Buzău


Student Posdărăscu Daniel, Universitatea Bucureşti

Pentru rezolvarea primei cerinţe se construieşte un vector cu toate valorile lui k! pentru k & 20
apoi printr-o strategie Greedy se determină care este cel mai apropiat factorial mai mic decât
numărul dat, se calculează coeficientul acelui termen, se scade din numărul dat termenul respectiv,
se memorează ı̂n vectorul de descompuneri coeficientul calculat şi se reia operaţia până când
numărul curent devine 0.
Pentru rezolvarea celei de-a doua cerinţe se observă ı̂ncă de la ı̂nceput că o soluţie care să
calculeze numărul Y şi apoi să determine descompunerea ı̂n bază factorială folosind ideea de la
cerinţa anterioară nu poate obţine puncte deoarece descompunerea dată are un număr foarte mare
de termeni şi corespunde unui număr Y foarte mare.
Soluţia constă ı̂ntr-o observaţie matematică şi anume că se poate parcurge vectorul descom-
punerii şi dacă un coeficient este mai mare decât limita impusă vom calcula cât din valoarea
respectivă va fi transportată către următorul coeficient.
Se actualizează astfel atât coeficientul curent cât şi cel următor şi ı̂n cazul cănd ultimul coefi-
cient este mai mare decât limita impusă se adaugă noi coeficienţi.
O astfel de soluţie nu presupune calcularea numărului asociat descompunerii.
Exemplu: Pentru descompunerea 4 7 12 14 9 se trece prin următoarele etape
4 7 12 14 9 (7 1! = 1 + 3 2!)
4 1 15 14 9 (15 2! = 0 + 5 3!)
4 1 0 19 9 (19 3! = 3 + 4 4!)
4 1 0 3 13 (13 4! = 3 + 2 5!) s-a mai adăugat un termen
5 1 0 3 3 2

5.1.2 Cod sursă

Listing 5.1.1: 1 bazaf.cpp


// student Posdarascu Daniel

#include<stdio.h>
CAPITOLUL 5. ONI 2018 5.1. BAZAF 73

#include<algorithm>
#include<vector>
#include<cassert>

using namespace std;

#define NMAX 100005

long long n;
int v[NMAX], p;

void findBazaFactoriala(long long n)


{
long long fact = 1, k;
for(k = 1; ; k++)
{
if(fact * (k + 1) > n)
break;
fact *= (k + 1);
}

vector<int> answer;

for(; k >= 1; k--)


{
answer.push_back(n / fact);
n %= fact;
fact /= k;
}

int lim = answer.size();


printf("%d ", lim);

for(int i = lim - 1; i >= 0; i--)


printf("%d ", answer[i]);
printf("\n");
}

void solve1()
{
scanf("%lld",&n);
assert(1 <= n && n <= 1LL * 1000000000000000);
findBazaFactoriala(n);
}

void solve2()
{
scanf("%lld",&n);
assert(1 <= n && n <= 100000);
for(int i = 1; i <= n; i++)
{
assert(0 <= v[i] && v[i] <= 1000000000);
scanf("%d",&v[i]);
}

for(long long i = 1; i <= n || v[i] > 0; i++)


{
v[i + 1] += v[i] / (i + 1);
v[i] %= (i + 1);
n = max(n, i);
}

printf("%lld ", n);


for(int i = 1; i <= n; i++)
{
printf("%d ", v[i]);
}
printf("\n");
}

int main ()
{
freopen("bazaf.in","r",stdin);
freopen("bazaf.out","w",stdout);

scanf("%d", &p);
CAPITOLUL 5. ONI 2018 5.1. BAZAF 74

assert(1 <= p && p <= 2);


if(p == 1)
{
solve1();
}
else
{
solve2();
}

return 0;
}

Listing 5.1.2: 2 bazaf.cpp


// prof. Chesca Ciprian

#include<fstream>
#include<iostream>

using namespace std;

ifstream fin("bazaf.in");
ofstream fout("bazaf.out");

#define ll long long

int nx,m,i;
ll v,x[21],fi[100005];
ll f[21],p,c,r;

int main()
{
f[0]=1;
for (i=1;i<=20;i++)
{
f[i]=f[i-1]*i;
}

fin>>p;

if (p==1)
{

fin >> v;
nx=0;
for(int j=20;j>=1;j--)
{
x[j]=v/f[j];
if(x[j]>0 && nx==0)
{
nx=j;
}
v=v%f[j];
}

fout<<nx;
for(int j=1;j<=nx;j++)
{
fout<<" "<<x[j];
}
}

if (p==2)
{
fin >> m;

for(i=1;i<=m;i++)
fin >> fi[i];

for(i=1;i<=m-1;i++)
{
c=fi[i]/(i+1);
r=fi[i]%(i+1);
fi[i]=r;
CAPITOLUL 5. ONI 2018 5.1. BAZAF 75

fi[i+1]+=c;
}

while (fi[m]>m)
{
c=fi[m]/(m+1);
r=fi[m]%(m+1);
fi[m]=r;
fi[++m]+=c;
}

fout << m <<" ";


for(i=1;i<=m;i++)
fout << fi[i]<<" ";
fout << "\n";
}

fout.close();
fin.close();
return 0;
}

Listing 5.1.3: 3 bazaf.cpp


// student Posdarascu Daniel - brute force
#include<stdio.h>
#include<cassert>
#include<algorithm>
#include<vector>

using namespace std;

#define NMAX 100005

long long n;
long long v[NMAX], p;

void findBazaFactoriala(long long n)


{
long long fact = 1, k;
for(k = 1; ; k++)
{
if(fact * (k + 1) > n)
break;
fact *= (k + 1);
}

vector<int> answer;

for(; k >= 1; k--)


{
answer.push_back(n / fact);
n %= fact;
fact /= k;
}

long long lim = answer.size();


printf("%d ", lim);
for(int i = lim - 1; i >= 0; i--)
printf("%d ", answer[i]);
printf("\n");
}

void solve1()
{
scanf("%lld",&n);
assert(1 <= n && n <= 1LL * 1000000000000000);
findBazaFactoriala(n);
}

void solve2()
{
long long fact = 1, value = 0;

scanf("%lld",&n);
CAPITOLUL 5. ONI 2018 5.1. BAZAF 76

assert(1 <= n && n <= 100000);


for(int i = 1; i <= n; i++)
{
scanf("%d",&v[i]);
assert(0 <= v[i] && v[i] <= 1000000000);
fact *= i;
value += v[i] * fact;
}

findBazaFactoriala(value);
}

int main ()
{
freopen("bazaf.in","r",stdin);
freopen("bazaf.out","w",stdout);

scanf("%d", &p);
assert(1 <= p && p <= 2);

if(p == 1)
solve1();
else
solve2();

return 0;
}

Listing 5.1.4: 4 bazaf.cpp


// prof. Chesca Ciprian
#include<fstream>
#include<iostream>

using namespace std;

ifstream fin("bazaf.in");
ofstream fout("bazaf.out");

#define ll long long

int nx,m,i,j;
ll v,x[21],fi[100005];
ll f[21],p,c,r;

int main()
{
f[0]=1;
for (i=1;i<=20;i++)
{
f[i]=f[i-1]*i;
}

fin>>p;

if (p==1)
{

fin >> v;
nx=0;
for(int j=20;j>=1;j--)
{
x[j]=v/f[j];
if(x[j]>0 && nx==0)
{
nx=j;
}
v=v%f[j];
}
fout<<nx;

for(int j=1;j<=nx;j++)
fout<<" "<<x[j];
}
CAPITOLUL 5. ONI 2018 5.2. MEXITATE 77

if (p==2)
{
fin >> m;
long long s=0;
for(i=1;i<=m;i++)
{
fin >> fi[i];
s+=fi[i]*f[i];
}

v=s;
nx=0;
for(int j=20;j>=1;j--)
{
x[j]=v/f[j];
if(x[j]>0 && nx==0)
{
nx=j;
}
v=v%f[j];
}
fout<<nx;

for(int j=1;j<=nx;j++)
{
fout<<" "<<x[j];
}

fout << "\n";


}

fout.close();
fin.close();
return 0;
}

5.1.3 *Rezolvare detaliată

5.2 mexitate
Problema 2 - mexitate 100 de puncte
Se dă o matrice A cu N linii şi M coloane cu elemente numere naturale nu neapărat distincte.
Pentru o submatrice definim mex-ul acesteia ca fiind cea mai mică valoare naturală nenulă care
nu apare ı̂n aceasta.

Cerinţe

Să se calculeze produsul mex-urilor tuturor submatricelor având K linii şi L coloane ale matricei
A.

Date de intrare

Fişierul mexitate.in conţine pe prima linie patru numere naturale N , M , K şi L separate
printr-un spaţiu cu semnificaţia din enunţ.
Pe fiecare dintre următoarele N linii se află câte M numere naturale nenule, despărţite prin
câte un spaţiu, reprezentând valorile matricei.

Date de ieşire

Fişierul mexitate.out va conţine un singur număr natural reprezentând produsul mex-urilor


tuturor submatricelor având K linii şi L coloane ale matricei modulo 1 000 000 007.

Restricţii şi precizări


CAPITOLUL 5. ONI 2018 5.2. MEXITATE 78

a 1 & N ˜ M & 400 000


a 1&K&N
a 1&L&M
a 1 & Aij  & N ˜ M , 1 & i & N , 1 & j & M
a Pentru 20% din punctajul total există teste cu 1 & N, M & 50
a Pentru alte 20% din punctajul total există teste cu 1 & N, M & 630

Exemple:

mexitate.in mexitate.out Explicaţii


3423 400 N = 3 şi M = 4
1232 K = 2 şi L = 3
2314 Submatricile cu 2 linii şi 3 coloane sunt:
1126
1 2 3 cu mex-ul 4
231

2 3 2 cu mex-ul 5
314

2 3 1 cu mex-ul 4
112

3 1 4 cu mex-ul 5
126

Produsul tuturor mex-urilor este:


4 5 4 5 = 400
400 % 1 000 000 007 = 400
Timp maxim de executare/test: 3.0 secunde
Memorie: total 128 MB din care pentru stivă 32 MB
Dimensiune maximă a sursei: 10 KB

5.2.1 Indicaţii de rezolvare

Posdarascu Eugenie Daniel - Universitatea din Bucureşti

Soluţie 20 de puncte:
Pentru fiecare submatrice de K * L, se parcurg elementele şi se pun ı̂ntr-un vector de frecvenţă.
Pentru a determina răspunsul (primul element lipsă), este suficient să parcurgem vectorul de
frecvent̆ă si să aflăm prima poziţie cu frecvenţa 0.

Soluţie 100 de puncte:


Soluţia se construieşte folosind următoarele 3 optimizări:
1. Presupunem că avem vectorul de frecvenţă pentru o submatrice. În momentul ı̂n care mutăm
matricea la dreapta (de exemplu), vectorul de frecvenţă se schimbă cu foarte puţine elemente. Mai
exact, coloana din stânga dispare şi apare noua coloană din dreapta. În total, avem doar 2 * k
elemente pe care trebuie să le modificăm. Analog, când mutâm matricea ı̂n jos, avem 2 * l elemente
pe care trebuie să le modificăm.
Putem parcurge submatricele ı̂n următorul mod:

- pornesc cu matricea din colţul (1,1)


- matricea se mută din stânga ı̂n dreapta până ajunge ı̂n capăt
- coborâm matricea cu o unitate
- matricea se mută din dreapta ı̂n stânga până ajunge ı̂n capăt
- coborâm matricea cu o unitate
- revenim la pasul 2
CAPITOLUL 5. ONI 2018 5.2. MEXITATE 79

Observăm că mutarea la stânga-dreapta o facem de ordinul O(N * M), iar mutarea ı̂n jos
de ordinul O(N). Complexitatea până ı̂n acest punct devine O(N * M * K + N * L), ignorând
momentan partea ı̂n care trebuie să determinăm rapid mex-ul din vectorul de frecvenţă.
2. Avem restrictia N * M ¡= 400.000
=¿ K * L ¡= 400.000
=¿ min(K, L) ¡= sqrt(400.000)
Daca K ¡= L
=¿ k ¡= sqrt(400.000) = sqrt(VMAX)
=¿ complexitatea devine O(N * M * sqrt(VMAX) + N * L)
În schimb, dacă K ¿ L este nevoie să rotim matricea şi să aplicăm acelaşi procedeu, complexi-
tatea devenind O(N * M * L + M * K), adică O(N * M * sqrt(VMAX) + M * K).
Aplicând acest hibrid, complexitatea devine O(min(N * M * sqrt(VMAX) + N * L, N * M
*sqrt(VMAX) + M * K)). Din moment ce N * L respectiv M * K sunt irelevante, complexitatea
finală va fi: O(N * M * sqrt(VMAX)).
3. Rămâne de văzut cum putem determina rapid care este mex-ul cu ajutorul vectorului de
frecvenţă (din moment ce acesta este foarte mare, nu putem merge element cu element).
Aplicând primele 2 observaţii, deducem că avem O(N * M * sqrt(VMAX)) valori care se
modifică (update-uri), respectiv O(N * M) interogări de mex (query-uri).
Putem echilibra această complexitate dacă am avea o structură ce foloseste O(1) pe update
şi O(sqrt(VMAX)) pe query. Putem ţine bucăţi de dimensiune sqrt(VMAX) pe vectorul de
frecvenţă, fiecare bucată având marcat câte elemente apar cel puţin o dată. Vectorul de frecvenţă
şi informaţia pentru fiecare bucată de radical se pot menţine uşor la update ı̂n O(1). La query,
pentru fiecare bucată de radical putem să o sărim dacă toate elementele apar ı̂n acel interval, re-
spectiv să aplicam un brute-force dacă răspunsul este ı̂n interiorul acelui interval. Mai exact, sărim
elemente din radical ı̂n radical (vom face asta de maxim sqrt(VMAX) ori), respectiv căutam el-
ement cu element intr-un interval de lungime radical. Astfel, vom putea determina valoarea
mex-ului ı̂n maxim sqrt(VMAX) paşi.

5.2.2 Cod sursă

Listing 5.2.1: mexitate1.cpp


/* Eugenie Daniel Posdarascu
100 de puncte
*/
#include<stdio.h>
#include<vector>
#include<stdlib.h>
#include<time.h>

using namespace std;

#define MOD 1000000007


#define NMAX 400005

int n, m, k, l, f[NMAX], fb[NMAX];


int bucket = 100, indexB[NMAX];
long long answer = 1;
long long ans = 0;

vector<int> matrix[NMAX], matrix2[NMAX];

void deleteElements(int a, int b, int c, int d)


{
for(int i = a; i <= c; i++)
for(int j = b; j <= d; j++)
{
int value = matrix[i][j];
f[value]--;
if(!f[value])
fb[indexB[value]]--;
CAPITOLUL 5. ONI 2018 5.2. MEXITATE 80

}
}

void addElements(int a, int b, int c, int d)


{
for(int i = a; i <= c; i++)
for(int j = b; j <= d; j++)
{
int value = matrix[i][j];
f[value]++;
if(f[value] == 1)
fb[indexB[value]]++;
}
}

void query()
{
for(int buc = 1; ; buc++)
{
if(fb[buc] == bucket)
continue;
for(int i = (buc - 1) * bucket + 1; ; i++)
{
if(!f[i])
{
answer *= i;
ans += i;
if(answer >= MOD)
answer %= MOD;
break;
}
}
break;
}
}

void initializeaza()
{
addElements(1,1,k,l);
query();
}

int main ()
{
int value, sign;

srand(time(0));

freopen("mexitate.in","r",stdin);
freopen("mexitate.out","w",stdout);

scanf("%d%d%d%d",&n,&m,&k,&l);

for(int i = 1; i <= n; i++)


{
matrix2[i].push_back(0);
for(int j = 1; j <= m; j++)
{
scanf("%d",&value);
matrix2[i].push_back(value);
}
}
if(k > l)
{ // linia devine coloana // m linia 1 m - 1 2
for(int i = 1; i <= m; i++)
for(int j = 0; j <= n; j++)
matrix[i].push_back(0);

for(int i = 1; i <= n; i++)


for(int j = 1; j <= m; j++)
matrix[m - j + 1][i] = matrix2[i][j];

int aux = n;
n = m;
m = aux;
CAPITOLUL 5. ONI 2018 5.2. MEXITATE 81

aux = l;
l = k;
k = aux;
}
else
{
for(int i = 1; i <= n; i++)
{
matrix[i].push_back(0);
for(int j = 1; j <= m; j++)
{
matrix[i].push_back(matrix2[i][j]);
}
}
}

int last = 1;
for(int i = 1; i <= n * m; i++)
{
indexB[i] = last;
if(i % bucket == 0)
last++;
}

initializeaza();
for(int i = 1; i <= n - k + 1; i++)
{
sign = ((i&1) ? +1 : -1);
if(sign == 1)
{
for(int j = 2; j <= m - l + 1; j++)
{
deleteElements(i, j - 1, i + k - 1, j - 1);
addElements(i, j + l - 1, i + k - 1, j + l - 1);
query();
}

if(i <= n - k)
{
deleteElements(i, m - l + 1, i, m);
addElements(i + k, m - l + 1, i + k, m);
query();
}
}
else
{
for(int j = m - l; j >= 1; j--)
{
deleteElements(i, j + l, i + k - 1, j + l);
addElements(i, j, i + k - 1, j);
query();
}

if(i <= n - k)
{
deleteElements(i, 1, i, l);
addElements(i + k, 1, i + k, l);
query();
}
}
}

printf("%lld\n", answer);

return 0;
}

Listing 5.2.2: mexitate2.cpp


/*
Narcis Gemene
*/
#include <iostream>
#include <cstdio>
#include <vector>
CAPITOLUL 5. ONI 2018 5.2. MEXITATE 82

#include <cassert>

using namespace std;

const int MOD = 1000000007;

void Update(vector<int>&frecv, vector<int>&buckets, int bucket, int i, int val)


{
if (frecv[i] == 0)
buckets[i/bucket]++;
frecv[i] += val;
if (frecv[i] == 0)
buckets[i/bucket]--;
}

int Query(vector<int>&frecv,vector<int>&buckets, int bucket)


{
int i;
for(i = 0; buckets[i] == bucket; i++);
for(i = bucket*i;frecv[i] != 0; i++);
return i;
}

int main()
{
freopen("mexitate.in", "r", stdin);
freopen("mexitate.out", "w", stdout);

int n, m, k, l;

cin >> n >> m >> k >> l;


assert(1 <= n*m && n*m <= 400000);

vector<vector<int>>a(n, vector<int>(m,0));
vector<int>frecv(n * m + 5), buckets(n * m + 5);

int bucket = 1;
for(;bucket*bucket < n*m ;bucket++);

Update(frecv, buckets, bucket, 0, 1);

for(auto&line: a)
for(auto &elem: line)
{
cin >> elem;
assert(1 <= elem && elem <= n * m);
}

if(k > l)
{
vector<vector<int>> b = a;

swap(n, m);
swap(k, l);

a = vector<vector<int>>(n, vector<int>(m,0));

for (int i = 0; i < n; i++)


for (int j = 0; j < m; j++)
a[i][j] = b[j][i];
}

for(int i = 0; i < k; i++)


{
for (int j = 0;j < l;j ++)
{
Update(frecv, buckets, bucket, a[i][j], 1);
}
}

const int left[] = {0, m - 1};


const int right[] = {m, -1};
long long sol = 1;

auto mat = vector<vector<int>>(n, vector<int>(m,0));


CAPITOLUL 5. ONI 2018 5.3. PLAJA 83

for(int i = 0; i + k - 1 < n; i++)


{
int dir = 1;
if (i&1)
dir = -1;

for(int j = left[i&1]; j + dir * (l - 1) != right[i&1]; j += dir)


{
int mex = Query(frecv, buckets, bucket);

mat[i][min(j, j + dir * (l - 1))] = mex;


sol = 1LL*sol*mex%MOD;

int nextCol = j + dir * l;

if(0 <= nextCol && nextCol < m)


{
for(int step = 0; step < k; step++)
{
Update(frecv, buckets, bucket, a[i+step][j], -1);
Update(frecv, buckets, bucket, a[i+step][nextCol], 1);
}
}
}

if (i + k < n)
{
for(int step = 0 ; step < l; step++)
{
int col = left[(i & 1) xor 1] + (-1) *dir * step;
Update(frecv, buckets, bucket, a[i][col], -1);
Update(frecv, buckets, bucket, a[i + k][col], 1);
}
}
}

cout << sol <<"\n";


return 0;
}

5.2.3 *Rezolvare detaliată

5.3 plaja
Problema 3 - plaja 100 de puncte
Zizi ı̂şi va petrece concediul ı̂n această vară ı̂ntr-o frumoasă staţiune de la Marea Neagră. Acolo
va sta N zile. Zilele sunt numerotate de la 1 la N . În fiecare dintre cele N zile de concediu, ea
intenţionează să facă plajă un număr cât mai mare de unităţi de timp. Va trebui să ţină seama
totuşi de prognoza meteo, care este nefavorabilă ı̂n K dintre cele N zile, respectiv ı̂n zilele z1 , z2 ,
..., zk . În fiecare dintre aceste K zile va ploua sau va fi prea mult soare, iar Zizi va trebui să-şi
limiteze timpii de plajă la cel mult t1 , t2 , ..., tk unităţi de timp.
Deasemenea, din motive de confort fizic, Zizi doreşte ca diferenţa ı̂n valoare absolută a timpilor
de plajă ı̂ntre oricare două zile consecutive să nu depăşească T .

Cerinţe

Cunoscând zilele z1 , z2 , ..., zk ı̂n care există limitările t1 , t2 , ..., tk pentru timpul de plajă şi
valoarea T , să se determine numărul maxim de unităţi de timp pe care Zizi le poate petrece la
plajă ı̂ntr-o singură zi dintre cele N zile de concediu.

Date de intrare

Fişierul plaja.in conţine pe prima linie trei numere naturale N K şi T separate printr-un
spaţiu, reprezentând numărul de zile de concediu, numărul de zile ı̂n care există limitări pentru
timpul de plajă şi diferenţa maximă admisă a timpilor de plajă pentru oricare două zile consecutive.
CAPITOLUL 5. ONI 2018 5.3. PLAJA 84

Pe fiecare dintre următoarele K linii se află câte două numere z şi t, despărţite printr-un spaţiu,
reprezentând numărul de ordine al unei zile cu limitări pentru timpul de plajă, respectiv limita
de timp pentru ziua respectivă.
Valorile z1 , z2 , ..., zk sunt ı̂n ordine strict crescătoare.

Date de ieşire

Fişierul plaja.out va conţine pe prima linie un singur număr natural tmax, reprezentând
numărul maxim de unităţi de timp pe care Zizi le poate petrece făcând plajă ı̂ntr-una din cele N
zile de concediu.

Restricţii şi precizări

a 1 & N & 1 000 000 000


a 1 & K & 100 000
a 1 & t1 , t2 , ..., tk & 100 000
a 1 & z1 $ z2 $ ... $ zk & N
a 2 & T & 1 000 000
a Pentru 20% din punctajul total există teste cu 1 & N, K & 1 000
a Pentru 65% din punctajul total există teste cu 1 & N, K & 100 000

Exemple:

plaja.in plaja.out Explicaţii


313 8 În ziua 1 timpul de plajă este limitat la 2 unităţi de timp. În
12 ziua a doua timpul maxim de plajă este 2 + 3, iar ı̂n ziua a
treia, timpul maxim este 2 + 3 + 3 = 8 unităţi de timp.
5 2 11 16 În ziua 2 timpul de plajă este limitat la 2 unităţi de timp, iar ı̂n
22 zilele 1 şi 3 nu există limitare. Atunci timpul maxim de plajă
45 pentru zilele 1 sau 3 este 2 + 11 = 13.
În ziua 4 timpul de plajă este limitat la 5 unităţi de timp, iar ı̂n
ziua 5 nu există limitare. Deci ı̂n ziua 5 timpul maxim de plajă
este 5 + 11 = 16.
Timp maxim de executare/test: 1.0 secunde
Memorie: total 128 MB din care pentru stivă 32 MB
Dimensiune maximă a sursei: 10 KB

5.3.1 Indicaţii de rezolvare

prof. Constantin Gălăţan - C. N. ”Liviu Rebreanu” Bistriţa

Soluţie O(N * log N * log T)


Răspunsul se caută binar. Fie t0 o valoare de test ı̂n căutarea binară. Pentru fiecare zi d[i]
dintre cele cu limită superioară pentru timpul de plajă, se reţine un interval de forma [d[i] - x, d[i]
+ x], astfel ı̂ncât pentru orice zi din acest interval, nu este posibil să se atingă timpul t0, datorită
restricţiei T din enunţul problemei.
x = (t0 - t[i] - 1) este distanţa ı̂n zile dintre ziua d[i] şi cea mai ı̂ndepărtată zi pentru care nu
se poate atinge valoarea T. Avem: x = (t0 - t[i] - 1) / T.
Cele K intervale conţin doar zile pentru care nu se poate ajunge la valoarea t0. Aceste intervale
se pot intersecta sau nu.
Dacă exista cel puţin o zi neacoperită de nici un interval, atunci valoarea T poate fi atinsă
sau eventual depăşită. Capetele intervalelor se pot reţine ı̂ntr-un şir cu valorile +1 pentru capătul
stânga, respectiv -1 pentru cel din dreapta. Se ordonează şirul şi se ı̂nsumează valorile. În
momentul ı̂n care se atinge valoarea 0, suntem ı̂n afara oricărui interval dintre cele de mai sus.
Punctaj acordat: aproximativ 90 de puncte.
Soluţie O(N * log T)
Se procedează ca mai sus, doar că pentru determinarea unei sume parţiale de valoare 0, se pot
face updateuri cu ajutorul unui algoritm de tip ”mars”. Dezavantajul metodei este că necesită
CAPITOLUL 5. ONI 2018 5.3. PLAJA 85

un şir de dimensiune N , ceea ce duce la depăşire de memorie pentru testele cu N foarte mare.
Punctaj acordat: aproximativ 65 de puncte.
Soluţie O(K * log T)
Se observă că parcurgând zilele cu limitări pentru timpul de plajă, este posibil să găsim o zi
d[i] cu restricţia de timp t[i], care face imposibil faptul ca ı̂n ziua d[i + 1] să se atingă valoarea t[i
+ 1].
Astfel, vom parcurge cele K zile cu restricţii şi pentru fiecare valoare t[i] vom reduce valoarea
t[i + 1] la o nouă valoare posibil a fi atinsă. Apoi acelaşi lucru se va face pornind de la sfârşitul
şirului t către ı̂nceputul său.
În etapa următoare se poate căuta binar răspunsul pentru fiecare pereche de zile de forma (d[i],
d[i + 1]), iar verificarea posibilităţii de a se atinge o valoare propusă t0, de face printr-o testare
relativ simplă:
d[i + 1] - d[i] ¿= (t0 - t[i] + T - 1) / T + (t0 - t[i + 1] + T - 1) / T;
Punctaj acordat: 100 de puncte
Solutie O(k) (Balţatu Andrei-Mircea)
Iniţial se iau cele k zile restricţionate şi se aduc la valorile maximale posibile, adică pentru o
zi 1 ¡= i ¡= m cu valoarea initială t(i) şi pozitia x(i), dacă există o altă zi j şi t(j) + T * abs(x(i)
- x(j)) ¡ t(i), atunci t(i) se scade deoarece este restricţionat de alte zile. (abs - funcţia modul).
Apoi observăm că putem afla răspunsul ı̂ntre oricare două zile consecutive ı̂n O(1). Având cele
două zile (X - ı̂n stanga, Y - ı̂n dreapta), ştim că fiecare va creşte cu T ı̂n drumul spre ziua maximă.
Astfel, aducem pe minimul dintre ele, adică min(t(X), t(Y)) la un număr nr ¡= max(t(X), t(Y))
cât mai mare, după care ambele ı̂ncep să crească cu T alternativ, spre o pozitia P necunoscută.
În funcţie de paritatea secventei de zile rămase după ce am crescut minimul dintre ele, putem
afla poziţia de valoare maximă (P) ı̂n funcţie de paritatea secvenţei de zile rămase. Punctaj: 100
de puncte

5.3.2 Cod sursă

Listing 5.3.1: plaja N+K.cpp


/*
Narcis Gemene
O(N + K)
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <cassert>

using namespace std;

const int KMAX = 1000005;


long long z[KMAX];
long long timp[KMAX];

int main(int argc, const char * argv[])


{
freopen("plaja.in", "r", stdin);
freopen("plaja.out", "w", stdout);

int n, k, T;

cin >> n >> k >> T;


for (int i = 1; i<= k; i++)
cin >> z[i] >> timp[i];

for(int i = 2;i <= k; i++)


timp[i] = min(timp[i], timp[i-1] + T*(z[i] - z[i-1]));

for(int i = k-1;i >= 1; i--)


timp[i] = min(timp[i], timp[i+1] + T*(z[i+1] - z[i]));

long long ans = max(1LL*timp[1]+T*(z[1]-1), 1LL*timp[k]+T*(n-z[k]));


CAPITOLUL 5. ONI 2018 5.3. PLAJA 86

for (int i = 2; i <= k; i++)


{
for(int j = z[i-1]; j <= z[i]; j++)
ans = max(ans,
min(1LL*timp[i-1] + 1LL*T*(j-z[i-1]),
1LL*timp[i] + 1LL*T*(z[i] - j)));
}

cout << ans << "\n";


return 0;
}

Listing 5.3.2: plaja ternara.cpp


/*
Narcis Gemene
cautare ternara
*/
#include <iostream>
#include <cstdio>
#include <vector>
#include <cassert>

using namespace std;

const int KMAX = 1000005;


long long z[KMAX];
long long timp[KMAX], T;

long long F(int x, int i)


{
return min(1LL*timp[i-1] + 1LL*T*(x-z[i-1]), timp[i] + 1LL*T*(z[i]-x));
}

int main()
{
freopen("plaja.in", "r", stdin);
freopen("plaja.out", "w", stdout);

int n, k;
cin >> n >> k >> T;
for (int i = 1; i<= k; i++)
cin >> z[i] >> timp[i];

for(int i = 2;i <= k; i++)


timp[i] = min(timp[i], timp[i-1] + T*(z[i] - z[i-1]));

for(int i = k-1;i >= 1; i--)


timp[i] = min(timp[i], timp[i+1] + T*(z[i+1] - z[i]));

long long ans = max(1LL*timp[1]+T*(z[1]-1), 1LL*timp[k]+T*(n-z[k]));

for (int i = 2; i <= k; i++)


{
int l = z[i-1];
int r = z[i];
while(r - l > 3)
{
int mid1 = l + (r-l)/3;
int mid2 = r - (r-l)/3;
if(F(mid1, i) > F(mid2, i))
r = mid2;
else
l = mid1;
}

for(; l <= r; l++)


ans = max(ans, F(l, i));
}

cout << ans << "\n";


return 0;
}
CAPITOLUL 5. ONI 2018 5.3. PLAJA 87

Listing 5.3.3: plaja1.cpp


/*
Andrei Baltatu
Solutie O(K)
*/

#include <bits/stdc++.h>
#define int long long

using namespace std;

struct point_wall
{
int pos, val;
point_wall(int _pos, int _val)
{
pos = _pos;
val = _val;
}
};

int n, k, t;
vector<point_wall> restr;

vector<point_wall> Normalize(vector<point_wall> A)
{
vector<point_wall> res(A.begin(), A.end());

// from left
point_wall mn = A.front();
for (int i = 0; i < A.size(); i++)
{
// check if present element is a better restriction
if (1LL * t * abs(A[i].pos - mn.pos) + mn.val > A[i].val)
mn = A[i];

// change element on this position in result


res[i].val = min(res[i].val,
1LL * t * abs(res[i].pos - mn.pos) + mn.val);
}

// from right
mn = A.back();
for (int i = A.size() - 1; i >= 0; i--)
{
// check if present element is a better restriction
if (1LL * t * abs(A[i].pos - mn.pos) + mn.val > A[i].val)
mn = A[i];

// change element on this position in result


res[i].val = min(res[i].val,
1LL * t * abs(res[i].pos - mn.pos) + mn.val);
}

return res;
}

int Local_maxima(point_wall x, point_wall y)


{
int mx = max(x.val, y. val);

// now look between the walls


if (y.val < x.val) swap(x, y);

int dif = abs(y.pos - x.pos) - 1;


int steps = (y.val - x.val) / t;

if (!dif || dif < steps) return mx;

dif -= steps;
x.val += 1LL * steps * t;

if (!dif) return mx;


if (dif & 1) // odd
{
CAPITOLUL 5. ONI 2018 5.3. PLAJA 88

int length = (dif / 2) + 1;


mx = max(mx, x.val + 1LL * length * t);
}
else
{
int length = dif / 2;
mx = max(mx, y.val + 1LL * length * t);
}

return mx;
}

int32_t main()
{
freopen("plaja.in", "r", stdin);
freopen("plaja.out", "w", stdout);

cin.sync_with_stdio(false);

cin >> n >> k >> t;


for (int i = 1; i <= k; i++)
{
int x, y;
cin >> x >> y;
restr.push_back(point_wall(x, y));
}

if (restr.back().pos < n)
restr.push_back(point_wall(n, 1LL * n * t + (1 << 30)));

if (restr.front().pos > 1)
restr.insert(restr.begin(), point_wall(1, 1LL * n * t + (1 << 30)));

restr = Normalize(restr);

int mx = 0;
for (int i = 0; i < restr.size() - 1; i++)
mx = max(mx, Local_maxima(restr[i], restr[i + 1]));

cout << mx << "\n";


return 0;
}

Listing 5.3.4: plaja2.cpp


/*
prof. Constantin Galatan
solutie O(K * log T)
*/
#include <fstream>
#include <vector>
#include <deque>
#include <algorithm>

using namespace std;

ifstream fin("plaja.in");
ofstream fout("plaja.out");

using LL = long long;


using VLL = vector<LL>;

int N, K, T;
VLL d, t;

bool Try(LL t0, int i);

int main()
{
fin >> N >> K >> T;
d = t = VLL(K + 1);

for (int i = 1; i <= K; ++i)


fin >> d[i] >> t[i];
CAPITOLUL 5. ONI 2018 5.3. PLAJA 89

for (int i = 1; i < K; ++i)


t[i + 1] = min(t[i + 1], t[i] + (d[i + 1] - d[i]) * T);

for (int i = K - 1; i >= 1; --i)


t[i] = min(t[i], t[i + 1] + (d[i + 1] - d[i]) * T);

LL res = 0, l, r, m, tmp;

for (int i = 1; i < K; ++i)


{
l = max(t[i], t[i + 1]), r = 1LL * N * T;
while (l <= r)
{
m = (l + r) / 2;
if (Try(m, i))
{
l = m + 1;
tmp = m;
}
else
r = m - 1;
}
res = max(res, tmp);
}
res = max(res, max(t[K] + (N - d[K]) * T, t[1] + (d[1] - 1) * T));
fout << res;

bool Try(LL t0, int i)


{
return d[i+1] - d[i] >= (t0 - t[i] + T-1) / T + (t0 - t[i+1] + T-1) / T;
}

Listing 5.3.5: plaja3.cpp


// O(n * log n * log T)
#include <fstream>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

ifstream fin("plaja.in");
ofstream fout("plaja.out");

struct Entry
{
int day, sgn;
bool operator < (const Entry& e) const
{
return day < e.day || ( day == e.day && sgn > e.sgn);
}
};

int N, K, T;
vector<long long> d, t;

bool Try(long long H);

int main()
{
fin >> N >> K >> T;
int day, time;

for (int i = 0; i < K; ++i)


{
fin >> day >> time;
d.push_back(day);
t.push_back(time);
}

long long l = 0, r = (1LL << 35), m;


long long res = 0;
CAPITOLUL 5. ONI 2018 5.3. PLAJA 90

while (l <= r)
{
m = (l + r) / 2;
if (Try(m))
{
res = m;
l = m + 1;
}
else
r = m - 1;
}

fout << res;


}

bool Try(long long t0)


{
vector <Entry> e;

e.push_back( {1, 0} );
e.push_back({N + 1, -1});

int a, b;
for (int i = 0; i < K; i++)
if (t[i] < t0)
{
long long x = (t0 - t[i] - 1) / T;
a = max(d[i] - x, 1LL);
b = min(d[i] + x + 1, (long long)(N + 1));

e.push_back({a, 1});
e.push_back({b, -1});
}

sort(e.begin(), e.end());

long long cnt = 0;


for (int i = 0; i < e.size(); i++)
{
cnt += e[i].sgn;
if (cnt <= 0 && e[i].day <= N)
return true;
}

return false;
}

Listing 5.3.6: plaja4.cpp


/*
prof. Constantin Galatan
solutie O(N * log T)
*/
#include <fstream>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

ifstream fin("plaja.in");
ofstream fout("plaja.out");

int N, K, T;
vector<long long> d, t;

bool Try(long long H);

int main()
{
fin >> N >> K >> T;
int day, time;
for (int i = 0; i < K; ++i)
{
CAPITOLUL 5. ONI 2018 5.4. BSREC 91

fin >> day >> time;


d.push_back(day);
t.push_back(time);
}

long long l = 0, r = (1LL * N * T), m;


long long res = 0;

while (l <= r)
{
m = (l + r) / 2;
if (Try(m))
{
res = m;
l = m + 1;
}
else
r = m - 1;
}

fout << res;


}

bool Try(long long t0)


{
vector <int> e(N + 2);
e[1] = 0;
e[N + 1] = -1;
int a, b;

for (int i = 0; i < K; i++)


if (t[i] < t0)
{
long long x = (t0 - t[i] - 1) / T;
a = max(d[i] - x, 1LL);
b = min(d[i] + x + 1, (long long)(N + 1));

e[a]++;
e[b]--;
}

long long cnt = 0;


for (int i = 1; i <= N; i++)
{
cnt += e[i];
if (cnt <= 0)
return true;
}
return false;
}

5.3.3 *Rezolvare detaliată

5.4 bsrec
Problema 4 - bsrec 100 de puncte
Fie un vector v sortat crescător cu N elemente naturale nenule
distincte pe care nu le cunoaştem, dar pe care ne propunem să le
determinăm.
Având la dispoziţie acest vector v, cu ajutorul următorului algo-
ritm de căutare binară (vezi Figura 1) putem răspunde la query-uri
de forma:
”Dându-se un număr X şi un interval a, b se cere să se determine
cel mai mic element mai mare decât X aflat ı̂n intervalul determinat
de indicii a şi b, interval din vectorul v.”
Se cunosc paşii pe care algoritmul de cautare binară i-a urmat
pentru diferite valori ale tripletului X, a, b.
CAPITOLUL 5. ONI 2018 5.4. BSREC 92

Cerinţe

Dându-se N (lungimea vectorului), Q (numărul de query-uri apelate) şi cele Q query-uri, să
se determine vectorul iniţial. Dacă există mai multe soluţii se va afişa soluţia minim lexicografică.
Dacă nu există soluţie se va afişa valoarea -1.
Un vector V 1 se consideră mai mic lexicografic decât un alt vector V 2 dacă există un indice i
astfel ı̂ncât: V 11 V 21, V 12 V 22, ..., V 1i  1 V 2i  1 şi V 1i $ V 2i.
Cele Q query-uri sunt formate din:

a X - valoarea pe care o căutăm binar ı̂n vector


a M - numărul de iteraţii ı̂n algoritmul de căutare binară
a a, b - intervalul ı̂n care se aplică algoritmul de căutare binară (perechea (a, b) este consid-
erată prima iteraţie ı̂n algoritm)
a M  1 perechi de indici reprezentând valorile afişate de algoritmul de căutare binară ı̂n urma
fiecărei iteraţii

Date de intrare

Fişierul bsrec.in conţine pe prima linie o valoare T reprezentând numărul de teste ce vor fi
efectuate.
Pentru fiecare din cele T teste se va citi de pe prima linie valoarea N (numărul de elemente
ale vectorului), respectiv Q (numărul de query-uri), despărţite prin câte un spaţiu.
Următoarele linii descriu cele Q query-uri.
În cadrul unui query, prima linie va conţine o pereche de numere (X, M ) despărţite printr-un
spaţiu, unde X reprezintă valoarea căutată ı̂n vector, iar M numărul de paşi efectuaţi ı̂n căutarea
binară a celei mai mici valori din vector care este mai mare decât X.
Pe următoarea linie a query-ului se găseşte perechea de valori (a, b) având semnificaţia de mai
sus.
Următoarele M  1 linii conţin perechi (st, dr) de valori naturale despărţite prin câte un
spaţiu care reprezintă indicii stânga, respectiv dreapta ce sunt afişaţi ı̂n urma fiecărei iteraţii a
algoritmului de mai sus.

Date de ieşire

Fişierul bsrec.out va conţine T linii, linia i conţinând răspunsul pentru testul i. Dacă testul
admite soluţie, se vor afişa N numere naturale nenule strict crescătoare ce vor reprezenta valorile
vectorului v.
Deoarece există mai multe soluţii, se va afişa soluţia minim lexicografică. Dacă testul NU
admite soluţie, se va afişa -1.

Restricţii şi precizări

a 1 & T & 10
a 1 & N, Q & 1000
a 1 & X & 1 000 000 000
a 1 & st & dr & N , cu excepţia ultimei perechi din căutarea binară unde st % dr
a Pentru 20% din punctajul total există teste cu 1 & N, Q & 10 şi soluţia minim lexicografică
admite valori până ı̂n 20
a Se garantează că M & 15

Exemple:

bsrec.in bsrec.out Explicaţii


CAPITOLUL 5. ONI 2018 5.4. BSREC 93

2 1 3 4 25 26 Fişierul conţine 2 teste.


53 -1 Primul test are 3 query-uri:
34 a Primul query caută binar valoarea 3 ı̂n intervalul [1, 5] şi face
15 4 iteraţii
12 a Cel de al doilea query caută binar valoarea 30 pe intervalul
22 [2, 4] şi face 3 iteraţii
21 a Cel de al treilea query caută binar valoarea 25 pe intervalul
30 3 [4, 5] şi face 2 iteraţii
24 Cel de al doilea test are 3 query-uri, dar NU admite soluţie.
44
54
25 2
45
43
53
30 4
15
12
22
21
33
24
44
54
52
45
43

Timp maxim de executare/test: 0.3 secunde


Memorie: total 128 MB din care pentru stivă 32 MB
Dimensiune maximă a sursei: 10 KB

5.4.1 Indicaţii de rezolvare

Posdarascu Eugenie Daniel - Universitatea din Bucureşti

Parcurgem toate iteraţiile căutarilor binare. Pentru o valoare X (pe care o căutam binar) şi
o pereche (lef t, right) din căutarea binară se poate deduce o restricţionare de valoare pentru
elementul din mijloc mid lef t  right©2, ı̂n funcţie de alegerea făcută de iteraţia următoare.
Mai exact, pentru o pereche (lef t, right) urmată de o pereche (lef t2, right2) avem următoarele
2 cazuri:
1. left == left2 şi right2 ¡ right: acest caz presupune că conditia v[mid] ¡ X este falsă, deci
v[mid] ¿= x
2. left ¡ left2 şi right2 == right: cazul presupune faptul că conḑitia v[mid] ¡ x este adevarată,
deci v[mid] ¡= x - 1
În urma tuturor acestor relaţii, putem deduce că pentru fiecare element v[i] din vector (i de
la 1 la N ), această valoare este mărginită inferior de o valoare downi, respectiv superior de o
valoare upi. Dacă elementul nu este mărginit, downi este automat 0, iar upi = INF.
Ultima restricţie pe care trebuie să o impunem este faptul că v[i - 1] ¡ v[i], pentru orice i de la
2 la N.
Pentru a determina soluţia minimă lexicografic, aplicăm o strategie Greedy parcurgând vectorul
de la stânga la dreapta. Presupunând că am calculat deja soli, soluţia pentru elementul i,
soli  1 va fi maximul dintre soli  1 şi downi  1. La final, rămâne de verificat dacă soli
¡= upi pentru fiecare i de la 1 la N . Dacă toate restricţiile se respectă, avem soluţie şi o afisăm.
Altfel, răspunsul e -1.

5.4.2 Cod sursă


CAPITOLUL 5. ONI 2018 5.4. BSREC 94

Listing 5.4.1: bsrec.cpp


#include<stdio.h>
#include<algorithm>

using namespace std;

#define NMAX 1005


#define INF 1000000007

int tests, up[NMAX], down[NMAX], n, q;


int left[NMAX], right[NMAX], sol[NMAX];

void readAndRestrict()
{
int value, steps;

scanf("%d%d", &value, &steps);


for(int i = 1; i <= steps; i++)
{
scanf("%d%d", &left[i], &right[i]);
}

for(int i = 1; i < steps; i++)


{
if(left[i] == left[i + 1])
down[right[i + 1] + 1] = max(down[right[i + 1] + 1], value);
else
up[left[i + 1] - 1] = min(up[left[i + 1] - 1], value - 1);
}
}

void setRestrictions()
{
for(int i = 1; i <= n; i++)
{
down[i] = 0;
up[i] = INF;
}
}

int main ()
{

freopen("bsrec.in","r",stdin);
freopen("bsrec.out","w",stdout);

scanf("%d",&tests);
for(; tests; tests--)
{
scanf("%d%d",&n,&q);
setRestrictions();

for(int i = 1; i <= q; i++)


{
readAndRestrict();
}

int have_sol = 1;
for(int i = 1; i <= n && have_sol; i++)
{
sol[i] = max(sol[i - 1] + 1, down[i]);
if(sol[i] > up[i]) {
have_sol = 0;
}
}

if(!have_sol)
{
printf("-1\n");
}
else
{
for(int i = 1; i <= n; i++)
printf("%d ", sol[i]);
printf("\n");
CAPITOLUL 5. ONI 2018 5.4. BSREC 95

}
}

return 0;
}

Listing 5.4.2: bsrec2.cpp


#include <fstream>
#include <cstdio>
#include <vector>
#include <cassert>

using namespace std;

int main()
{
ifstream fin("bsrec.in");
ofstream fout("bsrec.out");

int t;
fin >> t;

while(t--)
{
int n, q;
fin >> n >> q;

vector<int> maxx(n + 1, 1<<30);


vector<int> minn(n + 1, 1);

while(q--> 0)
{
int x, mid, m;
fin >> x >> m;
for(int i = 1;i <= m;i++)
{
int l, r;
fin >> l >> r;
if (i > 1)
{
if(l == mid+1)
{
maxx[mid] = min(maxx[mid], x - 1);
}
else
{
minn[mid] = max(minn[mid], x);
}
}

mid = (l+r)/2;
}
}

vector<int>sol;
int x = 0;

for(int i = 1; i <= n;i++)


{
x = max(minn[i], x + 1);
if(x > maxx[i])
break;
sol.push_back(x);
}

if(sol.size() == n)
{
for(auto x: sol)
fout << x << " ";
fout <<"\n";
}
else
fout << "-1\n";
}
CAPITOLUL 5. ONI 2018 5.5. NUMINUM 96

return 0;
}

5.4.3 *Rezolvare detaliată

5.5 numinum
Problema 5 - numinum 100 de puncte
Se consideră următoarea structură de date:

a ı̂n vârful structurii se găseşte fracţia 11 .


p
a Din fiecare vârf ı̂n care se găseşte fracţia q
se formează alte două fracţii trasând câte 2
segmente de dreaptă astfel: către stânga fracţia p
pq
şi către dreapta fracţia pq .
q

Cerinţe
Cunoscând numărătorul, respectiv numitorul a două fracţii ireductibile diferite din struc-
tură, determinaţi numărul minim de segmente de dreaptă cu care putem conecta ı̂n structura
dată, cele două fracţii.
Date de intrare
Pe prima linie a fişierului de intrare numinum.in se găseşte un număr natural N .
Pe fiecare dintre următoarele N linii se găsesc câte 4 numere naturale xi , yi , ai , bi , 1 & i & N ,
despărţite prin câte un spaţiu unde xi , yi reprezintă numărătorul, respectiv numitorul primei
fracţii de pe linia i  1, iar ai , bi reprezintă numărătorul, respectiv numitorul celei de-a doua
fracţii de pe linia i  1.
Date de ieşire
Fişierul de ieşire numinum.out va conţine N linii. Pe linia i se va scrie numărul minim de
segmente de dreaptă necesare pentru a conecta, pe structura dată, fracţia xyi cu fracţia ab i .
i i

Restricţii şi precizări


a 1 & N & 10 000
a 1 & xi , yi , ai , bi & 1 000 000 000, 1 & i & N

Exemple:
numinum.in numinum.out Explicaţii
1 6 N=1
4325 x1 4, y1 3; a1 2, b1 5.
Pentru a conecta fracţia 34 cu fracţia 52 avem nevoie de minim 6
segmente, după cum urmează:
4/3 1/3 1/2 1/1 2/1 2/3 2/5
Timp maxim de executare/test: 0.2 secunde
Memorie: total 128 MB din care pentru stivă 32 MB
Dimensiune maximă a sursei: 10 KB
CAPITOLUL 5. ONI 2018 5.5. NUMINUM 97

5.5.1 Indicaţii de rezolvare

prof. Ciprian Cheşcă - Liceul Tehnologic ”Grigore C. Moisil” Buzău

Soluţia 1 O max a1 , b1 ©min a1 , b1   max a2 , b2 ©min a2 , b2 


Pentru fiecare dintre cele două fracţii se determină o secvenţă de 1 şi 0 obţinută prin aplicarea
algoritmului lui Euclid prin scăderi repetate ı̂ntre numărător şi numitor.
Spre exemplu, pentru fracţia 3/4 se efectuează operaţiile 3/4 3/1 2/1 1/1 şi se va
obţine secvenţa 011.
Se determină, de la dreapta la stânga, subsecvenţa comună celor două secvenţe determinate
anterior şi se calculează suma numărului de cifre din subsecvenţele necomune.
Soluţia are dezavantajul că necesită multă memorie, având ı̂n vedere că arborele poate avea
9
10 nivele. Gestionând atent memoria disponibilă, se pot obţine aproximativ 40 puncte.

Soluţia 2 O log min a1, b1  log min a2, b2


Pentru a avea un model vizual asociat acestei soluţii se completează pentru ı̂nceput structura
cu fracţia 0/1 aşezată deasupra fracţiei 1/1 şi se consideră că se ajunge de la fracţia 0/1 la fracţia
1/1 plecând către dreapta.
Se aplică algoritmul lui Euclid prin ı̂mpărţiri repetate şi se construieşte o secvenţă de numere
27
formate din câturile obţinute la fiecare pas al aplicării algoritmului Euclid .
Semnificaţia unei astfel de secvenţe este următoarea:
citită de la sfârşit câtre ı̂nceput secvenţa arată drumul (şi lungimea) spre o anumită fracţie.
De exemplu pentru fracţia 5/3 se obţine secvenţa [1,1,2] care arată că se porneşte din 0/1, se
parcurg două segmente către dreapta apoi unul către stânga şi apoi unul către dreapta şi ajungem
la fracţia 5/3.
Dacă se trasează o axă de simetrie (virtual) prin fracţia 1/1 şi se ı̂mparte structura ı̂n două
zone similare dar ı̂n care fracţiile sunt răsturnate, se poate observa că toate fracţiile din stânga
vor avea ı̂n secvenţă un număr par de termeni, iar cele din dreapta un număr impar de termeni.
În cazul ı̂n care o secvenţă conţine un număr par de termeni se va adăuga un termen de 1
la sfărşitul secvenţei şi se va decrementa ultimul termen. Această operaţie nu modifică lungimea
drumului dar permite realizarea unei diferenţe ı̂ntre drumurile ce pleacă din 0/1 ı̂n 1/1 şi apoi
merg către dreapta structurii, faţă de cele care pleacă din 0/1 ı̂n 1/1 şi apoi merg către stânga
structurii.
Ca şi ı̂n soluţia anterioară se determină apoi subsecvenţa comună a secvenţelor obţinute pentru
cele două fracţii şi se calculează suma elementelor din subsecvenţele necomune.
Spre exemplu pentru fracţiile 2/5 şi 5/3 secvenţele asociate asociate sunt: [0,2,2] şi respectiv
[1,1,2]. Analizate de la dreapta la stânga se observă că cele două secvenţe au ı̂n comun 2 şi apoi
ı̂ncă un element. Suma elementelor din subsecvenţele necomune este 0 + 1 (din prima fracţie) şi
1 (din a doua fracţie), deci ı̂n total 2.
Această soluţie obţine 100 puncte.

Soluţia 3: student Daniel Posdărăscu, Universitatea Bucureşti


Observăm că pentru orice pereche (a,b), părintele acestei perechi este o pereche (c,d), pereche
care se determină ı̂n urma aplicării algoritmului de cel mai mic divizor comun prin scăderi repetate.
O soluţie naivă ı̂n O N  (ı̂n O(rezultat) mai exact) presupune ca la fiecare moment să selectăm
perechea cu una din cele 2 valori maxime şi aplicarea scăderii acelei perechi.
De exemplu:
Dacă avem 2 perechi: (2, 17) şi (13, 25)
Pasul 1: 25 este valoarea maximă deci scădem 13 din 25.
Perechea devine (13,12).
Pasul 2: 17 este valoarea maximă deci scădem 2 din 17.
Perechea devine (2,15)
etc.
În momentul ı̂n care perechile devin egale, ne oprim.
După cum bine ştim, o optimizare a algoritmului de cmmdc prin scăderi repetate este algoritmul
lui Euclid prin ı̂mpărţiri care face O log  paşi.
Din păcate, ı̂n cazul acestei probleme, aplicarea algoritmului lui Euclid poate să sară peste
”punctul de ı̂ntâlnire” a celor 2 perechi.
27
ı̂n matematică o astfel de secvenţă poartă denumirea de fracţie continuă
CAPITOLUL 5. ONI 2018 5.5. NUMINUM 98

Ce putem deduce ı̂n schimb este faptul că dacă am determina prima pereche din cele comune
celor 2 perechi, aceasta pereche ar fi o pereche care se poate determina ”ı̂n mod direct” din punctul
de ı̂ntâlnire.
Definim că dintr-o pereche (a,b) putem ajunge ”ı̂n mod direct” ı̂n altă pereche (c,d) dacă
putem aplica algoritmul de scăderi repetate doar pentru una din variabile (ori a = c şi modificăm
doar b, ori b = d şi modificam doar a).
Astfel, pornind de la 2 perechi (a,b) şi (c,d), am redus problema la alte 2 perechi (a2, b2) şi
(c2, d2), perechi care se pot ı̂ntâlni ”ı̂n mod direct” ı̂ntr-un punct comun. Din acest punct, putem
determina ı̂n O 1 distanţa dintre cele 2 perechi aplicând formule simple ı̂n funcţie de cazuri.
Exemplu de caz:
A2 ¡ B2, C2 ¿ D2 - din acest caz deducem că prin scăderi repetate A2 rămâne pe loc ı̂n timp
ce B2 scade cu căte A2, respectiv D2 rămâne pe loc şi C2 scade cu câte D2.
Din moment ce ştim că aceste perechi se vor ı̂ntâlni ı̂ntr-un punct comun avem relaţiile:
a B2 - k1 * A2 = D2
a C2 - k2 * D2 = A2
Din aceste 2 relaţii putem determina k1 (distanţa primei perechi până ı̂n punctul comun)
respectiv k2 (distanţa celei de a doua perechi până ı̂n punctul comun. Distanţa ı̂ntre cele 2
perechi este desigur k1 + k2.
Restul cazurilor rămâne tema de gandire!

Soluţia 4: prof. Marcel Drăgan - Colegiul Naţional ”Samuel Von Brukenthal”, Sibiu
Pentru complexitate liniară folosim scăderi repetate asupra ambelor fracţii parcurgând astfel
structura ı̂napoi spre vârf. La fiecare pas transformăm fracţia ı̂n componenta căreia intră valoarea
numerică mai mare (la numărator sau numitor) şi numărăm paşii realizaţi.
Ne oprim atunci când cele două fracţii devin identice.
Pentru complexitate logaritmică folosim ı̂n loc de scăderi repetate restul ı̂mpărţirii (la fel
ca ı̂n Algoritmul lui Euclid). La această modalitate trebuie să avem grijă la situaţia ı̂n care
transformarea fracţiei trece dincolo de prima fracţie comună celor două şiruri de transformări
neobţinând astfel numărul minim de segmente. Pentru aceasta la fiecare pas verificăm dacă
fracţiile nu au ajuns cumva pe aceeaşi ramură:
p1==p2 şi (q1-q2)%p1==0 sau q1==q2 şi (p1-p2)%q1==0.
Dacă au ajuns calculăm numărul de segmente dintre cele două fracţii:
(p1-p2)/q1 sau (q1-q2)/p1
şi ajustăm numărul total de segmente.

5.5.2 Cod sursă

Listing 5.5.1: 1 numinum 100.cpp


// prof. Chesca Ciprian
// sursa 100 p, O(log(min(a1,b1))+log(min(a2,b2)))

#include <fstream>
#include <cassert>

#define nmax 1000

using namespace std;

int main()
{
long long a1,b1,a2,b2,T;
long long w1[nmax],w2[nmax];
long long d, i, j, k, c, r; // teorema impartirii cu rest
long long k1,k2,ok1,ok2,s;

ifstream fin("numinum.in");
ofstream fout("numinum.out");
fin >> T;
assert(T>=1 && T<=10000);

for(k=1;k<=T;k++)
{
fin >> a1 >> b1 >> a2 >> b2;
assert(a1>=1 && a1<=1000000000);
CAPITOLUL 5. ONI 2018 5.5. NUMINUM 99

assert(b1>=1 && b1<=1000000000);


assert(a2>=1 && a2<=1000000000);
assert(b2>=1 && b2<=1000000000);

// prima fractie.......................................................
// determin fractia continua atasata primei fractii
d = a1;i = b1; r = d%i; k1 = 0;
if (r==0) w1[++k1] = d/i;
while (r)
{
r = d%i;
c = d/i;
w1[++k1] = c;
d = i;
i = r;
}

// daca lungimea secventei este un numar par mai adaug un termen de 1


if (k1%2==0)
{w1[++k1]=1;w1[k1-1]--;}

//calculez suma elementelor vectorului fractiei continue


ok1=0;
for(i=1;i<=k1;i++)
ok1+=w1[i];

// fractia a doua..........................................
// determin fractia continua atasata cele de-a doua fractii
d = a2;i = b2; r = d%i; k2 = 0;
if (r==0) w2[++k2] = d/i;
while (r)
{
r = d%i;
c = d/i;
w2[++k2] = c;
d = i;
i = r;
}

// daca lungimea secventei este un numar par mai adaug un termen de 1


if (k2%2==0)
{
w2[++k2]=1;
w2[k2-1]--;
}

//calculez suma elementelor vectorului fractiei continue


ok2=0;
for(i=1;i<=k2;i++)
ok2+=w2[i];

// determin secventa comuna celor doi vectori ai fractiilor continue


i=k1;j=k2;s=0;
while (i>=1 and j>=1)
{
if (w1[i]==w2[j])
{
s=s+2*w1[i];
i--;
j--;
}
else
{
if (w1[i]<w2[j])
s=s+2*w1[i];
else
s=s+2*w2[j];

break;
}
}

// afisez rezultatul
fout << ok1 + ok2 - s << "\n";
}
CAPITOLUL 5. ONI 2018 5.5. NUMINUM 100

fin.close();
fout.close();

return 0;
}

Listing 5.5.2: 2 numinum 100.cpp


// student Daniel Posdarascu

#include<stdio.h>
#include<vector>
#include<algorithm>

using namespace std;

#define x first.first
#define y first.second
#define dist second

int a, b, c, d, T;
vector< pair<pair<int,int>, int> > mylist[3];

void getList(int index, int a, int b)


{
if(a == 1 && b == 1)
{
mylist[index].push_back(make_pair(make_pair(a, b), 0));
return ;
}

int c;
if(a < b)
{
if(a > 1)
{
c = b / a;
getList(index, a, b % a);
}
else
{
c = b - 1;
getList(index, 1, 1);
}
}
else
{
if(b > 1)
{
c = a / b;
getList(index, a % b, b);
}
else
{
c = a - 1;
getList(index, 1, 1);
}
}

mylist[index].push_back(make_pair(make_pair(a, b), c));


}

void clearAll()
{
mylist[1].clear();
mylist[2].clear();
}

inline int modul(int value)


{
return (value < 0 ? -value : value);
}

int main ()
CAPITOLUL 5. ONI 2018 5.5. NUMINUM 101

freopen("numinum.in","r",stdin);
freopen("numinum.out","w",stdout);

scanf("%d",&T);

for(int k=1;k<=T;k++)
{

scanf("%d%d%d%d",&a,&b,&c,&d);

getList(1,a,b);
getList(2,c,d);

int lca=0, lim1=mylist[1].size(), lim2=mylist[2].size();


while(lca < lim1 && lca < lim2 &&
mylist[1][lca] == mylist[2][lca])
lca++;

int ans = 0;
for(int i = lim1 - 1; i > lca; i--)
ans += mylist[1][i].dist;

for(int i = lim2 - 1; i > lca; i--)


ans += mylist[2][i].dist;

if(lca == lim1)
{
int A = mylist[2][lca].x;
int B = mylist[2][lca].y;
int C;
if(A < B)
{
C = mylist[2][lca - 1].x;
ans += (B - C) / A;
}
else
{
C = mylist[2][lca - 1].y;
ans += (A - C) / B;
}

printf("%d\n",ans);
clearAll();
continue;
}

if(lca == lim2)
{
int A = mylist[1][lca].x;
int B = mylist[1][lca].y;
int C;

if(A < B)
{
C = mylist[1][lca - 1].x;
ans += (B - C) / A;
}
else
{
C = mylist[1][lca - 1].y;
ans += (A - C) / B;
}

printf("%d\n", ans);
clearAll();
continue;
}

int A = mylist[1][lca].x;
int B = mylist[1][lca].y;

int C = mylist[2][lca].x;
int D = mylist[2][lca].y;
CAPITOLUL 5. ONI 2018 5.6. ROSII MICI 102

if(A < B)
{ // C > D || A == C
if(A == C)
ans += modul(B - D) / A;
else
ans += (C - A) / D + (B - D) / A;
}
else
{// A > B && C < D || B == D
if(B == D)
ans += modul(A - C) / B;
else
ans += (A - C) / B + (D - B) / C;
}

printf("%d\n", ans);
clearAll();
}

return 0;
}

5.5.3 *Rezolvare detaliată

5.6 rosii mici


Problema 6 - rosii mici 100 de puncte
Dan este un mare pasionat al fructelor, printre preferatele sale fiind strugurii şi pepenii. ı̂nsă
recent şi-a descoperit şi pasiunea pentru legume, ı̂n special pentru roşii, dar mai ales roşiile mici.
Spre norocul lui, grădina bunicului este plină de roşii.
Grădina are forma unei matrice cu N linii şi M coloane cu elemente numere naturale, nu
neapărat distincte, unde fiecare element din matrice reprezintă dimensiunea unei roşii. Matricea
are proprietatea că oricare coloană are valorile ordonate crescător de sus ı̂n jos, adică de la prima
spre ultima linie.
Bunicul său ı̂i cere să rezolve Q sarcini. Pentru fiecare sarcină, Dan primeşte un număr
natural x şi trebuie să găsească o submatrice de arie maximă care ı̂ncepe de pe linia 1 a matricei
care reprezintă grădina şi are toate elementele mai mici sau egale decât x. Pentru determinarea
submatricei cerute, Dan are voie să mute toate valorile unei coloane ı̂n faţa oricărei alte coloane.
De asemenea, ı̂i este permis să facă oricâte mutări de tipul acesta.

Cerinţe

Să se calculeze aria maximă a unei submatrice care respectă specificaţiile din enunţ, pentru
fiecare din cele Q sarcini date de către bunic.

Date de intrare

Fişierul rosiimici.in conţine pe prima linie trei numere naturale N , M şi Q separate printr-un
spaţiu, având semnificaţia din enunţ.
Pe fiecare dintre următoarele N linii se află câte M numere naturale despărţite prin câte un
spaţiu, reprezentând valorile matricei.
Pe următoarele Q linii se află câte un număr natural x, reprezentând dimensiunea unei roşii.

Date de ieşire

Fişierul rosiimici.out va conţine pe primele Q linii câte un număr natural, reprezentând aria
maximă cerută pentru fiecare sarcină, ı̂n ordinea ı̂n care acestea apar ı̂n fişierul de intrare.

Restricţii şi precizări


CAPITOLUL 5. ONI 2018 5.6. ROSII MICI 103

a 1 & N, M & 1 000


a 1 & Q & 100 000
a 1 & Aij  & N ˜ M , 1 & i & N , 1 & j & M
a 1&x&N ˜M
a Pentru 30% din punctajul total există teste cu 1 & N, M, Q & 50
a Pentru alte 20% din punctajul total există teste cu 1 & M & 100

Exemple:

rosiimici.in rosiimici.out Explicaţii


343 4 Pentru rezolvarea primei sarcini Dan mută prima coloană ı̂n faţa
1962 9 celei de a patra obţinând matricea:
1 10 10 4 6
7 15 10 6
6
10
9
Alege apoi submatricea cu colţul stânga sus ı̂n (1,3) şi colţul
dreapta jos ı̂n (2, 4). Aria acesteia este 4. Pentru rezolvarea
celei de a doua sarcini, Dan mută prima coloană ı̂n faţa celei de
a treia obţinând matricea:

Soluţia este submatricea cu colţul stânga sus ı̂n (1,2) şi colţul
dreapta jos ı̂n (3, 4). Aria acesteia este 9. Pentru rezolvarea
celei de a treia sarcini, Dan mută ultima coloană ı̂n faţa primei
coloane, obţinând matricea:

Soluţia este submatricea cu colţul stânga sus ı̂n (1,1) şi colţul
dreapta jos ı̂n (3, 2). Aria acesteia este 6.

Timp maxim de executare/test: 1.0 secunde


Memorie: total 128 MB din care pentru stivă 32 MB
Dimensiune maximă a sursei: 10 KB

5.6.1 Indicaţii de rezolvare

Gemene Narcis-Gabriel - Universitatea din Bucureşti

Rezolvare 30 de puncte:
Pentru 30 de puncte putem precalcula răspunsul pentru fiecare valoare.
Astfel, pentru o valoare x calculăm inaltime[i] = numărul de elemente mai mici sau egale cu
x de pe coloane i. Având vectorul de ı̂nălţimi calculat putem folosi o abordare de tip greedy.
În primul rând sortam coloanele după ı̂nălţime. Apoi, pentru o coloană i, aria maximă a unui
dreptunghi este inaltime[i] * numărul de coloane care au ı̂nălţimea mai mare sau egală cu ı̂nălţimea
coloanei, soluţia fiind coloana care maximizează expresia de mai sus.
O abordare similară este calcularea vectorului inaltime pentru fiecare query ı̂n parte.
2 2
Complexităţi : O n ˜ m  Q sau O Q ˜ n  m

Rezolvare 50 de puncte:
Pentru aproximativ 50 de puncte ne vom folosi de faptul că fiecare coloană este sortată.
Astfel, vom folosi algoritmul descris ı̂n problema bsrec pentru calcularea ı̂n m*log(n) paşi a
vectorui inaltime.
CAPITOLUL 5. ONI 2018 5.6. ROSII MICI 104

O altă aborare este parcurgerea matricei ı̂n ordinea sortată a elementelor şi menţinerea vec-
torului inaltime sortat la fiecare pas, deoarece adăugarea elementului duce la modificarea unei
singure poziţii ı̂n vector, şi anume inaltimej .
2 2
Complexităţi : O n ˜ logn ˜ m  Q sau O Q ˜ mlogn sau O n ˜ m  Q

Rezolvare 100 de puncte:


Pentru 100 de puncte vom optimizara soluţia precedentă folosindu-ne de observaţia că pentru
a menţine vectorul sortat putem cauta binar poziţia care se modifică sau folosirea unui vector de
frecvenţă care să ne spună numărul de elemente mai mari sau egale cu o valoare i din vectorul de
ı̂nălţimi.

5.6.2 Cod sursă

Listing 5.6.1: rosiimici.cpp


#include <cstdio>
#include <vector>
#include <fstream>
#include <cassert>

using namespace std;

int main()
{
ifstream fin("rosiimici.in");
ofstream fout("rosiimici.out");

int n, m, q;
fin >> n >> m >> q;

assert(1 <= n && n <= 1000);


assert(1 <= m && m <= 1000);
assert(1 <= q && q <= 100000);

vector<vector<int>> positions(n * m + 5);


vector<int> minn(m, 0);

for (int i = 0; i < n; ++i)


{
for (int j = 0; j < m; ++j)
{
int x;
fin >> x;
assert(minn[j] <= x);
minn[j] = x;
assert(1 <= x && x <= n*m);
positions[x].push_back(j);
}
}

vector<int>height(m + 1, 0), sum(n + 1, 0);


vector<int>ans(n * m + 5, 0);

int sol = 0;
for (int val = 1;val <= n*m; ++val)
{
for(auto j: positions[val])
{
height[j]++;
sum[height[j]]++;
sol = max(sol,sum[height[j]] * height[j]);
}
ans[val] = sol;
}

for (int i = 1; i <= n*m; i++)


ans[i] = max(ans[i-1], ans[i]);

while(q-- > 0)
{
int x;
CAPITOLUL 5. ONI 2018 5.6. ROSII MICI 105

fin >> x;
assert(1 <= x && x <= n*m);
fout << ans[x] << "\n";
}

return 0;
}

Listing 5.6.2: rosiimici2.cpp


/*
Fac inaltimile elementelor pe parcurs, sortez coloanele in O(log(M))
si actualizez maximul
*/

#include <bits/stdc++.h>

using namespace std;

const int VALMAX = 1000005;


const int NMAX = 1005;
const int QMAX = 100005;

int n, m, q;
int a[NMAX][NMAX];
int rasp[QMAX];
int column_height[NMAX], which_column[NMAX], actual_pos[NMAX];

vector< pair<int, int> > pos, queries;

void Swap(int poza, int pozb)


{
swap(column_height[poza], column_height[pozb]);
swap(actual_pos[which_column[poza]], actual_pos[which_column[pozb]]);
swap(which_column[poza], which_column[pozb]);
}

void Prep()
{
for (int i = 1; i <= m; i++)
{
column_height[i] = 0;
which_column[i] = actual_pos[i] = i;
}

int i = 0, j = 0;
int mx = 0;
while (i < pos.size() && j < queries.size())
{
pair<int, int> x = pos[i];
pair<int, int> y = queries[j];

if (a[x.first][x.second] <= y.first)


{
// binary search
int st=1, dr = actual_pos[x.second], ans = actual_pos[x.second];
while (st <= dr)
{
int mij = (st + dr) / 2;
if(column_height[mij] == column_height[actual_pos[x.second]])
{
ans = mij;
dr = mij - 1;
}
else
st = mij + 1;
}

Swap(actual_pos[x.second], ans);

// add to the column


column_height[actual_pos[x.second]]++;

mx = max(mx,
actual_pos[x.second] *
CAPITOLUL 5. ONI 2018 5.6. ROSII MICI 106

column_height[actual_pos[x.second]]);
i++;
}
else
{
rasp[y.second] = mx;
j++;
}
}

while (j < queries.size())


{
rasp[queries[j].second] = mx;
j++;
}
}

bool cmp(pair<int, int> A, pair<int, int> B)


{
return a[A.first][A.second] < a[B.first][B.second];
}

int main()
{
freopen("rosiimici.in", "r", stdin);
freopen("rosiimici.out", "w", stdout);
cin.sync_with_stdio(false);

cin >> n >> m >> q;


for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
pos.push_back(make_pair(i, j));
}

for (int i = 1; i <= q; i++)


{
int x;
cin >> x;
queries.push_back(make_pair(x, i));
}

sort(queries.begin(), queries.end());
sort(pos.begin(), pos.end(), cmp);

Prep();

for (int i = 1; i <= q; i++)


cout << rasp[i] << "\n";
return 0;
}

5.6.3 *Rezolvare detaliată


Capitolul 6

ONI 2017

6.1 arhipelag
Problema 1 - arhipelag 100 de puncte
În regiunea Ionia a lumii greceşti antice, regiune ce corespunde teritoriului actual al Mării
Egee, există mai multe insule. Harta mării este reprezentată de o matrice de dimenisuni N  M ,
având valori de 1 şi 0, iar fiecare element din matrice reprezintă o zonă de dimensiune 1  1 din
mare. Liniile matricei sunt numerotate de la 1 la N , de sus ı̂n jos, iar coloanele de la 1 la M , de
la stânga la dreapta. Astfel, colţul din stânga sus al matricei este asociat zonei (1, 1), iar colţul
din dreapta jos corespunde zonei (N , M ).
Un element care conţine valoarea 0 reprezintă faptul că ı̂n acea zonă se află apă. O insulă este
determinată de un dreptunghi format ı̂n totalitate din valori de 1. Se garantează faptul că toate
zonele care conţin valoarea 1 formează dreptunghiuri valide şi că oricare două insule sunt separate
de apă. De exemplu, Figura 1 de mai jos reprezintă o hartă validă, ı̂n timp ce Figura 2 şi Figura
3 NU reprezintă o hartă validă.

Cerinţe

Ionienii, fiind oameni practici, doresc construirea unui far-bibliotecă (aşezat pe o platformă 1
 1), ı̂ntr-o zonă acoperită de apă. Poziţia platformei va fi aleasă ı̂ntr-o celulă C astfel ı̂ncât suma
distanţelor dintre toate insulele şi C să fie minimă.
Distanţa dintre o celulă C şi o insulă este definită ca fiind minimul dintre distanţele Manhattan
dintre C şi fiecare celulă care aparţine insulei (distanţa poate trece atât prin alte insule, cât şi
prin zone acoperite de apă).
Distanţa Manhattan dintre două celule aflate pe linia x1 şi coloana y1, respectiv pe linia x2 şi
coloana y2, este definită ca —x1 - x2— + —y1 - y2—, unde —x— reprezintă valoarea absolută a
lui x.

Date de intrare

Fişierul de intrare arhipelag.in conţine, pe prima linie, valorile N şi M , având semnificaţia din
enunţ. Următoarele N linii conţin câte M valori binare, separate de câte un spaţiu, reprezentând
harta mării.

Date de ieşire

107
CAPITOLUL 6. ONI 2017 6.1. ARHIPELAG 108

Fişierul de ieşire arhipelag.out va conţine o pereche de numere naturale, reprezentând linia


si coloana celulei alese de ionieni pentru construcţie. Dacă există mai multe soluţii posibile, se va
alege cea care are linia minimă. Dacă ı̂n continuare există mai multe soluţii, se va alege cea care
are coloana minimă.

Restricţii şi precizări

a Pentru teste ı̂n valoare de 15 puncte, 1 & N, M & 50


a Pentru alte teste ı̂n valoare de 20 de puncte, 1 & N, M & 300, iar numărul de insule din
arhipelag nu depăşeşte 300
a Pentru alte teste ı̂n valoare de 20 de puncte, 1 & N, M & 300
a Pentru restul de teste, 1 & N, M & 1000
a Se garantează că există cel puţin o zonă acoperită de apă

Exemple:

arhipelag.in arhipelag.out Explicaţii


77 23 Notând cu D(x1, y1, x2, y2) insula determinată de dreptunghiul
0101011 având colţul stânga sus ı̂n (x1, y1) şi colţul dreapta jos ı̂n (x2,
0101011 y2), arhipelagul conţine următoarele insule: D1(1, 2, 2, 2), D2(1,
0001000 4, 7, 4), D3(1, 6, 2, 7), D4(6, 1, 7, 2) şi D5(6, 6, 7, 7). Notând
0001000 cu dist(D) distanţa dintre celula (2, 3) şi insula D, distanţele
0001000 sunt următoarele:
1101011 dist(D1) = min —2 - 1— + —3 - 2—, —2 - 2— + —3 - 2— =
1101011 1, dist(D2) = 1, dist(D3) = 3, dist(D4) = 5 şi dist(D5) = 7.
44 12 Pentru fiecare dintre celulele (1, 2), (2, 2), (3, 2), (4, 3) si (4,
0011 4), distanţa dintre celulă şi singura insulă existentă ı̂n acest
0011 exemplu este aceeaşi. Se va alege cea care are linia minimă,
0011 iar ı̂n caz de egalitate se va alege cea care are coloana minimă.
0000 Astfel, celula (1, 2) reprezintă soluţia.
Timp maxim de executare/test: 0.3 secunde
Memorie: total 32 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 15 KB

6.1.1 Indicaţii de rezolvare

În descrierea soluţiei, C x, y  reprezintă o celulă aflată pe linia x şi coloana y, iar
D x1, y1, x2, y2 reprezintă o insulă care are colţul stânga sus ı̂n x1, y1 şi colţul dreapta jos
ı̂n x2, y2.
Soluţia 1 (15 puncte)
Se iterează toate celulele care conţin apă şi pentru fiecare dintre acestea se calculează suma
distanţelor către toate insulele. Distanţa dintre o celulă C si o insulă se calculează iterând toate
celulele care aparţin insulei şi păstrând distanţa Manhattan minimă dintre o astfel de celulă şi
celula C.
2 2
Complexitate: O N ˜ M .
Soluţia 2 (35 de puncte)
Se procedează asemanator ca ı̂n soluţia 1, observând faptul că distanţa dintre o celulă C x, y 
şi o insulă D x1, y1, x2, y2 se poate calcula ı̂n O 1 astfel:
Dist C, D dx  dy, unde:
a dx =

` x1 - x, dacă x ¡ x1
` x - x2, dacă x ¿ x2
` 0, altfel
a dy =
` y1 - y, dacă y ¡ y1
` y - y2, dacă y ¿ y2
` 0, altfel
CAPITOLUL 6. ONI 2017 6.1. ARHIPELAG 109

Complexitate: O N ˜ M ˜ N R IN SU LE 
Soluţia 3 (55 de puncte)
Se observă faptul că pentru o celulă C x, y , avem două costuri asociate: costul implicat de
alegerea liniei x şi costul implicat de alegerea coloanei y; aceste două costuri sunt independente (nu
depind unul de celălalt). Astfel, se precalculează 2 vectori total dxx (1 & x & N ) şi total dy y 
(1 & y & M ), reprezentând costul asociat cu alegerea liniei x, respectiv a coloanei y, iar apoi se
iterează toate celulele care conţin apă şi se alege cea care are total dx  total dy minim.
Valoarea total dxx se calculează astfel: se itereaza toate insulele, iar pentru fiecare insulă
D x1, y1, x2, y2, se adaugă la total dxx valoarea dx, definită la fel ca in solutia 2. Analog, se
calculează valorile total dy y .
Complexitate: O N ˜ N R IN SU LE  M ˜ N R IN SU LE 
Solutia 4 (100 de puncte)
Se procedează asemănător ca ı̂n soluţia 3, cu excepţia modalităţii de calculare a vectorilor
total dxx şi total dy y .
Cum se calculează total dxx (total dy y  se calculează ı̂n mod similar):
a se calculează intervalele ı̂nchise x1i, x2i, liniile pe care se intinde insula i
a total dx1 se calculează ca ı̂n soluţia 3, ı̂n O N R IN SU LE 
a se iterează x de la 2 la N , la fiecare pas ţinând minte 2 variabile:

` a - câte intervale x1, x2 cu proprietatea că x2 $ x există


` b - câte intervale x1, x2 cu proprietatea că x $ x1 există
Pentru x % 1, total dxx total dxx  1  b  a (folosind valoarea b de la pasul precedent
şi a-ul curent; intuitiv, cele b intervale de la dreapta se aproprie cu 1 fata de x-ul curent, deci se
scad b ˜ 1 din costul total, iar cele a aflate la stanga, inclusiv cele care tocmai au ramas ı̂n spatele
lui x - de aceea ”a-ul curent” - se departează cu 1, deci se adaugă a ˜ 1 la costul total).
Valorile a şi b se pot calcula simplu, ı̂n O 1 la fiecare pas, folosind nişte vectori ı̂n care marcăm
unde ı̂ncepe, respectiv unde se termină, un interval.
Complexitate: O N ˜ M 

6.1.2 Cod sursă

Listing 6.1.1: arhipelag 100p 1.cpp


// Mihai Enache
#include <iostream>
#include <fstream>
using namespace std;

const int MAX_N = 1002;

int N, M, xs, ys;


int A[MAX_N][MAX_N], cx[MAX_N], cy[MAX_N], startX[MAX_N],
endX[MAX_N], startY[MAX_N], endY[MAX_N];

void computeCostForCoordinate(int n, int c[], int startHere[], int endHere[])


{
int toRight = 0;
int toLeft = 0;
for(int i = 2; i <= n; ++i)
{
c[1] += (i - 1) * startHere[i];
toRight += startHere[i];
}

for(int i = 2; i <= n; ++i)


{
toLeft += endHere[i - 1];
c[i] = c[i - 1] - toRight + toLeft;

toRight -= startHere[i];
}
}

int main() {
ifstream cin("arhipelag.in");
CAPITOLUL 6. ONI 2017 6.1. ARHIPELAG 110

ofstream cout("arhipelag.out");

cin >> N >> M;


for(int i = 1; i <= N; ++i)
for(int j = 1; j <= M; ++j)
cin >> A[i][j];

for(int i = 1; i <= N; ++i)


{
for(int j = 1; j <= M; ++j)
{
if(A[i][j] == 1)
{
int xl = i;
int xr = i;

while(A[xr + 1][j])
++xr;

int yl = j;
int yr = j;
while(A[i][yr + 1])
++yr;

for(int k = xl; k <= xr; ++k)


for(int h = yl; h <= yr; ++h)
A[k][h] = -1;

++startX[xl];
++endX[xr];

++startY[yl];
++endY[yr];
}
}
}

computeCostForCoordinate(N, cx, startX, endX);


computeCostForCoordinate(M, cy, startY, endY);

int bestTotalDistance = 0x3f3f3f3f;


for(int i = 1; i <= N; ++i)
{
for(int j = 1; j <= M; ++j)
{
if(!A[i][j] && cx[i] + cy[j] < bestTotalDistance)
{
bestTotalDistance = cx[i] + cy[j];
xs = i;
ys = j;
}
}
}

cout << xs << " " << ys << "\n";


// printf("Best total disance = %d\n", bestTotalDistance);

return 0;
}

Listing 6.1.2: arhipelag 100p 2.cpp


// Alexandru Velea
#include <fstream>
#include <iostream>
#include <vector>

using namespace std;

const int kMaxN = 1e3+5;

int el[kMaxN][kMaxN];

struct Rectangle
{
CAPITOLUL 6. ONI 2017 6.1. ARHIPELAG 111

int x, y, lx, ly;


Rectangle() : x(0), y(0), lx(0), ly(0) { }
};

vector<Rectangle> rectangles;

void AddRectangle(int x, int y)


{
Rectangle r;
r.x = x;
r.y = y;

while (el[x][y + r.ly] == 1)


r.ly += 1;

while (el[x + r.lx][y] == 1)


r.lx += 1;

rectangles.push_back(r);
}

int lazy_x[kMaxN];
int lazy_y[kMaxN];

int X[kMaxN];
int Y[kMaxN];

int main()
{
ifstream cin("arhipelag.in");
ofstream cout("arhipelag.out");

int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i += 1)
for (int j = 1; j <= m; j += 1)
cin >> el[i][j];

for (int i = 1; i <= n; i += 1)


for (int j = 1; j <= m; j += 1)
if (el[i][j] and el[i - 1][j] == 0 and el[i][j - 1] == 0)
AddRectangle(i, j);

int sum_x = 0, sum_y = 0;


int lazy_x_sum = 0;
int lazy_y_sum = 0;

for (auto itr : rectangles)


{
lazy_x_sum -= 1;
lazy_x[itr.x + 1] += 1;
sum_x += itr.x;

lazy_x[itr.x + itr.lx] += 1;

lazy_y_sum -= 1;
lazy_y[itr.y + 1] += 1;
sum_y += itr.y;
lazy_y[itr.y + itr.ly] += 1;
}

for (int i = 1; i <= n; i += 1)


{
lazy_x_sum += lazy_x[i];
sum_x += lazy_x_sum;
X[i] = sum_x;
}

for (int i = 1; i <= m; i += 1)


{
lazy_y_sum += lazy_y[i];
sum_y += lazy_y_sum;
Y[i] = sum_y;
}

int mn = 1e9;
CAPITOLUL 6. ONI 2017 6.2. MIRROR 112

int rx = -1, ry = -1;


for (int i = 1; i <= n; i += 1)
{
for (int j = 1; j <= m; j += 1)
{
if (el[i][j] == 1)
continue;

if (X[i] + Y[j] < mn)


{
mn = X[i] + Y[j];
rx = i;
ry = j;
}
}
}

cout << rx << ’ ’ << ry << ’\n’;


return 0;
}

6.1.3 *Rezolvare detaliată

6.2 mirror
Problema 2 - mirror 100 de puncte
Numim ”oglinda” numărului natural nenul a, numărul b, obţinut prin modificarea fiecărei cifre din
reprezentarea sa binară, de exemplu pentru a 22 10 10110 2 se obţine 01001 2 9 10 b.
Cerinţe
Cunoscându-se numerele naturale N , K şi cele N numere natural nenule, scrieţi un program
care:
1) Transformă ı̂n baza doi termenii şirului dat obţinându-se un nou şir format din alipirea
cifrelor binare. Din acest şir se vor determina şi afişa, separate prin câte un spaţiu, toate
reprezentările ı̂n baza 10 corespunzătoare secvenţelor alăturate de exact K cifre binare, parcurse
de la stânga la drepta. Dacă ultima secvenţă nu are exact K cifre binare, atunci aceasta nu se va
mai lua ı̂n considerare.
2) Să aplice K transformări asupra şirului iniţial, ı̂nlocuind la fiecare pas orice termen cu
”oglinda” sa. Asupra termenilor care devin zero nu se vor mai efectua alte operaţii. După
efectuarea celor K transformări, să se determine cea mai lungă secvenţă de numere care au cifra 1
pe aceeaşi poziţie ı̂n reprezentarea lor ı̂n baza doi. Dacă sunt mai multe astfel de secvenţe având
lungimea maximă, se va afişa cea mai din stânga.
Date de intrare
Fişierul de intrare mirror.in conţine pe primul rând numărul C, reprezentând cerinţa. Pe al
doilea rând se află scrise numerele naturale N şi K. Pe rândul al treilea sunt cele N numere ale
şirului separate prin câte un spaţiu.
Date de ieşire
Dacă C 1, atunci ı̂n fişierul de ieşire mirror.out se vor scrie separate prin câte un spaţiu,
toate numerele cerute ı̂n enunţ.
Dacă C 2, atunci ı̂n fişierul de ieşire mirror.out se va scrie pe prima linie lungimea maximă
a secvenţei determinate, iar pe următoarea linie separate prin spaţiu, poziţia primului şi ultimului
termen din secvenţă (prima poziţie este 1).
Restricţii şi precizări
a 1 & N &100 000
a 0 & K & 30
a Elementele şirului sunt mai mici decât 2 000 000 001;
a Pentru 30% din teste cerinţa va fi C 1.
CAPITOLUL 6. ONI 2017 6.2. MIRROR 113

Exemple:

mirror.in mirror.out Explicaţii


1 330111 7 10 111 2 ; 8 10 1000 2 ; 2 10 10 2 ; 11 10 1011 2 ;
42 Sirul format este: 1111000101011 şi grupate câte 2 avem nu-
7 8 2 11 merele:
11 2 3 10 ; 11 2 3 10 ; 00 2 0 10 ; 01 2 1 10 ; 01 2
1 10 ; 01 2 1 10 ;
2 3 După o transformare numerele ı̂n baza 2 sunt:
51 13
37 72 101 50
116

Cea mai lungă secvenţă este de lungime 3, fiind formată din


numerele 37, 72, 101 care ı̂ncepe pe poziţia 1 şi se termină pe
poziţia 3. Mai există ı̂ncă o astfel de secvenţa (101, 50, 116) dar
se alege cea mai
Timp maxim de executare/test: 1.0 secunde
Memorie: total 32 MB
Dimensiune maximă a sursei: 10 KB

6.2.1 Indicaţii de rezolvare

i
Soluţia problemei se bazează pe generarea unui şir de 31 de valori egale cu 2  1, rezultând
astfel doar cifre de 1 ı̂n scrierea binară a numerelor din şir şi utilizând aceste numere pentru
transformările cerute.
La cerinţa 1: cu ajutorul operaţiilor pe biţi, considerăm un număr numit masca, care iniţial
este 1 şi pe care ı̂l mărim până când aceasta ajunge la cifra de unu cea mai semnificativă a
elementelor şirului dat, astfel ı̂ncât la operaţia de ”şi pe biţi” ı̂ntre masca şi număr să determinăm
cifra binară de pe poziţia curentă şi să realizăm construcţia numerelor cerute (cu K cifre binare).
La cerinţa 2: realizăm cele K transformări asupra şirului dat, ţinând cont de faptul că multe
numere ajung zero după un număr de transformări şi determinând platouri de numere nenule,
căutând printre ele, cifră binară cu cifră binară cel mai lung platou de cifre de 1 care se află
0 1
respectiv pe aceeaşi poziţie, ca putere a lui doi (2 , 2 , ...)

6.2.2 Cod sursă

Listing 6.2.1: mirror 100p 1.cpp


#include <fstream>
#include<cstdio>
#include<climits>
#include <stdlib.h>
#include<algorithm>

using namespace std;

FILE *fin=fopen("mirror.in","r");
ofstream fout("mirror.out");

int v[1000005],n,ct,k;

void rezolva1()
{
int masca=0,i,y=1<<(k-1),nr,x=0;
for(i=1; i<=n; i++)
{
nr=v[i];
if(masca==0)
masca=1;

while(masca<nr)
CAPITOLUL 6. ONI 2017 6.2. MIRROR 114

masca<<=1;

if(masca>nr)masca>>=1;

while(masca)
{
if(masca&nr)
x=x+y;
y/=2;
if(y==0)
{
fout<<x<<" ";
y=1<<k-1;
x=0;
}
masca>>=1;
}
}
fout<<’\n’;
}

int lungime(int &st, int &dr)


{
int ok,l,lmax=0,masca=1,ddr,i;
do
{
ok=0;//pp ca nu mai sunt cifre binare de 1
l=0;
for(i=st; i<=dr; i++)
{
if(masca<v[i])
ok=1;
if(masca&v[i])
l++;
else
{
if(l>lmax)
{
lmax=l;
ddr=i-1;
}
l=0;
}
}

if(l>lmax)
{
lmax=l;
ddr=dr;
}
masca<<=1;
}

while(ok==1);
dr=ddr;
st=ddr-lmax+1;
return lmax;
}

void secventa()
{
int st=0,dr,i,lung,lmax=0,sst,ddr;
i=1;

while(v[i]==0&&i<=n)
i++;

if(i<=n)
for( ; i<=n; i++)
if(v[i]!=0)
if(st==0)
st=dr=i;
else
dr++;
else
{
CAPITOLUL 6. ONI 2017 6.2. MIRROR 115

if(st)
{
lung=lungime(st,dr);
if(lung>lmax)
{
lmax=lung;
sst=st;
ddr=dr;
}
st=0;
}
}

if(st)
{
lung=lungime(st,dr);
if(lung>lmax)
{
lmax=lung;
sst=st;
ddr=dr;
}
}

fout<<lmax<<’\n’<<sst<<" "<<ddr<<’\n’;
}

int main()
{
int x=1,i,j,masca;
fscanf(fin,"%d%d%d",&ct,&n,&k);
for(i=1; i<=n; i++)
fscanf(fin,"%d",&v[i]);

if(ct==1)
{
rezolva1();
return 0;
}

// transformare cautand cifrele care se modifica si tinand cont


// de paritatea lui k
int u[33],nr,p,l,r;
if(k)
for(r=1; r<=n; r++)
{
masca=1;
masca<<=30;
while((masca&v[r])==0)
masca>>=1;
nr=0;

//pun cifrele binare in vector


while(masca)
{
if(masca&v[r])
u[++nr]=1;
else
u[++nr]=0;

masca>>=1;
}

int cont=0;//numara de cate ori se schimba cifrele binare


int cifbin=1;
for(j=1; j<=nr; j++)
if(u[j]!=cifbin)
{
cont++;
if(cifbin==1)
cifbin=0;
else
cifbin=1;
}

if(cont<k)
CAPITOLUL 6. ONI 2017 6.2. MIRROR 116

v[r]=0;
else
{
// modific toate cifrele odata daca k impar,
// altfel cifrele raman la fel
if(k%2==1)
for(j=1; j<=nr; j++)
if(u[j]==0)
u[j]=1;
else
u[j]=0;

//elimin cifrele care se pierd la transformare


i=1;
cont=0;
while(cont<k&&i<=nr)
{
i++;
if(u[i]!=u[i-1])
cont++;
}

if(i>nr)
v[r]=0;
else
{
p=1;
x=0;
for(j=nr; j>=i; j--)
{
x=x+p*u[j];
p<<=1;
}

v[r]=x;
}
}
}

secventa();
return 0;
}

Listing 6.2.2: mirror 100p 2.cpp


#include <iostream>
#include <fstream>

using namespace std;

char A[1000000][32],x2[32];

int main()
{
ifstream in("mirror.in");
ofstream out("mirror.out");

int c,k,a[30][33],s,nc,max=0;
long n,x,r,p2,p,lmax,psf,psfmin,nr,i,j,l;

in>>c; in>>n>>k;

if(c==1)
{ //cer 1
p2=1;
for(i=1;i<=k-1;i++)
p2=p2*2;

nr=0;
r=0;
p=p2;
nc=0;
while(nr<n)
{
s=0; i=0;
CAPITOLUL 6. ONI 2017 6.2. MIRROR 117

while(s<k && nr<n)


{
in>>x; nr++; j=0;
while(x!=0)
{
a[i][++j]=x%2;
x=x/2;
}

a[i][0]=j;
s=s+j;
i++;
}

x=r;
for(l=0;l<i;l++)
for(j=a[l][0];j>0;j--)
{
x=x+a[l][j]*p;
p=p/2;
nc++;
if(nc==k)
{
out<<x<<" ";
x=0;
p=p2;
nc=0;
}
}
r=x;
}
}
else
{ //cer 2
for(i=0;i<n;i++)
{
in>>x; nc=0;
if(x==0)
nc=1;

while(x!=0)
{
x2[nc++]=x%2;
x=x/2;
}

for(j=1;j<=k;j++)
{
r=j%2;
nc--;
if(nc>=1)
while(x2[nc-1]==r && nc>0)
nc--;
}

if(nc>max)
max=nc;

for(j=0;j<nc;j++)
{
if(k%2==1)
A[i][j]=!x2[j];
else
A[i][j]=x2[j];
}
}

//det secv de 1 cu lung max pe col si poz inc si sf


lmax=0;
psf=1;
psfmin=n;
for(j=0;j<max;j++)
{
l=0;
for(i=0;i<n;i++)
{
CAPITOLUL 6. ONI 2017 6.2. MIRROR 118

if(A[i][j]==1)
l++;
else
{
if(l>lmax)
{
lmax=l;
psf=i;
psfmin=i;
}

if(l==lmax)
psf=i;

if(psf<psfmin)
psfmin=psf;

l=0;
}
}

i=i-1;
if(A[i][j]==1 && l>lmax)
{
lmax=l;
psf=i+1;
psfmin=psf;
}
}

out<<lmax<<’\n’; out<<psfmin+1-lmax<<’ ’<<psfmin<<’\n’;


}

in.close();
out.close();
return 0;
}

Listing 6.2.3: mirror 100p 3.cpp


// Autor: Mihai Enache
#include <iostream>
#include <fstream>

using namespace std;

const int MAX_N = 1000002;

int C, N, K;
int v[MAX_N];

int main()
{
ifstream cin("mirror.in");
ofstream cout("mirror.out");

cin >> C >> N >> K;


for(int i = 1; i <= N; ++i)
cin >> v[i];

if(C == 1)
{
int currentValue = 0;
int nrBits = 0;

for(int i = 1; i <= N; ++i)


{
int maxBit = 0;
for(int bit = 31; bit >= 0; --bit)
{
if(v[i] & (1 << bit))
{
maxBit = bit;
break;
}
CAPITOLUL 6. ONI 2017 6.2. MIRROR 119

for(int bit = maxBit; bit >= 0; --bit)


{
if(v[i] & (1 << bit))
currentValue = currentValue * 2 + 1;
else
currentValue = currentValue * 2;

++nrBits;

if(nrBits == K)
{
cout << currentValue << " ";

currentValue = 0;
nrBits = 0;
}
}
}

cout << "\n";


}
else
{
for(int i = 1; i <= N; ++i)
{
for(int j = 1; j <= K; ++j)
{
if(!v[i])
break;

int maxBit = 0;
for(int bit = 31; bit >= 0; --bit)
{
if(v[i] & (1 << bit))
{
maxBit = bit;
break;
}
}

v[i] ˆ= (1 << (maxBit + 1)) - 1;


}
}

int startPos = -1;


int endPos = -1;
int maxLength = 0;

for(int bit = 0; bit < 32; ++bit)


{
int currentLength = 0;
for(int i = 1; i <= N; ++i)
{
if(v[i] & (1 << bit))
++currentLength;
else
currentLength = 0;

if(currentLength > maxLength ||


(currentLength == maxLength &&
i - currentLength + 1 < startPos))
{
maxLength = currentLength;
startPos = i - currentLength + 1;
endPos = i;
}
}
}

cout << maxLength << "\n";


cout << startPos << " " << endPos << "\n";
}

return 0;
CAPITOLUL 6. ONI 2017 6.2. MIRROR 120

Listing 6.2.4: mirror 100p 4.cpp


#include<iostream>
#include <fstream>
#include<cstdio>
#include<climits>
#include <stdlib.h>

using namespace std;

FILE *fin=fopen("mirror.in","r");
ofstream fout("mirror.out");

int v[1000005],n,ct,k;
int w[33];

int cautare_binara(int x)
{
int st=1,dr=31,mij;

if(x>=w[31])
return w[31];
if(x<=w[1])
return w[1];

while(st<=dr)
{
mij=(st+dr)>>1;
if(w[mij]==x)
return x;
if(x>w[mij] && x<=w[mij+1])
return w[mij+1];
if(x<w[mij])
dr=mij-1;
else
st=mij+1;
}

return -1;
}

int transforma(int x)
{
int y=cautare_binara(x);
y=(xˆy);
return y;
}

void rezolva1()
{
int masca=0,i,j,y=1<<k-1,nr,x=0;
for(i=1; i<=n; i++)
{
nr=v[i];
if(masca==0)
masca=1;
while(masca<nr)
masca<<=1;
if(masca>nr)masca>>=1;
while(masca)
{
if(masca&nr)
x=x+y;
y/=2;
if(y==0)
{
fout<<x<<" ";
y=1<<k-1;
x=0;
}
masca>>=1;
}
}
CAPITOLUL 6. ONI 2017 6.2. MIRROR 121

fout<<’\n’;
}

int lungime(int &st, int &dr)


{
int ok,l,lmax=0,masca=1,ddr,i;
do
{
ok=0;//pp ca nu mai sunt cifre binare de 1
l=0;
for(i=st; i<=dr; i++)
{
if(masca<=v[i])
ok=1;
if(masca&v[i])
l++;
else
{
if(l>lmax)
{
lmax=l;
ddr=i-1;
}
l=0;
}
}
if(l>lmax)
{
lmax=l;
ddr=dr;
}
masca<<=1;
} while(ok==1);

dr=ddr;
st=ddr-lmax+1;
return lmax;
}

void secventa()
{
int st=0,dr=0,i,lung,lmax=0,sst,ddr;
i=1;
while(v[i]==0&&i<=n)
i++;

if(i<=n)
for( ; i<=n; i++)
if(v[i]!=0)
if(st==0)
st=dr=i;
else
dr++;
else
{
if(st)
{
lung=lungime(st,dr);
if(lung>lmax)
{
lmax=lung;
sst=st;
ddr=dr;
}
st=0;
}
}

if(dr)
{
lung=lungime(st,dr);
if(lung>lmax)
{
lmax=lung;
sst=st;
ddr=dr;
CAPITOLUL 6. ONI 2017 6.2. MIRROR 122

}
}

fout<<lmax<<’\n’<<sst<<" "<<ddr<<’\n’;
}

int main()
{
int x=1,i,j,masca;
for(i=1; i<=31; i++)
{
x=x<<1;
w[i]=x-1;
}
fscanf(fin,"%d%d%d",&ct,&n,&k);
for(i=1; i<=n; i++)
fscanf(fin,"%d",&v[i]);

if(ct==1)
{
rezolva1();
return 0;
}

//transformare cu cautare binara- timp mai mare?


for(i=1; i<=n; i++)
{
for(j=1; j<=k&&v[i]; j++)
v[i]=transforma(v[i]);
// fout<<v[i]<<" ";
}

secventa();
return 0;
}

Listing 6.2.5: mirror 100p 5.cpp


#include<fstream>
#include<cstdio>

using namespace std;

FILE *fin;
ofstream fout("mirror.out");

int C, N, K, pos[33], posL[33], L[33], maxL[33], v[33],z;


long long x, y, d[33];

int posd(int x)
{
///d[i] este cea mai mica putere a lui 2 care depaseste pe x
int i=0;
while(d[i]<=x)
i++;
return i;
}

void afisareK(long long &x, int &p)


{
while(p>=K)
{
p=p-K;
fout<<x/d[p]<<" ";
x=x%d[p];
}
}

int main()
{
int i,q,p,j,jmax,l,pmax;

d[0]=1;
for(i=1;i<=32;i++)
{
CAPITOLUL 6. ONI 2017 6.2. MIRROR 123

d[i]=d[i-1]*2;
}

fin=fopen("mirror.in","rt");
fscanf(fin,"%d %d %d",&C,&N,&K);

if(C==1)
{
fscanf(fin,"%d",&z);
x=z;
p=posd(x);
afisareK(x,p);
for(i=2;i<=N;i++)
{
fscanf(fin,"%d",&z);
y=z;
q=posd(y);
x=x*d[q]+y;
p+=q;
afisareK(x,p);
}
}
else
{
for(j=0;j<33;j++)
{
posL[j]=0;
maxL[j]=0;
}
pmax=0;
for(i=1;i<=N;i++)
{
fscanf(fin,"%d",&z);
x=z;
for(j=0;j<=31;j++)
{
v[j]=x%2;
x=x/2;
if(v[j]==1){
p=j;
}
}
if(p>pmax)pmax=p;
for(l=1;l<=K && p>=0;l++)
{
while(p>=0 && v[p]==l%2)
{
v[p]=0;
p--;
}
}
for(j=0;j<=pmax;j++)
{
q=v[j];
if(K%2) q=1-q;
if(j>p) q=0;

if(q==1)
{
L[j]++;
if(L[j]==1)pos[j]=i;
if(L[j]>maxL[j])
{
maxL[j]=L[j];
posL[j]=pos[j];
}
}
else
{
L[j]=0;
}
}
}

jmax=0;
for(j=0;j<=pmax;j++)
CAPITOLUL 6. ONI 2017 6.2. MIRROR 124

{
if((maxL[j]>maxL[jmax])||
(maxL[j]==maxL[jmax] && posL[j]<posL[jmax]))
{
jmax=j;
}
}

fout<<maxL[jmax]<<"\n";
fout<<posL[jmax]<<" "<<posL[jmax]+maxL[jmax]-1<<"\n";
}

fclose(fin);
fout.close();
return 0;
}

Listing 6.2.6: mirror 100p 6.cpp


#include <fstream>
#include <iostream>
#include <vector>

using namespace std;

int c, n, k;
vector<int> el;

void Solve1()
{
vector<bool> bits;
for (auto itr : el)
{
int p = 30;
for (; p >= 0; p -= 1)
if ((1 << p) & itr)
break;

if (p == -1)
p = 0;

for (; p >= 0; p -= 1)
bits.push_back(!!((1 << p) & itr));
}

ofstream cout("mirror.out");

for (int i = 0; i + k <= (int)bits.size(); i += k)


{
int x = 0;
for (int j = 0; j < k; j += 1)
{
x *= 2;
x += bits[i + j];
}

cout << x << ’ ’;


}
cout << ’\n’;

return;
}

void Mirror(int& a, int k)


{
int p = 30;
for (; p >= 0; p -= 1)
if ((1 << p) & a)
break;

if (p == -1)
p = 0;

vector<bool> bits;
for (int i = 0; i <= p; i += 1)
CAPITOLUL 6. ONI 2017 6.2. MIRROR 125

bits.push_back(!!((1 << i) & a));

for (int i = 0; i < k and bits.size(); i += 1)


{
int last = bits.back();
while (bits.size() and bits.back() == last)
bits.pop_back();
}

k = k & 1;

int x = 0;
while (bits.size())
{
x *= 2;
x += k ˆ bits.back();
bits.pop_back();
}

a = x;
}

void Solve2()
{
int mx = -1;
int where = 0;

vector<int> bits(31, 0);


int right = 1;
for (auto itr : el)
{
Mirror(itr, k);
for (int p = 0; p <= 30; p++)
{
if ((1 << p) & itr)
{
bits[p]++;
if (bits[p] > mx)
{
mx = bits[p];
where = right;
}
}
else
bits[p] = 0;
}

right++;
}

ofstream cout("mirror.out");

cout << mx << ’\n’;


cout << where - mx + 1 << ’ ’ << where << ’\n’;

return;
}

int main()
{
ifstream cin("mirror.in");
cin >> c >> n >> k;
el.resize(n);

for (int i = 0; i < n; i += 1)


cin >> el[i];

if (c == 1)
Solve1();
else
Solve2();

return 0;
}
CAPITOLUL 6. ONI 2017 6.3. OKCPP 126

6.2.3 *Rezolvare detaliată

6.3 okcpp
Problema 3 - okcpp 100 de puncte
Despre numărul natural N spunem că are proprietatea okcpp dacă oricum alegem K cifre ale
sale vom găsi printre ele cel puţin P cifre distincte (oricare k cel puţin p).

Cerinţe

(1) Fiind date numerele naturale K, P , A şi B să se calculeze şi să se afişeze numărul de
numere okcpp din intervalul A, B .
(2) Fiind date numerele naturale K, P şi N să se calculeze şi să se afişeze cel mai mic număr
okcpp care este mai mare sau egal cu N .

Date de intrare

Fişierul de intrare okcpp.in conţine pe primul rând numărul C.


Dacă C 1, atunci pe al doilea rând se vor afla scrise, separate prin spaţiu, numerele naturale
K, P , A şi B.
Dacă C 2, atunci pe al doilea rând se vor afla scrise, separate prin spaţiu, numerele naturale
K, P şi N .

Date de ieşire

Dacă C 1, atunci ı̂n fişierul de ieşire okcpp.out se va scrie numărul de numere okcpp din
intervalul A; B .
Dacă C 2, atunci ı̂n fişierul de ieşire okcpp.out se va scrie cel mai mic număr natural okcpp
care este mai mare sau egal cu N .

Restricţii şi precizări

a 1 & P & 10
a P & K & numărul de cifre al lui N & 18
a Pentru 20% din teste cerinţa va fi C 1
Pentru cerinţa C 1 vom avea 0 & A $ B $ 10 şi B  A & 10000
18
a
a Pentru cerinţa C 2 se garantează că există ı̂ntotdeauna soluţie

Exemple:

okcpp.in okcpp.out
Explicaţii
1 3 Avem K 4 şi P 2. ı̂n intervalul [99997;100001] sunt trei
5 2 99997 100001 numere okcpp: 99997, 99998 şi 100001.
2 100023 Avem K 5, P 3 şi N 99997. Se observă uşor că
5 3 99997 numerele 99997, 99998 , ..., 100022 nu corespund. Primul
număr care corespunde cerinţelor este 100023.
Timp maxim de executare/test: 0.1 secunde
Memorie: total 32 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 10 KB

6.3.1 Indicaţii de rezolvare

autor - prof. Pit-Rada Ionel-Vasile


CAPITOLUL 6. ONI 2017 6.3. OKCPP 127

Soluţie 1 Pit-Rada Mihail-Cosmin


Pentru a verifica proprietatea ”okcpp” a unui num’u ar putem proceda astfel:
- dacă P 1, atunci numărul are proprietatea ”okcpp”
- dacă P % 1, atunci putem calcula suma S a celor mai mari P  1 frecvenţe de apariţie ale
cifrelor din număr şi dacă S $ K şi există cel puţin P cifre distincte ı̂n numărul dat, atunci
numărul are proprietatea ”okcpp”, iar ı̂n caz contrar nu are proprietatea cerută
Cerinta C=1
Se poate verifica fiecare număr din intervalul A, B  şi se contorizează numerele care au pro-
prietatea ”okcpp”
Cerinţa C=2
Presupunem că N are M cifre.
Dacă numărul N verifică proprietatea, atunci va fi afişat, iar algoritmul se opreşte.
Daca N nu verifică proprietatea, atunci pentru P ' 2 se ı̂ncearcă păstrarea unui prefix cât mai
lung a lui N .
Fie L lungimea prefixului curent
(a) se ı̂ncearcă ı̂nlocuirea cifrei din N aflată la poziţia L  1 cu o cifră mai mare astfel ı̂ncât
construcţia să aibă proprietatea ”okcpp” şi apoi se completează construcţia la poziţiile L  2, L  3,
..., M completând lexicografic minim şi corect până la obţinerea lungimii M ; se pastrează soluţia
găsită şi se opreste algoritmul;
(b) dacă cu prefixul de lungime L nu este posibilă construirea unei soluţii, atunci se continuă
căutarea cu un prefix mai scurt;
Dacă pe parcurs a fost reţinută o soluţie aceasta se afişează.
În caz contrar se construieşte o soluţie de lungime M  1 minimă lexicografic şi cu proprietatea
”okcpp”.
Se garantează că există sigur soluţie.

6.3.2 Cod sursă

Listing 6.3.1: okcpp 97p.cpp


#include<fstream>
#include<iostream>
#include<cstring>
#include<cstdlib>

using namespace std;

ifstream fin("okcpp.in");
ofstream fout("okcpp.out");

int C,T,K,P;
long long A,B,X,N;
int v[10002], nv, x[10002], nx;

int verificare(int a[], int na)


{
int f[11]={0}, i, ok, aux, w[11]={0},nw, s1, s2, s3, h, r, z;
for(i=1;i<=na;i++)f[a[i]]++;
nw=0;
for(i=0;i<=9;i++)
if(f[i]>0)
nw++;w[nw]=f[i];

do
{
ok=1;
for(i=1;i<nw;i++)
if(w[i]<w[i+1])
ok=0; aux=w[i]; w[i]=w[i+1]; w[i+1]=aux;
} while(!ok);

s1=0;
for(i=1;i<=P-1;i++) s1=s1+w[i];
if((nw>=P-1 && s1<=K-1)||(nw<P-1 && s1+P-1-nw<=K-1))
{
s2=0;
CAPITOLUL 6. ONI 2017 6.3. OKCPP 128

for(i=P;i<=9;i++) s2=s2+w[i];

w[0]=1000;
s3=s1;
i=min(nw+1,P-1);
h=w[i];
while(i>=1 && (K-1-s3)/(P-i)+h>=w[i-1])
{
r=(K-1-s3)/(P-i);
if(r+h>w[i-1]) r=w[i-1]-h;
s3=s3+r*(P-i);
i--;
h=w[i];
}

r=(K-1-s3)/(P-i);
if(r+h>w[i-1]) r=w[i-1]-h;
h=h+r;
s3=s3+r*(P-i);
z=(K-1-s3)%(P-i)+s3+h*(10-(P-1));
if(z<nv) return 0;
return 1;
}
else
return 0;
}

int main()
{
int i,j,l,ok,r,i1,i2,ok1,aux,c;
fin>>C;

if(C==1)
{
fin>>K>>P>>A>>B;
c=0;
for(N=A;N<=B;N++)
{
X=N;
nv=0;
do
{
v[++nv]=X%10;
X=X/10;
} while(X);

for(i=1;i<=nv/2;i++)
{
aux=v[i];
v[i]=v[nv+1-i];
v[nv+1-i]=aux;
}

if(P==1 || verificare(v,nv)==1)
c++;
}

fout<<c;
}

if(C==2)
{
fin>>K>>P>>N;
X=N;
nv=0;
do
{
v[++nv]=X%10;
X=X/10;
} while(X);

for(i=1;i<=nv/2;i++)
{
aux=v[i];
v[i]=v[nv+1-i];
v[nv+1-i]=aux;
CAPITOLUL 6. ONI 2017 6.3. OKCPP 129

ok=0;
if(P==1 || verificare(v,nv)==1)
{
fout<<N<<"\n";
ok=1;
}

if(ok==0)
{
nx=0;
for(i=1;i<=nv;i++)
if(v[i]<9)
nx++;x[nx]=i;

i1=1; i2=nx;
while(i1<=i2)
{
i=(i1+i2)/2;
///pastrez prefix de lungime i, crescand a i-a cifra
ok1=0;
aux=v[x[i]];
for(l=v[x[i]]+1;l<=9 && 0==ok1;l++)
{
v[x[i]]=l;
if(verificare(v,x[i])==1)
ok1=1;
}

v[x[i]]=aux;
if(ok1==1)
i1=i+1;
else
i2=i-1;
}

i=x[i2];
for(l=v[i]+1;l<=9 && i>=1 && 0==ok;l++)
{
v[i]=l;
if(verificare(v,i)==1)
{
for(r=i+1;r<=nv;r++)
{
v[r]=0;
while(verificare(v,r)==0)
v[r]++;
}

ok=1;
for(j=1;j<=nv;j++)
fout<<v[j];

fout<<"\n";
}
}
}

if(ok==0)
{
v[1]=1;
if(verificare(v,1)==1)
{
nv++;
for(r=2;r<=nv;r++)
{
v[r]=0;
while(verificare(v,r)==0)
v[r]++;
}
ok=1;
for(j=1;j<=nv;j++)
fout<<v[j];

fout<<"\n";
CAPITOLUL 6. ONI 2017 6.3. OKCPP 130

}
}
///if(ok==0) fout<<-1<<"\n";
}

fout.close();
fin.close();
return 0;
}

Listing 6.3.2: okcpp 100p 1 doar linux.cpp


// doar linux
#include <vector>
#include <string>
#include <fstream>
#include <numeric>
#include <algorithm>

using namespace std;

bool isGoodFrequency(vector<int> C, int K, int P)


{
sort(C.begin(), C.end(), greater<int>());
if (C[P - 1] == 0) return false;
return (accumulate(C.begin(), C.begin() + P - 1, 0) < K);
}

bool isGood(string &S, int K, int P)


{
vector<int> C(10);
for (char d : S) C[d - ’0’]++;
return isGoodFrequency(C, K, P);
}

bool canFill(vector<int> C, int K, int P, int N)


{
while (N--) ( *min_element(C.begin(), C.end()))++;
return isGoodFrequency(C, K, P);
}

string findNext(string S, int K, int P)


{
if (isGood(S, K, P)) return S;

vector<int> C(10);
for (char d : S) C[d - ’0’]++;

int i = S.length() - 1;
bool found = false;
for (; i >= 0; i--)
{
C[S[i] - ’0’]--;
for (char d = S[i] + 1; d <= ’9’; d++)
{
C[d - ’0’]++;
if (canFill(C, K, P, S.length() - i - 1))
{
found = true;
S[i] = d;
break;
}
C[d - ’0’]--;
}

if (found) break;
}

if (i < 0)
{
S = string(S.length() + 1, ’0’);
S[0] = ’1’;
i = 0;
C[1]++;
if (!canFill(C, K, P, S.length() - 1)) return "-1";
CAPITOLUL 6. ONI 2017 6.3. OKCPP 131

for (i++; i < S.length(); i++)


for (char d = ’0’; d <= ’9’; d++)
{
C[d - ’0’]++;
if (canFill(C, K, P, S.length() - i - 1))
{
S[i] = d;
break;
}
C[d - ’0’]--;
}

return S;
}

int main()
{
ifstream f("okcpp.in");
ofstream g("okcpp.out");

int C, K, P, ns;
long long A, B, X;
string S;

f >> C ;
if(C == 1)
{
f >> K >> P >> A >> B;
ns = 0;
for( X = A; X <= B ; X ++)
{
S = to_string(X);
if(isGood(S, K, P))
{
ns ++;
}
g << ns << endl;
}
}
else
{
f >> K >> P >> S;
g << findNext(S, K, P) << endl;
}

return 0;
}

Listing 6.3.3: okcpp 100p 2.cpp


// Alexandru Velea

#include <cassert>
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

typedef long long int64;

string AddStuffAtEnd(string start, vector<int> initial_digits,


vector<int> final_digits)
{
for (int i = 0; i < 10; i += 1)
final_digits[i] -= initial_digits[i];

for (int i = 0; i < 10; i += 1)


for (int j = 0; j < final_digits[i]; j += 1)
start += char(’0’ + i);

return start;
}
CAPITOLUL 6. ONI 2017 6.3. OKCPP 132

vector<int> ans;

bool IsOk(vector<int> digits, int k, int p)


{
sort(digits.begin(), digits.end());
reverse(digits.begin(), digits.end());

for (int i = 0; i + 1 < p; i += 1)


k -= digits[i];

if (k > 0)
return true;
else
return false;
}

bool Possible(vector<int> digits, int k, int p, int remaining)


{
for (int i = 0; i < remaining; i += 1)
( *(min_element(digits.begin(), digits.end())))++;

return IsOk(digits, k, p);


}

void SolveThis(vector<int> digits, int digit, int k, int p,


int remaining, bool& ok)
{
if (digit == 10)
{
if (remaining != 0)
return;

ok = true;
ans = digits;
return ;
}

for (int current = remaining; current >= 0; current -= 1)


{
digits[digit] += current;
if (Possible(digits, k, p, remaining - current))
{
SolveThis(digits, digit + 1, k, p, remaining - current, ok);
auto d = digits;
if (ok)
return;
}
digits[digit] -= current;
}

return;
}

void SolveFixed(vector<int> digits, int last_digit, int k, int p,


int remaining, bool& ok, int& digit)
{
for (int current_digit = last_digit + 1;
current_digit < 10;
current_digit += 1)
{
digits[current_digit] += 1;
SolveThis(digits, 0, k, p, remaining - 1, ok);
digits[current_digit] -= 1;

if (ok)
{
digit = current_digit;
return;
}
}

return;
}

string Solve(int k, int p, string n)


CAPITOLUL 6. ONI 2017 6.3. OKCPP 133

{
vector<int> digits(10, 0);
for (char itr : n)
digits[itr - ’0’] += 1;

if (IsOk(digits, k, p))
return n;

int to_match = 0;
while (n.size())
{
to_match += 1;
digits[n.back() - ’0’] -= 1;

bool ok = false;
int digit = 0;
SolveFixed(digits, n.back() - ’0’, k, p, to_match, ok, digit);
n.pop_back();

if (ok)
{
digits[digit] += 1;
n += char(’0’ + digit);
return AddStuffAtEnd(n, digits, ans);
}
}

digits = vector<int>(10, 0);


digits[1] += 1;

bool ok = false;
int digit = 0;
SolveFixed(digits, -1, k, p, to_match, ok, digit);

if (ok)
{
digits[digit] += 1;
n = "1";
n += char(’0’ + digit);
return AddStuffAtEnd(n, digits, ans);
}
else
return "-1";
}

bool IsOk(int64 n, int k, int p)


{
vector<int> digits(10, 0);
do
{
digits[n % 10]++;
n /= 10;
} while (n);

return IsOk(digits, k, p);


}

int Solve1(int k, int p, int64 left, int64 right)


{
int a = 0;
for (int64 i = left; i <= right; i += 1)
a += IsOk(i, k, p);

return a;
}

int main()
{
freopen("okcpp.in", "r", stdin);
freopen("okcpp.out", "w", stdout);

int c;
cin >> c;

if (c == 2)
{
CAPITOLUL 6. ONI 2017 6.4. ADLIC 134

int k, p;
string n;
cin >> k >> p >> n;
cout << Solve(k, p, n) << ’\n’;
}
else
{
int k, p;
int64 left, right;
cin >> k >> p >> left >> right;
cout << Solve1(k, p, left, right) << ’\n’;
}

return 0;
}

6.3.3 *Rezolvare detaliată

6.4 adlic
Problema 4 - adlic 100 de puncte
Pentru următorul an şcolar admiterea celor N elevi ı̂n liceu se va face pe baza unor evaluări
complexe.
Fiecare dintre viitorii elevi ai clasei a IX-a va primi, ı̂n urma testelor şi probelor pe care le va
sus ţine, un punctaj (număr natural nenul) cu care va participa la admiterea electronică.
Repartizarea fiecărui elev ı̂n clase se face ı̂n ordinea ı̂nscrierii respectând criteriile:
a Primul elev se repartizează ı̂n clasa cu numărul de ordine 1.
a ı̂n clasa ı̂n care este repartizat un elev nu există, până la momentul repartizării sale, nici un
punctaj mai mare decât al său.
a Numărul claselor să fie cât mai mic posibil.

Cerinţe

Determinaţi:
1. Punctajul primului elev care nu ar mai putea fi repartizat ı̂n prima clasă ı̂n condiţiile ı̂n
care toţi elevii ı̂şi doresc să fie repartizaţi ı̂n prima clasă (se aplică doar la cerinţa 1).
2. Numărul claselor ce se vor forma respectând criteriile.

Date de intrare

Fişierul de intrare adlic.in conţine pe primul rând numărul C a cărui valoare poate fi 1 sau
2, apoi separat de un spaţiu numărul natural N .
Pe liniile următoare se găsesc cele N punctaje ale elevilor ı̂n ordinea ı̂nscrierii, numere naturale
nenule despărţite prin câte un spaţiu.

Date de ieşire

Dacă C 1, atunci ı̂n fişierul de ieşire adlic.out se va scrie soluţia cerinţei 1.


Dacă C 2, atunci ı̂n fişierul de ieşire adlic.out se va scrie soluţia cerinţei 2.

Restricţii şi precizări

a Punctajele elevilor vor avea cel mult şase cifre


a 1 & N & 1 000 000
a Pentru cerinţa 1 se garantează existenţa soluţiei
a Pentru 20% din punctaj cerinţa va fi C 1
a Pentru alte 20% din punctaj cerinţa va fi C 2 şi N & 1000
a Pentru restul testelor C 2 şi N & 1000 000

Exemple:
CAPITOLUL 6. ONI 2017 6.4. ADLIC 135

adlic.in adlic.out
Explicaţii
19 2 4 se repartizează ı̂n prima clasă, iar 2 trebuie repartizat ı̂n
4 2 4 2 7 10 9 11 8 cea de-a doua clasă
29 3 O soluţie posibilă este cea ı̂n care se formează clasele:
4 2 4 2 7 10 9 11 8 4479
2 2 10 11
8
Timp maxim de executare/test: 1.5 secunde
Memorie: total 32 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 10 KB

6.4.1 Indicaţii de rezolvare

1. Se parcurge fişierul adlic.in determinându-se primul termen mai mic decât predecesorul
său - complexitate O n
2. Se formează vectorul v ı̂n care se reţine ultimul element al subşirurilor crescătoate. V este
creat descrescător. La citirea din fişier elementul curent actualizează v ı̂nlocuind primul element
mai mic decât el. Dacă un asemenea element nu există se crează o nouă poziţie ı̂n v. Căutarea ı̂n
2
v se face binar. Complexitate nlog n. În cazul căutării ı̂n n se obţine punctaj parţial.

6.4.2 Cod sursă

Listing 6.4.1: adlic 100p 1.cpp


#include <iostream>
#include <fstream>

using namespace std;

ifstream f("adlic.in");
ofstream g("adlic.out");

int v[1000001], n=0;

int Bins(int l,int h, int w)


{
int m;
while(l<h)
{
m=(l+h)/2;
if (w>=v[m])
h=m;
else l=m+1;
}
if(l>h or w<v[h])
return h+1;
else
return h;
}
int main()
{
int c,w,k,uw=0,i,m;
f>>c>>m;
if(c==1)
{
for(i=1;i<=m;i++)
{
f>>w;
if(w>=uw)
uw=w;
else
g<<w;i=m;
}
}
else
for(i=1;i<=m;i++)
{
CAPITOLUL 6. ONI 2017 6.4. ADLIC 136

f>>w;
k=Bins(1,n,w);

if(k==n+1)
v[++n]= w;
else
v[k]=w;

g<<n;
}

return 0;
}

Listing 6.4.2: adlic 100p 2.cpp


// Autor: Mihai Enache
#include <fstream>

using namespace std;

const int MAX_N = 1000002;

int N, cerinta;
int punctaje[MAX_N], ultim[MAX_N];

int main()
{
ifstream f("adlic.in");
ofstream g("adlic.out");

f >> cerinta >> N;


for(int i = 1; i <= N; ++i)
f >> punctaje[i];

if(cerinta == 1)
{
int maxP = 0;
int ans = 0;
for(int i = 1; i <= N; ++i)
if(punctaje[i] < punctaje[i - 1])
{
ans = punctaje[i];
break;
}

g << ans << "\n";


}
else
{
int nrClase = 0;
for(int i = 1; i <= N; ++i)
{
int l = 1;
int r = nrClase;
int clasa = 0;
while(l <= r)
{
int m = (l + r) / 2;

if(ultim[m] <= punctaje[i])


{
clasa = m;
r = m - 1;
}
else
l = m + 1;
}

if(clasa)
ultim[clasa] = punctaje[i];
else
{
++nrClase;
ultim[nrClase] = punctaje[i];
CAPITOLUL 6. ONI 2017 6.4. ADLIC 137

}
}

g << nrClase << "\n";


}

f.close();
g.close();

return 0;
}

Listing 6.4.3: adlic 100p 3.cpp


#include <iostream>
#include <fstream>

using namespace std;

long mx[1000000];

int main()
{
ifstream in("adlic.in");
ofstream out("adlic.out");

int V,rep;
long p,i=0,k,st,dr, max, m; //j;
in>>V;
if(V==1) // cerinta 1
{
in>>p; max=p; k=1;
while(!in.eof())
{
in>>p;
if(p>=max)
max=p;
else
{
out<<p;
break;
}
}

//cout<<k;
}
else // cerinta 2
{
k=0;
while(in>>p)
{
st=1;dr=k;
while(st<=dr)
{
m=(st+dr)/2;
if(p>=mx[m])
dr=m-1;
else
st=m+1;
}

if(st>k)
{
k++;
mx[k]=p;
}
else
{
mx[st]=p;
}
}

out<<k;
// cout<<k;
}
CAPITOLUL 6. ONI 2017 6.5. BOMBOANE 138

in.close();
out.close();
return 0;
}

6.4.3 *Rezolvare detaliată

6.5 bomboane
Problema 5 - bomboane 100 de puncte
Zeno are n cutii cu bomboane, iar ı̂n fiecare cutie se găseşte un număr natural nenul de
bomboane. Zeno poate ı̂mpărţi bomboanele din toate cutiile colegilor ı̂n două moduri: frăţeşte
sau diferenţiat. ı̂mpărţirea frăţească se realizează astfel:
- numărul de colegi care primesc bomboane din fiecare cutie este acelaşi (dacă din prima cutie
primesc bomboane k colegi şi din cutia 2 vor primi tot k colegi, şi din cutia 3 tot k colegi etc).
- bomboanele din fiecare cutie se ı̂mpart ı̂n mod egal ı̂ntre cei k colegi, aceştia primind un
număr nenul de bomboane;
- ı̂n final ı̂n fiecare cutie trebuie să rămână un număr identic de bomboane (posibil zero) care
ı̂i revin lui Zeno. De exemplu dacă n 3, iar ı̂n cutii se găsesc 14, 23 respectiv 17 bomboane, din
prima cutie oferă câte 4 bomboane pentru 3 colegi, din a doua cutie câte 7 bomboane pentru 3
colegi, iar din ultima cutie câte 5 bomboane pentru 3 colegi, iar ı̂n fiecare cutie rămân 2 bomboane.
ı̂mpărţirea diferenţiată se realizează ı̂n felul următor:
- dintre colegii care primesc bomboane din aceeaşi cutie fiecare coleg primeşte un număr diferit
de bomboane (număr nenul), neexistând doi colegi care primesc număr identic de bomboane din
aceeaşi cutie;
- din fiecare cutie Zeno oferă bomboane unui număr cât mai mare de colegi.
- diferenţele ı̂n modul dintre numărul de bomboane primite consecutiv de doi colegi sunt
distincte două câte două. De exemplu dacă n 3, iar ı̂n cutii se găsesc 14, 23 respectiv 17
bomboane, bomboanele din prima cutie se pot ı̂mpărţi astfel (3, 4, 6, 1), bomboanele din a doua
cutie (6, 2, 7, 1, 3, 4), iar bomboanele din a treia cutie se pot ı̂mpărţi astfel (2, 1, 3, 7, 4).

Cerinţe

Cunoscând n numărul de cutii şi numărul de bomboane din fiecare cutie să se scrie un program
care determină:
a) Numărul maxim de colegi care pot primi bomboane, dacă Zeno alege ı̂mpărţirea frăţească.
b) O modalitate de ı̂mpărţire a bomboanelor din fiecare cutie, dacă se face ı̂mpărţirea
diferenţiată.

Date de intrare

Fişierul de intrare bomboane.in conţine pe prima linie două numere naturale p (numărul
cerinţei de rezolvat), şi n (numărul de cutii), despărţite printr-un spaţiu. Pe următoarea linie se
găsesc n valori naturale, separate prin câte un spaţiu, reprezentând numărul de bomboane din
fiecare cutie.

Date de ieşire

Dacă p 1 se va rezolva numai punctul a) din cerinţă. În acest caz fişierul de ieşire bom-
boane.out va conţine o valoare naturală reprezentând numărul maxim de colegi care pot primi
bomboane, dacă Zeno alege ı̂mpărţirea frăţească.
Dacă p 2 se rezolvă numai punctul b). Fişierul de ieşire bomboane.out va conţine n linii. Pe
fiecare linie i, prima valoare nri reprezintă numărul maxim de colegi care pot primi bomboane din
cutia i, urmată de nri valori separate prin câte un spaţiu reprezentând o modalitate de ı̂mpărţire
a bomboanelor din cutia i, dacă Zeno alege ı̂mpărţirea diferenţiată.

Restricţii şi precizări


CAPITOLUL 6. ONI 2017 6.5. BOMBOANE 139

a 1 & p & 2;
Dacă p 1 atunci 1 & n & 10 000 şi 1 & numărul de bomboane din cutii & 10 .
6
a
a Dacă p 2 atunci 1 & n & 200 şi 1 & numărul de bomboane din cutii & 100 000.
a Dacă există mai multe soluţii se poate afişa oricare.
a Pentru rezolvarea fiecărei cerinţe se acordă 50% din punctaj.

Exemple:

bomboane.in bomboane.outExplicaţii
13 3 Se rezolvă numai punctul a). Numărul maxim de colegi care
14 23 17 pot primi bomboane dacă Zeno alege ı̂mpărţirea frăţească e 3.
2 3 14 23 17 43461 Se rezolvă numai punctul b). Din prima cutie pot primi bom-
6 6 2 7 1 3 4 boane maxim 4 colegi. O modalitate de ı̂mpărţire astfel ı̂ncât
521374 fiecare coleg să primească un număr diferit de bomboane, iar
diferenţele dintre bomboanele primite de doi colegi consecutivi
să fie distincte două câte două este (3,4,6,1). Este corectă şi
soluţia (1, 2, 7, 4).
Timp maxim de executare/test: 0.2 secunde
Memorie: total 16 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 10 KB

6.5.1 Indicaţii de rezolvare

a) ı̂mpărţirea frăţească
fie a[i] - numarul de bomboane din cutia i
Metoda 1 - 10% din punctajul cerinţei
Se generează toate valorile pentru numărul de colegi şi se verifică dacă numărul de bomboane
din fiecare cutie ı̂mpărţit la numărul de colegi dă acelaşi rest.
Complexitate O(N*max(a[i])
Metoda 2 - 30% din punctajul cerinţei
Se observă că numărul de bomoane care pot rămâne ı̂n fiecare cutie este cuprins ı̂ntre 0 şi
min(a[i]).
Complexitate O(N*min(a[i])
Metoda 3 - 100% din punctaj
fie a[i] - numarul de bomboane din cutia i
Trebuie determinat un numar k astfel incat fiecare a[i] sa dea acelasi rest la impartirea la k.

a[1] = x1 * k + r
a[2] = x2 * k + r
......
a[n] = x3 * k + r

Daca facem diferenta intre oricare doua elemente ale vectorului a[i] si a[j] obtinem:
a[i] - a[j] = xi * k + r - (xj * k + r) = (xi - xj) * k
Prin urmare diferenta intre doua elemente ale vectorului este multiplu de k.
Pentru k maxim acesta va fi cmmdc(a[1] - a[2], a[2] - a[3], a[3] - a[4] ... a[n-1] - a[n]).
Un algoritmul de rezolvare:
- pentru a evita valori negative se determina min = min(a[1], a[2] ... a[n])
- se scade min din elementele vectorului a[1] = a[1] - min, a[2] = a[2] - min ... a[n] = a[n] - min
- se determina k = cmmdc(a[1], a[2] ... a[n]).
b) ı̂mpărţirea diferenţiată
Fie ci numarul maxim de colegi care pot primi bomboane din cutia i
Pentru a determina ci se determina cea mai mare valoare pentru care
ci * (ci + 1) / 2 ¡= a[i]
Metoda 1 - 30% din punctaj
Se calculează numărul maxim de elevi care pot primi bomboane dintr-o cutie. Se observă că
pentru ca acest număr să fie maxim trebuie găsiţi cei mai mici termeni, respectiv 1, 2, 3 ... Se
ı̂ncearcă formarea unui vector cu numărul de bomboane distribuit. Se procedează asemănător
CAPITOLUL 6. ONI 2017 6.5. BOMBOANE 140

sortării prin inserţie. Se adaugă ultimul termen la sfârşitul vectorului. Dacă se obţine o diferenţă
deja creată, acesta se deplasează spre ı̂nceputul vectorului până când diferenţele sunt distincte.
Metoda 2
Cea mai simplă modalitate de a afişa o ı̂mpărtire este
1 ci 2 ci-1 3 ci-2 .... (de exemplu pt 10 bomboane 1, 4, 2, 3)
La afişarea primei perechi trebuie avut ı̂n vedere faptul că dacă ci * (ci + 1) ¡ a[i] atunci se
afişează 1 şi ci + diferenţa (a[i] - ci * (ci+1) / 2
De exemplu pentru a[i] = 10 se determină ci = 4.
Împărţirea diferenţiată 1 4 2 3
Pentru a[i] = 12 ı̂mpărţirea diferenţiată 1 6 2 3 (prima pereche 1 şi 4 + 12 - 10)
Algoritmul

l <- 1 //pornim de la marginea stanga


r <- ci //pornim de la marginea dreapta

cat timp l <= r executa


daca suntem la prima pereche
afiseaza l si k + a[i] - ci * (ci + 1) / 2
altfel
afiseaz l si r

l <- l + 1//trecem la urmatoarele valori


r <- r - 1
sf cat timp

Datorită faptului că la fiecare pas diferenţa ı̂ntre 2 elemente consecutive scade, se respectă
cerinţa ca elementele consecutive să aibă diferenţa distinctă.

6.5.2 Cod sursă

Listing 6.5.1: bomboane 100p 1.cpp


#include <iostream>
#include <fstream>

using namespace std;

ifstream in("bomboane.in");
ofstream out("bomboane.out");

int n,p,a[10002],i,k;

int cmmdc(int a, int b)


{
int r;
while(b > 0)
{
r = a % b;
a = b;
b = r;
}
return a;
}

int nr_max_col(int n)
{
//determinam valoarea maxima pentru k, a.i k * (k + 1) / 2 <= n
int k;
for (k = 1; ; k++)
if (k * (k + 1) / 2 > n)
return k-1;
}

int main()
{
in >> p >> n;
CAPITOLUL 6. ONI 2017 6.5. BOMBOANE 141

for(i = 1; i <= n; i++) in >> a[i];

if(p == 1)
{
int vmin = a[1];

//determinam vmin = min(a[1], a[2] ... a[n])


for(i = 1; i <= n; i++)
if(vmin > a[i]) vmin = a[i];

//scadem vmin din elementele vectorului a


for(i = 1; i <= n; i++) a[i] = a[i] - vmin;

//dereminam k = cmmdc(a[1], a[2] ... a[n])


k = 0;
for(i = 1; i <= n; i++)
k = cmmdc(a[i], k);

out << k;
}
else
{
int l, r;
for(i = 1; i <= n; i++){
k = nr_max_col(a[i]); // determinam valoarea maxima pentru k,
// a.i k * (k + 1) / 2 <= a[i]

out << k << " ";


// cea mai simpla modalitate este sa se afiseze 1 k 2 k-1 3 k-2
// etc
// la afisarea primei prechi trebuie avut in vedere ca daca
// k * (k + 1) < n
//atunci se afiseaza 1 si k + diferenta (a[i] - k * (k+1) / 2

l = 1; // se porneste cu 2 indici l = 1
r = k; // si r = k si se afiseaza alternativ

//dupa fiecare afisare l creste, iar r scade


while(l <= r)
{
if(l != r)
{
out << l << " ";
if(r == k) out << r + a[i] - k * (k+1) / 2<< " ";
else out << r << " ";
}
else
out << l << " ";

l = l + 1;
r = r - 1;
}

out << "\n";


}
}

return 0;
}

Listing 6.5.2: bomboane 100p 2.cpp


#include <iostream>
#include <fstream>
#include <math.h>

using namespace std;

long cmmdc(long a, long b)


{
while(a!=b)
if(a>b)
a=a-b;
else
b=b-a;
CAPITOLUL 6. ONI 2017 6.5. BOMBOANE 142

return a;
}

int main()
{
ifstream in("bomboane.in");
ofstream out("bomboane.out");

int p,n,i,k=0,j,nb;
long b[10001], c[10001], min, s, r, st, dr;
in>>p>>n;
// cout<<n<<endl;
if(p==1) //cerinta 1
{
in>>b[1]; min=b[1];
for(i=2;i<=n;i++)
{
in>>b[i];
if(b[i]<min)
min=b[i];
}

for(i=1;i<=n;i++)
b[i]=b[i]-min;
i=1;
while(b[i]==0)
i++;
k=b[i];
for(i=2;i<=n;i++)
if(b[i]!=0)
k=cmmdc(k,b[i]);
out<<k;
}
else //cerinta 2
{
for(i=1;i<=n;i++)
{
in>>b[i];
c[i]=sqrt(2*b[i]);
if(c[i]*(c[1]+1)>2*b[i])
c[i]--;

s=0;
j=0;
do
{
j++;
s=s+j;
} while(s<=b[i]);

if(s==b[i])
r=j;
else
{
s=s-j;
j--;
r=j+b[i]-s;
}

st=1;
dr=j;
p=1;
out<<j<<’ ’;
while(st<=dr)
{
if(p==1)
out<<1<<’ ’<<r<<’ ’;
else
if(st==dr)
out<<st<<’ ’;
else
out<<st<<’ ’<<dr<<’ ’;

st++;
dr--;
p++;
CAPITOLUL 6. ONI 2017 6.5. BOMBOANE 143

out<<endl;
}
}

in.close();
out.close();
return 0;
}

Listing 6.5.3: bomboane 100p 3.cpp


#include<fstream>
#include<cmath>

using namespace std;

ifstream fin("bomboane.in");
ofstream fout("bomboane.out");

int p,n,a[10005];

int cmmdc(int a, int b)


{
if(b==0)
return a;
return cmmdc(b,a%b);
}

void impf()
{
int amin=a[1], i, k;
for(i=2;i<=n;i++)
amin=min(amin,a[i]);
k=a[1]-amin;
for(i=2;i<=n;i++)
k=cmmdc(k,a[i]-amin);
fout<<k;
}

void d(int s)
{
int a,i,r=(sqrt(8*s+1)-1)/2;
a=(r+1)/2;
fout << r << " " << a << " ";
s=s-a;
for(i=1;i<=(r-2)/2;i++,s=s-2*a)
{
fout << a+i << " " << a-i << " ";
}

if(r%2==0)
fout << s << "\n";
else
fout << s-1 << " 1\n";
}

void impd()
{
for(int i=1;i<=n;i++)
d(a[i]);
}

int main()
{
fin>>p>>n;
for(int i=1;i<=n;i++)
fin>>a[i];
if(p==1)
impf();
else
impd();

fout.close();
CAPITOLUL 6. ONI 2017 6.6. ORASE 144

return 0;
}

6.5.3 *Rezolvare detaliată

6.6 orase
Problema 6 - orase 100 de puncte
În tărâmul Jupânului există N + 1 oraşe. Acestea au fost construite ı̂n linie dreaptă, ı̂ncepând
cu cel ı̂n care este casa Jupânului. Între oricare 2 oraşe consecutive s-a construit câte un drum.
Pentru fiecare drum, se cunoaşte lungimea lui, exprimată ı̂n metri şi viteza cu care se poate
parcurge, exprimată ı̂n metri pe secundă.

Cerinţe

Jupânul trebuie să ajungă din oraşul 0 ı̂n oraşul N .


Acesta ştie că poate ı̂mbunătăţi un drum, mărindu-i viteza de la V metri pe secundă la V +
1 metri pe secundă, cu costul de 1 dolar. Acesta poate ı̂mbunătăţi un drum de mai multe ori.
Jupânul are un buget de X dolari şi ar vrea să-i folosească pentru a micşora timpul ı̂n care
ajunge din oraşul 0 ı̂n oraşul N .

Date de intrare

Fişierul de intrare orase.in are următoarea structură:


a Pe prima linie se află numărul T , reprezentând tipul de restricţii pe care ı̂l respectă testul.
a Pe a doua linie, se află 2 numere naturale N şi X.
a Pe a treia linie se află N numere naturale, al i-lea număr reprezentând distanţa ı̂ntre oraşele
i  1 şi i.
a Pe a patra linie se află N numere naturale, al i-lea număr reprezentând viteza ı̂ntre oraşele
i  1 şi i.

Date de ieşire

Fişierul de ieşire orase.out va conţine pe prima linie un număr natural R ce reprezintă partea
ı̂ntreagă a timpului minim de parcurgere a distanţei dintre oraşul 0 şi oraşul N .

Restricţii şi precizări

1 & N & 5 10
4
a
a 1 & X & 10
7
4
a lungimea drumului dintre oricare 2 oraşe este un număr natural din intervalul 1, 10 
4
a viteza iniţială dintre oricare 2 oraşe consecutive este un număr natural din intervalul 1, 10 
Tipul 1: pentru 5% din punctaj N & 10 şi X & 10
Tipul 2: pentru alte 10% din punctaj N & 10 şi X & 10
3 3

Tipul 3: pentru alte 15% din punctaj 1 & N & 5 10 , 1 & X & 10 , distanţele sunt mai mici
4 4

decât 200 şi se garantează că vitezele finale vor fi mai mici sau egale decât 1000
Tipul 4: pentru alte 20% din punctaj 1 & N & 5 10 , 1 & X & 10 şi toate distanţele sunt
4 7

egale
Tipul 5: pentru restul de 50% din punctaj 1 & N & 5 10 şi 1 & X & 10
4 7

Exemple:
CAPITOLUL 6. ONI 2017 6.6. ORASE 145

orase.in orase.out Explicaţii


1 3 Timpul minim este 3.65, iar rezultatul este [3.65]=3
35 Vitezele finale vor fi 4, 3, 5
537 5 / 4 + 3 / 3 + 7 / 5 = 3.65
214
1 4 Timpul minim este 4.321, iar rezultatul este [4.321]=4
46 Vitezele finale vor fi 4, 7, 7, 5
3 8 10 5 3 / 4 + 8 / 7 + 10 / 7 + 5 / 5 = 4.32142857
4373
1 4 Timpul minim este 4.65, iar rezultatul este [4.65]=4
56 Vitezele finale vor fi 5, 4, 3, 3, 3
25324 2 / 5 + 5 / 4 + 3 / 3 + 2 / 3 + 4 / 3 = 4.65
51213
Timp maxim de executare/test: 1.0 secunde
Memorie: total 32 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 15 KB

6.6.1 Indicaţii de rezolvare

Velea Alexandru

Structura solutiei
1) Observaţii generale
2) Soluţie ı̂n O N ˜ X  - 15 puncte
3) Soluţie restricţii de tipul 3 - 15 puncte
4) Soluţie restricţii de tipul 4 - 20 puncte
5) Soluţia oficială - 100 puncte

Pentru un drum care are lungime D şi viteza V, timpul de deplasare este de D/V
Dacă mărim viteza drumului de la V la (V+1), timpul de deplasare devine D/(V+1)
Astfel, timpul salvat ı̂n urma măririi vitezei este de (D/V) - (D/(V+1)) = D/(V*(V+1))
Se observă că prima ı̂mbunătăţire salvează D/(V*(V+1)) secunde, a doua ı̂mbunătăţire
D/((V+1)*(V+2)), etc
Astfel, fiecare ı̂mbunătăţire salvează din ce ı̂n ce mai puţin timp.
2) Soluţie ı̂n O N ˜ X 
Folosind observaţiile de la punctul 1, putem alege o strategie greedy de ı̂mbunătăţire a dru-
murilor.
Dacă am avea de făcut o singură ı̂mbunătăţire, am alege să o facem pe drumulul unde aceasta
ar salva cât mai mult timp.
Acesta fiind drumul cu D/(V*(V+1)) maxim.
Astfel, putem demonstra că tot timpul este bine să alegem să ı̂mbunătăţim drumul care ne
salvează cât mai mult timp.
Dacă am avea de făcut doar o ı̂mbunătăţire, e bine să alegem acel drum. După ce l-am ales,
următoarea ı̂mbunătăţire va fi mai mică decât cea precedentă, iar acest lucru ne garantează faptul
că dacă am putea să facem acest lucru acum, nu am avea niciun motiv să facem ı̂mbunătăţirea pe
drumul respectiv mai tarziu.
Iteram de X ori peste toate drumurile, la fiecare pas alegem drumul care salvează cel mai mult
timp şi ı̂l ı̂mbunătăţim.
3) Soluţia pentru tipul 3
Observăm că dacă am vrea să ı̂mbunătăţim un drum care are distanţa D, este cel mai bine să
ı̂mbunătăţim drumul care are viteza minimă, pentru a maximiza timpul câştigat.
Astfel, faptul că distanţele sunt mai mici decât 200, permite o optimizare a algoritmului de la
pasul 2.
În loc să iterăm la fiecare pas prin toate drumurile şi să ı̂l alegem pe cel care salvează cât mai
mult timp, putem itera prin distanţele drumurilor, aflăm care este viteza minimă a unui drum
cu distanţa respectivă, iar mai apoi ı̂mbunătăţim unul dintre drumurile care aveau viteza minimă
pentru distanţa fixată (este posibil să fie mai multe astfel de drumuri)
Astfel, fiecare ı̂mbunătăţire se poate realiza ı̂n 200 de iteraţii, nu ı̂n N (50 000).
CAPITOLUL 6. ONI 2017 6.6. ORASE 146

Pentru a afla drumul de viteză minimă pentru o distanţă fixată, putem să reţinem drumurile
ı̂ntr-o matrice de frecvenţe
drumuri[d][v] reprezentând câte drumuri au distanţa d şi viteza v
mai ţinem un şir viteza minima[d] reprezentând cea mai mică viteză a unui drum cu distanţa
d
- echivalent cu prima valoare v pentru care drumuri[d][v] are o valoare nenulă.
Dacă la un pas am ales să ı̂mbunătăţim un drum care are distanţa d şi viteza v, trebuie să
micşorăm numărul de drumuri cu viteza v: drumuri[d][v] -= 1
Mărim numărul de drumuri cu viteza (v + 1): drumuri[d][v + 1] += 1
* dacă nu mai avem drumuri cu viteza v, mărim viteza minima[d]:
if (drumuri[d][v] == 0) { viteza\_minima[d] += 1; }
4) Soluţia pentru tipul 4
Asemănător cu tipul 3, doar că aici distanţele nu sunt până ı̂n 200, ci toate distanţele sunt
egale.
Astfel, ne interesează doar vitezele, distanţa fiind necesară doar la calcularea rezultatului.
O soluţie ar fi să ţinem un şir de frecvenţe pentru fiecare viteză, şi să aplicăm următorul
algoritm:

int i = 1;
while (x)
{
if (frecventa[i] > x)
{
frecventa[i + 1] += frecventa[i];
x -= frecventa[i];
frecventa[i] = 0;
}
else
{
frecventa[i] -= x;
frecventa[i + 1] += x;
x = 0;
}

i += 1;
}

O astfel de soluţie se comportă foarte bine când valoarea lui N este mare, dar dacă N = 1, de
exemplu, ar face X paşi.
Ca să scăpăm de cazul acesta, am putea căuta binar cea mai mică viteză.
Astfel putem afla câte ı̂mbunătăţiri trebuie să facem: suma din(max(0, viteza cautata binar -
viteza[i]))
Dacă numărul de ı̂mbunătăţiri depăşeşte X, reducem viteza, altfel o mărim.
La final, ı̂mbunătăţim toate drumurile ¡= viteza căutată pâna la ea, şi mai ı̂mbunătăţim unele
drumuri cu acea viteză (nu le putem ı̂mbunătăţi pe toate) dacă mai avem ı̂mbunătăţiri rămase.
5) Soluţia oficială
Privind ideile de la punctul 1, am putea ı̂ncerca să căutam binar valoarea ultimului timp salvat.
Notăm ultimul timp salvat cu T
Astfel, am putea calcula pentru un drum de câte ori trebuie să ı̂l ı̂mbunătăţim, ajungând la
viteza V, astfel ı̂ncât ı̂mbunătăţirea de la V la V+1 să fie ¡ T
Acest lucru se poate rezolva tot cu o căutare binară a numărului vitezei, sau folosind o ecuaţie
de gradul 2
Notăm cu D distanţa drumului; trebuie să ı̂i găsim viteza V

D / (V * (V + 1)) < T
D / T < V * (V + 1))
0 < V * V + V - D / T

Dacă vrem V minim, ajungem la ecuaţia


CAPITOLUL 6. ONI 2017 6.6. ORASE 147

0 = V * V + V - D / T
delta = 1 + 4 * D / T
V1,2 = (-1 +- sqrt(1 + 4 * D / T)) / 2

cum V e pozitiv, valoarea lui va fi


V = -0.5 + sqrt(1 + 4 * D / T) / 2
V trebuie rotunjit ı̂n sus, deoarece dacă V = 3.3 ı̂nseamnă că pentru V = 3 ı̂ncă ı̂mbunătăţirea
de la 3 la 4 este mai bună decât T.
Astfel, putem calcula ı̂n O(N) de câte ı̂mbunătăţiri am avea nevoie pentru un timp de
ı̂mbunătăţire fixat.
Căutarea binară se va face pe numere reale, la fel ca cea pe ı̂ntregi, doar că pe reale.
În loc de stanga < dreapta se vor face 40 de iteraţii de exemplu.
La final aducem toate drumurile să respecte valoarea căutată binar.
Dacă ştim că pentru 1.5000000 avem nevoie de 10 ı̂mbunătăţiri, dar pentru 1.5000001 avem
nevoie de 15 ı̂mbunătăţiri, având 13 ı̂mbunătăţiri posibile, putem spune că o să mai ı̂mbunătăţim
3 drumuri, fiecare drum salvând 1.5000001 secunde.
Nu contează exact ce drumuri ı̂mbunătăţim. Ştim că ele există, şi că sunt fix 5 care au acea
proprietate.
Acest lucru se ı̂ntâmplă când avem multe drumuri cu aceeaşi distanţă, sau cu distanţe
proporţionale.
Timp final: O N logX 
Memorie: O N 
Dacă nu facem abstracţie de faptul că funcţia sqrt este ı̂nceată, şi că ı̂n practică face tot
O log , timpul este O N logXlog de la sqrt

6.6.2 Cod sursă

Listing 6.6.1: orase 100p.cpp


// Problema Orase - Oni clasa a 9-a
// sursa Velea Alexandru
// time O(n * log2(x))
// 100 points

#include <cmath>

#include <iomanip>
#include <fstream>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

typedef long long int64;

const int kAcceptedRemaining = 2e4;

struct Road
{
int distance;
int speed;
int id;

long double SavedTime(int bonus_time = 0) const


{
return 1.0*distance / (speed+bonus_time) / (speed+1+bonus_time);
}

long double Time(int bonus_time = 0) const


{
return 1.0 * distance / (speed + bonus_time);
}

bool operator<(const Road& rhs) const


{
return SavedTime() < rhs.SavedTime();
CAPITOLUL 6. ONI 2017 6.6. ORASE 148

}
};

int64 x;
vector<Road> roads;

int Read()
{
ifstream cin("orase.in");
int test_type;
cin >> test_type;

int n;
cin >> n >> x;

roads.resize(n);

for (int i = 0; i < n; i += 1)


roads[i].id = i;

for (auto& itr : roads)


cin >> itr.distance;

for (auto& itr : roads)


cin >> itr.speed;

return 1;
}

int is_read = Read();

int64 UpgradeRoad(int index, long double saved_time)


{
return max(0LL,
int64(1.0 + -0.5 +
sqrt(1.0+4.0*roads[index].distance / saved_time) / 2.0 +
1e-10) -
roads[index].speed);
}

vector<int64> GetUpgrades(long double saved_time)


{
vector<int64> answer(roads.size(), 0);

for (int i = 0; i < (int)roads.size(); i += 1)


answer[i] = UpgradeRoad(i, saved_time);

return answer;
}

int64 GetNumOperations(long double saved_time)


{
auto upgrades = GetUpgrades(saved_time);

int64 answer = 0;
for (auto itr : upgrades)
answer += itr;

return answer;
}

long double EstimateSolution()


{
long double mx_save = 0;
for (auto& itr : roads)
mx_save = max(mx_save, itr.SavedTime());

long double left = 0, right = mx_save;

int64 mx_upgrades = 0;
long double mx_where = 0;

int64 mn_upgrades = 1e9;


long double mn_where = 0;

for (int i = 0; i < 100; i += 1)


CAPITOLUL 6. ONI 2017 6.6. ORASE 149

{
long double mid = (right + left) / 2;
int64 num_operations = GetNumOperations(mid);

if (num_operations == x)
{
mx_upgrades = num_operations;
mx_where = mid;
break;
}

if (num_operations > x)
{
left = mid;

if (mn_upgrades >= num_operations)


{
mn_upgrades = num_operations;
mn_where = mid;
}
}
else
{
right = mid;

if (mx_upgrades <= num_operations)


{
mx_upgrades = num_operations;
mx_where = mid;
}
}
}

auto upgrades = GetUpgrades(mx_where);

int64 sum = 0;
for (int i = 0; i < (int)roads.size(); i += 1)
{
roads[i].speed += upgrades[i];
sum += upgrades[i];
}

x -= sum;

double answer = 0;
for (auto& itr : roads)
answer += itr.Time();

answer -= 1.0 * mx_where * x;

return answer;
}

long double Solve()


{
return EstimateSolution();
}

int main()
{
long double answer = Solve();

ofstream cout("orase.out");
cout << int64(answer) << ’\n’;

return 0;
}

6.6.3 *Rezolvare detaliată


Capitolul 7

ONI 2016

7.1 civilizatie
Problema 1 - civilizatie 100 de puncte
În vremuri străvechi Pământul era locuit de către o civilizaţie neobişnuită condusă după reguli
matematice foarte riguroase. Această civilizaţie era formată din mai multe oraşe-stat asemeni
oraşelor antice. Fiecare oraş s-a dezvoltat treptat pornind de la un singur cartier de formă pătrată
cu suprafaţa de un hectar, ı̂n jurul căruia se adăugau ı̂n fiecare an cartiere de câte un hectar
fiecare ı̂n felul următor: ı̂n primul an s-a format cartierul iniţial, ı̂n al doilea an oraşul s-a extins
formând patru noi cartiere ı̂n toate cele patru puncte cardinale, ı̂n anul următor oraşul s-a extins
cu 8 noi cartiere dispuse ı̂n jurul cartierelor deja formate, şi aşa mai departe, ı̂n fiecare an oraşul
extinzându-se cu ı̂ncă un rând de cartiere.

Extinderea unui oraş se opreşte când ı̂ntâlneşte un alt oraş sau dacă, deşi nu a ı̂ntâlnit ı̂ncă
un alt oraş, ajunge la marginea hărţii pe oricare dintre cele patru puncte cardinale. Două oraşe
se ı̂ntâlnesc când suprafeţele ocupate de ele ajung să se atingă sau ı̂ntre cartierele marginale ale
celor două oraşe se mai află doar un hectar.

Cerinţe

1. Dimensiunea suprafeţei (ı̂n hectare) pe care ar ocupa-o după t ani, dacă nu ar ı̂ntâlni nici
un alt oraş şi nici nu ar ajunge la marginea hărţii.

150
CAPITOLUL 7. ONI 2016 7.1. CIVILIZATIE 151

2. Timpul scurs până când toate cele N oraşe şi-au ı̂ncetat extinderea, ı̂ncepută din cartierele
iniţiale ale căror coordonate se citesc din fişier, şi aria suprafeţei din hartă rămasă neocupată,
exprimată ı̂n hectare.

Date de intrare

Fişierul de intrare civilizatie.in conţine pe prima linie un număr natural p. Pentru toate
testele de intrare, p poate avea doar valoarea 1 sau valoarea 2.
A doua linie a fişierului conţine două numere naturale x şi y reprezentând dimensiunile hărţii.
A treia linie a fişierului conţine numărul natural t.
A patra linie a fişierului conţine numărul natural N .
Pe următoarele N linii se găsesc câte două numere i şi j reprezentând coordonatele iniţiale ale
celor N oraşe.

Date de ieşire

Dacă valoarea lui p este 1, atunci se va rezolva numai prima cerinţă.


În acest caz, ı̂n fişierul de ieşire civilizatie.out se va scrie un singur număr natural,
reprezentând aria suprafeţei (ı̂n hectare) unui oraş după t ani, dacă nu ar ı̂ntâlni nici un alt
oraş şi nici nu ar ajunge la marginea hărţii.
Dacă valoarea lui p este 2 atunci, se va rezolva numai a doua cerinţă.
În acest caz, fişierul de ieşire va conţine pe prima linie un număr natural reprezentând aria
suprafeţei din hartă rămasă neocupată după ce toate cele N oraşe şi-au ı̂ncetat expansiunea, iar
pe a doua linie un număr natural reprezentând timpul scurs până când ultimul oraş s-a oprit din
expansiune.

Restricţii şi precizări

a 1 & N & 2000


a 1 & x, y, t & 100 000
a Pentru 30% din teste se garantează faptul că x, y & 500
a Pentru rezolvarea corectă a primei cerinţe se acordă 20 de puncte, iar pentru rezolvarea
corectă a celei de-a doua cerinţe se acordă 80 de puncte.

Exemple:

civilizatie.in civilizatie.out Explicaţii


1 145 p = 1, ı̂n fişier se va scrie aria suprafeţei ce ar putea fi ocupată
79 de un oraş ı̂n timp de 9 ani.
9 Atenţie! Pentru acest test se rezolvă doar cerinţa 1).
2
32
46
2 33
79 4
5
2
32
46
2 97 p=2, deci se rezolvă doar cerinţa 2
10 10 1 ı̂n acest caz, cele 3 civilizaţii nu se vor putea extinde deloc, deci
5 celelalte 97 de hectare rămân neocupate.
3
22
24
32
Timp maxim de executare/test: 3.0 secunde
Memorie: total 128 MB
Dimensiune maximă a sursei: 15 KB
CAPITOLUL 7. ONI 2016 7.1. CIVILIZATIE 152

7.1.1 Indicaţii de rezolvare


prof. Marcel Drăgan

Cerinţa 1 - Soluţia 1
Considerăm un tablou bidimensional, ı̂n care fiecare hectar este un element al tabloului.
În acest tablou considerăm coordonatele de origine ale oraşului ı̂n mijlocul tabloului.
ˆ pentru t=1 coordonatele noilor cartiere sunt chiar coordonatele iniţiale;
ˆ pentru t=2 coordonatele noilor cartiere sunt date de: —x-x0—+—y-y0—=1;
ˆ pentru t=3 coordonatele noilor cartiere sunt date de: —x-x0—+—y-y0—=2;
ˆ ...
ˆ pentru cazul general —x-x0—+—y-y0—=t-1;

Pentru a calcula dimensiunea suprafeţei parcurgem tabloul bidimensional şi contorizăm ele-
mente care respectă condiţia:
—x-x0—+—y-y0—+—z-z0—¡=t-1.
Se observă că nu avem nevoie efectiva de tabloul tridimensional, pentru că totul se reduce la
relaţii ı̂ntre coordonate.
Cerinţa 1 - Soluţia 2
Analizând structura obţinută observăm că pentru fiecare valoare a lui t se formează structuri
ı̂n formă de romb cu numărul de cuburi egal cu suma termenilor unei progresi aritmetice cu raţia
4:
ˆ pentru t=1 avem un singur cub;
ˆ pentru t=2 avem cubul anterior ı̂n jurul căruia s-au adăugat ı̂nca 4: 1+4;
ˆ pentru t=3 avem cuburile anterioare ı̂n jurul căruia s-au adăugat ı̂nca 8: 1+4+8;
ˆ pentru t=4 avem cuburile anterioare ı̂n jurul căruia s-au adăugat ı̂nca 12: 1+4+8+12;
ˆ ...

În concluzie numărul de cuburi din fiecare romb se poate calcula după formula:
1+4+8+12+16+...+4(k-1)=
1+4(1+2+3+4+...+k-1)=
1+4k(k-1)/2=
1+2k(k-1)
Cerinţa 2 - Soluţia 1
Folosim un tablou bidimensional de dimensiune N  N ı̂n care reţinem după câţi ani s-ar
ı̂ntâlnii oraşele două câte două ignorându-le pe toate celelalte (ı̂niţial punctul de ı̂ntâlnire va fi la
jumătatea drumului).
Folosim un tablou unidimensional de dimensiune N ı̂n care reţinem cel mai mic număr de ani
după care un oraş ar ajunge la una dintre margini.
Alegem pe baza acestor două tablouri oraşele care se opresc primele şi recalculăm momentele
de ı̂ntânire ale acestor oraşe cu toate celelalte (ı̂ntâlnirea nu va mai fi pentru toate la jumătatea
drumului).
Reluăm acest procedeu cu cele rămase până când alegem toate civilizaţiile.
3
Complexitate O N 
Cerinţa 2 - Soluţia 2
Pentru a reduce timpul de execuţie folosim un tablou unidimensional ı̂n care reţinem minimele
pe linii. Aceste minime le actualizăm atunci când recalculăm momentele de ı̂ntalnire dintre oraşele
alese şi celelalte oraşe.

7.1.2 Cod sursă

Listing 7.1.1: civilizatie BICSI.cpp


#include <fstream>
#include <vector>
#include <cmath>
#include <utility>
#include <iostream>
CAPITOLUL 7. ONI 2016 7.1. CIVILIZATIE 153

#define cin fin

using namespace std;

typedef pair<int, int> Pair;

const int MAXN = 2005;


const int MAXD = 1e6 + 1;

int Stopped[MAXN], X[MAXN], Y[MAXN], T[MAXN][MAXN];


vector<Pair> Meets[MAXD];

int maxx, maxy, n, now;


long long Extend[MAXD];
vector<int> ToStop;

int getTime(int i, int j)


{
if(j == 0)
return min(min(X[i], Y[i]), min(maxx - X[i] + 1, maxy - Y[i] + 1));

int d = abs(X[i] - X[j]) + abs(Y[i] - Y[j]);


if(Stopped[j] >= 0)
return d - Stopped[j] + 1;
return (1 + d) / 2;
}

void addEvent(int i, int j)


{
int t = getTime(i, j);
Meets[t].push_back(make_pair(i, j));
}

int main()
{
int p, t;

ifstream cin("civilizatie.in");
ofstream cout("civilizatie.out");

cin >> p;
cin >> maxx >> maxy;
Extend[1] = 1;
for(int i = 2; i < MAXD; ++i)
Extend[i] = Extend[i - 1] + 4 * i - 4;

cin >> t;
if(p == 1)
{
cout << Extend[t];
return 0;
}
else
{
cin >> n;

for(int i = 1; i <= n; ++i)


{
cin >> X[i] >> Y[i];
Stopped[i] = -1;
}

for(int i = 1; i <= n; ++i)


for(int j = n; j >= 0; --j)
if(i != j)
addEvent(i, j);

int cnt = n;
for(now = 1; cnt; ++now)
{
for(int z = 0; z < Meets[now].size(); ++z)
{
Pair p = Meets[now][z];
if(Stopped[p.first] == -1 &&
now == getTime(p.first, p.second))
CAPITOLUL 7. ONI 2016 7.1. CIVILIZATIE 154

{
ToStop.push_back(p.first);
}
}

for(int i = 0; i < ToStop.size(); ++i)


{
int no = ToStop[i];
if(Stopped[no] >= 0) continue;

Stopped[no] = now;
--cnt;

for(int j = 1; j <= n; ++j)


if(Stopped[j] == -1)
addEvent(j, no);
}

ToStop.clear();
}

long long ans = 1LL * maxx * maxy;


for(int i = 1; i <= n; ++i)
ans -= Extend[Stopped[i]];
}

return 0;
}

Listing 7.1.2: civilizatie100p.cpp


#include <fstream>
#include <cmath>

using namespace std;

ifstream fin("civilizatie.in");
ofstream fout("civilizatie.out");

#define nrCiv 2001 // numarul maxim de civilizatii

int p // tipul de cerinta


,n // mumarul de civilizatii
,nx,ny // dimensiunile hartii
,x[nrCiv],y[nrCiv] // coordonatele initiale ale oraselor
,dxyMin[nrCiv] // timpul pana la atingerea marginilor
,dMin[nrCiv] // timpul pana la oprire
,difMin[nrCiv][nrCiv] // distata dintre oricare doua civilizatii
,difMin2[nrCiv][nrCiv] // timpul minim dintre oricare doua civilizatii
,lMin[nrCiv] // minimele pe linii
,jlMin[nrCiv]; // pozitia minimului pe linii

unsigned long long t // timpul pt cerinta 1


,ct[nrCiv]; // suprafata ocupata de orase
int main()
{
fin>>p>>nx>>ny>>t>>n;
if(p==1)
{
unsigned long long ct=ct+2*t*(t-1)+1;;
fout<<ct<<’\n’;
}
else
{
for(int i=1;i<=n;i++)
fin>>x[i]>>y[i];

// minime pana la margini


for(int i=1;i<=n;i++)
{
int dxMin=min(nx-x[i]+1,x[i]),dyMin=min(ny-y[i]+1,y[i]);
dxyMin[i]=min(dxMin,dyMin);
}

// calcul distantei initiale pana la celelalte civilizatii


CAPITOLUL 7. ONI 2016 7.1. CIVILIZATIE 155

for(int i=1;i<=n;i++)
{
lMin[i]=nx+ny;
for(int j=1;j<=n;j++)
{
difMin[i][j]=abs(x[i]-x[j])+abs(y[i]-y[j])+1;
difMin2[i][j]=difMin[i][j]/2;
if(difMin2[i][j]<lMin[i] && i!=j)
{
lMin[i]=difMin2[i][j];jlMin[i]=j;
}
}
}

// selectare orase in ordinea opriri expansiunii


int nr=0;
while(nr<n)
{
// cautare minim
int mMin=nx+ny,iMin,jMin,im=0;
for(int i=1;i<=n;i++)
{
if(mMin>lMin[i] && !dMin[i])
{
mMin=lMin[i];iMin=i;jMin=jlMin[i];im=0;
}
if(mMin>dxyMin[i] && !dMin[i])
{
mMin=dxyMin[i];im=i;
}
}

// calcul timp de oprire oras selectat si ajustare timpi


// pentru celelalte
int i=iMin,j=jMin;
if(im!=0)
{
i=im;dMin[i]=dxyMin[i];
}
else
{
if(dMin[i])
{ // civilizatia i verificata deja
i=jMin;j=iMin;
}

dMin[i]=difMin2[i][j];
}

nr++;
lMin[i]=nx+ny;
for(int k=1;k<=n;k++) // pt toate elementele de pe col i
if(!dMin[k] || !dMin[i])
if(difMin2[k][i]!=dMin[i])
{
difMin2[k][i]=difMin2[i][k]=difMin[k][i]-dMin[i];
if(difMin2[k][i]<lMin[i] &&
k!=i &&
(!dMin[k] || !dMin[i]))
{
lMin[i]=difMin2[k][i];jlMin[i]=k;
}
if(jlMin[k]==i)
{
lMin[k]=nx+ny;
for(int ik=1;ik<=n;ik++) // pt toate elementele
// de pe linia k
if(difMin2[k][ik]<lMin[k] && k!=ik )
{
lMin[k]=difMin2[k][ik];jlMin[k]=ik;
}
}
}
}

// calcul suprafete
CAPITOLUL 7. ONI 2016 7.2. CMMDC 156

for(int i=1;i<=n;i++)
{
t=dMin[i];
ct[i]=2*t*(t-1)+1;
}

unsigned long long tot=0;


int dMax=0;
for(int i=1;i<=n;i++)
{
tot=tot+ct[i];
if(dMax<dMin[i])
dMax=dMin[i];
}

fout<<(unsigned long long)nx*ny-tot<<’\n’;


fout<<dMax<<’\n’;
}

return 0;
}

7.1.3 *Rezolvare detaliată

7.2 cmmdc
Problema 2 - cmmdc 100 de puncte
Fie un şir de numere naturale nenule a1 , a2 , ..., an şi un număr natural k.

Cerinţe

Să se determine un grup de k numere din şir care au proprietatea ca cel mai mare divizor
comun al lor este maxim. Dacă există mai multe astfel de grupuri, se cere acel grup pentru care
suma elementelor este maximă.

Date de intrare

Fişierul cmmdc.in conţine pe prima linie numerele naturale n şi k separate prin spaţiu. Pe
linia a doua se găsesc numerele naturale a1 , a2 , ..., an separate prin câte un spaţiu.

Date de ieşire

Fişierul cmmdc.out conţine pe prima linie un număr natural reprezentând cel mai mare
divizor comun a exact k numere din şir, maxim posibil. Pe linia a doua, separate prin câte un
spaţiu şi ordonate descrescător, se află cele k numere din şir care dau cel mai mare divizor comun
maxim.

Restricţii şi precizări

a 1 & n & 1 000 000


a 2 & k & 100 000
a k&n
a 1 & ai & 1 000 000, i 1..n
a Valorile din şir se pot repeta.

Exemple:

cmmdc.in cmmdc.out Explicaţii


63 3 Cel mai mare divizor comun care se poate obţine dintr-un grup
6 9 8 10 15 3 15 9 6 de 3 numere este 3, iar cele 3 numere care dau suma maximă,
ordonate descrescător, sunt 15, 9 şi 6.
Timp maxim de executare/test: 1.0 secunde
Memorie: total 64 MB
Dimensiune maximă a sursei: 15 KB
CAPITOLUL 7. ONI 2016 7.2. CMMDC 157

7.2.1 Indicaţii de rezolvare

prof. Stelian Ciurea, prof. Dan Pracsiu

Se construieşte un vector de frecvenţe (v i = de câte ori apare i ı̂n sirul de numere dat);
Se face rationamentul urmator: dacă k numere au cmmdc-ul egal cu x, atunci ele sunt fie egale
cu x, fie sunt multiplii ai numarului x.
Astefel se parcurge cu o variabila x descrescator intervalul 1 000 000 2 si pentru fiecare
valoare a lui x se determina daca exista cel putin k numere egale cu x sau multiplii de-i lui x, cu
un algoritm asemanator cu Ciurul lui Eratostene.
Aceasta determinare se face parcurgand multiplii lui x descrescator, astfel prima submultime
determinata este solutia ceruta. Cel mai mare multiplu a lui x care teoretic poate sa apara printre
cele n numere se poate calcula in functie de valoarea maxima din sirul a (notata max a si care
oricum nu depaseste 1 000 000).
Complexitate:
O n pentru constructia vectorului v.
O max a ˜ log max a pentru determinarea rezultatului (unde max a e maximul din sirul
a)
Expresia de mai sus e aproximarea pentru:

max a max a max 3 max a max a


   ...  x  ... 
1 2 1 max a

Rezolvări alternative se pot face prin generare de submultimi:


- una dintre surse genereaza o submultime de k elemente, ii calculeaza cmmdc-ul si o reţine
daca are cmmdc-ul maxim - 25 puncte
- celalta e optimizată ı̂n sensul că se calculează cmmdc-ul după fiecare element adăugat la
submulţime şi dacă acesta e mai mic decât maximul de până atunci se trece la alegerea altui
element - 40 puncte
În ambele surse alternative se sortează elementele descrescător.

7.2.2 Cod sursă

Listing 7.2.1: cmmdc BICSI.cpp


#include <iostream>
#include <fstream>

using namespace std;

const int MAXN = 2e6;

int Freq[MAXN], FreqFact[MAXN];

int main()
{
ifstream fin("cmmdc.in");
ofstream fout("cmmdc.out");

int n, val, k, maxx = 0;

fin >> n >> k;


for(int i = 1; i <= n; ++i)
{
fin >> val;
Freq[val] += 1;
maxx = max(maxx, val);
}

for(int i = 1; i <= maxx; ++i)


for(int j = i; j <= maxx; j += i)
FreqFact[i] += Freq[j];

for(int i = maxx; i; --i)


CAPITOLUL 7. ONI 2016 7.3. LIVADA 158

if(FreqFact[i] >= k)
{
fout << i << endl;
for(int j = maxx / i * i; j; j -= i)
while(Freq[j] > 0)
{
fout << j << " ";
if(--k == 0)
return 0;
--Freq[j];
}
}

return 0;
}

7.2.3 *Rezolvare detaliată

7.3 livada
Problema 3 - livada 100 de puncte
Fermierul Quinto are o livadă plină cu pomi fructiferi. Livada are N rânduri, numerotate de
la 1 la N , pe fiecare rând aflându-se câte M pomi fructiferi, numerotaţi de la 1 la M . Livada lui
Quinto este una specială, aşa că pentru unii pomi se cunoaşte cantitatea de fructe (exprimată ı̂n
kg) care poate fi culeasă, iar pentru alţii aceasta poate fi determinată pe baza unei formule.
Quinto şi-a propus să recolteze C kg de fructe din pomii aflaţi ı̂n livada lui. Acesta foloseşte
un utilaj modern pentru culesul fructelor. Utilajul poate fi folosit pe oricare din rândurile livezii,
dar poate aduna doar fructele dintr-un şir consecutiv de pomi, ı̂ncepând cu primul pom de pe
rândul respectiv, neavând posibilitatea de a culege parţial fructele dintr-un pom.
Preocupat de frumuseţea livezii sale, Quinto s-a gândit la restricţii suplimentare pentru
recoltarea cantităţii C de fructe. Astfel, el doreşte să adune fructele din pomi de pe maximum R
rânduri diferite, pentru ca N  R rânduri să rămână complete.
Deasemenea, el doreşte să culeagă cu prioritate pomii care au o cantitate cât mai mică de
fructe, pentru ca ı̂n livadă să rămână cei mai roditori pomi. Quinto şi-a dat seama că este dificil
să culeagă fix C kg de fructe, prin urmare este mulţumit şi cu o cantitate mai mare, care respectă
celelalte condiţii impuse de el.

Cerinţe

Determinaţi cea mai mică valoare X posibilă astfel ı̂ncât să se poată culege, ı̂n condiţiile de
mai sus, o cantitate de cel puţin C kg de fructe şi orice pom din care se culeg fructe să conţină
cel mult X kg de fructe.

Date de intrare

Pe prima linie a fişierului livada.in se află 4 numere naturale N , M , C, R cu semnificaţia din


enunţ.
Pe a doua linie din fişierul de intrare se află 5 numere naturale x, y, z, w, u, separate printr-un
spaţiu.
Dacă notăm cu Aij  cantitatea de fructe (exprimată ı̂n kg) din cel de-al j-lea pom de pe
linia i, atunci:
Linia a treia din fişierul de intrare conţine M valori A1i, 1 & i & M , separate printr-un
spaţiu
Linia a patra din fişierul de intrare conţine N  1 valori Ai1, 2 & i & N , separate printr-un
spaţiu
Celelalte valori Aij , 2 & i & N , 2 & j & M , se calculează conform formulei:

Aij  x ˜ Ai  1j   y ˜ Aij  1  z ˜ Ai  1j  1  w%u.

Date de ieşire
CAPITOLUL 7. ONI 2016 7.3. LIVADA 159

Fişierul de ieşire livada.out va conţine o singură valoare scrisă pe prima linie, care reprezintă
cea mai mică valoare a cantităţii de fructe (exprimată ı̂n kg) dintr-un pom cules, astfel ı̂ncât să
fie respectate toate restricţiile problemei.

Restricţii şi precizări

a 1 & R & N & 100


a 1 & M & 25 000
a 0 & x, y, z, w, u & 10
9

a 0 & Aij  & 10


9

a Atenţie la determinarea fiecărei valori Aij  pentru că ı̂n formulă sunt produse care pot
32
să furnizeze valori mai mari decât 2  1.
a 1 & C & 10
18

a Se garantează că pentru toate testele problema are soluţie


a Pentru 30% din teste se garantează faptul că 1 & M & 100 şi 1 & Aij  & 100
a Pentru 70% din teste se garantează faptul că 1 & M & 4 000

Exemple:

livada.in livada.outExplicaţii
5 6 18 4 3 6 5 4 Sunt 5 rânduri cu câte 6 pomi pe fiecare rând. Figura alăturată
2741351 arată matricea care se obţine conform formulelor precizate.
25263 Se doreşte culegerea a cel puţin 18 de kg de fructe de pe maxim
4 rânduri din cele 5.
ı̂n figura alăturată, este prezentată o soluţie
posibilă ı̂n care cantitatea maximă culeasă
dintr-un pom este de 4 kg.
Nu se pot culege 18 de kg de fructe de pe
maxim 4 rânduri astfel ı̂ncât să fie culeşi doar pomi cu cantitate
de fructe 3kg (ı̂n acest caz se pot culege cel mult 8 kg).
Timp maxim de executare/test: 0.5 secunde
Memorie: total 64 MB
Dimensiune maximă a sursei: 15 KB

7.3.1 Indicaţii de rezolvare

prof. Mircea Lupşe-Turpan - Liceul Teoretic Grigore Moisil Timisoara

Problema se rezolvă prin căutarea soluţiei (căutare binară sau secvenţială). Se caută cantitatea
minimă de fructe culeasă dintr-un singur pom fructifer (se verifică pe rând valori posibile şi se
alege valoarea minimă care respectă restricţiile problemei).
Soluţii parţiale 30 / 70 puncte
Odată fixată valoarea pentru cantitatea maximă de kg de fructe culese dintr-un singur pom,
se determină cantitatea maximă de fructe care poate fi culeasă din ı̂ntreaga livadă astfel ı̂ncât să
fie respectate restricţiile problemei.
Această valoare poate fi aflată dacă se determină pentru fiecare rând cantitatea maximă de
fructe care poate fi culeasă din pomii de pe acel rând, ı̂nsumând pe rând valorile de pe acest rând
până la prima valoare mai mare decât valoarea fixată, şi se memorează aceste valori ı̂ntr-un vector,
2
care este apoi sortat descrescător. Este suficient un algoritm de sortare de complexitate O N .
Se ı̂nsumează apoi primele R valori din vectorul sortat, această sumă reprezentând cantitatea
maximă de fructe care pot fi adunate din livadă.
După determinarea cantităţii maxime care poate fi culeasă din ı̂ntreaga livadă, se verifică dacă
aceasta este cel puţin egală cu cantitatea C cerută ı̂n enunţ.
O implementare a ideii de mai sus care foloseşte căutarea secvenţială a soluţiei obţine 30
puncte, complexitatea algoritmului fiind O N ˜ M ˜ M axV al, unde N e numărul de rânduri,
M e numărul de pomi de pe fiecare rând, iar M axV al e cea mai mare cantitate de fructe dintr-un
singur pom.
O implementare a ideii de mai sus care foloseşte căutarea binară a soluţiei obţine 70 puncte,
complexitatea algoritmului fiind O N ˜ M ˜ log M axV al.
CAPITOLUL 7. ONI 2016 7.3. LIVADA 160

Soluţie 100 puncte


Este necesară precalcularea unor sume parţiale şi maxime parţiale pentru fiecare rând.
Vom calcula pentru fiecare rând i:
Sumij  - Cantitatea de fructe din pomii numerotaţi de la 1 la j, de pe rândul i
M axij  - Cea mai mare cantitate de fructe dintr-unul din pomii numerotaţi de la 1 la j de
pe rândul i
Folosind aceste sume parţiale, cantitatea maximă de fructe care poate fi culeasă de pe un rând
i poate fi determinată prin căutarea binară a celei mai mari valori mai mici decat valoarea fixată
de pe linia i a matricei M ax (se observă că pe orice linie din matricea M ax valorile sunt ı̂n ordine
crescătoare) şi reţinerea valorii din linia i a matricei Sum.
În acest fel, operaţia de verificare a unei valori fixate pentru soluţia problemei are complexitatea
O N ˜ log M , complexitatea determinării soluţiei devenind O N ˜ log M ˜ log M axV al, dar
complexitatea algoritmului este totuşi O N ˜ M  din cauza generării matricei.

7.3.2 Cod sursă

Listing 7.3.1: livada dan.cpp


#include <bits/stdc++.h>

#define inFile "livada.in"


#define outFile "livada.out"

#define Nmax 101


#define Mmax 25001

using namespace std;

int a[Nmax][Mmax], mx[Nmax][Mmax];


long long s[Nmax][Mmax];
long long t[Mmax];
int n, m, R;
long long C;

void Citire()
{
int i, j, x, y, z, w, u;
ifstream fin(inFile);
fin >> n >> m >> C >> R;
fin >> x >> y >> z >> w >> u;
for (i = 1; i <= m; ++i)
fin >> a[1][i];
for (i = 2; i <= n; ++i)
fin >> a[i][1];
for (i = 2; i <= n; ++i)
for (j = 2; j <= m; ++j)
a[i][j]=(1LL*x*a[i-1][j] + 1LL*y*a[i][j-1] +
1LL*z*a[i-1][j-1] + w) % u;
fin.close();
}

void Procesare()
{
int i, j;
for (i = 1; i <= n; ++i)
for (j = 1; j <= m; ++j)
{
mx[i][j] = max(mx[i][j-1], a[i][j]);
s[i][j] = s[i][j-1] + a[i][j];
}
}

/// returneaza cantitatea maxima care se poate culege


/// de pe linia k daca pot culege din copacii care
/// au cel mult cantitatea cant
long long Linia(int k, int cant)
{
if (cant < mx[k][1]) return 0;
if (cant >= mx[k][m]) return s[k][m];
CAPITOLUL 7. ONI 2016 7.3. LIVADA 161

int st, dr, mij, sol;


sol = 0;
st = 1; dr = m;
while (st <= dr)
{
mij = (st + dr) / 2;
if (mx[k][mij] > cant)
dr = mij - 1;
else
{
sol = mij;
st = mij + 1;
}
}
return s[k][sol];
}

void CautBin()
{
ofstream fout(outFile);
int st, dr, sol, mij, i;
long long suma;
sol = 0;
st = 1;
dr = 1000000000;
while (st <= dr)
{
mij = (st + dr) / 2;
for (i = 1; i <= n; ++i)
t[i] = Linia(i, mij);
sort(t + 1, t + n + 1);
suma = 0;
for (i = n; i > n - R; i--)
suma += t[i];
if (suma >= C)
{
sol = mij;
dr = mij - 1;
}
else
st = mij + 1;
}

fout << sol << "\n";


fout.close();
}

int main()
{
Citire();
Procesare();
CautBin();
return 0;
}

Listing 7.3.2: Livada100.cpp


#include <fstream>
#include <algorithm>

using namespace std;

ifstream fin("livada.in");
ofstream fout("livada.out");

const int MaxVal = 1000000000;


const int NMax = 100;
const int MMax = 25000;

int A[NMax+5][MMax+5],Max[NMax+5][MMax+5];
long long Sum[NMax+5][MMax+5];
int N,M,R,Sol;
long long C;

void Read()
CAPITOLUL 7. ONI 2016 7.3. LIVADA 162

{
long long x,y,z,w,u;

fin>>N>>M>>C>>R;
fin>>x>>y>>z>>w>>u;

for(int i = 1; i<=M; i++)


fin>>A[1][i];

for(int i = 2; i<=N; i++)


fin>>A[i][1];

for(int i = 2; i <= N; ++i)


for(int j = 2; j <= M; ++j)
A[i][j] = (x * A[i - 1][j] +
y * A[i][j - 1] +
z * A[i - 1][j - 1] +
w) % u;

void Precalculate()
{
for(int i = 1; i <= N; ++i)
for(int j = 1; j <= M; ++j)
{
Sum[i][j] = A[i][j] + Sum[i][j-1];
Max[i][j] = max(Max[i][j-1],A[i][j]);
}
}

long long Find(int i, int Value)


{
long long Crop = 0;

int Left = 1, Right = M;

while(Left <= Right)


{
int Mid = (Left+Right) / 2;
if(Max[i][Mid] <= Value)
{
Crop = Sum[i][Mid];
Left = Mid + 1;
}
else
Right = Mid - 1;
}
return Crop;
}

bool Check(int Value)


{
long long V[NMax],k = 0;

for(int i = 1; i <= N; i++)


V[++k] = Find(i,Value);

sort(V+1,V+k+1,greater<long long>());

long long Total = 0;

for(int i = 1; i<=R && i<=k; i++)


Total+=V[i];

return (Total >= C);


}

void Solve()
{
int Left = 1, Right = MaxVal;
while(Left <= Right)
{
int Mid = (Left + Right) / 2;

if(Check(Mid))
CAPITOLUL 7. ONI 2016 7.4. DIF2 163

{
Sol = Mid;
Right = Mid - 1;
}
else
Left = Mid + 1;
}
}

void Print()
{
fout<<Sol<<"\n";
}

int main()
{
Read();
Precalculate();
Solve();
Print();
return 0;
}

7.3.3 *Rezolvare detaliată

7.4 dif2
Problema 4 - dif2 100 de puncte
Sandu a studiat la ora de informatică mai multe aplicaţii cu vectori de numere naturale, iar
acum are de rezolvat o problemă interesantă. Se dă un şir X X1 , X2 , ..., Xn  de numere naturale
nenule şi două numere naturale p1 şi p2 , unde p1 $ p2 . Sandu trebuie să construiască un nou şir
Y Y1 , Y2 , ..., Yn˜n  cu n ˜ n elemente obţinute din toate produsele de câte două elemente din
şirul X (fiecare element din şirul Y este de forma Xi ˜ Xj , 1 & i,j & n). Sandu are de calculat două
valori naturale d1 şi d2 obţinute din şirul Y . Valoarea d1 este egală cu diferenţa maximă posibilă
dintre două valori ale şirului Y . Pentru a obţine valoarea d2 , Sandu trebuie să considere că şirul
Y are elementele ordonate descrescător iar d2 va fi diferenţa dintre valorile aflate pe poziţiile p1
şi p2 ı̂n şirul ordonat descrescător. Sandu a găsit rapid valorile d1 şi d2 şi, pentru a le verifica, vă
roagă să le determinaţi şi voi.

Cerinţe

Dându-se şirul X cu n elemente şi valorile p1 şi p2 , determinaţi valorile d1 şi d2 .

Date de intrare

Fişierul de intrare dif2.in va conţine pe prima linie un număr natural C care poate fi doar
1 sau 2. Dacă C 1, atunci pe linia a doua se va afla numărul natural n. Dacă C 2, atunci
pe linia a doua se vor afla numerele naturale n, p1 şi p2 separate prin câte un spaţiu. ı̂n ambele
cazuri, pe următoarele n linii se vor afla elementele şirului X, câte un număr natural pe fiecare
linie a fişierului.

Date de ieşire

În cazul C 1, fişierul de ieşire dif2.out va conţine pe prima linie valoarea d1 egală cu
diferenţa maximă dintre oricare două valori din şirul Y . ı̂n cazul C 2 fişierul de ieşire va conţine
pe prima linie un număr natural d2 reprezentând diferenţa dintre valorile aflate pe poziţiile p1 şi
p2 din şirul Y , presupunând că ar fi ordonat descrescător.

Restricţii şi precizări

a 3 $ n $ 300 000
a 1 & p1 $ p2 & n2
a 1 & Xi $ 300 000, i 1..n
CAPITOLUL 7. ONI 2016 7.4. DIF2 164

a Pentru teste valorând 30 de puncte vom avea C 1, iar pentru teste valorând 70 de puncte
vom avea C 2.
a Pentru teste valorând 10 puncte vom avea C 2 şi n & 100

Exemple:

dif2.in dif2.out Explicaţii


1 32 Atenţie, C 1, deci se rezolvă doar cerinţa 1!
4 Valoarea maximă d1 va fi 32 şi se obţine efectuând diferenţa
3 dintre 6*6 şi 2*2.
5
2
6
2 8 Atenţie, C 2, deci se rezolvă doar cerinţa 2!
4 5 11 Se obţin ı̂n Y următoarele 16 valori: 3*3, 3*5, 3*2, 3*6, 5*3,
3 5*5, 5*2, 5*6, 2*3, 2*5, 2*2, 2*6, 6*3, 6*5, 6*2, 6*6.
5 Valoarea d2 va fi 8, deoarece dacă vom considera şirul Y ordonat
2 descrescător (36, 30, 30, 25, 18, 18, 15, 15, 12, 12, 10, 10, 9, 6,
6 6, 4), atunci Y[5]-Y[11]=18-10=8
Timp maxim de executare/test: 2.0 secunde
Memorie: total 16 MB
Dimensiune maximă a sursei: 15 KB

7.4.1 Indicaţii de rezolvare

Gheorghe Manolache, Colegiul Naţional de Informatică, Piatra Neamţ

Se observă că valoarea d1 este egală cu diferenţa dintre valoarea maximă şi valoarea minimă a
elementelor din şirul Y . Se pot calcula valorile extreme ale elementelor lui X iar d1 va fi diferenţa
pătratelor lor.
Pentru d2 nu se poate construi şirul Y şi apoi să se ordoneze descrescător deoarece există o
2
limitare a memoriei disponibile şi algoritmi k log k pentru sortare (k n ).
2
Voi prezenta un algoritm de complexitate O DIMn  n log DIMV , unde DIMV este valoarea
maxima din X.
Se vor număra câte perechi de elemente din X au valoarea mai mică sau egală decât i. Se
poate face numărarea ı̂n O n dacă vom precalcula un sir ci cu numărul de valori din X mai
mici sau egale cu i.
Pentru a afla pe ce poziţie va fi un produs p, vom utiliza şirul c precalculat şi vom numără
pentru fiecare element din şir câte elemente contribuie la construirea unei valori ce nu depăşeşte
p.
Vom utiliza căutarea binară pentru determinarea celor două valori. Nu e necesar să facem
sortări descrescătoare, se deduce uşor poziţia necesară dacă vom face calculul ı̂n cazul ordonării
crescătoare.
Soluţia comisiei (studenţi LUCIAN BICSI şi PETRU-ERIC STAVARACHE)
Soluţie O(V logV):
Ţinem un vector de frecvenţă f req i pentru valorile elementelor ı̂mpreună cu sume parţiale
pe prefixe pe acest vector Sumi.
Pentru a afla produsul aflat pe o poziţie Z, căutăm binar produsul, iar pentru un produs P
fixat trebuie sa vedem câte perechi (a, b) exista cu a ˜ b & P .
Pentru asta vom parcurge fiecare valoare posibila, iar pentru o valoare A fixata deducem ca
B & P ©A. Aşadar toate elementele cu valoarea cel mult B vor forma perechi posibile ı̂mpreună
cu A. Vor exista f A ˜ SumB  astfel de perechi.
Dacă numărul total de perechi de acest fel este & Z, atunci soluţia ' P .
2
Solutie O N log V :
Similar cu soluţia anterioara, doar ca in loc sa ţinem un vector de frecventa, sortam vectorul
iniţial.
CAPITOLUL 7. ONI 2016 7.4. DIF2 165

Cu Z si P fixate (ca in soluţia anterioara), pentru a numără perechile vom face:


Pentru o poziţie i din vector, trebuie sa găsim j maxim astfel ı̂ncât v i ˜ v j  & P .
Odată găsit, v i, v 1, ..., v i, j  toate vor fi perechi posibile.
Acest j poate fi căutat binar, deoarece vectorul este sortat.
Soluţie O(N logV):
Plecând de la soluţia anterioară, observăm că:
Suntem la pasul i şi ştim j pentru acest pas.
Când trecem la pasul i  1, j poate doar sa scadă.
(Deoarece v i  1 ' v i)
Aşadar ı̂n loc să căutăm binar vom ţine doi pointeri cu care vom parcurge vectorul.

7.4.2 Cod sursă

Listing 7.4.1: dif2 bicsi.cpp


#include <bits/stdc++.h>

using namespace std;

#define int long long


const int MAXN = 300000 + 10;
int V[MAXN], n;

long long countSmall(long long x)


{
long long ans = 0;

for(int i = 1, j = n; i <= n; ++i)


{
while(1LL * V[i] * V[j] > x)
--j;
ans += j;
}

return ans;
}

long long kth(long long pos)


{
long long ans = 0;
for(long long i = (1LL << 40); i; i >>= 1LL)
{
int x = countSmall(ans + i);
if(x < pos)
ans += i;
}
return ans + 1;
}

int32_t main()
{
ifstream cin("dif2.in");
ofstream cout("dif2.out");

int t;
long long a, b;

cin >> t >> n;

if(t == 2) cin >> a >> b;

for(int i = 1; i <= n; ++i)


cin >> V[i];

sort(V + 1, V + n + 1);

if(t == 1)
cout << 1LL * V[n] * V[n] - 1LL * V[1] * V[1] << endl;
else
CAPITOLUL 7. ONI 2016 7.4. DIF2 166

cout << kth(1LL * n * n - a + 1) - kth(1LL * n * n - b + 1) << endl;

return 0;
}

Listing 7.4.2: dif2 eric.cpp


#include <bits/stdc++.h>

using namespace std;

typedef long long LL;


#define int LL

const int MAX_V = 300000 + 1;


const int MAX_N = 300000 + 1;

int v[MAX_N];
int f[MAX_V];
LL S[MAX_V];
int n;

LL p1, p2;

LL test1()
{
return 1LL * v[n] * v[n] - 1LL * v[1] * v[1];
}

LL before(LL prod)
{
LL ret = 0;
for(int i = min(1LL * v[n], prod); i >= 1; --i)
{
LL other = prod / i;

if(other <= v[n])


ret += 1LL * S[other] * f[i];
else
ret += 1LL * S[v[n]] * f[i];
}

return ret;
}

LL query(LL poz)
{
LL i, step;
LL mx = 1LL * v[n] * v[n];
for(step = 1; step < mx; step *= 2);

for(i = 0; step; step /= 2)


if(i + step <= mx && before(i + step) < poz)
i += step;

return i + 1;
}

int32_t main()
{
ifstream in("dif2.in");
int c;
in >> c;
assert(c == 1 || c == 2);

in >> n;
assert(3 < n && n < MAX_N);

if(c == 2)
in >> p1 >> p2;

for(int i = 1; i <= n; ++i)


{
in >> v[i];
CAPITOLUL 7. ONI 2016 7.4. DIF2 167

assert(1 <= v[i] && v[i] < MAX_V);


f[v[i]]++;
}

for(int i = 1; i < MAX_V; ++i)


S[i] = S[i - 1] + f[i];

sort(v + 1, v + n + 1);

ofstream out("dif2.out");

if(c == 1)
{
out << test1() << "\n";
return 0;
}
else
{
p2 = 1LL * n * n - p2 + 1;
p1 = 1LL * n * n - p1 + 1;
out << query(p1) - query(p2) << "\n";
}

return 0;
}

Listing 7.4.3: dif2 N log2.cpp


#include <bits/stdc++.h>

using namespace std;

const int MAXN = 500000;


int V[MAXN], n;

long long countSmall(long long x)


{
long long ans = 0;

for(int i = 1; i <= n; ++i)


ans += upper_bound(V + 1,
V + n + 1,
min((long long)V[n], x / V[i])) - V - 1;

return ans;
}

long long kth(long long pos)


{
long long ans = 0;
for(long long i = (1LL << 40); i; i >>= 1LL)
{
int x = countSmall(ans + i);
if(x < pos)
ans += i;
}

return ans + 1;
}

template<typename T>

void Read(T &a)


{
char c;
for(c = getchar(); !isdigit(c); c = getchar());

for(a = 0; isdigit(c); c = getchar())


a = a * 10 + c - ’0’;
}

int main()
{
freopen("dif2.in", "r", stdin);
freopen("dif2.out", "w", stdout);
CAPITOLUL 7. ONI 2016 7.5. LEDURI 168

int t;
long long a, b;

Read(t);
Read(n);

if(t == 2)
{
Read(a);
Read(b);
}

for(int i = 1; i <= n; ++i)


Read(V[i]);

sort(V + 1, V + n + 1);

if(t == 1)
cout << 1LL * V[n] * V[n] - 1LL * V[1] * V[1] << endl;
else
cout << kth(1LL * n * n - a + 1) - kth(1LL * n * n - b + 1) << endl;

return 0;
}

7.4.3 *Rezolvare detaliată

7.5 leduri
Problema 5 - leduri 100 de puncte
Am un cablu cu N leduri (numerotate de la 1 la N ) aşezate echidistant. Iniţial, unele leduri
sunt aprinse, iar altele sunt stinse. Ledurile sunt legate ı̂ntre ele astfel ı̂ncât atingerea fiecărui
led produce modificarea atât a stării lui, cât şi a ledurilor vecine lui. Deci, dacă se atinge ledul i
(2 & i & N  1) atunci se modifică stările ledurilor i  1, i şi i  1. Dacă se atinge ledul 1, atunci
se modifică stările ledurilor 1 şi 2, iar dacă se atinge ledul N , atunci se modifică stările ledurilor
N  1 şi N . Vreau să modific starea ledurilor astfel ı̂ncât să semene cu cablul cu N leduri pe care
ı̂l are Ionuţ, prietenul meu (două cabluri seamănă dacă pentru orice i 1..N stările ledurilor de
pe poziţia i sunt identice).

Cerinţe

Cunoscând cum arată cablul lui Ionuţ, ajutaţi-mă să determin numărul minim de atingeri ale
unor leduri astfel ı̂ncât cablul meu să arate ca şi cablul lui Ionuţ.

Date de intrare

Fişierul de intrare leduri.in conţine pe prima linie numărul natural N . Pe a doua linie sunt
N cifre binare separate prin câte un spaţiu reprezentând stările ledurilor de pe cablul meu. Cifra
de pe poziţia i este 0 dacă ledul i este stins, respectiv este 1 dacă ledul i este aprins (i 1..N ).
Pe a treia linie sunt N cifre binare separate prin câte un spaţiu, reprezentând stările ledurilor de
pe cablul lui Ionuţ.

Date de ieşire

Fişierul de ieşire leduri.out va conţine pe prima linie un singur număr natural reprezentând
numărul minim de atingeri ale unor leduri astfel ı̂ncât cablul meu să arate ca şi cablul lui Ionuţ.

Restricţii şi precizări

a 1 & N & 100 000


a Se garantează că pentru toate testele există soluţie.
a Pentru teste valorând 30 de puncte, N va fi cel mult 20
CAPITOLUL 7. ONI 2016 7.5. LEDURI 169

Exemple:

leduri.in leduri.out Explicaţii


4 2 O soluţie posibilă este:
1010 Se apasă mai ı̂ntâi al doilea led: 1 0 1 0 0100
0111 Se apasă ultimul led: 0 1 0 0 0111
Timp maxim de executare/test: 0.5 secunde
Memorie: total 64 MB
Dimensiune maximă a sursei: 15 KB

7.5.1 Indicaţii de rezolvare

prof. Ionel-Vasile Piţ-Rada, Colegiul Naţional Traian, Drobeta Turnu Severin


student Lucian Bicsi, Universitatea Bucureşti

În primul rând, se observa becurile pot fi atinse in orice ordine, fără a produce schimbări de
efect ı̂n final. O a doua observaţie utilă ı̂n rezolvarea problemei este că, pentru a transforma
configuraţia iniţială ı̂n cea finala, orice bec ajunge sa fie atins maximum o data.
Acest lucru rezultă din faptul că atingerea unui bec pentru a doua oara produce neutralizarea
primei operaţii.
n
Din cele doua observaţii se deduce că numărul de seturi de operaţii diferite posibile este 2
(orice set de operaţii se poate vedea ca atingerea unei submulţimi de becuri din cele n).
Soluţia pentru 20p:
Pentru 20% din teste, n & 20, deci se poate simula orice set de operaţii şi, pentru cele care
produc efectul dorit, să se calculeze numărul de operaţii şi să se compare cu minimul obţinut de
până atunci.
n
Complexitatea acestei soluţii este O 2 .
Soluţia pentru 100p:
La o analiză mai atentă a configuraţiilor posibile şi a modurilor din care se poate ajunge la
acestea, se observă că există maximum două soluţii de transformare valide. Mai exact, aplicând
o strategie tip Greedy pentru a transforma şirul iniţial ı̂n cel final, se observă că setul de operaţii
este unic determinat de atingerea sau nu a primului element. Mai exact:
Să presupunem că am ales să atingem primul element (cazul ı̂n care nu ı̂l atingem se va trata
ı̂n mod analog). Se disting două cazuri:
1) Becul 1 din starea actuală e diferit de becul 1 ı̂n starea finală, unde singura variantă posibilă
de a rezolva această problemă este să atingem becul 2 (deoarece becul 1 nu mai poate fi schimbat,
din observaţiile de mai sus)
2) Stările celor doua becuri coincid, caz ı̂n care nu avem voie sa atingem becul 2 (dacă l-am
atinge, nu am mai putea modifica ı̂napoi starea becului 1)
Inductiv, se demonstrează că atingerile tuturor celor n becuri sunt unic determinate.
În final, se poate să obţinem o stare diferită pentru ultimul bec, caz in care soluţia obtinută
nu este corectă.
Soluţia de 100p tratează cele două cazuri (atingerea sau nu a primului bec), după care simulează
strategia descrisă mai sus pentru a obţine o configuraţie care diferă de cea finală la cel mult ultima
poziţie. Dacă cele doua valori coincid, se actualizează eventualul număr minim de operaţii. Dacă
acest minim nu a fost actualizat, nu vor exista soluţii (ı̂n cadrul problemei, ı̂nsă, orice test admite
soluţie validă).
Extra:
Pentru o mai bună ı̂nţelegere a corectitudinii problemei, propunem ı̂ncă o demonstraţie, mai
formală:
Datorita comutativităţii setului de operaţii, este suficient şi necesar să aducem ambele
configuraţii la o stare intermediară.
Să considerăm (S0) = 000 ... 000 şi (S1) = 000 ... 001.
Lemă: Din orice stare iniţială se poate ajunge ı̂n S0 sau S1
Demonstraţie: Rezultă din algoritmul descris mai sus (de fiecare dată când găsim un bec
aprins, atingem becul imediat următor şi repetăm).
În continuare, dacă se poate ajunge din S0 ı̂n S1 sau invers, atunci se poate ajunge din orice
stare ı̂n orice stare. Ne interesează acum să analizăm când se ı̂ntâmplă acest lucru.
CAPITOLUL 7. ONI 2016 7.5. LEDURI 170

Analizăm ı̂n funcţie de resturile lui n la ı̂mpărţirea cu 3:


0) n 3k: Putem din starea (S1) să atingem becurile 1, 2, 4, 5, 7, 8, ..., 3k-2, 3k-1 şi ajungem
ı̂n (S0)
1) n 3k  1: Putem din starea (S1) să atingem becurile 2, 3, 5, 6, ..., 3k - 1, 3k şi ajungem
ı̂n (S0)
2) n 3k  2: Acesta este cazul interesant. ı̂n acest caz demonstrăm imediat că din (S0) nu
se poate ajunge ı̂n (S1) (sau invers)
Pentru cazurile ı̂n care n = 3k sau n = 3k + 1 rezultă imediat că se poate ajunge din orice
stare ı̂n oricare alta (deoarece din orice stare se poate ajunge ı̂n (S0) sau (S1) şi din (S0) se poate
ajunge ı̂n (S1) ).
n
Cum dintr-o stare x se poate ajunge ı̂n toate cele 2 stări diferite, iar numărul de operaţii
n
posibile este 2 , rezultă imediat că modul prin care se ajunge din x ı̂n y este unic, pentru orice y
(să presupunem că există două submulţimi care ı̂l transformă pe x ı̂n y; conform principiului lui
k k
Dirichlet, există o stare care nu poate fi atinsă, pentru că există 2 stări diferite şi 2 transformări
diferite, iar două dintre ele duc ı̂n aceeaşi stare =¿ contradicţie cu ce am demonstrat mai sus).
În concluzie, pentru n 3k sau n 3k  1, soluţia este unică şi este obţinută prin combinarea
(prin sau exclusiv) a celor două şiruri rezultate din algoritmul descris.
Pentru n 3k  2, să considerăm starea (S0) şi să presupunem prin absurd că putem ajunge ı̂n
(S1). Atunci, ı̂n mod evident, putem ajunge din (S0) ı̂n orice altă stare. Folosind un raţionament
asemănător cu cel din demonstraţia precedentă, rezultă că există un singur mod de a transforma
(S0) ı̂ntr-o stare x. Ori, există două moduri de a ajunge de la (S0) la (S0): una este setul vid,
iar alta este setul de operaţii 1, 2, 4, 5, 7, 8, ..., 3k + 1, 3k + 2 (se demonstrează prin inducţie).
Deci presupunerea făcută este falsă. Mai mult, se demonstrează că acest set de operaţii nevid este
unicul mod de a ajunge din (S0) ı̂n (S0).
Notă: Acelaşi set de operaţii transformă şirul (S1) ı̂n (S1).
De aici se observă că pentru a ajunge dintr-o stare iniţială (A) ı̂ntr-o stare finală (B) este
suficient şi necesar ca:
1) Din stările (A), (B) folosind algoritmul din demonstraţia lemei să se ajungă la aceeaşi stare
2) Setul de operaţii să fie format din combinarea celor doua seturi de operaţii din algoritmul
respectiv, sau prin combinarea celor două cu ”şirul special” 1, 2, 4, 5, ..., 3k + 1, 3k + 2
În concluzie, soluţia se poate obţine şi prin aducerea celor doua şiruri la o formă intermediară
(tehnica cunoscută şi ca ”meet in the middle”), combinarea celor două seturi de operaţii şi,
eventual, tratarea cazului special n 3k  2 (singurul caz ı̂n care există mai multe soluţii ale
problemei).
Complexitate finală: O n

7.5.2 Cod sursă

Listing 7.5.1: leduri bicsi.cpp


#include <bits/stdc++.h>

using namespace std;

const int MAXN = 1000000;


int A0[MAXN], A1[MAXN], B[MAXN];
int n;

int transform(int A[])


{
int ops = 0;

for(int i = 1; i < n; ++i)


{
if(A[i] != B[i])
{
A[i] ˆ= 1;
A[i + 1] ˆ= 1;
A[i + 2] ˆ= 1;
ops += 1;
}
}
return ops;
}
CAPITOLUL 7. ONI 2016 7.5. LEDURI 171

int main()
{
freopen("leduri.in", "r", stdin);
freopen("leduri.out", "w", stdout);

cin >> n;

for(int i = 1; i <= n; ++i)


{
cin >> A0[i];
A1[i] = A0[i];
}

for(int i = 1; i <= n; ++i)


cin >> B[i];

A1[1] ˆ= 1;
A1[2] ˆ= 1;

int a = transform(A0);
int b = transform(A1);

int ans = 1000000;


if(A0[n] == B[n]) ans = min(ans, a);
if(A1[n] == B[n]) ans = min(ans, b + 1);

if(ans == 1000000)
ans = -1;

cout << ans << endl;

return 0;
}

Listing 7.5.2: leduri eric.cpp


#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 1e5 + 10;

int n;
bool v[MAX_N];
bool init[MAX_N];
bool fin[MAX_N];

void apply(int x)
{
v[x] ˆ= 1;
if(x - 1 >= 1)
v[x - 1] ˆ= 1;
if(x + 1 <= n)
v[x + 1] ˆ= 1;
}

const int INF = (1 << 29) - 1;

int go()
{
int cnt = 0;

for(int i = 2; i <= n; ++i)


{
if(v[i - 1] != fin[i - 1])
{
cnt++;
apply(i);
}
}

for(int i = 1; i <= n; ++i)


if(v[i] != fin[i])
return INF;
CAPITOLUL 7. ONI 2016 7.6. OMOGENE 172

return cnt;
}

ofstream out("leduri.out");

int main()
{
ifstream in("leduri.in");
in >> n;
assert(1 <= n && n <= 100000);

for(int i = 1; i <= n; ++i)


{
in >> v[i];
init[i] = v[i];
assert(v[i] == 0 || v[i] == 1);
}

for(int i = 1; i <= n; ++i)


{
in >> fin[i];
assert(fin[i] == 0 || fin[i] == 1);
}

int s1 = go();

for(int i = 1; i <= n; ++i)


v[i] = init[i];

v[1] ˆ= 1;
v[2] ˆ= 1;

int s2 = go() + 1;

if(s1 >= INF && s2 >= INF)


{
out << "-1\n";
return 0;
}
else
out << min(s1, s2) << "\n";

return 0;
}

7.5.3 *Rezolvare detaliată

7.6 omogene
Problema 6 - omogene 100 de puncte
Se consideră o matrice cu L linii şi C coloane care memorează doar valori din mulţimea 0,1,2. O
submatrice nevidă (formată din cel puţin o linie şi cel puţin o coloană) a acestei matrice o numim
omogenă dacă numărul valorilor de 0 este egal cu numărul de valori de 1 şi egal cu numărul
valorilor de 2.
De exemplu, ı̂n matricea
0 1 2 0
1 2 0 1
sunt şase submatrice omogene, acestea fiind:
012 120 012 120 120 201
120 201
Submatricele a treia şi a patra sunt formate din prima linie a matricei iniţială, iar submatricele
a cincea şi a şasea sunt formate din a doua linie.

Cerinţe

Să se determine câte submatrice nevide omogene există.


CAPITOLUL 7. ONI 2016 7.6. OMOGENE 173

Date de intrare

Fişierul omogene.in conţine pe prima linie numerele naturale L şi C. Pe următoarele L linii
se află câte C numere naturale separate prin spaţii reprezentând câte o linie din matrice.

Date de ieşire

Fişierul omogene.out va conţine pe prima linie un singur număr natural reprezentând


numărul submatricelor nevide omogene.

Restricţii şi precizări

a 2 & L & C & 5 000


a 4 & L ˜ C & 65 536
a Atenţie, o submatrice este formată dintr-o secvenţă continuă de linii şi coloane, deci, de
exemplu, dacă se aleg dintr-o matrice liniile 1, 2 şi 5, atunci acestea nu formează o submatrice.
9
a Numărul submatricelor omogene va fi mai mic decât 2 ˜ 10
a ı̂ntreaga matrice poate fi submatrice omogenă

Exemple:

omogene.in omogene.out Explicaţii


24 6 Cele şase submatrice au fost menţionate ı̂n enunţ.
0120
1201
33 3
012
022
011
Timp maxim de executare/test: 3.0 secunde
Memorie: total 64 MB
Dimensiune maximă a sursei: 15 KB

7.6.1 Indicaţii de rezolvare

prof. Dan Pracsiu, Liceul Teoretic Emil Racoviţă Vaslui

Rezolvăm mai ı̂ntâi problema liniară. Se consideră un vector t de lungime n care memorează
doar valori din mulţimea {1,2,3}. Să se determine numărul secvenţelor omogene (adică ı̂n care
numărul valorilor de 0 este egal cu numărul valorilor de 1 şi egal cu numărul valorilor de 2).
Construim sirul sumelor partiale:
s0[i]= numărul valorilor de 0 din t[1..i]
s1[i]= numărul valorilor de 1 din t[1..i]
s2[i]= numărul valorilor de 2 din t[1..i]
Apoi construim şirurile
d01[0] = d12[0] = d20[0] = 0 şi
d01[i] = s0[i] - s1[i], i=1..n
d12[i] = s1[i] - s2[i] , i=1..n
d20[i] = s2[i] - s0[i], i=1..n
Vom determina apoi câte perechi de triplete { d01[i], d12[i], d20[i] } egale sunt ı̂n acest şir de
lungime n+1 (i=0..n). Dacă două triplete { d01[i],d12[i], d20[i] } şi { d01[k],d12[k], d20[k] } (i ¡ j)
sunt identice, atunci secvenţa t[i+1], t[i+2],t[j] este o secvenţă omogenă.
Pentru a afla numărul perechilor de triplete egale, se poate sorta şirul tripletelor. ı̂n şirul
sortat, tripletele identice sunt alăturate. Dacă identificăm X triplete identice, atunci numărul
perechilor de triplete identice este X(X-1)/2 (oricare două).
Deci complexitatea determinării numărului de secvenţe omogene din vectorul t este dată se
sortarea tripletelor, deci O n log n.
CAPITOLUL 7. ONI 2016 7.6. OMOGENE 174

Revenind la problema bidimensională: Din restricţii, L & C şi L ˜ C &65 536. De unde rezultă
că
L & sqrt 65536 256.
Deci numărul de linii din matrice nu poate fi mai mare de 256.
Construim două matrice de sume parţiale:
z[i][j] = numărul de valori de 0 aflate pe coloana j, ı̂ncepând de la poziţia (i,j) ı̂n sus.
u[i][j] = numărul de valori de 1 aflate pe coloana j, ı̂ncepând de la poziţia (i,j) ı̂n sus.
Pentru orice două linii k şi p (unde k¡=p), dorim să aflăm câte submatrice omogene de ı̂nălţime
H=p-k+1 există ı̂ntre cele două linii. Vom construi deci vectorul t0[j] = z[p][j]-z[k-1][j], j=1..C,
deci ı̂n care t[j] memorează numărul valorilor de 0 aflate pe coloana j ı̂ntre liniile k şi p. Similar,
se construieşte t1[j]=u[p][j]-u[k-1][j], j=1..C, deci ı̂n care t1[j] memorează numărul valorilor de 1
aflate pe coloana j ı̂ntre liniile k şi p. Atunci t2[j]=H-t0[j]-t1[j], pentru j=1..C.
Apoi problema se rezolvă ca ı̂n cazul unidimensional.
Complexitatea va fi deci O L ˜ L ˜ C ˜ logC 

7.6.2 Cod sursă

Listing 7.6.1: omogene eric.cpp


#include <fstream>
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

typedef long long LL;

const int MAX_N = 320 + 10;


const int MAX_M = 10000 + 10;

int v[MAX_N][MAX_M];
int n, m;

int s[MAX_N][MAX_M][3];

LL ans = 0;

int vect[MAX_M][3];
//vect[i][j] -> partial sum of j over the i prefix

struct state
{
int v[3];
//v[0] -> cnt[0] - cnt[1]
//v[1] -> cnt[0] - cnt[2]
//v[2] -> cnt[1] - cnt[2]

bool operator==(const state &other)


{
for(int k = 0; k < 3; ++k)
if(v[k] != other.v[k])
return 0;
return 1;
}
};

state good = {0, 0, 0};

bool comp(const state &a, const state &b)


{
int k = 0;
while(k < 2 && a.v[k] == b.v[k])
k++;
return a.v[k] < b.v[k];
}

state sts[MAX_M];
CAPITOLUL 7. ONI 2016 7.6. OMOGENE 175

state getState(int i)
{
state ret;
ret.v[0] = vect[i][0] - vect[i][1];
ret.v[1] = vect[i][0] - vect[i][2];
ret.v[2] = vect[i][1] - vect[i][2];
return ret;
}

LL cnt(int l1, int l2)


{
for(int i = 1; i <= m; ++i)
{
for(int k = 0; k < 3; ++k)
vect[i][k] = vect[i - 1][k] + s[l2][i][k] - s[l1 - 1][i][k] -
s[l2][i - 1][k] + s[l1 - 1][i - 1][k];
}

for(int i = 1; i <= m; ++i)


sts[i] = getState(i);

sort(sts + 1, sts + m + 1, comp);

LL ret = 0;

for(int i = 1, j; i <= m; i = j)
{
j = i + 1;
while(j <= m && sts[i] == sts[j])
j++;

LL diff = j - i;
ret += diff * (diff - 1) / 2;
}

for(int i = 1; i <= m; ++i)


ret += sts[i] == good;

return ret;
}

int main()
{
ifstream in("omogene.in");
ofstream out("omogene.out");

in >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
in >> v[i][j];

for(int i = 1; i <= n; ++i)


for(int j = 1; j <= m; ++j)
{
for(int k = 0; k < 3; ++k)
{
s[i][j][k] += s[i - 1][j][k] + s[i][j - 1][k]
- s[i - 1][j - 1][k];
}
s[i][j][v[i][j]]++;
}

for(int i = 1; i <= n; ++i)


for(int j = i; j <= n; ++j)
ans += cnt(i, j);

out << ans << "\n";


return 0;
}

Listing 7.6.2: omogene n 4.cpp


#include <bits/stdc++.h>
CAPITOLUL 7. ONI 2016 7.6. OMOGENE 176

#define MAXC 10003


#define MAXL 320
#define inFile "omogene.in"
#define outFile "omogene.out"

using namespace std;

int z[MAXL][MAXC];
int u[MAXL][MAXC];
int d[MAXL][MAXC];

int L, C;
long long sol;

void NrSubmat()
{
int i, j, i2, j2, zero, unu, doi;
for (i = 1; i <= L; ++i)
for (j = 1; j <= C; ++j)
for (i2 = i; i2 <= L; ++i2)
for (j2 = j; j2 <= C; ++j2)
{
zero=z[i2][j2]-z[i-1][j2]-z[i2][j-1]+z[i-1][j-1];
unu=u[i2][j2]-u[i-1][j2]-u[i2][j-1]+u[i-1][j-1];
doi=d[i2][j2]-d[i-1][j2]-d[i2][j-1]+d[i-1][j-1];
if (zero == unu && zero == doi)
sol++;
}
}

void Citire()
{
int i, j, x;
ifstream fin(inFile);
fin >> L >> C;
for (i = 1; i <= L; ++i)
for (j = 1; j <= C; ++j)
{
fin >> x;
z[i][j] = z[i-1][j]+z[i][j-1]-z[i-1][j-1]+(x == 0);
u[i][j] = u[i-1][j]+u[i][j-1]-u[i-1][j-1]+(x == 1);
d[i][j] = d[i-1][j]+d[i][j-1]-d[i-1][j-1]+(x == 2);
}
fin.close();
}

void Afisare()
{
ofstream fout(outFile);
fout << sol << "\n";
fout.close();
}

int main()
{
Citire();
NrSubmat();
Afisare();
return 0;
}

Listing 7.6.3: omogeneMG.cpp


#include<fstream>
#include<algorithm>

using namespace std;

ifstream f("omogene.in");
ofstream g("omogene.out");

struct nod
{
int d01,d12,d20;
};
CAPITOLUL 7. ONI 2016 7.6. OMOGENE 177

inline bool test(const nod &t1,const nod &t2)


{
return t1.d01<t2.d01 ||
(t1.d01==t2.d01 && t1.d12<t2.d12) ||
(t1.d01==t2.d01 && t1.d12==t2.d12 && t1.d20<t2.d20);
}

nod v[10001];
int a[318][10001];
int z[318][10001],u[318][10001];
int t0,t1,t2,st0,st1,st2;
long long sol;
int n,m;

void citire()
{
f>>n>>m;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
f>>a[i][j];
f.close();
}

void prelucrare()
{
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
z[i][j]=z[i-1][j]+(a[i][j]==0);
u[i][j]=u[i-1][j]+(a[i][j]==1);
}

for(int k=1;k<=n;++k)
for(int p=k;p<=n;++p)
{
int d=p-k+1;
st0=st1=st2=0;
v[0].d01=v[0].d12=v[0].d20=0;
for(int j=1;j<=m;++j)
{
t0=z[p][j]-z[k-1][j];st0+=t0;
t1=u[p][j]-u[k-1][j];st1+=t1;
t2=d-t0-t1;st2+=t2;
v[j].d01=st0-st1;
v[j].d12=st1-st2;
v[j].d20=st2-st0;
}

sort(v,v+m+1,test);

int x=1;
for(int i=1;i<=m;++i)
if(v[i-1].d01==v[i].d01 &&
v[i-1].d12==v[i].d12 &&
v[i-1].d20==v[i].d20)
x++;
else
{
sol=sol+1ll*x*(x-1)/2;
x=1;
}

sol=sol+1ll*x*(x-1)/2;
}
}

int main()
{
citire();
prelucrare();
g<<sol<<’\n’;
return 0;
}
CAPITOLUL 7. ONI 2016 7.6. OMOGENE 178

7.6.3 *Rezolvare detaliată


Capitolul 8

ONI 2015

8.1 cub
Problema 1 - cub 100 de puncte
Sărbătorile de iarnă tocmai s-au ı̂ncheiat. Florinel doreşte să-şi ajute
părinţii la despodobirea bradului. Tubul luminos pe care l-au folosit
3
anul acesta este mai special. Are N becuri luminoase numerotate de
3
la 1 la N , iar fiecare bec care este inscripţionat cu un număr prim, va
rămâne mereu aprins.
Cutia ı̂n care trebuie strâns tubul este un cub de latură N . Becul cu
numărul 1, trebuie pus ı̂n colţul de coordonate (1,1,1), restul ı̂n spirală
până la umplerea nivelului, apoi nivelul următor ı̂n sens invers, ş.a.m.d.

Cerinţe

Cunoscând latura N a cubului, să se umple cubul cu tubul luminos


(becurile fiind legate crescător), apoi să se determine:
1. Coordonatele x, y, z  ale becului cu numărul V . (x-linia, y-coloana, z-ı̂nălţimea)
2. Numărul de becuri luminoase situate pe fiecare faţă a cubului.

Date de intrare

Fişierul de intrare cub.in conţine pe prima linie un număr natural p. Pentru toate testele de
intrare, numărul p poate avea doar valoarea 1 sau valoarea 2.
Pe a doua linie a fişierului de intrare, sunt scrise două numere naturale N şi V separate printr-
un spaţiu reprezentând dimensiunea cubului şi valoarea becului pentru care trebuie determinate
coordonatele.

Date de ieşire

a Dacă valoarea lui p este 1, se va rezolva numai cerinţa 1. În acest caz, ı̂n fişierul de ieşire
cub.out se vor scrie trei numere naturale x y z, separate prin câte un spaţiu, reprezentând
coordonatele becului cu valoarea V .

179
CAPITOLUL 8. ONI 2015 8.1. CUB 180

a Dacă valoarea lui p este 2, se va rezolva numai cerinţa 2. În acest caz, fişierul de ieşire
cub.out va conţine 4 linii. Pe fiecare linie i, se va scrie câte un număr natural fi, reprezentând
numărul de becuri inscripţionate cu numere prime de pe faţa i.

Restricţii şi precizări

a 1 & N & 200


1&V &N
3
a
a Pentru rezolvarea corectă a primei cerinţe se acordă 20 de puncte, iar pentru cerinţa a doua
se acordă 80 de puncte.
a Pentru 20% dintre teste: 1 & N & 20
a Pentru 30% dintre teste: 21 & N & 100
a Pentru 50% dintre teste: 101 & N & 200

Exemple:

cub.in cub.outExplicaţii
1 3 10 222 Atenţie! Pentru acest test se rezolvă doar cerinţa 1).
linia 2, coloana 2, nivel 2 - este becul 10
2 3 10 4343 Atenţie! Pentru acest test se rezolvă doar cerinţa 2).
4 - becuri inscripţionate cu numere prime pe faţa 1: 2, 3, 17, 19
3 - becuri inscripţionate cu numere prime pe faţa 2: 3, 5, 23
4 - becuri inscripţionate cu numere prime pe faţa 3: 5, 7, 13, 23
3 - becuri inscripţionate cu numere prime pe faţa 4: 7, 11, 19
Timp maxim de executare/test: 0.5 secunde
Memorie: total 8 MB
Dimensiune maximă a sursei: 10 KB

8.1.1 Indicaţii de rezolvare

prof. Claudiu-Cristian Gorea - C. N. ”Al. Papiu Ilarian” Târgu-Mureş

Cubul e format din N matrici pătratice de N*N elemente.


Primul nivel se completează circular spre interior ı̂n sensul acelor de ceasornic:
a plecând din (1,1) : linia 1, coloana N, linia N, coloana 1.
a plecând din (2,2) : linia 2 (necompletată), coloana N-1, linia N-1, coloana 2. ş.a.m.d.

Dacă N este un număr par, se vor folosi la fiecare plecare 4 direcţii matricea fiind completată
astfel.
Dacă N este impar elementul din centru va fi completat ultimul.
Ultimul element completat pe nivelul 1, decide poziţia de plecare ı̂n umplerea nivelului următor,
parcurgând circular spre exterior ı̂n sens trigonometric matricea de la nivelul 2.
Matricea de pe nivelul 2 se poate obţine din valorile matricii de la nivelul 1 astfel:

B ij  2 ˜ n ˜ n  1  Aij .

unde A este matricea asociată primului nivel, iar B este matricea asociată nivelului 2.
Se observă că matricile de nivel mai mare decât 2, se pot obţine din matricile de la nivelele
anterioare astfel:
a Valoarea unui element de pe nivelul 3 se obţine adunând la valoarea de pe nivelul 1 cu
aceleaşi coordonate valoarea 2*N*N.
a Valoarea unui element de pe nivelul 4 se obţine adunând la valoarea de pe nivelul 2 cu
aceleaşi coordonate valoarea 2*N*N.
a Valoarea unui element de pe nivelul 5 se obţine adunând la valoarea de pe nivelul 3 cu
aceleaşi coordonate valoarea 2*N*N. ş.a.m.d.
Pentru rezolvarea cerinţei 1 (20 pct.): ajunge să scădem ı̂n mod repetat din valoarea V numărul
2*N*N, ţinând minte că trecem peste 2 nivele la fiecare scădere. Coordonatele X şi Y le vom obţine
căutând valoarea rămasă ı̂n matricile A sau B, iar coordonata Z, se obţine din numărul total de
scăderi*2 + valoarea 1 sau 2, ı̂n funcţie de nivelul matricii la care ajungem.
Pentru rezolvarea cerinţei 2 (80 pct.): ajunge să testăm elementele de pe chenarul matricilor
A şi B:
CAPITOLUL 8. ONI 2015 8.1. CUB 181

- linia 1 pentru faţa 1 (spate)


- coloana N pentru faţa 2 (dreapta)
- linia N pentru faţa 3 (frontal)
- coloana1 pentru faţa 4 (stânga),
la care vom aduna pentru fiecare nivel nou 2*N*N.
Pentru testarea numerelor prime se poate folosi ciurul lui Eratostene.
Complexitate O n ˜ n - 100 puncte

8.1.2 Cod sursă

Listing 8.1.1: cubul cp1.cpp


// prof. Carmen Popescu
// colegiul National "Gheorghe Lazar" Sibiu

#include <fstream>
#include <bitset>

using namespace std;

ifstream f("cub.in");
ofstream g("cub.out");

int m=2;
bitset<8000005> pr;

void prim(int n)
{
int i=1,j;
for (i=1;i<=n;i++)
pr[i]=true;
pr[1]=false;
for (i=2;i<=n/2;i++)
if (pr[i])
{
j=i+i;
while (j<=n)
{
pr[j]=false;
j+=i;
}
}
}

int main()
{
int v,n,n2,k,z,p,i,j,f1=0,f2=0,f3=0,f4=0;
m=2; pr[1]=2; pr[2]=3;
f>>v;
f>>n>>k;
n2=n*n;
prim(n2*n);
if (v==1)
{
z=1;
v=0;
while (k-v>n2)
{
z++;
v=v+n2;
}

if (z%2==1)
{
for (p=1;;p++)
{
for (j=p;j<=n-p+1;j++)
{
v++;
if (v==k)
{
CAPITOLUL 8. ONI 2015 8.1. CUB 182

g<<p<<" "<<j<<" "<<z<<"\n";


g.close();
return 0;
}
}
for (i=p+1;i<=n-p+1;i++)
{
v++;
if (v==k)
{
g<<i<<" "<<n-p+1<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (j=n-p;j>=p;j--)
{
v++;
if (v==k)
{
g<<n-p+1<<" "<<j<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (i=n-p;i>=p+1;i--)
{
v++;
if (v==k)
{
g<<i<<" "<<p<<" "<<z<<"\n";
g.close();
return 0;
}
}
}
}
else
{
v=v+n2+1;
for (p=1;;p++)
{
for (j=p;j<=n-p+1;j++)
{
v--;
if (v==k)
{
g<<p<<" "<<j<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (i=p+1;i<=n-p+1;i++)
{
v--;
if (v==k)
{
g<<i<<" "<<n-p+1<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (j=n-p;j>=p;j--)
{
v--;
if (v==k)
{
g<<n-p+1<<" "<<j<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (i=n-p;i>=p+1;i--)
{
v--;
if (v==k)
CAPITOLUL 8. ONI 2015 8.1. CUB 183

{
g<<i<<" "<<p<<" "<<z<<"\n";
g.close();
return 0;
}
}
}

}
}
else
{
for (z=1;z<=n;z++)
if (z%2==1)
{
p=1; v=(z-1)*n2;

// coltul st-sus
v++;
if (pr[v]) { f1++; f4++; }

for (j=p+1; j<=n-p; j++)


{
v++;
if (pr[v]) f1++;
}

// coltul dreapta sus


v++;
if (pr[v]) { f1++; f2++; }

for (i=p+1; i<=n-p; i++)


{
v++;
if (pr[v]) f2++;
}

// coltul dreapta jos


v++;
if (pr[v]) { f2++; f3++; }

for (j=n-p; j>=p+1; j--)


{
v++;
if (pr[v]) f3++;
}

// coltul stanga-jos
v++;
if (pr[v]) { f3++; f4++; }

for (i=n-p; i>=p+1; i--)


{
v++;
if (pr[v]) f4++;
}

v=v+n2-(4*n-4);
}
else
{
p=1;
v=v+n2-(4*n-4);
for (i=p+1; i<=n-p; i++)
{
v++;
if (pr[v]) f4++;
}

// coltul stanga jos


v++;
if (pr[v]) { f4++; f3++; }

for (j=p+1; j<=n-p; j++)


{
v++;
CAPITOLUL 8. ONI 2015 8.1. CUB 184

if (pr[v]) f3++;
}

// coltul dreapta jos


v++;
if (pr[v]) { f3++; f2++; }

for (i=n-p; i>=p+1; i--)


{
v++;
if (pr[v]) f2++;
}

// coltul dreapta sus


v++;
if (pr[v]) {f2++; f1++; }

for (j=n-p; j>=p+1; j--)


{
v++;
if (pr[v]) f1++;
}

// coltul stanga sus


v++;
if (pr[v]) { f1++; f4++; }
}

g<<f1<<"\n"<<f2<<"\n"<<f3<<"\n"<<f4<<"\n";
g.close();

}
return 0;
}

Listing 8.1.2: cubul cp2.cpp


// prof. Carmen Popescu
// colegiul National "Gheorghe Lazar" Sibiu

#include <fstream>

using namespace std;

ifstream f("cub.in");
ofstream g("cub.out");

int pr[1000000],m=2;

int prim(int x)
{
int i=1;
if (x==1) return 0;
if (x==2 || x==3)
return 1;

while (pr[i]<=x/2)
{
if (x%pr[i]==0)
return 0;
i++;
}

m++;
pr[m]=x;
return 1;
}

int main()
{
int v,n,n2,k,x,y,z,p,i,j,f1=0,f2=0,f3=0,f4=0;
m=2;
pr[1]=2;
pr[2]=3;
f>>v;
CAPITOLUL 8. ONI 2015 8.1. CUB 185

f>>n>>k;
n2=n*n;
if (v==1)
{
z=1;
v=0;
while (k-v>n2)
{
z++;
v=v+n2;
}
if (z%2==1)
{
for (p=1;;p++)
{
for (j=p;j<=n-p+1;j++)
{
v++;
if (v==k)
{
g<<p<<" "<<j<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (i=p+1;i<=n-p+1;i++)
{
v++;
if (v==k)
{
g<<i<<" "<<n-p+1<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (j=n-p;j>=p;j--)
{
v++;
if (v==k)
{
g<<n-p+1<<" "<<j<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (i=n-p;i>=p+1;i--)
{
v++;
if (v==k)
{
g<<i<<" "<<p<<" "<<z<<"\n";
g.close();
return 0;
}
}
}
}
else
{
v=v+n2+1;
for (p=1;;p++)
{
for (j=p;j<=n-p+1;j++)
{
v--;
if (v==k)
{
g<<p<<" "<<j<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (i=p+1;i<=n-p+1;i++)
{
v--;
if (v==k)
CAPITOLUL 8. ONI 2015 8.1. CUB 186

{
g<<i<<" "<<n-p+1<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (j=n-p;j>=p;j--)
{
v--;
if (v==k)
{
g<<n-p+1<<" "<<j<<" "<<z<<"\n";
g.close();
return 0;
}
}
for (i=n-p;i>=p+1;i--)
{
v--;
if (v==k)
{
g<<i<<" "<<p<<" "<<z<<"\n";
g.close();
return 0;
}
}
}

}
}
else
{
for (z=1;z<=n;z++)
if (z%2==1)
{
p=1; v=(z-1)*n2;

// coltul st-sus
v++;
k=prim(v);
if (k) { f1++; f4++; }

for (j=p+1; j<=n-p; j++)


{
v++;
k=prim(v);
if (k) f1++;
}

// coltul dreapta sus


v++;
k=prim(v);
if (k) { f1++; f2++; }

for (i=p+1; i<=n-p; i++)


{
v++;
k=prim(v);
if (k) f2++;
}

// coltul dreapta jos


v++;
k=prim(v);
if (k) { f2++; f3++; }

for (j=n-p; j>=p+1; j--)


{
v++;
k=prim(v);
if (k) f3++;
}

// coltul stanga-jos
v++;
k=prim(v);
CAPITOLUL 8. ONI 2015 8.1. CUB 187

if (k) { f3++; f4++; }

for (i=n-p; i>=p+1; i--)


{
v++;
k=prim(v);
if (k) f4++;
}

for (i=1; i<=n2-(4*n-4); i++)


{
v++;
prim(v);
}
}
else
{
p=1;
for (i=1; i<=n2-(4*n-4); i++)
{
v++;
prim(v);
}

for (i=p+1; i<=n-p; i++)


{
v++;
k=prim(v);
if (k) f4++;
}

// coltul stanga jos


v++;
k=prim(v);
if (k) { f4++; f3++; }

for (j=p+1; j<=n-p; j++)


{
v++;
k=prim(v);
if (k) f3++;
}

// coltul dreapta jos


v++;
k=prim(v);
if (k) { f3++; f2++; }

for (i=n-p; i>=p+1; i--)


{
v++;
k=prim(v);
if (k) f2++;
}

// coltul dreapta sus


v++;
k=prim(v);
if (k) {f2++; f1++; }

for (j=n-p; j>=p+1; j--)


{
v++;
k=prim(v);
if (k) f1++;
}

// coltul stanga sus


v++;
k=prim(v);
if (k) { f1++; f4++; }
}
g<<f1<<"\n"<<f2<<"\n"<<f3<<"\n"<<f4<<"\n";
g.close();
CAPITOLUL 8. ONI 2015 8.1. CUB 188

}
return 0;
}

Listing 8.1.3: cubul gcc 2matrici.cpp


///prof. Gorea Claudiu-Cristian
///C.N. Al. Papiu Ilarian Tg-Mures
#include <fstream>

using namespace std;

ifstream fin ("cub.in");


ofstream fout("cub.out");

int a[201][201],b[201][201];
int p,n,v,i,j,x,k,sum,f1,f2,f3,f4,nr;

int prim(int m)
{
int d;
if (m<2 ||(m>2 && m%2==0)) return 0;
for(d=2;d*d<=m;d++)
if(m%d==0) return 0;
return 1;
}

void afisare(int mat[201][201])


{
int l,c;
for(l=1;l<=n;l++)
{
for(c=1;c<=n;c++)
fout<<mat[l][c]<<" ";
fout<<endl;
}
fout<<"---------------------\n";
}

int main()
{
fin>>p;
fin>>n>>v;

///nivel 1 - matrice A
x=1;
k=0;
while(x<=n/2)
{
///spre stanga - linia x
for(j=x;j<=n-x;j++)
a[x][j]=++k;

///spre jos - coloana n-x+1


for(i=x;i<=n-x;i++)
a[i][n-x+1]=++k;

///spre stanga - linia n-x+1


for(j=n-x+1;j>x;j--)
a[n-x+1][j]=++k;

///spre sus - coloana x


for(i=n-x+1;i>x;i--)
a[i][x]=++k;
x++;
}

if (n%2==1) a[n/2+1][n/2+1]=++k;
//afisare(a);

///nivel 2 - matrice B
sum=2*n*n+1;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
b[i][j]=sum-a[i][j];
CAPITOLUL 8. ONI 2015 8.1. CUB 189

//afisare(b);
if (p==1)
{
///cerinta 1
x=v;
int nivel=0;
while(x>2*n*n)
x=x-2*n*n, nivel+=2;
int stop=0;
for(i=1;i<=n && stop==0; i++)
for(j=1;j<=n && stop==0; j++)
{
if(a[i][j]==x)
{
fout<<i<<" "<<j<<" "<<nivel+1<<endl;
stop=1;
}
if(b[i][j]==x)
{
fout<<i<<" "<<j<<" "<<nivel+2<<endl;
stop=1;
}
}
}
else
{
///cerinta 2
f1=f2=f3=f4=0;
///fata 1 - linia 1 A,B,.... 200*200 elemente=40.000 nr de testat
for(j=1;j<=n;j++)
{
nr=a[1][j];
while(nr<n*n*n)
{
if (prim(nr)) f1++;
nr+=2*n*n;
}
nr=b[1][j];
while(nr<n*n*n)
{
if (prim(nr)) f1++;
nr+=2*n*n;
}
}
///fata 2 - coloana n A,B,....
for(i=1;i<=n;i++)
{
nr=a[i][n];
while(nr<n*n*n)
{
if (prim(nr)) f2++;
nr+=2*n*n;
}
nr=b[i][n];
while(nr<n*n*n)
{
if (prim(nr)) f2++;
nr+=2*n*n;
}
}
///fata 3 - linia n A,B,....
for(j=1;j<=n;j++)
{
nr=a[n][j];
while(nr<n*n*n)
{
if (prim(nr)) f3++;
nr+=2*n*n;
}
nr=b[n][j];
while(nr<n*n*n)
{
if (prim(nr)) f3++;
nr+=2*n*n;
}
}
CAPITOLUL 8. ONI 2015 8.1. CUB 190

///fata 4 - coloana 1 A,B,....


for(i=1;i<=n;i++)
{
nr=a[i][1];
while(nr<n*n*n)
{
if (prim(nr)) f4++;
nr+=2*n*n;
}
nr=b[i][1];
while(nr<n*n*n)
{
if (prim(nr)) f4++;
nr+=2*n*n;
}
}
fout<<f1<<’\n’;
fout<<f2<<’\n’;
fout<<f3<<’\n’;
fout<<f4<<’\n’;
}

return 0;
}

Listing 8.1.4: cubul gcc umplere ciur.cpp


///prof. Gorea Claudiu-Cristian
///C.N. Al. Papiu Ilarian Tg-Mures
#include <cstdio>

using namespace std;

int a[101][101][101],n,v,l,c,x,y,z,nivel,i,j,k,colt,lim,p;
bool ciur[1000001];
int f1,f2,f3,f4;

void afisare()
{
int i,j;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
printf("%4d",a[i][j][nivel]);
printf("\n");
}
printf("\n");
}

int main()
{
freopen ("cub.in","r",stdin);
freopen ("cub.out","w",stdout);

scanf("%d",&p);
scanf("%d %d",&n,&v);

nivel=0;
k=0;
ciur[1]=ciur[0]=true; /// adica nu e prim;
for(i=2;i<=n*n*n;i++)
if (ciur[i]==false)
for(j=2;j<=n*n*n/i;j++)
ciur[i*j]=true;

f1=f2=f3=f4=0;
/// SE COMPLETEAZA INTREGUL CUB
while(nivel<n)
{
nivel++;
///nivel impar - plec din 1,1
l=c=1;
while(l<=n/2)
{
///dir1
CAPITOLUL 8. ONI 2015 8.1. CUB 191

for(j=c;j<=n-c;j++)
{
k++;
a[l][j][nivel]=k;
if (k==v && p==1) printf("%d %d %d\n",l,j,nivel);
}
///dir2
for(i=l;i<=n-l;i++)
{
k++;
a[i][n-c+1][nivel]=k;
if (k==v && p==1) printf("%d %d %d\n",i,n-c+1,nivel);
}
///dir3
for(j=n-c+1;j>c;j--)
{
k++;
a[n-l+1][j][nivel]=k;
if (k==v && p==1) printf("%d %d %d\n",n-l+1,j,nivel);
}
///dir4
for(i=n-l+1;i>l;i--)
{
k++;
a[i][c][nivel]=k;
if (k==v && p==1) printf("%d %d %d\n",i,c,nivel);
}
l++;
c++;
}
if (n%2==1)
{
k++;
a[n/2+1][n/2+1][nivel]=k;
if (k==v && p==1) printf("%d %d %d\n",n/2+1,n/2+1,nivel);
}
// afisare();
if (nivel<n)
{
colt=a[1][1][nivel];
nivel++; /// nivelul par
lim=colt+n*n*2;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
a[i][j][nivel]=(colt-1)+lim-a[i][j][nivel-1];
if (a[i][j][nivel]==v && p==1)
printf("%d %d %d\n",i,j,nivel);
}
k+=n*n;
// afisare();
}
}
///fata 1
for(nivel=1;nivel<=n;nivel++)
for(j=1;j<=n;j++)
if(ciur[a[1][j][nivel]]==false)
{
// printf("%d ",a[1][j][nivel]);
f1++;
}
if (p==2) printf("%d\n",f1);
///fata 2
for(nivel=1;nivel<=n;nivel++)
for(i=1;i<=n;i++)
if(ciur[a[i][n][nivel]]==false)
{
// printf("%d ",a[i][n][nivel]);
f2++;
}
if (p==2) printf("%d\n",f2);
///fata 3
for(nivel=1;nivel<=n;nivel++)
for(j=1;j<=n;j++)
if(ciur[a[n][j][nivel]]==false)
{
CAPITOLUL 8. ONI 2015 8.1. CUB 192

// printf("%d ",a[n][j][nivel]);
f3++;
}
if (p==2) printf("%d\n",f3);
///fata 4
for(nivel=1;nivel<=n;nivel++)
for(i=1;i<=n;i++)
if(ciur[a[i][1][nivel]]==false)
{
// printf("%d ",a[i][1][nivel]);
f4++;
}
if (p==2) printf("%d\n",f4);
return 0;
}

Listing 8.1.5: cubul mot e.cpp


// cubul - E.M. - O(nˆ2*log n)
#include <fstream>
#include <cmath>

using namespace std;

const int Npr=430;


int p[Npr]=
{2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,
101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,
193,197,199,
211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,
307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,
401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,
503,509,521,523,541,547,557,563,569,571,577,587,593,599,
601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,
701,709,719,727,733,739,743,751,757,761,769,773,787,797,
809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,
907,911,919,929,937,941,947,953,967,971,977,983,991,997,
1009,1013,1019,1021,1031,1033,1039,1049,1051,1061,1063,1069,1087,1091,1093,
1097,
1103,1109,1117,1123,1129,1151,1153,1163,1171,1181,1187,1193,
1201,1213,1217,1223,1229,1231,1237,1249,1259,1277,1279,1283,1289,1291,1297,
1301,1303,1307,1319,1321,1327,1361,1367,1373,1381,1399,
1409,1423,1427,1429,1433,1439,1447,1451,1453,1459,1471,1481,1483,1487,1489,
1493,1499,
1511,1523,1531,1543,1549,1553,1559,1567,1571,1579,1583,1597,
1601,1607,1609,1613,1619,1621,1627,1637,1657,1663,1667,1669,1693,1697,1699,
1709,1721,1723,1733,1741,1747,1753,1759,1777,1783,1787,1789,
1801,1811,1823,1831,1847,1861,1867,1871,1873,1877,1879,1889,
1901,1907,1913,1931,1933,1949,1951,1973,1979,1987,1993,1997,1999,
2003,2011,2017,2027,2029,2039,2053,2063,2069,2081,2083,2087,2089,2099,
2111,2113,2129,2131,2137,2141,2143,2153,2161,2179,
2203,2207,2213,2221,2237,2239,2243,2251,2267,2269,2273,2281,2287,2293,2297,
2309,2311,2333,2339,2341,2347,2351,2357,2371,2377,2381,2383,2389,2393,2399,
2411,2417,2423,2437,2441,2447,2459,2467,2473,2477,
2503,2521,2531,2539,2543,2549,2551,2557,2579,2591,2593,
2609,2617,2621,2633,2647,2657,2659,2663,2671,2677,2683,2687,2689,2693,2699,
2707,2711,2713,2719,2729,2731,2741,2749,2753,2767,2777,2789,2791,2797,
2801,2803,2819,2833,2837,2843,2851,2857,2861,2879,2887,2897,
2903,2909,2917,2927,2939,2953,2957,2963,2969,2971,2999};

int prim(int x)
{
int i,L;
if (x<2) return 0;

L=(int)sqrt((double)x);
for (i=0;p[i]<=L;i++)
if(x%p[i]==0)
return 0;
return 1;
}

int a[202][202];
int dx[4]={0,1,0,-1}, dy[4]={1,0,-1,0};
CAPITOLUL 8. ONI 2015 8.1. CUB 193

int main()
{
ifstream fi("cub.in"); ofstream fo("cub.out");
int i,j,k,d,p,N,V;
fi>>p>>N>>V;

if (p==1)
{
for(i=0;i<=N+1;i++)
a[0][i]=a[N+1][i]=a[i][0]=a[i][N+1]=1;

i=j=1;
d=0;
for(k=1;k<=N*N;k++)
{
a[i][j]=k;
if(!a[i+dx[d]][j+dy[d]])
{
i+=dx[d];
j+=dy[d];
}
else
{
d=(d+1)%4;
i+=dx[d];
j+=dy[d];
}
}

k=V/(N*N);
V%=(N*N);
if (k&1)
V=N*N+1-V;

for (i=1;i<=N;i++)
for (j=1;j<=N;j++)
if (a[i][j]==V)
{
fo<<i<<" "<<j<<" "<<k+1<<"\n";
break;
}
}
else
{
int f[5],t;
f[1]=f[2]=f[3]=f[4]=0;
for (i=1;i<=N;i++)
for (j=1;j<=N;j++)
for (t=0;t<4;t++)
{
if (i&1)
k=j+(i-1)*N*N+t*(N-1);
else
k=i*N*N-j+1-t*(N-1);

if (t==3 && j==N)


k=(i&1)*(1+(i-1)*N*N);

f[t+1]+=prim(k);
}
for (t=1;t<=4;t++)
fo<<f[t]<<"\n";
}

return 0;
}

8.1.3 *Rezolvare detaliată


CAPITOLUL 8. ONI 2015 8.2. RISC 194

8.2 risc
Problema 2 - risc 100 de puncte
Pentru a participa la un concert, n persoane s-au aşezat la coadă pe un singur rând ı̂n aşteptarea
deschiderii casei de bilete. Înălţimile celor n persoane sunt toate distincte. Pe baza acestei
informaţii cruciale, agenţii de securitate au decis ca din motive de ... securitate, ordinea per-
soanelor care aşteaptă la coadă trebuie schimbată ı̂n funcţie de ı̂nălţimile lor.
Astfel, agentii de pază vor alege, pe rând, câte o persoană şi o vor trimite la sfârşitul rândului.
După fiecare operaţie de tipul acesta, să-i spunem ”de mutare”, rândul se restrânge, ocupându-
se poziţia rămasă liberă. Strategia agenţilor de pază este aceasta: la terminarea tuturor operaţiilor
de mutare, riscul minim de securitate se obţine dacă toate persoanele aflate ı̂n dreapta persoanei
celei mai ı̂nalte vor fi mai ı̂nalte decât cele aflate ı̂n stânga persoanei cele mai ı̂nalte. În plus,
ı̂nalţimile persoanelor vor fi crescătoare până la poziţia k a persoanei celei mai ı̂nalte şi de-
screscătoare după poziţia k.
Mai exact: dacă h1 , h2 , ..., hn sunt ı̂nălţimile persoanelor după finalizarea operaţiilor de
mutare, atunci: există o poziţie k, cu 1 & k & n astfel ı̂ncât

h1 $ h2 $ ...hk1 $ hk % hk1 % ... % hn1 % hn


şi ı̂n plus hi $ hj pentru oricare i $ k şi k $ j.
Deoarece o asemenea logică este greu de combătut, iar agenţii nu au aerul că vor să glumească,
persoanele care aşteaptă la coadă vor accepta toate mutările impuse de către aceştia.

Cerinţe

Cunoscând numărul de persoane n şi ı̂nălţimile h1 , h2 , ..., hn ale acestora să se scrie un program
care determină :
1. Poziţia persoanei celei mai ı̂nalte ı̂n rândul iniţial, ı̂n cazul ı̂n care nu sunt necesare operaţii
de mutare.
2. Numărul minim de mutări necesare pentru ca rândul de persoane să prezinte un risc minim
de securitate.

Date de intrare

Pe prima linie a fişierului de intrare risc.in se găseşte numărul natural p a cărui valoare poate
fi doar 1 sau 2.
Pe a doua linie a fişierului de intrare se află numărul natural n cu semnificaţia din enunţ.
Pe a treia linie se găsesc n numere naturale, distincte: h1 , h2 , ..., hn , separate prin câte un
singur spaţiu, reprezentând ı̂nălţimile persoanelor.

Date de ieşire

Fişierul de ieşire este risc.out.


a Dacă valoarea lui p este 1 atunci se va rezolva numai cerinţa 1. ı̂n acest caz, fişierul de ieşire
va conţine pe prima linie un număr natural poz ce reprezintă poziţia persoanei celei mai ı̂nalte ı̂n
rândul iniţial. Dacă rândul iniţial nu respectă condiţiile de risc minim de securitate, atunci poz
este -1.
a Dacă valoarea lui p este 2 se va rezolva numai cerinţa 2. ı̂n acest caz fişierul de ieşire va
conţine pe prima linie un număr natural m, reprezentând numărul minim de mutări necesare
pentru a obţine risc minim de securitate.

Restricţii şi precizări

a 1 & n & 100 000


a 1 & h1 , h2 , ...hn & 100 000
a Persoana cea mai ı̂naltă ı̂ntr-o configuraţie cu risc minim de securitate poate fi plasată pe
oricare dintre poziţiile 1...n
a Pentru 50% din teste, n & 2 000, iar pentru alte 40% din teste, n & 10 000
a Pentru rezolvarea corectă a primei cerinţe se acordă 20 de puncte, iar pentru cerinţa a doua
se acordă 80 de puncte.

Exemple:
CAPITOLUL 8. ONI 2015 8.2. RISC 195

risc.in risc.out Explicaţii


1 4 35 20 10 1 p = 1, deci se rezolvă numai cerinţa 1.
1 Rândul ı̂ndeplineşte condiţiile de risc minim de securitate.
Persoana cea mai ı̂naltă se găseste pe poziţia poz = 1.
1 6 1 8 12 20 -1 p = 1, deci se rezolvă numai cerinţa 1.
15 10 Rândul NU ı̂ndeplineşte condiţiile de risc minim de securitate.
şirul ı̂nălţimilor nu respectă condiţia ca toate valorile ı̂nălţimilor
din dreapta poziţiei persoanei celei mai ı̂nalte să fie mai mari
decât toate valorile ı̂nălţimilor din stânga acesteia.
Deci poz = -1.
2 5 2 8 4 20 1 p = 2, deci se rezolvă numai cerinţa 2.
16 Se mută persoana de ı̂nălţime 8 la sfârşitul rândului.
Deci m = 1
2 6 3 19 7 30 2 p = 2, deci se rezolvă numai cerinţa 2.
10 25 Prima operaţie: se mută persoana de ı̂nălţime 19 la sfârşitul
rândului.
Rândul devine: 3 7 30 10 25 19
A doua operaţie: se mută persoana de ı̂nălţime 10 la sfârşitul
rândului.
Rândul devine: 3 7 30 25 19 10
Deci m = 2
Timp maxim de executare/test: 0.2 secunde
Memorie: total 4 MB
Dimensiune maximă a sursei: 10 KB

8.2.1 Indicaţii de rezolvare

prof. Constantin Gălăţan - C. N. ”Liviu Rebreanu” Bistriţa index[person]Constantin Gălăţan

Cerinţa 1. 20 puncte
Se determină ı̂nalţimea hmax şi poziţia pmax a celei mai ı̂nalte persoane.
Pentru fiecare poziţie 2 & i & pmax se verifică dacă h[i] ¿ h[i -1] şi similar, pentru fiecare
poziţie i % pmax se verifică dacă pentru persoana i aflată ı̂n dreapta acesteia se verifică dacă
hi $ hi  1. Pentru a determina dacă toate persoanele aflate ı̂n dreapta poziţiei pmax sunt
mai ı̂nalte decât cele aflate ı̂n stânga se verifică condiţia hpmax  1 $ hn.
Cerinţa 2. Total - 80 de puncte
Soluţia 1 - Complexitate O n ˜ n, prof. Carmen Popescu, C. N. ”Gheorge Lazăr” Sibiu
Se marchează valorile care trebuie mutate folosind un vector boolean. Simultan cu aceste
mutări vom calcula şi următoarele valori:
mx = maximul valorilor ce se mută la sfârşit
mn = minimul valorilor ce vor fi ı̂n dreapta maximului după mutări
Pentru fiecare ı̂nălţime hi, i $ k căutăm toate valorile din faţa sa care sunt mai mari, adică
hj  % hi cu j $ i (este ı̂ncălcată prima condiţie de mai sus), acestea vor fi ”mutate”.
Vom ”muta” apoi la sfârşit toate valorile aflate ı̂n dreapta maximului care nu respectă cerinţa
a doua de mai sus, deci pentru fiecare ı̂nălţime hi cu i % k  1 vom ”muta” la sfârşit valorile
din faţa sa, care sunt mai mici decât hi, adică mutăm valorile hj  cu j % k şi j $ i pentru care
hj  $ hi.
”Mutăm” apoi valorile care au rămas ı̂n stânga maximului şi care sunt mai mari decât minimul
valorilor din dreapta (condiţia a treia de mai sus), deci mai mari decât mn.
Acum toate valorile sunt ı̂n ”jumătatea” potrivită, ı̂nsă s-ar putea ca elementele care se aflau
iniţial ı̂n dreapta maximului şi nu au fost mutate să fie mai mici decât maximul valorilor mutate la
sfârşit (ı̂ncalcă condiţia a doua de mai sus) si să trebuiască să le ”mutăm” la sfârşit. Vom ”muta”
aşadar la sfârşit valorile hi $ mx cu i % k.
O altă posibilitate de a ”aranja” şirul dat este să mutăm maximul la sfârşitul şirului şi apoi să
reluăm ”mutările” ca mai sus. Se va alege cea mai favorabilă din cele două variante.
Această soluţie obţine 30 de puncte din 80 posibile.
CAPITOLUL 8. ONI 2015 8.2. RISC 196

Soluţia 2 - Complexitate O n ˜ log n prof. Constantin Gălăţan, C. N. ”Liviu Rebreanu”


Bistriţa
Persoanele care nu se mută trebuie alese ı̂n aşa fel ı̂ncât să existe certitudinea că celelalte
persoane pot fi mutate la capătul din dreapta al rândului. Astfel, va trebui să găsim lungimea
celui mai lung subşir crescător, format din cele mai mici numere din şir. De asemenea, ı̂ncepând
cu poziţia pmax a maximului ı̂nălţimilor, vom determina un număr Desc reprezentând lungimea
celui mai lung subşir descrescător format din cele mai mari numere din şir.
Atenţie, nu se aplica algoritmul clasic de programare dinamica!
Se face o copie c a sirului h al ı̂năltimilor şi se sortează copia. Apoi, pentru fiecare poziţie i
din sirul h, se contorizează numărul de valori ı̂ntâlnite ı̂n parcurgerea sirului h de la inceput spre
sfarşit şi care sunt cele mai mici valori şi totodată consecutive ı̂n sirul c.
Deci pentru fiecare poziţie i, ı̂n sirul h, se determină numărul Asci, reprezentand numărul
de valori aflate ı̂n ordine crescătoare, din intervalul de poziţii 1..i, astfel ı̂ncât acestea să fie cele
mai mici valori din sir. Pornind de la poziţia pmax, mergând către sfârşitul şirului se determină
un singur număr: Desc, reprezentând numărul de valori din intervalul de poziţii pmax, n, aflate
ı̂n ordine descrescatoare astfel ı̂ncât acestea să fie cele mai mari valori din şir.
Fie M numărul de elemente care trebuie mutate. Dacă Ascpmax  Desc % Ascn, atunci
maximul nu se mută la capătul randului şi M Ascpmax  Desc.
Un caz special este acela ı̂n care lungimea Ascn a celui mai lung subşir crescător format din
cele mai mici numere din şir, este mai mare decât Ascpmax  Desc. În acest caz, rămân pe loc
toate valorile din acest subşir şi se mută toate celelalte, inclusiv maximul.
În concluzie numărul maxim de persoane care nu se mută este: max Ascn, Ascpmax 
Desc, iar
M n  max Ascn, Ascpmax  Desc
Să luăm un prim exemplu:

Cu roşu s-au reprezentat cele mai mici numere din şir aflate ı̂n ordine crescătoare ı̂n parcurgerea
şirului de la stânga la dreapta. Numărul acestora este Asci 3 (pentru i ' 5). Cu albastru sunt
figurate cele mai mari numere din şir aflate ı̂n ordine descrescătoare ı̂n parcurgerea şirului de la
stânga la dreapta. Numărul lor este Desc 2, iar capătul din stânga al acestui subşir ı̂ncepe la
poziţia pmax 7.
Numerele marcate cu negru trebuie să fie mutate. Ordinea mutării poate fi aleasă astfel ı̂ncât
să respecte cerinţa de risc minim.
pmax 7, Asc7  Desc 3  2 % Ascn 3.
Prin urmare, M n  Ascpmax  Desc 10  5 5
O altă situaţie este exemplificată mai jos:
CAPITOLUL 8. ONI 2015 8.2. RISC 197

Se observă că pmax 6, Asc6  Desc 3  3 % Ascn 5.


Aşadar şi ı̂n acest caz M n  Ascpmax  desc 11  6 5
Un ultim exemplu:

În acest caz, pmax 5, Asc5  Desc 2  2 $ Ascn 5, iar M n  Ascn.


Aşadar, pentru toate situaţiile: M n  max Ascpmax  desc, Ascn
Pentru determinarea şirurilor Asc şi a variabilei Desc, este necesară copierea şirului h şi
sortarea copiei, urmate de parcurgerea şirului h.
Determinarea valorii numărului M de persoane care trebuie mutate se face ı̂n timp constant
conform relaţiei de mai sus.
Complexitatea algoritmului depinde de sortare, deci O n ˜ logn.
O asemenea soluţie obţine 70 de puncte din 80 posibile.
Soluţia 3 - Complexitate O n
Soluţia este de fapt identică cu Soluţia 2, dar se coboară la O n complexitatea algoritmului
de sortare cu o sortare prin numărare.
O asemenea soluţie obţine 80 de puncte din 80 posibile.

8.2.2 Cod sursă

Listing 8.2.1: risc.cpp


/*
Constantin Galatan
Complexitate: O(n)
*/

#include <fstream>
#include <vector>
#include <algorithm>

using namespace std;


using VI = vector<int>;
CAPITOLUL 8. ONI 2015 8.2. RISC 198

VI a, b, c; // sirul sirul inaltimilor, sirul sortat, sir auxiliar


int n, V;

int main()
{
ifstream fin("risc.in");
ofstream fout("risc.out");

int hmax(0), pmax(-1);


fin >> V >> n;
a = VI(n);
for (int i = 0; i < n; ++i)
{
fin >> a[i];
if ( a[i] > hmax )
hmax = a[i], pmax = i;
}

if ( V == 1 )
{
bool ok = true;
for (int i = 0; i < n && ok; ++i)
{
if ( i && i <= pmax && a[i - 1] > a[i] )
ok = false;

if ( i > pmax && a[i - 1] < a[i] )


ok = false;
}

if ( ok && pmax && a[pmax - 1] > a[n - 1])


ok = false;
if ( ok )
fout << pmax + 1 << ’\n’;
else
fout << -1 << ’\n’;
}
else
{
b = VI(n); c = VI(hmax + 1);
for (int i = 0; i < n; i++)
c[a[i]]++; // c[i] - numarul elem egale cu i (frecventele)

for (int i = 1; i <= hmax; i++) // O(n)


c[i] += c[i-1]; // c[i] - numarul elem mai mici sau egale cu i

for (int i = 0; i < n; i++)


b[c[a[i]] - 1] = a[i];

int cntAsc(0), cntDesc(0), k(0);


for (int i(0), j(0); i < n; ++i) // partea crescatoare
{
if (a[i] == b[j] )
cntAsc++, j++;

if ( i == pmax )
k = cntAsc;
}

for (int i(pmax), j(n - 1); i < n; ++i) // partea descrescatoare


if (a[i] == b[j] )
cntDesc++, j--;

fout << n - max(cntAsc, cntDesc + k) << ’\n’;


}

fin.close();
fout.close();
return 0;
}

Listing 8.2.2: risc n2.cpp


// prof. Carmen Popescu - Colegiul National "Gh. Lazar" Sibiu
// O(nˆ2)
CAPITOLUL 8. ONI 2015 8.2. RISC 199

#include <iostream>
#include <fstream>
#include <algorithm>
#include <bitset>

using namespace std;

int a[100001];
int n,mx,p;

ifstream f("risc.in");
ofstream g("risc.out");

bitset<100001> b;
bitset<100001> c;

int main()
{
int v,i,j,k,mn;
f>>v>>n;
for (i=1;i<=n;i++)
{
f>>a[i];
if (a[i]>mx)
{
mx=a[i]; p=i;
}
}

if (v==1)
{
// ordinea din stanga
for (i=1;i<p;i++)
if (a[i]>a[i+1])
{
g<<-1<<"\n";
return 0;
}

// ordinea din dreapta


for (i=p+1;i<n;i++)
if (a[i]<a[i+1])
{
g<<-1<<"\n";
return 0;
}

// in stanga sunt numere mai mici decat minimul din dreapta


if (a[p-1]>a[n] || a[p+1]<a[p-1])
{
g<<-1<<"\n";
return 0;
}
g<<p<<"\n";
}
else
{
// CAZUL 1: mutam ce nu e ok din stanga si dreapta

// mn = minimul valorilor ce vor fi in dreapta dupa mutari


mn=a[p];

// marcam nr din stanga ce nu respecta ordinea si vor fi


// mutate la sfarsit
k=0;
mx=-1; // mx= maximul valorilor ce se muta la sfarsit
for (i=2;i<p;i++) // pt fiecare valoare din stanga
for (j=i-1;j>=1;j--) // mutam (marcam) numerele din fata sa
// care sunt mai mari, adica ele
// vor trebui mutate
if (a[j]>a[i] && !b[j])
{
b[j]=true;
k++;
if (a[j]>mx) mx=a[j];
if (a[j]<mn) mn=a[j];
CAPITOLUL 8. ONI 2015 8.2. RISC 200

// marcam valorile din dreapta care nu respecta ordinea si


// vor fi mutate la sfarsit
for (i=p+2;i<=n;i++)
{
if (a[i]<mn)
mn=a[i];
for (j=i-1;j>p;j--)
if (a[j]<a[i] && !b[j])
{
b[j]=true;
k++;
if (a[j]>mx) mx=a[j];
}
}

// marcam valorile care au ramas in stanga (nemutate) care sunt


// mai mari decat minimul din dreapta si care vor
// fi mutate la sfarsit
for (i=1;i<p;i++)
if (!b[i] && a[i]>mn)
{
b[i]=true;
k++;
if (a[i]>mx) mx=a[i];
}

// verificam valorile din dreapta care au ramas nemutate si care


// sunt mai mici decat maximul celor mutate, ele trebuie mutate
// la sfarsit
for (i=n;i>p;i--)
if (!b[i] && a[i]<mx)
k++;

// CAZUL 2:
// mutam maximul la sfarsit pe urma mutam ce nu respecta ordinea
// mn = minimul valorilor ce se vor afla in dreapta
mn=a[p];
int k1=1; // am mutat maximul la sfarsit
for (i=2;i<=n;i++)
if (i!=p)
for (j=i-1;j>=1;j--)
if (j!=p && a[j]>a[i] && !c[j])
{
c[j]=true;
k1++;
if (a[j]<mn) mn=a[j];
}
// cautam valorile ramase nemutate si care sunt mai mari decat
// minimul celor mutate si ele vor fi mutate
for (i=1;i<=n;i++)
if (i!=p && !c[i] && a[i]>mn)
k1++;

// solutia e cea din cazul cel mai favorabil


if (k<k1)
g<<k<<"\n";
else
g<<k1<<"\n";
}

return 0;
}

Listing 8.2.3: risc nlogn.cpp


/*
Constantin Galatan
Complexitate: O(n*log n)
*/
#include <fstream>
#include <vector>
#include <algorithm>
CAPITOLUL 8. ONI 2015 8.2. RISC 201

using namespace std;


using VI = vector<int>;

VI a, b; // sirul inaltimilor, sirul sortat


int n, V;

int main()
{
ifstream fin("risc.in");
ofstream fout("risc.out");

int hmax(0), pmax(-1);


fin >> V >> n;
a = VI(n);
for (int i = 0; i < n; ++i)
{
fin >> a[i];
if ( a[i] > hmax )
hmax = a[i], pmax = i;
}

if ( V == 1 )
{
bool ok = true;
for (int i = 0; i < n && ok; ++i)
{
if ( i && i <= pmax && a[i - 1] > a[i] )
ok = false;

if ( i > pmax && a[i - 1] < a[i] )


ok = false;
}

if ( ok && pmax && a[pmax - 1] > a[n - 1])


ok = false;
if ( ok )
fout << pmax + 1 << ’\n’;
else
fout << -1 << ’\n’;
}
else
{
b = a;
sort(b.begin(), b.end());
int cntAsc(0), cntDesc(0), k(0);

for (int i(0), j(0); i < n; ++i) // partea crescatoare


{
if (a[i] == b[j] )
cntAsc++, j++;

if ( i == pmax )
k = cntAsc;
}

for (int i(pmax), j(n - 1); i < n; ++i) // partea descrescatoare


if (a[i] == b[j] )
cntDesc++, j--;

fout << n - max(cntAsc, cntDesc + k) << ’\n’;


}

fin.close();
fout.close();
return 0;
}

8.2.3 *Rezolvare detaliată


CAPITOLUL 8. ONI 2015 8.3. ROBOTI 202

8.3 roboti
Problema 3 - roboti 100 de puncte
O firmă de construcţii imobiliare a achiziţionat recent un teren dreptunghiular de dimensiuni
N  M . Terenul este ı̂mpărţit ı̂n parcele de dimensiune 1  1. Pe unele dintre cele N  M
parcele sunt plantaţi copaci. Firma doreşte construirea unui grandios complex comercial şi este
necesară defrişarea ı̂ntregului teren. ı̂n acest scop sunt utilizaţi roboţi, fiecare robot având baza
un pătrat de latură L. Suprafaţa defrişată de fiecare robot la un moment dat este chiar aria de
acoperire a robotului, L  L. Fiecare robot pătrunde prin colţul stânga sus de coordonate 1, 1,
se poate deplasa doar ı̂n dreapta şi ı̂n jos şi poate părăsi suprafaţa numai prin colţul dreapta jos,
de coordonate N, M .

Cerinţe

Cunoscând dimensiunile N , M ale terenului şi coordonatele parcelelor ı̂n care sunt plantaţi
copaci se cere:
1. Numărul minim de roboţi necesari defrişării ı̂ntregului teren.
2. Să se răspundă la Q interogări de forma k, unde k este un număr natural. Pentru fiecare
interogare de această formă va trebui determinată latura minimă a unui robot astfel ı̂ncât să fie
necesari pentru defrişare cel mult k roboţi.

Date de intrare

Fişierul de intrare roboti.in conţine:


a Pe prima linie un număr natural p reprezentând varianta cerinţei de rezolvare. Pentru toate
testele de intrare, numărul p poate avea doar valoarea 1 sau valoarea 2.
a Pe a doua linie se află 3 numere naturale N , M , T separate prin câte un spaţiu
reprezentând numărul liniilor, numărul coloanelor terenului dreptunghiular, respectiv numărul
copacilor plantaţi.
a Următoarele T linii conţin fiecare câte două numere naturale x, y separate prin câte un
spaţiu, reprezentând linia, respectiv coloana parcelei ı̂n care este plantat un copac.
a În cazul cerinţei 1, ultima linie conţine un singur număr natural L, reprezentând latura unui
robot.
a În cazul cerinţei 2, penultima linie va conţine un număr natural Q, iar ultima linie Q numere
naturale k1 , k2 , ..., kQ separate prin câte un spaţiu, reprezentând numărul maxim de roboţi ce pot
fi utilizaţi ı̂n fiecare dintre cele Q interogări.

Date de ieşire

a Dacă valoarea lui p este 1, se va rezolva numai cerinţa 1. În acest caz, ı̂n fişierul de
ieşire roboti.out se va scrie un singur număr natural n1 , reprezentând numărul minim de roboţi
utilizaţi.
a Dacă valoarea lui p este 2, se va rezolva numai cerinţa 2. În acest caz, ı̂n fişierul de ieşire
roboti.out se vor scrie Q linii. Fiecare linie i va conţine câte un număr natural ni , reprezentând
latura minimă a unui robot astfel ı̂ncât pentru defrişare să fie utilizaţi cel mult ki roboţi.

Restricţii şi precizări

a 2 & N, M, L & 150


a 1 & Q & 150
a 1 & ki & 150, 1 & i & Q
a 1 & T & 1000
a Latura robotului nu poate fi mai mare decât dimensiunile terenului
a Pe tot parcursul deplasării, fiecare robot se va afla ı̂n interiorul suprafeţei terenului.
a ı̂n orice moment ı̂n interiorul suprafeţei terenului se va afla cel mult un robot.

Exemple:

roboti.in roboti.out Explicaţii


CAPITOLUL 8. ONI 2015 8.3. ROBOTI 203

1688415 1 p 1
3352655 Dacă roboţii au latura 4, pentru defrişarea ı̂ntregului teren este
4738684 necesar un singur robot.
Atenţie! Pentru acest test se rezolvă doar cerinţa 1.
2 688415 41 p 2
3 352655 Prima valoare din fişierul de ieşire reprezintă latura minimă pe
4 738682 care o pot avea roboţii astfel ı̂ncât pentru defrişarea ı̂ntregului
1 3 teren să fie necesar un singur robot, conform primei interogări.
A doua valoare din fişierul de ieşire reprezintă latura minimă pe
care o pot avea roboţii astfel ı̂ncât pentru defrişarea ı̂ntregului
teren să fie necesari cel mult trei roboţi, conform celei de-a doua
interogări.
Atenţie! Pentru acest test se rezolvă doar cerinţa 2.

Timp maxim de executare/test: 0.1 secunde


Memorie: total 8 MB
Dimensiune maximă a sursei: 10 KB

8.3.1 Indicaţii de rezolvare

Autor: prof. Sofia Viţelaru - C. N. ”Fratii Buzesti” Craiova

Cerinţa 1. 50 puncte prof. Constantin Gălăţan, C. N. ”Liviu Rebreanu” Bistrita


Soluţia 1 - Complexitate O(n* T*log T)
Se aplica o tehnică greedy. Se sortează coordonatele copacilor crescător după linie şi pentru
aceeaşi linie, crescător după coloană. Se plasează robotul curent cu colţul dreapta sus pe primul
copac (cel mai de sus şi din stânga). Din acest moment se simulează mişcarea.
Se parcurg punctele ı̂n ordinea sortata. Pentru fiecare copac ı̂ntâlnit pot exista situaţiile: este
acoperit de robot (copac tip A), se află ı̂n dreapta şi mai jos faţă de colţul dreapta sus al robotului
(copac de tip B), se află sub latura de jos a robotului (copac de tip C) sau se află la stânga
robotului (copac de tip D). Copacii de tip A şi de tip C se şterg din şirul de copaci, copacii de tip
D se pierd şi va trebui să fie tăiaţi de următorii roboţi, iar ı̂n cazul ı̂n care apare un copac de tip
D, robotul se mută cu colţul stânga sus pe acel robot şi ı̂l va tăia.
Când robotul curent atinge colţul stânga jos al terenului, urmaătorul robot va parcurge şirul
de copaci rămaşi ı̂n şir. Algoritmul se opreşte când toţi copacii din şir au fost tăiaţi (şterşi din şir)
Soluţia 2
Din desenul de mai jos se observă că numarul total de roboţi necesari tăierii tuturor copacilor
este egal cu lungimea maximă a unei secvenţe de copaci ı̂n cazul din exemplu patru) care sunt
plasaţi descrescator după linie şi la aceeasi linie crescător dupa coloană, astfel ı̂ncat distanţa pe
orizontală ı̂ntre doi roboţi alăturaţi ı̂n secvenţă să fie mai mare sau egală decat latura robotului.
Toţi ceilalti roboţi de pe teren pot fi tăiaţi de unul dintre aceşti roboţi.

Se parcurg copacii de sus ı̂n jos şi de la dreapta la stânga.


ı̂n funcţie de metoda aleasă pentru determinarea celei mai lungi secvenţe de copaci care re-
spectă condiţia menţionată mai sus se pot obţine diverse complexităţi (de exemplu, O n ˜ n cu
programare dinamică respectiv O T ˜ log2n cu AIB 2D).
Cerinţa 2. 50 de puncte
CAPITOLUL 8. ONI 2015 8.3. ROBOTI 204

Se caută binar latura minimă a roboţilor care trebuie folosiţi pentru a nu depăşi numărul admis
de roboţi utilizaţi. Numărul de roboţi folosiţi se face la fel ca la cerinţa 1. Pentru un algoritm de
tip greedy, complexitatea soluţiei la cerinţa 2 este O Q ˜ n ˜ T ˜ log T 

8.3.2 Cod sursă

Listing 8.3.1: roboti aib.cpp


/*
prof Constantin Galatan
Complexitate: O(Q * T * log(n)ˆ3)
*/
#include <fstream>
#include <algorithm>
#include <cassert>

using namespace std;

struct Cell
{
int i, j;
bool operator < (const Cell& c) const
{
if (i != c.i )
return i < c.i;
return j > c.j;
}
};

int n, m;
vector<Cell> cl;
int mx[1002][1002];

int Query(int i, int j);


void Update(int i, int j, int val);
void Reset(int i, int j);
int CountRobots(int L); // O(T * log(n)ˆ2)

int main()
{
ifstream fin("roboti.in");
ofstream fout("roboti.out");

int x, y, p, T, L;

fin >> p >> n >> m >> T;

for (int i = 0; i < T; ++i)


{
fin >> x >> y;
// assert(x <= n && y <= m);
cl.push_back({x, y});
}

sort(cl.begin(), cl.end());

if (p == 1) // O(T * log(n)ˆ2)
{
fin >> L;
fout << CountRobots(L) << ’\n’;
}
else // O(Q * T * log(n)ˆ3)
{
int Q, k, lo, hi, Lmin;

for (fin >> Q; Q--; fout << Lmin << ’\n’)


{
fin >> k;
lo = 1, hi = n / k + 1;
while (lo <= hi)
{
L = (lo + hi) / 2;
if (CountRobots(L) <= k)
CAPITOLUL 8. ONI 2015 8.3. ROBOTI 205

{
Lmin = L;
hi = L - 1;
}
else
lo = L + 1;
}
}
}

fin.close();
fout.close();
}

int CountRobots(int L) // O(T * log(n)ˆ2)


{
int cnt(0), cntUp(0);

for (const auto& c : cl)


{
cntUp = 1;
if (c.i > L && c.j <= m - L)
cntUp += Query(c.i - L, m - c.j - L + 1);

Update(c.i, m - c.j + 1, cntUp);


cnt = max(cnt, cntUp);
}

for (const auto& c : cl)


Reset(c.i, m - c.j + 1);

return cnt;
}

void Reset(int ic, int jc)


{
for (int i = ic; i <= n; i += i & -i)
for (int j = jc; j <= m; j += j & -j)
mx[i][j] = 0;
}

void Update(int ic, int jc, int val)


{
for (int i = ic; i <= n; i += i & -i)
for (int j = jc; j <= m; j += j & -j)
mx[i][j] = max(mx[i][j], val);
}

int Query(int ic, int jc)


{
int cnt(0);
for (int i = ic; i >= 1; i -= i & -i)
for (int j = jc; j >= 1; j -= j & -j)
cnt = max(cnt, mx[i][j]);
return cnt;
}

Listing 8.3.2: roboti dp.cpp


/*
prof. Constantin Galatan
O(Q * (nˆ2) * log n)
*/
#include <fstream>
#include <algorithm>
#include <vector>
#include <bitset>

using namespace std;


using VI = vector<int>;

const int MaxN = 155;


bitset<MaxN> b[MaxN];
vector<VI> a;
int n, m;
CAPITOLUL 8. ONI 2015 8.3. ROBOTI 206

int CountRobots(int L);

ifstream fin("roboti.in");
ofstream fout("roboti.out");

int main()
{
int x, y, p, T, L;

fin >> p >> n >> m >> T;

for (int i = 1; i <= T; ++i)


{
fin >> x >> y;
b[x][y] = 1;
}

if ( p == 1 )
{
fin >> L;
fout << CountRobots(L) << ’\n’;
}
else
{
int Q, k, lo, hi, Lmin;

for (fin >> Q; Q--; fout << Lmin << ’\n’)


{
fin >> k;
lo = 1, hi = n / k + 1;
while (lo <= hi)
{
L = (lo + hi) / 2;

if (CountRobots(L) <= k)
{
Lmin = L;
hi = L - 1;
}
else
lo = L + 1;
}
}
}

fin.close();
fout.close();
}

int CountRobots(int L)
{
int cnt = 0;
a = vector<VI>(n + 1, VI(m + 2));

for (int i = 1; i <= n; ++i)


for (int j = m; j >= 1; --j)
{
if ( b[i][j] )
{
if ( i > L && j <= m - L )
a[i][j] = 1 + a[i - L][j + L];
else
a[i][j] = 1;
}
else
a[i][j] = max(a[i][j + 1], a[i - 1][j]);

cnt = max(cnt, a[i][j]);


}

return cnt;
}

Listing 8.3.3: roboti greedy.cpp


CAPITOLUL 8. ONI 2015 8.3. ROBOTI 207

/*
prof. Constantin Galatan
O(Q * T * log n)
*/
#include <iostream>
#include <fstream>
#include <algorithm>
#include <deque>

using namespace std;


using PII = pair<int, int>;

#define I q1.front().first
#define J q1.front().second

deque<PII> q, q1, q2, q3;


int n, m;

int CountRobots(int L);

int main()
{
ifstream fin("roboti.in");
ofstream fout("roboti.out");

int x, y, p, T, L;

fin >> p >> n >> m >> T;


for (int i = 1; i <= T; ++i)
{
fin >> x >> y;
q.push_back({x, y});
}

sort(q.begin(), q.end());

if ( p == 1 )
{
fin >> L;
fout << CountRobots(L) << ’\n’;
}
else
{
int Q, k, lo, hi, Lmin;

for (fin >> Q; Q--; fout << Lmin << ’\n’)


{
fin >> k;
lo = 1, hi = n / k + 1;
while (lo <= hi)
{
L = (lo + hi) / 2;

if ( CountRobots(L) <= k)
{
Lmin = L;
hi = L - 1;
}
else
lo = L + 1;
}
}
}

fin.close();
fout.close();
}

int CountRobots(int L)
{
int lastJ, lastI, i1, i2, j1, cnt = 0;
bool first(true);
for (q1 = q; !q1.empty(); q1.swap(q2))
{
cnt++;
CAPITOLUL 8. ONI 2015 8.3. ROBOTI 208

lastJ = J, lastI = I, first = true;

for ( ; !q1.empty(); )
{
// cobor cat pot pe verticala
if (!q1.empty() && J <= lastJ && J > lastJ - L)
{
q1.pop_front();
continue;
}

// daca e un nou robot si vin din stanga, atunci el ia


// tot ce robotul pe orizontala
if (first && !q1.empty() && J <= lastJ && I <= lastI + L - 1 )
{
q1.pop_front();
continue;
}

// se pierde - va fi luat de urmatorul robot


if ( !q1.empty() && J <= lastJ - L )
{
q2.push_back(q1.front());
q1.pop_front();
continue;
}

if ( J > lastJ )
{
i1 = I, i2 = min(n, i1 + L - 1), j1 = max(J, L);

while ( !q1.empty() && I >= i1 && I <= i2 )


{

// dreptunghiul pana sub copacul pe care a fost


// plasat robotul anterior
if ( !q1.empty() && J <= j1 && J >= lastJ - L + 1 )
{
q1.pop_front();
continue;
}

// suntem inca in intervalul de culisare


// (penultimul robot) spre stanga
if (first && !q1.empty() && J <= lastJ && I <= lastI + L - 1 )
{
q1.pop_front();
continue;
}

// ramane neacoperit in stanga robotului


if ( !q1.empty() && J <= lastJ - L )
{
q2.push_back(q1.front());
q1.pop_front();
continue;
}

// ce-i in dreapta i jos fata de copacull de la


// coloana J pun in alt sir
if ( !q1.empty() && J > j1 )
{
q3.push_back(q1.front());
q1.pop_front();
continue;
}
}

for (; !q3.empty(); q3.pop_back())


q1.push_front(q3.back());

lastJ = j1;
lastI = i1;
first = false;
}
}
CAPITOLUL 8. ONI 2015 8.4. CASA 209

return cnt;
}

8.3.3 *Rezolvare detaliată

8.4 casa
Problema 4 - casa 100 de puncte
În această poveste este vorba despre o casă cu mai multe camere. O cameră are forma unui
pătrat de latură 1. Dacă două camere au un perete comun, atunci se poate trece dintr-o cameră
ı̂n alta. Casa nu are neapărat formă dreptunghiulară.
O asemenea casă poate fi descrisă ı̂n povestea noastră ı̂n două moduri:
- prin matricea minimală: o matrice cu elemente 0 şi 1 ı̂n care există N valori egale cu 1, ce
corespund camerelor, iar prima linie, ultima linie, prima coloană şi ultima coloană au cel puţin
un element egal cu 1.
- prin construcţie: un şir de N  1 perechi ai , bi  1 & i $ n ı̂n care ai " r1, 2, ..., ix şi
bi " rN, S, E, V x. Camerele vor fi numerotate de la 1 la n. Perechea ai , bi  precizează poziţia
camerei i  1 faţă de camera ai : E ı̂nseamnă la dreapta (est), N deasupra (nord), V la stânga
(vest), S dedesubt (sud). Observaţi că pentru prima cameră nu există nicio precizare!

De exemplu, casa de mai sus poate fi descrisă de şirul (1 E) (2 E) (2 S) (3 S), adică a doua
cameră e ”lipită” la est de prima cameră, următoarea (a treia) la est de camera 2, a patra la sud
de camera 2, iar ultima la sud de camera 3.

Cerinţe

1. Se dă descrierea unei case prin construcţie şi se cere descrierea acesteia prin matricea
minimală.
2. Se dă descrierea unei case prin matricea minimală şi se cere descrierea acesteia prin
construcţie.

Date de intrare

Fişierul casa.in conţine:


a Pe prima linie un număr natural p reprezentând cerinţa care trebuie rezolvată. Pentru toate
testele de intrare, numărul p poate avea valoarea 1 sau 2.
a Dacă valoarea lui p este 1 atunci liniile următoare conţin descrierea unei case prin construcţie
astfel: pe linia a doua un număr natural N reprezentând numărul de camere ale casei, iar pe fiecare
din următoarele N  1 linii câte două valori separate prin câte un spaţiu - un număr natural şi un
caracter, cu semnificaţia de mai sus.
a Dacă valoarea lui p este 2 atunci liniile următoare conţin descrierea unei case prin matrice
minimală astfel: pe linia a doua două numere naturale nenule M , N reprezentând dimensiunile
matricei, iar pe următoarele M linii câte N numere 0 sau 1 separate prin câte un spaţiu.

Date de ieşire

Dacă valoarea lui p este 1 atunci se va rezolva numai cerinţa 1. În acest caz fişierul
casa.out va conţine pe prima linie două numere naturale M şi N , separate prin câte un sin-
gur spaţiu reprezentând numărul de linii respectiv numărul de coloane ale matricei minimale, iar
pe următoarele M linii câte N valori 0 sau 1 separate prin câte un singur spaţiu.
Dacă valoarea lui p este 2 atunci se va rezolva numai cerinţa 2. În acest caz fişierul casa.out
va conţine pe prima linie două numere naturale N r şi C separate printr-un singur spaţiu. N r
reprezintă numărul de camere, iar C poziţia camerei 1 (cel mai mic număr de ordine al unei
CAPITOLUL 8. ONI 2015 8.4. CASA 210

coloane care conţine valoarea 1 ı̂n prima linie). Următoarele N r  1 linii vor conţine fiecare câte
două valori separate printr-un singur spaţiu, reprezentând descrierea unei case prin construcţie
conform precizărilor din enunţ. Coloanele vor fi numerotate ı̂ncepând de la 1, iar dacă există mai
multe soluţii va fi afişată cea mai mică ı̂n ordine lexicografică: perechea k, l va fi afişată ı̂naintea
perechii k, l dacă k $ k sau dacă k k şi l $ l (adică E ¡ N ¡ S ¡ V).

Restricţii şi precizări

a Matricea minimală a unei case are maximum 100 000 elemente.

Exemple:

casa.in casa.out Explicaţii


1 23
5 111
1E 011
2E
2S
3S
2 51
23 1E
111 1S
101 2E
4S
Timp maxim de executare/test: 0.1 secunde
Memorie: total 4 MB
Dimensiune maximă a sursei: 10 KB

8.4.1 Indicaţii de rezolvare

prof. Nistor Moţ - Colegiul Naţional ”Nicolae Bălcescu” Brăila

Cerinţa 1.
Se determină câţi paşi se fac spre nord, sud, est, vest, pornind din camera 1. Astfel se determină
dimensiunile matricei, apoi, având poziţionată corect camera 1, se completează matricea cu 1
urmând succesiv paşii daţi .
Cerinţa 2.
Se determină uşor poziţia primei camere, pe linia 1. Fiecare din camerele următoare se deter-
mină căutând pentru camera anterioară ı̂n cele 4 direcţii vecinii cu valoarea 1 care nu au fost deja
enumeraţi.
Deoarece nu se cunosc limitele maxime ale dimensiunilor m şi n ale matricei, dar se cunoaşte
produsul lor m*n ¡= 100 000 se foloseşte ı̂n locul unei matrice un vector de dimensiune 100 000
(liniarizare matrice).

8.4.2 Cod sursă

Listing 8.4.1: casa.cpp


// Casa O(m*n)
#include <fstream>

using namespace std;

#define M 100001
int p[M][4],t[M];

int main()
{
ifstream fi("casa.in");
ofstream fo("casa.out");
CAPITOLUL 8. ONI 2015 8.4. CASA 211

int a[M],m,n,i,j,k,v,nc,op;
char c[M],pct[7]="xENSV";

fi>>op;
if (op==1)
{
fi>>nc;
for (i=1;i<nc;i++)
fi>>a[i]>>c[i];

int max_e=0,max_v=0,max_n=0,max_s=0;
for (i=1;i<=nc;i++)
{
for(j=0;j<4;j++)
p[i+1][j]=p[a[i]][j];

switch(c[i])
{
case ’N’:
p[i+1][2]--;
p[i+1][1]++;
if(p[i+1][1]>max_n)
max_n=p[i+1][1];
break;
case ’S’:
p[i+1][1]--;
p[i+1][2]++;
if(p[i+1][2]>max_s)
max_s=p[i+1][2];
break;
case ’E’:
p[i+1][3]--;
p[i+1][0]++;
if(p[i+1][0]>max_e)
max_e=p[i+1][0];
break;
case ’V’:
p[i+1][0]--;
p[i+1][3]++;
if(p[i+1][3]>max_v)
max_v=p[i+1][3];
break;
}
}

m=max_n+max_s+1;
n=max_e+max_v+1;
fo<<m<<" "<<n<<"\n";
p[1][1]=max_n+1;
p[1][2]=max_v+1;

for (i=1;i<nc;i++)
{
switch(c[i])
{
case ’N’:
p[i+1][1]=p[a[i]][1]-1;
p[i+1][2]=p[a[i]][2];
break;
case ’S’:
p[i+1][1]=p[a[i]][1]+1;
p[i+1][2]=p[a[i]][2];
break;
case ’E’:
p[i+1][1]=p[a[i]][1];
p[i+1][2]=p[a[i]][2]+1;
break;
case ’V’:
p[i+1][1]=p[a[i]][1];
p[i+1][2]=p[a[i]][2]-1;
break;
}
}

for (i=1;i<=nc;i++)
CAPITOLUL 8. ONI 2015 8.4. CASA 212

t[(p[i][1]-1)*n+p[i][2]-1]=1;

for (i=1;i<=m;i++)
{
for (j=1;j<=n;j++)
fo<<t[(i-1)*n+j-1]<<" ";
fo<<"\n";
}
}

if (op==2)
{
fi>>m>>n;
nc=0;
k=0;

for (j=1;j<=n;j++)
{
fi>>t[j-1];
nc+=t[j-1];
if (k==0 && t[j-1]==1)
k=j;
}

for (i=2;i<=m;i++)
for (j=1;j<=n;j++)
{
fi>>t[(i-1)*n+j-1];
nc+=t[(i-1)*n+j-1];
}

fo<<nc<<" "<<k<<"\n";
p[1][0]=1;
p[1][1]=k;
t[k-1]=2;
k=1;
v=1;

while (v<nc)
{
i=p[k][0];
j=p[k][1];

if (j<n && t[(i-1)*n+j]==1)


{
v++;
t[(i-1)*n+j]=v+1;
p[v][0]=i;
p[v][1]=j+1;
p[v][2]=1;
}

if (i>1 && t[(i-2)*n+j-1]==1)


{
v++;
t[(i-2)*n+j-1]=v+1;
p[v][0]=i-1;
p[v][1]=j;
p[v][2]=2;
}

if (i<m && t[i*n+j-1]==1)


{
v++;
t[i*n+j-1]=v+1;
p[v][0]=i+1;
p[v][1]=j;
p[v][2]=3;
}

if (j>1 && t[(i-1)*n+j-2]==1)


{
v++;
t[(i-1)*n+j-2]=v+1;
p[v][0]=i;
p[v][1]=j-1;
CAPITOLUL 8. ONI 2015 8.4. CASA 213

p[v][2]=4;
}

k++;
}

for (k=2;k<=nc;k++)
{
i=p[k][0];
j=p[k][1];
switch(p[k][2])
{
case 1: j--; break;
case 2: i++; break;
case 3: i--; break;
case 4: j++; break;
}

fo<<t[(i-1)*n+j-1]-1<<" "<<pct[p[k][2]]<<"\n"; }
}

return 0;
}

Listing 8.4.2: casa slow.cpp


///prof. Gorea Claudiu-Cristian
///Colegiul National Al. Papiu Ilarian Tg-Mures
#include <fstream>

using namespace std;

ifstream fin ("casa.in");


ofstream fout("casa.out");

struct casa
{
int x,y;
} coada[100002];

int dx[4]={ 0,-1,+1, 0};


int dy[4]={+1, 0, 0,-1};

int caz,t,leg,lin,col,lmax,cmax,i,j,n,m,st,dr,k;
char c;

int main()
{
fin>>caz;

if (caz==1)
{
fin>>t;
coada[1].x=coada[1].y=1;
lmax=1;
cmax=1;

for(i=2;i<=t;i++)
{
fin>>leg>>c;
lin=coada[leg].x;
col=coada[leg].y;
if (c==’E’) col++;
if (c==’V’) col--;
if (c==’N’) lin--;
if (c==’S’) lin++;
coada[i].x=lin;
coada[i].y=col;
// a[lin][col]=1;
if (lin>lmax) lmax=lin;
if (col>cmax) cmax=col;
}
fout<<lmax<<" "<<cmax<<endl;
CAPITOLUL 8. ONI 2015 8.4. CASA 214

bool a[lmax+2][cmax+2];
///initializare
for(i=1;i<=lmax;i++)
for(j=1;j<=cmax;j++)
a[i][j]=0;
///copiere din coada
a[1][1]=1;
for(i=2;i<=t;i++) a[coada[i].x][coada[i].y]=1;

///afisare
for(i=1;i<=lmax;i++)
{
for(j=1;j<cmax;j++)
fout<<a[i][j]<<" ";
fout<<a[i][j]<<endl; ///ultimul de pe linie
}
}

if (caz==2)
{
fin>>n>>m;
bool a[n+2][m+2]; ///definire locala de matrice

///initializare bordura
for(i=0;i<=n+1;i++) a[i][0]=a[i][m+1]=0;
for(j=1;j<=m+1;j++) a[0][j]=a[n+1][j]=0;

///prelucrare
t=0;
lmax=0;
cmax=0;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
{
fin>>a[i][j];
t+=a[i][j];
if ((lmax+cmax==0) && (a[i][j]==1)) lmax=i,cmax=j;
}
fout<<t<<" "<<cmax<<"\n";

st=dr=1;
coada[1].x=lmax;
coada[1].y=cmax;
a[lmax][cmax]=0;
while(st<=dr)
{
// st - nr camerei vecina
lmax=coada[st].x;
cmax=coada[st].y;
for(k=0;k<4;k++)
if (a[lmax+dx[k]][cmax+dy[k]]==1)
{
fout<<st;
if (k==0) fout<<" E\n";
if (k==1) fout<<" N\n";
if (k==2) fout<<" S\n";
if (k==3) fout<<" V\n";
a[lmax+dx[k]][cmax+dy[k]]=0;
dr++;
coada[dr].x=lmax+dx[k];
coada[dr].y=cmax+dy[k];
}
st++;
}
}

return 0;
}

Listing 8.4.3: casa2.cpp


//Prof. Vitelaru Sofia
#include <fstream>
#include<algorithm>
CAPITOLUL 8. ONI 2015 8.4. CASA 215

using namespace std;

ifstream f("casa.in");
ofstream g("casa.out");

struct matrice
{
int l,c;
} q[100010];

int a[100010];
int dx[4]={0,1,-1,0}, dy[4]={1,0,0,-1},dx1[]={0,-1,1,0},dy1[]={-1,0,0,1};
char dir1[]="ESNV";
int var,i,j,n,lmax,cmax,cmin,v,m,nr,lmin;
char ch;int dir[256];

int cmp(matrice a, matrice b)


{
if(a.l==b.l)
return a.c<b.c;
return a.l<b.l;
}

int main()
{
f>>var;
dir[’E’]=0;
dir[’S’]=1;
dir[’N’]=2;
dir[’V’]=3;

if(var==1)
{
f>>n;
q[1].l=1;
q[1].c=1;
cmin=lmin=1000000000;

for(i=2;i<=n;i++)
{
f>>v>>ch;
q[i].l=q[v].l+dx[dir[ch]];
q[i].c=q[v].c+dy[dir[ch]];

if(q[i].l>lmax)
lmax=q[i].l;

if(q[i].l<lmin)
lmin=q[i].l;

if(q[i].c>cmax)
cmax=q[i].c;

if(q[i].c<cmin)
cmin=q[i].c;
}

if(cmin<=0)
{
cmax+=(-cmin)+1;
for(i=1;i<=n;i++)
q[i].c+=(-cmin)+1;
}

if(lmin<=0)
{
lmax+=(-lmin);
for(i=1;i<=n;i++)
q[i].l+=(-lmin)+1;
}

g<<lmax<<" "<<cmax<<’\n’;

sort(q+1,q+n+1,cmp);
CAPITOLUL 8. ONI 2015 8.5. LENES 216

int k=1;
for(i=1;i<=lmax;i++)
{
for(j=1;j<=cmax;j++)
if(i==q[k].l&&j==q[k].c)
{
g<<1<<" ";k++;
}
else
g<<0<<" ";

g<<’\n’;
}

return 0;
}

f>>n>>m;
int x,c1;
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
{
f>>x;
if(x==1)
{
nr++;
if(nr==1)
c1=j;

a[(i-1)*m+j]=-1;
}
}

g<<nr<<" "<<c1<<’\n’;

q[1].l=1;
q[1].c=c1;
a[c1]=1;
int p=1,u=1;
nr=1;

while(p<=u)
{
for(int k=0;k<4;k++)
{
int l=q[p].l+dx[k];
int c=q[p].c+dy[k];
int t=(l-1)*m+c;
if(l>=1&&l<=n&&c>=1&&c<=m&&a[t]==-1)
{
nr++;
g<<a[(q[p].l-1)*m+q[p].c]<<" "<<dir1[k]<<’\n’;
a[t]=nr;
u++;
q[u].l=l;
q[u].c=c;
}
}

p++;
}

return 0;
}

8.4.3 *Rezolvare detaliată

8.5 lenes
Problema 5 - lenes 100 de puncte
CAPITOLUL 8. ONI 2015 8.5. LENES 217

Leneşul este un animal foarte leneş. El se deplasează numai ı̂n linie dreaptă, dar face din când
ı̂n când câte un popas. ı̂n această problemă leneşul trebuie să traverseze de la nord la sud şi
ı̂napoi un teren reprezentat de o matrice de dimensiuni M  N cu valori numere naturale. Valorile
reprezintă efortul cerut pentru traversarea zonei respective. Leneşul va alege o coloană pentru
traversarea matricei, iar pentru popasuri, ı̂n număr de k1, va alege zone alăturate drumului din
coloana din stânga sau cea din dreapta. ı̂n cazul ı̂n care se va ı̂ntoarce va proceda la fel, dar va
face k2 popasuri. Regulile problemei cer ca cele două drumuri să nu aibă zone comune.

Cerinţe

Cunoscând dimensiunile M , N ale terenului, numărul de popasuri k1, k2 şi efortul pentru
traversarea fiecărei zone a terenului, să se determine:
1. Efortul minim de parcurgere a terenului de la Nord la Sud, folosind k1 popasuri.
2. Efortul minim de parcurgere a terenului de la Nord la Sud şi ı̂napoi de la Sud la Nord,
folosind k1 popasuri la deplasarea Nord - Sud, respectiv k2 popasuri la deplasarea Sud - Nord.

Date de intrare

Fişierul lenes.in conţine:


a Pe prima linie un număr natural p reprezentând cerinţa de rezolvare. Pentru toate testele
de intrare numărul p poate avea doar valoarea 1 sau 2.
a Pe linia a doua sunt 4 numere naturale M , N , k1, k2, separate prin câte un spaţiu cu
semnificaţia de mai sus.
a Pe următoarele M linii se găsesc câte N numere naturale separate prin câte un spaţiu,
reprezentând eforturile de traversare a fiecărei zone a terenului.

Date de ieşire

a Dacă valoarea lui p este 1, se va rezolva numai cerinţa 1. ı̂n acest caz fişierul lenes.out va
conţine un singur număr natural reprezentând efortul minim necesar pentru traversarea terenului
ı̂n condiţiile date de la Nord la Sud.
a Dacă valoarea lui p este 2, se va rezolva numai cerinţa 2. ı̂n acest caz fişierul lenes.out va
conţine un singur număr natural reprezentând efortul minim necesar pentru traversarea terenului
ı̂n condiţiile date ı̂n ambele sensuri de la Nord la Sud şi de la Sud la Nord.

Restricţii şi precizări

a 3 & M, N & 500


a 0 & k1, k2 & M
a Valorile din matrice sunt numere naturale din intervalul 1, 1000.
a Leneşul poate să facă popasuri pe aceeaşi linie ı̂n ambele celule din stânga şi din dreapta
coloanei parcurse.
a Deplasarea ı̂ntre ultima zonă a drumului parcurs de la Nord la Sud şi prima zonă a drumului
parcurs de la Sud la Nord la ı̂ntoarcere se face cu efort 0.

Exemple:

lenes.in lenes.out Explicaţii


1 12 p 1
4723 Leneşul traversează terenul de la Nord la Sud pe coloana a 5-a
99 1 33 9 2 4 7 cu popas ı̂n zonele (2, 6) şi (4, 6).
99 1 44 8 1 2 3 Atenţie! Pentru acest test se rezolvă doar cerinţa 1.
98 1 55 8 2 3 2
97 1 66 4 3 2 1
2 35 p 2
4732 Leneşul traversează terenul de la Nord la Sud pe coloana a 7-a
99 1 33 9 2 4 7 cu popasuri ı̂n zonele (3, 6), (1, 6), (4, 6), iar de la Sud la Nord
99 1 44 8 1 2 3 pe coloana a 5 - a, cu popas ı̂n zonele (4, 4) şi (2, 6).
98 1 55 8 2 2 2 Atenţie! Pentru acest test se rezolvă doar cerinţa 2.
97 1 66 4 3 2 1
CAPITOLUL 8. ONI 2015 8.5. LENES 218

2 19 p 2
3722 Leneşul traversează terenul de la Nord la Sud pe coloana a 6-a
2 1 33 9 99 4 7 cu popasuri ı̂n zonele (2, 7), (3, 7), iar de la Sud la Nord pe
1 1 44 9 99 2 3 coloana a 2 - a, cu popasuri ı̂n zonele (3, 1) şi (2, 1). Efortul
2 1 55 9 99 2 2 de deplasare ı̂ntre zonele (3, 6) şi (3, 2) este nul.
Atenţie! Pentru acest test se rezolvă doar cerinţa 2.

Timp maxim de executare/test: 0.1 secunde


Memorie: total 4 MB
Dimensiune maximă a sursei: 10 KB

8.5.1 Indicaţii de rezolvare

prof. Nistor Moţ - Colegiul Naţional ”Nicolae Bălcescu” Brăila

După citirea datelor se vor sorta elementele de pe fiecare coloană


crescător, şi se vor calcula sumele de pe fiecare coloană.
Cerinţa 1
Pentru fiecare i, se determinăsuma celor mai mici k1 numere din
coloanele i  1 şi i  1, la care adunăm şi suma elementelor din coloana
i. Pentru prima şi ultima coloană, cele k1 popasuri se fac doar pe
coloanele 2 şi n  1.
Cerinţa 2
Se disting trei cazuri distincte: Figura 8.1: lenes
a) drumurile sunt adiacente: coloanele i si i  1, atunci se iau in considerare pentru popas cele
mai mici k1 valori din coloana i  1şi cele mai mici k2 valori din coloana i  2 sau invers.
b) drumurile au cel puţin două coloane ı̂ntre ele, atunci sumele se calculează independent, ca
la cerinţa 1.
c) ı̂ntre drumuri există o singură coloană: drumurile sunt pe coloanele i şi i  2, atunci vom
alege cele mai mici k1 valori din coloana i  1 şi respectiv cele mai mici k2 valori din coloana i  3,
incercând apoi să ı̂nlocuim cele mai mari dintre aceste volori cu cele mai mici valori din coloana
i  1.

8.5.2 Cod sursă

Listing 8.5.1: lenes.cpp


/**
* Problema: Lenes O(nˆ2)
* Stud. Popescu Silviu-Emil
* Automatica si Calculatoare
* Facultatea Politehnica Bucurest
*/

#include <stdio.h>
#include <string.h>
#include <algorithm>

#define NMax 1010


#define VMax 1010
#define oo 0x3f3f3f3f

using namespace std;

const char IN[] = "lenes.in", OUT[] = "lenes.out";

int Case, N, M, k1, k2;


int Mat[NMax][NMax];
int Sum[NMax], V[NMax], left[NMax], right[NMax];

int get_line_sum( int lin, int k, bool up = true, bool down = true )
CAPITOLUL 8. ONI 2015 8.5. LENES 219

{
int res = Sum[lin];

static int v[VMax];


memset(v, 0, sizeof(v));

for ( int i = 1; i <= M; ++ i )


{
if ( up )
++ v[ Mat[lin - 1][i] ];
if ( down )
++ v[ Mat[lin + 1][i] ];
}

for ( int i = 0; i < VMax && k ; ++ i )


if ( k >= v[i] )
{
res += i * v[i];
k -= v[i];
}
else if ( v[i] )
{
res += k * i;
k = 0;
}

return res;
}

int case1()
{

int ret = get_line_sum(1, k1);

for ( int i = 1; i <= N; ++ i )


ret = min( ret, get_line_sum(i, k1));

return ret;
}

int case2()
{

int ret = oo;

for ( int i = 1; i <= N; ++ i )


V[i] = get_line_sum(i, k2);

left[1] = V[1];
for ( int i = 2; i <= N; ++ i )
left[i] = min(V[i], left[i - 1]);

right[N] = V[N];
for ( int i = N - 1; i > 0; -- i )
right[i] = min(V[i], right[i + 1]);

// computing distant lines


for ( int i = 1; i <= N; ++ i )
{
int r = get_line_sum(i, k1);
int other = oo;

if ( i > 3 ) other = min(other, left[i - 3]);


if ( i < N - 2) other = min(other, right[i + 3]);

ret = min(ret, r + other);


}

// computing adiacent lines


for ( int i = 1; i < N; ++ i )
{

int r1 = get_line_sum(i, k1 , true, false);


int r2 = get_line_sum(i + 1, k2, false, true);
CAPITOLUL 8. ONI 2015 8.5. LENES 220

ret = min( ret, r1 + r2 );

r1 = get_line_sum(i, k2 , true, false);


r2 = get_line_sum(i + 1, k1, false, true);

ret = min( ret, r1 + r2 );


}

//fprintf(stderr, "%d\n", ret);


// computing common margin lines

bool parity = true;

for ( int i = 1; i < N - 1; ++ i )


{

int sum = Sum[i] + Sum[i + 2];


int up = i - 1;
int mid = i + 1;
int down = i + 3;
int index_up = 1;
int index_down = 1;

for ( int j = 1; j <= M && j <= k1 + k2; ++ j )


sum += Mat[mid][j];

// adding extra elements


for ( int j = M + 1; j <= k1 + k2; ++ j )
{
if ( index_up <= k1 &&
Mat[up][index_up] <= Mat[down][index_down] ||
index_down > k2 )
{
sum += Mat[up][index_up];
++ index_up;
}
else
{
sum += Mat[down][index_down];
++ index_down;
}
}

ret = min(ret, sum);


// fprintf(stderr, "begining with: (%d, %d, %d)\n",
// index_up - 1, min(M, k1 + k2), index_down - 1);
for ( int j = min(M, k1 + k2); j > 0; -- j )
{
sum -= Mat[mid][j];
if ( index_up <= k1 &&
Mat[up][index_up] <= Mat[down][index_down] ||
index_down > k2 )
{
sum += Mat[up][index_up];
++ index_up;
}
else
{
sum += Mat[down][index_down];
++ index_down;
}

ret = min( ret, sum );


if ( ret == sum )
{
// fprintf(stderr, "(up: %d, mid: %d, down: %d -> %d\n",
// index_up - 1, j - 1, index_down - 1, ret);
}
}

if ( parity )
{
parity = false;
-- i;
}
else
CAPITOLUL 8. ONI 2015 8.5. LENES 221

{
parity = true;
}

swap( k1, k2 );
}

return ret;
}

int ( *Work[] )() = { case1, case2 };

int main()
{
freopen(IN, "r", stdin);
freopen(OUT, "w", stdout);

scanf("%d%d%d%d%d", &Case, &N, &M, &k1, &k2);

for ( int i = 1; i <= N; ++ i)


for ( int j = 1; j <= M; ++ j )
scanf("%d", &Mat[j][i]);

swap(N, M);

for ( int i = 1; i <= N; ++ i )


for ( int j = 1; j <= M; ++ j )
Sum[i] += Mat[i][j];

for ( int i = 1; i <= M; ++ i )


Mat[0][i] = Mat[N + 1][i] = VMax - 1;

for ( int i = 1; i <= N; ++ i )


sort( Mat[i] + 1, Mat[i] + M + 1);

printf("%d\n", Work[Case - 1]());

return 0;
}

Listing 8.5.2: lenes2.cpp


// prof. Carmen Popescu - Colegiul National "Gh. Lazar" Sibiu
#include <fstream>
#include <climits>

using namespace std;

int a[502][502],sum[502],m,n;
int mn=INT_MAX;

ifstream f("lenes.in");
ofstream g("lenes.out");

int calc(int c,int k)


{
int s=0,i1=1,i2=1,i;
for (i=1;i<=k;i++)
if (a[i1][c-1]<a[i2][c+1])
{
s+=a[i1][c-1];
i1++;
}
else
{
s+=a[i2][c+1];
i2++;
}
s+=sum[c];
return s;
}

int main()
{
int i,j,i1,i2,i3,ok,j1,k1,k2,k3,k4,p;
CAPITOLUL 8. ONI 2015 8.5. LENES 222

int s1,s2,s;
f>>p;
f>>m>>n>>k1>>k2;

for (i=1;i<=m;i++)
{
for (j=1;j<=n;j++)
{
f>>a[i][j];
sum[j] += a[i][j];
}
a[i][0]=INT_MAX;
a[i][n+1]=INT_MAX;
}

for (j=1;j<=n;j++)
{
a[0][j]=INT_MAX;
a[m+1][j]=INT_MAX;
// sortam coloana j
for (i=1;i<m;i++)
for (i1=i+1;i1<=m;i1++)
if (a[i][j]>a[i1][j])
{
s=a[i][j];
a[i][j]=a[i1][j];
a[i1][j]=s;
}

if (p==1)
{
for (j=1;j<n;j++)
{
s=calc(j,k1);
if (s<mn) mn=s;
}
g<<mn<<"\n";
}
else
{
int cc;
// cazul 1: doua coloane alaturate (j si j+1)
for (j=2;j<n-1;j++)
{
for (int g=1;g<=2;g++)
{
s=0;
for (i=1;i<=k1;i++)
s=s+a[i][j-1];
for (i=1;i<=k2;i++)
s+=a[i][j+2];

s=s+sum[j]+sum[j+1];
if (s<mn) mn=s;
s1=k1; k1=k2; k2=s1;
}
}

// cazul 2: coloanele j si j+2 (o coloana goala intre ele)


for (j=2;j<n-2;j++)
for (int g=1; g<=2; g++)
{
s1=0;
for (i=1; i<=k1; i++)
s1+=a[i][j-1];
for (i=1; i<=k2; i++)
s1+=a[i][j+3];

// incercam sa inlocuim o parte din popasuri cu


// popasuri pe coloana din mijloc, j+1
i1=k1; i2=1; i3=k2;
s=s1;
do
{
CAPITOLUL 8. ONI 2015 8.5. LENES 223

ok=0;
if (i1>0 &&
a[i1][j-1]>=a[i3][j+3] &&
a[i1][j-1]>a[i2][j+1]) // inlocuim un popas de pe
// coloana j-1 cu popas pe
{
// coloana j+1
s=s+a[i2][j+1]-a[i1][j-1];
i1--;
i2++;
ok=1;
}
else
if (i3>0 &&
a[i3][j+3]>=a[i1][j-1] &&
a[i3][j+3]>a[i2][j+1]) // inlocuim un popas de pe
// coloana j-1 cu popas pe
{
// coloana j+1
s=s+a[i2][j+1]-a[i3][j+3];
i3--;
i2++;
ok=1;
}
} while (ok==1);

s+=sum[j]+sum[j+2];
if (s<mn)
mn=s;

s=k1; k1=k2; k2=s;


}

// caz particular la cazul 2: coloana 1 si coloana 3


for (int g=1;g<=2;g++)
{
s1=0;
for (i=1;i<=k1;i++)
s1+=a[i][2];

i1=k1+1;
i2=1;
for (i=1;i<=k2;i++)
if (i1<=m && a[i1][2]<a[i2][4])
{
s1+=a[i1][2]; i1++;
}
else
{
s1+=a[i2][4]; i2++;
}

s1+=sum[1]+sum[3];
if (s1<mn)
mn=s1;

s1=k1;
k1=k2;
k2=s1;
}

// caz particular la cazul 2: coloana n-2 si coloana n


for (int g=1;g<=2;g++)
{
s1=0;
for (i=1;i<=k1;i++)
s1+=a[i][n-1];

i1=k1+1;
i2=1;
for (i=1;i<=k2;i++)
if (i1<=m && a[i1][n-1]<a[i2][n-3])
{
s1+=a[i1][n-1]; i1++;
}
else
CAPITOLUL 8. ONI 2015 8.5. LENES 224

{
s1+=a[i2][n-3]; i2++;
}

s1+=sum[n]+sum[n-2];
if (s1<mn)
mn=s1;

s1=k1;
k1=k2;
k2=s1;
}

// cazul 3: doua linii total independente


for (j=1;j<=n-3;j++)
{
s1=calc(j,k1); // dus pe j intrs pe j1
s2=calc(j,k2); // dus pe j1 intors pe j
for (j1=j+3;j1<=n;j1++)
{
i1=s1+calc(j1,k2);
i2=s2+calc(j1,k1);
if (i1<mn)
mn=i1;
if (i2<mn)
mn=i2;
}
}

g<<mn<<"\n";
}
}

Listing 8.5.3: lenes3.cpp


// prof. Carmen Popescu - Colegiul National "Gh. Lazar" Sibiu
#include <fstream>
#include <climits>
#include <algorithm>

using namespace std;

int sum[502][502],a[502][502],m,n;
int mn=INT_MAX;

ifstream f("lenes.in");
ofstream g("lenes.out");

int calc(int i,int k1)


{
int mn=INT_MAX,s=0,j,s1;

if (i>1 && i<n)


{
s=sum[i][m];
for (j=0; j<=k1; j++)
{
s1 = sum[i-1][k1-j];
s1 += sum[i+1][j];
s1 += s;
if (s1<mn) mn=s1;
}
}
else
if (i==1)
mn=sum[1][m]+sum[2][k1];
else
mn=sum[n][m]+sum[n-1][k1];
return mn;
}

int main()
{
int p,k1,k2,i,j,s,s1,s2,k,i1,i2,i3,gg;
int mn=INT_MAX;
CAPITOLUL 8. ONI 2015 8.5. LENES 225

f>>p;
f>>m>>n>>k1>>k2;

for (i=1;i<=m;i++)
for (j=1;j<=n;j++)
f>>a[j][i];

for (j=1;j<=n;j++)
{
sort(a[j]+1,a[j]+m+1);
sum[j][1]=a[j][1];
for (i=2;i<=m;i++)
sum[j][i]=a[j][i]+sum[j][i-1];
}

if (p==1)
{
for (i=1;i<=n;i++)
{
s=calc(i,k1);
if (s<mn) mn = s;
}
g<<mn<<’\n’;
}
else
{
// cazul 1: doua coloane consecutive
for (i=2;i<n-1;i++)
{
s = sum[i-1][k1] + sum[i][m] + sum[i+1][m] + sum[i+2][k2];
if (s<mn) mn=s;

s = sum[i-1][k2] + sum[i][m] + sum[i+1][m] + sum[i+2][k1];


if (s<mn) mn=s;
}

// cazul 2: 2 coloane cu una intre ele


for (int tt=1;tt<=2;tt++)
{
for (i=2;i<=n-3;i++)
{

i1=k1; i2=0; i3=k2;


s = sum[i][m] + sum[i+2][m];
do
{
gg=0;
if (i1>=1 && i2<=m &&
a[i+1][i2+1]<a[i-1][i1] && a[i+3][i3]<=a[i-1][i1])
{
i1--; i2++; gg=1;
}
else
if (i3>=1 && i2<=m &&
a[i+1][i2+1]<a[i+3][i3] && a[i-1][i1]<=a[i+3][i3])
{
i3--; i2++; gg=1;
}
} while (gg==1);
s1 = s + sum[i-1][i1] + sum[i+1][i2] + sum[i+3][i3];
if (s1<mn) mn=s1;
}

// coloanele 1 si 3
s = sum[1][m] + sum[3][m];

if (n>3)
{
for (j=0;j<=k2;j++)
if (k1+j<=m && k2>j)
{
s1 = sum[2][k1+j];
s1 += sum[4][k2-j];
s1 += s;
if (s1<mn) mn=s1;
}
CAPITOLUL 8. ONI 2015 8.6. SIPET 226

}
else
{
s=s+sum[2][k1+k2];
if (s<mn) mn=s;
}

// coloanele n-2 si n
if (n>3)
{
s = sum[n-2][m] + sum[n][m];

for (j=0;j<=k1;j++)
if (k2+j<=m && k1>j)
{
s1 = sum[n-3][k1-j];
s1 += sum[n-1][k2+j];
s1 += s;
if (s1<mn) mn=s1;
}
}
i1=k1; k1=k2; k2=i1;
}

// cazul 3: doua coloane oarecare


for (i=1; i<=n-3; i++)
{
s1 = calc(i,k1);
s2 = calc(i,k2);
for (i1=i+3;i1<=n;i1++)
{
s = s1 + calc(i1,k2);
if (s<mn) mn=s;

s = s2 + calc(i1,k1);
if (s<mn) mn=s;
}
}
g<<mn<<’\n’;
}
}

8.5.3 *Rezolvare detaliată

8.6 sipet
Problema 6 - sipet 100 de puncte
Un arheolog a găsit un sipet interesant. După ce l-a deschis cu grijă, a
constatat cu surprindere că sipetul conţine bănuţi de aur. Uitându-se mai
atent a mai găsit ceva: un pergament ascuns ı̂ntr-un compartiment secret al
sipetului, cu un text scris ı̂ntr-o limbă antică, pe care, din fericire, arheologul
o cunoştea. Din text a reieşit că un grup de negustori foarte bogaţi a vrut
să ascundă ı̂n mare secret averea breslei lor, formată din monede de aur,
deoarece se prevestea un război cumplit. Negustorii ştiau că există şanse ca această comoară să
fie găsită şi confiscată de duşmani, deci s-au sfătuit cum e mai bine să procedeze, cum să ascundă
comoara. Arheologul a reuşit să deducă din text următoarele:
a) Cele N monede, care formau averea breslei, au fost ı̂mpărţite ı̂n maximum trei feluri de
grămezi, formate din p1, p2 şi p3 bănuţi, p1, p2 şi p3 fiind numere prime consecutive, p1 $ p2 $ p3.
Fiecare grămadă a fost pusă ı̂n ı̂ntregime ı̂ntr-un sipet.
b) Este posibil să existe 0 (zero) grămezi formate din p1 sau p2 sau p3 monede, scopul fiind
să se obţină o ı̂mpărţire ı̂n care numărul monedelor rămase nedistribuite să fie minim, iar dacă
există mai multe posibilităţi, se alege aceea pentru care numărul de grămezi este mai mare. Dacă
există mai multe astfel de soluţii, se consideră corectă oricare dintre ele.
c) Monedele care nu au putut fi distribuite conform regulilor stabilite, au fost donate bisericii.
Cerinţe
CAPITOLUL 8. ONI 2015 8.6. SIPET 227

Scrieţi un program care determină numărul maxim S de sipete şi numărul sipetelor cu p1, p2
respectiv p3 monede, precum şi suma donată bisericii.

Date de intrare

Fişierul sipet.in conţine, pe prima linie numărul natural T , iar pe următoarele T linii câte
două numerele naturale N şi p1, despărţite printr-un singur spaţiu.

Date de ieşire

Fişierul sipet.out va conţine pe primele T linii câte 5 numere naturale, separate prin câte
un spaţiu: S, x, y, z şi r, reprezentând numărul maxim S de sipete, numărul x de sipete cu
p1 monede, numărul y de sipete cu p2 monede, respectiv numărul z de sipete cu p3 monede şi
numărul r de monede donate bisericii, corespunzătoare datelor de intrare de pe linia T  1 a
fişierului sipet.in. Dacă există mai multe soluţii corecte, este acceptată oricare dintre ele.

Restricţii şi precizări

a 1 & N & 10 000 000


a 2 & p1 $ p2 $ p3 & N
a 1 & T & 10 - ı̂n fişierul de intrare nu vor fi mai mult de 10 perechi de numere N p1

Exemple:

sipet.in sipet.out Explicaţii


3 33000 - numărul maxim de sipete este 3, toate cu câte 3 monede;
15 5 21010 - sau: 2 0 2 0 0 (1*3+1*7=2*5=10); (ambele soluţii sunt
10 3 31110 corecte!)
41 11 - numărul maxim de sipete este 3; 1 sipet cu 11, unul cu 13 şi
unul cu 17 monede.
Timp maxim de executare/test: 1.5 secunde
Memorie: total 128 MB
Dimensiune maximă a sursei: 10 KB

8.6.1 Indicaţii de rezolvare

prof. Budai István - Lic. Teor. ”Nagy Mózes” Târgu Secuiesc


3
Soluţia O n  - 60 puncte - prof. Claudiu Cristian Gorea - C. N. ”Al. Papiu Ilarian”
Târgu-Mureş
Pentru fiecare pereche N p1, aplicăm următorul algoritm:
Asociem problemei următoarea ecuaţie:
x p1  y p2  z p3  r N
Se caută prima soluţie cu restul r cel mai mic din intervalul [0..p1-1].
ı̂n cadrul acestei soluţii x trebuie să fie cât mai mare.
Valoarea N  r  x ˜ p1 va fi distribuită ı̂n y grămezi cu p2 monede.
Valoarea N  r  x ˜ p1  y ˜ p2 va fi distribuită ı̂n z grămezi cu p3 monede.
Parcurgând descrescător valorile posibile pentru x şi y, vom obţine o sumă cu valoare maximă.
Ne oprim la soluţia ı̂n care este ı̂ndeplinită condiţia: N  r  x ˜ p1  y ˜ p2  z ˜ p3 0.
ı̂n funcţie de algoritmul de testare/generare a valorilor prime p2 şi p3, dar şi a utilizării eficiente
a structurilor repetitive, se pot obţine maxim 60 puncte.
Solutie O N  - 100p stud. Popescu Silviu-Emil, Univ. Politehnică Bucureşti
Se observă ca problema se reduce la rezolvarea egalităţii:
a ˜ p1  b ˜ p2  c ˜ p3 N  p, cu a, b, c, p naturale.
Vom folosi o strategie de tip Meet In The Middle astfel:
Rescriem ecuaţia ı̂n forma: a ˜ p1  b ˜ p2 N  c ˜ p3  p
Aplicăm următoarea strategie:
1) calculăm toate valorile posibile ale expresiei: a ˜ p1  b ˜ p2
2) calculăm toate valorile posibile ale expresiei: N  c ˜ p3  p
CAPITOLUL 8. ONI 2015 8.6. SIPET 228

3) verificăm dacă ı̂n cele 2 seturi de valori posibile exista valori comune. Din setul de valori
comune alegem soluţia cu p minim şi S maxim
Explicarea complexităţii:
Amintim ca a aparţine intervalului 0.. N ©p1 şi b, c şi p aparţin intervalul 0..p1.
De asemenea p1 ˜ p1 & N .
Numărul de valori posibile din prima expresie va fi O N ©p1 ˜ p1 O N 
Numărul de valori posibile din a doua expresie va fi O p1 ˜ p1 O N 
Specificăm că verificarea egalităţii se va face folosind un vector de apariţii ı̂n O 1

8.6.2 Cod sursă

Listing 8.6.1: sipet.cpp


/**
* Problema: Sipet O( N )
* Stud. Popescu Silviu Emil
* Automatica si Calculatoare
* Universitatea Politehnica Bucuresti
*/

#include <stdio.h>
#include <assert.h>
#define NMax 10000010

const char IN[] = "sipet.in", OUT[] = "sipet.out";

struct pereche
{
int a, b;
};

int Tes, N, L, p[4];


int A, B, C, R;
pereche v[NMax];
bool bb[NMax];

bool isPrime( int x )


{

for ( int d = 2; d * d <= x; ++ d )


if ( x % d == 0 )
return false;
return true;

int sqr( int x )


{
return x * x;
}

int main()
{

freopen(IN, "r", stdin);


freopen(OUT, "w", stdout);

scanf("%d", &Tes);

while ( Tes -- )
{
scanf("%d%d", &N, &p[1]);

assert(isPrime(p[1]));
for ( int i = 2; i <= 3; ++ i )
for ( p[i] = p[i - 1] + 1; !isPrime(p[i]); ++ p[i] );

// fprintf(stderr, "%d %d %d\n", p[1], p[2], p[3]);


for ( int a = 0; a * p[1] <= N; ++ a )
for ( int b = 0; a * p[1] + b * p[2] <= N && b < p[1]; ++ b )
{
CAPITOLUL 8. ONI 2015 8.6. SIPET 229

int pos = a * p[1] + b * p[2];


v[pos].a = a;
v[pos].b = b;
bb[pos] = true;
}

bool found = false; R = -1;


for ( int r = 0; r <= p[1] && R == -1; ++ r )
for ( int c = 0; r + c * p[3] <= N && c <= p[1]; ++ c )
{
int pos = c * p[3] + r;
if ( bb[N - pos] &&
(R == -1 || R != -1 &&
A + B + C < v[N - pos].a + v[N - pos].b + c ))
{
found = true;
A = v[N - pos].a;
B = v[N - pos].b;
C = c;
R = r;
}
}

for ( int a = 0; a * p[1] <= N; ++ a )


for ( int b = 0; a * p[1] + b * p[2] <= N && b < p[1]; ++ b )
{
int pos = a * p[1] + b * p[2];
bb[pos] = false;
}

printf("%d %d %d %d %d\n", A + B + C, A, B, C, R);

return 0;
}

8.6.3 *Rezolvare detaliată


Capitolul 9

ONI 2014

9.1 harta
Problema 1 - harta 100 de puncte
Pe baza unei imagini preluate din satelit, se realizează harta unei mici localităţi. Localitatea
ocupă o suprafaţă dreptunghiulară, cu laturile orientate pe direcţiile Nord-Sud, respectiv Est-Vest.
Studiind imaginea obţinută de la satelit, cartografii au constatat că toate cele k clădiri au
forma unor dreptunghiuri distincte. Imaginea poate fi reprezentată sub forma unui tablou cu
n  m celule aşezate pe n linii numerotate de la 1 la n şi m coloane numerotate de la 1 la m.
Numim drum, un dreptunghi al tabloului care străbate ı̂ntreaga localitate pe direcţia Est-Vest
şi are un număr maxim de linii sau un dreptunghi care străbate ı̂ntreaga localitate pe direcţia
Nord-Sud şi are un număr maxim de coloane. Drumurile, evident, nu trebuie să treacă prin
clădiri.
Cartografii sunt interesaţi ca pe această hartă să fie reprezentate la scară doar clădirile, nu
şi drumurile. De aceea, pentru realizarea hărţii, lăţimile drumurilor au fost reduse la o singură
celulă.
Tabloul care reprezintă imaginea localităţii se codifică astfel: 1 pentru o celulă ocupată de o
clădire şi 0 pentru o celulă neocupată.

Cerinţe

Cunoscând n, m şi k, precum şi tabloul care codifică imaginea, se cere să se determine:
1. Numărul S de celule ocupate de către clădirea pătratică cu latura maximă şi numărul
de clădiri C alese dintre celelalte k  1 clădiri, cu proprietatea că fiecare dintre ele ”ı̂ncape” ı̂n
interiorul clădirii pătratice cu latură maximă, fără să se suprapună peste celulele marginale ale
acesteia.
2. Tabloul care reprezintă harta, ı̂n urma prelucrării imaginii iniţiale.

Date de intrare

Fişierul de intrare harta.in conţine pe prima linie un număr natural p. Pentru toate testele
de intrare, numărul p poate avea doar valoarea 1 sau valoarea 2.
Pe linia a doua se găsesc numerele naturale n, m şi k separate prin câte un spaţiu.
Pe fiecare dintre următoarele k linii, se găsesc câte patru numere naturale i1 j1 i2 j2 separate
prin câte un spaţiu, primele două numere reprezentând coordonatele celulei din extremitatea Nord-
Vest, iar ultimele două, coordonatele celulei din extremitatea Sud-Est pentru fiecare dintre cele k
clădiri.

Date de ieşire

a Dacă valoarea lui p este 1, atunci se va rezolva numai cerinţa 1. În acest caz, ı̂n fişierul de
ieşire harta.out se vor scrie cele două numere S şi C având semnificaţia descrisă la cerinţa 1,
separate printr-un singur spaţiu.
a Dacă valoarea lui p este 2, atunci se va rezolva numai cerinţa 2. În acest caz, fişierul de
ieşire harta.out va conţine tabloul care reprezintă harta obţinută pe baza imaginii din satelit.
Fişierul va avea n1 linii. Pe fiecare linie se vor găsi câte m1 valori 0 sau 1 separate prin câte un
singur spaţiu. Celulele situate pe marginile clădirilor vor avea valoarea 1. Celulele din interiorul
clădirilor, ca şi cele din exterior, vor avea valoarea 0.

230
CAPITOLUL 9. ONI 2014 9.1. HARTA 231

Restricţii şi precizări

a 3 & n, m & 1500


a 1 & i1 & i2 & n
a 1 & j1 & j2 & m
a 1 & k & 1000
a 1 & Lmax & 50 (Lmax - latura maximă a unui dreptunghi)
a Se garantează că există soluţie pentru ambele cerinţe, pentru toate datele de test.
a Pentru rezolvarea corectă a primei cerinţe se acordă 20 de puncte, iar pentru cerinţa a doua
se acordă 80 de puncte.

Exemple:

harta.in harta.out Explicaţii


1 16 2 Atenţie! Pentru acest test se rezolvă doar cerinţa 1.
774 Clădirea de coordonate 1 1 4 4
1144 este cel mai mare pătrat şi ocupă
6264 S = 4 x 4 = 16 celule.
3636 Clădirile de coordonate 3 6 3 6 şi 6 6 7 7
6677 ”ı̂ncap” ı̂n interiorul clădirii 1 1 4 4
fără să se suprapună
peste celulele sale marginale.
Deci C = 2.
2 01110000 Atenţie! Pentru acest test se rezolvă doar cerinţa 2.
10 11 4 01010010
1244 01010010
8989 01110000
7395 00000000
2939 00111000
00101010
00111000
00000000

Timp maxim de executare/test: 1.0 secunde


Memorie: total 16 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 10 KB

9.1.1 Indicaţii de rezolvare

prof. Constantin Gălăţan - C. N. Liviu Rebreanu” Bistriţa

Cerinţa a) - 20 puncte
Se determină latura Lmax a celei mai mari clădiri pătratice. Apoi, pentru fiecare clădire, se
determină dacă laturile sale de lungimi L şi H ı̂ndeplinesc condiţia: L $ Lmax  1 şi H $ Lmax  1.
Cerinţa b) Total - 80 de puncte
Soluţia 1 - 30 de puncte
Liniile şi coloanele care trebuie şterse trebuie să nu conţină valori 1. Se reţin numerele de
ordine acele acestor linii şi coloane. Dacă determinarea acestor linii sau coloane se face prin
parcurgerea lor şi ştergerea se fece linie cu linie, respectiv coloană cu coloană, atunci, ı̂n funcţie
de alte optimizări, se pot obţine 30 de puncte.
Soluţia 2 - 55 de puncte
CAPITOLUL 9. ONI 2014 9.1. HARTA 232

Se reţin la fel ca ı̂n soluţia anterioară numerele de ordine ale linilor şi coloanelor care trebuie
şterse. Pentru fiecare drum pe hartă, se şterg numărul maxim posibil de linii, respectiv coloane
adiacente.
Solutia 3 - 80 de puncte
În timpul citirii se generează clădirile ı̂n interiorul matricei a conform cerinţelor de afişare.
Pentru a evita operaţiile de ştergere de linii/coloane se marchează liniile/coloanele care trebuie
sa fie şterse astfel:
- initial a[i][0]=0; respectiv a[0][j]=0;
- dacă linia i / coloana j intersectează cel puţin o clădire, atunci a[i][0]=1; respectiv a[0][j]=1;
- dacă a[i][0]==0 si a[i+1][0]==0, atunci a[i][0]=2; (marcăm linia i pentru eliminare)
- dacă a[0][j]==0 si a[0][j+1]==0, atunci a[0][j]=2; (marcăm coloana j pentru eliminare)
- se afişează toate elementele a[i][j] pentru care a[i][0]!=2 && a[0][j]!=2

Soluţia 4 - 80 de puncte
Se reţin ı̂n şirurul x toate liniile pe care se găseşte cel puţin o celulă marginală ocupată de
o clădire, iar ı̂n şirul y toate coloanele pe care se găseşte cel puţin o celulă marginală ocupată
de o clădire. Pentru fiecare dreptunghi i1 j1 i2 j2, se inserează ı̂n x şi y şi coordonatele celulei
i1  1, j1  1 situate ı̂n exteriorul cădirii.
ı̂n şirurile xa şi xb se reţin liniile şi coloanele tuturor celulelor marginale ocupate de clădiri.
Se ordonează crescător şirurile x şi y, apoi, pentru fiecare valoare din şirul xa, se caută binar
poziţia i a acesteia ı̂n şirul x, iar pentru fiecare valoare din şirul ya, se caută binar poziţia j a
acesteia ı̂n şirul y.
Poziţia i, j  obţinută astfel reprezintă noile coordonate ale unei celule marginale ocupate de
o clădire pe hartă.

9.1.2 Cod sursă

Listing 9.1.1: harta.cpp


/* 100 pucte
Constantin Galatan
*/

#include <fstream>
#include <vector>
#include <algorithm>

using namespace std;

#define pb push_back
#define DIM 1501

typedef vector<int> VI;

ifstream fin("harta.in");
ofstream fout("harta.out");

struct Dr
{
int i1, j1, i2, j2;
Dr(int _i1, int _j1, int _i2, int _j2)
: i1(_i1), j1(_j1), i2(_i2), j2(_j2) {
}
};

vector<Dr> d;

int imax, jmax;


VI xa, ya, x, y, c1, c2, b1, b2;
int k, N, M, n, m, Lmax, L, H, T, nr_dr;

bool A[DIM][DIM], s1[DIM], s2[DIM];

void WriteMatr(bool a[][DIM], int N, int M);


int GetPos(VI, int val);
CAPITOLUL 9. ONI 2014 9.1. HARTA 233

int main()
{
fin >> T >> N >> M >> k;
int i1, j1, i2, j2;
x.pb(0), y.pb(0);
s1[0] = true;
s2[0] = true;
for ( int i = 0; i < k; ++i )
{
fin >> i1 >> j1 >> i2 >> j2;
d.pb(Dr(i1, j1, i2, j2));
L = i2 - i1 + 1, H = j2 - j1 + 1;
if ( L == H && L > Lmax )
Lmax = L;

for ( int i = i1; i <= i2; ++i)


{
xa.pb(i), ya.pb(j1), xa.pb(i), ya.pb(j2);
if ( !s1[i] )
x.pb(i), s1[i] = true;
}

for ( int j = j1; j <= j2; ++j )


{
xa.pb(i1), ya.pb(j);
xa.pb(i2), ya.pb(j);
if ( !s2[j] )
y.pb(j), s2[j] = true;
}

if ( !s1[i1 - 1] )
x.pb(i1 - 1), s1[i1 - 1] = true;
if ( !s2[j1 - 1] )
y.pb(j1 - 1), s2[j1 - 1] = true;
imax = max(imax, i2);
jmax = max(jmax, j2);
}

if ( T == 1 )
{
for ( int i = 0; i < k; ++i )
{
L = d[i].i2 - d[i].i1 + 1, H = d[i].j2 - d[i].j1 + 1;
if ( L < Lmax - 1 && H < Lmax - 1 )
nr_dr++;
}
fout << Lmax * Lmax << ’ ’ << nr_dr << ’\n’;
}
else
{
c1 = VI(imax + 1);
c2 = VI(jmax + 1);

for (int i = 0; i < x.size(); ++i )


c1[x[i]]++;
for (int i = 0; i < y.size(); ++i )
c2[y[i]]++;

for (int i = 1; i <= imax; ++i )


c1[i] += c1[i - 1];
for (int i = 1; i <= jmax; ++i )
c2[i] += c2[i - 1];

b1 = VI(x.size()); // aici - x sortat


b2 = VI(y.size());

for ( int i = 0; i < x.size(); ++i )


b1[c1[x[i]] - 1] = x[i], c1[x[i]]--;

for ( int i = 0; i < y.size(); ++i )


b2[c2[y[i]] - 1] = y[i], c2[y[i]]--;

n = 0, m = 0; int i, j;
for (size_t k = 0; k < xa.size(); ++k )
{
i= GetPos(b1, xa[k]);
CAPITOLUL 9. ONI 2014 9.1. HARTA 234

j = GetPos(b2, ya[k]);
n = max(n, i), m = max(m, j);
A[i][j] = 1;
}

if ( n < M ) n++;
if ( m < M ) m++;
WriteMatr(A, n, m);
}

fin.close();
fout.close();
return 0;
}

int GetPos(VI v, int val)


{
int i, p2, n = v.size();
int lo = 0, hi = n, mid;

while ( lo <= hi )
{
mid = lo + (hi - lo) / 2;
if ( v[mid] == val )
return mid;
if ( val < v[mid] )
hi = mid - 1;
else
lo = mid + 1;
}

return 0;
}

void WriteMatr(bool a[][DIM], int N, int M)


{
for ( int i = 1; i <= N; ++i )
{
for ( int j = 1; j <= M; ++j )
fout << a[i][j] << ’ ’;
fout << ’\n’;
}
}

Listing 9.1.2: harta Adriana.cpp


//100 puncte
// prof. Adriana Simulescu

# include <fstream>
# include <cstdio>

using namespace std;

int n,m,Lmax,p,k,S,s,C;
int x[1501],linii[1501],coloane[1501];

FILE *fout = fopen("harta.out","wt");

struct dreptunghi
{
int i1,i2,j1,j2;
};

dreptunghi d[1001],dmax,d2[1001],d1[1001];

void read ()
{
FILE *fin=fopen("harta.in","rt");
fscanf(fin,"%d%d%d%d",&p,&n,&m,&k);
for(int i=1;i<=k;++i)
{
fscanf(fin,"%d%d%d%d",&d[i].i1,&d[i].j1,&d[i].i2,&d[i].j2);
d1[i]=d2[i]=d[i];
CAPITOLUL 9. ONI 2014 9.1. HARTA 235

}
fclose(fin);
}

void solve()
{
int a,b;
if(p==1)
{
for(int i=1;i<=k;i++)
{
a=(d[i].i2-d[i].i1+1);
b=(d[i].j2-d[i].j1+1);
if(a==b)
{
s=a*b;
if(s>S)
{
S=s;
dmax=d[i];
}
}
}

for(int i=1;i<=k;i++)
{
if(d[i].i2-d[i].i1+1<=dmax.i2-dmax.i1-1&&
d[i].j2-d[i].j1+1<=dmax.j2-dmax.j1-1)
C++;
}
fprintf(fout,"%d %d\n",S,C);

}
else
{
int i,j,di=0,dj=0,ix=1,iy,xx=1,yy=1;
dreptunghi aux;
for(i=1;i<k;i++)
for(j=i+1;j<=k;j++)
{
if(d[i].i1>d[j].i1)
{
aux=d[i];
d[i]=d[j];
d[j]=aux;
}

if(d2[i].i2>d2[j].i2)
{
aux=d2[i];
d2[i]=d2[j];
d2[j]=aux;
}

if(d1[i].j1>d1[j].j1)
{
aux=d1[i];
d1[i]=d1[j];
d1[j]=aux;
}
}

for(i=1;i<=k;i++)
{
if(d[i].i1-2>=xx)
for(int ix=xx;ix<=d[i].i1-2;ix++)
linii[ix]=1;

if(d[i].i2>=xx)
xx=d[i].i2+1;

if(d1[i].j1-2>=yy)
for(int iy=yy;iy<=d1[i].j1-2;iy++)
coloane[iy]=1;

if(d1[i].j2>=yy)
CAPITOLUL 9. ONI 2014 9.1. HARTA 236

yy=d1[i].j2+1;
}

if(n-xx>=1)
for(int ix=xx;ix<=n-1;ix++)
linii[ix]=1;

if(m-yy>=1)
for(int iy=yy;iy<=m-1;iy++)
coloane[iy]=1;

int ii=1,iii=1;
i=1;
while(i<=n)
{
if(!linii[i])
{
while(d[ii].i1==i&&ii<=k)
{
x[d[ii].j1]=x[d[ii].j2]=d[ii].i2-d[ii].i1+1;

for(int jj=d[ii].j1+1;jj<d[ii].j2;jj++)
x[jj]=1;
ii++;
}

while(d2[iii].i2==i&&iii<=k)
{
for(int jj=d2[iii].j1+1;jj<d2[iii].j2;jj++)
x[jj]=1;
iii++;
}

for(j=1;j<=m;j++)
if(!coloane[j])
if(!x[j])
fprintf(fout,"0 ");
else
{
fprintf(fout,"1 ");
x[j]--;
}
fprintf(fout,"\n");
}

i++;
}
}
}

int main()
{
read();
solve();
fclose(fout);
return 0;
}

Listing 9.1.3: harta brut 1.cpp


/* 50 puncte
Constantin Galatan
*/
#include <fstream>
#include <algorithm>

using namespace std;

#define DIM 1501


ifstream fin("harta.in");
ofstream fout("harta.out");

struct Drept
{
int i1, j1, i2, j2;
CAPITOLUL 9. ONI 2014 9.1. HARTA 237

} d[DIM];

int sc[DIM], sl[DIM], a[DIM][DIM];


int k, N, M, Lmax, L, H, T, nr_dr;

void WriteMatr(int a[][DIM], int N, int M);


void DeleteLine(int L);
void DeleteColumn(int C);

int main()
{
fin >> T >> N >> M >> k;
int i1, j1, i2, j2;

for ( int i = 0; i < k; ++i )


{
fin >> i1 >> j1 >> i2 >> j2;
d[i].i1 = i1, d[i].j1 = j1, d[i].i2 = i2, d[i].j2 = j2;
L = i2 - i1 + 1, H = j2 - j1 + 1;

if ( L == H && L > Lmax )


Lmax = L;

for ( int i = i1; i <= i2; ++i)


a[i][j1] = a[i][j2] = 1, sl[i] += 2;

for ( int j = j1; j <= j2; ++j )


a[i1][j] = a[i2][j] = 1, sc[j] += 2;
}

if ( T == 1 )
{
for ( int i = 0; i < k; ++i )
{
L = d[i].i2 - d[i].i1 + 1, H = d[i].j2 - d[i].j1 + 1;
if ( L < Lmax - 1 && H < Lmax - 1 )
nr_dr++;
}

fout << Lmax * Lmax << ’ ’ << nr_dr << ’\n’;


}
else
{
for ( int col = 1; col <= M; ++col ) // pt fiec coloana O(M)
{
int k = 0;
while ( col + k <= M && !sc[col + k] )
k++;

if ( k > 1 )
{
for ( int j = col + 1; j < col + k; ++j )
a[1][j] = 2;

col += k - 1;
}
}

for ( int j = 1; j <= M; ++j )


if ( a[1][j] == 2 )
DeleteColumn(j), --j;

for ( int lin = 1; lin <= N; ++lin ) // pt fiec linie O(N)


{
int k = 0;
while ( lin + k <= N && !sl[lin + k] )
k++;

if ( k > 1 )
{
for ( int i = lin + 1; i < lin + k; ++i )
a[i][1] = 2;

lin += k - 1;
}
}
CAPITOLUL 9. ONI 2014 9.1. HARTA 238

for ( int i = 1; i <= N; ++i )


if ( a[i][1] == 2 )
DeleteLine(i), --i;

WriteMatr(a, N, M);
}

fin.close();
fout.close();
return 0;
}

void DeleteLine(int L)
{
for ( int j = 1; j <= M; ++j )
for ( int i = L; i < N; ++i )
a[i][j] = a[i + 1][j];
N--;
}

void DeleteColumn(int C)
{
for ( int i = 1; i <= N; ++i )
for ( int j = C; j < M; ++j )
a[i][j] = a[i][j + 1];
M--;
}

void WriteMatr(int a[][DIM], int N, int M)


{
for ( int i = 1; i <= N; ++i )
{
for ( int j = 1; j <= M; ++j )
fout << a[i][j] << ’ ’;
fout << ’\n’;
}
}

Listing 9.1.4: harta brut 2.cpp


/* 75 puncte
Constantin Galatan
*/
#include <iostream>
#include <fstream>
#include <algorithm>

using namespace std;

#define DIM 1501

ifstream fin("harta.in");
ofstream fout("harta.out");

struct Drept
{
int i1, j1, i2, j2;
} d[DIM];

int sc[DIM], sl[DIM], a[DIM][DIM];


int k, N, M, Lmax, L, H, T, nr_dr;

void WriteMatr(int a[][DIM], int N, int M);


void DeleteKLines(int L, int k);
void DeleteKColumns(int C, int k);

int main()
{
fin >> T >> N >> M >> k;
int i1, j1, i2, j2;

for ( int i = 0; i < k; ++i )


CAPITOLUL 9. ONI 2014 9.1. HARTA 239

{
fin >> i1 >> j1 >> i2 >> j2;
d[i].i1 = i1, d[i].j1 = j1, d[i].i2 = i2, d[i].j2 = j2;
L = i2 - i1 + 1, H = j2 - j1 + 1;
if ( L == H && L > Lmax )
Lmax = L;

for ( int i = i1; i <= i2; ++i)


a[i][j1] = a[i][j2] = 1, sl[i] += 2;

for ( int j = j1; j <= j2; ++j )


a[i1][j] = a[i2][j] = 1, sc[j] += 2;
}

if ( T == 1 )
{
for ( int i = 0; i < k; ++i )
{
L = d[i].i2 - d[i].i1 + 1, H = d[i].j2 - d[i].j1 + 1;
if ( L < Lmax - 1 && H < Lmax - 1 )
nr_dr++;
}

fout << Lmax * Lmax << ’ ’ << nr_dr << ’\n’;


}
else
{
for ( int col = 1; col <= M; ++col ) // pt fiec coloana O(M)
{
int k = 0;
while ( col + k <= M && !sc[col + k] )
k++;

if ( k > 1 )
{
for ( int j = col + 1; j < col + k; ++j )
a[1][j] = 2;

col += k - 1;
}
}

for ( int j = 1; j <= M; ++j )


{
int k = 0;
while ( j + k <= M && a[1][j + k] == 2 )
++k;

if ( k ) DeleteKColumns(j, k), --j;


}

for ( int lin = 1; lin <= N; ++lin ) // pt fiec linie O(N)


{
int k = 0;
while ( lin + k <= N && !sl[lin + k] )
k++;

if ( k > 1 )
{
for ( int i = lin + 1; i < lin + k; ++i )
a[i][1] = 2;
lin += k - 1;
}
}

for ( int i = 1; i <= N; ++i )


{
int k = 0;
while ( i + k <= N && a[i + k][1] == 2 )
++k;

if ( k ) DeleteKLines(i, k), --i;


}

WriteMatr(a, N, M);
}
CAPITOLUL 9. ONI 2014 9.1. HARTA 240

fin.close();
fout.close();
return 0;
}

void DeleteKLines(int L, int k)


{
for ( int j = 1; j <= M; ++j )
for ( int i = L; i + k <= N; ++i )
a[i][j] = a[i + k][j];

N -= k;
}

void DeleteKColumns(int C, int k)


{
for ( int i = 1; i <= N; ++i )
for ( int j = C; j + k <= M; ++j )
a[i][j] = a[i][j + k];

M -= k;
}

void WriteMatr(int a[][DIM], int N, int M)


{
for ( int i = 1; i <= N; ++i )
{
for ( int j = 1; j <= M; ++j )
fout << a[i][j] << ’ ’;

fout << ’\n’;


}
}

Listing 9.1.5: harta eugen.cpp


/* 100 puncte
prof. Eugen Nodea
*/
# include <cstdio>

using namespace std;

struct cladire
{
short x1, y1, x2, y2;
int l, L;
} C[1002];

short n, m, k;
bool A[1502][1502];
short l[1502], c[1502];

int main()
{
int i, j, p, Max, a, nr;
freopen("harta.in", "r", stdin);
freopen("harta.out","w", stdout);
scanf("%d", &p);
scanf("%hd %hd %hd", &n, &m, &k);
for (i=1; i<=k; ++i)
scanf("%hd %hd %hd %hd", &C[i].x1, &C[i].y1, &C[i].x2, &C[i].y2);

if (p == 1)
{
Max = 0;
for (i=1; i<=k; ++i)
{
C[i].l = C[i].x2 - C[i].x1 + 1;
C[i].L = C[i].y2 - C[i].y1 + 1;
if (C[i].l == C[i].L)
if (C[i].l > Max)
Max = C[i].l;
}
CAPITOLUL 9. ONI 2014 9.1. HARTA 241

a = (Max - 1) * (Max - 1);


nr = 0;
for (i=1; i<=k; ++i)
if (C[i].l * C[i].L <= a &&
C[i].l < Max - 1 &&
C[i].L < Max - 1)
++nr;

printf("%d %d\n", Max * Max, nr);


}
else
{
for (; k>0; --k)
{
for (i=C[k].x1; i<=C[k].x2; ++i)
{
A[i][C[k].y1] = A[i][C[k].y2] = 1;
l[i] = 1;
}

for (i=C[k].y1; i<=C[k].y2; ++i)


{
A[C[k].x1][i] = A[C[k].x2][i] = 1;
c[i] = 1;
}
}

for (i=1; i<=m; ++i)


if (c[i] == 0)
{
j = i + 1;
while (c[j] == 0 && j <= m)
c[j++] = 2;
}

for (i=1; i<=n; ++i)


if (l[i] == 0)
{
j = i + 1;
while (l[j] == 0 && j <= n)
l[j++] = 2;
}

for (i=1; i<=n; ++i)


if (l[i] < 2)
{
for (j=1; j<=m; ++j)
if (c[j] < 2) printf("%d ", (int) A[i][j]);
printf("\n");
}
}

return 0;
}

Listing 9.1.6: harta pit.cpp


/* 100 puncte
prof. Ionel-Vasile Pit-Rada
*/
#include<fstream>

using namespace std;

ifstream fin("harta.in");
ofstream fout("harta.out");

int p,n,m,k;
int lung,lat,lx;
int patrate[52],lmax;
char a[1502][1502];
int i1,j1,i2,j2,i;
int l1,c1;
int S,C;
CAPITOLUL 9. ONI 2014 9.1. HARTA 242

int main()
{
fin>>p>>n>>m>>k;
if(p==1)
{
for(i=1;i<=k;i++)
{
fin>>i1>>j1>>i2>>j2;
lat=i2-i1+1;
lung=j2-j1+1;
lx=lat;
if(lung>lx)lx=lung;
patrate[lx]++;
if(lat==lung)
if(lat>lmax)
lmax=lat;
}

S=lmax*lmax;
C=0;
for(i=1;i<=lmax-2;i++)
C=C+patrate[i];

fout<<S<<" "<<C;
}
else
{
for(i=1;i<=k;i++)
{
fin>>i1>>j1>>i2>>j2;
for(l1=i1;l1<=i2;l1++)
{
a[l1][j1]=1;
a[l1][j2]=1;
a[l1][0]=1;//marcat linie care ramane
}

for(c1=j1;c1<=j2;c1++)
{
a[i1][c1]=1;
a[i2][c1]=1;
a[0][c1]=1;//marcat coloana care ramane
}
}

for(l1=1;l1<=n-1;l1++)
if(a[l1][0]==0 && a[l1+1][0]==0)
a[l1][0]=2;//linia l1 se va sterge

for(c1=1;c1<=m-1;c1++)
if(a[0][c1]==0 && a[0][c1+1]==0)
a[0][c1]=2;//coloana c1 se va sterge

//vom pastra doar liniile si coloanele marcate diferit de 2


for (l1=1;l1<=n;l1++)
{
if(a[l1][0]!=2)
{
for(c1=1;c1<=m;c1++)
if(a[0][c1]!=2)
fout<<(int)a[l1][c1]<<" ";

fout<<"\n";
}
}
}

fout.close();
fin.close();
return 0;
}

Listing 9.1.7: harta1.cpp


CAPITOLUL 9. ONI 2014 9.1. HARTA 243

/* 100 puncte
Constantin Galatan
*/
#include <fstream>
#include <vector>
#include <algorithm>

using namespace std;

#define pb push_back
#define DIM 1501

ifstream fin("harta.in");
ofstream fout("harta.out");

struct Dr
{
int i1, j1, i2, j2;
Dr(int _i1, int _j1, int _i2, int _j2)
: i1(_i1), j1(_j1), i2(_i2), j2(_j2) {
}
};

typedef vector<int> VI;

VI xa, ya, x, y;
vector<Dr> d;
int imax, jmax;
int T, N, M, n, m, k, L, H, Lmax;
bool A[DIM][DIM], s1[DIM], s2[DIM];

void One(), Two(), WriteMatr(bool a[][DIM], int N, int M);

int main()
{
fin >> T >> N >> M >> k;
int i1, j1, i2, j2;
x.pb(0), y.pb(0);
s1[0] = true, s2[0] = true;
for ( int i = 0; i < k; ++i )
{
fin >> i1 >> j1 >> i2 >> j2;
d.pb(Dr(i1, j1, i2, j2));
L = i2 - i1 + 1, H = j2 - j1 + 1;
if ( L == H && L > Lmax )
Lmax = L;

for ( int i = i1; i <= i2; ++i)


{
xa.pb(i), ya.pb(j1), xa.pb(i), ya.pb(j2);
if ( !s1[i] ) x.pb(i), s1[i] = true;
}

for ( int j = j1; j <= j2; ++j )


{
xa.pb(i1), ya.pb(j); xa.pb(i2), ya.pb(j);
if ( !s2[j] ) y.pb(j), s2[j] = true;
}

if ( !s1[i1 - 1] )
x.pb(i1 - 1), s1[i1 - 1] = true;
if ( !s2[j1 - 1] )
y.pb(j1 - 1), s2[j1 - 1] = true;
imax = max(imax, i2); jmax = max(jmax, j2);
}

if ( T == 1 )
One();
else
Two();

fin.close();
fout.close();
return 0;
}
CAPITOLUL 9. ONI 2014 9.2. QVECT 244

void One()
{
int nr_dr(0);
for ( int i = 0; i < k; ++i )
{
L = d[i].i2 - d[i].i1 + 1, H = d[i].j2 - d[i].j1 + 1;
if ( L < Lmax - 1 && H < Lmax - 1)
nr_dr++;
}
fout << Lmax * Lmax << ’ ’ << nr_dr << ’\n’;
}

void Two()
{
sort(x.begin(), x.end());
sort(y.begin(), y.end());

x.erase(unique(x.begin(), x.end()), x.end());


x.erase(unique(x.begin(), x.end()), x.end());
y.erase(unique(y.begin(), y.end()), y.end());

int i, j;
for (size_t k = 0; k < xa.size(); ++k )
{
i = lower_bound(x.begin(), x.end(), xa[k]) - x.begin();
j = lower_bound(y.begin(), y.end(), ya[k]) - y.begin();
n = max(n, i), m = max(m, j);
A[i][j] = 1;
}

if ( n < M ) n++;
if ( m < M ) m++;
WriteMatr(A, n, m);
}

void WriteMatr(bool a[][DIM], int N, int M)


{
for ( int i = 1; i <= N; ++i )
{
for ( int j = 1; j <= M; ++j )
fout << a[i][j] << ’ ’;
fout << ’\n’;
}
}

9.1.3 *Rezolvare detaliată

9.2 qvect
Problema 2 - qvect 100 de puncte
Se consideră N vectori cu elemente ı̂ntregi, numerotaţi de la 1 la N , sortaţi crescător, fiecare
vector având un număr precizat de elemente.

Cerinţe

Să se răspundă la Q ı̂ntrebări de tipul:


a) 1 i j
cu semnificaţia: care este minimul dintre modulele diferenţelor oricăror două elemente,
primul element aparţinând vectorului numerotat cu i, iar cel de al doilea element aparţinând
vectorului numerotat cu j ?
b) 2 i j
cu semnificaţia: care este valoarea ce se găseşte pe poziţia mediană ı̂n vectorul obţinut prin
interclasarea vectorilor având numerele de ordine i, i  1, ...,j (i $ j).

Date de intrare
CAPITOLUL 9. ONI 2014 9.2. QVECT 245

Fişierul de intrare qvect.in conţine pe prima linie două numerele naturale N Q, separate
printr-un spaţiu, ce reprezintă numărul de vectori, respectiv numărul de ı̂ntrebări.
Pe fiecare dintre următoarele N linii se găseşte descrierea unui vector sub forma: k a1 a2 ... ak ,
unde k reprezintă numărul de elemente, iar a1 , ..., ak reprezintă elementele vectorului, separate
prin câte un spaţiu.
Pe fiecare dintre următoarele Q linii se găseşte descrierea unei ı̂ntrebări sub forma unui triplet
de numere naturale: t i j, separate prin câte un spaţiu, unde t reprezintă tipul ı̂ntrebării şi poate
lua numai valorile 1 sau 2, iar i şi j au semnificaţia precizată ı̂n cerinţă.
Date de ieşire
Fişierul de ieşire qvect.out va conţine Q numere ı̂ntregi, câte unul pe linie, reprezentând ı̂n
ordine, răspunsurile la cele Q ı̂ntrebări.
Restricţii şi precizări
a 1 & N, i, j & 100
a 1 & Q & 1 000
a 1 & t & 2
a 1 & k & 5 000
a -1 000 000 000 & a1 , a2 , ...ak & 1 000 000 000
a Prin valoarea aflată pe poziţia mediană a unui vector a cu k elemente se ı̂nţelege valoarea
elementului situat pe poziţia [k/2], adică partea ı̂ntreagă a lui k / 2.
a 15% dintre teste vor conţine numai ı̂ntrebări de tipul 1
a 15% dintre teste vor conţine numai ı̂ntrebări de tipul 2

Exemple:
qvect.in qvect.out
Explicaţii
33 13 Prima ı̂ntrebare este de tipul 2. Vectorul nou obţinut
7 1 4 5 8 11 18 19 3 prin interclasarea vectorilor numerotaţi cu 2 şi cu 3 este
6 2 4 5 10 21 29 10 următorul: 2, 4, 5, 10, 13, 14, 15, 15, 21, 29 şi conţine
4 13 14 15 15 6+4=10 elemente, valoarea elementului median este 13.
223 A doua ı̂ntrebare este de tipul 1. Diferenţa minimă se
123 obţine pentru perechea (10,13), unde valoarea 10 aparţine
213 vectorului numerotat cu 2, iar valoarea 13 aparţine vectoru-
lui numerotat cu 3.
A treia ı̂ntrebare este de tipul 2. Poziţia mediană ı̂n vec-
torul nou obţinut prin interclasare este (7+6+4)/2 = 8, deci
valoarea ce se găseşte pe poziţia mediană este 10.
Timp maxim de executare/test: 1.0 secunde
Memorie: total 8 MB din care pentru stivă 2 MB
Dimensiune maximă a sursei: 15 KB

9.2.1 Indicaţii de rezolvare

prof. Eugen Nodea - C. N. ”Tudor Vladimirescu” Tg. Jiu


Aparent este o problema de implementare (oare?).
Rezolvarea intrebărilor de tip 1:
Deşi pare o problemă de căutare, rezolvarea optimă este asemănătoare unui algoritm de inter-
clasare:
subrutina q1(int a[], int b[], int n, int m)
{
i = 1, j = 1 , Min = inf;
Cat timp (i<=n && j <=m)
{
Min = minim(abs(a[i]-b[j]), Min)
i++, j++;
}
return Min
}
CAPITOLUL 9. ONI 2014 9.2. QVECT 246

Rezolvarea ı̂ntrebărilor tipului 2:


Deşi pare o problema de interclasare a mai mulţi vectori, totuşi vom folosi căutarea binară.
Astfel, vom căuta şi număra ı̂n toţi vectorii cuprinşi ı̂ntre i şi j câte valori sunt mai mici sau
egale cu valoarea căutată x (valoarea aflată pe poziţia mediană).
O soluţie brute-force obţine 20 p.

9.2.2 Cod sursă

Listing 9.2.1: qvect eugen bf.cpp


# include <fstream>
# include <algorithm>
# include <cmath>

# define inf 1000000001

using namespace std;

ifstream f("qvect.in");
ofstream g("qvect.out");

int a[102][5002];
int z[500002];

int q1(int x, int y)


{
int i, j, n = a[x][0], m=a[y][0], z, Min;
Min = 2 * inf;
for (i=1; i<=n; ++i)
for (j=1; j<=m; ++j)
{
z = abs(a[x][i] - a[y][j]);
if (z < Min) Min = z;
}
return Min;
}

int q2(int x, int y)


{
int i, k = 0, nr=0;

for (k=x; k<=y; ++k)


for (i=1; i<=a[k][0]; ++i)
z[++nr] = a[k][i];

nth_element(z+1, z+(nr/2), z+nr+1);

return z[nr/2];
}
int main()
{
int n, q, op, i, j, x, y;

f >> n >> q;
for (i=1; i<=n; ++i)
{
f >> x;
a[i][0] = x;
for (j=1; j<=x; ++j)
f>> a[i][j];
}

while ( q-- )
{
f >> op >> x >> y;
switch (op)
{
case 1: {
g << q1(x, y) << "\n";
break;
};
case 2: {
CAPITOLUL 9. ONI 2014 9.2. QVECT 247

g << q2(x, y) << "\n";


break;
};
}
}
return 0;
}

Listing 9.2.2: qvect eugen fs.cpp


# include <fstream>
# include <algorithm>
# define inf 1000000000

using namespace std;

ifstream f("qvect.in");
ofstream g("qvect.out");

int a[101][5001];
int nr[101];

int q1(int x, int y)


{
int i = 1, j = 1, n = a[x][0], m = a[y][0], Min = inf, z;
while ( i <= n && j <= m )
{
if (a[x][i] > a[y][j])
z = a[x][i] - a[y][j];
else
z = a[y][j] - a[x][i];

if (z == 0) return 0;
if (z < Min) Min = z;

if (a[x][i] < a[y][j])


++i;
else
++j;
}

return Min;
}

int q2(int x, int y, int p)


{
int i, j, s, d, val, nr, k, ok, nr0, rez, sol;
s = -inf, d = inf;
while ( s <= d )
{
val = (s + d) >> 1 ;
nr = 0;
ok = 0;
for (k = x; k<=y; ++k)
{
i = 1; j = a[k][0];
nr0 = upper_bound(a[k] + 1, a[k] + j + 1, val) - a[k] - 1;
if (a[k][nr0] == val)
{
sol = val; ok = 1;
}
nr += nr0;
}

if (nr >= p)
{
if (ok)
{
rez = sol;
if (nr == p) return sol;
}
d = val - 1;
}
else
s = val + 1;
CAPITOLUL 9. ONI 2014 9.2. QVECT 248

return rez;
}

int main()
{
int n, q, op, i, j, x, y, m;

f >> n >> q;
for (i=1; i<=n; ++i)
{
f >> x;
a[i][0] = x;
nr[i] = nr[i-1] + x;
for (j=1; j<=x; ++j)
f>> a[i][j];
}

while ( q-- )
{
f >> op >> x >> y;
switch (op)
{
case 1:
{
g << q1(x, y) << "\n";
break;
};
case 2:
{
m = (nr[y] - nr[x-1]) / 2;
g << q2(x, y, m) << "\n";
break;
};
}
}

return 0;
}

Listing 9.2.3: qvect eugen std.cpp


# include <cstdio>
# include <algorithm>

# define inf 1000000000

using namespace std;

int a[102][5002];
int nr[102];

int q1(int x, int y)


{
int i = 1, j = 1, n = a[x][0], m = a[y][0], Min = 2*inf, z;
while ( i <= n && j <= m )
{
if (a[x][i] > a[y][j])
z = a[x][i] - a[y][j];
else
z = a[y][j] - a[x][i];

if (z == 0) return 0;
if (z < Min) Min = z;

if (a[x][i] < a[y][j])


++i;
else
++j;
}

return Min;
}
CAPITOLUL 9. ONI 2014 9.2. QVECT 249

int q2(int x, int y, int p)


{
int i, j, s, d, val, nr, k, ok, nr0, rez, sol;
s = -inf, d = inf;
while ( s <= d )
{
val = (s + d) >> 1 ;
nr = 0; ok = 0;
for (k = x; k<=y; ++k)
{
i = 1; j = a[k][0];
nr0 = upper_bound(a[k] + 1, a[k] + j + 1, val) - a[k] - 1;
if (a[k][nr0] == val)
{
sol = val; ok = 1;
}

nr += nr0;
}

if (nr >= p)
{
if (ok)
{
rez = sol;
if (nr == p) return sol;
}
d = val - 1;
}
else
s = val + 1;
}

return rez;
}

int main()
{
int n, q, op, i, j, x, y, m;

freopen("qvect.in", "r", stdin);


freopen("qvect.out","w", stdout);

scanf("%d%d", &n, &q);


for (i=1; i<=n; ++i)
{
scanf("%d", &x);
a[i][0] = x;
nr[i] = nr[i-1] + x;
for (j=1; j<=x; ++j)
scanf("%d", &a[i][j]);
}

while ( q-- )
{
scanf("%d%d%d", &op, &x, &y);
switch (op)
{
case 1: {
printf("%d\n", q1(x, y));
break;
};
case 2: {
m = (nr[y] - nr[x-1]) >> 1;
printf("%d\n", q2(x, y, m));
break;
};
}
}

return 0;
}

Listing 9.2.4: qvect inter.cpp


CAPITOLUL 9. ONI 2014 9.2. QVECT 250

#include <algorithm>
#include <fstream>

using namespace std;

ifstream f("qvect.in");
ofstream g("qvect.out");

int a[101][5005], poz[101], n, q,nr;


int b[250005],c[250005];

void citeste()
{
f>>n>>q;
for(int i=1;i<=n;i++)
{
f>>a[i][0];
for(int j=1;j<=a[i][0];j++)
f>>a[i][j]; poz[i]=1;
}
}

int abs(int x)
{
return max(x,-x);
}

int cerinta1(int n1, int n2)


{
int i=1,j=1,m=0,nr1,nr2, dif=1000000005,x,y;
nr1=a[n1][0]; nr2=a[n2][0];
while(i<=nr1 && j<=nr2)
{
if(a[n1][i]<a[n2][j])
{
x=a[n1][i];i++;
if(m==2)
dif=min(dif, abs(x-y));
m=1;
}
else
if(a[n1][i]>a[n2][j])
{
y=a[n2][j];j++;
if(m==1)
dif=min(dif, abs(x-y));
m=2;
}
else
if(a[n1][i]==a[n2][j])
return 0;
}

if(i<=nr1)
{
if(m==2)
dif=min(dif, abs(a[n1][i]-y));
}
else
{
if(m==1)
dif=min(dif, abs(x-a[n2][j]));
}

return dif;
}

int interc(int k, int p,int nr)


{
int i=1,j=1,n=0;
while(i<=k && j<=a[p][0] && n<nr)
if(b[i]<=a[p][j])
c[++n]=b[i++];
else
c[++n]=a[p][j++];
CAPITOLUL 9. ONI 2014 9.2. QVECT 251

while(j<=a[p][0] && n<=nr)


c[++n]=a[p][j++];

while(i<=k && n<=nr)


c[++n]=b[i++];

for(i=1;i<=n;i++)
b[i]=c[i];

return n;
}

int cerinta2(int n1, int n2)


{
int i,j,nr=0,k;
for(i=n1;i<=n2;i++)nr+=a[i][0];
nr/=2;
k=min(nr,a[n1][0]);
for(i=1;i<=k;i++)
b[i]=a[n1][i];

for(i=n1+1;i<=n2;i++)
k=interc(k,i,nr);

return b[nr];
}

int main()
{
citeste();
int k,t,i,j;
for(k=1;k<=q;k++)
{
f>>t>>i>>j;
if(t==1)
g<<cerinta1(i,j)<<endl;
else
g<<cerinta2(i,j)<<endl;
}

return 0;
}

Listing 9.2.5: qvect mink.cpp


#include <fstream>
using namespace std;

ifstream f("qvect.in");
ofstream g("qvect.out");

int a[101][5005], poz[101], n, q,nr;

void citeste()
{
f>>n>>q;
for(int i=1;i<=n;i++)
{
f>>a[i][0];
for(int j=1;j<=a[i][0];j++)
f>>a[i][j];

poz[i]=1;
}
}

int abs(int x)
{
return max(x,-x);
}

int cerinta1(int n1, int n2)


{
int i=1,j=1,m=0,nr1,nr2, dif=1000000005,x,y;
nr1=a[n1][0];
CAPITOLUL 9. ONI 2014 9.2. QVECT 252

nr2=a[n2][0];
while(i<=nr1 && j<=nr2)
{
if(a[n1][i]<a[n2][j])
{ x=a[n1][i];i++;
if(m==2)
dif=min(dif, abs(x-y));
m=1;
}
else
if(a[n1][i]>a[n2][j])
{
y=a[n2][j];j++;
if(m==1)
dif=min(dif, abs(x-y));
m=2;
}
else
if(a[n1][i]==a[n2][j])
return 0;
}

if(i<=nr1)
{
if(m==2)
dif=min(dif, abs(a[n1][i]-y));
}
else
{
if(m==1)
dif=min(dif, abs(x-a[n2][j]));
}

return dif;
}

int minim(int n1, int n2)


{
int p=0,i, mi=1000000005,j,k;

for(i=n1;i<=n2;i++)
{
j=poz[i];
if(j<=a[i][0] && a[i][j]<mi)
{
mi=a[i][j];
p=i;
}
}

poz[p]++;
return mi;
}

int cerinta2(int n1, int n2)


{
int i,med,nr=0;
for(i=1;i<=n;i++)
poz[i]=1;

for(i=n1;i<=n2;i++)
nr+=a[i][0];

nr/=2;
for(i=1;i<=nr;i++)
med=minim(n1,n2);

return med;
}

int main()
{
citeste();

int k,t,i,j;
CAPITOLUL 9. ONI 2014 9.2. QVECT 253

for(k=1;k<=q;k++)
{
f>>t>>i>>j;
if(t==1)
g<<cerinta1(i,j)<<endl;
else
g<<cerinta2(i,j)<<endl;
}

return 0;
}

Listing 9.2.6: qvect qsort.cpp


#include <algorithm>
#include <fstream>

using namespace std;

ifstream f("qvect.in");
ofstream g("qvect.out");

int a[101][5005], poz[101], n, q,nr;


int b[500005];

void citeste()
{
f>>n>>q;
for(int i=1;i<=n;i++)
{
f>>a[i][0];
for(int j=1;j<=a[i][0];j++)
f>>a[i][j];

poz[i]=1;
}
}

int abs(int x)
{
return max(x,-x);
}

int cerinta1(int n1, int n2)


{
int i=1,j=1,m=0,nr1,nr2, dif=1000000005,x,y;
nr1=a[n1][0];
nr2=a[n2][0];

while(i<=nr1 && j<=nr2)


{
if(a[n1][i]<a[n2][j])
{
x=a[n1][i];i++;
if(m==2)
dif=min(dif, abs(x-y));
m=1;
}
else
if(a[n1][i]>a[n2][j])
{
y=a[n2][j];
j++;
if(m==1)
dif=min(dif, abs(x-y));
m=2;
}
else
if(a[n1][i]==a[n2][j])
return 0;
}

if(i<=nr1)
{
if(m==2)
CAPITOLUL 9. ONI 2014 9.2. QVECT 254

dif=min(dif, abs(a[n1][i]-y));
}
else
{
if(m==1)
dif=min(dif, abs(x-a[n2][j]));
}

return dif;
}

int cerinta2(int n1, int n2)


{
int i,j,nr=0,k;
for(i=n1;i<=n2;i++)
nr+=a[i][0];

nr/=2;
k=min(nr,a[n1][0]);
for(i=1;i<=k;i++)
b[i]=a[n1][i];

for(i=n1+1;i<=n2;i++)
{
for(j=1;j<=a[i][0] && j<=nr;j++)
b[++k]=a[i][j];

sort(b+1,b+k+1);
k=min(k,nr);
}

return b[k];
}

int main()
{
citeste();
int k,t,i,j;
for(k=1;k<=q;k++)
{
f>>t>>i>>j;
if(t==1)
g<<cerinta1(i,j)<<endl;
else
g<<cerinta2(i,j)<<endl;
}

return 0;
}

Listing 9.2.7: qvect qsortscanf.cpp


#include <algorithm>
#include <cstdio>

using namespace std;

int a[101][5005], poz[101], n, q,nr;


int b[500005];

void citeste()
{
freopen("qvect.in", "r", stdin);

scanf("%d%d", &n, &q);


for(int i=1;i<=n;i++)
{
scanf("%d",&a[i][0]);
for(int j=1;j<=a[i][0];j++)
scanf("%d",&a[i][j]);

poz[i]=1;
}
}
CAPITOLUL 9. ONI 2014 9.2. QVECT 255

int abs(int x)
{
return max(x,-x);
}

int cerinta1(int n1, int n2)


{
int i=1,j=1,m=0,nr1,nr2, dif=1000000005,x,y;
nr1=a[n1][0];
nr2=a[n2][0];

while(i<=nr1 && j<=nr2)


{
if(a[n1][i]<a[n2][j])
{
x=a[n1][i];i++;
if(m==2)
dif=min(dif, abs(x-y));
m=1;
}
else
if(a[n1][i]>a[n2][j])
{
y=a[n2][j];
j++;
if(m==1)
dif=min(dif, abs(x-y));
m=2;
}
else
if(a[n1][i]==a[n2][j])
return 0;
}

if(i<=nr1)
{
if(m==2)
dif=min(dif, abs(a[n1][i]-y));
}
else
{
if(m==1)
dif=min(dif, abs(x-a[n2][j]));
}

return dif;
}

int cerinta2(int n1, int n2)


{
int i,j,nr=0,k;
for(i=n1;i<=n2;i++)
nr+=a[i][0];

nr/=2;
k=min(nr,a[n1][0]);
for(i=1;i<=k;i++)
b[i]=a[n1][i];

for(i=n1+1;i<=n2;i++)
{
for(j=1;j<=a[i][0] && j<=nr;j++)
b[++k]=a[i][j];

sort(b+1,b+k+1);
k=min(k,nr);
}

return b[k];
}

int main()
{
citeste();
freopen("qvect.out","w", stdout);
CAPITOLUL 9. ONI 2014 9.2. QVECT 256

int k,t,i,j;

for(k=1;k<=q;k++)
{
scanf("%d%d%d", &t, &i, &j);
if(t==1)
printf("%d\n",cerinta1(i,j));
else
printf("%d\n",cerinta2(i,j));
}

return 0;
}

Listing 9.2.8: qvect vs.cpp


#include<fstream>

using namespace std;

ifstream fin("qvect.in");
ofstream fout("qvect.out");

int a[102][5003] ,N ,b[102] ,Q , v[500009], n, w[500009];


int inf=2100000000;

int dif_min(int i1, int j1)


{
int i,j,d,dmin;
i=1;
j=1;
dmin=inf;
while(i<=b[i1] && j<=b[j1])
{
d=a[i1][i]-a[j1][j];

if(d<0)d=-d;
if(d<dmin)dmin=d;
if(dmin==0)return 0;

if(a[i1][i]<a[j1][j])
i++;
else
j++;
}

return dmin;
}

int maxim(int a, int b)


{
return ((a>b)?a:b);
}

int cautbin(int i, int v)


{
int p,q,m;
a[i][b[i]+1]=maxim(v+1,a[i][b[i]]);
p=1; q=b[i];
while(p<=q)
{
m=p+(q-p)/2;
if(v<a[i][m])
q=m-1;
else
p=m+1;
}

return q;
}

int cautbin2(int i, int v)


{
int p,q,m,r;
p=1;
CAPITOLUL 9. ONI 2014 9.2. QVECT 257

q=b[i];
r=-1;
while(p<=q && r==-1)
{
m=p+(q-p)/2;
if(v==a[i][m])
return m;

if(v<a[i][m])
q=m-1;
else
p=m+1;
}

return -1;
}

void sortare()
{
int i,j,k,p,q,r,s,ok;

//sterg elementele suplimentare


k=1;
for (i=2;i<=n;i++)
{
if(v[k]!=v[i])
v[++k]=v[i];
}

n=k;
ok=0;
while(ok==0)
{
r=0;
s=0;
do
{
p=r+1;
q=p;
while(q+1<=n && v[q]<=v[q+1])
q++;

s++;
r=q+1;
while(r+1<=n && v[r]<=v[r+1])
r++;

if(p<=q && q<r && r<=n)


{
s++;
i=p;
j=q+1;
k=0;
while(i<=q && j<=r)
{
if(v[i]<v[j])
w[++k]=v[i++];
else
w[++k]=v[j++];
}

while(i<=q)
w[++k]=v[i++];

while(j<=r)
w[++k]=v[j++];

i=p;
for(j=1;j<=k;j++)
v[i++]=w[j];
}
} while(r<n);

if(s==1) ok=1;
}
CAPITOLUL 9. ONI 2014 9.2. QVECT 258

//sterg elementele suplimentare


k=1;
for (i=2;i<=n;i++)
{
if(v[k]!=v[i])
v[++k]=v[i];
}
n=k;
}

int mmse(int i1, int j1, int v)


{
//aflam fata de cate valori din secventa i1..j1 este v mai mare sau egala
int s,i;
s=0;
for(i=i1;i<=j1;i++)
{
if(a[i][b[i]]<=v)
s=s+b[i];
else
if(a[i][1]<=v)
s=s+cautbin(i,v);
}

return s;
}

int exista(int i1, int j1, int v)


{
int s,i;
for(i=i1;i<=j1;i++)
{
if(a[i][1]<=v && v<=a[i][b[i]])
{
s=cautbin2(i,v);
if(s!=-1) return 1;
}
}

return -1;
}

int mediana(int i1, int j1)


{
int p,q,m,s,nr;
nr=0;
for(p=i1;p<=j1;p++)
nr=nr+b[p];

p=1;
q=n;
while(p<=q)
{
m=p+(q-p)/2;
s=mmse(i1,j1,v[m]);
if(s<nr/2)
p=m+1;
else
q=m-1;
}

while(exista(i1,j1,v[p])==0)
p++;

return v[p];
}

int main()
{
int i,j,t,i1,j1;
fin>>N>>Q;
n=0;
for (i=1;i<=N;i++)
{
fin>>b[i];
for (j=1;j<=b[i];j++)
CAPITOLUL 9. ONI 2014 9.3. TG 259

{
fin>>a[i][j];
n++;
v[n]=a[i][j];
}
}

sortare();

for (i=1;i<=Q;i++)
{
fin>>t>>i1>>j1;
if(t==1)
fout<<dif_min(i1,j1);
else
fout<<mediana(i1,j1);

fout<<"\n";
}

fout.close();
fin.close();
return 0;
}

9.2.3 *Rezolvare detaliată

9.3 tg
Problema 3 - tg 100 de puncte
Fie un număr natural N . Spunem că a, b, c este un triplet geometric
Ó
limitat de N , dacă a, b
şi c sunt trei numere naturale astfel ı̂ncât 1 & a $ b $ c & N şi b a c.

Cerinţe

Să se determine numărul tripletelor geometrice limitate de numărul natural N .

Date de intrare

Fişierul de intrare tg.in conţine pe prima linie un număr natural N .

Date de ieşire

În fişierul de ieşire tg.out se va scrie numărul tripletelor geometrice limitate de N .

Restricţii şi precizări

a 4&N & 4 000 000

Exemple:

tg.in tg.out Explicaţii


8 2 Cele două triplete sunt 1, 2, 4 şi 2, 4, 8
Timp maxim de executare/test: 0.1 secunde
Memorie: total 32 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 5 KB
CAPITOLUL 9. ONI 2014 9.3. TG 260

9.3.1 Indicaţii de rezolvare

prof. Piţ-Rada Ionel-Vasile - C. N. ”Traian” Drobeta-Tr. Severin

Complexitate O N ˜ N 
Pentru fiecare a din mulţimea r1, 2, ..., N  2x şi pentru fiecare c din mulţimea a  2, a  3, ..., N
se verifică dacă a ˜ c este pătrat perfect şi ı̂n caz afirmativ se calculează b sqrt a ˜ c şi se
contorizează rezultatul.
Complexitate O N ˜ sqrt N 
Pentru fiecare a din mulţimea 1, 2, ..., N  2 se observă că dacă ar exista tripletul a, b, c atunci
ar trebui să avem c b ˜ b©a, deci b ˜ b ar trebui să fie multiplu al lui a şi ı̂n acelaşi timp ar trebui
să fie pătrat perfect, iar b % a.
Ne-ar ajuta astfel să ştim care este cel mai mic pătrat perfect multiplu al lui a. Fie a1 acest
număr. Am avea a1 a ˜ x, unde x este cel mai mic posibil astfel incat a1 să fie pătrat perfect.
e e e f f f
Dacă a p11 ˜ p22 ˜ ... ˜ pkk , atunci x p11 ˜ p22 ˜ ... ˜ pkk unde fi ei mod 2 , adică x este
produsul factorilor primi care apar la puteri impare ı̂n descompunerea lui a.
Se mai observă apoi că orice alt multiplu al lui a care este si pătrat perfect va fi de forma
a2 a ˜ x ˜ k , unde k ' 1. Pentru k 1 se obtine a1 . Deoarece trebuie să avem b ˜Õb % a ˜ a,
2

atunci patratele perfecte care ne interesează pentru obţinerea lui c se obţin pentru k % xa . Astfel
Ó Õ
a x k si c b ˜ b©a x k . Deoarece c & N vom avea k & N
2
putem determina b x
.
ÕCu alte cuvinte
Õ pentru fiecare
Õ a din mulţimea r 1, 2, ..., N  2x vom parcurge k din mulţimea
a a a
r x  1 ,  x  2 , ...,  x x şi astfel vom obţine toate tripletele geometrice căutate, care sunt
Ó 2
de forma ( a, b a x k, c x k ).
Pentru determinarea lui x putem folosi algoritmul de descompunere ı̂n factori primi O sqrt a.
Complexitate O N 
Ideea de rezolvare este asemănatoare cu cea anterioară. Se incearcă diminuarea efortului de
calculare la fiecare pas a lui x prin construirea vectorului xi = cel mai mic numar natural care
ı̂nmulţit cu i produce un pătrat perfect, adică vom avea i ˜ xi cel mai mic patrat perfect multiplu
al lui i.
Se procedează asemănător cu algoritmul ”Ciurul lui Eratostene”. Se iniţializează xi cu 0 şi
se parcurge ı̂n ordinea 1, 2, 3, ..., N . Daca avem xi 0 atunci vom marca xi ˜ j ˜ j  i pentru
toti 1 & i ˜ j ˜ j & N .

9.3.2 Cod sursă

Listing 9.3.1: tg 100p.cpp


#include <fstream>
#include <algorithm>
using namespace std;

ifstream fin("tg.in");
ofstream fout("tg.out");

int N;
bool ok[4000010];
long long sol;

int main()
{
fin >> N;
for (int i = 1; i <= N; ++i)
if (!ok[i])
for (int j = 1; i * j * j <= N; ++j)
{
sol += j - 1;
ok[i * j * j] = true;
}

fout << sol << ’\n’;


CAPITOLUL 9. ONI 2014 9.3. TG 261

fin.close();
fout.close();
}

Listing 9.3.2: tg on.cpp


//nr triplete 1<=p<q<r<=n aflate in progresie geometrica
#include<fstream>
#include<cmath>

using namespace std;

ifstream fin("tg.in");
ofstream fout("tg.out");

int v[1000010];
int main()
{
long long n,a,i,k,s,p,n4,n4a;
fin>>n;
n4=n/4;
s=0;
for(a=1;a<=n4;a++)
{
if (v[a]==0)
{
n4a=n4/a;
for(i=1;i*i<=n4a;i++)
{
v[a*i*i]=a;
}
k=(int)sqrt(n/a);
if(k%2==0)
p=k/2*(k-1);
else
p=(k-1)/2*k;
s=s+p;
}
}

fout<<s<<endl;
fout.close();
fin.close();
return 0;
}

Listing 9.3.3: tg on v2.cpp


//nr triplete 1<=p<q<r<=n aflate in progresie geometrica
#include<fstream>
#include<cmath>

using namespace std;

ifstream fin("tg.in");
ofstream fout("tg.out");

int v[4000010];

int main()
{
long long n,s,a,x,k1,k2,t,d,c,b,i,k,s1,p,r;
fin>>n;
s=0;
t=0;
for (a=1;a<=n-2;a++)
{
if(v[a]==0)//i are toti factorii primi cu exponenti impari
{
b=(n-2)/a;
for(i=1;i*i<=b;i++)
v[a*i*i]=a;
}
CAPITOLUL 9. ONI 2014 9.3. TG 262

x=v[a];
k1=sqrt(a/x);
k2=sqrt(n/x);
s=s+k2-k1;
}

fout<<s<<endl;
fout.close();
fin.close();
return 0;
}

Listing 9.3.4: tg on2.cpp


//nr triplete 1<=p<q<r<=n aflate in progresie geometrica
#include<fstream>
#include<cmath>

using namespace std;

ifstream fin("tg.in");
ofstream fout("tg.out");

int main()
{
int a,c,s,n,ua,uc,uac;
long long bb;
double r;
fin>>n;
s=0;
for(a=1;a<=n-2;a++)
{
ua=a%10;
for (c=a+2;c<=n;c++)
{
uc=c%10;
uac=(ua*uc)%10;
if(uac==0 || uac==1 || uac==4 ||
uac==5 || uac==6 || uac==9)
{
bb=a;
bb=bb*c;
r=sqrt(bb);
if(r==(int)r)
s++;
}
}
}

fout<<s;
fout.close();
fin.close();
return 0;
}

Listing 9.3.5: tg onsqrtn.cpp


//nr triplete 1<=p<q<r<=n aflate in progresie geometrica
#include<fstream>
#include<cmath>

using namespace std;

ifstream fin("tg.in");
ofstream fout("tg.out");

int main()
{
long long n,s,a,x,k1,k2,t,d,c;
fin>>n;
s=0;
for (a=1;a<=n-2;a++)
{
CAPITOLUL 9. ONI 2014 9.4. PROGRESIE 263

x=1;
t=a;
d=2;
while(d*d<=t)
{
c=0;
while(t%d==0)
{
c++;
t=t/d;
}
if(c%2==1) x=x*d;
d++;
}

if(t>1) x=x*t;
k1=sqrt(a/x);
k2=sqrt(n/x);
s=s+k2-k1;
}

fout<<s<<endl;
fout.close();
fin.close();
return 0;
}

9.3.3 *Rezolvare detaliată

9.4 progresie
Problema 4 - progresie 100 de puncte
Cerinţe
Să se determine un şir strict Ócrescător, cu lungimea N , format din numere naturale nenule,
1 & a1 $ a2 $ a3 $ ... $ aN & 2N N , cu proprietatea că oricare trei termeni distincţi ai şirului nu
sunt ı̂n progresie aritmetică, adică pentru oricare numere naturale i, j şi k cu 1 & i $ j $ k & N ,
este ı̂ndeplinită condiţia: ai  ak j 2 aj . Prin x s-a notat partea ı̂ntreagă a lui x.
De Óexemplu, pentru N 5, cel mai mare termen al şirului va trebui să fie mai mic sau egal cu
2 5 5% adică aN & 22, deci o soluţie este: 1, 2, 4, 5, 10.
Date de intrare
Fişierul de intrare progresie.in conţine pe primul rând numărul natural N cu semnificaţia de
mai sus.
Date de ieşire
În fişierul de ieşire progresie.out se vor scrie pe primul rând, despărţite prin câte un spaţiu,
cele N elemente ale şirului ai , 1 & i & N .
Restricţii şi precizări
a 3 & N & 20 000
a Dacă soluţia nu este unică, se va accepta orice soluţie corectă.

Exemple:
progresie.in progresie.out Explicaţii
5 1 2 4 5 10 N 5; aN & 22;
Un şir strict crescător format din 5 numere naturale
nenule cu proprietatea că oricare 3 termeni ai săi nu
sunt ı̂n progresie aritmetică este: 1, 2, 4, 5, 10
7 3 5 6 11 12 14 15 N 7; aN & 37;
Un şir strict crescător format din 7 numere naturale
nenule cu proprietatea că oricare 3 termeni ai săi nu
sunt ı̂n progresie aritmetică este: 3, 5, 6, 11, 12, 14, 15
CAPITOLUL 9. ONI 2014 9.4. PROGRESIE 264

Timp maxim de executare/test: 0.1 secunde


Memorie: total 32 MB din care pentru stivă 16 MB
Dimensiune maximă a sursei: 5 KB

9.4.1 Indicaţii de rezolvare

prof. Cheşcă Ciprian - Liceul Tehnologic ”Costin Neniţescu” Buzău

Varianta 1
Această soluţie utilizează un algoritm asemănător cu algoritmul lui Eratostene de determinare
a numerelor prime. Se utilizează un vector care iniţial pe toate poziţiile este iniţializat cu 1 şi apoi
ı̂ncepând cu a doua poziţie se ”elimină” din acest şir toate poziţiile obţinute cu formula 2*poziţia
curentă - i unde i ia toate valorile anterioare poziţiei curente. Se avansează apoi ı̂n şir până la
prima valoare nenulă. Soluţia are un ordin de complexitate O n ˜ log n şi obţine aproximativ
30% din punctaj.
Varianta 2
Fie Tn mulţimea tuturor ı̂ntregilor nenegativi a căror scriere ı̂n baza 3 conţine cel mult n cifre,
toate fiind diferite de cifra 2.
Numărul de elemente din Tn este 2n deoarece elementele lui Tn sunt n-uple formate din 0 şi 1,
iar cel mai mare ı̂ntreg din Tn este 111....11 = (3n - 1)/2. Tn nu va conţine trei numere distincte
ı̂n progresie aritmetică deoarece dacă x, y, z " Tn 2y x  z, numărul 2y conţine numai cifrele 0
şi 2 ı̂n reprezentarea sa ı̂n baza trei. Rezultă că x y z, ţinând seama de algoritmul de adunare
ı̂n baza trei, ceea ce contrazice ipoteza. Soluţia obţine aproximativ 50% din punctaj.
Varianta 3
Soluţia are la bază un mecanism constructiv, observând că din prima stare notată S1 r1, 2x
se poate trece ı̂n starea următoare reunind elementele lui S1 cu elementele lui S1 adunate cu prima
putere a lui 3. Se obţine astfel S2 r1, 2, 4, 5x. Apoi se obţine S3 r1, 2, 4, 5, 10, 11, 13, 14x. Se
continuă mecanismul atât timp cât valorile din starea curentă sunt mai mici sau egale cu N . Se
poate demonstra prin inducţie matematică că acest algoritm conduce la mulţimi cu proprietatea
cerută de problemă. Ordinul de complexitate al acestei soluţii este O n şi obţine punctaj maxim.
Varianta 4 prof. Eugen Nodea - Colegiul Naţional ”Tudor Vladimirescu” Tg. Jiu
Se poate construi un şir care respectă cerinţele folosind relaţia de recurenţă:
a[2*i] = 3 * a[i] - 2
a[2*i+1] = 3 * a[i] - 1, i=0, ..., N, cu a[0] = 1
Complexitate : O N 
Varianta 5 prof. Piţ-Rada Ionel-Vasile, Colegiu Naţional ”Traian” Drobeta Turnu - Severin
Se construieşte şirul v[1], v[2], ..., v[N-1], format din numere naturale nenule cu proprietatea
că
(*) oricare două secvenţe adiacente din şirul v[] au sume diferite, astfel:
- primii trei termeni sunt 1, 2, 1
- al patrulea termen nu poate fi 1, 2, 3, 4 deci va fi aleasă valoarea 5, apoi pentru termenii
cinci, şase şi şapte se pot alege valorile 1, 2, 1 şi se obţine şirul cu şapte termeni 1, 2, 1, 5, 1, 2, 1
- al optulea termen nu poate fi dintre 1, 2, 3, ...,13 şi se va alege valoarea 14, apoi iaraşi se pot
completa următorii şapte termeni egali cu primii şapte termeni şi se obtine un şir cu 15 termeni
1, 2, 1, 5, 1, 2, 1, 14, 1, 2, 1, 5, 1, 2, 1
p
- apare astfel ideea de a construi un şir care pe poziţiile i egale cu puteri ale lui doi, i 2 , să
aibă valoarea egală cu suma termenilor anteriori plus 1,
v[i] = 1 +v[1] + v[2] + ... + v[i-1],
apoi următorii i  1 termeni vor fi aleşi identici cu primii obţinându-se un şir cu 2 ˜ i  1 termeni
şi procesul va fi repetat.
Şirul cerut de problemă se va obţine prin a[1]=1 şi a[k+1]=a[k]+v[k], pentru 1 & k $ N .
Din şirul 1, 2, 1, 5, 1, 2, 1, 14, 1, 2, 1, 5, 1, 2, 1 se va obţine şirul 1, 2, 4, 5, 10, 11, 13, 14, 28,
29, 31, 32, 37, 38, 40, 41, ...
Algoritmul permite implementare cu complexitatea O N .
CAPITOLUL 9. ONI 2014 9.4. PROGRESIE 265

9.4.2 Cod sursă

Listing 9.4.1: CC2 progresie.cpp


// Solutie Chesca Ciprian - 95 p
// Toate numere care in baza 3 nu contin cifra 2

#include <fstream>
#include <math.h>

using namespace std;

int main()
{
ifstream f("progresie.in");
ofstream g("progresie.out");

int n,i,s,x,ok,c;

f>>n;

s=0;
for(i=1;i<=2*n*sqrt(n);i++)
{
x=i;
ok=1;
do
{
c=x%3;
if (c==2) ok=0;
x=x/3;
} while (x>0&&ok);

if (ok)
if (s<n)
{
s++;
g<<i<<" ";
}
else
break;

g<<"\n";

f.close();
g.close();
return 0;
}

Listing 9.4.2: CC3 progresie.cpp


// Solutie Chesca Ciprian - 100 p
// Algoritm constructiv - inductie matematica

#include <fstream>
#include <math.h>
#include <iostream>

#define nmax 100009

using namespace std;

int a[nmax];

int main()
{
ifstream f("progresie.in");
ofstream g("progresie.out");

long long k,p;


int n,i,j,t,un,x;
CAPITOLUL 9. ONI 2014 9.4. PROGRESIE 266

f>>n;

a[1]=1;a[2]=2;k=2;

i=1;
un=0;
p=3;
while (!un)
{
t=k;
for(j=1;j<=k;j++)
{
x=a[j]+p;
if (x<=3*n*sqrt(n)) a[++t]=x;
else un=1;

if (!un)
k=2*k;
else
k=t;
i++;
p=p*3;
}
// cout << t ;

for(i=1;i<=n;i++)
g<<a[i]<<" ";

g<<"\n";
f.close();
g.close();
return 0;
}

Listing 9.4.3: CM progresie.cpp


#include <fstream>

using namespace std;

int v[2200002];

int main()
{
int n,j,u,i,x;

ifstream f("progresie.in") ;
ofstream g("progresie.out");

f>>n;
g<<1<<" "<<2;
v[1]=v[2]=1;
u=2;
for(i=3;i<=n;i++)
{
x=2*u;
for(j=u-1;j>=1;j--)
if(v[j]==1 && v[x-j]!=1)
v[x-j]=2;
u++;
while(v[u]) u++;
v[u]=1;
g<<" "<<u;
}

g<<endl;
return 0;
}

Listing 9.4.4: EN progresie.cpp


CAPITOLUL 9. ONI 2014 9.4. PROGRESIE 267

# include <cstdio>
# include <cmath>
using namespace std;

int N, K, i;
int a[100003];

int main()
{
freopen("progresie.in", "r", stdin);
freopen("progresie.out","w",stdout);

scanf("%d", &N);

a[0] = 1;
for (i=0; i<=N; ++i)
{
a[2*i] = 3 * a[i] - 2;
a[2*i+1] = 3 * a[i] - 1;
}

for (i=0; i<N; ++i)


printf("%d ", a[i]);

return 0;
}

Listing 9.4.5: PIT progresie.cpp


//Solutie - Pit Rada Vasile Ionel - 100 p

#include<fstream>

using namespace std;

ifstream fin ("progresie.in");


ofstream fout("progresie.out");

int N,K,s,a,i,v[220009],j;
long long p;

int main()
{
fin>>N;
s=0;
a=1;
fout<<a+s<<" ";
p=1;

for (i=1;i<N;i++)
{
if(i==p)
{
p=p*2;
v[i]=s+1;
s=s+v[i];
j=1;
}
else
{
v[i]=v[j];
s=s+v[i];
j++;
}

fout<<a+s<<" ";
}

fout.close();
fin.close();
return 0;
}

Listing 9.4.6: SP progresie.cpp


CAPITOLUL 9. ONI 2014 9.5. REFLEX 268

#include <stdio.h>
#include <math.h>

#define NMax 10002

int N, Limit;
bool b[2 * NMax * NMax];
int sol[NMax];

bool bkt( int x, int val )


{
if ( x == N + 1 )
return true;

for ( int v = val; v <= Limit; ++ v )


if ( b[v]==false )
{
for ( int i = 1; i < x; ++ i)
b[ 2 * v - sol[i] ]=true;

sol[x] = v;

if ( bkt( x + 1, v + 1) )
return true;

for ( int i = 1; i < x; ++ i)


b[ 2 * v - sol[i] ]=false;
}

return false;
}

int main()
{
freopen("progresie.in", "r", stdin);
freopen("progresie.out", "w", stdout);

scanf("%d", &N);
Limit = 2 * N * sqrt(N);
if ( bkt( 1, 1 ) )
{
for ( int i = 1; i <= N; ++ i)
printf("%d ", sol[i]);

printf("\n");
}
else
return 0;
}

9.4.3 *Rezolvare detaliată

9.5 reflex
Problema 5 - reflex 100 de puncte
La un concurs de robotică, ı̂n timpul prezentării, un roboţel cu corp cilindric cu diametrul de
o unitate scapă de sub control şi se deplasează ı̂ntr-un ring de formă dreptunghiulară. Ringul este
ı̂mpărţit ı̂n N  M pătrate identice, cu latura de o unitate, aşezate pe N linii şi M coloane.
Robotul poate părăsi ringul numai pe la colţuri, acestea
fiind numerotate de la 1 la 4, colţul cu numărul 1 fiind cel din
stânga jos apoi restul fiind numerotate ı̂n sens trigonometric.
Suprafaţa ringului este delimitată de exterior prin intermediul
a patru pereţi despărţitori: doi pereţi ”verticali” (aşezaţi de
la colţul 1 la colţul 4, respectiv de la colţul 2 la colţul 3) şi doi
pereţi ”orizontali” (aşezaţi de la colţul 1 la colţul 2, respectiv
de la colţul 3 la colţul 4), fără a bloca ieşirile, ca ı̂n desenul alăturat.
CAPITOLUL 9. ONI 2014 9.5. REFLEX 269

Robotul pătrunde ı̂n ring prin colţul cu numărul 1 sub un unghi de 45 grade şi cu o viteză de
un pătrat/s.
Ciocnirile cu pereţii sunt considerate perfect elastice (robotul nu-şi pierde din viteză) iar
unghiul de incidenţă este egal cu cel de reflexie.

Cerinţe

Se cere să se determine:


a) după câte secunde şi prin ce colţ al ringului va ieşi robotul;
b) de câte ori se ciocneşte robotul de pereţii orizontali şi verticali, rezultând o schimbare de
direcţie, până la ieşirea din ring.

Date de intrare

Fişierul de intrare reflex.in conţine pe prima linie două numere naturale N şi M , separate
printr-un singur spaţiu.

Date de ieşire

Fişierul de ieşire reflex.out va conţine pe prima linie două numere naturale S şi C, separate
printr-un singur spaţiu, S reprezentând numărul de secunde după care robotul va ieşi din ring, iar
C reprezintă numărul colţului prin care acesta va ieşi. Pe a doua linie, fişierul de ieşire va conţine
două numere naturale H şi V , separate printr-un spaţiu, H reprezentând numărul de ciocniri cu
pereţii orizontali ai ringului, iar V numărul de ciocniri cu pereţii verticali.

Restricţii şi precizări

a 3 & N, M & 2 000 000 000


a Pentru rezolvarea corectă a unei singure cerinţe se acordă 50% din punctaj, iar pentru
rezolvarea corectă a ambelor cerinţe se acordă 100% din punctaj.

Exemple:

reflex.in reflex.out Explicaţii


36 11 4 4 1
Până la ieşire se parcurg 11 pătrate,
ieşirea se produce pe la colţul 4. Se
produc 4 ciocniri cu pereţii orizontali
şi o ciocnire cu pereţii verticali.

57 13 4 2 1
Se parcurg 13 pătrate, ieşirea se face
la colţul 4 şi de produc 2 ciocniri cu
pereţii orizontali (ı̂n punctele a şi c
respectiv o ciocnire cu pereţii verti-
cali ı̂n punctul b).

Timp maxim de executare/test: 0.5 secunde


Memorie: total 2 MB din care pentru stivă 1 MB
Dimensiune maximă a sursei: 5 KB

9.5.1 Indicaţii de rezolvare

prof. Silaghi Lucian - Lic. Tehnologic ”Lucian Blaga” Reghin

Soluţia 1 - 40 de puncte
Se simulează deplasarea robutului din pătrat ı̂n pătrat pe diagonală (x x  1 şi y z  1),
se verifică ciocnirea cu un petete orizontal (y 1 sau y N ) sau cu un perete vertical (x 1 sau
x M ). Se verifică dacă s-a ajuns ı̂ntr-un colţ astfel:
- Colţul 1 (stânga jos) cu x 1 şi y 1;
- Colţul 2 (dreapta jos) cu x M şi y 1;
CAPITOLUL 9. ONI 2014 9.5. REFLEX 270

- Colţul 3 (dreapta sus) cu x M şi y N ;


- Colţul 4 (stânga sus) cu x 1 şi y N ;

În urma fiecărei ciocniri se schimba direcţia de deplasare pe axa x sau y, ı̂n funcţie de peretele
ciocnit.
Pentru rezolvarea cerinţei 1) se numără pătraţelele parcurse păna se ajunge ı̂ntr-un colţ, viteza
fiind de un pătrat/s, timpul ı̂n secunde coincide cu numărul de pătrate parcurse.
În cazul unei ciocniri cu un perete se numără această ciocnire ı̂n funtie de perete (orizontal
sau vertical).
Soluţia 2 - 68 de puncte
Se simulează deplasarea robotului din perete ı̂n perete şi se ı̂nâlnesc două situaţii:
- se poate face un salt pe x şi y cu min N, M  (N -lungimea şi M -lăţimea ringului);
- se produce o ciocnire ı̂nainte de a parcurge o distanţă egală cu min N, M , situaţie ı̂n care
incrementarea se face fără depăşirea lui N şi M sau 1.

ı̂n acest caz timpul (nr. de pătrate parcurse) se poate calcula mai simplu după relaţia:
S V M  1, ı̂n care S - nr. de secunde, V - nr. de ciocniri cu pereţii verticali, M - lungimea
ringului.
Solutia 3 - 100 de puncte
Dacă se analizează parcursul robotului desfăşurat (vezi imaginea de mai jos) şi se notează
n N  1 şi m M  1 se poate observa că numărul de ciocniri cu pereţii orizontali H, respectiv
numărul de ciocniri cu pereţii verticali V sunt daţi de relaţiile:
H N ©cmmdc N, M  şi V M ©cmmdc N, M  ı̂n care cu cmmdc N, M  s-a notat cel mai
mare divizor comun al lui N şi M .
Nr. de pătrate parcurse şi implicit timpul ı̂n secunde se poate calcual ca S cmmmc N, M 1.
Colţul ı̂n care se produce ieşirea se poate determina din nr. de ciocniri cu pereţii orizontali şi
verticali astfel:
- pt. număr impar de ciocniri cu pereţii orizontali se poate ieşi pe latura de sus adică colţurile
3 sau 4.
- pt. V par singura posibilitate de ieşire este colţul 2 (pe latura de jos);
- pt. H impar ieşirea se va face pe latura din dreapta, colţurile 2 sau 3;
- pt. H par ieşirea se face pe latura sin stânga singura posibilitate fiind colţul 4 .

Colţul exact se determină prin combinarea posibilitătilor rezultate ı̂n funtie de paritatea lui H
şi V .

Figura 9.1: reflex

9.5.2 Cod sursă

Listing 9.5.1: reflex eugen.cpp


# include <fstream>

using namespace std;

ifstream f("reflex.in");
ofstream g("reflex.out");

long long cmmmc, n, m, a, b, r, v, h, c;

int main()
CAPITOLUL 9. ONI 2014 9.5. REFLEX 271

{
f >> n >> m;
--n, --m;

//cmmdc
a = n;
b = m;
while(b)
{
r = a % b;
a = b; b = r;
}

//cmmmc
cmmmc = n * m / a;

v = n / a;
h = m / a;
c = 1;
if (v % 2 == 0)
c = 4;
else
if (h % 2 == 0)
c = 2;
else
c = 3;

g << cmmmc + 1 << " " << c << "\n";


g << h - 1 << " " << v - 1 << "\n";
return 0;
}

Listing 9.5.2: reflex LS brut.cpp


#include <iostream>
#include <iomanip>
#include <fstream>

using namespace std;

struct punct
{
long x,y;
};

int main()
{
long long n,m,x=1,N, S, E, W;
int dx,dy,colt=0;
punct c1,c2,c3,c4,b;

ifstream inFile;
ofstream outFile;

// citire date din fis


inFile.open("reflex.in");
outFile.open("reflex.out");

N=0;
S=0;
E=0;
W=0;
inFile >> n >> m;

c1.x=1; c1.y=1;
c2.x=m; c2.y=1;
c3.x=m; c3.y=n;
c4.x=1; c4.y=n;
b=c1;

dx=1;
dy=1;
do
{
x++;
CAPITOLUL 9. ONI 2014 9.5. REFLEX 272

b.x+=dx;
b.y+=dy;
//cout << "B("<<b.y<<"."<<b.x<<")\n ";
if (b.x == m) {dx= -1; E++;} //lat E
if (b.y == n) {dy= -1; N++;} //lat N
if (b.x == 1) {dx = 1; W++;} //lat W
if (b.y == 1) {dy = 1; S++;} //lat S
if ((b.x==c1.x) && (b.y==c1.y)) {colt=1; S--; W--;}
if ((b.x==c2.x) && (b.y==c2.y)) {colt=2; S--; E--;}
if ((b.x==c3.x) && (b.y==c3.y)) {colt=3; N--; E--;}
if ((b.x==c4.x) && (b.y==c4.y)) {colt=4; N--; W--;}

} while (!colt);

outFile << x << " " << colt << "\n";


outFile << N + S << " " << E + W << "\n";

inFile.close();
outFile.close();
return 0;
}

Listing 9.5.3: reflex LS Euclid 100p.cpp


#include <iostream>
#include <iomanip>
#include <fstream>

using namespace std;

long cmmdc(long a, long b)


{
long r;
while (b)
{
r=a%b;
a=b;
b=r;
}
return a;
}

int main()
{
long n,m,N,M,CMMDC,nl,nh;
long long s;
int colt=0;

ifstream inFile;
ofstream outFile;

// citire date din fis


inFile.open("reflex.in");
outFile.open("reflex.out");
inFile >> n >> m;
N=n-1;
M=m-1;
CMMDC=cmmdc(N,M);

//CMMMC = N * M / CMMDC;
//nl=CMMMC/M;
//nh=CMMMC/N;

nl=N/CMMDC;
nh=M/CMMDC;
if (nl%2==0)
colt=4;
else
if (nh%2==0)
colt=2;
else
colt=3;

s = nl;
s = s * M + 1;
CAPITOLUL 9. ONI 2014 9.5. REFLEX 273

outFile << s << " " << colt << "\n";


outFile << nh -1 << " " << nl -1 << "\n";

inFile.close();
outFile.close();
return 0;
}

Listing 9.5.4: reflex LS med.cpp


#include <iostream>
#include <iomanip>
#include <fstream>

using namespace std;

struct punct
{
long long x,y;
};

int main()
{
long long n,m,N, S, E, W;
long long s=1;

int dx,dy,colt=0;
punct c1,c2,c3,c4,b;

ifstream inFile;
ofstream outFile;

// citire date din fis


inFile.open("reflex.in");
outFile.open("reflex.out");

N=0, S=0, E=0, W=0;


inFile >> n >> m;

c1.x=1; c1.y=1;
c2.x=m; c2.y=1;
c3.x=m; c3.y=n;
c4.x=1; c4.y=n;
b=c1;

if (n>m)
dx=m-1;
else
dx=n-1;

dy=dx;
do
{
//cout <<"dx="<<dx<< " dy="<<dy<<"\n";
if ( (b.x+dx <= m) && (b.y+dy <= n) && (b.x+dx >=1) && (b.y+dy>=1) )
{
b.x+=dx;
b.y+=dy;
}
else
{
if (b.x+dx > m)
{
if (dy > 0)
b.y+=m-b.x;
else
b.y-=m-b.x;

b.x=m;
}
else
if (b.y+dy > n)
{
if (dx > 0)
CAPITOLUL 9. ONI 2014 9.5. REFLEX 274

b.x+=n-b.y;
else
b.x-=n-b.y;

b.y=n;
}
else
if (b.x+dx < 1)
{
if (dy > 0)
b.y+=b.x-1;
else
b.y-=b.x-1;

b.x=1;
}
else
if (b.y+dy < 1)
{
if (dx > 0)
b.x+=b.y-1;
else
b.x-=b.y-1;

b.y=1;
}
}

//cout << "B("<<b.y<<"."<<b.x<<")\n ";


//cin >> ch;
if (b.x == m) {dx= -dx; E++; s+=m-1;} //lat E
if (b.y == n) {dy= -dy; N++;} //lat N
if (b.x == 1) {dx = -dx; W++; s+=m-1;} //lat W
if (b.y == 1) {dy = -dy; S++;} //lat S
if ((b.x==c1.x) && (b.y==c1.y)) {colt=1; S--; W--;}
if ((b.x==c2.x) && (b.y==c2.y)) {colt=2; S--; E--;}
if ((b.x==c3.x) && (b.y==c3.y)) {colt=3; N--; E--;}
if ((b.x==c4.x) && (b.y==c4.y)) {colt=4; N--; W--;}
} while (!colt);

outFile << s << " " << colt << "\n";


outFile << N + S << " " << E +W << "\n";

inFile.close();
outFile.close();
return 0;
}

Listing 9.5.5: reflex LS Sr 100p.cpp


#include <iostream>
#include <iomanip>
#include <fstream>

using namespace std;

long cmmdc(long a, long b)


{
while (a!=b)
{
if (a>b)
a=a-b;
else
b=b-a;
}
return a;
}

int main()
{
long n,m,N,M,CMMDC,nl,nh;
long long s;
int colt=0;

ifstream inFile;
CAPITOLUL 9. ONI 2014 9.5. REFLEX 275

ofstream outFile;

// citire date din fis


inFile.open("reflex.in");
outFile.open("reflex.out");
inFile >> n >> m;

N=n-1;
M=m-1;
CMMDC=cmmdc(N,M);
// CMMMC = N * M / CMMDC;
nl=N/CMMDC;
nh=M/CMMDC;
if (nl%2==0)
colt=4;
else
if (nh%2==0)
colt=2;
else
colt=3;

s = nl;
s = s * M + 1;

outFile << s << " " << colt << "\n";


outFile << nh -1 << " " << nl -1 << "\n";

inFile.close();
outFile.close();
return 0;
}

Listing 9.5.6: reflex vs.cpp


#include <iostream>
#include <iomanip>
#include <fstream>

using namespace std;

long cmmdc(long a, long b)


{
while (a!=b)
{
if (a>b)
a=a-b;
else
b=b-a;
}
return a;
}

int main()
{
long n,m,N,M,CMMDC,nl,nh;
long long s;
int colt=0;

ifstream inFile;
ofstream outFile;

// citire date din fis


inFile.open("reflex.in");
outFile.open("reflex.out");
inFile >> n >> m;

N=n-1;
M=m-1;
CMMDC=cmmdc(N,M);
// CMMMC = N * M / CMMDC;
nl=N/CMMDC;
nh=M/CMMDC;
if (nl%2==0)
colt=4;
else
CAPITOLUL 9. ONI 2014 9.6. TRASEU 276

if (nh%2==0)
colt=2;
else
colt=3;

s = nl;
s = s * M + 1;

outFile << s << " " << colt << "\n";


outFile << nh -1 << " " << nl -1 << "\n";

inFile.close();
outFile.close();
return 0;
}

9.5.3 *Rezolvare detaliată

9.6 traseu
Problema 6 - traseu 100 de puncte
ı̂ntr-un oraş există un hotel de formă cubică, cu N etaje, nu-
merotate de la 1 la N . Suprafaţa fiecărui etaj K (1 & K & N )
este pătratică şi este ı̂mpărţită ı̂n N N camere identice alăturate,
dispuse pe N linii şi N coloane, fiecare cameră având drept
etichetă un triplet de numere naturale KLC  (K=etajul, L=linia,
C=coloana, 1 & L, C & N ), ca ı̂n imaginea alăturată.
Dintre cele N N N camere ale hotelului, una este specială
deoarece ı̂n ea locuieşte de mult timp un şoricel. Fiind isteţ, el ştie
eticheta camerei ı̂n care se află precum şi eticheta camerei ı̂n care
bucătarul hotelului depozitează alimente.
Studiind hotelul, şoricelul a constatat că pe fiecare etaj, din orice cameră poate intra ı̂n toate
camerele care au un perete comun cu aceasta (existând un mic orificiu pentru aerisire).
De asemenea, şoricelul a constatat că din fiecare cameră (situată la etajele 2, 3,
..., sau N  1) poate intra ı̂n camera situată imediat deasupra ei şi ı̂n camera situată
imediat sub ea.
Fiind un şoricel binecrescut, el nu intră ı̂n nicio cameră ocupată de clienţi ca să
nu-i deranjeze.
Hotelul având mulţi clienţi, şoricelul trebuie să-şi găsească cel mai scurt traseu de
la camera lui la camera cu alimente, traseu care să treacă printr-un număr minim de
camere, toate neocupate.

Cerinţe

Se cere să se determine:


a) numărul de camere prin care trece cel mai scurt traseu al şoricelului de la camera lui la
camera cu alimente (inclusiv camera lui şi camera cu alimente);
b) etichetele camerelor prin care trece traseul determinat la punctul a).

Date de intrare

Fişierul traseu.in conţine:


a pe prima linie, două numere naturale N şi M separate printr-un spaţiu, N cu semnificaţia
din enunţ iar M reprezentând numărul de camere ocupate de clienţii hotelului;
a pe a doua linie, trei numere naturale K1 L1 C1, separate prin câte un spaţiu, reprezentând
eticheta camerei ı̂n care se află şoricelul;
a pe a treia linie, trei numere naturale K2 L2 C2, separate prin câte un spaţiu, reprezentând
eticheta camerei ı̂n care sunt depozitate alimentele;
a pe fiecare dintre următoarele M linii, câte trei numere naturale X Y Z, separate prin câte
un spaţiu, reprezentând etichetele celor M camere ocupate de clienţi.
CAPITOLUL 9. ONI 2014 9.6. TRASEU 277

Date de ieşire

Fişierul de ieşire traseu.out va conţine pe prima linie un număr natural T reprezentând


numărul de camere prin care trece cel mai scurt traseu al şoricelului de la camera lui la camera
cu alimente determinat la punctul a).
Pe fiecare din următoarele T linii, se vor scrie câte trei numere naturale X Y Z, separate prin
câte un spaţiu, reprezentând etichetele camerelor prin care trece traseul determinat la punctul
a), ı̂n ordinea ı̂n care sunt parcurse camerele de către şoricel pentru a ajunge din camera lui ı̂n
camera cu alimente.

Restricţii şi precizări

a 2 & N & 100; 1 & M & 5000 şi M $ N ˜ N  2


a Şoricelul nu intră decât ı̂n camere neocupate de clienţi.
a Camera şoricelului este o cameră neocupată de clienţi.
a Dacă există mai multe trasee ale şoricelului de la camera lui la camera de alimente care trec
prin exact T camere, atunci traseul afişat va fi cel mai mic traseu din punct de vedere lexicografic.
a Eticheta (X1 Y1 Z1 ) se consideră strict mai mică ı̂n sens lexicografic ca eticheta (X2 Y2 Z2 )
dacă este satisfăcută doar una dintre condiţiile:
1) X1 $ X2 2) X1 X2 şi Y1 $ Y2 3) X1 X2 şi Y1 Y2 şi Z  1 $ Z2
a Eticheta X1 Y1 Z1 se consideră egală cu eticheta X2 Y2 Z2 dacă X1 X2 şi Y1 Y2 şi
Z1 Z2 . Vom scrie egalitatea lor astfel: (X1 Y1 Z1 ) = (X2 Y2 Z2 ).
a Traseul ce trece (ı̂n această ordine) prin camerele cu etichetele (X1 Y1 Z1 ), (X2 Y2 Z2 ), ...,
(XT YT ZT ) este mai mic din punct de vedere lexicografic decât traseul (A1 B1 C1 ), (A2 B2 C2 ),
..., (AT BT CT ) dacă există un indice J (1 & J & T ) astfel ı̂ncât (X1 Y1 Z1 ) = (A1 B1 C1 ), (X2
Y2 Z2 ) = (A2 B2 C2 ), ..., (XJ 1 YJ 1 ZJ 1 ) = (AJ 1 BJ 1 CJ 1 ) iar eticheta (XJ YJ ZJ ) este
strict mai mică decât eticheta (AJ BJ CJ ).
a Se acordă: 40% din punctaj pentru determinarea corectă a numărului T şi 100% din punctaj
pentru rezolvarea corectă a ambelor cerinţe.
a Se garantează că există soluţie pentru ambele cerinţe, pentru toate datele de test.

Exemple:

traseu.in traseu.outExplicaţii
34 7 Hotelul are trei etaje (1,2 şi 3). Pe fiecare etaj sunt 3*3 camere.
111 111 şoricelul se află ı̂n camera cu eticheta 1 1 1 iar camera cu alimente
333 112 are eticheta 3 3 3.
331 113 Sunt 4 camere ocupate de
211 123 clienţi. Acestea au etichetele
311 133 : 3 3 1, 2 1 1, 3 1 1, 3 1 3.
313 233 Traseul cel mai scurt trece
333 prin T=7 camere.
Sunt mai multe astfel de
trasee. De exemplu:
1) (1 1 1, 1 1 2, 1 1 3, 1 2 3,
1 3 3, 2 3 3, 3 3 3)
2) (1 1 1, 1 1 2, 1 1 3, 2 1 3, 2
2 3, 3 2 3, 3 3 3)
3) (1 1 1, 1 2 1, 1 3 1, 1 3 2, 2 3 2, 3 2 3, 3 3 3) etc.
Cel mai mic astfel de traseu (ı̂n sens lexicografic) este traseul 1).
Timp maxim de executare/test: 1.0 secunde
Memorie: total 16 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 10 KB

9.6.1 Indicaţii de rezolvare

prof. Carmen Mincă, Colegiul Naţional de Informatică ”Tudor Vianu”, Bucureşti

O soluţie se poate obţine astfel:


CAPITOLUL 9. ONI 2014 9.6. TRASEU 278

- Vom utiliza un masiv tridimensional A cu N*N*N componente (hotelul cu N*N*N camere),


care iniţial are toate elementele A[x][y][z]=0.
- Se poate borda masivul cu -1 pentru a se evita ieşirea ı̂n afara poziţiilor camerelor hotelului
- Marcăm cu 1 poziţia camerei şoricelului A[K1][L1][C1]=1.
- Pentru fiecare cameră accesibilă (fie x y z eticheta acestei camere) studiem cele maximum 6
camere vecine ı̂n care ar putea intra şoricelul:

Figura 9.2: traseu

- Poate fi accesibilă oricare din cele 6 camere cu dacă elemntul masivului corespunzător
etichetei camerei este 0 sau este mai mare ca A[x][y][z]
- Vizităm şi marcăm fiecare cameră accesibilă memorând valoarea A[x][y][z]+1 ı̂n elementul
corespunzător etichetei camerei ı̂n masiv. Valoare memorată reprezentând numărul minim
de camere prin care trebuie să treacă şoricelul pentru a ajunge ı̂n camera cu eticheta curentă,
plecând din camera iniţială.
- Poziţiile camerelor care au fost vizitate le vom reţine ı̂ntr-o coadă, ı̂n ordinea vizitării lor.
- Primul elemnt din coadă reţine poziţia (K1,L1,C1), memorat pe poziţia 1.
- Cât timp nu a fost vizitată camera cu alimente, vom vizita pentru poziţia curentă p din
coadă (x,y,z) cele maximum 6 camere accesibile. Vom adăuga la finalul cozii poziţiile fiecărei
camere accesibile, ı̂n ordinea vizitării. Trecem la poziţia p+1 din coadă şi acest procedeu.
- La final, valoarea T=A[K2][L2][C2] reprezintă numărul minim de camere prin care trece
taseul cel mai scurt al şoricelului de la camera lui la camera cu alimente.
- Traseul se va reconstitui plecând de la camera cu alimente către camera şoricelului. Se
vizitează cele maximum 6 camere accesibile marcate cu T-1, ı̂n ordinea din desen, pentru
a obţine traseul cel mai mic ı̂n sens lexicografic. Dintre acestea se alege prima cameră
marcată cu T-1. Apoi, pentru camera aleasă se vizitează camerele accesibile marcate cu T-2
ı̂n ordinea din imagine şi din nou se alege prima dintre acestea, etc. Traseul a fost obţinut
dacă s-a ajuns la camera şoricelului marcată cu 1.
- Poziţiile acestor T camere alese se vor memora ı̂ntr-un vector ale căror componente se vor
afişa ı̂n ordine inversă.
3
Complexitate O N 

9.6.2 Cod sursă

Listing 9.6.1: traseu carmen nerec.cpp


#include <fstream>
#include <iostream>

using namespace std;

int a[105][105][105], p, u,i;


short int n,m,k1,l1,c1,k2,l2,c2,x,y,z,xc,yc,zc;
struct camera
{
short int x,y,z;
} c[1000005];
CAPITOLUL 9. ONI 2014 9.6. TRASEU 279

short int dx[]={-1,0,0,0,0,1}, dy[]={0,-1,0,0,1,0},dz[]={0,0,-1,1,0,0};

ofstream g("traseu.out");

void citeste()
{
ifstream f("traseu.in");
f>>n>>m;
f>>k1>>l1>>c1>>k2>>l2>>c2;
for(i=1;i<=m;i++)
{
f>>x>>y>>z;
a[x][y][z]=-1;
}
}

void bordare()
{
for(int i=0;i<=n+1;i++)
for(int j=0;j<=n+1;j++)
{
a[0][i][j]=-1;
a[n+1][i][j]=-1;
a[i][0][j]=-1;
a[i][n+1][j]=-1;
a[i][j][0]=-1;
a[i][j][n+1]=-1;
}
}

void coada()
{
p=u=1;
c[1].x=k1;
c[1].y=l1;
c[1].z=c1;
a[k1][l1][c1]=1;

while((p<=u)&& (a[k2][l2][c2]==0))
{
x=c[p].x;
y=c[p].y;
z=c[p++].z;
for(i=0;i<6;i++)
{
xc=x+dx[i];
yc=y+dy[i];
zc=z+dz[i];
if (a[xc][yc][zc]==0)
{
u++;
a[xc][yc][zc]=a[x][y][z]+1;
c[u].x=xc;
c[u].y=yc;
c[u].z=zc;
}
}
}
}

void drum(int val)


{
while(val>0)
{
if (a[x-1][y][z]==val-1)
--x;
else
if (a[x][y-1][z]==val-1)
--y;
else
if (a[x][y][z-1]==val-1)
--z;
else
if (a[x][y][z+1]==val-1)
++z;
else
CAPITOLUL 9. ONI 2014 9.6. TRASEU 280

if (a[x][y+1][z]==val-1)
++y;
else
if (a[x+1][y][z]==val-1)
++x;
--val;
c[val].x=x;
c[val].y=y;
c[val].z=z;
}
}

int main()
{
citeste();
bordare();
coada();

int val=a[k2][l2][c2];
g<<val<<endl;
c[val].x=k2;
c[val].y=l2;
c[val].z=c2;
drum(val);

for(int i=1;i<=val; i++)


g<<c[i].x<<’ ’<<c[i].y<<’ ’<<c[i].z<<endl;

g.close();
return 0;
}

Listing 9.6.2: traseu carmen rec.cpp


#include <fstream>
#include <iostream>

using namespace std;

int a[105][105][105], p, u,i;


short int n,m,k1,l1,c1,k2,l2,c2,x,y,z,xc,yc,zc;
struct camera
{
short int x,y,z;
} c[1000005];
short int dx[]={-1,0,0,0,0,1}, dy[]={0,-1,0,0,1,0},dz[]={0,0,-1,1,0,0};

ofstream g("traseu.out");

void citeste()
{
ifstream f("traseu.in");
f>>n>>m;
f>>k1>>l1>>c1>>k2>>l2>>c2;
for(i=1;i<=m;i++)
{
f>>x>>y>>z;
a[x][y][z]=-1;
}
}

void bordare()
{
for(int i=0;i<=n+1;i++)
for(int j=0;j<=n+1;j++)
{
a[0][i][j]=-1;
a[n+1][i][j]=-1;
a[i][0][j]=-1;
a[i][n+1][j]=-1;
a[i][j][0]=-1;
a[i][j][n+1]=-1;
}
}
CAPITOLUL 9. ONI 2014 9.6. TRASEU 281

void coada()
{
p=u=1;
c[1].x=k1;
c[1].y=l1;
c[1].z=c1;
a[k1][l1][c1]=1;
while((p<=u)&&(a[k2][l2][c2]==0))
{
x=c[p].x;
y=c[p].y;
z=c[p++].z;
for(i=0;i<6;i++)
{
xc=x+dx[i];
yc=y+dy[i];
zc=z+dz[i];
if (a[xc][yc][zc]==0)
{
u++;
a[xc][yc][zc]=a[x][y][z]+1;
c[u].x=xc;
c[u].y=yc;
c[u].z=zc;
}
}
}
}

void drum(short int x, short int y, short int z, int val)


{
if(val>1)
{
if (a[x-1][y][z]==val-1) drum(x-1,y,z,val-1);
else
if (a[x][y-1][z]==val-1) drum(x,y-1,z,val-1);
else
if (a[x][y][z-1]==val-1) drum(x,y,z-1,val-1);
else
if (a[x][y][z+1]==val-1) drum(x,y,z+1,val-1);
else
if (a[x][y+1][z]==val-1) drum(x,y+1,z,val-1);
else
if (a[x+1][y][z]==val-1) drum(x+1,y,z,val-1);

g<<x<<’ ’<<y<<’ ’<<z<<endl;


}
}

int main()
{
citeste();
bordare();
coada();

int val=a[k2][l2][c2];
g<<val<<endl;
g<<k1<<’ ’<<l1<<’ ’<<c1<<endl;
drum(k2,l2,c2,val);

//cout<<val;
g.close();
return 0;
}

Listing 9.6.3: traseu eugen.cpp


# include <fstream>
# include <queue>
# define inf 20000003

using namespace std;

ifstream f("traseu.in");
ofstream g("traseu.out");
CAPITOLUL 9. ONI 2014 9.6. TRASEU 282

struct cel
{
short k, l, c;
};

short dx[]={-1, 0, 1, 0, 0, 0};


short dy[]={ 0, 1, 0,-1, 0, 0};
short dz[]={ 0, 0, 0, 0, 1,-1};
bool a[102][102][102];
int M[102][102][102], T[102][102][102];
short n, m, k1, l1, c1, k2, l2, c2;

queue <cel> q;

void lee()
{
short k;
cel x, y;
x.k = k1; x.l = l1; x.c = c1;
M[x.k][x.l][x.c] = 1;
T[x.k][x.l][x.c] = -1;
q.push(x);

while(!q.empty())
{
x = q.front(); q.pop();
for (k=0; k<6; ++k)
{
y.k = x.k + dz[k];
y.l = x.l + dx[k];
y.c = x.c + dy[k];
if (a[y.k][y.l][y.c] == 0)
if (M[x.k][x.l][x.c] + 1 < M[y.k][y.l][y.c])
{
if (T[y.k][y.l][y.c] == 0)
T[y.k][y.l][y.c] = x.k*1000000 + x.l*1000 + x.c;
else
{
if(T[y.k][y.l][y.c] > x.k*1000000+x.l*1000+x.c)
T[y.k][y.l][y.c] = x.k*1000000+x.l*1000+x.c;
}

M[y.k][y.l][y.c] = M[x.k][x.l][x.c] + 1;
q.push(y);
}
else
if (M[x.k][x.l][x.c] + 1 == M[y.k][y.l][y.c])
{
if (T[y.k][y.l][y.c] > x.k*1000000 + x.l*1000 + x.c)
T[y.k][y.l][y.c] = x.k*1000000 + x.l*1000 + x.c;
}
}
}
}

void afis(short k, short l, short c)


{
short k2, l2, c2;
int x;
if (k == k1 && l == l1 && c == c1)
g << k << " " << l << " " << c << "\n";
else
{
x = T[k][l][c];
k2 = x / 1000000;
l2 = x / 1000 % 1000;
c2 = x % 1000;
afis(k2, l2, c2);
g << k << " " << l << " " << c << "\n";
}
}

int main()
{
short k, l, c;
CAPITOLUL 9. ONI 2014 9.6. TRASEU 283

f >> n >> m;
for (k=0; k<=n+1; ++k)
for (l=0; l<=n+1; ++l)
for (c=0; c<=n+1; ++c)
{
M[k][l][c] = inf;
if (k==0 || l==0 || c==0)
a[k][l][c] = 1;
if (k==n+1 || l==n+1 || c==n+1)
a[k][l][c] = 1;
}

f >> k1 >> l1 >> c1;


f >> k2 >> l2 >> c2;

while (m--)
{
f >> k >> l >> c;
a[k][l][c] = 1;
}

lee();

g << M[k2][l2][c2] << "\n";


afis(k2, l2, c2);
return 0;
}

Listing 9.6.4: traseu vspit.cpp


#include<fstream>

using namespace std;

ifstream fin("traseu.in");
ofstream fout("traseu.out");

int N, M, K1, L1, C1, K2, L2, C2, K, L, C, k, l, c, T;


int A[103][103][103];
char dc[6]={ 0, 0,-1,+1, 0, 0};
char dl[6]={ 0,-1, 0, 0,+1, 0};
char dk[6]={-1, 0, 0, 0, 0,+1};

struct eticheta
{
char K, L, C;
};

eticheta tail[1000001],poz;
int tf, tl, tlen, i, j;

int main()
{
fin>>N>>M>>K1>>L1>>C1>>K2>>L2>>C2;
for(i=1;i<=M;i++)
{
fin>>K>>L>>C;
A[K][L][C]=-1;
}

A[K1][L1][C1]=1*10;
tail[0].K=K1;
tail[0].L=L1;
tail[0].C=C1;
tf=0;
tl=0;
tlen=1;
while(A[K2][L2][C2]==0 && tlen>0)
{
k=tail[tf].K;
l=tail[tf].L;
c=tail[tf].C;
for (j=0;j<=5;j++)
{
K=k+dk[j];
CAPITOLUL 9. ONI 2014 9.6. TRASEU 284

L=l+dl[j];
C=c+dc[j];
if(1<=K && K<=N && 1<=L && L<=N &&
1<=C && C<=N && A[K][L][C]==0)
{
A[K][L][C]=j + (1 + A[k][l][c] / 10) * 10;
tl++;
tail[tl].K=K;
tail[tl].L=L;
tail[tl].C=C;
tlen++;
if(K==K2 && L==L2 && C==C2)break;
}
}

tf++;
tlen--;
}

T=A[K2][L2][C2]/10;
K=K2;
L=L2;
C=C2;

for(i=T;i>=1;i--)
{
tail[i].K=K;
tail[i].L=L;
tail[i].C=C;
j=A[K][L][C]%10;
j=5-j;
K=K+dk[j];
L=L+dl[j];
C=C+dc[j];
}

fout<<T<<"\n";
for (i=1;i<=T;i++)
{
fout<<(int)tail[i].K<<" "<<(int)tail[i].L<<" "<<(int)tail[i].C<<"\n";
}

fout.close();
fin.close();
return 0;
}

9.6.3 *Rezolvare detaliată


Capitolul 10

ONI 2013

10.1 aranjare
Problema 1 - aranjare 100 de puncte
Toată lumea ştie că Mirel are 2 ˜ N sticluţe cu parfum aşezate pe un raft cu 2 ˜ N poziţii,
numerotate de la 1 la 2 ˜ N . El are N sticluţe cu parfum cumpărate din ţară şi alte N sticluţe cu
parfum cumpărate din Franţa.
Sticluţele cumpărate din ţară sunt etichetate cu r1 , r2 , r3 , ..., rN , iar sticluţele cumpărate din
Franţa sunt etichetate cu f1 , f2 , f3 , ..., fN . Fiecare sticluţă are asociată valoarea cu care a fost
cumpărată.
Iniţial, Mirel are aşezate pe primele N poziţii sticluţele cumpărate din ţară sortate
crescător după valoare, iar pe următoarele N poziţii sticluţele cumpărate din Franţa sortate tot
crescător după valoare. Astfel, cele 2 ˜ N sticluţe cu parfum sunt aşezate ı̂n felul următor:
r1 , r2 , r3 , ..., rN , f1 , f2 , f3 , ..., fN . Mai exact, sticluţa ri se află pe poziţia i, iar sticluţa fi se află
pe poziţia N  i, pentru i din intervalul 1, N .
Prietenul său cel mai bun, Marian, s-a gândit să-i facă o surprinză şi să-i schimbe aranjarea
sticluţelor cu parfum ı̂n următoarea ordine: r1 , f1 , r2 , f2 , r3 , f3 , ..., rN , fN . Cum Marian are două
mâini, el poate face numai următorul tip de operaţie: ia două sticluţe cu parfum de pe raft (de
pe două poziţii diferite) şi le interschimbă.

Cerinţe

Dându-se numărul N , şi 2 ˜ N valori reprezentând valoarea fiecărei sticluţe cu parfum, ajutaţi-l
pe Marian să facă operaţiile necesare pentru a schimba ordinea sticluţelor cu parfum ı̂n ordinea
precizată ı̂n enunţ.

Date de intrare

Fişierul de intrare aranjare.in conţine pe prima linie numărul N , iar pe următoarea linie 2 ˜ N
numere naturale, separate prin câte un spaţiu. Primele N numere reprezintă valorile sticluţelor
cumpărate din ţară, iar următoarele N numere reprezintă valorile sticluţelor cumpărate din Franţa.
Atât primele N , cât şi ultimele N numere sunt sortate crescător ı̂n funcţie de valoare.

Date de ieşire

Fişierul de ieşire aranjare.out va conţine mai multe linii. Pe fiecare linie se vor afla două
numere diferite x şi y din intervalul 1, 2 ˜ N , semnificând faptul că Marian trebuie să interschimbe
sticluţa de pe poziţia x cu sticluţa de pe poziţia y.

Restricţii şi precizări

a 2 & N & 100 000


a Dacă există mai multe soluţii, se poate afişa oricare dintre ele.
a Soluţia nu trebuie să facă neapărat numărul minim de operaţii.
a Pentru 15% din teste se garantează că N & 13.
a Pentru alte 25% din teste se garantează că N & 300.
a Pentru alte 30% din teste se garantează că N & 1000.

285
CAPITOLUL 10. ONI 2013 10.1. ARANJARE 286

Exemple:

aranjare.in aranjare.out
Explicaţii
3 24 În explicaţia de mai jos, fiecare sticluţă are numele etichetei
135235 35 urmat de valoarea ei ı̂n paranteză.
34 Şirul iniţial este: r1 1r2 3r3 5f1 2f2 3f3 5
După prima mutare devine: r1 1f1 2r3 5r2 3f2 3f3 5
După a doua mutare devine: r1 1f1 2f2 3r2 3r3 5f3 5
După ultima mutare devine: r1 1f1 2r2 3f2 3r3 5f3 5
Timp maxim de executare/test: 0.5 secunde
Memorie: total 16 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 5 KB

10.1.1 Indicaţii de rezolvare

stud. Cosmin Mihai Tutunaru, Universitatea Babeş Bolyai Cluj

Se observă că valorile sticluţelor din fişierul de intrare nu influenţează cu nimic rezolvarea
problemei, deoarece ele trebuie rearanjate doar după etichetă, deci atât configuraţia iniţială cât şi
cea finală se pot deduce cunoscând doar valoarea N .
Configuraţie iniţială: r1 r2 r3 ...rN f1 f2 f3 ...fN
Configuraţie finală: r1 f1 r2 f2 r3 f3 ...rN fN
Putem calcula pentru orice sticluţă poziţia finală pe care trebuie să o mutăm, astfel că putem
rezolva problema folosind algoritmul:

ˆ pentru i de la 1 la 2 ˜ N
` dacă pe poziţia i nu avem sticluţa care trebuie:
t facem swap ı̂ntre i şi pozitia pe care se află sticluţa ce trebuie aşezată pe poziţia i

La fiecare pas aducem pe poziţia corectă cel puţin o sticluţă, deci rezultă că sunt necesare cel
mult 2 ˜ N astfel de mutări pentru a duce configuraţia iniţială la configuraţia finală.
Complexitate: O N 

10.1.2 Cod sursă

Listing 10.1.1: aranjare.cpp


# include <fstream>
# include <iostream>

# define DIM 100003

using namespace std;

int n, v[2*DIM], p[2*DIM];

void read ()
{
ifstream fin ("aranjare.in");
fin>>n;
}

int main()
{
read ();
ofstream fout ("aranjare.out");

for(int i=1;i<=2*n;++i)
p[i]=i,
v[i]=i;

for(int i=1;i<n;++i)
{
CAPITOLUL 10. ONI 2013 10.2. GRADINA 287

if (v[2*i]!=n+i)
{
v[p[n+i]]=v[2*i];
p[v[2*i]]=p[n+i];
fout<<2*i<<" "<<p[n+i]<<"\n";
}

if (v[2*i+1]!=i+1)
{
v[p[i+1]]=v[2*i+1];
p[v[2*i+1]]=p[i+1];
fout<<2*i+1<<" "<<p[i+1]<<"\n";
}
}

return 0;
}

10.1.3 *Rezolvare detaliată

10.2 gradina
Problema 2 - gradina 100 de puncte
Păcală a reuşit să ducă la bun sfârşit ı̂n elegerea cu boierul căruia-i fusese slugă şi, conform
ı̂nvoielii, boierul trebuie să-l răsplătească dându-i o parte din livada sa cu pomi fructiferi. Boierul
este un om foarte ordonat, aşa că livada sa este un pătrat cu latura de N metri unde, pe vremuri,
fuseseră plantate N rânduri cu câte N pomi fiecare. Orice pom fructifer putea fi identificat
cunoscând numărul rândului pe care se află şi poziţia sa ı̂n cadrul rândului respectiv. Cu timpul,
unii pomi s-au uscat şi acum mai sunt doar P pomi. Păcală trebuie să-şi delimiteze ı̂n livadă o
grădină pătrată cu latura de K metri.

Cerinţe

Cunoscând dimensiunile livezii şi grădinii, numărul pomilor din livadă şi poziţia fiecăruia,
determinaţi numărul maxim de pomi dintr-o grădină pătrată de latură K şi numărul modurilor
ı̂n care poate fi amplasată grădina cu numărul maxim de pomi.

Date de intrare

Fişierul gradina.in conţine:


- pe prima linie numerele naturale N , P şi K, separate prin câte un spaţiu, cu semnificaţia
din enunţ;
- pe următoarele P linii câte 2 numere naturale Lin şi Col, separate printr-un spaţiu,
reprezentând numărul rândului, respectiv poziţia ı̂n rând a fiecărui pom din livadă.

Date de ieşire

Fişierul gradina.out va conţine:


- pe prima linie numărul maxim de pomi fructiferi dintr-o grădină pătrată cu latura de K
metri;
- pe a doua linie numărul de posibilităţi de a amplasa grădina astfel ı̂ncât să conţină numărul
maxim de pomi determinat.

Restricţii şi precizări

a 2 & N & 1000


1&P &N
2
a
a 1&K&N

Exemple:
CAPITOLUL 10. ONI 2013 10.2. GRADINA 288

gradina.in gradina.out Explicaţii


12 10 5 5 Grădina lui Păcală poate avea maximum 5 pomi fructiferi.
43 5 Ea poate fi amplasată ı̂n 5 moduri, având colţul stânga-sus de
55 coordonate:
68 (5, 3), (5, 4), (5, 5), (6, 6), (7, 3).
73
77
88
93
96
10 10
11 5
Timp maxim de executare/test: 0.5 secunde
Memorie: total 16 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 5 KB

10.2.1 Indicaţii de rezolvare

prof. Alin Burţa - C.N. ”B.P. Hasdeu” Buzău

Pentru a putea determina grădina cu număr maxim de pomi vom calcula un tablou bidimen-
sional S cu sume parţiale:
S ij  = suma elementelor din subtabloul care are colţul stânga-sus de coordonate (1,1) şi
colţul dreapta-jos de coordonate i, j , adică numărul pomilor din această zonă;
Bazându-ne pe tabloul S, pentru fiecare poziţie i, j , determinăm numărul pomilor din sub-
tabloul care are colţul stânga-sus de coordonate i  k  1, j  k  1 şi colţul dreapta-jos de
coordonate i, j , după formula:

npomi S ij   S i  k j   S ij  k   S i  k j  k 

Pe măsură ce calculăm, actualizăm numărul maxim de pomi şi numărul de soluţii.


2
Complexitatea algoritmului este O N .
Se pot utiliza şi alte idei de rezolvare de complexitate mai mare. De exemplu, putem contoriza
3
numărul pomilor din fiecare pătrat de latură K, ı̂nsă algoritmul are complexitatea O N  şi va
obţine cel mult 60p.
Dacă se foloseşte ”căutarea brută” a gradinii cerute, determinând pentru fiecare poziţie i, j 
4
numărul pomilor din grădina ce are colţul stânga-sus ı̂n această poziţie, complexitatea va fi O N ,
iar algoritmul va obţine cel mult 30p.

10.2.2 Cod sursă

Listing 10.2.1: gradina.cpp


# include <fstream>
# include <cstdio>

# define DIM 1003

using namespace std;

int n, p, k, a[DIM][DIM], pmax, nsol;

void read ()
{
FILE *fin=fopen("gradina.in","rt");
fscanf(fin,"%d%d%d",&n,&p,&k);

for(int i=1;i<=p;++i)
{
int x, y;
fscanf(fin,"%d%d",&x,&y);
a[x][y]=1;
CAPITOLUL 10. ONI 2013 10.3. SPLIT 289

fclose(fin);
}

void solve ()
{
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
a[i][j]+=a[i][j-1]+a[i-1][j]-a[i-1][j-1];

for(int i=k;i<=n;++i)
for(int j=k;j<=n;++j)
{
int np = a[i][j]-a[i-k][j]-a[i][j-k]+a[i-k][j-k];
if (np>pmax)
{
pmax=np;
nsol=1;
}
else
if (np==pmax)
++nsol;
}
}

int main ()
{
read ();
solve ();

FILE *fout = fopen("gradina.out","wt");


fprintf(fout,"%d\n%d\n",pmax,nsol);
fclose(fout);
return 0;
}

10.2.3 *Rezolvare detaliată

10.3 split
Problema 3 - split 100 de puncte
Fie un şir a1 , a2 , ..., aN de numere naturale. Se ı̂mparte şirul ı̂n patru secvenţe astfel ı̂ncât
orice element din şir să aparţină unei singure secvenţe şi fiecare secvenţă să conţină cel puţin
două elemente. Mai exact, se identifică trei indici i $ j $ k astfel ı̂ncât prima secvenţă este
formată din elementele a1 , a2 , ..., ai , a doua din elementele ai1 , ai2 , ..., aj , a treia din elementele
aj 1 , aj 2 , ..., ak şi ultima din elementele ak1 , ak2 , ..., an . Pentru fiecare secvenţă se determină
costul ei ca fiind diferenţa dintre valoarea maximă şi cea minimă din acea secvenţă.

Cerinţe

Să se determine o ı̂mpărţire a şirului ı̂n patru secvenţe astfel ı̂ncât suma costurilor celor patru
secvenţe să fie maximă.

Date de intrare

Fişierul split.in conţine pe prima linie numărul natural N . Pe linia a doua se găsesc N numere
naturale, separate prin câte un spaţiu, reprezentând elementele şirului a.

Date de ieşire

Fişierul split.out conţine pe prima linie un singur număr natural reprezentând suma maximă
a costurilor celor patru secvenţe. Pe linia a doua se află trei numere naturale i, j şi k, separate
prin câte un spaţiu, cu semnificaţia din enunţ.

Restricţii şi precizări


CAPITOLUL 10. ONI 2013 10.3. SPLIT 290

a8 & N & 5 000


a0 & ai & 100 000 000, pentru orice i 1..N
a O secvenţă poate avea costul 0 (valoarea maximă egală cu valoarea minimă)
a Dacă există mai multe soluţii cu aceeaşi sumă maximă, atunci se va alege soluţia cu i minim.
Dacă există mai multe soluţii cu acelaşi i minim, se alege aceea cu j minim, iar dacă există mai
multe soluţii cu acelaşi i şi j minim, se alege aceea cu k minim.

Exemple:

split.in split.outExplicaţii
11 29 Cele 4 secvenţe sunt:
9 7 3 0 2 1 8 6 0 11 4 479 9 7 3 0 (cost 9 - 0 = 9)
2 1 8 (cost 8 - 1 = 7)
6 0 (cost 6 - 0 = 6)
11 4 (cost 11 - 4 = 7)
O altă soluţie care obţine tot suma maximă 29 este 5 7
9, dar nu are i minim.
Timp maxim de executare/test: 1.0 secunde
Memorie: total 16 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 5 KB

10.3.1 Indicaţii de rezolvare

prof. Dan Pracsiu - Liceul ştefan Procopiu Vaslui

Se construiesc mai ı̂ntâi liniar vectorii maxst, minst, maxdr, mindr, de lungime N :
minsti = valoarea minimă din secvenţa a1..i
maxsti = valoarea maximă din secvenţa a1..i
mindri = valoarea minimă din secvenţa ai..N 
maxdri = valoarea minimă din secvenţa ai..N 
Pentru fiecare j (4 & j & n  4):
a vom căuta poziţia i plecând de la j spre stânga şi determinând la fiecare pas minimul şi
maximul din intervalul i..j (aceste două valori aparţin secvenţei a doua). Evident, prima secvenţă
are costul dat de maxsti  1  minsti  1.
a vom căuta poziţia k plecând de la j  1 dpre dreapta şi determinând la fiecare pas minimul
şi maximul din intervalul j  1..k (aceste două valori aparţin secvenţei a treia). Evident, ultima
secvenţă are costul dat de maxdrk  1  mindrk  1.
a calculează costul celor 4 secvenţe şi actualizează costul maxim al celor 4 secvenţe
2
Complexitatea totală este O N .

10.3.2 Cod sursă

Listing 10.3.1: split.cpp


/*
Complexitate O (N x N)
*/
#include<fstream>

#define inFile "split.in"


#define outFile "split.out"
#define Dim 10005

using namespace std;

int a[Dim], minst[Dim], mindr[Dim], maxdr[Dim], maxst[Dim], n;

int main()
{
int i, j, k, x, di, dk, maxim, minim, xi, xj, xk;
CAPITOLUL 10. ONI 2013 10.3. SPLIT 291

int solst, soldr, solmax;

// citire
ifstream fin(inFile);
fin >> n;
for (i = 1; i <= n; i++)
fin >> a[i];
fin.close();

//min max stg


minst[1] = maxst[1] = a[1];
for (i = 2; i <= n; i++)
{
if (a[i] > maxst[i-1])
maxst[i] = a[i];
else
maxst[i] = maxst[i-1];

if (a[i] < minst[i-1])


minst[i] = a[i];
else
minst[i] = minst[i-1];
}

//min max drp


mindr[n] = maxdr[n] = a[n];
for (i = n - 1; i >= 1; i--)
{
if (a[i] > maxdr[i+1])
maxdr[i] = a[i];
else
maxdr[i] = maxdr[i+1];

if (a[i] < mindr[i+1])


mindr[i] = a[i];
else
mindr[i] = mindr[i+1];
}

// calcul
solmax = 0;
di = dk = 0;
xi = 2; xj = 4; xk = 6;
for (j = 4; j <= n - 3; j++)
{
// maximul din stanga: i=2..j-2
if (a[j] > a[j - 1])
{
maxim = a[j];
minim = a[j - 1];
}
else
{
maxim = a[j - 1];
minim = a[j];
}

solst = maxim - minim + maxst[j-2] - minst[j-2];


di = j - 2;
for (i = j - 2; i > 2; i--)
{
if (a[i] > maxim) maxim = a[i];
if (a[i] < minim) minim = a[i];
x = maxim - minim + maxst[i-1] - minst[i-1];
if (solst <= x)
{
solst = x;
di = i-1;
}
}

//maximul din dreapta k=j+2..n-2


if (a[j+1] > a[j+2])
{
maxim = a[j+1];
minim = a[j+2];
CAPITOLUL 10. ONI 2013 10.4. MOMENTE 292

}
else
{
maxim = a[j+2];
minim = a[j+1];
}

soldr = maxim - minim + maxdr[j+3] - mindr[j+3];


dk = j + 2;
for (k = j + 3; k <= n - 2; k++)
{
if (a[k] > maxim) maxim = a[k];
if (a[k] < minim) minim = a[k];
x = maxim - minim + maxdr[k+1] - mindr[k+1];
if (soldr < x)
{
soldr = x;
dk = k;
}
}

x = solst + soldr;
if (x > solmax)
{
solmax = x;
xi = di;
xj = j;
xk = dk;
}
}

ofstream fout(outFile);
fout << solmax << "\n";
fout << xi << " " << xj << " " << xk << "\n";
fout.close();

return 0;
}

10.3.3 *Rezolvare detaliată

10.4 momente
Problema 4 - momente 100 de puncte
G are un ceas digital care afişează ora printr-o valoare ı̂ntre 0 şi 23 sub forma unui număr de
una sau două cifre, minutul printr-o valoare ı̂ntre 0 şi 59 sub forma unui număr de exact două cifre
(prima cifră este 0 dacă numărul de minute care trebuie afişat este mai mic decât 10) şi secunda
printr-o valoare ı̂ntre 0 şi 59 sub forma unui număr de exact doua cifre (dacă numărul de secunde
care trebuie afişat este mai mic decât 10, atunci prima cifră este 0). Aceste informaţii apar ı̂n
ordinea: numărul de ore, numărul de minute, numărul de secunde şi sunt separate prin câte un
spaţiu. Exemple: 23 39 17 (pentru ora 23, 39 minute şi 17 secunde) , 1 00 01 (pentru ora 1, 0
minute şi o secundă) sau 0 02 02 (pentru ora 0, 2 minute şi 2 secunde).
G observă că dacă alătură aceste trei valori poate construi un număr natural. Asfel, pentru
exemplele de mai sus obţine numerele 233917, 10001 şi respectiv 202 (Atenţie! Numărul rezultat
nu ı̂ncepe cu 0 - eventualele cifre nule aflate la ı̂nceputul lui sunt eliminate!). G mai observă că
există momente de timp, când numărul astfel format este un palindrom, cum este cazul celui de-al
doilea şi celui de-al treilea exemplu. G denumeşte aceste momente de timp momente palindromice
şi doreşte să afle câte astfel de momente sunt ı̂ntr-un interval de timp dat.
Un interval de timp este situat pe parcursul anului 2013 fiind precizat prin data şi ora exactă
când ı̂ncepe şi data şi ora exactă când se termină. Data este precizată prin doua numere care
reprezintă luna şi ziua, iar ora exactă sub forma afişată de ceasul digital al lui G.

Cerinţe

Determinaţi câte momente palidromice au loc ı̂n k intervale de timp date.


CAPITOLUL 10. ONI 2013 10.4. MOMENTE 293

Date de intrare

Fişierul de intrare momente.in conţine pe prima linie numărul natural k cu semnificaţia din
enunţ. Pe fiecare dintre următoarele k linii se află câte 10 valori naturale separate prin câte un
spaţiu. Primele cinci numere reprezintă luna, ziua, ora, minutul şi secunda când ı̂ncepe intervalul
de timp dat. Următoarele cinci numere reprezintă luna, ziua, ora, minutul şi secunda când se
termină intervalul de timp dat.

Date de ieşire

Fişierul de ieşire momente.out va conţine k linii. Pe linia i (1 & i & k) se va afla un singur
număr care va reprezenta numărul de momente palindromice din intervalul i.

Restricţii şi precizări

a k & 100 000


a data de ı̂nceput precede data de sfârşit pentru fiecare interval de timp;
a ı̂n anul 2013 luna februarie are 28 zile;
a pentru 50% dintre teste vom avea k 1;
a se numeşte palindrom un număr care citit de la stânga la dreapta sau de la dreapta la stânga
are aceeaşi valoare.
a dacă intervalul de timp considerat ı̂ncepe sau se termină cu un moment palindromic, acesta
este numărat.

Exemple:

momente.in momente.out Explicaţii


1 24 Fişierul de intrare conţine un singur interval de timp, intre
2 28 23 44 32 28 februarie ora 23, 44 minute şi 32 secunde şi 1 martie
3 1 0 02 02 ora 0, 2 minute şi 2 secunde.
In acest interval de timp sunt 24 momente palindomice
dupa cum urmeaza:
- ı̂n data de 28 februarie la orele 23 44 32 şi 23 55 32;
- ı̂n data de 1 martie la orele 0 00 00, 0 00 01, 0 00 02, 0
Timp maxim de executare/test: 1.2 secunde
Memorie: total 16 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 5 KB

10.4.1 Indicaţii de rezolvare

prof. Stelian Ciurea, Universitatea ”Lucian Blaga”, Sibiu

Ideea de plecare in rezolvarea problemei este sa determinam cate palindromuri sunt intr-o zi
”intreaga” (de la ora 0 la 23:59:59). Putem determina acest numar prin simulare directa, adica
testand daca fiecare moment din acest interval este sau nu palindrom. Astfel determinam ca sunt
699 palindromuri ı̂ntr-o zi.
Obtinerea raspunsului pentru un singur interval necesită să determinăm:

1 câte zile ”ı̂ntregi” sunt ı̂n intervalul dat (aceasta putem să o facem prin diverse metode -
chiar printr-o metodă ”brută”);

2 câte momente palindromice sunt ı̂n prima zi a intervalului dat;


3 câte momente palindromice sunt ı̂n ultima zi intervalului dat.

Pentru 2 şi 3 putem folosi:

- un tablou precalculat ı̂n care memorăm numărul de palindromuri pentru fiecare secundă de
pe parcursul unei zile =¿ complexitate constantă pentru a determina acest rezultat şi deci
complexitate O k  pentru ı̂ntrega soluţie (100 de puncte);
CAPITOLUL 10. ONI 2013 10.4. MOMENTE 294

- un tabou precalculat ı̂n care reţinem momentele de timp care sunt palindromice şi aplicăm o
căutare binară ı̂n acest tablou =¿ complexitate O k ˜ log 700 pentru ı̂ntrega soluţie (100
de puncte);
- metoda brută: determinarea numerelor afişate de ceas din secundă ı̂n secundă şi numărarea
celor care sunt palindromice (obţinând cel mult 50 puncte)

Soluţiile bazate pe simularea brută (parcurgerea fiecărui interval din secundă ı̂n secundă) obţin
cel mult 20 puncte.

10.4.2 Cod sursă

Listing 10.4.1: momente.pas


1 { momente palindromice varianta 2: k intervale;
2 solutie "oficiala-oficiala": pentru fiecare interval
3 determina cate zile complete sunt cu 699 momente /zi
4 precalculeaza numarul de palindroame in fiecare moment al zilei
5 apoi determina cate momente sunt in prima zi de la momentul initial
6 pana la 23:59:59 si in ultima de la 0:00:00 pana la momentul final
7 }
8
9 const zifin : array[1..12] of integer = (31,28,31,30,31,30,31,31,30,31,30,31);
10
11 var ora1,min1,sec1,luna1,zi1,ora2,min2,sec2,luna2,zi2 : longint;
12 zi,luna,ora,min,sec : longint;
13 st,dr,mj,i,k,nr,ctz,ct1,ct2,ct3 : longint;
14 fi,fo : text;
15 mom : array[-1..90000] of longint;
16
17
18 function inv(nr : longint) : longint;
19 begin
20 inv := 0;
21 while nr>0 do
22 begin
23 inv := inv*10+nr mod 10;
24 nr := nr div 10
25 end;
26 end;
27
28
29 procedure precalc;
30 var ora,min,sec,i:longint;
31
32 begin
33 ora := 0;
34 min := 0;
35 sec := 1;
36
37 for i := 1 to 24*3600-1 do
38 begin
39
40 nr := ora*10000 + min*100 + sec;
41 if inv(nr) = nr then
42 mom[i] := mom[i-1]+1
43 else
44 mom[i] := mom[i-1];
45
46 inc(sec);
47 if sec=60 then
48 begin
49 sec := 0;
50 inc(min)
51 end;
52 if min=60 then
53 begin
54 min := 0;
55 inc(ora);
56 end;
57 end;
58 end;
CAPITOLUL 10. ONI 2013 10.5. SECVENTE 295

59
60
61 begin
62
63 mom[0] := 1;
64
65 precalc;
66
67 assign(fo,’momente.out’);
68 assign(fi,’momente.in’);
69 rewrite(fo);
70 reset(fi);
71 readln(fi,k);
72
73 for i := 1 to k do
74
75 begin
76
77 ct3 := 0; ct2 := 0; ct1 := 0; ctz := 0;
78
79
80 read(fi,luna1,zi1,ora1,min1,sec1);
81 read(fi,luna2,zi2,ora2,min2,sec2);
82
83
84 if ora1+min1+sec1=0 then
85 ctz:=1;
86
87 luna := luna1;
88 zi := zi1;
89 while not ((zi=zi2) and (luna=luna2)) do
90 begin
91 inc(zi);
92 if zifin[luna]+1=zi then
93 begin
94 zi := 1;
95 inc(luna)
96 end;
97 inc(ctz);
98 end;
99 dec(ctz);
100
101
102 ct2 := 699*ctz; //zile complete*699 /zi
103
104 nr := ora1*3600 + min1*60 + sec1;
105
106 if nr>0 then
107 ct1 := 699 - mom[nr-1]
108 else
109 ct1 := 0;
110
111 nr := ora2*3600 + min2*60 + sec2;
112
113 ct3 := mom[nr];
114
115 writeln(fo,ct1+ct2+ct3);
116
117 end;
118
119 close(fo);
120 end.

10.4.3 *Rezolvare detaliată

10.5 secvente
Problema 5 - secvente 100 de puncte
Considerăm şirul de numere naturale nenule distincte a1 , a2 , ..., aN . Notăm cu Li lungimea
maximă a unei secvenţe de elemente cu valori consecutive care se poate obţine prin ordonarea
CAPITOLUL 10. ONI 2013 10.5. SECVENTE 296

crescătoare a primelor i elemente din şirul dat. De exemplu, pentru şirul 7, 2, 3, 8, 20, 4, 10, 9
avem:
L1 1, L2 1, L3 2, L4 2, L5 2, L6 3, L7 3, L8 4.

Cerinţe

Să se determine L1 , L2 , ..., LN .

Date de intrare

Fişierul secvente.in conţine pe prima linie numărul natural N . Pe fiecare din următoarele N
linii se găseşte câte un număr natural, deci pe linia i  1 se va afla elementul ai , pentru i 1...N .

Date de ieşire

Fişierul secvente.out conţine exact N linii. Pe linia i (i 1...N ) se va afişa valoarea Li .

Restricţii şi precizări

a 3 & N & 200 000 a 1 & ai & 1 000 000, pentru orice i 1...N a Pentru 35% din teste se
garantează că N & 1000

Exemple:

secvente.in secvente.out
Explicaţii
8 1 L 1. şirul : 7. Lungime maximă 1
7 1 L2. şirul: 7,3. Lungime maximă 1
3 2 L 3. şirul: 7,3,2. şirul sortat este 2,3,7. Lungimea maximă este
2 2 2 (dată de secvenţa 2,3)
8 2 L 4. şirul: 7,3,2,8. Lungime maximă 2 (dată de 2,3)
20 3 L5. şirul: 7,3,2,8,20. Lungime maximă 2 (dată de 2,3).
4 3 L 6. şirul: 7,3,2,8,20,4. şirul sortat este 2,3,4,7,8,20. Lungimea
10 4 maximă este 3 (dată de secvenţa 2,3,4).
9 L 7. şirul: 7,3,2,8,20,4,10. Lungime maximă 3 (dată de 2,3,4).
L 8. şirul: 7, 3, 2, 8, 20, 4, 10, 9.
şirul sortat este 2,3,4,7,8,9,10,20. Lungimea maximă este 4 (dată
de secvenţa 7,8,9,10).
Timp maxim de executare/test: 0.2 secunde
Memorie: total 16 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 5 KB

10.5.1 Indicaţii de rezolvare

stud. Cosmin Mihai Tutunaru, Universitatea Babeş Bolyai Cluj

Soluţie 35 pct
O primă idee ar fi să avem un vector de vizite pentru a şti la fiecare pas dacă un element se
află ı̂n secvenţa curentă (care ı̂ncepe pe poziţia 1). Un algoritm de rezolvare este următorul:

ˆ best = 0, lungimea secvenţei de valori consecutive pentru secvenţa vidă

ˆ pentru fiecare i de la 1 la N , unde i este capătul drept al secvenţei

` vz v i = true;
` crt = numărul de valori de 1 consecutive ı̂n vectorul vz ı̂n jurul poziţiei v i
` dacă crt % best
t best crt
` scrie best
CAPITOLUL 10. ONI 2013 10.5. SECVENTE 297

Complexitate: O(N*N)
Soluţie 100 pct
Pornind de la ideea anterioară, putem renunţa la vz şi să-l ı̂nlocuim cu pos, unde posx este:

ˆ 0, dacă elementul x nu se află ı̂n secvenţa curentă


ˆ poziţia unde se termină secvenţa, dacă x este primul element din secvenţă
ˆ poziţia unde ı̂ncepe secvenţa, dacă x este ultimul element din secvenţă

Pentru valorile x care aparţin unei secvenţe de valori consecutive dar nu se află pe marginile
secvenţei, se observă că posx este irelevant.
Noul algoritm de rezolvare este:

ˆ best = 0
ˆ pentru fiecare i de la 1 la N
` begin v i, poziţia unde ı̂ncepe secvenţa singurului element v i
` end v i, poziţia unde se termină secvenţa singurului element v i
` dacă posv i  1! 0, adică elementul v i  1 se află deja ı̂ntr-o secvenţă
t begin posv i  1, actualizăm ı̂nceputul secvenţei ı̂n care se află elementul v i
` dacă posv i  1! 0, adică elementul v i  1 se află deja ı̂ntr-o secvenţă
t end posv i  1, actualizăm sfârşitul secvenţei ı̂n care se află elementul v i
` deci elementul v i se află ı̂n interiorul secvenţei begin, end. Cum poziţiile de ı̂nceput
şi de sfârşit al elementelor din interiorul intervalului sunt irelevante, actualizăm pos
doar pentru elementele din capăt:
t posbegin end
t posend begin
` dacă end  begin  1 % best:
t best end  begin  1
` scrie best

Complexitate: O N  V M AX , unde V M AX este valoarea maximă din şir

10.5.2 Cod sursă

Listing 10.5.1: secvente.cpp


#define TuTTy "Cosmin-Mihai Tutunaru"
#include <cstdio>
#include <cassert>
#include <vector>
#include <algorithm>

#define infile "secvente.in"


#define outfile "secvente.out"
#define nMax 200013
#define vMax 1000013

using namespace std;

bool vz[vMax];

int v[nMax];
int pos[vMax];
int n;

void read()
{

scanf("%d\n", &n);
CAPITOLUL 10. ONI 2013 10.6. SPIDER 298

for (int i = 0; i < n; ++i)


{
scanf("%d", &v[i+1]);

assert(vz[v[i+1]] == false);
vz[v[i+1]] = true;
}
}

void solve()
{

int best = 0;

for (int i = 1; i <= n; ++i)


{
int begin = v[i], end = v[i];

if (pos[v[i]-1]) begin = pos[v[i]-1];


if (pos[v[i]+1]) end = pos[v[i]+1];

pos[begin] = end;
pos[end] = begin;

best = max(best, end - begin + 1);


printf("%d\n", best);
}
}

int main()
{
freopen(infile, "r", stdin);
freopen(outfile, "w", stdout);

read();
solve();

fclose(stdin);
fclose(stdout);
return 0;
}

10.5.3 *Rezolvare detaliată

10.6 spider
Problema 6 - spider 100 de puncte
Omul păianjen (Spiderman) sare de pe o clădire pe alta, aflată ı̂n imediata vecinătate, ı̂n nord,
est, sud sau vest. Clădirile din cartierul omului păianjen au o ı̂nălţime exprimată ı̂n numere
naturale şi sunt aşezate pe m rânduri, câte n pe fiecare rând.
Spiderman va alege să sară pe una dintre clădirile vecine, care are ı̂nălţimea mai mică sau egală,
iar diferenţa de ı̂nălţime este minimă. Dacă există mai multe clădiri vecine de aceeaşi ı̂nălţime,
omul păianjen aplică ordinea preferenţială nord, est, sud, vest, dar nu sare ı̂ncă o dată pe o clădire
pe care a mai sărit. Scopul omului păianjen este acela de a reuşi să facă un număr maxim de
sărituri succesive.
Cerinţe
Scrieţi un program care determină numărul maxim de sărituri succesive, pe care ı̂l poate
efectua, pornind de la oricare dintre clădiri, precum şi poziţiile clădirilor care formează drumul
maxim.
Date de intrare
Fişierul spider.in conţine, pe prima linie, două numere naturale, m şi n, despărţite printr-un
spaţiu. Fiecare dintre următoarele m rânduri conţine n numere naturale, separate prin câte un
spaţiu, reprezentând ı̂nălţimile clădirilor.
CAPITOLUL 10. ONI 2013 10.6. SPIDER 299

Date de ieşire

Fişierul de ieşire spider.out va conţine, pe prima linie, un singur număr natural k,


reprezentând numărul maxim de sărituri. Următoarele k linii vor conţine câte două numere natu-
rale, separate printr-un spaţiu, reprezentând coordonatele clădirilor care formează drumul maxim,
ı̂n ordinea parcurgerii. Dacă există mai multe soluţii, ı̂n fişierul de ieşire se va scrie drumul care
porneşte de la poziţia i, j  cu i minim, iar dacă există mai multe soluţii cu acelaşi i minim, se va
scrie ı̂n fişier soluţia cu i şi j de valoare minimă.

Restricţii şi precizări

a 0 $ m, n & 1000
a ı̂nălţimile clădirilor sunt numere naturale din intervalul [1,10000000]
a ı̂n orice zonă pătratică de 2x2 clădiri vecine există cel mult 2 clădiri de aceeaşi ı̂nălţime.

Exemple:

spider.in spider.out Explicaţii


55 8 Spiderman porneşte de pe blocul de 90 de metri aflat ı̂n
35 38 42 40
50 54 poziţia (5, 4), face 8 sărituri şi ajunge ı̂n poziţia (1, 4), de
34 38 30 75
50 53 unde nu mai are posibilităţi de a sări.
70 78 88 86
30 43
39 90 88 23
25 33
35 80 89 90
34 34
24
25
15
14
Timp maxim de executare/test: 3.0 secunde
Memorie: total 64 MB din care pentru stivă 8 MB
Dimensiune maximă a sursei: 5 KB

10.6.1 Indicaţii de rezolvare

Budai István, Liceul Teoretic ”Nagy Mózes”

Soluţie 35 pct
Se definesc două matrice: A si B cu maxim 1 000 de rânduri şi maxim 1 000 de coloane,
precum şi două tablouri, v şi w, având cel mult 1 000 000 de elemente. ı̂n matricea A se citesc
valorile ı̂nălţimilor clădirilor (numere naturale din intervalul [1, 10 000 000]. Se construieşte câte
unui drum, pornind de la toate elementele matricei A, şi se reţine drumul de lungime maximă.
Se utilizează o funcţie care primeşte, prin intermediul parametrului pas, numărul de ordine al
drumului care se construieşte. Funcţia ı̂ntoarce ı̂nălţimea clădirii cu diferenţa de ı̂nălţime minimă,
şi care respectă condiţiile din enunţ. Funcţia verifică ı̂n matricea B dacă clădirea de coordonate
i, j  a mai fost - sau nu - vizitată de Spiderman: dacă B ij  pas, atunci clădirea de coordonate
i, j  a fost deja vizitată pe parcursul drumului cu numărul de ordine pas, deci clădirea nu este
eligibilă. Dacă B ij  este diferit de pas, atunci se alege clădirea respectivă şi lui B ij  i se
atribuie valoarea pas.
Se utilizează un subprogram care primeşte, prin parametrii de intrare r şi c, coordonatele unei
clădiri şi construieşte drumul care poate fi parcurs pornind de la aceea clădire. Pentru selectarea
unei clădiri se utilizează funcţia descrisă mai sus. Dacă drumul cu numărul de ordine actual
este mai lung decât precedentul drum, se va reţine lungimea drumului ı̂n variabila k, precum şi
elementele vectorului de direcţii v ı̂n vectorul w, care va avea lungimea k şi va reţine direcţiile
corespunzătoare drumului maxim. Elementele vectorului v se definesc astfel:
v i 1, dacă direcţia aleasă pentru săritura i este Nord;
v i 2, dacă direcţia aleasă pentru săritura i este Est;
v i 3, dacă direcţia aleasă pentru săritura i este Sud;
v i 4, dacă direcţia aleasă pentru săritura i este Vest.
Programul principal va apela acest subprogram pentru toate elementele matricei A, pornind
de la elementul de coordonate 1, 1, parcurgând matricea de la linia 1 la linia m, şi elementele
CAPITOLUL 10. ONI 2013 10.6. SPIDER 300

pe linii, de la 1 la n. Datele unui drum se vor reţine doar dacă lungimea sa este strict mai mare
decât cea mai mare lungime obţinută până ı̂n acel moment. La scrierea datelor ı̂n fişierul de ieşire
(k şi cooronatele clădirilor care formează drumul maxim) se va porni de la coordonatele primei
clădiri, iar coordonatele clădirilor care formează drumul maxim se vor determina prin interpretarea
corectă a elementelor vectorului w.
Complexitate: O N ˜ N ˜ M ˜ M 
Soluţie 100 pct
Pornind de la soluţia anterioară, observăm că, dacă nu am avea două valori identice pe poziţii
alăturate, nu are sens să calculăm din fiecare poziţie de ı̂nceput care este lungimea maximă,
deoarece putem stoca ı̂ntr-o matrice auxiliară bestij  lungimea maximă a unei secvenţe care
ı̂ncepe pe poziţia i, j , iar atunci când determinăm un drum să actualizăm toate aceste costuri
ale poziţiilor prin care trece.
Cum putem avea două valori vecine cu aceeaşi valoare, acest tablou best trebuie schimbat puţin
ı̂n bestxy d având semnificaţia: lungimea maximă a unei secvenţe care ı̂ncepe pe poziţia x, y 
care nu face prima săritură ı̂n direcţia d (d poate avea valori de la 0 la 3, fiecare valoare semnificând
una din cele 4 direcţii posibile).
Complexitate: O N ˜ M 

10.6.2 Cod sursă

Listing 10.6.1: spider.cpp


#define TuTTy "Cosmin-Mihai Tutunaru"
#include <cstdio>
#include <vector>
#include <algorithm>

#define infile "spider.in"


#define outfile "spider.out"
#define nMax 1013

using namespace std;

#define point pair <int, int>

point outside = make_pair(-1, -1);

const int ii[] = {-1, 0, 1, 0};


const int jj[] = {0, 1, 0, -1};

int best[nMax][nMax][5];
int h[nMax][nMax];
int n, m;

point sol;
int steps;

inline bool valid(point p)


{
if (p.first < 1 || p.first > n) return false;
if (p.second < 1 || p.second > m) return false;
return true;
}

inline int getHeight(point p)


{

if (p == outside)
{
return -1;
}

return h[p.first][p.second];
}

pair<point, int> getNextPosition(point crt, point prv)


{
CAPITOLUL 10. ONI 2013 10.6. SPIDER 301

point ret = outside;


int dir = -1;

for (int t = 0; t < 4; ++t)


{
point nxt = make_pair(crt.first + ii[t], crt.second + jj[t]);
if (nxt != prv && valid(nxt) &&
getHeight(ret) < getHeight(nxt) &&
getHeight(nxt) <= getHeight(crt))
{
ret = nxt;
dir = t;
}
}

return make_pair(ret, dir);


}

void read()
{

scanf("%d %d\n", &n, &m);

for (int i = 0; i < n; ++i)


{
for (int j = 0; j < m; ++j)
{
scanf("%d", &h[i+1][j+1]);
}
}
}

int solve(int x, int y, int d)


{

if (best[x][y][d])
{
return best[x][y][d];
}

best[x][y][d] = 1;
point prv;

if (d == 4)
{
prv = outside;
}
else
{
prv = make_pair(x + ii[d], y + jj[d]);
}

pair<point, int> nxt = getNextPosition(make_pair(x, y), prv);

if (nxt.first != outside)
{
best[x][y][d] +=
solve(nxt.first.first, nxt.first.second, (nxt.second + 2) % 4);
}

return best[x][y][d];
}

void solve()
{

for (int i = 0; i < n; ++i)


{
for (int j = 0; j < m; ++j)
{
int dist = solve(i+1, j+1, 4);
if (dist > steps)
{
steps = dist;
sol = make_pair(i+1, j+1);
}
CAPITOLUL 10. ONI 2013 10.6. SPIDER 302

}
}

void write()
{

printf("%d\n", steps - 1);

point prv = outside;

while(sol != outside)
{
printf("%d %d\n", sol.first, sol.second);
point aux = getNextPosition(sol, prv).first;

prv = sol;
sol = aux;
}
}

int main()
{
freopen(infile, "r", stdin);
freopen(outfile, "w", stdout);

read();
solve();
write();

fclose(stdin);
fclose(stdout);
return 0;
}

10.6.3 *Rezolvare detaliată


Capitolul 11

ONI 2012

11.1 7segmente
Problema 1 - 7segmente 100 de puncte
Un indicator cu 7 segmente este un dispozitiv de afişaj electronic destinat
afişării unei cifre zecimale. Aceste dispozitive sunt utilizate pe scară largă ı̂n
ceasuri digitale, contoare electronice şi alte aparate, pentru afişarea informaţiilor
numerice. Cele 7 segmente au fost notate cu literele a, b, c, d, e, f, g, după
modelul din figura alăturată. Afişarea uneia din cifrele de la 1 la 9 constă ı̂n
aprinderea anumitor segmente din cele 7, după cum urmează:
Cifră 1 2 3 4 5 6 7 8 9
Segmente b, c a, b, a, b, c, b, c, f, a, c, d, a, c, d, a, b, c a, b, c, a, b, c,
aprinse d, e, g d, g g f, g e, f, g d, e, f, g d, f, g
Proiectarea diverselor sisteme de afişaj trebuie să ţină cont şi de puterea necesară pentru
afişarea unei cifre. Pentru aprinderea unui segment este necesară o putere de 1 mW. Astfel, ı̂n
funcţie de cifra afişată, dispozitivul necesită o putere egală cu numărul de segmente aprinse la
afişarea cifrei respective. Puterea necesară pentru afişarea unui număr natural este egală cu suma
puterilor necesare afişării fiecăreia dintre cifrele sale.

Cerinţe

Să se scrie un program care citeşte două numere naturale nenule n şi p, (numărul n având
toate cifrele nenule) şi calculează:
a numărul natural k reprezentând puterea necesară pentru afişarea numărului n;
a cel mai mare număr natural t, format numai din cifre nenule, mai mic sau egal decât n, care
necesită pentru afişare o putere de cel mult p mW .

Date de intrare

Prima linie a fişierului de intrare 7segmente.in conţine două numere naturale nenule n şi p
(numărul n având toate cifrele nenule), despărţite printr-un spaţiu, cu semnificaţia de mai sus.

Date de ieşire

Fişierul de ieşire 7segmente.out va conţine pe o singură linie, cele două numere naturale
nenule k şi t (numărul t având toate cifrele nenule), separate printr-un spaţiu, cu semnificaţia de
mai sus.

Restricţii şi precizări

1 & n $ 10 ;
19
a
a 2 & p & 150;
a pentru rezolvarea primei cerinţe se va acorda 20% din punctaj, iar pentru rezolvarea celei
de-a doua cerinţe se va acorda 80% din punctaj.

Exemple:

303
CAPITOLUL 11. ONI 2012 11.1. 7SEGMENTE 304

7segmente.in 7segmente.outExplicaţii
7654 12 18 7511 Numărul n este 7654; puterea necesară pentru afişare este
3+6+5+4=18 mW, iar cel mai mare număr, mai mic sau egal cu
7654, format numai din cifre nenule, care necesită pentru afişare
o putere de cel mult 12 mW, este 7511.
Timp maxim de executare/test: 0.1 secunde
Memorie: total 2 MB din care pentru stivă 2 MB
Dimensiune maximă a sursei: 5 KB

11.1.1 Indicaţii de rezolvare

prof. Cheşcă Ciprian - Grup şcolar ”Costin Neniţescu” Buzău

Numărul de dispozitive de tip 7 segmente necesare pentru afişarea numărului n este egal cu
numărul de cifre al lui n. Puterea necesară pentru afişarea numărului n se calculează ca suma
puterilor necesare pentru afişarea fiecărei cifre ı̂n parte, ı̂n funcţie de numărul de segmente utilizat
la afişarea fiecărei cifrei, conform tabelului.
În vederea determinării numărului t se calculează pentru ı̂nceput numărul de cifre pe care-l
poate avea acesta. Având ı̂n vedere că cifra ce consumă cea mai mică putere este 1 (2 mW) şi nu
sunt utilizate cifre de 0, determinăm numărul de cifre al lui t ca fiind min(numărul de cifre al lui
n, p/2).
Separăm cifrele lui n ı̂ntr-un vector şi iniţializăm un alt vector, care are numărul de poziţii
egal cu numărul de cifre al lui t, cu 1. Din puterea dată scădem puterea utilizată pentru cifrele
de 1 şi apoi cât timp mai este putere de utilizat (putere disponibilă) şi cifre de aflat, determinăm
secvenţial cifrele lui t.
Se porneşte de la vectorul care are pe toate poziţiile cifra 1 şi se actualizează poziţiile acestuia
de la stânga la dreapta (de la cifra cea mai semnificativă către cifra cea mai puţin semnificativă) cu
cifra ce ţine cont de datele problemei până când puterea disponibilă devine nulă sau se completează
numărul de cifre.
Pentru determinarea unei cifre se ţine cont că aceasta trebuie să fie cifra cea mai mare, mai
mică sau egală cu cifra de pe aceeaşi poziţie a lui n şi a cărei utilizare nu epuizează puterea
disponibilă. La fiecare selectare a unei astfel de cifre se reactualizează puterea rămasă neutilizată.
O ”capcană” care trebuie evitată este situaţia ı̂n care o cifră determinată pe o anumită poziţie
este strict mai mică decât cifra de pe aceeaşi poziţie a lui n, situaţie ı̂n care la determinarea
următoarelor cifre ale lui t nu trebuie să mai ţinem cont de faptul că acestea trebuie să fie mai
mici decât cifrele lui n de pe poziţiile corespunzătoare.

11.1.2 Cod sursă

Listing 11.1.1: PIT 7segmente.c


// sursa 100 p - PIT-RADA VASILE

#include<stdio.h>
#include<string.h>

char s[30];
int p,w,pc,i,prest,j,ok,ok2;
int x[10]={0,2,5,5,4,5,6,3,7,6};

int main()
{
freopen("7segmente.in","r",stdin);
freopen("7segmente.out","w",stdout);

scanf("%s %d",s,&p);
w=strlen(s);

pc=0;
for (i=0;i<w;i++)
pc+=x[s[i]-’0’];
printf("%d ",pc);
CAPITOLUL 11. ONI 2012 11.1. 7SEGMENTE 305

prest=p;
ok=0;
ok2=1;
for (i=0;i<w;i++)
{
for (j=(s[i]-’0’)*ok2+9*(1-ok2);j>0;j--)
if (x[j]+(w-i-1)*2<=prest)
break;

if (j>0)
{
ok=1;
printf("%d",j);
}
if (j==0 && ok==1)
printf("%d",j);
if (j<s[i]-’0’)
ok2=0;
prest-=x[j];
}

fclose(stdin);
fclose(stdout);
return 0;
}

Listing 11.1.2: CC 7segmente.cpp


// sursa 100 puncte - Chesca Ciprian

#include <fstream>
#include <math.h>

using namespace std;

typedef unsigned short cifra;

ifstream f("7segmente.in");
ofstream g("7segmente.out");

// v = vector in care obtin cifrele lui n


// t = vector in care formez numarul t
cifra v[20],t[20],p=0;

// vectorul puterilor necesare la afisarea unei cifre


// (desi este prezenta - nu folosesc cifra 0)
cifra pw[10]={6,2,5,5,4,5,6,3,7,6};

unsigned long long n,cc=1;

int main()
{

cifra pc=0,i=0,nc=0,aux=0,dmin=0,dmax=0,cifra,egalcif=1;

f>>n>>p;

v[0]=0;
i=0;
while (n>0)
{
v[++i]=n%10;
switch (n%10)
{
case 1 : pc=pc+2;break;
case 2 : pc=pc+5;break;
case 3 : pc=pc+5;break;
case 4 : pc=pc+4;break;
case 5 : pc=pc+5;break;
case 6 : pc=pc+6;break;
case 7 : pc=pc+3;break;
case 8 : pc=pc+7;break;
case 9 : pc=pc+6;break;
CAPITOLUL 11. ONI 2012 11.1. 7SEGMENTE 306

n=n/10;
}

v[0]=i;
g<<pc<<" ";

// oglindesc cifrele
for(i=1;i<=v[0]/2;i++)
{
aux=v[i];
v[i]=v[v[0]+1-i];
v[v[0]+1-i]=aux;
}

// calculez numarul de cifre(nc) maxim pe care-l poate avea t


nc=p/2;
if (nc>=v[0]) nc=v[0];

// setez toate cifrele la 1


for(i=1;i<=nc;i++)
t[i]=1;

// din puterea data scad puterea utilizata pentru cifrele de 1


p=p-2*nc;

t[0]=nc;cc=1;
egalcif=1;
while (p>0&&cc<=t[0])
{
dmin=10;cifra=1;dmax=0;

// caut cifra corespunzatoare


for(i=2;i<=9;i++)
{
if (t[0]==v[0]&&egalcif==1)
if (i<=v[cc]&&abs(i-v[cc])<=dmin&&p-pw[i]+pw[1]>=0)
{
cifra=i;
dmin=abs(i-v[cc]);
}

if (t[0]<v[0]||egalcif==0)
if (i>=dmax&&p-pw[i]+pw[1]>=0)
{
cifra=i;
dmax=i;
}
}

// daca am setat o cifra mai mica, restul cifrelor pot fi mai mari
if (v[cc]-cifra!=0)
egalcif=0;

// asez cifra pe pozitia corecta si recalculez puterea


p+=pw[1];
p-=pw[cifra];
t[cc++]=cifra;
}

for(i=1;i<=t[0];i++)
g<<t[i];

g<<"\n";

f.close();
g.close();
return 0;
}

Listing 11.1.3: RH 7segmente.cpp


//sursa 100 p - Robert Hasna
CAPITOLUL 11. ONI 2012 11.1. 7SEGMENTE 307

#include <cstdio>

using namespace std;

static const int NMAX = 20;

int N, P;
int x[] = { 0, 2, 5, 5, 4, 5, 6, 3, 7, 6 };
int v[NMAX];

int cer1;
int rez[NMAX], nrCifre;

int main(int argc, char **argv)


{
freopen("7segmente.in", "r", stdin);
freopen("7segmente.out", "w", stdout);

char c = ’1’;
while (1)
{
scanf("%c", &c);
if (c == ’ ’)
break;
v[N++] = c - ’0’;
}
scanf("%d", &P);

//cerinta 1
for (int i = 0; i < N; cer1 += x[v[i++]]);
printf("%d ", cer1);

//cerinta 2
nrCifre = (P / 2) < N ? (P / 2) : N;

if (nrCifre < N)
{
printf("%c", P & 1 ? ’7’ : ’1’);
for (int i = 0; i < nrCifre - 1; printf("1"), ++i)
;
return 0;
}

P -= N << 1;
bool miss = false;
int i;
for (i = 0; i < N; ++i)
{
P += 2;
int j = miss ? 9 : v[i];
while (x[j] > P)
--j;

rez[i] = j;
P -= x[j];

if (rez[i] != v[i])
miss = true;
}

for (int i = 0; i < N; ++i)


printf("%d", rez[i]);
printf("\n");

return 0;
}

11.1.3 *Rezolvare detaliată


CAPITOLUL 11. ONI 2012 11.2. COPACI 308

11.2 copaci
Problema 2 - copaci 100 de puncte
Se consideră n copaci de diferite ı̂nălţimi, aflaţi ı̂n linie dreaptă la distanţe egale, numerotaţi
de la 1 la n. Pentru fiecare copac se cunoaşte ı̂nălţimea sa Hi . Cum şi copacii simt nevoia să
socializeze, fiecare dintre ei are prieteni printre ceilalţi copaci.
Prietenii oricărui copac i se pot afla atât la stânga, cât şi la dreapta sa. Relaţiile de prietenie
sunt definite ı̂n felul următor: pentru fiecare copac i considerăm un şir d1 , d2 , ..., dx reprezentând
prietenii copacului i situaţi ı̂n dreapta sa şi un şir s1 , s2 , ..., sy reprezentând prietenii copacului
i situaţi ı̂n stânga acestuia. Copacii din cele două şiruri corespunzătoare unui copac i formează
ı̂mpreună lista prietenilor acestuia.
Şirurile corespunzătoare copacului i se definesc astfel:

1. a d1 i  1 (dacă i n, atunci copacul i nu are niciun prieten la dreapta sa, şirul d rămânând
vid);

a pentru fiecare k ' 2, dk este cel mai mic indice (1 & dk & n) cu proprietatea că dk % dk1
şi Hdk % Hdk1 . Dacă dk nu există, atunci lista de prieteni la dreapta ai copacului i s-a
ı̂ncheiat şi construirea şirului se opreşte la acest pas.
2. a s1 i  1 (daca i 1, atunci copacul i nu are niciun prieten la stânga sa, sirul s rămânând
vid);
a pentru fiecare k ' 2, sk este cel mai mare indice (1 & sk & n) cu proprietatea că sk $ sk1
şi Hsk % Hsk1 . Dacă sk nu există, atunci lista de prieteni la stânga ai copacului i s-a
ı̂ncheiat şi construirea şirului se opreşte la acest pas.

De exemplu, ı̂n figura de mai jos sunt reprezentaţi 7 copaci, fiecare având precizată sub el
valoarea ı̂nălţimii sale. Primul copac din stânga are indicele 1, iar ultimul are indicele 7.
Copacul 1 este prieten cu copacul 2 fiind vecini, cu
copacul 5 (deoarece copacul 5 este primul din dreapta lui
2 cu ı̂nălţimea mai mare strict decât ı̂nălţimea lui 2). La
dreapta copacului 5 nu exista niciun copac cu ı̂nălţimea
mai mare strict decât a sa, deci singurii prieteni ai co-
pacului 1 sunt 2 şi 5.
Pentru copacul 3, prietenii la stânga sa sunt copacii 2 şi 1, iar cei de la dreapta sa sunt copacii
4 şi 5. Pentru copacul 6, singurul prieten la stânga este copacul 5, iar la dreapta copacul 7.
Copacul 7 poate avea prieteni doar la stânga, aceştia sunt 6 şi 5 (la stânga copacului 5 nu mai
există niciun copac cu ı̂nălţimea mai mare strict decât 8).
Grădinarul Marian vrea să aleagă 3 copaci diferiţi dintre cei n pentru a-i planta ı̂n altă grădină.
El doreşte ca dintre cei 3 copaci, oricum ar alege A si B, 2 dintre ei, atunci A este prieten cu B şi
B este prieten cu A (relaţiile de prietenie se consideră cele stabilite iniţial). Marian are mai multe
opţiuni şi se ı̂ntreabă ı̂n câte moduri distincte poate alege cei 3 copaci cu proprietatea cerută.

Cerinţe

Determinaţi ı̂n câte moduri se pot alege 3 copaci diferiţi dintre cei n cu proprietatea că, oricum
am alege 2 copaci dintre cei 3, fie aceştia copacul A şi copacul B, atunci A este prieten cu B şi B
este prieten cu A.

Date de intrare

Fişierul de intrare copaci.in conţine pe prima linie un număr natural n, reprezentând numărul
de copaci, iar pe a doua linie n numere naturale nenule, separate prin câte un spaţiu, reprezentând
ı̂nălţimile copacilor.

Date de ieşire

Fişierul de ieşire copaci.out va conţine pe prima linie un număr natural reprezentând numărul
de moduri ı̂n care Marian poate alege 3 copaci cu proprietatea din enunţ.

Restricţii şi precizări


CAPITOLUL 11. ONI 2012 11.2. COPACI 309

a 1 & n & 200.000;


a 1 & Hi & 200;
a nu vor exista 2 copaci alăturaţi cu aceeaşi ı̂nălţime;
a două triplete de copaci se consideră distincte dacă există cel puţin un copac din primul triplet
care nu se află şi ı̂n al doilea triplet;
a pentru 30% din teste, 1 & n & 200.

Exemple:

copaci.in copaci.outExplicaţii
7 4 Copacul 1 este prieten cu copacii: 2, 5
6423858 Copacul 2 este prieten cu copacii: 1, 3, 4, 5
Copacul 3 este prieten cu copacii: 1, 2, 4, 5
Copacul 4 este prieten cu copacii: 1, 2, 3, 5
Copacul 5 este prieten cu copacii: 1, 2, 4, 6, 7
Copacul 6 este prieten cu copacii: 5, 7
Copacul 7 este prieten cu copacii: 5, 6
Modurile in care Marian poate alege cei 3 copaci sunt:
(1, 2, 5), (2, 4, 5), (2, 3, 4), (5, 6, 7).
Timp maxim de executare/test: 0.7 secunde
Memorie: total 16 MB din care pentru stivă 4 MB
Dimensiune maximă a sursei: 10 KB

11.2.1 Indicaţii de rezolvare


Vlad Ionescu

Se observă că dacă am avea un triunghi (A, B, C) cu A ¡ B ¡ C, atunci copacul B trebuie să
aibă ı̂nălţimea minimă dintre cei 3 copaci.
O altă observaţie este că dacă fixăm copacul B ca fiind de ı̂nălţime minimă dintre cei 3 copaci,
atunci există cel mult un triunghi centrat in B.
Astfel trebuie să iterăm de la 1 la N şi fixăm copacul B, iar A şi C le stabilim ca fiind primii
copaci din stânga, respectiv din dreapta copacului B, cu proprietatea că au ı̂nălţimile mai mari sau
egale cu ı̂nălţimea copacului B. Acesta este un posibil triunghi, ı̂nsă trebuie să verificăm concret
dacă A şi C sunt prieteni reciproci. Pentru aceasta folosim 2 vectori ST i şi DRi care semnifică
până unde ı̂n stânga, respectiv ı̂n dreapta, copacul i are ı̂nălţimea maximă. Vectorii se pot calcula
ı̂n O N ˜ Hmax sau, mai eficient, cu o stivă ı̂n complexitate O N . Ambele metode intră ı̂n
timp.
Complexitate finală: O N ˜ Hmax sau O N .

11.2.2 Cod sursă

Listing 11.2.1: PIT copaci.c


#include<stdio.h>

unsigned char h[200005];


int n,i,j,k;
int nr=0;

int main()
{
freopen("copaci.in","r",stdin);
freopen("copaci.out","w",stdout);

scanf("%d",&n);
for (i=1;i<=n;i++) scanf("%d",&h[i]);

for (j=2;j<n;j++)
{
/*caut in stanga primul i cu h[i]>=h[j]*/
i=j-1;
while (0<i && h[i]<h[j])i--;
CAPITOLUL 11. ONI 2012 11.2. COPACI 310

/*caut in dreapta primul k cu h[j]<=h[k]*/


k=j+1;
while (k<=n && h[j]>h[k])k++;
if (i>0 && k<=n &&
h[i]>h[j] &&
h[j]<h[k]) // trebuie sa avem h[i]!=h[j] si h[j]!=h[k],
// putem avea insa h[i]==h[k]
nr++;
}

printf("%d",nr);
fclose(stdout);
fclose(stdin);
return 0;
}

Listing 11.2.2: RH copaci.cpp


#include <cstdio>
#include <stack>
#include <utility>

using namespace std;

static const int NMAX = 200001;

int N, rez;
int h[NMAX], st[NMAX], dr[NMAX];

stack< pair< int, int > > S;

int main(int argc, char **argv)


{
freopen("copaci.in", "r", stdin);
freopen("copaci.out", "w", stdout);

scanf("%d\n", &N);
for (int i = 0; i < N; scanf("%d ", h + i), ++i);

for (int i = 0; i < N; ++i)


{
while (!S.empty() && h[i] > S.top().first)
{
dr[S.top().second] = 1;
S.pop();
}

if (!S.empty() && h[i] == S.top().first)


S.pop();
else
S.push(make_pair(h[i], i));
}

while (!S.empty()) S.pop();

for (int i = N-1; i >=0; --i)


{
while (!S.empty() && h[i] > S.top().first)
{
st[S.top().second] = 1;
S.pop();
}

if (!S.empty() && h[i] == S.top().first)


S.pop();
else
S.push(make_pair(h[i], i));
}

for (int i = 0; i < N; ++i)


rez += st[i] && dr[i] ? 1 : 0;

printf("%d\n", rez);

return 0;
CAPITOLUL 11. ONI 2012 11.2. COPACI 311

Listing 11.2.3: VI copaci.cpp


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;

#define maxn 200010


#define valmax 210

int N, sol;
int ST[maxn], DR[maxn], H[maxn];
int val[valmax];

/** functii calc_st() si calc_dr() se pot implementa eficient


folosind o stiva **/
void calc_st()
{
memset(val, 0, sizeof(val));
int i, j, p;

for(i=1; i<=N; i++)


{
p = 0;
for(j=H[i]; j<valmax; j++)
{
p = max(p, val[j]);
}

ST[i] = p;
val[ H[i] ] = i;
}
}

void calc_dr()
{
memset(val, 0, sizeof(val));
int i, j, p;

for(i=N; i>=1; i--)


{
p = N+1;
for(j=H[i]; j<valmax; j++)
{
if(val[j]) p = min(p, val[j]);
}

DR[i] = p;
val[ H[i] ] = i;
}
}

int main()
{
FILE *f1=fopen("copaci.in", "r"), *f2=fopen("copaci.out", "w");
int i, j, p, q;

fscanf(f1, "%d\n", &N);


for(i=1; i<=N; i++)
{
fscanf(f1, "%d", &H[i]);
}

calc_st();
calc_dr();

for(i=2; i<=N-1; i++)


{
// consideram copacul i ca fiind de inaltime minima
// din triunghiul (A, i, B)
// poate exista cel mult un triunghi centrat in i
CAPITOLUL 11. ONI 2012 11.3. INTERSECŢII 312

p = ST[i], q = DR[i];
if(p == 0 || q == N+1) continue;

//trebuie sa verificam daca p vede pe i, daca q vede pe i


//si daca p si q se vad reciproc
if(ST[i] <= p && DR[i] >= q && DR[p] >= q && ST[q] <= p)
{
//cout<<p<<" "<<i<<" "<<q<<endl;
sol ++;
}
}

fprintf(f2, "%d\n", sol);

fclose(f1); fclose(f2);
return 0;
}

11.2.3 *Rezolvare detaliată

11.3 intersecţii
Problema 3 - intersecţii 100 de puncte
Dreptunghiul ABCD are laturile de lungimi w şi h, numere naturale pare. Acest dreptunghi
este desenat pe o foaie de matematică şi este descompus ı̂n w h pătrate de latură 1.
Vârfurile A, B, C şi D sunt plasate ı̂n colţurile unor pătrate
de latură 1. Se alege un punct P din interiorul dreptunghiului
ABCD, situat ı̂n colţul unui pătrat de latură 1 şi se uneşte prin
segmente de dreaptă cu cele patru colţuri ale dreptunghiului. Un-
ele segmente intersectează pătrate de latură 1 ı̂n exact două puncte
distincte, altele ı̂ntr-un singur punct.
Numim pătrat 2-intersectat, un pătrat de latură 1 intersectat
de un segment ı̂n exact 2 puncte distincte. ı̂n dreptunghiul din figura alăturată, segmentul P A
trece prin 3 pătrate 2-intersectate, segmentul P B trece prin 9 pătrate 2-intersectate, segmentul
P C trece prin 13 pătrate 2-intersectate, iar segmentul P D prin 7.

Cerinţe

Se dau două numere naturale w şi h reprezentând lungimile laturilor dreptunghiului ABCD,
un număr natural n şi n numere naturale x1 , x2 , ..., xn . Punctul P se plasează, pe rând, ı̂n toate
punctele interioare dreptunghiului ABCD care sunt colţuri ale unor pătrate de latură 1. Pentru
fiecare valoare xi (1 & i & n), determinaţi numărul de segmente distincte care trec prin exact
xi pătrate 2-intersectate.

Date de intrare

Fişierul de intrare intersectii.in conţine pe prima linie trei numere naturale w, h (reprezentând
dimensiunile dreptunghiului) şi n. Următoarele n linii conţin câte un număr natural xi cu
semnificaţia de mai sus.

Date de ieşire

Fişierul de ieşire intersectii.out va conţine n linii. Pe fiecare linie i va fi scris numărul de


segmente care trec prin exact xi pătrate 2-intersectate, obţinute după plasarea punctului P ı̂n
fiecare colţ al unui pătrat de latură 1 din interiorul dreptunghiului ABCD.

Restricţii şi precizări

a 2 & w, h & 2 000 numere naturale pare;


a 2 & n & 100 000;
a punctul P se alege doar ı̂n interiorul dreptunghiului;
a pentru 40% din teste 2 & w, n, h & 500.
CAPITOLUL 11. ONI 2012 11.3. INTERSECŢII 313

Exemple:

intersectii.in intersectii.out Explicaţii


46235 12 4 Se pot obţine 12 segmente care trec prin exact 3 pătrate
2-intersectate şi 4 segmente care trec prin exact 3 pătrate
2-intersectate.
Timp maxim de executare/test: 0.2 secunde
Memorie: total 2 MB din care pentru stivă 2 MB
Dimensiune maximă a sursei: 5 KB

11.3.1 Indicaţii de rezolvare

prof. Gheorghe Manolache - Colegiul Naţional de Informatică , Piatra Neamţ

Se observă că dimensiunile dreptunghiului sunt numere pare, dar, nu putem restrânge analiza
problemei la un sfert din dimensiunea dreptunghiului iniţial decât dacă studiem unele cazuri
particulare.
Nu are importanţă cum alegem colţul fix, deci putem studia doar cazul colţului A iar la
final vom multiplica rezultatul cu patru. Putem considera că vârful A, este originea unui sistem
cartezian de coordonate.
Pentru a calcula numărul de intersecţii al unui segment P A, se observă că pentru un punct
P de coordonate x şi y prime intre ele, din acest dreptunghi, avem intersectate exact x  y  1
pătrate de latura 1, iar daca x şi y nu sunt prime ı̂ntre ele, atunci dacă d cmmdc x, y , numărul
de intersecţii al segmentului P A va fi in acest caz x  y  d.
Pentru a reduce complexitatea algoritmului, putem face o optimizare a calculului valorii cmmdc
pentru toate posibilităţile de alegere ale coordonatelor punctului P realizând o variantă 2D a
ciurului lui Eratostene. Vom folosi un vector de vizitare a coordonatelor şi pentru un punct P xy 
nevizitat, cu cmmdc x, y  1, marcăm ca vizitate punctele din dreptunghi de coordonate d ˜ x
şi d ˜ y nevizitate ca având un număr de intersecţii dat de valoarea d ˜ x  y  1. Astfel voi
contoriza toate valorile distincte de intersecţii, având astfel posibilitatea de a răspunde la fiecare
test ı̂n timp 1.
Deci complexitatea algoritmului devine O h ˜ w  n. .

11.3.2 Cod sursă

Listing 11.3.1: PIT1 intersectii.c


#include<stdio.h>

int s[5000],W,H,Q,i,j,x,y,r,k,H12,W12,H2,W2;

int main()
{
freopen("intersectii.in","r",stdin);
freopen("intersectii.out","w",stdout);
scanf("%d%d%d",&W,&H,&Q);

W2=W/2;
H2=H/2;
H12=(H-1)/2;
W12=(W-1)/2;
for (i=1;i<=W12;i++)
for (j=1;j<=H12;j++)
{
x=i;
y=j;
while (y){r=x%y; x=y; y=r;}
s[i+j-x]+=4;
x=W-i;
y=j;
while (y){r=x%y; x=y; y=r;}
s[W-i+j-x]+=4;
x=i;
CAPITOLUL 11. ONI 2012 11.3. INTERSECŢII 314

y=H-j;
while (y){r=x%y; x=y; y=r;}
s[i+H-j-x]+=4;
x=W-i;
y=H-j;
while (y){r=x%y; x=y; y=r;}
s[W-i+H-j-x]+=4;
}

if (W%2==0)
for (j=1;j<=H12;j++)
{
x=W2;
y=j;
while (y){r=x%y; x=y; y=r;}
s[W2+j-x]+=4;

x=W2;
y=H-j;
while (y){r=x%y ;x=y; y=r;}
s[W2+H-j-x]+=4;
}

if (H%2==0)
for (i=1;i<=W12;i++)
{
x=i;
y=H2;
while (y){r=x%y; x=y; y=r;}
s[i+H2-x]+=4;

x=W-i;
y=H2;
while (y){r=x%y; x=y; y=r;}
s[W-i+H2-x]+=4;
}

if (W%2==0 && H%2==0)


{
x=W2;
y=H2;
while (y){r=x%y; x=y; y=r;}
s[W2+H2-x]+=4;;
}

for (i=1;i<=Q;i++)
{
scanf("%d",&k);
printf("%d\n",s[k]);
}

fclose(stdout);
fclose(stdin);
return 0;
}

Listing 11.3.2: PIT2 intersectii.c


#include<stdio.h>

int s[5000],W,H,Q,i,j,x,y,r,k;

int main()
{
freopen("intersectii.in","r",stdin);
freopen("intersectii.out","w",stdout);
scanf("%d%d%d",&W,&H,&Q);

for (i=1;i<=W-1;i++)
{
if (i<W && i<H)s[i]++;
for (j=i+1;j<=H-1;j++)
{
x=i; y=j;
while (y){r=x%y; x=y; y=r;}
CAPITOLUL 11. ONI 2012 11.3. INTERSECŢII 315

s[i+j-x]++;
if(i<H && j<W)
s[i+j-x]++;
}
}

for (i=1;i<=Q;i++)
{
scanf("%d",&k);
printf("%d\n",4*s[k]);
}

fclose(stdout);
fclose(stdin);
return 0;
}

Listing 11.3.3: brut intersectii.cpp


#include<cstdio>
#include<bitset>

using namespace std;

int H,W,Q,i,j,n,I,J,N,cnt[4010],poz,m1,m2,m3,m4;

int euclid(int a, int b)


{
int c;
while(b)
{
c=a%b;
a=b;
b=c;
}
return a;
}

int main()
{
freopen("intersectii.in","r",stdin);
freopen("intersectii.out","w",stdout);
scanf("%d%d%d",&W,&H,&Q);

for(i=1;i<W;++i)
for(j=1;j<H;++j)
{
m1=i+j-euclid(i,j);
m2=i+H-j-euclid(i,H-j);
m3=W-i+H-j-euclid(W-i,H-j);
m4=W-i+j-euclid(W-i,j);
cnt[m1]++;
cnt[m2]++;
cnt[m3]++;
cnt[m4]++;
}

poz=W+H;
//for(i=1;i<=poz;++i) cnt[i]+=cnt[i-1];
for(;Q;Q--)
{
scanf("%d",&i);
if(i>poz)
printf("0\n");
else
printf("%d\n",cnt[i]);
}

return 0;
}

Listing 11.3.4: GM intersectii.cpp


#include<cstdio>
CAPITOLUL 11. ONI 2012 11.3. INTERSECŢII 316

#include<bitset>

using namespace std;

int H,W,NT,i,j,n,I,J,N,c[4110],poz;

bitset<2110> v[2110];

int main()
{
freopen("intersectii.in","r",stdin);
freopen("intersectii.out","w",stdout);
scanf("%d%d%d",&W,&H,&NT);

for(i=1;i<W;i++)
for(j=1;j<H;j++)
if(!v[i][j])
{
n=i+j-1;
for(I=i,J=j,N=n;I<W && J<H;I+=i,J+=j,N+=n)
{
v[I][J]=1;
c[N]++;
if(poz<N) poz=N;
}
}

//for(i=1;i<=poz;++i) c[i]+=c[i-1];
for(;NT;NT--)
{
scanf("%d",&i);
if(i>poz)
printf("0\n");
else
printf("%d\n",4*c[i]);
}

return 0;
}

Listing 11.3.5: VI intersectii.cpp


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;

#define maxn 2048

int W, H, T, N;
int res[maxn], viz[maxn], d[maxn], a[maxn];

int main()
{
FILE *f1=fopen("intersectii.in", "r"), *f2=fopen("intersectii.out", "w");
int i, j, p, q, k;

fscanf(f1, "%d %d %d\n", &W, &H, &T);

N = max(W, H) + 1;

for(i=1; i<=N; i++)


d[i] = 1;

for(i=2; i<=N; i++)


{
if(!viz[i])
{
for(j=i; j<=N; j+=i)
{
viz[j] = 1;
d[j] = i;
}
CAPITOLUL 11. ONI 2012 11.4. PALINDROM 317

}
}

for(i=1; i<H; i++)


{
memset(a, 0, sizeof(a));

for(j=1; j<W; j++)


{
if(d[j] == 1 || d[i] == 1)
a[j] = 1;

p = j / d[j];
q = i / a[p];

if(q % d[j] == 0)
a[j] = d[j] * a[p];
else
a[j] = a[p];

if(i == 1)
{
p = j;
res[p] += 4;
}
else
if(j == 1)
{
p = i;
res[p] += 4;
}
else
if(i == j)
{
p = i;
res[p] += 4;
}
else
{
p = i + j - a[j];
res[p] += 4;
}
}
}

while(T--)
{
fscanf(f1, "%d\n", &p);
fprintf(f2, "%d\n", res[p]);
}

fclose(f1);
fclose(f2);
return 0;
}

11.3.3 *Rezolvare detaliată

11.4 palindrom
Problema 4 - palindrom 100 de puncte
Cu mult timp ı̂n urmă, ı̂ntr-un tărâm foarte, foarte ı̂ndepărtat, a existat o ţară numită Tnamap.
Locuitorii acestei ţări puteau să aplice instantaneu transformări asupra cifrelor unui număr, uti-
lizând un tablou de corespondenţe T.
O cifră c a unui număr poate fi ı̂nlocuită cu
cifra corespunzătoare ei, T c.
Dalv şi Sogard, doi indivizi speciali ai acestei societăţi ciudate se aflau ı̂n drum spre INO când
au conştientizat că pot transforma instantaneu, folosind număr minim de transformări de cifre,
CAPITOLUL 11. ONI 2012 11.4. PALINDROM 318

orice număr N ı̂ntr-un palindrom divizibil cu un număr natural K. Dacă sunt mai multe astfel
de numere, ı̂l determină pe cel mai mare.
Voi puteţi?

Cerinţe

Cunoscând valorile T0 , T1 , ..., T9 , numărul ce urmează a fi transformat N şi numărul K


(divizorul palindromului), determinaţi:
1. Numărul maxim care se poate obţine aplicând transformări succesive numărului N dat.
2. Cel mai mare dintre palindromurile divizibile cu K, ce se pot obţine din numărul N ,
efectuând un număr minim de transformări asupra cifrelor numărului dat, respectiv asupra cifrelor
numerelor obţinute pe parcurs.

Date de intrare

Pe prima linie a fişierului palindrom.in sunt memorate 10 cifre distincte, separate prin câte
un spaţiu, reprezentând valorile T0 , T1 , ..., T9 .
Pe a doua linie sunt memorate cifrele numărului N , iar pe cea de a treia linie un numărul
natural K.

Date de ieşire

Fişierul palindrom.out va conţine pe prima linie numărul maxim care se poate obţine aplicând
transformări succesive numărului N , iar pe a doua linie palindromul divizibil cu K, de valoare
maximă, ce se poate obţine din numărul N , efectuând un număr minim de transformări asupra
cifrelor.

Restricţii şi precizări

1 & N $ 10
1.000.000
a ;
a N are un număr par de cifre;
a 2 & K & 20;
a se garantează faptul că toate testele au soluţie;
a pentru rezolvarea primei cerinţe se va acorda 20% din punctaj, iar pentru rezolvarea celei
de-a doua cerinţe se va acorda 80% din punctaj.

Exemple:

palindrom.in palindrom.out Explicaţii


0465127893 4994
1234 4224 1234 4 234 4 634 4 734 4 834 4 934 49
3 54 49 24 49 64 49 74 49 84 499 4
Numărul N trece prin următoarele stări ı̂nainte de a de-
veni palindrom cu valoarea maximă, divizibil cu 3: 1234
42 34 42 54 422 4.
Timp maxim de executare/test: 0.4 secunde
Memorie: total 256 MB din care pentru stivă 64 MB
Dimensiune maximă a sursei: 10 KB

11.4.1 Indicaţii de rezolvare

student Robert Hasna


student Vlad Ionescu
student Cosmin Tutunaru
student Vlad Duţă
student Dragoş Oprică
student Alexandru Cazacu
CAPITOLUL 11. ONI 2012 11.4. PALINDROM 319

Se precalculează elementele matricei ajunge, după relaţia


ajungeij  = numărul minim de transformări ce pot fi efectuate pornind de la cifra i pentru
a obtine cifra j.
Având aceasta matrice calculată, primul subpunct este trivial, rămâne de văzut pentru fiecare
pereche de cifre din şirul iniţial i, N  i  1, cu i de la 1 la N ©2 (unde N = lungimea şirului)
care este cea mai mare cifră care se poate obţine prin transformări repetate, atât din Ai, cât şi
din AN  i  1 (am notat cu A şirul iniţial de lungime N ).
Pentru al doilea subpunct vom folosi o recurenţă ı̂ncepând de la jumătatea şirului spre ı̂nceput,
Dij  = numărul minim de aplicări pentru care şirul cuprins intre poziţiile i şi N  i  1 este
palindrom şi dă restul j la ı̂mpărţirea prin K. Dacă pe poziţiile i, N  i  1 punem cifra q, atunci
restul obţinut la pasul anterior S devine S  q ˜ 10 N  i  10 i  1%K. Astfel se deduce
relaţia de recurenţă:
if(ajunge[ A[i] ][p] >= 0 && ajunge[ A[N-i+1] ][p] >= 0) {
//se poate ajunge la cifra p
//vechiul rest este j
int nou_rest = (j + p * (put_zece[N-i] + put_zece[i-1])) % K;
D[i][nou_rest] = min(D[i][nou_rest], D[i+1][j] + ajunge[cif1][p]
+ ajunge[cif2][p]);
}

unde i ia valori de la N ©2 la 1, j ia valori de la 1 la K şi p ia valori de la 1 la 9. Astfel vom avea


ı̂n D10 numărul minim de aplicări.
Pentru a reconstitui palindromul maxim divizibil cu K, repetăm procedeul plecând de la
ı̂nceput spre mijloc (pentru a ne asigura că se obţine cel mai mare palindrom), reţinând poziţia
pe care ne aflăm şi restul corespunzător secvenţei de palindrom rămase.
Complexitatea algoritmului: O N ˜ K .

11.4.2 Cod sursă

Listing 11.4.1: CTpalindrom.cpp


#define TuTTy "Cosmin-Mihai Tutunaru"
#include<cstdio>
#include<cstring>
#include<algorithm>

#define infile "palindrom.in"


#define outfile "palindrom.out"
#define nMax 1000013
#define kMax 23
#define sigma 10
#define inf (nMax * kMax)

using namespace std;

int p[sigma];
char s[nMax];

int co[sigma][sigma];
int dp[nMax][kMax];

char chr[nMax][kMax];
char prv[nMax][kMax];

char sol[nMax];

int t[nMax];
int n, k;

void read()
{
for(int i = 0; i < sigma; ++i)
{
scanf("%d", &p[i]);
}
CAPITOLUL 11. ONI 2012 11.4. PALINDROM 320

scanf("\n");
scanf("%s\n", s + 1);
scanf("%d", &k);
}

void init()
{
n = strlen(s + 1);

t[0] = 1;
for(int i = 1; i <= n; ++i)
{
t[i] = (t[i-1] * 10) % k;
s[i] -= ’0’;
}

for(int i = 0; i < sigma; ++i)


{
for(int j = 0; j < sigma; ++j)
co[i][j] = inf;

int crt = i, cost = 0;


while(cost < co[i][crt])
{
co[i][crt] = cost++;
crt = p[crt];
}
}

for(int i = 1; i < k; ++i)


dp[(n>>1) + 1][i] = inf;
}

void solve()
{
for(int i = (n>>1); i >= 1; --i)
{
int first = i;
int last = n - i + 1;

for(int j = 0; j < k; ++j)


{
dp[i][j] = inf;

for(int d = sigma - 1; d >= 0 + (i == 1); --d)


{
int cost = co[s[first]][d] + co[s[last]][d];
int div = (d * t[n - first] + d * t[n - last]) % k;
int prvDiv = (j - div + k) % k;

if(cost >= inf || dp[i-1][prvDiv] >= inf)


continue;

cost += dp[i+1][prvDiv];
if(cost < dp[i][j])
{
dp[i][j] = cost;
chr[i][j] = d;
prv[i][j] = prvDiv;
}
}

//if(dp[i][j] != inf) printf("%d %d = %d\n", i, j, dp[i][j]);


}
}
}

void write()
{
printf("%d\n", dp[1][0]);
int last = 0;
for(int i = 1; i <= (n>>1); ++i)
{
sol[i] = sol[n - i + 1] = chr[i][last] + ’0’;
last = prv[i][last];
CAPITOLUL 11. ONI 2012 11.4. PALINDROM 321

printf("%s\n", sol + 1);


}

int main()
{
freopen(infile, "r", stdin);
freopen(outfile, "w", stdout);

read();
init();
solve();
write();

fclose(stdin);
fclose(stdout);
return 0;
}

Listing 11.4.2: RHpalindrom.cpp


#include <stdio.h>

#define NMAX 1000001


#define KMAX 21

int P[10], K, x[NMAX], N;


int cost[10][10];
int compar[KMAX][KMAX];
int costMin[2][KMAX];
int rez[NMAX][KMAX];
int back[NMAX][KMAX];
int putere[NMAX];
int partial[KMAX][2];
int rezFinal[NMAX];
int cnt, k;

void citire()
{
freopen("palindrom.in", "r", stdin);
freopen("palindrom.out", "w", stdout);

for (int i = 0; i < 10; scanf("%d ", P + i), ++i);


while (true)
{
char c;
scanf("%c", &c);
if (c >= ’0’ && c<=’9’)
x[N++] = c - ’0’;
else
{
scanf("%d", &K);
break;
}
}
}

void preprocesareCosturiTranzitie()
{
int uz[10];

for (int i = 0; i < 10; ++i)


{
for (int j = 0; j < 10; uz[j] = 0, cost[i][j++] = -1);
int j = i;
int cc = 0;

cost[i][i] = 0;
uz[i] = 1;

while (uz[P[j]] == 0)
{
uz[P[j]] = 1;
cost[i][P[j]] = ++cc;
CAPITOLUL 11. ONI 2012 11.4. PALINDROM 322

j = P[j];
}
}
}

void preprocesarePuteri10()
{
putere[0] = 1;
for (int i = 1; i < N; ++i)
putere[i] = (putere[i - 1] * 10) % K;
}

void cerintaA()
{
for (int i = 0; i < N; ++i)
{
int j = 9;
while (cost[x[i]][j] == -1)
--j;
printf("%d", j);
}

printf("\n");
}

void cerintaB()
{
preprocesarePuteri10();

costMin[1][0] = 0;
for (int i = 1; i < K; ++i)
costMin[1][i] = -1;

for (int i = 0, L = N / 2; i < L; ++i)


{
int st = x[i];
int dr = x[N - i - 1];
k = i & 1;

for (int ii = 0; ii < K; ++ii)


partial[ii][0] = partial[ii][1] =
costMin[k][ii] = rez[i][ii] = back[i][ii] = -1;

for (int j = 9; i == 0 ? j >= 1 : j >= 0; --j)


{
if (cost[st][j] != -1 && cost[dr][j] != -1)
{
int costTranf = cost[st][j] + cost[dr][j];
int restAdaugat = (j * putere[i] + j * putere[N-1-i]) % K;

if (partial[restAdaugat][0] == -1
|| costTranf < partial[restAdaugat][0])
{
partial[restAdaugat][0] = costTranf;
partial[restAdaugat][1] = j;
}
}
}

for (int j = 0; j < K; ++j)


printf("part %d: %d %d\n", j, partial[j][0], partial[j][1]);

for (int j = 0; j < K; ++j)


{
for (int l = 0; l < K; ++l)
{
if (partial[l][0] == -1 || costMin[1-k][j] == -1)
continue;

int restTarget = (j + l) % K;

if (costMin[k][restTarget] == -1
|| costMin[k][restTarget]
> costMin[1 - k][j] + partial[l][0])
{
CAPITOLUL 11. ONI 2012 11.4. PALINDROM 323

costMin[k][restTarget] = costMin[1-k][j] + partial[l][0];


rez[i][restTarget] = partial[l][1];
back[i][restTarget] = j;
}

if (costMin[k][restTarget]
== costMin[1 - k][j] + partial[l][0])
{
if (compar[j][restTarget] == 0)
{
if (rez[i][restTarget] < partial[l][1])
{
costMin[k][restTarget] = costMin[1 - k][j]
+ partial[l][0];
rez[i][restTarget] = partial[l][1];
back[i][restTarget] = j;
}
}
else
if (compar[j][restTarget] == 1)
{
costMin[k][restTarget] = costMin[1 - k][j]
+ partial[l][0];
rez[i][restTarget] = partial[l][1];
back[i][restTarget] = j;
}
}
}
}

for (int j = 0; j < K; ++j)


printf("%d : %d %d %d\n", j,costMin[k][j],rez[i][j],back[i][j]);

//refac comparatiile
for (int j = 0; j < K; ++j)
for (int l = 0; l < K; ++l)
if (compar[j][l] == 0)
if (rez[i][j] > rez[i][l])
compar[j][l] = 1;
else if (rez[i][j] < rez[i][l])
compar[j][l] = -1;
}

int poz = 0;
int i = N / 2 - 1;
while (i >= 0)
{
rezFinal[cnt++] = rez[i][poz];
poz = back[i][poz];
--i;
}

for (int i = N / 2 - 1; i >= 0; --i)


printf("%d", rezFinal[i]);
for (int i = 0; i < N / 2; ++i)
printf("%d", rezFinal[i]);
printf("\n");
printf("%d\n", costMin[k][0]);
}

int main(int argc, char *argv)


{
citire();

preprocesareCosturiTranzitie();

cerintaA();
//cerintaB();

return 0;
}

Listing 11.4.3: VIpalindrom.cpp


#include <iostream>
CAPITOLUL 11. ONI 2012 11.4. PALINDROM 324

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>

using namespace std;

#define maxn 100010


#define maxk 22
#define maxcifre 10
#define inf 99999999
#define pii pair<int, int>
#define pb push_back
#define mkp make_pair

int N, K;
char A[maxn], sol[maxn];
int perm[maxcifre], put[maxn];
int ajunge[maxcifre][maxcifre];
int D[maxn][maxk];

void preproc_puteri()
{
int i;

put[0] = 1;
for(i=1; i<=N; i++)
put[i] = (put[i-1] * 10) % K;
}

void preproc_ajunge()
{
int i, j, p;

for(i=0; i<maxcifre; i++)


for(j=0; j<maxcifre; j++)
ajunge[i][j] = -1;

for(i=0; i<maxcifre; i++)


{
ajunge[i][i] = 0;
p = i;
for(j=1; j<=100; j++)
{
p = perm[p];
if(p == i) break;
ajunge[i][p] = j;
}
}
}

int main()
{
FILE *f1=fopen("palindrom.in", "r"), *f2=fopen("palindrom.out", "w");
int i, j, p, q;

for(i=0; i<=9; i++)


fscanf(f1, "%d ", &perm[i]);

fscanf(f1, "%s\n", A+1);


N = strlen(A+1);

fscanf(f1, "%d\n", &K);

preproc_puteri();
preproc_ajunge();

int jumatate = N / 2;

//prima cerinta
for(i=jumatate; i>=1; i--)
{
int cif1 = A[i] - ’0’;
int cif2 = A[N-i+1] - ’0’;

int finish = 0;
CAPITOLUL 11. ONI 2012 11.4. PALINDROM 325

if(i == 1) finish = 1;

for(q=9; q>=1; q--)


{
if(ajunge[cif1][q] >= 0 && ajunge[cif2][q] >= 0)
{
sol[i] = (q + ’0’);
break;
}
}
}

for(i=1; i<=jumatate; i++)


fprintf(f2, "%c", sol[i]);

for(i=jumatate; i>=1; i--)


fprintf(f2, "%c", sol[i]);

fprintf(f2, "\n");

//a doua cerinta


for(i=jumatate+1; i>=1; i--)
for(j=0; j<K; j++)
D[i][j] = inf;

D[jumatate+1][0] = 0;

for(i=jumatate; i>=1; i--)


{
int cif1 = A[i] - ’0’;
int cif2 = A[N-i+1] - ’0’;

for(j=0; j<K; j++)


{
int start = 0;
if(i == 1) start = 1; //prima cifra a palindromului nu poate fi 0

for(p=start; p<=9; p++)


if(ajunge[cif1][p] >= 0 && ajunge[cif2][p] >= 0)
{
int nou_rest = (j + p * (put[N-i] + put[i-1])) % K;
D[i][nou_rest] = min(D[i][nou_rest], D[i+1][j] +
ajunge[cif1][p] + ajunge[cif2][p]);
}
}
}

int cost = D[1][0];


int rest = 0;

if(cost >= N * 10)


{
fprintf(f2, "-1\n");
fclose(f1);
fclose(f2);
return 0;
}

for(i=1; i<=jumatate; i++)


{
//calculez sol[i]
int cif1 = A[i] - ’0’;
int cif2 = A[N-i+1] - ’0’;

int finish = 0;
if(i == 1) finish = 1;

for(q=9; q>=finish; q--)


{
if(ajunge[cif1][q] >= 0 && ajunge[cif2][q] >= 0)
{
p = (q * (put[N-i] + put[i-1])) % K;
int nou_rest = rest - p;
if(nou_rest < 0) nou_rest += K;

if(D[i+1][nou_rest]+ajunge[cif1][q]+ajunge[cif2][q] == cost)
CAPITOLUL 11. ONI 2012 11.5. SSTABIL 326

{
//este ok ok ok
sol[i] = (q + ’0’);

cost -= (ajunge[cif1][q] + ajunge[cif2][q]);


rest = nou_rest;

break;
}
}
}
}

for(i=1; i<=jumatate; i++)


fprintf(f2, "%c", sol[i]);

for(i=jumatate; i>=1; i--)


fprintf(f2, "%c", sol[i]);

fprintf(f2, "\n");

fclose(f1);
fclose(f2);
return 0;
}

11.4.3 *Rezolvare detaliată

11.5 sstabil
Problema 5 - sstabil 100 de puncte
Numim număr sstabil orice număr natural care este format dintr-o singură cifră sau care are
suma oricăror două cifre vecine strict mai mare decât nouă.
Asupra oricărui număr care nu este sstabil se pot efectua operaţii de ı̂nlocuire a oricăror două
cifre vecine care au suma strict mai mică decât zece cu o cifră egală cu suma lor.
Operaţiile de ı̂nlocuire pot fi aplicate, ı̂n acelaşi condiţii, şi asupra numerelor rezultate după
fiecare ı̂nlocuire, de câte ori este nevoie, până când se obţine un număr sstabil.
De exemplu, 291 este număr sstabil deoarece 2+9¿9 şi 9+1¿9, iar 183 nu este sstabil pentru
că 1+8¡10. Din numărul 2453, efectuând o singură ı̂nlocuire, putem obţine 653 sau 293 (număr
sstabil) sau 248. Numărul 653, nefiind sstabil, permite o nouă operaţie de ı̂nlocuire, obţinând
astfel numărul 68, care este sstabil. Analog, din numărul 248 se poate obţine numărul sstabil 68.

Cerinţe

Scrieţi un program care să determine cel mai mare număr natural sstabil care se poate obţine
dintr-un număr natural dat, aplicând una sau mai multe operaţii de ı̂nlocuire de tipul menţionat.

Date de intrare

Fişierul de intrare sstabil.in conţine pe prima linie un număr natural n, reprezentând numărul
de cifre al numărului dat, iar pe linia a doua, separate prin câte un spaţiu, cifrele acestui număr.

Date de ieşire

Fişierul de ieşire sstabil.out va conţine pe o linie numărul sstabil maxim obţinut.

Restricţii şi precizări

a 1 & n & 1 000 000

Exemple:
CAPITOLUL 11. ONI 2012 11.5. SSTABIL 327

sstabil.in sstabil.out Explicaţii


510451 191 10 451 1 091 191
552832 785
52832 78 32 785
Timp maxim de executare/test: 1.0 secunde
Memorie: total 16 MB din care pentru stivă 4 MB
Dimensiune maximă a sursei: 5 KB

11.5.1 Indicaţii de rezolvare

De la dreapta la stânga se determină cifrele , cât mai mici, ale unui număr sstabil. Procedând
astfel vom obţine un număr sstabil cu număr maxim de cifre şi care are primele cifre cele mai mari
(faţă de un alt număr sstabil care ar avea acelaşi număr de cifre). Vom demonstra ı̂n continuare.
Demonstraţia soluţiei pentru problema sstabil”: ...

11.5.2 Cod sursă

Listing 11.5.1: PRIVsstabil 1.cpp


#include<stdio.h>

int a[1000005],b[1000005];
int n,i,j,r,p,k,s,t;

int main()
{
freopen("sstabil.in","r",stdin);
freopen("sstabil.out","w",stdout);

scanf("%d\n",&n);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);

k=0;
r=n;
while (r>0)
{
j=r;
s=0;
do
{
s=s+a[j];
j--;
} while (j>0 && s<10);

k++;
if (s<10)
{
b[k]=s;
r=j;
}
else
if (k==1)
{
p=r; t=a[p];
while (s-t>=10)
{
p--;
t=t+a[p];
}
b[k]=t;
r=p-1;
}
else
{
p=r; t=a[p];
while (s-t>9 || t+b[k-1]<10)
{
p--;
CAPITOLUL 11. ONI 2012 11.5. SSTABIL 328

t=t+a[p];
}
b[k]=t;
r=p-1;
}
}

for (i=k;i>=1;i--)
printf("%d",b[i]);
fclose(stdout);
fclose(stdin);
return 0;
}

Listing 11.5.2: PRIVsstabil 2.cpp


#include<stdio.h>

int a[1000005],b[1000005];
int n,i,j,r,p,k,s,t;

int main()
{
freopen("sstabil.in","r",stdin);
freopen("sstabil.out","w",stdout);

scanf("%d\n",&n);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);

a[0]=9;
a[n+1]=9;
b[0]=9;
k=0;
r=n;
while (r>0)
{
j=r; s=0;
do
{
s=s+a[j];
j--;
} while (s<10);

p=r;
t=a[p];

while (s-t>9 || t+b[k]<10)


{
p--;
t=t+a[p];
}

k++;
b[k]=t;
r=p-1;
}

for (i=k;i>=1;i--)
printf("%d",b[i]);

fclose(stdout);
fclose(stdin);
return 0;
}

Listing 11.5.3: VI sstabil.cpp


#include<stdio.h>

int a[1000005],b[1000005];
int n,i,j,r,p,k,s,t;

int main()
CAPITOLUL 11. ONI 2012 11.6. UNUZERO 329

{
freopen("sstabil.in","r",stdin);
freopen("sstabil.out","w",stdout);

scanf("%d\n",&n);
for (i=1;i<=n;i++)
scanf("%d",&a[i]);

a[0]=9;
a[n+1]=9;
b[0]=9;
k=0;
r=n;
while (r>0)
{
j=r; s=0;
do
{
s=s+a[j];
j--;
} while (s<10);

p=r;
t=a[p];

while (s-t>9 || t+b[k]<10)


{
p--;
t=t+a[p];
}

k++;
b[k]=t;
r=p-1;
}

for (i=k;i>=1;i--)
printf("%d",b[i]);

fclose(stdout);
fclose(stdin);
return 0;
}

11.5.3 *Rezolvare detaliată

11.6 unuzero
Problema 6 - unuzero 100 de puncte
Se consideră un şir format din N  2 cifre binare, care conţine cel puţin o cifră 1 şi cel puţin
trei cifre 0; prima şi ultima cifră a şirului sunt 0.
Numim 1-secvenţă o succesiune formată numai din cifre 1, aflate pe poziţii consecutive ı̂n acest
şir, delimitată de câte o cifră 0.
Corina construieşte un astfel de şir, ı̂n care numărul de cifre 1 ale fiecărei 1-secvenţe să fie
cuprins ı̂ntre două numere naturale date, p şi q (p & q).

Cerinţe

Scrieţi un program care să determine un număr natural K, egal cu restul ı̂mpărţirii la 666013
a numărului de şiruri distincte, de tipul celui construit de Corina.

Date de intrare

Fişierul de intrare unuzero.in conţine pe prima linie numărul natural N , iar pe cea de a doua
linie numerele naturale p şi q (p & q), separate printr-un spaţiu.

Date de ieşire
CAPITOLUL 11. ONI 2012 11.6. UNUZERO 330

Fişierul de ieşire unuzero.out va conţine pe prima linie numărul natural K cerut.

Restricţii şi precizări

a 1 & p & q $ N $ 1 000 000. a Pentru 20% din teste N & 25, iar pentru alte 40% din teste
25 $ N & 1 000.

Exemple:

unuzero.in unuzero.out Explicaţii


523 8 0000110
0001100
0001110
0011000
0011100
0110000
0110110
0111000
Timp maxim de executare/test: 0.2 secunde
Memorie: total 16 MB din care pentru stivă 16 MB
Dimensiune maximă a sursei: 5 KB

11.6.1 Indicaţii de rezolvare

prof. Gheorghe Manolache - Colegiul Naţional de Informatică , Piatra Neamţ

Se observă că problema nu poate obţine decât 20 de puncte dacă vom ı̂ncerca să generăm toate
soluţiile posibile pe care apoi să contorizăm si 60 de puncte pentru lungimi mai mici de 1000.
Ideea de rezolvare se bazează pe observarea unei relaţii de recurenţă ce se poate deduce după o
scurtă analiză. Putem considera că utilizăm cifrele zero şi unu iar secvenţele căutate sunt formate
din valori successive de unu. Dacă presupunem că am format deja o secvenţă de 1 pentru o lungime
dată, atunci ı̂n stânga va fi evident 0.
Fie U i, numărul de configuraţii corecte de lungime i, terminate cu 1 şi Z i, numărul de
configuraţii corecte de lungime i, terminate cu 0.
Atunci, avem relaţia de recurenţă următoare:

=Zj
ia
U i  
j ib

Iniţial Z 0 1, Z 1 1 iar U 1 1 doar dacă a este 1;


2
Se obţine un algoritm de complexitate O n  care poate obţine 60 de puncte.
Pentru punctaj maxim algoritmul trebuie optimizat folosind sume parţiale şi se obţine astfel
complexitate O n.

11.6.2 Cod sursă

Listing 11.6.1: GMunuab 100.cpp


// O(n)
#include <fstream>

using namespace std;

int mod=666013,a,b;
int s[1000100],u[1000100],z[1000100],n;

int main()
{
ifstream in("unuzero.in");
ofstream out("unuzero.out");
CAPITOLUL 11. ONI 2012 11.6. UNUZERO 331

in>>n>>a>>b;
z[0]=1;
s[0]=1;
for(int i=1;i<=n;++i)
{
z[i]=z[i-1]+u[i-1]; // daca punem 0
z[i]%=mod;

if(i-a>=0)
if(i-b-1>=0)
u[i]=s[i-a]-s[i-b-1];
else
u[i]=s[i-a];

if(u[i]<0) u[i]+=mod;
u[i]%=mod;

s[i]=s[i-1]+z[i];
s[i]%=mod;
}

out<<(z[n]+u[n]-1+(z[n]+u[n]-1<0?mod:0))%mod<<’\n’;
out.close();
return 0;
}

Listing 11.6.2: GMunuab back.cpp


#include <fstream>
using namespace std;

int n,a,b,sol;
int v[100100];

ifstream in("unuzero.in");
ofstream out("unuzero.out");

inline void verif()


{
int ok=0;
for(int i=1;i<=n;)
{
if(v[i]==1)
{
ok=1;
int sv=i,nr=0;
while(v[i]==v[sv] && i<=n) ++i,++nr;
if(!(nr>=a && nr<=b)) return ;
}
else ++i;
}

if(!ok) return;

sol++;
/*for(int i=1;i<=n;++i)
{
out<<v[i];
}
out<<’\n’;*/
}

void back(int k)
{
if(k>n)
verif();
else
for(int i=0;i<=1;++i)
{
v[k]=i;
back(k+1);
}
}

int main()
CAPITOLUL 11. ONI 2012 11.6. UNUZERO 332

{
in>>n>>a>>b;
back(1);
out<<sol%666013<<’\n’;
out.close();
return 0;
}

Listing 11.6.3: GMunuab n patrat.cpp


// O(nˆ2)
#include <fstream>

using namespace std;

int mod=666013,a,b;
int u[1000100],z[1000100],n;

int main()
{
ifstream in("unuzero.in");
ofstream out("unuzero.out");

in>>n>>a>>b;
z[0]=1;
z[1]=1;
if(a==1) u[1]=1;
for(int i=2;i<=n;++i)
{
z[i]=z[i-1]+u[i-1]; // daca punem 0
z[i]%=mod;
for(int j=i-b;j<=i-a;++j) // daca punem 1
{
if(j>=0)
{
u[i]+=z[j];
u[i]%=mod;
}
}
}

out<<(z[n]+u[n]-1+(z[n]+u[n]-1<0?mod : 0))%mod<<’\n’;
out.close();
return 0;
}

Listing 11.6.4: VI unuzero.cpp


#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;

#define maxn 1000010


#define mod 666013

int N, A, B;
int s[maxn], n[maxn];

int main()
{
FILE *f1=fopen("unuzero.in", "r"), *f2=fopen("unuzero.out", "w");
int i, p;

fscanf(f1, "%d %d %d\n", &N, &A, &B);


N ++;

n[0] = s[0] = 1;
for(i=1; i<=N; i++)
{
n[i] = n[i-1];
// negru pe pozitia i
CAPITOLUL 11. ONI 2012 11.6. UNUZERO 333

// (i-1, i-2, ..., i-A, i-A-1, ..., i-B


if(i-A-1 >= 0)
{
if(i-B-2 >= 0)
{
n[i] += (s[i-A-1] - s[i-B-2] + mod) % mod;
}
else
{
n[i] += (s[i-A-1]) % mod;
}

n[i] %= mod;
}

s[i] = (s[i-1] + n[i]) % mod;


}

fprintf(f2, "%d\n", (mod + n[N] - 1) % mod);

fclose(f1);
fclose(f2);
return 0;
}

11.6.3 *Rezolvare detaliată


Capitolul 12

ONI 2011

12.1 poligon
Problema 1 - poligon 100 de puncte
Poligonul de tragere este un teren special amenajat ı̂n cadrul căruia se fac exerciţii şi se execută
trageri cu arme de foc. Comandantul plasează câte o ţintă ı̂n toate punctele aflate la distanţele
Ri , 1 & i & n faţă de punctul de tragere (origine) şi care au coordonatele carteziene numai numere
naturale nenule.
Specialiştii ı̂n armament români au creat recent o nouă armă sub forma unui tun laser care
ı̂şi lansează razele pe o traiectorie rectilinie şi are capacitatea de a distruge toate ţintele aflate pe
direcţia de tragere.

Cerinţe

Ştiind că tunul laser se găseşte ı̂n originea sistemului de co-


ordonate, să se scrie un program care să determine: numărul
de ţinte, numărul minim de lovituri de tun laser necesare
pentru a distruge toate ţintele precum şi numărul de ţinte
doborâte la fiecare lovitură.
Spre exemplu, dacă avem n 6 distanţe (5, 7, 10, 13, 15,
17) pentru care se ı̂ncearcă plasarea ţintelor, atunci ı̂n poligon
se vor plasa 10 ţinte, va fi nevoie de 6 lovituri pentru a doborı̂
toate ţintele iar la fiecare lovitură se vor doborı̂ respectiv 1,
1, 3, 3, 1, 1 ţinte.

Date de intrare

Fişierul de intrare poligon.in conţine pe prima linie numărul n de distanţe la care vor fi
plasate ţinte, iar pe a doua linie n numere naturale nenule distincte separate printr-un spaţiu, ce
reprezintă aceste distanţe.

Date de ieşire

Fişierul de ieşire poligon.out va conţine 3 linii. Pe prima linie se va scrie numărul ţintelor
plasate ı̂n poligon. Pe a doua linie se va scrie numărul minim de lovituri de tun laser cu care se pot
doborı̂ toate ţintele, iar pe a treia linie se va scrie numărul de ţinte doborâte la fiecare lovitură,
separate printr-un spaţiu, ı̂n ordinea crescătoare a unghiurilor direcţiilor cu axa OX.

Restricţii şi precizări

a 1 & n & 1000


a 1 & Ri & 1000
a pentru fiecare set de date de intrare, ı̂n poligon va exista cel puţin o ţintă.
a se acordă:
- 20% din punctaj pentru determinarea corectă a numărului de ţinte.
- 40% din punctaj pentru determinarea corectă a numărului minim de lovituri.
- 40% din punctaj pentru determinarea corectă a numărului de ţinte doborâte la fiecare lovi-
tură.

334
CAPITOLUL 12. ONI 2011 12.1. POLIGON 335

Exemple:

poligon.in poligon.outExplicaţii
6 10 Avem 6 distante: 5,10,15,7,13,17.
5 10 15 7 13 17 6 În poligon vor fi plasate 10 ţinte (punctele negre marcate pe
113311 figură) care pot fi doborâte din 6 lovituri iar la fiecare lovitură
se vor doborı̂ câte 1, 1, 3, 3, 1, 1 ţinte.
Timp maxim de executare/test: 0.4 secunde
Memorie: total 2 MB
Dimensiune maximă a sursei: 5 KB

12.1.1 Indicaţii de rezolvare

Observăm că pentru a determina ţintele active din poligon trebuie determinat numărul de
2 2 2
soluţii al ecuaţiei R x  y unde R reprezintă raza cercurilor de activare. ı̂n vederea rezolvării
şi cerinţelor ulterioare, coordonatele ţintelor active se vor ı̂ncărca ı̂ntr-o structura de date, pe care
se vor face interogări suplimentare. Micşorarea timpului de rezolvare a acestei ecuaţii are la bază
observaţia că soluţiile sunt simetrice faţă de prima bisectoare.
În vederea determinării numărului minim de lovituri trebuie să determinăm câte drepte dis-
tincte se formează unind coordonatele ţintelor active cu originea sistemului de axe. Având ı̂n
vedere ı̂nsă că trebuie să determinăm şi numărul de puncte coliniare de pe fiecare dreapta, ı̂n
sens trigonometric, vom face sortarea coordonatelor ţintelor active după panta acestora. Se poate
utiliza relaţia conform căreia dacă două puncte A x1 , y1  şi B x2 , y2  sunt coliniare cu originea
sistemului de axe atunci x1 ˜ y2 x2 ˜ y1 .
După sortare se determină câte puncte fac parte din aceeaşi categorie şi se afişează rezultatele.

12.1.2 Cod sursă

Listing 12.1.1: poligon.cpp


// sursa Pit-Rada Vasile - 100 p
#include<fstream>
#include<math.h>

using namespace std;

ifstream f1("poligon.in");
ofstream f2("poligon.out");

int n,i,c,s,aux,r[1001],j,k;
int x[10000],y[10000];

int main()
{
f1>>n;
c=0;
for (i=1;i<=n;i++)
{
f1>>r[i];
for (j=1;j<=r[i]-1;j++)
{
k=(int)sqrt((double)r[i]*r[i]-j*j);
if (k*k+j*j==r[i]*r[i])
{
c++;
x[c]=j; y[c]=k;

}
}
}

f2<<c<<"\n";
for (i=1;i<c;i++)
for (j=i+1;j<=c;j++)
if (x[i]*y[j]>x[j]*y[i])
CAPITOLUL 12. ONI 2011 12.2. STALPI 336

{
aux=x[i]; x[i]=x[j]; x[j]=aux;
aux=y[i]; y[i]=y[j]; y[j]=aux;
}

x[0]=0; y[0]=1;
s=0;
for (i=1;i<=c;i++)
{
if (x[i-1]*y[i]<x[i]*y[i-1])
s++;
}

f2<<s<<"\n";
s=1;
for (i=2;i<=c;i++)
{
if (x[i-1]*y[i]==x[i]*y[i-1])
{
s++;
}
else
{
f2<<s<<" ";
s=1;
}
}

f2<<s;
f2.close();
f1.close();
return 0;
}

12.1.3 *Rezolvare detaliată

12.2 stalpi
Problema 2 - stalpi 100 de puncte
Între doi stâlpi verticali aflaţi pe malurile unui râu (de o parte şi de alta a râului) se află
legate două cabluri bine ı̂ntinse, paralele cu solul, având distanţa dintre ele egală cu d centimetri.
Cablurile sunt folosite pentru traversarea râului ı̂n caz de inundaţii. Stâlpii sunt notaţi cu A şi B,
iar cablurile cu 1 şi 2 ca ı̂n figura de mai jos.
Pe cabluri există desenate câte n puncte colorate cu diverse culori, culorile fiind codificate prin
numerele 1, 2, 3,..., k. Poziţia punctelor pe fiecare cablu este dată prin distanţa faţă de stâlpul
A pentru fiecare punct. Punctele de pe fiecare cablu sunt numerotate cu 1, 2, 3 ,..., n. Pe fiecare
cablu există cel puţin un punct colorat cu fiecare culoare. Pentru a uşura deplasarea pe cablu,
primarul hotărăşte să lege cu sârmă perechi de puncte de aceeaşi culoare, unul de pe primul cablu,
iar celălalt de pe al doilea cablu, astfel ı̂ncât:
- pentru fiecare culoare să existe o singură pereche de puncte ı̂ntre care să fie legătură;
- lungimea totală de sârmă folosită să fie minimă.

Cerinţe

Să se scrie un program care determină lungimea minimă a sârmei ce va fi folosită pentru
rezolvarea problemei şi o mulţime de perechi de puncte ce urmează a fi legate pentru a obţine
acest minim.

Date de intrare

Fişierul de intrare stalpi.in va conţine:


- pe prima linie numerele naturale nenule n, d separate printr-un spaţiu;
- pe a doua linie n perechi de numere, formate din distanţa faţă de stâlpul A la fiecare punct
şi culoarea asociată punctului, separate prin câte un spaţiu, aflate pe cablul 1;
CAPITOLUL 12. ONI 2011 12.2. STALPI 337

- pe a treia linie n perechi de numere, formate din distanţa faţă de stâlpul A la fiecare punct
şi culoarea asociată punctului, separate prin câte un spaţiu, aflate pe cablul 2.

Date de ieşire

Fişierul de ieşire stalpi.out va conţine pe prima linie valoarea minimă cerută, iar pe
următoarele k linii numerele de ordine ale punctelor ce vor fi legate cu sârmă, separate printr-un
spaţiu, ı̂ntâi cele de pe cablu 1, urmate de cele de pe cablu 2, ı̂n ordinea crescătoare a culorilor.

Restricţii şi precizări

a 1 & n & 10 000


a 1 & k & 100
a 1 & d & 1 000
a Distanţa dintre cei doi stâlpi A şi B este 30 000.
a Distanţele de la stâlpul A la puncte sunt numere naturale.
a Distanţa minimă va fi afişată trunchiată la primele 3 zecimale.
a Toate punctele de pe un cablu sunt distincte.
a Se acordă 40% din punctaj pentru determinarea corectă a minimului din cerinţă.

Exemple:

stalpi.in stalpi.out
Explicaţii
3 100 211.803 Sunt n 3 perechi de puncte, k 2 culori, codificate cu 1 şi 2.
50 1 200 2 100 1 32 Necesarul minim de sârmă este 211.803.
250 2 100 1 300 2 21 Se leagă punctul P3 de punctul Q2 (ambele au culoarea 1).
Se leagă punctul P2 de punctul Q1 (ambele au culoarea 2).
Timp maxim de executare/test: 0.2 secunde
Memorie: total 4 MB
Dimensiune maximă a sursei: 5 KB

12.2.1 Indicaţii de rezolvare

prof. Doru Anastasiu Popescu, C. N. ”Radu Greceanu”, Slatina

Folosind datele de intrare, pentru fiecare culoare c din mulţimea r1, 2, ..., k x se construiesc doi
vectori:
a a0 , a1 , ..., a30000 , unde ai este codul punctului aflat la distanţa i de stâlpul A de pe
primul cablu (0 dacă nu există punct).
b b0 , b1 , ..., b30000 , unde bi este codul punctului aflat la distanţa i de stâlpul A de pe al
doilea cablu (0 dacă nu există punct).
Parcurgem folosind acelaşi indice i (0, 1, 2, ..., 30 000) ambii vectori a şi b şi actualizăm trei
variabile xa, xb şi min (iniţial min este 20 000), astfel:
dacă ai j 0 si xa=0, atunci xa=i
dacıa bi j 0 si xb=0, atunci xb=i
dacă ai j 0 si xb j 0 si —i-xb—¡min, atunci xa=i; min=—i-xb—
dacă bi j 0 si xa j 0 si —i-xa—¡min, atunci xb=i; min=—i-xb—
CAPITOLUL 12. ONI 2011 12.2. STALPI 338

Õ
S d2  xa  xb2
Introducem xa şi xb ı̂n doi vectori x şi y.
Afişăm s şi elementele vectorilor x şi y.
În algoritm am folosit:

M N =minim, dacă M Q=minim, pentru că P Q d, care este constantă.


Din M Q ¶xP  xM ¶, P Q d şi teorema lui Pitagora obţinem:
Õ
MN d2  xP  xM 2

O altă solutie de complexitate O k ˜ n este următoarea :

prof. Pit-Rada Ionel Vasile, Colegiul National ”Traian”, Drobeta Turnu Severin

Se construiesc tablourile cablu1[ ] şi cablu2[ ], fiecare de câte 30 000 de elemente de tip int.
Pentru fiecare punct de pe fiecare cablu, poziţia i din şirul de date citit şi perechea (distanţă,
culoare) specifice fiecărui punct le-am memorat prin
cablu1[distanţă] = i*100+culoare-1, respectiv
cablu2[distanţă] = i*100+culoare-1
În procesul de citire a datelor şi construire a tablourilor cablu1[ ] şi cablu2[ ] am calculat
numărul de culori k ca fiind valoarea maximă a valorilor culorilor citite.
Pentru fiecare culoare cul, ı̂n ordinea 1,2, ..., k, am utilizat tablourile cablu1[ ] şi cablu2[ ]
pentru sortarea ı̂n complexitate O n a punctelor de aceeaşi culoare de pe fiecare cablu, preluând
distanţa faţă de capătul stâng al cablului pe care se află şi poziţia din şirul datelor de intrare ı̂n
tablourile x1[] şi z1[] cu n1 elemente, respectiv x2[] şi z2[] cu n2 elemente.
Folosind un algoritm asemănător cu cel de la interclasarea a doi vectori ordonaţi am determinat
ı̂n complexitate O n diferenţa cea mai mică dif t, ı̂n valoare absolută, dintre distanţele faţă de
capetele din stânga ale cablurilor, ale celor două puncte de aceeaşi culoare cul aflate pe cabluri
diferite, x1i şi x2j , 1 & i & n1 şiÔ1 & j & n2.
Apoi am calculat, cu formula dif t ˜ dif t  d ˜ d, lungimea sârmei care va uni cele două
puncte, ca lungime a ipotenuzei ı̂ntr-un triunghi dreptunghic de catete dif t şi d şi am adăugat-o
sumei dmin. Am memorat prin xcul i1 şi y cul i2 perechea de poziţii i1 şi i2, din şirul de
date de intrare, corespunzătoare celor două puncte alese.
La final am afişat cu trei zecimale exacte valoarea dmin şi apoi cele k perechi xi, y i,
1 & i & k.

12.2.2 Cod sursă

Listing 12.2.1: stalpi.cpp


// Sursa Pit-Rada Vasile - 100 p
#include<fstream>
#include<math.h>

using namespace std;

int cablu1[30001],cablu2[30001];
int x1[10001],x2[10001],z1[10001],z2[10001],x[101],y[101];
int n1,n2,n,i,d,k,poz,cul,d1,d2,j,dif,dift,i1,i2,w1,w2,w,m1,m2;
CAPITOLUL 12. ONI 2011 12.2. STALPI 339

double dmin;

ifstream f1("stalpi.in");
ofstream f2("stalpi.out");

int main()
{

f1>>n>>d;
k=0;
d1=0;
for (i=1;i<=n;i++)
{
f1>>poz>>cul;
cablu1[poz]=i*100+cul-1;
if (cul>k) k=cul;
if (poz>d1)d1=poz;
}

d2=0;
for (i=1;i<=n;i++)
{
f1>>poz>>cul;
cablu2[poz]=i*100+cul-1;
if (poz>d2)d2=poz;
}

dmin=0;
for (cul=1;cul<=k;cul++)
{
n1=0;
m1=0;
for (i=0;i<=d1;i++)
{
if (cablu1[i])
{
m1++;
if (cablu1[i]%100+1 == cul)
{
n1++;
x1[n1]=i;
z1[n1]=cablu1[i]/100;
}
}
}

n2=0;
m2=0;
for (i=0;i<=d2;i++)
{
if (cablu2[i])
{
m2++;
if (cablu2[i]%100+1 == cul)
{
n2++;
x2[n2]=i;
z2[n2]=cablu2[i]/100;
}
}
}

i=1;
j=1;
dift=x1[i]-x2[j];
i1=z1[i];
i2=z2[j];
if (dift<0) dift=-dift;
while (i<=n1 && j<=n2 && dift!=0)
{
dif=x1[i]-x2[j];
if (dif<0)dif=-dif;
if (dif<dift)
{
dift=dif;
i1=z1[i];
CAPITOLUL 12. ONI 2011 12.3. TORT 340

i2=z2[j];
}

if (x1[i]<x2[j])
i++;
else
j++;
}

x[cul]=i1;
y[cul]=i2;
dmin=dmin+sqrt((double)dift*dift+d*d);
}

w1=(int)floor(dmin);
w2=(int)floor((dmin-w1)*1000);
f2<<w1<<".";
if (w2<10)
f2<<"00"<<w2;
else
if (w2<100)
f2<<"0"<<w2;
else
f2<<w2;

f2<<"\n";
for (cul=1;cul<=k;cul++)
{
f2<<x[cul]<<" "<<y[cul]<<"\n";
}

f2.close();
f1.close();
return 0;
}

12.2.3 *Rezolvare detaliată

12.3 tort
Problema 3 - tort 100 de puncte
De ziua lui, Gigel a primit un tort de formă dreptunghiulară, ornat cu un caroiaj ce ı̂mparte
tortul ı̂n m  n pătrate, ı̂n fiecare pătrat aflându-se câte o cireaşă sau o căpşună. Caroiajul cu
fructe este reprezentat printr-o matrice cu 0 şi 1, 0 ı̂nsemnând cireaşă şi 1 căpşună.
Sărbătoritul are dreptul să taie k felii de tort. O felie se poate obţine prin tăierea după liniile
caroiajului, dintr-un capăt ı̂n celălalt, având lăţimea egală cu 1, de pe oricare latură a tortului,
codificate cu N, E, S, V. Gigel fiind mare amator de căpşuni vrea să taie cele k felii astfel ı̂ncât
numărul căpşunilor din aceste felii să fie cât mai mare.
Spre exemplu, dacă tortul iniţial este reprezentat ca o matrice având 6  6 linii şi coloane,
după 3 tăieturi N, E, V bucata rămasă şi feliile obţinute vor fi conform figurii alăturate.

Cerinţe
Să se scrie un program care să determine numărul de posibilităţi de tăiere a k felii de tort,
pentru a obţine un număr maxim de căpşuni. Două variante ı̂n care diferă doar ordinea de tăiere,
CAPITOLUL 12. ONI 2011 12.3. TORT 341

dar rămâne aceeaşi bucată de tort, nu sunt considerate distincte. De exemplu, dacă numărul
maxim de căpşuni se poate obţine prin una din variantele : VSNNV sau VVNSN, acestea nu sunt
considerate distincte.

Date de intrare

Pe prima linie a fişierului de intrare tort.in sunt scrise dimensiunile tortului, m şi n şi numărul
k al feliilor de tort tăiate de Gigel, separate prin câte un spaţiu. Pe următoarele m linii e descris
caroiajul cu fructe printr-o matrice cu valori de 0 şi 1.

Date de ieşire

Prima linie a fişierului tort.out va conţine numărul maxim de căpşuni care poate fi obţinut
din cele k felii de tort. Pe linia a doua se va găsi numărul de posibilităţi distincte de a obţine
numărul maxim de căpşuni.

Restricţii şi precizări

a 2 & m, n & 500


a 1 & k $ min m, n

Exemple:

tort.in tort.out Explicaţii


663 10 Tortul este format dintr-un caroiaj cu m 6 linii şi n 6
011101 5 coloane şi se pot tăia k 3 felii.
100001 Se pot obţine maxim 10 căpşuni.
000100 Cele 5 posibilităţi de a tăia cele 3 felii sunt: NNS, NSE, NSV,
010101 VEV şi NEV
100000
111001
Timp maxim de executare/test: 0.2 secunde
Memorie: total 4 MB
Dimensiune maximă a sursei: 5 KB

12.3.1 Indicaţii de rezolvare

După tăierea celor k felii din tort, rămâne un dreptunghi de dimensiuni m  m1  n  n1,
unde m1 şi n1 reprezintă numarul feliilor orizontale, respectiv verticale (n1  n2 k). Trebuie ca
acest dreptunghi să conţină cât mai puţine valori 1 (capsuni). Construim o matrice ı̂n care pentru
1 & i & m şi 1 & j & n memoram numărul de capşuni din dreptunghiul cu vârfurile opuse 1, 1
şi i, j , după care numărul de capşuni din orice submatrice se gaseşte ı̂n O 1. Vom determina
pentru fiecare k1 cu valori de la 0 la k toate submatricele de dimensiuni m  k1  n  k  k1 cu
număr minim de 1 printr-o singura parcurgere a matricei obţinănd o complexitate O m ˜ n ˜ k ,
suficientă pentru dimensiunile date.
Algoritm tort
citeste m,n,k
pt. i=1,m
pt. j=1,n citeste Aij sf.
sf.
B[1,1]=A[1,1]; min=m*n+1;
pt. i=2,m B[i,1]=B[i-1,1]+A[i,1] sf.
pt. j=2,n B[1,j]=B[1,j-1]+A[1,j] sf.
pt. i=2,m
pt. j=2,n Bij=B[i,j-1]+B[i-1,j]-B[i-1,j-1] sf.
sf.
pt. k1=0,k1
pt. i=m-k1,m
pt. j=n-k+k1,n
S=Bij-B[i-m+k1,j]-B[i,j-n+k-k1]+B[i-m+k1,j-n+k-k1]
CAPITOLUL 12. ONI 2011 12.4. APE 342

daca S<min min=S; np=1


altfel daca S=min np=np+1 sf.
sf.
sf.
sf.
sf.
scrie min, np

12.3.2 Cod sursă

Listing 12.3.1: tort.c


// sursa 100 p - Nistor Mot
#include <stdio.h>
#define M 502

int a[M][M];

int main()
{
FILE *fi,*fo;
int i,j,m,n,k,k1,min,s,np;

fi=fopen("tort.in","rt");
fscanf(fi,"%d %d %d",&m,&n,&k);

for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
fscanf(fi,"%d",&a[i][j]);
fclose(fi);

min=m*n+1;
for(i=2;i<=m;i++)
a[i][1]+=a[i-1][1];

for(j=2;j<=n;j++)
a[1][j]+=a[1][j-1];

for(i=2;i<=m;i++)
for(j=2;j<=n;j++)
a[i][j]+=(a[i-1][j]+a[i][j-1]-a[i-1][j-1]);

for(k1=0;k1<=k;k1++)
for(i=m-k1;i<=m;i++)
for(j=n-k+k1;j<=n;j++)
{
s=a[i][j]-a[i-m+k1][j]-a[i][j-n+k-k1]+a[i-m+k1][j-n+k-k1];
if(s<min)
{
min=s;
np=1;
}
else
if(s==min) np++;
}

fo=fopen("tort.out","wt");
fprintf(fo,"%d\n%d\n",a[m][n]-min,np);
fclose(fo);
return 0;
}

12.3.3 *Rezolvare detaliată

12.4 ape
Problema 4 - ape 100 de puncte
CAPITOLUL 12. ONI 2011 12.4. APE 343

Mihai crede că mă pricep la informatică şi mă roagă să ı̂l ajut la efectuarea unor calcule. Mi-a
povestit că ı̂n vacanţă a fost la ţară la bunici. Bunicii lui se ocupă de piscicultură şi au preluat
spre utilizare o zonă de teren unde se află lacuri, heleştee şi bălţi. Sunt plătite taxe speciale ı̂n
funcţie de suprafeţele acestor ape. Bunicului i se pare că cei de la oficiul unde se plătesc taxele au
date greşite ı̂n dosare, despre ariile acestor suprafeţe de apă şi l-a rugat pe Mihai să ı̂i calculeze cu
aproximaţie aceste arii. Mihai a studiat problema şi s-a hotărât să ı̂nconjoare fiecare apă, mergând
pe conturul acesteia. Pasul lui are lungimea de 1 metru. La fiecare pas Mihai foloseşte o busolă
şi ı̂şi notează ı̂ntr-un carneţel direcţia ı̂nspre care a fost efectuat pasul Nord, Sud, Est sau Vest.
După fiecare pas Mihai actualizează şi numărul de paşi pe care i-a făcut.

Cerinţe

Se doreşte să se afle, pentru fiecare traseu:


a) Dimensiunile pe direcţiile Vest-Est şi respectiv Nord-Sud ale unei suprafeţe dreptunghiulare
de arie minimă care cuprinde ı̂n interior sau pe margini suprafaţa apei.
b) Sensul ı̂n care a fost parcurs traseul: 0- pentru sens orar, respectiv 1- pentru sens invers
orar;
c) Aria suprafeţei apei ı̂nconjurate, din interiorul traseului.

Date de intrare

Fişierul de intrare ape.in are pe prima linie numărul P de paşi ai traseului. ı̂n linia 2 se află
un şir de P litere mari, fără spaţii ı̂ntre ele, din mulţimea {N, S, E, V} reprezentând traseul.

Date de ieşire

Fişierul de ieşire ape.out va conţine patru numere naturale separate prin câte un spaţiu:
primul număr reprezintă dimensiunea pe direcţia Vest-Est şi al doilea număr reprezintă dimensi-
unea pe direcţia Nord-Sud a suprafeţei dreptunghiulare de arie minimă care cuprinde ı̂n interior
sau pe margini suprafaţa apei delimitată de traseu; al treilea număr reprezintă sensul parcurgerii,
iar al patrulea număr reprezintă aria.

Restricţii şi precizări

a 1 & P & 10 000


a Pentru 30% din teste traseele vor avea lungimea maximă 2 000.
a Dacă dimensiunile suprafeţei dreptunghiulare de arie minimă sunt corecte, atunci se va
acorda 10% din punctaj/test.
a Dacă sensul traseului este determinat corect, atunci se vor primi 10% puncte/test.
a Aria suprafeţei apei este un număr natural nenul.

Exemple:

ape.in ape.out Explicaţii


16 3315 Căsuţele marcate cu ’-’ reprezintă traseul, iar pătratele
NNVVSVSSESEEENNV negre reprezintă apa.
Zona mărginită de linia ı̂ngroşată
reprezintă suprafaţa dreptunghiu-
lară minimală ce conţine pătratele
negre, care reprezintă apa.
Căsuţa marcată cu * este poziţia
de pornire.
Timp maxim de executare/test: 0.1 secunde
Memorie: total 2 MB
Dimensiune maximă a sursei: 5 KB

12.4.1 Indicaţii de rezolvare

prof. Piţ-Rada Ionel-Vasile, Colegiul Naţional ”Traian”, Drobeta Turnu Severin


CAPITOLUL 12. ONI 2011 12.4. APE 344

Presupunem că pornim cu poziţia x; y  0; 0. ı̂n timpul parcurgerii putem păstra maximul
şi minimul absciselor (xM şi xm) şi respectiv al ordonatelor (yM şi ym ) din care vom calcula
dimensiunile suprafeţei dreptunghiulare minime.
Pentru calcularea ariei vom porni din nou din 0; 0. Ne imaginăm că putem marca traseul
ı̂ntr-o matrice ı̂n care toate poziţiile sunt marcate iniţial cu 0. După fiecare pas ajungem din
poziţia anterioară x; y  la poziţia curentă x1; y1.
Vom parcurge traseul presupunând că o facem ı̂n sens orar şi vom efectua marcaje ı̂n matrice
ı̂n anumite puncte din traseu, şi anume ı̂n acelea care constituie, abscisele minime şi respectiv
maxime relative la aceeaşi ordonată y. Cu alte cuvinte fiecare bandă orizontală de ı̂nălţime 1 şi
lungime oricât de mare va intersecta apa formându-se una sau mai multe zone dreptunghiulare
de ı̂nălţime 1 şi lungime egală cu diferenţa dintre abscisa maximă şi cea minimă relative la acea
zonă.
Se poate observa că de fapt trebuie doar adunate/scăzute abscisele extreme care corespund
acelor zone de apă. Mai mult se poate observa că nu contează ordinea ı̂n care se adună/scad acele
valori, astfel că se pot efectua operaţiile atunci când ajungem la aceste extreme, şi deci nu avem
nevoie de matrice.
Dacă la sfârşitul calculelor obţinem valoare negativă, atunci sensul parcurgerii este cel invers
orar şi aria se calculează scăzând din modulul valorii ariei calculate numărul celulelor parcurse,
iar ı̂n cazul ı̂n care valoarea este pozitivă valoarea se păstrează nemodificată.
Complexitate O p

12.4.2 Cod sursă

Listing 12.4.1: ape.cpp


// Sursa 100 p - Pit-Rada Ionel Vasile
#include<fstream>

using namespace std;

int P,i,x=0,y=0,s=0,xm=0,xM=0,ym=0,yM=0,sens;
char a,b,c;

int main()
{
ifstream f1("ape.in",ios::in);
ofstream f2("ape.out",ios::out);
f1>>P;
f1>>a;
c=a;
for (i=2;i<=P+1;i++)
{
if (i>P)
b=c;
else
f1>>b;

if (a==’N’)
switch (b)
{
case ’N’ : {s=s-x-1; y++; break;}
case ’E’ : {x++; break;}
case ’V’ : {s=s-x-1; x--; break;}
}
else
if (a==’V’)
switch (b)
{
case ’N’ : {y++; break;}
case ’V’ : {x--; break;}
case ’S’ : {s=s+x; y--; break;}
}
else
if (a==’S’)
switch (b)
{
case ’S’ : {s=s+x; y--; break;}
case ’V’ : {x--; break;}
CAPITOLUL 12. ONI 2011 12.5. EC 345

case ’E’ : {s=s+x; x++; break;}


}
else
switch (b)
{
case ’S’ : {y--; break;}
case ’E’ : {x++; break;}
case ’N’ : {s=s-x-1; y++; break;}
}

if (x>xM) xM=x;
if (x<xm) xm=x;
if (y>yM) yM=y;
if (y<ym) ym=y;
a=b;
}

f2<<(xM-1-xm)<<" "<<(yM-1-ym)<<" ";

if (s<0)
{
sens=1;
s=-s;
s=s-P;
}
else
sens=0;

f2<<sens<<" "<<s<<endl;
f1.close();
f2.close();
return 0;
}

12.4.3 *Rezolvare detaliată

12.5 ec
Problema 5 - ec 100 de puncte
Alexandru are la dispoziţie un tablou pătratic de dimensiune n cu numere ı̂ntregi şi k ecuaţii
de tipul I şi II. Ecuaţiile de tipul I sunt de forma: ax  b c, cu a, b, c numere naturale, iar
2
ecuaţiile de tipul II sunt de forma: ax  bx  c d, cu a, b, c, d numere naturale.
Alexandru ı̂şi propune să determine pentru fiecare tip de ecuaţie: numărul lor şi câte dintre
ele au rădăcinile ı̂n tabloul dat.

Cerinţe

Să se scrie un program care determină numărul de ecuaţii de tipul I, câte dintre acestea au
exact o rădăcină ı̂n tablou, respectiv numărul de ecuaţii de tipul II şi câte dintre acestea au exact
ambele rădăcini ı̂n tablou.

Date de intrare

Fişierul de intrare ec.in va conţine: pe prima linie numerele naturale n şi k separate printr-un
spaţiu, pe următoarele n linii elementele tabloului separate prin câte un spaţiu, iar pe următoarele
k linii ecuaţiile ı̂n forma din enunţ, câte una pe fiecare linie.

Date de ieşire

Fişierul de ieşire ec.out va conţine pe prima linie două numere separate printr-un spaţiu
reprezentând numărul de ecuaţii de tipul I, respectiv numărul de ecuaţii de tipul I care au ex-
act o rădăcină, aflată ı̂n tablou, iar pe a doua linie tot două numere separate printr-un spaţiu
reprezentând numărul de ecuaţii de tipul II, respectiv numărul de ecuaţii de tipul II cu exact două
rădăcini, ambele rădăcini ı̂n tablou.
CAPITOLUL 12. ONI 2011 12.5. EC 346

Restricţii şi precizări

a 1 & n & 500


a 1 & k & 1000
a Elementele tabloului sunt numere ı̂ntregi cu maxim 4 cifre fiecare;
a La fiecare ecuaţie de tipul I a, b, c vor fi precizate, chiar dacă acestea au valoarea 0 sau 1,
(de exemplu x  2 3 va apare 1x  2 3).
a La fiecare ecuaţie de tipul II a, b, c, d vor fi precizate, chiar dacă acestea au valoarea 0 sau 1,
2
(de exemplu x2  1 3 va apare 1x  0x  1 3).
a Pentru ecuaţiile de tipul I a, b, c sunt numere naturale cu maxim 4 cifre;
a Pentru ecuaţiile de tipul II a, b, c, d sunt numere naturale cu maxim 4 cifre;
a Se va acorda: 10% din punctaj pentru numărul de ecuaţii de tipul I, 30% din punctaj pentru
câte dintre ele au exact o rădăcină ı̂n tablou, 20% din punctaj pentru numărul de ecuaţii de tipul
II şi 40% din punctaj pentru câte dintre ele au exact ambele rădăcini ı̂n tablou.

Exemple:

ec.in ec.out
Explicaţii
25 21 Prima ecuaţie este de tipul I si are rădăcina 0, care nu se găseşte
12 31 ı̂n tablou.
2 -1 A doua ecuaţie este de tipul II şi are două rădăcini egale cu -1,
1x+0=0 care se găsesc ı̂n tablou.
20xˆ2+40x+20=0 A treia ecuaţie este de tipul I şi are rădăcina 2, care se găseşte
101x+200=402 ı̂n tablou.
2xˆ2+1x+4=0 A patra ecuaţie este de tipul II şi nu are rădăcinile ı̂n tablou.
1xˆ2+1x+3=5 A cincea ecuaţie este de tipul II şi are rădăcinile -2 şi 1, dar nu
sunt amândouă ı̂n tablou.
Timp maxim de executare/test: 0.8 secunde
Memorie: total 4 MB
Dimensiune maximă a sursei: 5 KB

12.5.1 Indicaţii de rezolvare

prof. Doru Anastasiu Popescu, C.N. ”Radu Greceanu”, Slatina

1. Se citesc numerele din tablou şi se construieşte un vector cu componentele distincte: x


x1 , x2 , ..., xp .
2. Se ordonează crescător vectorul x.
3. Se citesc pe rând caracterele, pentru câte o ecuaţie, se verifică dacă este ecuaţie de tipul I sau
II şi se construiesc coeficienţii (de tip numeric), apoi se determină rădăcinile: x=(c-d)/a, dacă
a j 0, respectiv x1=(-b+sqrt(b2-4ac))/(2a), x2=(-b-sqrt(b2-4ac))/(2a), dacă b  4ac ' 0.
2

Se trateaza cazurile speciale in functie de coeficienti


4. În funcţie de tipul de ecuaţie se măreşte cu 1 numărul de ecuaţii de gradul I, sau cel de
gradul II.
5. Pentru rădăcinile determinate anterior, acestea se caută ı̂n vectorul x, folosind un algoritm
eficient. În cazul afirmativ se măreşte numărul de ecuaţii cu soluţii ı̂n tablou.
6. Se afişează numerele determinate.

12.5.2 Cod sursă

Listing 12.5.1: ec.cpp


// Sursa 100 p - Robert Hasna
#include <cstdio>
#include <cstring>
#include <cmath>

#define Lmax 100


#define Nmax 20000
CAPITOLUL 12. ONI 2011 12.5. EC 347

using namespace std;

int N, M, L, nr_ec1, nr_ec2, sol_ec1, sol_ec2;


int v[Nmax];
char line[Lmax];

inline bool is_int(long double x)


{
return abs((int)x - x) < 0.000000000001L;
}

int main()
{
freopen("ec.in", "r", stdin);
freopen("ec.out", "w", stdout);

scanf("%d %d\n", &N, &M);

L = N*N;
for (int i = 0; i < L; ++i)
{
int x;
scanf("%d ",&x);
v[x+10000] = 1;
}

int coef[4], nr_coef;


for (int i = 0; i < M; ++i)
{
scanf("%s\n", line);
nr_coef = 0;
int val = 0;
for (int j = 0, size = strlen(line); j < size; ++j)
{
if (line[j] <= ’9’ && line[j] >= ’0’)
val = val * 10 + line[j] - ’0’;

if (line[j] == ’x’)
{
coef[nr_coef++] = val;
val = 0;
if (line[j + 1] == ’ˆ’)
j+=3;
else
++j;
}

if (line[j] == ’=’)
{
coef[nr_coef++] = val;
val = 0;
}
}

coef[nr_coef++] = val;

long double sol;

if (nr_coef == 3)
{
//ec gr1
++nr_ec1;
if (coef[0] == 0) continue;
sol = ((long double)(coef[2] - coef[1])) / coef[0];
if (is_int(sol) && v[(int)sol+10000])
++sol_ec1;
}
else
{
//ec gr 2
++nr_ec2;
bool ok = true;
int delta = coef[1]*coef[1] - 4*coef[0]*(coef[2] - coef[3]);
if (coef[0] == 0)
CAPITOLUL 12. ONI 2011 12.6. FURNICI 348

{
if (coef[1] == 0)
continue;
sol = ((long double)(coef[3] - coef[2])) / coef[1];
if (is_int(sol) && v[(int)sol+10000])
++sol_ec2;
continue;
}

if (delta < 0) continue;

sol = (long double)(-coef[1] + sqrtl(delta)) / (2*coef[0]);


if (!(is_int(sol) && v[(int)sol+10000]))
ok = false;

sol = (long double)(-coef[1] - sqrtl(delta)) / (2*coef[0]);


if (is_int(sol) && v[(int)sol+10000] && ok)
++sol_ec2;
}
}

printf("%d %d\n%d %d\n", nr_ec1, sol_ec1, nr_ec2, sol_ec2);


return 0;
}

12.5.3 *Rezolvare detaliată

12.6 furnici
Problema 6 - furnici 100 de puncte
La Institutul de cercetare al insectelor s-a descoperit că dacă furnicile sunt puse pe o bară
metalică, ele au un comportament bine definit după următoarele reguli:
1. Imediat cum a fost pusă pe bară ea ı̂şi ı̂ncepe deplasarea ı̂n sensul ı̂n care a fost orientată,
cu viteza constantă de 1cm/s. Furnica nu se opreşte cât timp se află pe bara metalică chiar
dacă se ciocneşte cu altă furnică.
2. Dacă pe drum nu se ı̂ntâlneşte cu altă furnică ea ı̂şi va continua deplasarea până când va
cădea de pe bară.
3. Când două furnici se ı̂ntâlnesc, ele ı̂şi schimbă amândouă instantaneu sensul de deplasare.

Cerinţe

Ştiind că pe o bară metalică de lungime L cm se plasează exact N furnici ı̂n poziţii cunoscute şi
cu sensul iniţial de deplasare cunoscut, să se scrie un program care calculează numărul de secunde
după care va cădea de pe bară şi ultima furnică de la momentul iniţial. Toate furnicile ı̂şi ı̂ncep
deplasarea concomitent.

Date de intrare

Fişierul de intrare furnici.in conţine pe prima linie două numere naturale L şi N separate
printr-un spaţiu. Apoi urmează N linii cu câte 2 valori: pozi şi sensi separate printr-un spaţiu,
pozi este un număr natural care reprezintă coordonata la care se află furnica i la momentul iniţial,
iar sensi este un caracter din mulţimea {’S’, ’D’} ce arată sensul de deplasare iniţial pe care ı̂l
are furnica i (S pentru stânga şi D pentru dreapta).

Date de ieşire

Fişierul de ieşire furnici.out va conţine un singur număr care reprezintă timpul la care a căzut
ultima furnică.

Restricţii şi precizări

a 1 $ L $ 10 000 000
a 0 $ N $ 100 000
a 0 & pozi & L
CAPITOLUL 12. ONI 2011 12.6. FURNICI 349

Exemple:

furnici.in furnici.out Explicaţii


10 3 8 Bara are lungimea de 10 cm şi pe bară vor fi plasate 3 furnici
4D la distanţele de 4, 8, 1 faţă de capătul din stânga al barei şi vor
8S avea următoarele sensuri de deplasare: dreapta, stânga respectiv
1S stânga.
Primele două furnici se vor ı̂ntâlni după 2 secunde ı̂n punctul
de coordonată 6 şi ı̂şi vor schimba sensul de deplasare.
După schimbarea de sens a doua furnică va parcurge ı̂ncă 4cm
către dreapta şi va cădea, ı̂n timp ce prima furnică după schim-
barea de sens va parcurge ı̂ncă 6 cm către stânga, până la cădere.
În acest timp a treia furnică a căzut de pe bara după 1 secundă.
Pe bară nu vor mai fi furnici după 8 secunde.

Timp maxim de executare/test: 0.2 secunde


Memorie: total 2 MB
Dimensiune maximă a sursei: 5 KB

12.6.1 Indicaţii de rezolvare

student Robert Hasna Universitatea Bucuresti

Soluţia 1: 20 puncte
Se memorează ı̂ntr-un vector de lungime L (lungimea barei metalice) pe poziţia k direcţia
ı̂n care se deplasează furnica ce se afla pe bara la punctul de coordonata k. Apoi se simulează
deplasările cu pasul o unitate de timp pana când tot vectorul devine nul. Rezultatul este numarul
de paşi. Pentru a evita ciocnirile ı̂n coordonate raţionale este suficient să se observe că cioncirile ı̂n
aceste coordonate sunt de forma k  0.5. Lungimea vectorului se dublează şi direcţia de deplasare
a furnicilor care se află la coordonata k va fi salvată pe poziţia 2 ˜ k. Rezultatul este numărul de
paşi / 2.
Complexitate : O(L*timpul de cădere al ultimei furnici)
Soluţia 2: 50-60 puncte
Se observă că dacă sortăm furnicile crescător după coordonata la care se află iniţial pe bară,
furnica aflată pe poziţia k ı̂n vectorul sortat va rămâne pe poziţia k tot timpul simulării. (cu alte
cuvinte: dacă are x furnici ı̂n stânga şi y ı̂n dreapta, x şi y rămân constante. Furnicile care cad
se consideră prezente şi ele).
În vectorul de furnici sortat memorăm ca valoare reală coordonata la care se află fiecare furnică.
Parcurgem vectorul şi calculăm timpul maxim ı̂n care furnicile se pot deplasa fără să se ciocnească,
La o nouă parcurgere actualizăm coordonatele şi schimbăm sensurile la eventualele ciocniri (cel
puţin una).
CAPITOLUL 12. ONI 2011 12.6. FURNICI 350

Se observă că dacă la pasul curent nu este nici o furnică orientată spre exteriorul barei, la pasul
viitor cel puţin una va fi orientată spre exterior şi o scoatem din calcul ţinând cont de timpul care
s-a scurs până aceasta a căzut. Repetăm până când bara se eliberează. Rezultatul este maximul
dintre aceşti timpi.
2
Complexitate : O N 
Soluţie 3: 100 puncte
Trebuie să se observe următorul lucru :

În desen sunt două furnici ı̂n punctele A şi B care se deplasează ı̂n dreapta respectiv ı̂n stânga.
Deplasarea furnicii A : L©2  L©2  Y L  Y
Deplasarea furnicii B : L©2  L©2  X L  X
Se observă că după L secunde furnicile sunt ı̂n acelaşi loc ca la ı̂nceput dar cu sensuri de
deplasare inversate. Să consideram scenariul ı̂n care furnicile ”trec una prin alta” atunci când se
intalnesc:
După L secunde furnica A se va afla ı̂n punctul B iar furnica B ı̂n punctul A. Dacă nu ţinem
cont de notarea furnicilor (fapt care nu contează) suntem ı̂n acelaşi caz ca ı̂n regulile din enunţ
după L secunde.
Aceasta proprietate este adevarată doar ı̂n cazul in care furnicile au aceeaşi viteză (1cm/s
specificat ı̂n enunţ).
Proprietatea este adevarată şi pentru N furnici.
Deci problema cere să se calculeze maximul dintre distanţele pe care le au de parcurs furnicile
neţinand cont de ciocnirile dintre ele.
Complexitate O N 

12.6.2 Cod sursă

Listing 12.6.1: furnici.cpp


/*
Problema Furnici
Complexitate timp O(N)
Complexitate spatiu O(1)
sursa 100 p - Robert Hasna
*/

#include <cstdio>

using namespace std;

int L, N, rez, x;
char dir;

inline int max(int x, int y)


{
return x > y ? x : y;
}

int main()
{
freopen("furnici.in", "r", stdin);
freopen("furnici.out", "w", stdout);

scanf("%d %d\n", &L, &N);


for (int i = 0; i < N; ++i)
{
scanf("%d %c\n", &x, &dir);
if (dir == ’S’)
rez = max(rez, x);
else
rez = max(rez, L - x);
CAPITOLUL 12. ONI 2011 12.6. FURNICI 351

printf("%d\n", rez);
return 0;
}

12.6.3 *Rezolvare detaliată


Capitolul 13

ONI 2010

13.1 cern
Problema 1 - cern 100 de puncte
”CERN este un acronim folosit pentru a desemna Laboratorul Eu-
ropean pentru Fizica Particulelor Elementare. Acronimul s-a păstrat
de la vechea denumire ı̂n limba franceză, şi anume Conseil Européen
pour la Recherche Nucléaire. Acesta este cel mai mare laborator de
cercetare a particulelor elementare din lume, situat ı̂n suburbia nord-
vestică a Genevei, chiar pe graniţa dintre Elveţia şi Franţa. Funcţia
primară a complexului CERN este de a furniza acceleratoare de par-
ticule elementare şi alte tipuri de infrastructuri necesare fizicii par-
ticulelor de energii ı̂nalte.”
Acceleratorul de particule CERN este dispus sub forma a 3 cercuri cu aceeaşi rază, tangente
exterioare două câte două, numerotate pe figură cu 1, 2, 3. Traiectoria unei particule elementare
porneşte din unul din punctele marcate pe figură cu A, B, C, D, E, F şi se deplasează cu viteză
constantă de 10/unitatea de timp numai pe circumferinţa cercurilor.
La trecerea printr-un punct de tangenţă dintre două cercuri particula ı̂şi schimbă atât sensul
de deplasare, cât şi cercul pe care se deplasează.
Astfel, dacă sensul de deplasare a fost la un moment dat trigonometric,
la trecerea printr-un punct de tangenţă devine invers trigonometric şi dacă
sensul de deplasare a fost invers trigonometric, la trecerea printr-un punct
de tangenţă devine trigonometric.

Cerinţe
o
Ştiind că cercurile ce formează acceleratorul sunt marcate din grad ı̂n grad ı̂ncepând cu 0 , ı̂n
sens trigonometric (aşa cum se indică ı̂n figura alăturată), să se scrie un program, care, cunoscând
punctul iniţial şi sensul de deplasare al unei particule, să determine poziţia particulei ı̂n accelerator
după un număr dat de unităţi de timp.

Date de intrare

Prima linie a fişierului de intrare cern.in conţine un caracter p ce indică punctul de plecare
al particulei.
A doua linie a fişierului de intrare conţine două numere ı̂ntregi s şi t, separate printr-un spaţiu,
ce indică sensul de deplasare (1 pentru sens trigonometric şi -1 pentru sens invers trigonometric),
respectiv numărul de unităţi de timp cât durează deplasarea.

Date de ieşire

Pe prima linie a fişierului de ieşire cern.out se vor scrie două numere naturale g şi c, sep-
arate printr-un spaţiu, ce reprezintă numărul de grade, ı̂n sens trigonometric, respectiv cercul,
corespunzătoare poziţiei finale unde se va găsi particula după trecerea celor t unităţi de timp.

Restricţii şi precizări

352
CAPITOLUL 13. ONI 2010 13.1. CERN 353

a p " {’A’,’B’,’C’,’D’,’E’,’F’}
a s " {-1, 1}
a 0 & t & 1.000.000.000
a 0 & g & 359
a c " {1, 2, 3}
a pentru toate seturile de date de intrare, poziţia finală a particulei nu coincide cu unul dintre
punctele de tangenţă dintre cercuri.

Exemple:

cern.in cern.out
Explicaţii
A 200 3 Particula pleacă din punctul A ı̂n sens trigonometric şi are traseul:
o
1 320 a 180 pe cercul 1 ı̂n sens trigonometric
o
a 60 pe cercul 2 ı̂n sens invers trigonometric
o
a 80 pe cercul 3 ı̂n sens trigonometric
o
Poziţia finală este la 200 pe cercul 3
Timp maxim de executare/test: 0.1 secunde
Memorie: total 2 MB din care pentru stivă 1 MB
Dimensiune maximă a sursei: 20 KB

13.1.1 Indicaţii de rezolvare

prof. Sichim Cristina - Colegiul Naţional ”Ferdinand I” Bacău


prof. Cheşcă Ciprian - Grup şcolar ”Costin Neniţescu” Buzău

Deoarece toate cercurile au aceeaşi rază, triunghiul format cu centrele celor 3 cercuri este
o
echilateral şi va avea ı̂n consecinţă toate unghiurile de 60 . În felul acesta punctele de plecare ale
particulei sunt aşezate după cum urmează:
o
a ’A’ - 0 pe cercul 1
o
a ’B’ - 60 pe cercul 1
o
a ’C’ - 120 pe cercul 2
o
a ’D’ - 180 pe cercul 2
o
a ’E’ - 240 pe cercul 3
o
a ’F’ - 300 pe cercul 3
Următoarea observaţie legată de rezolvarea problemei este că după
o
un număr de 6π unităţi de timp (1080 ) particula va trece din nou prin
punctul de plecare, indiferent care este acesta. Problema nu necesită tipuri de date structurate
ı̂nsă presupune o analiza atentă a tuturor situaţiilor ı̂n care apar treceri de la un cerc la altul şi
o o
respectiv ı̂n care se fac treceri de la 360 la 0 ı̂n concordanţă cu sensul de deplasare.
Aceste situaţii sunt:
1. cercul 1 tangenţă cu cercurile 2 şi 3
2. cercul 2 tangenţă cu cercurile 3 şi 1
3. cercul 3 tangenţă cu cercurile 1 şi 2
o
4. trecerea prin 360 pentru cercurile 1 şi 3
Se analizează toate aceste situaţii şi se stabileşte modul de trecere de la un cerc la altul ţinând
cont desigur şi de sensul de deplasare al particulei. Problema poate fi abordată iterativ, recursiv
sau cel mai interesant numai prin tratarea punctelor de tangenţă (soluţia cea mai rapidă, ce nu
necesită instrucţiuni repetitive ci numai instrucţiuni de decizie).

13.1.2 Cod sursă

Listing 13.1.1: cern100v1.cpp


1 // Sursa 100 puncte numai cu if-uri prof. Cristina Sichim - Bacau
2 # include <fstream>
3
4 using namespace std;
5
6 ifstream fi("cern.in");
CAPITOLUL 13. ONI 2010 13.1. CERN 354

7 ofstream fo("cern.out");
8
9 long t;
10 int g,s,cerc;
11 char p;
12
13 int main()
14 { fi>>p>>s>>t;
15 t=t%1080;
16
17 switch(p)
18 { case ’A’:
19 if (s==1)
20 if(t<180) fo<<t<<" 1\n", t=0; else t=t-180,cerc=2,p=’G’;
21 else
22 if(t<120) fo<<360-t<<" 1\n",t=0; else t=t-120,cerc=3,p=’I’;
23 break;
24 case ’B’:
25 if (s==1)
26 if(t<120) fo<<t+60<<" 1\n", t=0; else t=t-120,cerc=2,p=’G’;
27 else
28 if(t<180) fo<<(360-(t-60))%360<<" 1\n",t=0;
29 else t=t-180,cerc=3,p=’I’;
30 break;
31 case ’C’:
32 if (s==1)
33 if(t<180) fo<<t+120<<" 2\n", t=0; else t=t-180,cerc=3,p=’H’;
34 else
35 if(t<120) fo<<120-t<<" 2\n",t=0; else t=t-120,cerc=1,p=’G’;
36 break;
37 case ’D’:
38 if (s==1)
39 if(t<120) fo<<t+180<<" 2\n", t=0; else t=t-120,cerc=3,p=’H’;
40 else
41 if(t<180) fo<<180-t<<" 2\n",t=0; else t=t-180,cerc=1,p=’G’;
42 break;
43 case ’E’:
44 if (s==1)
45 if(t<180) fo<<(240+t)%360<<" 3\n", t=0;
46 else t=t-180,cerc=1,p=’I’;
47 else
48 if(t<120) fo<<240-t<<" 3\n",t=0; else t=t-120,cerc=2,p=’H’;
49 break;
50
51 case ’F’:
52 if (s==1)
53 if(t<120) fo<<(300+t)%360<<" 3\n", t=0;
54 else t=t-120,cerc=1,p=’I’;
55 else
56 if(t<180) fo<<300-t<<" 3\n",t=0; else t=t-180,cerc=2,p=’H’;
57 break;
58 }
59
60 s=-s;
61
62 while(t)
63 { switch(p)
64 { case ’G’:
65 if(s==1)
66 if(cerc==1)
67 if (t>60)t=t-60,p=’I’; else fo<<180+t<<" 1\n",t=0;
68 else
69 if (t>300) t=t-300,p=’H’; else fo<<t<<" 2\n",t=0;
70 else
71 if(cerc==1)
72 if(t>300) t=t-300,p=’I’;else fo<<(540-t)%360<<" 1\n",t=0;
73 else
74 if(t>60) t=t-60,p=’H’; else fo<<360-t<<" 2\n",t=0;
75
76 cerc=3;
77 break;
78 case ’H’:
79 if(s==1)
80 if(cerc==3)
81 if (t>300)t=t-300,p=’I’; else fo<<120+t<<" 3\n",t=0;
82 else
CAPITOLUL 13. ONI 2010 13.1. CERN 355

83 if(t>60) t=t-60,p=’G’; else fo<<300+t<<" 2\n",t=0;


84 else
85 if(cerc==3)
86 if(t>60) t=t-60,p=’I’; else fo<<120-t<<" 3\n",t=0;
87 else
88 if(t>300) t=t-300,p=’G’; else fo<<(300-t)%360<<" 2\n",t=0;
89
90 cerc=1;
91 break;
92
93 case ’I’:
94 if(s==1)
95 if(cerc==1)
96 if(t>300)t=t-300,p=’G’; else fo<<(240+t)%360<<" 1\n",t=0;
97 else
98 if(t>60) t=t-60,p=’H’; else fo<<60+t<<" 3\n",t=0;
99 else
100 if(cerc==1)
101 if(t>60) t=t-60,p=’G’; else fo<<240-t<<" 1\n",t=0;
102 else
103 if(t>300) t=t-300,p=’H’; else fo<<(420-t)%360<<" 3\n",t=0;
104 cerc=2;
105 break;
106 }
107 s=-s;
108 }
109
110 fi.close();
111 fo.close();
112 return 0;
113 }

Listing 13.1.2: cern100v2.cpp


1 //ciprian chesca
2 #include <fstream>
3
4 using namespace std;
5
6 ifstream f("cern.in");
7 ofstream g("cern.out");
8
9 int main()
10 {
11 char p;
12 int s,c=1,pp=0;
13 long long t;
14
15 // citire date de intrare
16 f>>p;
17 f>>s;f>>t;
18
19 switch (p)
20 {
21 case ’A’:pp=0;c=1;break;
22 case ’B’:pp=60;c=1;break;
23 case ’C’:pp=120;c=2;break;
24 case ’D’:pp=180;c=2;break;
25 case ’E’:pp=240;c=3;break;
26 case ’F’:pp=300;c=3;break;
27 }
28
29 int i,pozitie=pp,sens=s,cerc=c;
30 for(i=1;i<=t%(3*360);i++)
31 {
32 switch (pozitie)
33 {
34 case 180 : if(sens==1&&cerc==1)
35 {
36 pozitie=360;
37 sens=-1;
38 cerc=2;
39 }
40 if (sens==-1&&cerc==1)
41 {
CAPITOLUL 13. ONI 2010 13.2. CMMMC 356

42 pozitie=0;
43 sens=1;
44 cerc=2;
45 }
46 break;
47 case 0 : if (cerc==2)
48 {
49 pozitie=180;
50 sens=sens*(-1);
51 cerc=1;
52 }
53 // trecerea prin 0 pentru cercurile 1 si 3
54 if (cerc==1&&sens==-1) pozitie=360;
55 if (cerc==3&&sens==-1) pozitie=360;
56 break;
57 case 300 : if (cerc==2)
58 {
59 pozitie=120;
60 sens=sens*(-1);
61 cerc=3;
62 }
63 break;
64 case 120 : if (cerc==3)
65 {
66 pozitie=300;
67 sens=sens*(-1);
68 cerc=2;
69 }
70 break;
71 case 60 : if (cerc==3)
72 {
73 pozitie=240;
74 sens=sens*(-1);
75 cerc=1;
76 }
77 break;
78 case 240 : if (cerc==1)
79 {
80 pozitie=60;
81 sens=sens*(-1);
82 cerc=3;
83 }
84 break;
85
86 case 360 : if (cerc==2&&sens==1)
87 {
88 pozitie=180;
89 cerc=1;
90 sens=sens*(-1);
91 }
92 // situatia cand trecem prin 360 grade
93 // pe cercurile 1 si 3
94 if (cerc==1&&sens==1) pozitie=0;
95 if (cerc==3&&sens==1) pozitie=0;
96 break;
97 }
98 pozitie=pozitie+sens;
99 }
100
101 g<<pozitie%360<<" "<<cerc<<"\n";
102 f.close();
103 g.close();
104
105 return 0;
106 }

13.1.3 *Rezolvare detaliată

13.2 cmmmc
Problema 2 - cmmmc 100 de puncte
CAPITOLUL 13. ONI 2010 13.2. CMMMC 357

Definim noţiunea de pereche ordonată, perechea de numere naturale x, y  cu x & y. Definim


cel mai mic multiplu comun al unei perechi ordonate ca fiind cel mai mic multiplu comun al
numerelor care formează perechea.
Se dau k numere naturale n1 , n2 , ..., nk .

Cerinţe

Să se determine pentru fiecare dintre numerele ni (i = 1, 2, ..., k):


a) câte perechi ordonate au cel mai mic multiplu comun egal cu ni .
b) dintre acestea, perechea ordonată care are suma minimă.

Date de intrare

Prima linie a fişierului cmmmc.in conţine un număr natural k. Următoarele k linii din acest
fişier vor conţine câte un număr natural; linia i  1 va conţine numărul ni (i 1, 2, ..., k) .

Date de ieşire

Fişierul cmmmc.out va conţine k linii. Pe fiecare dintre acestea se vor afla trei numere. Cele
trei numere de pe linia i vor reprezenta:
- primul, numărul de perechi ordonate care au cel mai mic multiplu comun egal cu ni ;
- următoarele două, numerele care alcătuiesc perechea ordonată care are cel mai mic multiplu
comun egal cu ni şi a căror sumă este minimă, afişate ı̂n ordine crescătoare.

Restricţii şi precizări

a 1 & k & 100


a 1 & ni & 2 000 000 000
a Pentru 20% dintre teste, k & 100 şi ni & 1 000
a Fiecare dintre cele k linii ale fişierului cmmmc.out trebuie să conţină exact trei numere
separate prin câte un spaţiu; ı̂n caz contrar, soluţia se consideră greşită şi se obţin 0 puncte
pentru testul respectiv. Rezolvarea corectă a cerinţei a) valorează 40% din punctajul unui test iar
rezolvarea corectă a cerinţei b) 60%.

Exemple:

cmmmc.in cmmmc.out Explicaţii


2 525 Există cinci perechi distincte care au cel mai mic multiplu comun
10 2 1 11 egal cu 10: (1,10), (2,10), (5,10), (2,5) (10,10). Dintre acestea
11 perechea cu cea mai mică sumă este (2,5).
Pentru n=11 există două perechi ordonate care au cel mai mic
multiplu comun 11: (1,11), (11,11). Dintre acestea perechea cu
cea mai mică sumă este (1,11).
Timp maxim de executare/test: 0.2 secunde
Memorie: total 2 MB din care pentru stivă 1 MB
Dimensiune maximă a sursei: 10 KB

13.2.1 Indicaţii de rezolvare

Stelian Ciurea, Pătcaş Csaba


q q q
Fie n p11 ˜ p22 ˜ ... ˜ pkk scrierea ı̂n descompunere de factori primi a numărului n.
Pentru ca două numere a şi b să aibă cel mai mare multiplu comun egal cu n, pentru fiecare
factor prim pi avem două posibilităţi:
pi apare la puterea qi ı̂n a, şi la o putere oarecare de la 0 la qi ı̂n b,
sau apare la puterea qi ı̂n b, şi la o putere oarecare de la 0 la qi ı̂n a.
În total avem deci qi  1  qi  1 2 ˜ qi  2 variante, dar am numărat de două ori varianta,
când pi apare ı̂n ambele numere la puterea qi , deci numărul corect al variantelor va fi 2 ˜ qi  1.
Aşadar numărul total al perechilor, care au cel mai mare divizor comun egal cu n este 2 ˜
q1  1 ˜ 2 ˜ q2  1 ˜ ... ˜ 2 ˜ qk  1.
CAPITOLUL 13. ONI 2010 13.2. CMMMC 358

Observăm, că am numărat perechile formate din numere distincte de două ori şi perechea
n, n o singură dată, aşa că adăugăm unu şi ı̂mpărţim la doi, pentru a răspunde la subpunctul
a).
Pentru a rezolva subpunctul b), observăm că pentru a obţine suma minimă, trebuie să con-
siderăm fiecare pi la puterea qi ı̂ntr-unul din numere, iar la puterea 0 ı̂n celălalt număr. Încercăm
toate cele 2k perechi ce se pot forma astfel şi alegem perechea cu suma minimă.
Pentru a genera aceste perechi, iterăm cu o variabilă de la 0 la 2k  1, pe care o convertim ı̂n
baza doi. Dacă cifra i ı̂n baza doi este egal cu 0, vom considera că pi apare ı̂n primul număr la
puterea qi şi ı̂n al doilea număr la puterea 0, iar dacă cifra i este egal cu 1, pi va apărea ı̂n primul
număr la puterea 0 şi ı̂n al doilea număr la puterea qi .
Pentru a implementa un algoritm cât mai rapid pentru descompunerea ı̂n factori primi, se reco-
mandă generarea numerelor prime folosind Ciurul lui Eratostene. Această abordare implementat
cu grijă duce la obţinerea punctajului maxim.
O altă soluţie posibilă este determinarea tuturor divizorilor lui n şi verificarea pentru fiecare
pereche de divizori, dacă are cel mai mare divizor comun egal cu n. Pentru a determina divizorii
unui număr este de ajuns să verificăm divizorii până la radicalul numărului şi când am găsit
divizorul d, acesta va implica că am găsit şi divizorul n©d. Cazul pătratelor perfecte trebuie tratat
separat.
Această abordare obţine 60 de puncte.

13.2.2 Cod sursă

Listing 13.2.1: cmmmc60.cpp


1 //Code by Patcas Csaba
2 //Time complexity: O(sqrt(n) + nDivisors ˆ 2)
3 //Space complexity: O(nDivisors)
4 //Method: Brute force
5 //Implementation time: 15 minutes
6
7 #include <vector>
8 #include <string>
9 #include <set>
10 #include <map>
11 #include <queue>
12 #include <bitset>
13 #include <stack>
14 #include <list>
15
16 #include <numeric>
17 #include <algorithm>
18
19 #include <cstdio>
20 #include <fstream>
21 #include <iostream>
22 #include <sstream>
23 #include <iomanip>
24
25 #include <cctype>
26 #include <cmath>
27 #include <ctime>
28 #include <cassert>
29
30 using namespace std;
31
32 #define LL long long
33 #define PII pair <int, int>
34 #define PLL pair <LL, LL>
35 #define VB vector <bool>
36 #define VI vector <int>
37 #define VD vector <double>
38 #define VS vector <string>
39 #define VLL vector <LL>
40 #define VPII vector <pair <int, int> >
41 #define VVI vector < VI >
42 #define VVB vector < VB >
43
44 #define FORN(i, n) for(int i = 0; i < (n); ++i)
CAPITOLUL 13. ONI 2010 13.2. CMMMC 359

45 #define FOR(i, a, b) for(int i = (a); i <= (b); ++i)


46 #define REPEAT do{
47 #define UNTIL(x) }while(!(x));
48
49 #define SZ size()
50 #define BG begin()
51 #define EN end()
52 #define CL clear()
53 #define X first
54 #define Y second
55 #define RS resize
56 #define PB push_back
57 #define MP make_pair
58 #define ALL(x) x.begin(), x.end()
59
60 #define IN_FILE "cmmmc.in"
61 #define OUT_FILE "cmmmc.out"
62 #define MAX 2000000000
63
64 ifstream fin(IN_FILE);
65 ofstream fout(OUT_FILE);
66
67 int test, nSol;
68 LL n;
69
70 LL Gcd(LL x, LL y)
71 {
72 while (y)
73 {
74 LL z = x % y;
75 x = y;
76 y = z;
77 }
78 return x;
79 }
80
81 PLL Solve()
82 {
83 PLL ans = MP(n, n);
84 LL gyokN = sqrt(double(n));
85 vector<LL> divisors;
86 FOR(i, 1, gyokN - 1)
87 if (!(n % i)) divisors.PB(i), divisors.PB(n / i);
88 if (!(n % gyokN))
89 if (gyokN * gyokN == n) divisors.PB(gyokN);
90 else divisors.PB(gyokN), divisors.PB(n / gyokN);
91 nSol = 1;
92 LL minSum = (LL)MAX + MAX + 1;
93 FORN(i, divisors.SZ - 1)
94 FOR(j, i + 1, divisors.SZ - 1)
95 {
96 LL aux1 = divisors[i], aux2 = divisors[j], gcd = Gcd(aux1, aux2);
97 if (aux1 / gcd * aux2 == n)
98 {
99 ++nSol;
100 if (minSum > aux1 + aux2)
101 {
102 minSum = aux1 + aux2;
103 ans = MP(aux1, aux2);
104 }
105 }
106 }
107 return ans;
108 }
109
110 int main()
111 {
112 fin >> test;
113 FORN(t, test)
114 {
115 fin >> n;
116 PII sol = Solve();
117 fout << nSol << " " << sol.X << " " << sol.Y << endl;
118 }
119 fin.close();
120 fout.close();
CAPITOLUL 13. ONI 2010 13.2. CMMMC 360

121
122 return 0;
123 }

Listing 13.2.2: cmmmc100.cpp


1 //Code by Patcas Csaba
2 //Time complexity: O(MAX log log MAX + sqrt(n) + 2 ˆ nFactors)
3 //Space complexity: O(sqrt(MAX))
4 //Method: Eratostenes sieve + factorization + brute force
5 //Implementation time: 45 minutes
6
7 #include <vector>
8 #include <string>
9 #include <set>
10 #include <map>
11 #include <queue>
12 #include <bitset>
13 #include <stack>
14 #include <list>
15
16 #include <numeric>
17 #include <algorithm>
18
19 #include <cstdio>
20 #include <fstream>
21 #include <iostream>
22 #include <sstream>
23 #include <iomanip>
24
25 #include <cctype>
26 #include <cmath>
27 #include <ctime>
28 #include <cassert>
29
30 using namespace std;
31
32 #define LL long long
33 #define PII pair <int, int>
34 #define PLL pair <LL, LL>
35 #define VB vector <bool>
36 #define VI vector <int>
37 #define VD vector <double>
38 #define VS vector <string>
39 #define VLL vector <LL>
40 #define VPII vector <pair <int, int> >
41 #define VVI vector < VI >
42 #define VVB vector < VB >
43
44 #define FORN(i, n) for(int i = 0; i < (n); ++i)
45 #define FOR(i, a, b) for(int i = (a); i <= (b); ++i)
46 #define REPEAT do{
47 #define UNTIL(x) }while(!(x));
48
49 #define SZ size()
50 #define BG begin()
51 #define EN end()
52 #define CL clear()
53 #define X first
54 #define Y second
55 #define RS resize
56 #define PB push_back
57 #define MP make_pair
58 #define ALL(x) x.begin(), x.end()
59
60 #define IN_FILE "cmmmc.in"
61 #define OUT_FILE "cmmmc.out"
62 #define MAX 2000000000
63
64 ifstream fin(IN_FILE);
65 ofstream fout(OUT_FILE);
66
67 int test, nSol;
68 LL n;
69 VPII factors;
CAPITOLUL 13. ONI 2010 13.2. CMMMC 361

70 VB isPrime;
71 VI primes;
72 VLL compacted;
73
74 void Erast()
75 {
76 int gyok = sqrt(double(MAX));
77 isPrime.RS(gyok + 1, true);
78 FOR(i, 2, gyok >> 1)
79 if (isPrime[i])
80 for(int j=i+i; j<=gyok; j+=i) isPrime[j]=false;
81 FOR(i, 2, gyok)
82 if (isPrime[i]) primes.PB(i);
83 }
84
85 void BuildFactors()
86 {
87 factors.CL, compacted.CL;
88 LL aux = n;
89 int iPrime = 0, gyok = sqrt(double(n));
90 while (iPrime < primes.SZ && aux > 1)
91 {
92 int exponent = 0, prime = primes[iPrime];
93 if (prime > gyok) break;
94 while (!(aux % prime))
95 {
96 ++exponent;
97 aux /= prime;
98 }
99 if (exponent) factors.PB(MP(prime, exponent));
100 if (aux < isPrime.SZ && isPrime[aux]) break;
101 ++iPrime;
102 }
103 if (aux > 1) factors.PB(MP(aux, 1));
104 FORN(i, factors.SZ)
105 {
106 LL factor = factors[i].X;
107 FOR(j, 2, factors[i].Y) factor *= factors[i].X;
108 compacted.PB(factor);
109 }
110 }
111
112 PLL Solve()
113 {
114 nSol = 1;
115 FORN(i, factors.SZ) nSol *= (2*factors[i].Y + 1);
116 nSol = nSol / 2 + 1;
117 PLL ans;
118 LL minSum = (LL)MAX + MAX + 1;
119 FORN(i, 1 << factors.SZ)
120 {
121 LL x = 1, y = 1;
122 FORN(j, factors.SZ)
123 if (i & (1 << j)) x *= compacted[j];
124 else y *= compacted[j];
125 if (minSum > x + y)
126 {
127 minSum = x + y;
128 ans = MP(x, y);
129 }
130 }
131 if (ans.X > ans.Y) swap(ans.X, ans.Y);
132 return ans;
133 }
134
135 int main()
136 {
137 Erast();
138
139 fin >> test;
140 FORN(t, test)
141 {
142 fin >> n;
143 BuildFactors();
144 PII sol = Solve();
145 fout << nSol << " " << sol.X << " " << sol.Y << endl;
CAPITOLUL 13. ONI 2010 13.3. SIMETRIC 362

146 }
147 fin.close();
148 fout.close();
149
150 return 0;
151 }

13.2.3 *Rezolvare detaliată

13.3 simetric
Problema 3 - simetric 100 de puncte
O matrice pătratică A care are P linii şi P coloane este simetrică dacă şi numai dacă pentru
orice indici i şi j ı̂ntre 1 şi P avem că Ai,j Aj,i . Astfel, matricea din figura 1 este simetrică, iar
cea din figura 2 nu este, deoarece există cel puţin o pereche de indici (de exemplu i 2 şi j 3),
pentru care Ai,j este diferit de Aj,i .

Pentru o matrice dată cu M linii şi N coloane, definim submatricea de vârfuri l1 , c1  şi l2 , c2 ,
cu 1 & l1 & l2 & M şi 1 & c1 & c2 & N , ca fiind tabloul format din toate elementele de coordonate
i şi j astfel ı̂ncât l1 & i & l2 şi c1 & j & c2 .

Cerinţe

Se dă o matrice cu M linii şi N coloane ı̂n care toate elementele sunt numere naturale. Fie L
latura maximă a unei submatrici simetrice din această matrice. Pentru fiecare dimensiune i ı̂ntre
1 si L să se determine câte submatrici simetrice şi cu latura i ale matricii date există.

Date de intrare

Prima linie a fişierului simetric.in conţine numerele M şi N , separate de exact un spaţiu,
reprezentând numărul de linii, şi respectiv de coloane, ale matricii care se citeşte. Fiecare din
următoarele M linii conţine câte N numere naturale, despărţite de exact un spaţiu, reprezentând
elementele matricii.

Date de ieşire

Fişierul de ieşire simetric.out conţine exact L linii, unde L este latura maximă a unei sub-
matrici simetrice din matricea considerată. Linia i conţine numărul de submatrici simetrice de
latură i.

Restricţii şi precizări

a 2 & M, N & 400


a Elementele matricii sunt numere naturale cuprinse ı̂ntre 1 şi 30 000

Exemple:
CAPITOLUL 13. ONI 2010 13.3. SIMETRIC 363

simetric.in simetric.out Explicaţii


45 20 Există 20 de submatrici simetrice de latură 1 (fiecare celulă este
51369 3 considerată submatrice), 3 submatrici simetrice de latură 2 şi 2
16289 2 de latură 3. Submatricile simetrice de latură 3 sunt:
32751
98538 5 1 3 6 2 8
1 6 2 2 7 5
3 2 7 8 5 3

Timp maxim de executare/test: 0.5 secunde


Memorie: total 2 MB din care pentru stivă 2 MB
Dimensiune maximă a sursei: 15 KB

13.3.1 Indicaţii de rezolvare

Filip Cristian Buruiană, Universitatea Politehnică Bucureşti


Marius Dumitran, Universitatea Bucureşti
5
Rezolvarea trivială are complexitatea O N : pentru orice vârf i, j  din matricea dată şi
pentru orice dimensiune x posibilă testăm dacă submatricea de vârfuri i, j  şi i  x  1, j  x  1
este simetrică. Pasul de testare se face parcurgând efectiv submatricea. Când am ı̂ntâlnit o
matrice simetrică de latură x, incrementăm numărul de soluţii pentru această latură. În plus,
comparăm x cu maximul găsit anterior şi eventual actualizăm acest maxim. Această abordare
obţine 20 de puncte.
4
Putem obţine un algoritm de complexitate O N  observând că după ce am fixat vârful i, j ,
incrementăm x doar cât timp submatricea de vârfuri i, j  şi i  x  1, j  x  1 este simetrică.
La pasul de verificare nu se mai parcurge efectiv toată submatricea, ci se testează doar egalitatea
ultimei linii adăugate cu ultima coloană adaugată ı̂n submatrice, deoarece submatricea de latură
x  1 care ı̂ncepe ı̂n i, j  este sigur simetrică. Această abordare obţine 40-50 de puncte.
3
Complexitatea optimă de rezolvare este ı̂nsă O N , şi există
mai multe soluţii cu această complexitate.
Prima se bazează şi pe ideea algoritmului de mai sus: pentru
un colţ fixat i, j , determinăm submatricea simetrică de latură
maximă cu colţul stanga-sus ı̂n i, j . Toate celelalte submatrici
cu latura mai mică sau egală decât acest maxim vor fi simetrice,
iar cele cu latura mai mare sigur nu vor fi simetrice. Pentru a
determina latura maximă din i, j  testăm cât ne putem deplasa
maxim pe linii şi pe coloane: pornim cu k = 0, şi cât timp Ai,j k
Aik,j (după cum se vede şi ı̂n desen), incrementăm k. Pentru ca o submatrice din i, j  să fie
simetrică, şi cea din i  1, j  1 trebuie să fie simetrică. Vom compara deci k-ul cu latura maximă
gasită pentru colţul i  1, j  1, şi vom reţine minimul dintre ele (atât este valoarea maximă cu
care ne putem extinde pentru a forma o submatrice simetrică din i, j ).
3
O alta soluţie care are tot complexitatea O N  este următoarea: pre-
supunând că matricea dată este patratică, alegem fiecare semidiagonală par-
alelă cu diagonala principală şi determinăm câte submatrici simetrice au
diagonala principală pe semidiagonala selectată. Pentru o semidiagonală
selectată trebuie să vedem din orice punct al ei cât ne putem extinde pe
semidiagonale paralele cu diagonala secundară. Determinarea unei subma-
trici simetrice se poate face astfel liniar. Pentru matrici iniţiale care nu sunt
pătratice abordarea este similară.

13.3.2 Cod sursă

Listing 13.3.1: simetric20.cpp


1 #include<stdio.h>
CAPITOLUL 13. ONI 2010 13.3. SIMETRIC 364

2 #include<string.h>
3 #define maxn 512
4
5 int N, M, V[ maxn ][ maxn ], nr[ maxn];
6
7 int simetric( int x, int y, int l)
8 {
9
10 for( int i = x; i <= x + l - 1; i++)
11 for( int j = y ; j <= y + l - 1; j++)
12 if( V[i][j] != V[ x + j - y ][ y + i - x ] )
13 return 0;
14 return 1;
15 }
16
17 int main()
18 {
19 freopen("simetric.in","r",stdin);
20 freopen("simetric.out","w",stdout);
21
22 memset( nr, 0, sizeof( nr ));
23
24 scanf("%d %d", &N, &M);
25
26 for( int i = 1; i <= N; ++i)
27 for( int j = 1; j <= M; ++j)
28 scanf("%d", &V[ i ][ j ]);
29 int optim = 1;
30 for( int i = 1; i <= N; ++i)
31 for( int j = 1; j <= M; ++j)
32 for( int l = 1; l + i - 1 <= N && l + j - 1 <= M; ++l)
33 if( simetric( i, j, l) )
34 {nr[ l ]++; if( l > optim) optim = l;}
35 else break;
36 for( int i = 1; i <= optim; i++)
37 printf("%d\n", nr[i]);
38
39 return 0;
40 }

Listing 13.3.2: simetric40.cpp


1 #include<stdio.h>
2 #include<string.h>
3
4 #define maxn 128 * 4
5
6 short int N, V[ maxn ][ maxn ], Max[ maxn][ maxn ], M;
7 int nr[ maxn];
8
9 int main()
10 {
11 freopen("simetric.in","r",stdin);
12 freopen("simetric.out","w",stdout);
13
14 memset( nr, 0, sizeof( nr ));
15 memset( Max, 0, sizeof( Max ));
16
17 scanf("%d %d",&N, &M);
18
19 for( int i = 1; i <= N; ++i)
20 for( int j = 1; j <= M; ++j)
21 scanf("%d", &V[ i ][ j ]);
22
23 int optim = 0;
24 for( int i = 1; i <= N; ++i)
25 for( int j = 1; j <= M; ++j)
26 {
27 int ok = 1;
28 for( int l = 1; i+l-1 <= N && l+j-1 <= M; l++)
29 {
30 for( int k = 1; k <= l; ++k)
31 if( V[i+l-1][j+k-1] != V[i+k-1][j+l-1] )
32 { ok = 0; break;}
33 if( ok == 0) break;
CAPITOLUL 13. ONI 2010 13.4. PESTI 365

34
35 nr[ l ]++;
36 if( l > optim )
37 optim = l;
38 }
39 }
40
41 for( int i = 1; i <= optim; i++)
42 printf("%d\n", nr[i]);
43
44 return 0;
45 }

Listing 13.3.3: simetric100.cpp


1 #include <stdio.h>
2
3 #define minim(a, b) ((a < b) ? a : b)
4 #define NMax 405
5
6 int M, N, A[NMax][NMax], bst[NMax][NMax];
7 int Res, cnt[NMax];
8
9 int main()
10 {
11 int i, j, k;
12
13 freopen("simetric.in", "r", stdin);
14 freopen("simetric.out", "w", stdout);
15
16 scanf("%d %d", &M, &N);
17 for (i = 1; i <= M; ++i)
18 for (j = 1; j <= N; ++j)
19 scanf("%d", &A[i][j]);
20
21 for (i = M; i; --i)
22 for (j = N; j; --j)
23 {
24 for (k = 1; i+k <= M && j+k <= N && A[i+k][j] == A[i][j+k]; ++k);
25 bst[i][j] = minim(bst[i+1][j+1]+1, k);
26 if (bst[i][j] > Res)
27 Res = bst[i][j];
28 ++cnt[bst[i][j]];
29 }
30
31 for (i = Res-1; i; --i)
32 cnt[i] += cnt[i+1];
33 for (i = 1; i <= Res; ++i)
34 printf("%d\n", cnt[i]);
35
36 return 0;
37 }

13.3.3 *Rezolvare detaliată

13.4 pesti
Problema 4 - pesti 100 de puncte
Nicuşor trebuie să aibă grijă, pe perioada vacanţei, de cei n peşti aflaţi ı̂n acvariile de la Muzeul
de ştiinţe ale Naturii din Constanţa. Peştii sunt numerotaţi cu numerele distincte de la 1 la n şi
sunt asezaţi ı̂n n acvarii identice, câte un peştişor ı̂n câte un acvariu. Iniţial, peştişorul numerotat
cu numărul 1 stă ı̂n acvariul etichetat cu numărul 1, peştişorul numerotat cu numărul 2 stă ı̂n
acvariul etichetat cu numărul 2, ..., peştişorul numerotat cu numărul n stă ı̂n acvariul etichetat
cu numărul n. Cele n acvarii sunt aşezate unul lângă altul, ı̂n ordinea crescătoare a etichetelor.
Cele n acvarii formează o grupă.
Pentru ca peştii să se dezvolte frumos şi să nu se plictisească, ei trebuie reaşezaţi zilnic ı̂n
acvarii.
CAPITOLUL 13. ONI 2010 13.4. PESTI 366

Astfel, ı̂n prima zi, Nicuşor formează două subgrupe de acvarii.


În subgrupa din stânga aşează, ı̂n ordine, peştii din acvariile aflate pe poziţii impare ı̂n grupă
(primul acvariu din grupă, al treilea, al cincilea etc).
În subgrupa din dreapta
aşează, ı̂n ordine, peştii din
acvariile aflate pe poziţii pare
ı̂n grupă (al doilea acvariu din
grupă, al patrulea, al şaselea
etc).
În fiecare dintre următoarele
zile, Nicuşor aplică operaţia de-
scrisă anterior pentru fiecare
subgrupă formată ı̂n ziua prece-
dentă.
Activitatea lui Nicuşor se ı̂ncheie ı̂n ziua ı̂n care fiecare dintre grupe este formată din cel mult
două acvarii.
Exemplu. Pentru n 9, la finalul celei de-a treia zi, peştişorii sunt aşezaţi ı̂n 5 grupe, conform
figurii alăturate.

Cerinţe

Scrieţi un program care să citească două numere naturale nenule n şi x, n reprezentând numărul
de peştişori şi x reprezentând numărul unui peştişor, şi care să determine:
- numărul z de zile ı̂n care Nicuşor ı̂şi desfăşoară activitatea;
- eticheta y a acvariului ı̂n care se găseşte peştişorul cu numărul x la ı̂ncheierea activităţii lui
Nicuşor;
- prima zi, u, ı̂n care ı̂n peştişorul cu numărul x a ajuns ı̂n acvariul etichetat cu numărul y şi
nu a mai fost mutat.

Date de intrare

Fişierul de intrare pesti.in conţine o singură linie pe care sunt scrise cele două numere naturale
n şi x, separate printr-un spaţiu.

Date de ieşire

Fişierul de ieşire pesti.out conţine o singură linie pe care sunt scrise cele trei numere naturale
z, y şi u (ı̂n această ordine), separate prin câte un spaţiu.

Restricţii şi precizări

a 3 & n & 2 000 000 000;


a 1 & x & n.
a Dacă un peşte nu este mutat deloc atunci răspunsul la a treia cerinţă este 1.
a Evaluare: dacă se răspunde corect la prima cerinţă se obţine 20% din punctaj. Dacă se
răspunde corect la primele două cerinţe se obţine 60% din punctaj. Dacă se răspunde corect la
toate cele trei cerinţe se obţine 100% din punctaj.

Exemple:

pesti.in pesti.out
Explicaţii
96 372 Nicuşor ı̂şi desfăşoară activitatea timp de z 3 zile. Peştişorul cu
numărul x 6 se va afla ı̂n ziua a treia ı̂n acvariul cu numărul y 7
şi ajunge ı̂n acest acvariu ı̂n ziua u 2.
Timp maxim de executare/test: 1.0 secunde
Memorie: total 64 MB din care pentru stivă 32 MB
Dimensiune maximă a sursei: 10 KB
CAPITOLUL 13. ONI 2010 13.4. PESTI 367

13.4.1 Indicaţii de rezolvare

prof. Cristina Sichim, Colegiul Naţional ”Ferdinand I” Bacău


asist. drd. Pătcaş Csaba, Universitatea Babeş-Bolyai, Cluj-Napoca

Cerinţa a) numărul z de zile până la ı̂ncheierea activităţii desfăţurată de Nicuşor este cel mai
mare număr natural care verifică inecuaţia 2 $ n, v ' 3.
z

Cerinţa b) eticheta y a acvariului ı̂n care se găseşte, ı̂n ziua z, peştişorul cu numărul x
Pentru a determina poziţia finală a peştişorului cu numărul x, trebuie să reţinem la fiecare pas
ı̂n ce grupă şi ı̂n ce acvariu se află.
Dacă, la un moment dat peştele se află ı̂n acvariul x ı̂n cadrul unei grupe ce conţine acvariile
etichetate cu a, a  1, ..., b, pe o poziţie pară ı̂n cadrul grupei, atunci el va ajunge ı̂n acvariul
a  b©2  x  a  1©2 din subgrupa din dreapta, iar dacă se află pe o poziţie impară ı̂n cadrul
grupei, atunci el va ajunge ı̂n acvariul a  x  a©2 din subgrupa din stânga.
Cerinţa c) prima zi, u, ı̂n care ı̂n peştişorul cu numărul x a ajuns ı̂n acvariul etichetat cu
numărul y şi nu a mai fost mutat
Este suficient să utilizăm o singură variabilă pe care să o actualizăm de fiecare dată când
poziţia curentă a peştelui este diferită de poziţia anterioară.
Această abordare rulează ı̂n timp O log n şi foloseşte memorie O 1.
Aproximativ 60-70% din punctaj se poate obţine şi prin simularea efectivă a mişcării peştilor.
Observăm că, pentru a genera poziţia peştilor dintr-o zi, avem nevoie doar de poziţia acestora din
ziua anterioară, şi mărimea grupelor din ziua anterioară. Această observaţie duce la ideea de a
reţine două matrici de câte două linii a şi groupSize, ı̂n care o linie va codifica starea pentru ziua
curentă, iar cealaltă pentru ziua anterioară. Menţionăm că, numărul de elemente dintr-o linie a
matricii groupSize se modifică dinamic de la o zi la alta, ı̂n schimb o linie din matricea a are
ı̂ntotdeauna n elemente. Această abordare rulează ı̂n timp O n log n şi foloseşte memorie O n.
Dacă se reţin stările pentru fiecare din zile, memoria folosită se modifică la O n log n şi se pot
obţine 40-50 puncte.

13.4.2 Cod sursă

Listing 13.4.1: pesti.cpp


1 # include <fstream>
2 # include <math.h>
3
4 using namespace std;
5
6 ifstream f("pesti.in");
7 ofstream g("pesti.out");
8
9 unsigned long n,x,a,b,c,z,u,p,zi=1;
10 long double r;
11
12 int main()
13 {
14 f>>n>>x;
15
16 //cerinta a
17 r=logl(n)/logl(2);
18 if(r==floorl(r)) z=r-1; else z=r;
19
20 //cerintele b si c
21 a=1;
22 b=n;
23 u=0;
24 do
25 {
26 p=x;
27 if((x-a)%2==0) // grupa din stanga
28 {
29 b=(a+b)/2;
30 x=a+(x-a)/2;
31 }
CAPITOLUL 13. ONI 2010 13.5. PLAJA 368

32 else
33 {
34 c=(a+b)/2; //grupa din dreapta
35 x=c+(x-a+1)/2;
36 a=c+1;
37 }
38
39 u++;
40 if(x!=p) zi=u;
41 // g<<" in ziua "<<u<<" pozitia "<<x<<’\n’;
42 } while (b-a>1);
43
44 //g<<" la final "<<z<<’ ’<<x<<’ ’<<zi<<’\n’;
45 g<<z<<’ ’<<x<<’ ’<<zi<<’\n’;
46 f.close();
47 g.close();
48 return 0;
49 }

13.4.3 *Rezolvare detaliată

13.5 plaja
Problema 5 - plaja 100 de puncte
Primăria oraşului Constanţa reamenajează plaja din staţiunea Mamaia. Aceasta este reprezen-
tată ca o zonă dreptunghiulară cu lăţimea de a unităţi şi lungimea de b unităţi. Pe plajă sunt
trasate linii paralele cu laturile dreptunghiului astfel ı̂ncât să formeze pătrate cu latura de o
unitate, numite zone.
Pe plajă se vor pune obiecte: umbrele şi prosoape. Se consideră că dacă un obiect intră ı̂n
interiorul unei zone, o ocupă ı̂n ı̂ntregime. Se poziţionează u umbrele de soare. ı̂ntr-o zonă se
poate aşeza cel mult o umbrelă.
N turişti vin şi ı̂şi aşează prosoapele pe plajă. Un prosop are formă dreptunghiulară şi va
fi aşezat paralel cu laturile dreptunghiului. Turiştii ı̂şi pot aşeza prosoapele pe zone libere sau
peste prosoape deja aşezate. Un turist nu ı̂şi poate aşeza ı̂nsă prosopul pe plajă dacă suprafaţa
acoperită de acesta include cel puţin o zonă ı̂n care se află o umbrelă.
M localnici au suprafeţe favorite pentru aşezarea prosoapelor. O suprafaţă favorită are forma
unui dreptunghi cu laturile paralele cu laturile dreptunghiului care marchează plaja. După ce
turiştii termină aşezarea prosoapelor, localnicii verifică dacă zonele din suprafaţa favorită sunt
libere (neacoperite de prosoape aşezate de turişti sau de umbrele).

Cerinţe

Scrieţi un program care să determine:


a numărul de turişti care au reuşit să ı̂şi aşeze prosoapele pe plajă;
a numărul de localnici ale căror zone favorite sunt libere.

Date de intrare

Fişierul de intrare plaja.in conţine pe prima linie trei numere naturale, separate prin câte un
spaţiu, a, b şi u, având semnificaţia din enunţ.
Fiecare din următoarele u linii conţine o pereche de numere naturale x y, reprezentând o zonă
ı̂n care se găseşte o umbrelă.
Următoarea linie din fişier conţine un număr natural N , reprezentând numărul de turişti.
Următoarele N linii descriu prosoapele turiştilor. Fiecare linie conţine 4 numere naturale x1
y1 x2 y2 , ce reprezintă colţurile unui prosop. Linia următoare conţine o singură valoare, M ,
reprezentând numărul de localnici.
¬ ¬ ¬ ¬
Pe următoarele M linii se află câte 4 numere, separate prin câte un spaţiu, x1 y1 x2 y2 , ce
reprezintă colţurile unei suprafeţe favorite.

Date de ieşire
CAPITOLUL 13. ONI 2010 13.5. PLAJA 369

Fişierul de ieşire plaja.out conţine pe prima linie două numere naturale separate printr-un
spaţiu. Primul număr reprezintă numărul de turişti care şi-au aşezat prosoapele pe plajă, iar cel
de-al doilea număr reprezintă numărul de localnici ale căror zone favorite sunt libere.

Restricţii şi precizări

a Colţul din stânga sus al zonei dreptunghiulare are coordonatele (1,1)


a 3 & a, b & 2 000
a 0 & u & 100
a 3 & m, n & 100 000
a Un prosop descris de x1 , y1 , x2 , y2  va avea 1 & x1 & x2 & a şi 1 & y1 & y2 & b
¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬
a O suprafaţă favorită descrisă de x1 , y1 , x2 , y2  va avea 1 & x1 & x2 & a şi 1 & y1 & y2 & b
a Pentru lucrul ı̂n C/C++, se recomandă citirea folosind scanf sau fscanf, deoarece sunt mai
rapide decât cin.
a Evaluare: dacă se răspunde corect la prima cerinţă se obţine 40% din punctaj. Dacă se
răspunde corect la ambele cerinţe se obţine 100% din punctaj.

Exemple:

plaja.in plaja.out Explicaţii


12 13 1 32 Ultimul turist nu ı̂şi poate aşeza prosopul.
6 11
4
3477
5688
9 2 10 3
5 10 8 12
3
1 8 3 13
10 3 12 4
2 10 5 12
Zona favorită al celui de-al 2-lea localnic nu este liberă.
Timp maxim de executare/test: 1.0 secunde
Memorie: total 64 MB din care pentru stivă 1 MB
Dimensiune maximă a sursei: 10 KB

13.5.1 Indicaţii de rezolvare

prof. Sichim Cristina - Colegiul Naţional ”Ferdinand I” Bacău


Filip Cristian Buruiană, Universitatea Politehnică Bucureşti
Marius Dumitran, Universitatea Bucureşti

Prima cerinţă cere determinarea numărului de dreptunghiuri care nu conţin ı̂n interiorul lor
nicio zonă ı̂n care se găseşte o umbrelă.
Rezolvarea pentru acest pas are complexitate O N ˜ U , testând pentru fiecare dreptunghi din
cele N dacă există o umbrelă pe care să o includă. Testarea se va face parcurgând efectiv lista de
umbrele. Această abordare garantează obţinerea a 40 de puncte.
Pentru a rezolva eficient şi cea de-a doua cerinţă, procedăm
ı̂n felul următor: atunci când avem un dreptunghi de coordonate
x1 y1 x2 y2  pe care trebuie să ı̂l aşezăm pe plajă, incrementăm cu
1 valoarea din celulele x1 , y1  si x2  1, y2  1 şi scădem cu 1
valoarea din celulele x1 , y2  1 şi x2  1, y1 , după cum se observă
ı̂n desenul alăturat.
Atunci când facem suma elementelor pe orice submatrice de
colţuri 1, 1 şi i, j , observăm că se va aduna o unitate ı̂n orice
celulă care aparţine dreptunghiului considerat. Astfel, de fiecare dată când avem un dreptunghi
care trebuie aşezat, adunăm sau scădem 1 ı̂n celulele menţionate. După amplasarea tuturor
celor N dreptunghiuri, calculăm suma Si,j pe orice submatrice de colţuri 1, 1 şi i, j , folosind
următoarea relaţie:
CAPITOLUL 13. ONI 2010 13.5. PLAJA 370

Si,j Si1,j  Si,j 1  Si1,j 1  Vi,j , unde Vi,j este valoarea de pe poziţia i, j .
Dacă Si,j % 0, atunci i, j  este acoperit de cel puţin un prosop. Facem din nou sume parţiale
pe această matrice nou obţinută, ı̂n care considerăm Si,j 1 dacă şi numai dacă i, j  este acoperit
¬ ¬ ¬ ¬
de cel puţin un prosop. Pentru a testa ı̂n O 1 dacă o suprafaţă x1 , y1 , x2 , y2  din cele M este
ı̂n ı̂ntregime liberă, calculăm suma Sx¬2 ,y2¬  Sx¬2 ,y1¬ 1  Sx¬1 1,y2¬  Sx¬1 1,y1¬ 1 . Dacă această sumă
este 0, ı̂nseamnă că suprafaţa este liberă, iar ı̂n caz contrar va exista cel puţin o zonă ı̂n ı̂nteriorul
suprafeţei care este acoperită.
Complexitatea finală este O N ˜ U  A ˜ B  M  N .

13.5.2 Cod sursă

Listing 13.5.1: plaja.cpp


1 #include <stdio.h>
2
3 #define NMax 2004
4
5 int M, N, A, B, U, X[128], Y[128];
6 int S[NMax][NMax], Res;
7
8 int main()
9 {
10 int i, j, x1, y1, x2, y2;
11 bool ok = false;
12
13 freopen("plaja.in", "r", stdin);
14 freopen("plaja.out", "w", stdout);
15
16 scanf("%d %d %d", &A, &B, &U);
17 for (i = 1; i <= U; ++i)
18 scanf("%d %d", &X[i], &Y[i]);
19 scanf("%d", &N);
20 for (i = 1; i <= N; ++i)
21 {
22 scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
23
24 for (j = 1, ok = true; j <= U && ok; ++j)
25 if (x1 <= X[j] && X[j] <= x2 && y1 <= Y[j] && Y[j] <= y2)
26 ok = false;
27 if (!ok)
28 continue;
29 ++Res;
30 ++S[x1][y1];
31 ++S[x2+1][y2+1];
32 --S[x1][y2+1];
33 --S[x2+1][y1];
34 }
35
36 printf("%d ", Res);
37
38 for (i = 1; i <= A; ++i)
39 for (j = 1; j <= B; ++j)
40 S[i][j] += S[i-1][j] + S[i][j-1] - S[i-1][j-1];
41
42 for (i = 1; i <= U; ++i)
43 S[X[i]][Y[i]] = 1;
44
45 for (i = 1; i <= A; ++i)
46 for (j = 1; j <= B; ++j)
47 S[i][j] = S[i-1][j] + S[i][j-1] - S[i-1][j-1] + (S[i][j] != 0);
48
49 Res = 0;
50 for (scanf("%d", &M); M; --M)
51 {
52 scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
53 Res += (S[x2][y2] - S[x2][y1-1] - S[x1-1][y2] + S[x1-1][y1-1] == 0);
54 }
55 printf("%d\n", Res);
56
57 return 0;
58 }
CAPITOLUL 13. ONI 2010 13.6. TANGO 371

13.5.3 *Rezolvare detaliată

13.6 tango
Problema 6 - tango 100 de puncte
Un tango este format din fraze muzicale, fiecare dintre acestea având 8 timpi muzicali. Timpii
muzicali au aceeaşi durată.
La fel de importantă ca melodia unui tango este şi dansul asociat ei. Mişcările efectuate ı̂n
timpul dansului se numesc figuri. Succesiunea de figuri efectuate ı̂n timpul dansului formează o
coregrafie. Două coregrafii se consideră diferite dacă succesiunea figurilor care le alcătuiesc este
diferită. O coregrafie frumoasă asociată unui tango are particularitatea următoare: atunci când
se termină o frază muzicală trebuie să se termine şi o figură.
D şi S se pregătesc pentru primul lor concurs de dans şi ei lucreaza momentan la coregrafia
de tango. Chiar dacă va fi primul lor concurs, ei deja ştiu n figuri de dans şi au calculat pentru
fiecare dintre aceste figuri câţi timpi muzicali durează. Fiindcă le place foarte mult să danseze
ı̂mpreună, ei vor să pregătească o coregrafie frumoasă pentru o piesă care durează exact k timpi
muzicali.
Cerinţe
Determinaţi numărul coregrafiilor frumoase modulo 999983 pentru o piesă, care: dureaza exact
k timpi muzicali, respectă condiţiile de mai sus şi sunt formate doar din cele n figuri cunoscute
de D şi S (mai este prea puţin timp până la concurs, ca ei să inveţe şi figuri noi).
Date de intrare
Pe prima linie a fişierului de intrare tango.in se află numerele naturale nenule n şi k, separate
printr-un singur spaţiu. Pe a doua linie se află exact n numere separate prin câte un spaţiu,
reprezintând lungimile figurilor.
Date de ieşire
În fişierul de ieşire tango.out se va afişa numărul de coregrafii posibile modulo 999983.
Restricţii şi precizări
a n & 100 000
a k & 2 000 000 000
a k va fi ı̂ntotdeauna divizibil cu 8
a 1 & lungimea unei figuri & 8
a pentru 30% din teste va exista o singură figură de o anumită lungime
a pentru 50% din teste n & 30
a pentru 70% din teste lungimile figurilor vor fi numai valori din mulţimea {2, 4, 6, 8}
a Prin a modulo b se ı̂nţelege restul ı̂mpărţirii lui a la b.

Exemple:
tango.in tango.out Explicaţii
3 16 66049 Sunt 16 timpi muzicali deci o coregrafie frumoasă se va dansa pe
118 16 / 8 = 2 fraze muzicale.
Dacă notăm figurile cu litere, avem figura A de lungime 1, figura B
de lungime 1 şi figura C de lungime 8. Prima frază muzicală poate
fi alcătuită din orice secvenţă alcătuită din opt bucăţi de A sau B,
deci ı̂n total 28 = 256 posibilităţi. ı̂ncă o posibilitate de alcătuire
a primei fraze este printr-un singur C. Rezultă un total de 257
posibilităţi. Pentru a doua frază avem tot atâtea posibilităţi, deci
ı̂n total există 257 * 257 = 66049 coregrafii frumoase posibile.
Cum 66049 modulo 999983 = 66049, se obţine rezultatul 66049.

Timp maxim de executare/test: 1.0 secunde


Memorie: total 32 MB din care pentru stivă 16 MB
Dimensiune maximă a sursei: 25 KB
CAPITOLUL 13. ONI 2010 13.6. TANGO 372

13.6.1 Indicaţii de rezolvare

asist. drd. Pătcaş Csaba, Universitatea Babeş-Bolyai Cluj-Napoca


Marius Dumitran, Universitatea Bucureşti

Primul pas va fi să calculăm numărul de posibilităţi pentru a dansa figuri care acoperă exact
o frază muzicală. Tratăm cazul ı̂n care figurile au lungime pară şi acestea sunt unice (acest caz
apare ı̂n 20% din teste). Vom ı̂ncerca să descompunem o frază ı̂n toate modurile posibile ı̂n figuri.
Avem doar următoarele posibilităţi:
1)8=2+2+2+2
2)8=2+2+4
3)8=2+4+2
4)8=2+6
5)8=4+2+2
6)8=4+4
7)8=6+2
8)8=8
În funcţie de ce figuri avem disponibile, o parte din aceste posibilităţi pot dispărea. De exemplu
dacă avem doar 2 şi 6 atunci putem forma doar variantele 1), 4) şi 7).
Tratăm acum cazul ı̂n care avem doar timpi pari, dar nu sunt unici. Să exemplificăm că avem
2 figuri de lungime 2 şi 3 de lungime 6. Atunci pentru cazul 1) vor fi 24 moduri de a fi dansat
(pentru fiecare figură de lungime 2 avem 2 posibilităţi), iar pentru cazurile 4) şi 7) vor fi 6 moduri
de a fi dansate (2 * 3).
Să extindem la cazul ı̂n care figurile au şi timpi impari. ı̂n acest caz sunt mai multe moduri
de a descompune o frază ı̂n figuri:
8=1+1+1+1+1+1+1+1
8=1+1+1+1+1+1+2+0
...........................
8 = x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8
Cum aceste cazuri sunt prea multe pentru a fi calculate de mână, se pot folosi 8 foruri imbricate
pentru a selecta valorile pentru x1, x2, ..., x8 (de la 0 la 8) astfel ı̂ncât x1 + x2 .... + x8 = 8.
Pentru a nu număra o posibilitate de mai multe ori, trebuie avut grijă să nu generăm cazuri de
forma 8 = 1 + 2 + 0 + 2 + 3 + 0 + 0, ci doar cazuri ı̂n care 0-urile sunt la coadă (vezi soluţia
oficială). Alte metode mai elegante pentru a genera numărul de posibilităţi sunt programarea
dinamică specifică problemei rucsacului, sau metoda backtracking.
Al doilea pas este să calculăm numărul de moduri de a dansa ı̂ntreg tangoul.
Pentru a afla această valoare, trebuie să calculăm numărul de posibilităţi de a dansa o frază
muzicală la puterea (k / 8). Pentru a obţine punctaj maxim, acest calcul trebuie făcut folosind
exponenţiere logaritmică.

13.6.2 Cod sursă

Listing 13.6.1: tango.cpp


1 #include<stdio.h>
2
3 #define prim 999983
4
5 int nr[20];
6
7 int main()
8 {
9 int n=0, k=0;
10 long long sol = 0;
11
12 freopen("tango.in","r",stdin);
13 freopen("tango.out","w",stdout);
14 scanf("%d %d", &n, &k);
15
16 //printf("%d %d\n",n,k);
17 //return 0;
18
CAPITOLUL 13. ONI 2010 13.6. TANGO 373

19 for( int i = 1; i <= n; ++i)


20 {
21 int x;
22 scanf("%d", &x);
23 nr[ x ]++;
24 }
25
26 nr[ 0 ] = 1;
27 for( int i1 = 1; i1 <= 8; i1++)
28 {
29 if( nr[ i1 ] == 0) continue;
30 for( int i2 = 0; i1 + i2 <= 8; i2++)
31 {
32 if( i1 + i2 != 8 && i2 == 0) continue;
33 if( nr[ i2 ] == 0) continue;
34 for( int i3 = 0; i1 + i2 + i3 <= 8; i3++)
35 {
36 if( nr[ i3 ] == 0) continue;
37 if( i1 + i2 + i3 != 8 && i3 == 0) continue;
38 for( int i4 = 0; i1 + i2 + i3 + i4 <= 8; i4++)
39 {
40 if( nr[ i4 ] == 0) continue;
41 if( i1 + i2 + i3 + i4 != 8 && i4 == 0) continue;
42 for( int i5 = 0; i1 + i2 + i3 + i4 +i5 <= 8; i5++)
43 {
44 if( nr[ i5 ] == 0) continue;
45 if( i1 + i2 + i3 + i4 +i5 != 8 && i5 == 0) continue;
46 for( int i6 = 0; i1+i2+i3+i4+i5+i6 <= 8; i6++)
47 {
48 if( nr[ i6 ] == 0) continue;
49 if( i1+i2+i3+i4+i5+i6 != 8 && i6 == 0) continue;
50 for( int i7 = 0; i1+i2+i3+i4+i5+i6+i7 <= 8; i7++)
51 {
52 if( nr[ i7 ] == 0) continue;
53 if( i1+i2+i3+i4+i5+i6+i7 != 8 && i7 == 0)
54 continue;
55 for( int i8 = 0;
56 i1+i2+i3+i4+i5+i6+i7+i8 <= 8;
57 i8++)
58 {
59 if( i1+i2+i3+i4+i5+i6+i7+i8 == 8 )
60 {
61 long long temp =
62 ((long long ) nr[ i1 ] *
63 nr[ i2 ] * nr[ i3 ] ) % prim;
64 temp *=
65 ((long long )nr[ i4 ] * nr[ i5 ] *
66 nr[ i6 ] ) % prim;
67 temp %= prim;
68 temp = (long long)temp *
69 nr[ i7 ] * nr[ i8 ] % prim;
70 sol = ( sol + temp) % prim;
71 }
72 if( nr[ i8 ] == 0) continue;
73 }
74 }
75 }
76 }
77 }
78 }
79 }
80 }
81
82 int p = k / 8;
83 long long rez = 1;
84 long long put = sol;
85 while( p )
86 {
87 if ( p & 1 ) rez = (put * rez) % prim;
88 put = ( put * put ) % prim;
89 p >>= 1;
90 }
91
92 printf("%d\n",(int) rez);
93 //fcloseall();
94 return 0;
CAPITOLUL 13. ONI 2010 13.6. TANGO 374

95 }

13.6.3 *Rezolvare detaliată


Capitolul 14

ONI 2009

14.1 joc
Problema 1 - joc 100 de puncte
Pentru un concurs de design de jocuri, Gigel vrea să construiască un joc. La joc participă n
concurenţi numerotaţi de la 1 la n. Fiecare concurent are la dispoziţie câte un şir de m ı̂ncăperi,
numerotate de la 1 la m. Scopul jocului este de a găsi o comoară ascunsă ı̂n una din aceste
ı̂ncăperi. Fiecare ı̂ncăpere conţine un cod, număr natural, fie egal cu 0, fie având cel puţin 2 cifre.
Ultima cifră indică numărul de etape de penalizare, adică numărul de etape ı̂n care concurentul nu
are voie să părăsească ı̂ncăperea. Numărul obţinut prin eliminarea ultimei cifre a codului indică
numărul ı̂ncăperii ı̂n care se va deplasa acesta la următoarea etapă sau la expirarea penalizării.
Există două excepţii de la regula de definire a codului: numărul 9999 codifică o ı̂ncăpere conţinând
o comoară, iar numărul 0 o ı̂ncăpere conţinând o capcană.
În etapa 1 fiecare jucător intră ı̂n ı̂ncăperea 1 din şirul său de ı̂ncăperi. În funcţie de codul
găsit ı̂n ı̂ncăpere sunt posibile următoarele situaţii:
- codul găsit este 9999 ceea ce ı̂nseamnă că jucătorul este câştigător şi jocul se ı̂ncheie la finalul
acestei etape;
- codul găsit este 0 ceea ce duce la eliminarea sa din joc;
- pentru celelalte coduri, după efectuarea etapelor de penalizare, jucătorul efectuează o de-
plasare ı̂n ı̂ncăperea indicată de cod. De exemplu la ı̂ntâlnirea codul 157, după efectuarea celor 7
etape de penalizare jucătorul se va deplasa ı̂n camera 15.
Trecerea de la o etapă la alta se realizează simultan pentru toţi concurenţii.

Cerinţe

Fiind dat numărul n de concurenţi, numărul m de ı̂ncăperi alocate fiecărui concurent, şi co-
durile din cele n  m ı̂ncăperi să se determine câştigătorul jocului, numărul ı̂ncăperii ı̂n care a găsit
comoara, numărul de etape parcurse până când câştigătorul găseşte comoara precum şi numărul
de concurenţi eliminaţi din joc până la etapa respectivă (inclusiv).

Date de intrare

Prima linie a fişierului de intrare joc.in conţine două numere naturale n şi m, separate printr-
un spaţiu, reprezentând numărul concurenţilor, respectiv numărul ı̂ncăperilor.
Următoarele n linii conţin câte m numere naturale, separate prin câte un spaţiu, reprezentând
codurile din fiecare ı̂ncăpere.

Date de ieşire

Prima linie a fişierului de ieşire joc.out va conţine patru numere naturale separate prin câte
un spaţiu, reprezentând indicele câştigătorului, numărul ı̂ncăperii unde a găsit comoara, numărul
etapei ı̂n care a câştigat şi respectiv numărul de concurenţi eliminaţi din joc.

Restricţii şi precizări

a 1 & n & 400


a 1 & m & 900
a Pentru toate testele de intrare se garantează că există exact un câştigător.

375
CAPITOLUL 14. ONI 2009 14.1. JOC 376

Exemple:

joc.in joc.out
Explicaţii
48 2571
Câştigă jucătorul al 2-lea, după 7 etape, iar ı̂ncăperea
0 9999 41 50 61 70 80 30 ı̂n care a găsit comoara este ı̂ncăperea 5. ı̂n cele 7
30 80 60 60 9999 21 40 50 etape a fost eliminat un singur concurent şi anume
20 30 40 50 61 71 81 9999 primul concurent.
20 30 50 0 61 71 9999 41 Încăperile prin care trece jucătorul câştigător până
la final sunt:
1 3 6 2 8 5
Timp maxim de executare/test: 0.2 secunde
Memorie: total 2 MB din care pentru stivă 1 MB

14.1.1 Indicaţii de rezolvare

Se citesc din fişier numerele n (numărul de jucători) şi m (numărul de ı̂ncăperi pentru fiecare
concurent). Pentru fiecare jucător i (1 & i & n) se execută următoarea secvenţă de paşi:
- se citeşte ı̂n vectorul a şirul său de ı̂ncăperi.
- se ı̂ncepe de la ı̂ncăperea 1 (a1) şi etapa 1
- pentru fiecare ı̂ncăpere la care a ajuns, se marchează ı̂ncăperea ca fiind vizitată, iar valoarea
din ı̂ncăpere poate fi:
` 0 - jucătorul este eliminat - se reţine ı̂ntr-un vector, pe poziţia i numărul etapei ı̂n care
a fost eliminat; se ı̂ncheie analiza şirului său de ı̂ncăperi
` 9999 - jucătorul a câştigat; se compară numărul etapei cu un minim şi se retine valoarea
minimă, indicele jucătorului şi numărul ı̂ncăperii ı̂n care a ajuns; se ı̂ncheie analiza
şirului său de ı̂ncăperi
` o valoare nenulă şi ¡ 9999; se calculează indicele j al ı̂ncăperii ı̂n care urmează să se
catapulteze; sunt posibile două cazuri:
t dacă acest indice este egal cu ı̂ncăperea ı̂n care a ajuns sau ı̂ncăperea ı̂n care
urmează să ajungă a mai fost vizitată, se ı̂ncheie analiza şirului de ı̂ncăperi,
deoarece jucătorul intră ı̂ntr-un ciclu infinit de mutări.
t indicele reprezintă o ı̂ncăpere nevizitată, se măreşte numărul etapei cu ultima cifră
a valorii codului şi se reia vizitarea cu ı̂ncăperea j.

După executarea secvenţei descrise pentru toţi jucătorii, se numără câţi jucători au fost pı̂nă
ı̂n etapa ı̂n care a fost stabilit câştigătorul (minimul calculat), inclusiv acea etapa şi se scriu ı̂n
fişier numerele cerute.

14.1.2 Cod sursă

Listing 14.1.1: joc.cpp


#include<fstream>
#include<iostream>

using namespace std;

int a[1001],n,m,t[401],s[401],v[401];

ifstream f("joc.in");
ofstream g("joc.out");

int main()
{
int i,j,x,castigator=0,nrp=0,viz[1001],l[1001];
long etapa,e_min=320000000,c_min,i_min;
f>>n>>m;
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
{
f>>a[j];
viz[j]=0;
CAPITOLUL 14. ONI 2009 14.2. PERSPIC 377

etapa=1;
j=1;
viz[1]=1;
castigator=0;
while(!castigator&&t[i]!=-1)
{
x=a[j];
viz[j]=1;

if(x==0)
{
t[i]=-1;
v[i]=etapa;
}
else
if(x==9999)
{
if(etapa<e_min)
{
c_min=i;
e_min=etapa;
i_min=j;
}
castigator=1;
}
else
{
if(j==x/10) break;
j=x/10;
if(viz[j]==1)
break;
else
etapa=etapa+x%10+1;
}
}
}

nrp=0;
for(i=1;i<=n;i++)
if(t[i]==-1&&v[i]<=e_min)
nrp++;
g<<c_min<<" "<<i_min<<" "<<e_min<<" "<<nrp<<endl;
f.close();
g.close();
return 0;
}

14.1.3 *Rezolvare detaliată

14.2 perspic
Problema 2 - perspic 100 de puncte
Se consideră o matrice pătratică cu N linii şi N coloane ce conţine toate numerele naturale de
la 1 la N ˜ N . Asupra matricei se definesc trei tipuri de operaţii codificate astfel:
a C i j - interschimbarea coloanelor i şi j ale matricei
a R i j - interschimbarea liniilor i şi j ale matricei
a E i j x y - interschimbarea elementului de pe linia i şi coloana j cu elementul de pe linia x
şi coloana y.
Asupra matricei se efectuează un set de M astfel de operaţii.
Cerinţe
Se cere să se determine numărul minim de aplicări complete ale acestui set de operaţii după
care se ajunge din nou ı̂n starea iniţială. În cadrul setului operaţiile se efectuează mereu ı̂n aceeaşi
ordine şi nu se poate sări peste o operaţie. Deoarece numărul acesta poate fi foarte mare se cere
restul ı̂mpărţirii sale la 13007.
CAPITOLUL 14. ONI 2009 14.2. PERSPIC 378

Date de intrare

Fişierul perspic.in conţine pe prima linie numerele naturale N şi M , separate printr-un spaţiu,
reprezentând dimensiunea matricei şi respectiv numărul de operaţii dintr-un set. Pe următoarele
M linii se descriu operaţiile setului.

Date de ieşire

Fişierul perspic.out va conţine restul ı̂mpărţirii la 13007 al numărului minim determinat.

Restricţii şi precizări

a 1 & N & 100


a 1 & M & 10.000
a Pentru 60% din teste numărul minim de aplicări ale setului de operaţii necesare va fi mai
mic decât 2.000.000.000.

Exemple:

perspic.in perspic.out Explicaţii


22 2 Matricea iniţială:
C12 12
R12 34
Matricea după prima operaţie:
21
43
Matricea după a doua operaţie (terminarea primului set):
43
21
Matricea după a treia operaţie:
34
12
Matricea după a patra operaţie(terminarea celui de al doilea set):
12
34
33 4
E1122
R12
C23
Timp maxim de executare/test: 0.1 secunde
Memorie: total 2 MB din care pentru stivă 1 MB

14.2.1 Indicaţii de rezolvare

Vom considera o matrice iniţială I:


1 2 ... N
N+1 N+2 ... 2*N
...
(N-1)*N+1 (N-1)*N+2 ... N*N.
Se vor simula toate cele M operaţii asupra matricei, obţinând ı̂n final o matrice A.
În continuare putem liniariza matricea (concatenăm liniile ı̂n ordine) şi vom avea o
corespondenţă ı̂ntre poziţiile iniţiale ale fiecarui număr şi poziţiile ı̂n care acestea se vor afla
dupa aplicarea setului de operaţii. Vom reţine aceste corespondenţe ı̂ntr-un vector P (P i va
reprezenta poziţia pe care se află i după aplicarea setului de M operaţii).
Pentru fiecare numar i de la 1 la N ˜ N vom determina un ciclu ı̂n felul urmator:

i, P i, P P i, P P P i...

La un moment dat se va ajunge ca P aplicat de k ori lui i să dea din nou i. Practic, acest număr
k va reprezenta numărul minim de aplicări ale setului după care i se va afla din nou ı̂n poziţia
iniţială. Pentru orice multiplu al lui k acest lucru va fi de asemenea adevarat.
CAPITOLUL 14. ONI 2009 14.2. PERSPIC 379

Vom avea
H i - numărul minim de aplicări ale setului de operaţii pentru care i să se afle din nou ı̂n
poziţia iniţială.
Pentru că vrem ca toate numerele i să ajungă din nou ı̂n poziţiile lor iniţiale vom căuta un
multiplu comun al tuturor valorilor H i, deci trebuie să calculăm

cmmmc H 1, H 2, ...H N ˜ N 

Pentru calcularea cmmmc se poate folosi pur şi simplu formula

cmmmc a, b a ˜ b©cmmdc a, b,

unde cmmdc a, b se calculează cu algorimul lui Euclid. Acest calcul va trece doar 60% din teste
deoarece rezultatul poate deveni foarte mare.
În general se pot parcurge toate numerele prime de la 1 la N ˜ N şi, pentru fiecare număr prim
p, se determină care este puterea cea mai mare e astfel ı̂ncât să existe un i pentru care H i sa
e e
fie divizibil cu p . Soluţia se va ı̂nmulţi cu p , obţinând ı̂n final cmmmc dorit.

14.2.2 Cod sursă

Listing 14.2.1: perspic.c


#include <stdio.h>
#include <string.h>

#define MAXN 100


#define MOD 13007
#define pos(i, j) ((i) * n + (j))
#define sw(x, y) (aux = A[x], A[x] = A[y], A[y] = aux)

int A[MAXN * MAXN], n, m;


char B[MAXN * MAXN];
int P[MAXN * MAXN], np = 0;
short E[MAXN * MAXN];

void dump()
{
int i, j;
printf("%d %d\n", n, m);
for (i = 0; i < n; ++i)
{
for (j = 0; j < n; ++j)
printf(" %d", A[pos(i, j)]);
printf("\n");
}
}

void read_eval()
{
int i, j, z, a, b, c, d, aux;
char op[10];
scanf("%d%d", &n, &m);

for (i = 0; i < n * n; ++i)


A[i] = i;

for (z = 0; z < m; ++z)


{
scanf("%s%d%d", op, &a, &b);
// printf("I: %s %d %d\n", op, a, b);
--a, --b;
switch (op[0])
{
case ’R’:
for (j = 0; j < n; ++j)
sw(pos(a, j), pos(b, j));
break;
case ’C’:
for (i = 0; i < n; ++i)
sw(pos(i, a), pos(i, b));
CAPITOLUL 14. ONI 2009 14.2. PERSPIC 380

break;
case ’E’:
scanf("%d%d", &c, &d);
--c, --d;
sw(pos(a, b), pos(c, d));
break;
default:
//printf("ERROR: %s\n", op);
break;
}
}
}

void cycles()
{
int i, len, j, d, pow, x;

memset(B, 0, MAXN * MAXN);


memset(E, 0, MAXN * MAXN);

for (i = 0; i < n * n; ++i)


{
for (len = 0, j = i; !B[j]; ++len)
{
// printf("c: \t%d\n", j);
B[j] = 1;
j = A[j];
}

if (len)
{
// printf("<<< %d\n", len);
for (x=len, d=0; x>1 && P[d]*P[d] <= x && d < np; ++d)
{
// printf("e: %d, %d, %d\n", d, P[d], x);
for (pow = 0; x % P[d] == 0; ++pow)
x /= P[d];
if (E[P[d]] < pow)
E[P[d]] = pow;
}

if (x > 1 && !E[x])


E[x] = 1;
// dump_exp();
}
}
}

void gen_primes()
{
int a, j, i;
P[np++] = 2;
for (a = 3; a < n * n; a += 2)
{
char prim = 1;
for (j = 0; prim && (P[j] * P[j] <= a) && j < np; ++j)
prim = ((a % P[j]) != 0);
if (prim)
P[np++] = a;
}
}

void dump_exp()
{
int i;
printf("exp:\n");
for (i = 0; i < n * n; ++i)
if (E[P[i]])
printf(" %dˆ%d", P[i], E[P[i]]);

printf("\n");
}

void dump_primes()
{
int i;
CAPITOLUL 14. ONI 2009 14.3. RAFTURI 381

printf("primes:\n");
for (i = 0; i < np; ++i)
printf(" %d", P[i]);

printf("\n");
}

int cmmmc()
{
int i, res, j;
for (res = 1, i = 1; i < n * n; ++i)
for (j = 0; j < E[i]; ++j)
res = (((long long)res * (i % MOD)) % MOD);

return res;
}

int main(void)
{
freopen("perspic.in", "rt", stdin);
freopen("perspic.out", "wt", stdout);

read_eval();
// dump();
gen_primes();
// dump_primes();
cycles();
printf("%d\n", cmmmc());
return 0;
}

14.2.3 *Rezolvare detaliată

14.3 rafturi
Problema 3 - rafturi 100 de puncte
Într-o bibliotecă se află C dulapuri identice aşezate unul lângă altul pe peretele unei ı̂ncăperi,
dulapurile fiind numerotate de la stânga spre dreapta cu numerele naturale de la 1 la C. Fiecare
dulap conţine 1000 de rafturi, situate vertical unul deasupra altuia, rafturile fiecărui dulap fiind
numerotate de la 1 la 1000 de jos ı̂n sus.
Fiecare dulap este prevăzut cu o scară cu care se poate ajunge la orice raft. Dacă bibliotecara
urcă scara unui anumit dulap D până la un anumit nivel k, ea va putea aduna orice carte de pe
rafturile 1 până la k inclusiv, din dulapul D şi din dulapurile ı̂nvecinate (dulapul D  1 şi dulapul
D  1).
Cunoscând dulapurile şi rafturile de unde trebuie luate cărţi, bibliotecara doreşte să adune
toate cărţile cerute, dar suma ı̂nălţimilor până la care trebuie să urce să fie minimă.

Cerinţe

Scrieţi un program care să determine suma minimă a ı̂nălţimilor până la care trebuie să urce
bibliotecara pentru a aduna toate cărţile cerute.

Date de intrare

Prima linie a fişierului de intrare rafturi.in conţine două numere naturale C şi N , separate
printr-un spaţiu, reprezentând numărul dulapurilor şi respectiv numărul cărţilor pe care bibliote-
cara trebuie să le adune.
Următoarele N linii conţin câte două numere naturale a b, separate printr-un spaţiu,
reprezentând numărul dulapului, respectiv numărul raftului de unde trebuie luată o carte.

Date de ieşire

Fişierul de ieşire rafturi.out va conţine un singur număr natural reprezentând suma minimă
a ı̂nălţimilor până la care trebuie să urce bibliotecara pentru a aduna toate cărţile cerute.
CAPITOLUL 14. ONI 2009 14.3. RAFTURI 382

Restricţii şi precizări

a 1 & C & 10.000


a 1 & N & 50.000

Exemple:

rafturi.in rafturi.out Explicaţii


10 4 11 Bibliotecara urcă astfel:
54 -pe dulapul 1 la raftul 1
11 -pe dulapul 4 la raftul 8
62 -pe dulapul 6 la raftul 2
38
Timp maxim de executare/test: 0.1 secunde
Memorie: total 2 MB din care pentru stivă 1 MB

14.3.1 Indicaţii de rezolvare

La citirea datelor reţinem ı̂n vectorul carti, pentru fiecare dulap, ı̂nălţimea cea mai mare de
pe care trebuie luată cartea.
Construim un vector inaltimi ı̂n care la fiecare pas vom calcula suma minimă a ı̂nălţimilor
rafturilor de pe care sunt coborı̂te cărţile până ı̂n acel moment.
Aceasta se obţine astfel:

inaltimi[0]=0
inaltimi[1]=carti[1]
inaltimi[2]=carti[2]

Pentru un dulap i ne putem afla ı̂n una din urmatoarele situaţii:

- cartea de pe dulapul i se ia impreuna cu cartea de pe dulapul precedent


- cartea se ia independent de grupul ultimelor trei carti
- cartea de pe dulapul i se ia impreuna cu cartile luate de pe ultimele doua dulapuri
~
„ max cartii, cartii  1  inaltimii  2
„
„ min inaltimii, cartii  inaltimii  1
inaltimii ‚
„
„
„ min inaltimii, max max cartii  2, cartii  1, cartii  inaltimei  3
€
Dacă avem cartii 0 atunci: inaltimii min inaltimii, inaltimii  1;

14.3.2 Cod sursă

Listing 14.3.1: rafturi.cpp


#include <stdio.h>

#define MAXN 10000


#define MAXV 2000000000

int C;
long N;
int carti[MAXN+1];
long inaltime[MAXN+1];

void afisare(long a[MAXN+1])


{
int i;
for(i=1;i<=C;i++)
printf("%ld",a[i]);

printf("\n");
}

void citire(void)
CAPITOLUL 14. ONI 2009 14.4. BR 383

{
FILE *f;
int x,y;
long i;

f=fopen("rafturi.in","r");
fscanf(f,"%d%ld",&C,&N);
for (i=1;i<=N;i++)
{
fscanf(f,"%d%d",&y,&x);
if (carti[y]<x) carti[y]=x;
}
carti[0]=0;
// afisare(carti);
fclose(f);
}

void calcul(void)
{
long h;
int i,j;
inaltime[0]=0;
for (i=1;i<=C;i++)
{
inaltime[i]=MAXV+1;
h=carti[i];
if (inaltime[i-1]+h<inaltime[i])
inaltime[i]=inaltime[i-1]+h;
for (j=i-1; j>i-3 && j>0 ;j--)
{
if (h<carti[j])
h=carti[j];
if (inaltime[j-1]+h<inaltime[i])
inaltime[i]=inaltime[j-1]+h;
}
}
}

void afisare(void)
{
FILE *g;
g=fopen("rafturi.out","w");
fprintf(g,"%ld\n",inaltime[C]);
//printf("%ld\n",inaltime[C]);
fclose(g);
}

int main()
{
citire();
calcul();
afisare();
return 0;
}

14.3.3 *Rezolvare detaliată

14.4 br
Problema 4 - br 100 de puncte
N prieteni, numerotaţi de la 1 la N , beau bere fără alcool la o masă rotundă. Pentru fiecare
prieten i se cunoaşte Ci - costul berii lui preferate. Din când ı̂n când, câte un prieten, fie el k,
cumpără câte o bere pentru o secvenţă de prieteni aflaţi pe poziţii consecutive la masă, inclusiv
lui, ı̂n sensul acelor de ceasornic. El este dispus să cheltuiască x bani şi doreşte să facă cinste la
un număr maxim posibil de prieteni.

Cerinţe
CAPITOLUL 14. ONI 2009 14.4. BR 384

Se cere numărul de beri pe care le va cumpăra fiecare prieten k ı̂n limita sumei x de bani de
care dispune. ı̂n caz că x este mai mare decât costul berilor pentru toţi prietenii de la masă, se
vor achiziţiona maxim N beri.

Date de intrare

Prima linie a fişierului de intrare br.in conţine două numere naturale N şi T separate printr-un
spaţiu reprezentând numărul de prieteni şi respectiv numărul de prieteni care doresc să facă cinste
prietenilor săi.
A doua linie a fişierului de intrare conţine N numere naturale C1 , C2 ..., CN , separate prin câte
un spaţiu, reprezentând costurile berilor preferate de fiecare prieten. Berea pentru prietenul i are
costul Ci .
Fiecare din următoarele T linii conţine câte două numere separate printr-un spaţiu:
k1 x1
k2 x2
...
kT xT
precizând indicele câte unui prieten care face cinste şi respectiv suma de bani de care acesta
dispune.

Date de ieşire

Fişierul de ieşire br.out va conţine T linii, fiecare cu un singur număr Di reprezentând numărul
de beri pe care le va cumpăra prietenul ki cu suma de bani xi in condiţiile problemei.

Restricţii şi precizări

a 1 & N & 15.000


a 1 & T & 10.000
a 1 & Ci & 100
a 1 & ki & N
a 1 & xi & 3.000.000
a Un program corect, care se ı̂ncadrează ı̂n timp pentru T & 4000, va obţine cel puţin 30 de
puncte
a Un program corect, care se ı̂ncadrează ı̂n timp pentru N & 2000, va obţine cel puţin 60 de
puncte
a Prietenii beau numai berea lor preferată.

Exemple:

br.in br.outExplicaţii
54 3 Prietenul 1 cumpără câte o bere pentru el şi pentru prietenii 2, 3.
10 5 15 22 13 4 Costul celor 3 beri este 30.
1 32 0 Prietenul 4 cumpără 4 beri: câte una pentru el şi pentru prietenii
4 50 5 5, 1, 2. Costul celor 4 beri este 50.
19 Cu 9 bani prietenul 1 nu poate cumpăra nici măcar berea lui.
4 200 Prietenul 4 cumpără 5 beri. Costul celor 5 beri este sub limita de
cost 200.
Timp maxim de executare/test: 0.1 secunde
Memorie: total 16 MB din care pentru stivă 1 MB

14.4.1 Indicaţii de rezolvare

O soluţie pentru 40 de puncte


Pentru fiecare pereche ki , xi  iterezi prin şirul de costuri până când suma lor depăşeşte xi .
Complexitate: O N T 
O soluţie pentru 60 de puncte
Sortezi perechile ki , xi  şi, pentru fiecare ki unic, iterezi prin şirul de costuri ca să obţii
răspunsurile pentru toate cererile cu acelaşi ki .
2
Complexitate: O N 
CAPITOLUL 14. ONI 2009 14.4. BR 385

O soluţie pentru 100 de puncte


Calculezi şirul de sume parţiale Si C1  C2 ...  Ci . Acest şir iţi permite să calculezi suma
costurilor pe orice interval de prieteni i, j : Ci  Ci1  ...  Cj Sj  Si1
Pentru fiecare pereche ki xi faci căutare binară pentru indicele q până la care se pot cumpăra
beri ı̂ncepând din k. Foloseşti S ca să determini dacă suma costurilor de la poziţia k la poziţia q
depăşeşte x.
Intrucât şirul este circular, trebuie să decizi ı̂n ce interval cauţi soluţia q: fie ı̂n intervalul k, n,
fie ı̂n intervalul 1, k  1. A doua variantă este posibilă numai dacă k % 1 şi Sn  Sk1  C1 & x.
Complexitate: O T log N 

14.4.2 Cod sursă

Listing 14.4.1: br.cpp


#include <stdio.h>

#define nmax 15006


#define FOR(i,s,d) for(i=(s);i<(d);++i)
#define pt(i) (1<<(i))

int n,m,k,x,sum[nmax];

int main()
{
freopen("br.in","r",stdin);
freopen("br.out","w",stdout);
int i,j,ii,rez;
scanf("%d %d",&n,&m);
FOR(i,1,n+1)
{
scanf("%d",&sum[i]);
sum[i]+=sum[i-1];
}

FOR(ii,0,m)
{
scanf("%d %d",&k,&x);
rez = 0;

if(sum[n]-sum[k-1] <=x)
{
rez += n-k+1;
x -= sum[n] - sum[k-1];
k = 0;
}
else
k--;

rez += n * (x / sum[n]);
x %= sum[n];

for(j=k,i=21;i>=0;--i)
if(pt(i)+j <= n && sum[pt(i)+j] - sum[k] <=x)
j += pt(i);

rez += j - k;
if(rez > n) rez = n;
printf("%d\n",rez);
}

return 0;
}

14.4.3 *Rezolvare detaliată


CAPITOLUL 14. ONI 2009 14.5. ORIGAMI 386

14.5 origami
Problema 5 - origami 100 de puncte
Costel este pasionat de arta orientală a confecţionării obiectelor de hârtie, origami, dar este
abia la ı̂nceput şi trebuie să se familiarizeze cu operaţiile de ı̂ndoire corectă a hârtiei. El are la
dispoziţie o foaie de hârtie pătrată, ruptă dintr-un caiet de matematică, având dimensiunea de
exact N  N pătrăţele. ı̂ndoiturile trebuie realizate exact pe o linie orizontală sau verticală.
Sunt permise două tipuri de ı̂ndoituri:
- ı̂ndoitura de tipul 1, ı̂ndoitură verticală executată la X pătrăţele faţă de marginea stângă
a foii: partea din stânga a foii se pliază către dreapta, de-a lungul liniei verticale aflate la
distanţa de X pătrăţele faţă de marginea stângă;
- ı̂ndoitura de tipul 2, ı̂ndoitură orizontală executată la X pătrăţele faţă de marginea supe-
rioară a foii: partea de sus a foii se pliază ı̂n jos, de-a lungul liniei aflate la distanţa de X
pătrăţele faţă de marginea de sus a hârtiei.
În urma realizării unei succesiuni de ı̂ndoituri, din foaia iniţială de hârtie se va obţine un obiect,
care va avea o formă dreptunghiulară, cu ı̂nălţimea H, lăţimea M şi având grosimea egală cu
numărul maxim de foi care se suprapun ı̂n cadrul obiectului obţinut.

Cerinţe

Dată fiind o succesiune de ı̂ndoituri aplicată unei foi de dimensiune N  N , scrieţi un program
care să determine ı̂nălţimea, lăţimea şi grosimea obiectului obţinut.

Date de intrare

Fişierul de intrare origami.in are următoarea structură:


- prima linie a fişierului conţine un număr natural N , reprezentând dimensiunea iniţială a
hârtiei;
- a doua linie conţine un număr natural K, reprezentând numărul ı̂ndoiturilor;
- pe următoarele K linii se găsesc perechi de numere naturale nenule, A B, separate printr-un
spaţiu, reprezentând tipul ı̂ndoiturii (A este 1 dacă se realizează o ı̂ndoitură verticală sau
A este 2 dacă se realizează o ı̂ndoitură orizontală), respectiv la ce distanţă se realizează
ı̂ndoitura;

Date de ieşire

Fişierul de ieşire origami.out va conţine, pe o singură linie, trei numere naturale nenule H, L,
G, separate prin câte un spaţiu, reprezentând ı̂nălţimea, lăţimea şi respectiv grosimea obiectului
obţinut.

Restricţii şi precizări

a 2 & N & 170


a 1 & K & 2N  2
a A 1 sau A 2
a 1 & B $ ı̂nălţimea sau lăţimea hârtiei la momentul respectiv (funcţie de tipul ı̂ndoiturii)

Exemple:

origami.in origami.out
Explicaţii
4 326 Hârtia are 4 unităţi ı̂nălţime şi 4 unităţi lăţime. Prima ı̂ndoitură
3 se realizează de la stânga la dreapta, de-a lungul celei de-a treia
13 linii verticale faţă de marginea stângă a foii. Se obţine o foaie de
23 ı̂nălţime 4, lăţime 3 şi grosime 2. A doua ı̂ndoitură se realizează
11 ı̂ndoind partea superioară a foii, ı̂n jos, de-a lungul celei de-a
treia linii orizontale faţă se marginea de sus a foii. Se obţine
un obiect de ı̂nălţime 3, lăţime 3 şi grosime 4. După a treia
ı̂ndoitură se obţine obiectul final, având ı̂nălţimea 3, lăţimea 2
şi grosimea 6.
Timp maxim de executare/test: 0.1 secunde
Memorie: total 16 MB din care pentru stivă 1 MB
CAPITOLUL 14. ONI 2009 14.5. ORIGAMI 387

14.5.1 Indicaţii de rezolvare

Reprezentarea foii de hârtie se va realiza cu ajutorul unui tablou bidimensional F , având iniţial
N linii şi N coloane şi elementele egale cu zero.
Deoarece ı̂ndoiturile se pot realiza doar pe liniile dintre elementele tabloului, F ij  va
reprezenta grosimea foii ı̂n zona pătrăţelei de pe linia i şi coloana j.
Matricea F va reflecta modificările aduse foii iniţiale după fiecare ı̂ndoitură din fişierul de
intrare, astfel:
- Pentru ı̂ndoitura de tip 1:
` Cazul 1: Distanta faţă de marginea stăngă a foii a liniei de-a lungul căreia se realizează
ı̂ndoitura (Lin) este mai mică decât jumatatea lăţimii hârtiei, adică 2 ˜ Lin & M .
ı̂n acest caz, realizez ı̂ndoitura suprapunând primele Lin linii, oglindite, peste
următoarele Lin linii, ı̂nsumând elementele corespunzătoare din F .
Mut apoi toate elementele matricei către stânga.
` Cazul 2: Distanta faţă de marginea stăngă a foii a liniei de-a lungul căreia se realizează
ı̂ndoitura (Lin) este mai mare decât jumatatea lăţimii hârtiei, adică 2 ˜ Lin % M .
Putem reduce acest caz la cazul precedent, mutând coloanele care, prin ı̂ndoire, depăşesc
marginea dreaptă a foii la sfârşitul tabloului, oglindite.
Se aplică apoi procedeul de la cazul precedent, ı̂ndoind foaia la distanţa M  Lin
elemente.
- Pentru ı̂ndoitura de tip 2 se procedează asemănător.
3
Algoritmul are, ı̂n cazul cel mai defavorabil, complexitatea O N r ˜ N .

14.5.2 Cod sursă

Listing 14.5.1: origami.cpp


#include <fstream>
#include <iostream>

using namespace std;

#define Fin "origami.in"


#define Fou "origami.out"
#define Nmax 171

typedef int Foaie[Nmax][Nmax];


Foaie F; //foaia de hartie

void indoaie(int &N, int &M, int Tip, int Lin)


{
int i,j, k ,CatIndoi, Rest, tmp;
if(Tip==1)
{
// daca indoitura depaseste cumva marginea dreapta a foii
if(2*Lin>M)
{
CatIndoi = M - Lin; //numarul de coloane ale indoiturii
Rest = Lin - CatIndoi; //cate coloane mut la sfarsit
}
else
{
CatIndoi = Lin;
Rest = 0;
}

// copiez primele Rest coloane, oglindite, la sfarsitul tabloului,


// deplasand elem la stanga
for(k=Rest;k>=1;k--)
for(i=1;i<=N;i++)
{
tmp = F[i][k];
for(j=k;j<M;j++) F[i][j] = F[i][j+1];
F[i][M] = tmp;
}
CAPITOLUL 14. ONI 2009 14.5. ORIGAMI 388

// indoi
for(j=1;j<=CatIndoi;j++)
for(i=1;i<=N;i++) F[i][2*CatIndoi-j+1] += F[i][j];

// mut elementele tabloului cu CatIndoi elemente la stanga


for(i=1;i<=N;i++)
for(j=1;j<=M-CatIndoi;j++) F[i][j] = F[i][j+CatIndoi];
M = M - CatIndoi;
}
else
{
//daca indoitura depaseste cumva marginea de jos a foii
if(2*Lin>N)
{
CatIndoi = N - Lin; // numarul de coloane ale indoiturii
Rest = Lin - CatIndoi; // cate coloane mut la sfarsit
}
else
{
CatIndoi = Lin;
Rest = 0;
}

// copiez primele Rest linii, oglindite, la sfarsitul tabloului,


// deplasand liniile in sus
for(k=Rest;k>=1;k--)
for(i=1;i<=M;i++)
{
tmp = F[k][i];
for(j=k;j<N;j++) F[j][i] = F[j+1][i];
F[N][i] = tmp;
}

// indoi
for(i=1;i<=CatIndoi;i++)
for(j=1;j<=M;j++) F[2*CatIndoi-i+1][j] += F[i][j];

// mut elementele tabloului cu CatIndoi elemente in sus


for(i=1;i<=N-CatIndoi;i++)
for(j=1;j<=M;j++)F[i][j] = F[i+CatIndoi][j];
N = N - CatIndoi;
}
}

int main()
{
ifstream f(Fin);
int N; // dimensiunea initiala a hartiei
int M; // latimea hartiei, initial = N
int Nr; // numarul indoiturilor

int Tip; // Tipul indoiturii: 1 verticala spre dreapta;


// 2 - orizontal de sus in jos
int Lin; // Linia de-a lungul careia facem indoitura
int Max; // Grosimea maxima
int i,j;

//citesc datele de intrare si indoi


f>>N>>Nr;
M = N; // initial hartia e patrata, pe urma nu

//initializez hartia
for(i=1;i<=N;i++) for(j=1;j<=M;j++) F[i][j] = 1;
for(i=1;i<=Nr;i++)
{
f>>Tip>>Lin;
indoaie(N,M,Tip,Lin);
}
f.close();

// aflu grosimea maxima si afisez datele de iesire


Max = 0;
for(i=1;i<=N;i++)
for(j=1;j<=M;j++)
if(F[i][j]>Max) Max = F[i][j];
CAPITOLUL 14. ONI 2009 14.6. PATRATE 389

ofstream g(Fou);
g<<N<<" "<<M<<" "<<Max<<’\n’;
/*
for(i=1;i<=N;i++)
{
for(j=1;j<=M;j++) g<<F[i][j]<<" ";
g<<’\n’;
}
*/
g.close();
return 0;
}

14.5.3 *Rezolvare detaliată

14.6 patrate
Problema 6 - patrate 100 de puncte
Fiind date două numere naturale n şi p se cere să se găsească un număr natural m & 350.000
cu proprietatea că el poate fi scris atât ca sumă de p pătrate perfecte nenule, cât şi ca sumă de
p  1 pătrate perfecte nenule, ..., cât şi ca sumă de n pătrate perfecte nenule.

Cerinţe

Date de intrare

Prima linie a fişierului de intrare patrate.in conţine două numere naturale n şi p separate
printr-un spaţiu, având semnificaţia de mai sus.

Date de ieşire

Prima linie a fişierului de ieşire patrate.out va conţine numărul natural m căutat.


Urmează n  p  1 linii. Linia i a fişierului, pentru i 2, 3, ..., n  p  2, va conţine p  i  2
numere naturale separate prin câte un spaţiu, cu proprietatea că suma pătratelor acestora este m.

Restricţii şi precizări

a2 & n & 1000


a2&p&n
a Soluţia nu este unică, se va accepta orice soluţie corectă;
a Un program corect, care se ı̂ncadrează ı̂n timp pentru n & 30, va obţine cel puţin 30 de
puncte.
a Un program corect, care se ı̂ncadrează ı̂n timp pentru n & 150, va obţine cel puţin 70 de
puncte.

Exemple:

patrate.in patrate.out Explicaţii


2 2 2
43 18 18 1  1  4
2 2 2 2
114 18 2  1  2  3
2123
Timp maxim de executare/test: 0.4 secunde
Memorie: total 16 MB din care pentru stivă 1 MB
CAPITOLUL 14. ONI 2009 14.6. PATRATE 390

14.6.1 Indicaţii de rezolvare

O primă idee, greu de implementat, este de a genera toate submulţimile de 2, 3, ..., n elemente
ale mulţimii numerelor naturale şi apoi de ale căuta pe acelea cu proprietatea că dau aceeaşi
sumă a pătratelor. Acesta idee este inutilizabilă din punct de vedere practic deoarece numărul de
submulţimi este foarte mare.
Soluţia se bazează pe o idee constructivă, mai precis pe construirea din aproape ı̂n aproape a
soluţiei.
Să demonstrăm pentru ı̂nceput, din punct de vedere matematic, că există un număr natural
m care se poate scrie, simultan ca suma de 2, 3, ..., n pătrate perfecte nenule, de numere ı̂ntregi.
Vom demonstra prin inducţie această proprietate.
P(1):
2 2
Pentru n 2 se găseşte m2 1 1 2
2 2 2 2 2
Pentru n 3 se găseşte m3 1 4 2 2 3 17
P(k): Presupunem relaţia adevărată pentru n k, cu alte cuvinte există un număr m care se
2 2 2 2 2
scrie ca suma de 2, 3, ..., k pătrate de numere ı̂ntregi nenule. Aşadar mk a1  a2 b1  b2  b3
2 2 2
... l1  l2  ...  lk
P(k+1): Vom demonstra acum ca şi relaţia P(k+1) este adevărată.
Mai ı̂ntâi vom utiliza un alt rezultat, uşor de demonstrat, şi anume că orice număr natural
p ' 7 poate fi scris sub forma p a  b  c . Vom aplica acest rezultat pentru mk şi vom obţine
2 2 2
2 2 2
3 numere ı̂ntregi a, b, c, astfel ı̂ncât mk a  b  c .
Odată mk scris sub acesta formă avem :
2 2 2 2 2 2 2 2 2 2 2 2
mk1 mk  c a2  b2 a1  a2  c b1  b2  b3  c ... l1  l2  ...  lk  c .
2 2 2
Exemplu m3 17 3  3  1 ,
2 2 2 2 2 2 2 2 2 2
de unde m4 17  1 3 3 1 4 1 2 2 3 1 18
Aşadar P(k+1) este adevărată deci P(n) este adevărată pentru orice n natural.
Soluţia urmăreşte mecanismul descris anterior, cu observaţia că pentru a genera un număr m
2 2 2
cât mai mic, fără a avea pretenţia că este cel mai mic, facem descompunerea mk a  b  c ı̂n
aşa fel ı̂ncât c să fie minim.

14.6.2 Cod sursă

Listing 14.6.1: patrate.cpp


#include <stdio.h>
#include <math.h>

#define FOR(i,s,d) for(i=(s);i<(d);++i)


#define nmax 100111

int n, A[nmax], B[nmax],C[nmax], p,a,b,c;

int test(int x)
{
int i,j;
FOR(i, 1, 10000)
{
if(x < i*i) break;
j = int(sqrt(x - i * i));
if(j*j + i*i != x) continue;
a = i;
b = j;
return 1;
}
return 0;
}

int doit(int x)
{
int i;
FOR(i, 1, 1000)
if(test(x + i * i))
CAPITOLUL 14. ONI 2009 14.6. PATRATE 391

{
c = i;
return x + i * i;
}
return -1;
}

int main()
{
int i,j,l,k;
freopen("patrate.in","r",stdin);
freopen("patrate.out","w",stdout);

scanf("%d", &n);
scanf("%d", &p);

j = 2;
A[0] = B[0] = 1;
C[0] = 0;
l = 1;
FOR(i, 3, n + 1)
{
j = doit(j);
A[l] = a ;
B[l] = b ;
C[l] = c ;
l++;
}
printf("%d\n", j);

FOR(i,p,n+1)
{
k = n - i;
printf("%d %d",A[k],B[k]);
FOR(j,k+1, l) printf(" %d",C[j]);
printf("\n");
}

return 0;
}

14.6.3 *Rezolvare detaliată


Capitolul 15

ONI 2008

15.1 ab
Problema 1 - ab 100 de puncte
Una din cele mai noi pasiuni ale lui Zăhărel este să studieze diverse proprietăţi ale permutărilor.
De exemplu, este interesat de permutările ı̂n care cel mai lung subşir crescător şi cel mai lung
subşir descrescător au lungimi date.

Cerinţe

Să se scrie un program care determină o permutare de lungime N ı̂n care cel mai lung subşir
crescător are lungime A şi cel mai lung subşir descrescător are lungime B.

Date de intrare

Fişierul de intrare ab.in va conţine pe prima linie numerele N A B.

Date de ieşire

Fişierul de ieşire ab.out va conţine pe prima linie N numere separate prin câte un spaţiu,
reprezentând o permutare care respectă condiţiile de mai sus. Dacă există mai multe soluţii, se
va afişa cea minimă din punct de vedere lexicografic.

Restricţii şi precizări

a 1 & N, A, B & 30.000


a Se garantează că mereu există soluţie pentru datele de intrare
a Se numeşte subşir al şirului X x1 , x2 , ..., xN , un şir Y xi1 , xi2 , ..., xiM  cu proprietatea
1 & i1 $ i2 $ ... $ iM & N
a Un şir x1 , x2 , ..., xK  este mai mic din punct de vedere lexicografic decat un alt şir
y1 , y2 , ..., yK  dacă există o poziţie p & K, astfel ı̂ncat xp $ yp şi x1 y1 , x2 y2 , ..., xp1 yp1
a Pentru un test se va acorda 70% din punctaj dacă permutarea afişată are cel mai lung subşir
crescător de lungime A şi cel mai lung subşir descrescător de lungime B, dar nu este minimă
lexicografic.

Exemple:

ab.in ab.out
Explicaţii
423 1432 Cel mai lung subşir crescător are lungime 2 (1 4, 1 3 sau 1 2), iar cel
mai lung subşir descrescător are lungime 3 (4 3 2).
O altă soluţie posibilă este 2 4 3 1, dar aceasta nu este minimă din punct
de vedere lexicografic.
Timp maxim de executare/test: 0.1 secunde

392
CAPITOLUL 15. ONI 2008 15.1. AB 393

15.1.1 Indicaţii de rezolvare

Mircea Paşoi

Vom prezenta un algoritm prin care se poate obţine o permutare de lungime N cu cel mai lung
subşir crescător de lungime A şi cel mai lung subşir descrescător de lungime B:
1) Se ı̂mpart numerele de la 1 la N (luate ı̂n ordinea asta) ı̂n A grupuri de lungime cel mult B
fiecare. Deasemenea, trebuie să existe cel puţin un grup de lungime fix B.
2) Se inversează elementele din fiecare grup
Orice subşir descrescător va face parte dintr-un grup, iar cum lungimea unui grup este maxim
B (şi există unul cu fix B), cel mai lung subşir descrescător va avea lungime B.
Orice subşir crescător va fi format cu câte un element din fiecare grup. Cum sunt A grupuri,
cel mai lung subşir crescător va avea lungime A.
Pentru a obţine o soluţie minimă lexicografic trebuie ca grupurile de la ı̂nceput să fie cât mai
mici ca mărime.
Exemplu pentru N 10, A 4, B 3
Se ı̂mparte ı̂n 4 grupuri, fiecare de lungime maxim 3 astfel ı̂ncât grupurile de la ı̂nceput să fie
cât mai mici:
1 — 2 3 4 — 5 6 7 — 8 9 10
Se inversează elementele din fiecare grup şi se obţine permutarea:
1 4 3 2 7 6 5 10 9 8

15.1.2 Cod sursă

Listing 15.1.1: ab.cpp


#include <stdio.h>

#define MAX_A 30005


#define FIN "ab.in"
#define FOUT "ab.out"
#define min(a, b) ((a) < (b) ? (a) : (b))

int N, A, B, size[MAX_A];

int main(void)
{
int i, j, n;

freopen(FIN, "r", stdin);


freopen(FOUT, "w", stdout);

scanf("%d %d %d", &N, &A, &B);

if (N < A+B-1 || N > A*B)


{
printf("Fara solutie\n");
return 0;
}

n = N;
for (i = A-1; i >= 0; --i)
{
size[i] = min(n-i, B);
n -= size[i];
}
for (i = 0; i < A; ++i)
{
for (j = n+size[i]; j > n; --j)
printf("%d ", j);
n += size[i];
}
printf("\n");

return 0;
}
CAPITOLUL 15. ONI 2008 15.2. IEPURAS 394

15.1.3 *Rezolvare detaliată

15.2 iepuras
Problema 2 - iepuras 100 de puncte
Un iepuraş se găseşte ı̂ntr-o grădină plină de surprize. Harta grădinii poate fi reprezentată sub
forma unei table dreptunghiulare cu m linii, numerotate de la 1 la m de sus ı̂n jos, şi n coloane,
numerotate de la 1 la n de la stânga la dreapta. ı̂n fiecare celulă a acestei grădini se poate găsi
cel mult una dintre următoarele surprize: săgeată, pom, zid, trapă, morcov, bombă.
O săgeată indică una din direcţiile nord, sud, est, vest. Odată ajuns ı̂ntr-o celulă conţinând
o astfel de săgeată iepuraşul ı̂şi va continua deplasarea ı̂n sensul indicat de săgeată, iar aceasta
dispare.
Într-o celulă ı̂n care se găseşte un pom sau un zid iepuraşul nu poate să pătrundă, ı̂nsă dacă
se ”loveşte” de un pom, el ı̂şi păstrează direcţia, ı̂nsă schimbă sensul (dacă se deplasa spre nord,
ı̂şi va schimba sensul spre sud, dacă se deplasa spre est, se va deplasa după aceea spre vest etc).
Dacă iepuraşul intră ı̂ntr-o celulă conţinând o trapă, toate zidurile aflate pe teren dispar şi vor
apărea alte ziduri, ı̂n poziţii precizate. Dacă iepuraşul va trece din nou printr-o celulă conţinând o
trapă, zidurile nou construite dispar şi vor reapărea zidurile iniţiale. Mai exact există două grupe
de ziduri care comută la fiecare trecere printr-o celulă care conţine o trapă.
Dacă iepuraşul va trece de două ori prin vecinătatea unei celule conţinând o bombă (adică
prin celulele ı̂nvecinate la sud, nord, est sau vest cu celula conţinând bombă), bomba va exploda
iar iepuraşul se transformă instantaneu ı̂n ı̂ngeraş. De asemenea, dacă iepuraşul ı̂ntră ı̂ntr-o celulă
conţinând o bombă se transformă instantaneu ı̂n ingeras.
Iepuraşul va ronţăi toţi morcovii care ı̂i ies ı̂n cale. Evident că dacă trece a doua oară prin
aceeaşi celulă, la a doua trecere nu va mai găsi morcov.
Surprizele din grădină sunt codificate astfel: 1 pentru săgeată spre nord, 2 pentru săgeată spre
vest, 3 pentru săgeată spre sud, 4 pentru săgeată spre est, 5 pentru pom, 6 pentru bombă, 7
pentru morcov, 8 pentru zid, 9 pentru trapă. Căsuţele libere de pe harta grădinii se codifică cu
0.
Iniţial, se cunosc poziţia şi sensul de deplasare ale iepuraşului. Expediţia acestuia se termină
ı̂n următoarele situaţii:
- la explozia unei bombe, caz ı̂n care se transformă ı̂n ı̂ngeraş;
- la părăsirea grădinii (adică la ieşirea ı̂n afara zonei dreptunghiulare date), caz ı̂n care se
rătăceşte;
- ı̂n momentul ı̂n care reuşeşte să ronţăie toţi morcovii, caz ı̂n care este fericit.

Cerinţe

Fiind data harta grădinii se cere să se determine starea finală a iepuraşului (ı̂ngeraş, rătăcit
respectiv fericit), numărul de morcovi ronţăiţi până ı̂n momentul terminării expediţiei, precum şi
numărul de paşi pe care ı̂i face iepuraşul până la acest moment.

Date de intrare

Pe prima linie a fişierului de intrare iepuras.in se găsesc două numere ı̂ntregi m şi n, separate
printr-un spaţiu, reprezentând numărul de linii, respectiv numărul de coloane ale hărţii.
Linia a doua a fişierului conţine trei numere naturale, separate prin câte un spaţiu, reprezentând
linia si coloana poziţiei iniţiale a iepuraşului pe hartă precum şi direcţia spre care acesta este
orientat. Direcţia este codificată astfel: 1 pentru nord, 2 pentru vest, 3 pentru sud, 4 pentru est.
Următoarele m linii conţin câte n numere ı̂ntregi separate prin câte un spaţiu, reprezentând
codificarea hărţii grădinii, conform celor precizate mai sus.
Următoarea linie conţine un singur număr natural t reprezentând numărul de celule ce vor
conţine ziduri după prima trecere printr-o celulă conţinând o trapă. Următoarele t linii conţin
câte două numere naturale i şi j, separate printr-un spaţiu, reprezentând coordonatele câte unei
celule ce va conţine zid după o primă trecere printr-o celulă conţinând o trapă.

Date de ieşire
CAPITOLUL 15. ONI 2008 15.2. IEPURAS 395

Fişierul de ieşire iepuras.out va conţine pe prima sa linie unul dintre cuvintele INGERAS,
RATACIT respectiv FERICIT, corespunzător stării finale a iepuraşului.
A doua linie a fişierului va conţine două numere ı̂ntregi separate printr-un spaţiu, reprezentând
linia şi coloana ultimei poziţii de pe teren a iepuraşului, adică poziţia ı̂n care a murit, sau ı̂n care
a devenit fericit, respectiv ultima poziţie a sa de pe teren, ı̂nainte de a se rătăci.
A treia linie a fişierului va conţine două numere ı̂ntregi separate printr-un spaţiu, reprezentând
numărul de morcovi culeşi până ı̂n momentul terminării expediţiei, respectiv numărul de paşi pe
care ı̂i face iepuraşul până ajunge ı̂n starea finală.

Restricţii şi precizări

a poziţia iniţială a iepuraşului este una validă, adică este o celulă liberă din interiorul terenului;
a pentru datele de test, se asigură că iepuraşul va face un număr finit de paşi până la terminarea
expediţiei sale;
a 1 & m, n & 200
a 0 & t & 20
a numărul maxim de ziduri aflate la un moment dat pe teren este de cel mult 50, şi este posibil
ca la un moment dat să nu existe niciun zid pe teren.
a Se garantează că pentru datele de intrare iepuraşul nu poate ajunge simultan ı̂n două din
cele trei stări.
a Pomii şi trapele sunt obiecte care rămân permanent ı̂n teren, ı̂n poziţiile lor iniţiale.
a ı̂ntr-o celulă a grădinii se poate găsi la un moment dat o singură surpriză (pom, zid, trapă,
morcov, săgeată, bombă)

Exemple:

Timp maxim de executare/test: 0.2 secunde pe Windows, 0.1 secunde pe Linux

15.2.1 Indicaţii de rezolvare

prof. Carmen Popescu

Pentru fiecare celulă ı̂n care ”intră” iepuraşul se verifică, ı̂n ordine, următoarele:
- dacă a iesit din teren, atunci iepuraşul e rătăcit
- dacă celula conţine un morcov acesta este ronţăit (se contorizează numărul de morcovi
ronţăiţi)
CAPITOLUL 15. ONI 2008 15.2. IEPURAS 396

- dacă celula conţine o bombă iepuraşul devine ı̂ngeraş


- dacă ı̂ntr-una din celulele vecine există o bombă se disting următoarele situaţii
t este prima trecere prin vecinătatea acestei bombe, atunci ı̂n matricea ce memorează
harta se va ı̂nlocui valoarea 6 cu altă valoare direfită de 0, 1, ..., 9.
t dacă iepuraşul a trecut a doua oară pe lângă această bomba (valoarea e diferită de
0,...9) atunci iepuraşul devine ı̂ngeraş
- dacă s-au cules toţi morcovii iepuraşul este fericit.
- dacă celula conţine o trapă se ”şterg” zidurile curente (poziţiile marcate cu 8 ı̂n matrice) şi
se marchează tot cu 8 poziţiile noilor ziduri.
- dacă celula conţine o săgeată se schimbă sensul de deplasare al iepuraşului (1  3, 2  4)
şi se şterge săgeata (se pune 0 ı̂n matrice ı̂n celula respectivă)
- dacă următoarea celulă de pe direcţia de deplasare a iepuraşului conţine un pom sau un zid
atunci se schimbă sensul de deplasare al iepuraşului (1  
3, 2 4)

15.2.2 Cod sursă

Listing 15.2.1: iepuras.cpp


#include <iostream>
#include <fstream>

using namespace std;

int m,n;
char a[201][201];
int di[4]={-1,0,1,0};
int dj[4]={0,-1,0,1};

struct zid
{
int i,j;
};
zid z1[50],z2[50];

int afara(int i,int j)


{
if (i<1 || i>m) return 1;
if (j<1 || j>n) return 1;
return 0;
}

int explod(int i,int j)


{
int k,i1,j1;
if (a[i][j]==’6’ || a[i][j]==’*’)
return 1;

for (k=0;k<4;k++)
{
i1=i+di[k];
j1=j+dj[k];

if (i1>=0 && i1<=m && j1>=1 && j1<=n)


if (a[i1][j1]==’6’)
a[i1][j1]=’*’;
else
if (a[i1][j1]==’*’)
return 1;
}

return 0;
}

main()
{
int k,i0,j0,d,i,j,n1=0,n2=0,pz=1;
long nrm,p=0,q=0;

ifstream f("iepuras.in");
ofstream g("iepuras.out");
CAPITOLUL 15. ONI 2008 15.2. IEPURAS 397

nrm=0;
f>>m>>n;
f>>i0>>j0>>d;

for (i=1;i<=m;i++)
for (j=1;j<=n;j++)
{
f>>a[i][j];
if (a[i][j]==’7’) nrm++;
if (a[i][j]==’8’)
{
z1[n1].i=i;
z1[n1].j=j;
n1++;
}
}

f>>n2;
for (i=0;i<n2;i++)
f>>z2[i].i>>z2[i].j;
f.close();

i=i0;
j=j0;
while (1)
{
i=i+di[d-1];
j=j+dj[d-1];
p++;

if (i==3 && j==190)


cout<<"bla";

// afara
if (afara(i,j))
{
g<<"RATACIT"<<endl;
g<<i-di[d-1]<<" "<<j-dj[d-1]<<endl;
g<<q<<" "<<p;
break;
}

// morcov
if (a[i][j]==’7’)
{
q++;
a[i][j]=’0’;
}

// e bomba sau in vecinatatea unei bombe pregatita sa explodeze


if (explod(i,j))
{
g<<"INGERAS"<<endl;
g<<i<<" "<<j<<endl;
g<<q<<" "<<p;
break;
}

// a cules toti morcovii


if (q==nrm)
{
g<<"FERICIT"<<endl;
g<<i<<" "<<j<<endl;
g<<q<<" "<<p;
break;
}

if (a[i][j]==’9’)
{
if (pz==1)
{
for (k=0;k<n1;k++)
a[z1[k].i][z1[k].j]=’0’;

for (k=0;k<n2;k++)
CAPITOLUL 15. ONI 2008 15.3. PALIND 398

a[z2[k].i][z2[k].j]=’8’;

pz=2;
}
else
{
for (k=0;k<n2;k++)
a[z2[k].i][z2[k].j]=’0’;

for (k=0;k<n1;k++)
a[z1[k].i][z1[k].j]=’8’;

pz=1;
}
}

// sageata, schimba directia


if (a[i][j]==’1’) { d=1; a[i][j]=’0’; }
if (a[i][j]==’2’) { d=2; a[i][j]=’0’; }
if (a[i][j]==’3’) { d=3; a[i][j]=’0’; }
if (a[i][j]==’4’) { d=4; a[i][j]=’0’; }

// pom sau zid, schimba directia


if (a[i+di[d-1]][j+dj[d-1]]==’5’ || a[i+di[d-1]][j+dj[d-1]]==’8’)
if (d==1)
d=3;
else
if (d==2)
d=4;
else
if (d==3)
d=1;
else
d=2;
}

g.close();
}

15.2.3 *Rezolvare detaliată

15.3 palind
Problema 3 - palind 100 de puncte
Ana a descoperit că are o adevărată pasiune pentru palindroame. Un şir de numere este
palindrom dacă se citeşte la fel de la stânga la dreapta şi de la dreapta la stânga (primul număr
este egal cu ultimul, al doilea cu penultimul etc). Ea are un şir cu N numere naturale şi vrea ca
orice subsecvenţă de lungime impară a şirului să fie palindrom. Pentru a-şi ı̂ndeplini dorinţa ea
poate efectua asupra şirului mai multe operaţii. O operaţie constă ı̂n alegerea unui element din
şir şi incrementarea sau decrementarea lui cu o unitate. Bineı̂nţeles, Ana doreşte să utilizeze un
număr minim de operaţii pentru ca şirul obţinut să respecte proprietatea amintită mai sus (orice
subsecvenţă de lungime impară să fie palindrom).

Cerinţe

Determinaţi pentru Ana numărul minim de operaţii pe care trebuie să-l efectueze pentru
ca orice subsecvenţă de lungime impară a şirului obţinut ı̂n urma efectuării operaţiilor să fie
palindrom. De asemenea aflaţi şi numărul de şiruri finale distincte pe care le poate obţine efectuând
acest numar minim de operaţii.

Date de intrare

Fişierul de intrare palind.in va conţine pe prima linie numărul T , reprezentând numărul de


seturi de date de intrare care urmează. ı̂n continuare urmează seturile de date de intrare, fiecare
pe câte două linii. Pe prima linie a unui set de date se află numărul N , având semnificaţia din
enunţ. Pe următoarea linie se află elementele şirului iniţial, separate prin câte un spaţiu.
CAPITOLUL 15. ONI 2008 15.3. PALIND 399

Date de ieşire

Fişierul de ieşire palind.out va conţine T linii, pe linia i aflându-se două numere, reprezentând
raspunsul pentru al i-lea set de date de intrare. Primul numar este numarul minim de operaţii,
iar al doilea numărul de şiruri distincte finale care se pot obţine efectuând numărul minim de
operaţii.

Restricţii şi precizări

a 1 & T & 20
a 1 & N & 10.000
a Elementele şirului sunt numere naturale din intervalul 1, 7.000
a subsecvenţă a unui şir este un subşir de numere care apar pe poziţii consecutive
a Toate testele folosite la corectare vor avea T 20
a Pentru 20% din teste 1 & N & 100
a Pentru 20% din teste valoarea maximă din oricare şir este cel mult 500 şi N ' 101

Exemple:

palind.in palind.out Explicaţii


2 23 Pentru primul test, cele trei şiruri posibile sunt:
3 01 1 2 1, 2 2 2 si 3 2 3.
123 Pentru al doilea test, singurul şir posibil este format din
1 elementul 3
3
Timp maxim de executare/test: 0.5 secunde pe Windows, 0.2 secunde pe Linux

15.3.1 Indicaţii de rezolvare

Adrian Airinei

Pentru ca orice subsecvenţă de lungime impară să fie palindrom şirul trebuie să fie de forma
XY XY XY XY...XY .
Astfel putem rezolva independent problema pentru poziţiile pare şi impare.
Dacă fixăm primul element din şir observăm că trebuie să minimizăm o sumă de forma
—V-A1—+—V-A2—+....+—V-An—.
Primele idei sunt de a fixa fiecare valoare posibilă şi apoi de a parcurge şirul ı̂n O N  sau vec-
torul frecvenţelor ı̂n O V M AX  pentru a obţine O V M AX ˜ N  respectiv O V M AX ˜ V M AX .
Pentru a optimiza soluţia vom calcula
cnti = suma care se obţine
dacă fixăm valoarea V i luăm ı̂n calcul doar valorile din şir mai mici decât i.
Observăm că dacă există N R numere mai mici decât i  1 ı̂n şir
cnti cnti  1  N R  C,
unde C = câte numere sunt egale cu i  1.
Vom aplica un procedeu asemănător şi pentru numerele mai mari decât i.
O altă soluţie ar fi să observăm că elementul V este de fapt mediana şirului A1A2...An care
se poate determina ı̂n O N lgN  cu o sortare sau ı̂n O N  cu statistici de ordine.

15.3.2 Cod sursă

Listing 15.3.1: palind.c


#include <stdio.h>
#include <string.h>

#define INF 2000000000


#define MAXN 7001
#define ABS(x) ((x) < 0 ? (-(x)) : (x))

int N, A[2][MAXN], cnt[MAXN];


CAPITOLUL 15. ONI 2008 15.3. PALIND 400

int sol[2][2];

void baga(int t)
{
int i, nr, val, x, mmin = INF;
memset(cnt, 0, sizeof(cnt));
for(val = nr = 0, i = 1; i < MAXN; i++)
val += nr, val += A[t][i-1], nr += A[t][i-1], cnt[i] += val;
for(nr = val = 0, i = MAXN-2; i >= 1; i--)
val += nr, val += A[t][i+1], nr += A[t][i+1], cnt[i] += val;
for(nr = 0, i = 1; i < MAXN; i++)
{
if(cnt[i] == mmin) nr++;
if(cnt[i] < mmin) mmin = cnt[i], nr = 1;
}
sol[t][0] = mmin, sol[t][1] = nr;
}

void solve(void)
{
int i, val, x;

memset(A, 0, sizeof(A));

scanf("%d", &N);
for(i = 1; i <= N; i++)
scanf("%d", &x), A[i&1][x]++;

baga(0), baga(1);
printf("%d %d\n", sol[0][0]+sol[1][0], sol[0][1]*sol[1][1]);
}

#define VMAX 7000

void brute(void)
{
int p[2][2], i, j, x, nr, val, t, mmin;

for(t = 0; t <= 1; t++)


{
nr = 0, mmin = INF;
for(i = 1; i <= VMAX; i++)
{
for(val = 0, j = 1; j <= VMAX; j++)
val += ABS(j-i)*A[t][j];
if(val == mmin) nr++;
if(val < mmin) mmin = val, nr = 1;
}
p[t][0] = mmin, p[t][1] = nr;
}
fprintf(stderr, "%d %d\n", p[0][0]+p[1][0], p[0][1]*p[1][1]);
}

int main(void)
{
freopen("palind.in", "rt", stdin);
freopen("palind.out", "wt", stdout);

int teste;

scanf("%d ", &teste);


while(teste--)
solve();//, brute();

return 0;
}

Listing 15.3.2: palindvxn.c


#include <stdio.h>
#include <string.h>

#define INF 2000000000


#define MAXN 7001
#define ABS(x) ((x) < 0 ? (-(x)) : (x))
CAPITOLUL 15. ONI 2008 15.3. PALIND 401

int N, A[2][MAXN], cnt[2];


int sol[2][2];

void solve(void)
{
int i, val, t, x, j, nr, mmin = INF;

memset(A, 0, sizeof(A)), cnt[0] = cnt[1] = 0;

scanf("%d", &N);
for(i = 1; i <= N; i++)
scanf("%d", &x), A[i&1][ ++cnt[i&1] ] = x;

for(t = 0; t <= 1; t++)


{
nr = 0, mmin = INF;
for(i = 1; i < MAXN; i++)
{
for(val = 0, j = 1; j <= cnt[t]; j++)
val += ABS(i-A[t][j]);
if(val == mmin) nr++;
if(val < mmin) mmin = val, nr = 1;
}
sol[t][0] = mmin, sol[t][1] = nr;
}

printf("%d %d\n", sol[0][0]+sol[1][0], sol[0][1]*sol[1][1]);


}

int main(void)
{
freopen("palind.in", "rt", stdin);
freopen("palind.out", "wt", stdout);

int teste;

scanf("%d ", &teste);


while(teste--)
solve();//, brute();

return 0;
}

Listing 15.3.3: palindvxv.c


#include <stdio.h>
#include <string.h>

#define INF 2000000000


#define MAXN 7001
#define ABS(x) ((x) < 0 ? (-(x)) : (x))

int N, A[2][MAXN], cnt[MAXN];


int sol[2][2];

#define VMAX 500

void brute(void)
{
int p[2][2], i, j, x, nr, val, t, mmin;

memset(A, 0, sizeof(A));
scanf("%d", &N);
for(i = 1; i <= N; i++)
scanf("%d ", &x), A[i&1][x]++;

for(t = 0; t <= 1; t++)


{
nr = 0, mmin = INF;
for(i = 1; i <= VMAX; i++)
{
for(val = 0, j = 1; j <= VMAX; j++)
val += ABS(j-i)*A[t][j];
if(val == mmin) nr++;
CAPITOLUL 15. ONI 2008 15.4. AUTO 402

if(val < mmin) mmin = val, nr = 1;


}

p[t][0] = mmin, p[t][1] = nr;


}

printf("%d %d\n", p[0][0]+p[1][0], p[0][1]*p[1][1]);


}

int main(void)
{
freopen("palind.in", "rt", stdin);
freopen("palind.out", "wt", stdout);

int teste;

scanf("%d ", &teste);


while(teste--)
brute();

return 0;
}

15.3.3 *Rezolvare detaliată

15.4 auto
Problema 4 - auto 100 de puncte
Se consideră o autostradă dispusă ı̂n linie dreaptă având N puncte de acces (intrare şi ieşire). ı̂n
fiecare punct de acces există containere pentru colectarea deşeurilor, toate containerele au aceeaşi
capacitate şi ı̂n fiecare punct de acces pot fi mai multe astfel de containere.
Firma care asigură curăţenia dispune de un singur mijloc de transport al containerelor. Acest
mijloc de transport poate ı̂ncărca exact un număr K de containere. Accesul mijlocului de transport
pe autostradă se face cu restricţii pentru a nu perturba traficul şi din acest motiv trebuie ca la
fiecare acces pe autostradă să fie colectate exact atâtea containere cât este capacitatea maşinii,
dar dintr-un punct de colectare trebuie să ia exact un container, deci dacă se intră pe autostradă
la punctul de acces P , unde P & N  K  1, atunci trebuie să ia containere de la punctele de
acces numerotate cu P, P  1, P  2, ..., P  K  1, ı̂n aceste puncte de acces scade cu 1 numărul
containerelor rămase.
Firma trebuie să găsească toate valorile posibile pentru K astfel ı̂ncât să poată colecta toate
containerele.

Cerinţe

Se cere să se găsească toate valorile posibile pentru K astfel ı̂ncât să fie adunate toate con-
tainerele.

Date de intrare

Fişierul de intrare auto.in va conţine pe prima linie numărul natural T , reprezentând numărul
de seturi de date de intrare. În continuare urmează seturile de date de intrare, fiecare pe cate două
linii. Pe prima linie a unui set se află numărul N , având semnificaţia din enunţ. Pe următoarea
linie se află N numere naturale separate printr-un spaţiu, reprezentând numărul de containere din
fiecare punct de acces.

Date de ieşire

Fişierul de ieşire auto.out va conţine T linii, pe linia i aflându-se răspunsul pentru al i-lea set
de date de intrare. Valorile posibile pentru K se vor afişa ı̂n ordine crescatoare, separate printr-un
spaţiu.

Restricţii şi precizări


CAPITOLUL 15. ONI 2008 15.4. AUTO 403

a 2 & T & 30
a 2 & N & 9 000
a 1&K&N
a 0 & numărul de containere din fiecare punct de acces & 10 000

Exemple:

auto.in auto.out Explicaţii


2 12
8 13
12342000
3
111
Timp maxim de executare/test: 0.8 secunde pe Windows, 0.2 secunde pe Linux

15.4.1 Indicaţii de rezolvare

Pentru un K fixat, putem calcula uşor ı̂n O N ˜ K  dacă reprezintă soluţie sau nu. Această
3
abordare are complexitate O N  şi obţine aproximativ 30 puncte.
Putem folosi o stivă ı̂n care avem la un moment dat invervalele sortate crescător după capătul
dreapta. Când suntem la al i-lea punct de intrare scoatem din stivă intervalele care nu conţin
punctul acesta şi eventual introducem dacă este nevoie intervalul i, i  k  1. Această soluţie are
2
complexitate O N  şi obtine 70 puncte.
Pentru a optimiza această soluţie observăm că dacă suma tuturor valorilor este SU M este
necesar să considerăm valorile posibile pentru K care este divizor al lui SU M . Astfel complexitatea
devine O N DIV ˜ N , unde N DIV este numărul de divizori ai lui SU M mai mici sau egali cu
N.

15.4.2 Cod sursă

Listing 15.4.1: auto.c


#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#define MAXN 9001


#define MAXDIV 770

int N, NR, A[MAXN], st[MAXN], pos[MAXN], sol[MAXDIV];


long steps = 0;

int check(int K)
{
int i, j, t = 1, p = 0, val = 0;

for(i = 1; i <= N; i++)


{
steps++;
while(t <= p && pos[t] <= i-K) val -= st[t++];
if(A[i] < val || (i > N-K+1 && A[i] != val)) return 0;
if(A[i] > val) pos[++p] = i, st[p] = A[i]-val, val = A[i];
}

return 1;
}

void read_and_solve(void)
{
int i, j, k; long d = 0;

scanf("%d\n", &N), NR = 0;
assert(N >= 1 && N <= 9000);
CAPITOLUL 15. ONI 2008 15.4. AUTO 404

for(i = 1; i <= N; i++) scanf("%d ", &A[i]), d += (long)A[i],


assert(A[i] >= 0 && A[i] <= 10000);

for(i = 1; i <= N; i++)


if(d % i == 0 && check(i)) sol[++NR] = i;

for(i = 1; i <= NR; i++)


for(j = i+1; j <= NR; j++)
if(sol[i] > sol[j]) k = sol[i], sol[i] = sol[j], sol[j] = k;

for(i = 1; i <= NR; i++) printf("%d ", sol[i]);


printf("\n");
}

int main(void)
{
int teste, start, end;

freopen("auto.in", "rt", stdin);


freopen("auto.out", "wt", stdout);

start = clock();

scanf("%d ", &teste);


while(teste--)
read_and_solve();

fprintf(stderr, "%ld\n", steps);

end = clock();

fprintf(stderr, "%lf\n", (double)(end-start)/CLOCKS_PER_SEC);

return 0;
}

Listing 15.4.2: autonk.c


#include <stdio.h>
#include <string.h>

#define MAXN 9001


#define MAXDIV 770

int N, NR, A[MAXN], st[MAXN], pos[MAXN], sol[MAXDIV];


long steps;

int check(int K)
{
int i, j, t = 1, p = 0, val = 0;

for(i = 1; i <= N; i++)


{
steps++;
while(t <= p && pos[t] <= i-K) val -= st[t++];
if(A[i] < val || (i > N-K+1 && A[i] != val)) return 0;
if(A[i] > val) pos[++p] = i, st[p] = A[i]-val, val = A[i];
}

return 1;
}

void read_and_solve(void)
{
int i, j, k; long d = 0;

scanf("%d\n", &N), NR = 0;
for(i = 1; i <= N; i++) scanf("%d ", &A[i]), d += (long)A[i];

for(i = 1; i <= N; i++)


if(check(i)) sol[++NR] = i;

for(i = 1; i <= NR; i++) printf("%d\n", sol[i]);


}
CAPITOLUL 15. ONI 2008 15.5. DIV 405

int main(void)
{
int teste;

freopen("auto.in", "rt", stdin);


freopen("auto2.out", "wt", stdout);

scanf("%d ", &teste);


while(teste--)
read_and_solve();

fprintf(stderr, "%ld\n", steps);

return 0;
}

15.4.3 *Rezolvare detaliată

15.5 div
Problema 5 - div 100 de puncte
Se citesc două numere naturale M şi N .

Cerinţe

Să se elimine o secvenţă de cifre din numărul N pentru a obţine un număr divizibil cu M de
valoare maximă.

Date de intrare

Fişierul de intrare div.in conţine pe prima linie numărul natural nenul M iar pe a doua linie
numărul natural N .

Date de ieşire

Fişierul de ieşire div.out va conţine două numere ı̂ntregi i1 şi i2 separate prin câte un spaţiu,
reprezentând indicii primei, respectiv ultimei cifre care vor fi şterse. Cifrele lui N se indexează de
la 1, de la stânga la dreapta. Dacă sunt mai multe soluţii se va scrie cea pentru care primul indice
este cel mai mic. Dacă nu trebuie eliminată nici o cifră se vor scrie două cifre de 0.

Restricţii şi precizări

a 2 & M & 30000


a N are cel mult 5 000 cifre
a prima cifră a lui N este nenulă
a o secvenţă este formată din cifre aflate pe poziţii consecutive ı̂n numărul N .

Exemple:

div.in div.out Explicaţii


2 1 10 ...
3333333333
7 00 ...
33332222
7 56 ...
3333322222
Timp maxim de executare/test: 1.0 secunde pe Windows, 0.3 secunde pe Linux
CAPITOLUL 15. ONI 2008 15.5. DIV 406

15.5.1 Indicaţii de rezolvare

prof. Nistor Moţ

Fie c1 c2 ...cn numărul dat. Se construiesc vectorii p, u, z,


pi = restul ı̂mpărţirii lui c1 c2 ...ci la M ,
ui = restul ı̂mpărţirii lui cni1 ...cn la M
i
z i = restul ı̂mpărţirii lui 10 la M
folosind relaţiile de recurenţă evidente.
Apoi se testează toate numerele obţinute prin eliminarea a 0, 1, 2, ..., n cifre:

Pentru i=0,n (i fiind numarul de cifre sterse)


pentru j=1,n-i+1 j fiind pozitia de inceput)
daca p[j-1]*z[n-i-j+1] mod M = 0 si numarul maxim
memoreaza i,j
E necesară o funcţie de comparare a două şiruri de cifre

15.5.2 Cod sursă

Listing 15.5.1: div rares.cpp


// https://infoarena.ro/job_detail/2580532?action=view-source
// Created by Eusebiu Rares on 13/03/2020.

#include <iostream>

FILE *in = fopen("div.in", "r"), *out = fopen("div.out", "w") ;

const int MV = 5e3 ;

int digits[MV + 1] ;
int putere[MV + 1] ;
int psm[MV + 1] ;
int suff[MV + 1] ;
int n ;

int main(int argc, const char * argv[])


{
int m ;
char digit ;

fscanf(in, "%d\n", &m) ;

for (digit = fgetc(in) ; digit != ’\n’ ; digit = fgetc(in))


digits[++ n] = digit - ’0’ ;

putere[0] = 1 ;

for (int i = 1 ; i <= MV ; ++ i)


{
putere[i] = putere[i - 1] * 10 ;
putere[i] %= m ;
}

for (int i = 1 ; i <= n ; ++ i)


{
psm[i] = psm[i - 1] * 10 + digits[i] ;
psm[i] %= m ;
}

if (psm[n] == 0)
{
fprintf(out, "0 0") ;
return 0 ;
}
CAPITOLUL 15. ONI 2008 15.6. TEATRU 407

for (int i = n ; i >= 1 ; -- i)


{
suff[i] = suff[i + 1] + putere[n - i] * digits[i] ;
suff[i] %= m ;
}

bool found(false) ;
int ansi(1), ansj(n) ;

for (int i = 1 ; i <= n && !found; ++ i)


{
for (int j = 1 ; j <= n - i + 1 ; ++ j)
{
int interval = (psm[j-1]*putere[n-i-j+1]+suff[j+i]) % m ;
if (interval == 0 && !(j == 1 && digits[i + j] == 0))
{
found = true ;
if (ansi == 1 && ansj == n)
{
ansi = j ;
ansj = j + i - 1 ;
}
else
{
for (int idx1 = 1, idx2 = 1 ; idx1 <= n && idx2 <= n ;)
{
while (ansi <= idx1 && idx1 <= ansj)
idx1 ++ ;

while (j <= idx2 && idx2 <= j + i - 1)


idx2 ++ ;

if (digits[idx1] < digits[idx2])


{
ansi = j ;
ansj = j + i - 1 ;
break ;
}

idx1 ++ ;
idx2 ++ ;
}
}
}
}
}

fprintf(out, "%d %d", ansi, ansj) ;

15.5.3 *Rezolvare detaliată

15.6 teatru
Problema 6 - teatru 100 de puncte
Alina este mare iubitoare de teatru. Directorul teatrului i-a oferit şansa să joace ı̂n mai multe
spectacole, ca figurant, deocamdată. Costumiera de scenă a decis să-i dea C costume diferite
dintre cele care sunt destinate acestei stagiuni. Alina va duce costumele acasă şi le va ajusta ca
să-i vină bine. Stagiunea durează N zile consecutive şi ı̂n fiecare zi se joacă câte o piesă. Aceeaşi
piesă se va juca, desigur ı̂n una sau mai mai multe zile ale stagiunii. Fiecărei piese i se asociază un
unic costum de figurant, deci pentru fiecare piesă ı̂n care joacă, Alina trebuie să ı̂mbrace un singur
costum, acela asociat piesei respective. Costumele de figuranţi sunt identificate prin literele mari
ale alfabetului englez: A, B, C, ..., X, Y, Z. Alina are voie să-şi aleagă cele C costume diferite.

Cerinţe
CAPITOLUL 15. ONI 2008 15.6. TEATRU 408

Cunoscând costumul asociat fiecărei zile a stagiunii, ajutaţi-o pe Alina să-şi aleagă cele C
costume diferite, ı̂n aşa fel ı̂ncât să poată juca ı̂ntr-un număr cât mai mare de piese consecutive.

Date de intrare

Fişierul de intrare teatru.in conţine pe prima linie două numere naturale Z şi C despărţite
printr-un spaţiu. Z este numărul de zile din stagiune iar C este numărul de costume diferite pe
care Alina le poate primi. Pe linia a doua, se găsesc Z caractere, litere mari ale alfabetului englez.
Caracterul al i-lea identifică costumul de figurant care trebuie ı̂mbrăcat ı̂n spectacolul din ziua i.

Date de ieşire

În fişierul de ieşire teatru.out se va scrie pe prima linie un număr natural N , reprezentând
numărul maxim de spectacole consecutive ı̂n care Alina poate juca. Pe linia a doua se scriu N
caractere, fără spaţii ı̂ntre ele, corespunzătoare costumelor care vor fi ı̂mbrăcate ı̂n cele N piese
de teatru alese, ı̂n ordinea spectacolelor ı̂n care va juca. Dacă există mai multe soluţii de lungime
N atunci se afişează cea căreia ı̂i corespunde o zi de ı̂nceput mai aproape de ı̂nceputul stagiunii.

Restricţii şi precizări

a 1 & Z & 55 000


a 1 & C & 26
a C&Z
a Pentru 20% dintre teste Z & 350

Exemple:

teatru.in teatru.out Explicaţii


10 3 5 ...
XKUFKFEGXG KUFKF
15 4 6 ...
IAJRAMRCZJJCDNS AJRAMR
25 5 8 ...
LDSDGIFAURLPTZLDLPNLEGFRN LPTZLDLP
Timp maxim de executare/test: 0.2 secunde Windows, 0.1 secunde Linux

15.6.1 Indicaţii de rezolvare

prof. Constantin Gălăţan

Problema admite o soluţie ı̂n timp liniar. Se păstrează poziţiile de ı̂nceput şi de sfârşit p şi u
ale secvenţei curente. Iniţial, u avansează de la poziţia 1 până când numărul de caractere diferite
din mulţime depăşeşte C. Urmează incrementarea lui p cu numărul de poziţii necesar pentru a
readuce mulţimea la cardinalul C. Se incrementează din nou u atâta timp numărul de caractere
distincte ale secvenţei nu este mai mare dacât C. Procesul continuă ı̂n felul acesta, până când se
parcurg toate elementele şirului. Inserarea şi extragerea elementelor din secvenţă se face ı̂n timp
constant cu ajutorul unui şir al frecvenţelor.
2 3
O soluţie de complexitate O N  va primi 40 de puncte, iar una O N  20 puncte.

15.6.2 Cod sursă

Listing 15.6.1: teatru.cpp


#include <stdio.h>

char s[55001];
long imax, jmax, Lmax, n, k;
long f[91];

void Citeste();
void Afis();
CAPITOLUL 15. ONI 2008 15.6. TEATRU 409

void Calculeaza();

int main()
{
freopen("teatru.in", "r", stdin);
freopen("teatru.out", "w", stdout);
Citeste();
Calculeaza();
Afis();
return 0;
}

void Citeste()
{
scanf("%d%d", &n, &k);
scanf("%c", &s[0]); // citeste ’\n’
for (long int i = 0; i < n; i++ )
scanf("%c", s + i);
}

void Afis()
{
printf("%d\n", Lmax);
for (long int i = imax; i <= jmax; i++ )
printf("%c", s[i]);
printf("\n");
}

void Calculeaza()
{
long int i = 0, j = 0, nr = 0;
f[s[0]]++; nr++;
do
{
while (nr <= k && j < n)
{
j++;
if ( j >= n ) break;
if (f[s[j]] == 0)
{
f[s[j]]++;
nr++;
}
else f[s[j]]++;
}

if (j - i > Lmax)
{
Lmax = j - i;
imax = i; jmax = j - 1;
}

while ( nr > k )
{
f[s[i]]--;
if ( f[s[i]] == 0 ) nr--;
i++;
}

} while (i < n && j < n);


}

Listing 15.6.2: teatru n2.cpp


#include <stdio.h>
#include <string.h>

#define MAX_N 55005


#define FIN "teatru.in"
#define FOUT "teatru.out"

int N, C, bst_len, start;


char S[MAX_N], cnt[26];

int main(void)
CAPITOLUL 15. ONI 2008 15.6. TEATRU 410

{
int i, j, k;

freopen(FIN, "r", stdin);


freopen(FOUT, "w", stdout);

scanf("%d %d %s", &N, &C, S);

for (i = 0; i < N; ++i)


{
memset(cnt, 0, sizeof(cnt));
for (j = i, k = 0; j < N && k <= C; ++j)
{
if (!cnt[S[j]-’A’]) { ++k; cnt[S[j]-’A’] = 1; }
if (k == C && bst_len < j-i+1)
{
bst_len = j-i+1;
start = i;
}
}
}

printf("%d\n", bst_len);
for (i = start; i < start+bst_len; ++i)
printf("%c", S[i]);
printf("\n");

return 0;
}

Listing 15.6.3: teatru n3.cpp


#include <stdio.h>

char s[55001];
long imax, jmax, Lmax, n, k;
long f[91];

void Citeste();
void Afis();
void Calculeaza();

int main()
{
freopen("teatru.in", "r", stdin);
freopen("teatru.out", "w", stdout);
Citeste();
Calculeaza();
Afis();
return 0;
}

void Citeste()
{
scanf("%d%d", &n, &k);
scanf("%c", &s[0]); // citeste ’\n’
for (long int i = 0; i < n; i++ )
scanf("%c", s + i);
}

void Afis()
{
printf("%d\n", Lmax);
for (long int i = imax; i <= jmax; i++ )
printf("%c", s[i]);
printf("\n");
}

void Calculeaza()
{
long int i = 0, j = 0, nr = 0, p;

for ( i = 0; i < n; i++ )


for ( j = i; j < n; j++ )
{
CAPITOLUL 15. ONI 2008 15.6. TEATRU 411

for ( p = ’A’; p <= ’Z’; p++ )


f[p] = 0;

for ( p = i; p <= j; p++ )


f[s[p]] = 1;

nr = 0;
for ( p = ’A’; p <= ’Z’; p++ )
if ( f[p] ) nr++;

if ( nr == k && Lmax < j - i + 1)


{
Lmax = j - i + 1;
imax = i; jmax = j;
}
}
}

15.6.3 *Rezolvare detaliată


Capitolul 16

OJI 2007

16.1 Cartele

În sediul unei firme se intră doar cu ajutorul cartelelor magnetice. De câte ori se schimbă
codurile de acces, cartelele trebuie formatate. Formatarea presupune imprimarea unui model
prin magnetizare. Dispozitivul ı̂n care se introduc cartelele, numit cititor de cartele, verifică acest
model. Toate cartelele au aceleaşi dimensiuni, suprafaţa pătrată şi grosimea neglijabilă. Cele două
feţe plane ale unei cartele se ı̂mpart fiecare ı̂n N  N celule pătrate, identice ca dimensiuni. Prin
formatare unele celule, marcate cu negru ı̂n exemplu, se magnetizează permiţând radiaţiei infraroşii
să treacă dintr-o parte ı̂n cealaltă a cartelei. În interiorul cititorului de cartele se iluminează
uniform una dintre feţele cartelei. De cealaltă parte fasciculele de lumină care străbat cartela sunt
analizate electronic. Pentru a permite accesul ı̂n clădire modelul imprimat pe cartelă trebuie să
coincidă exact cu modelul şablonului care memorează codul de intrare. Prin fanta dispozitivului
nu se pot introduce mai multe cartele deodată. Cartela se poate introduce prin fantă cu oricare
dintre muchii spre deschizătura fantei şi cu oricare dintre cele două feţe orientate către şablon.
După introducere cartela se dispune ı̂n plan paralel cu şablonul, lipit de acesta, astfel ı̂ncât cele
patru colţuri ale cartelei se suprapun exact cu colţurile şablonului. Modelele imprimate pe cele
două feţe ale unei cartele sunt identice. Unei celule magnetizate ı̂i corespunde pe faţa opusă tot
o celulă magnetizată, iar unei celule nemagnetizate ı̂i corespunde una nemagnetizată. O celulă
magnetizată este transparentă pentru radiaţia infraroşie indiferent de faţa care se iluminează.
Un angajat al firmei are mai multe cartele. Pe unele dintre acestea a fost imprimat noul cod
de intrare, iar pe altele sunt coduri mai vechi. Pentru a afla care sunt cartelele care-i permit
accesul ı̂n sediul firmei angajatul este nevoit să le verifice pe toate, introducn̂du-le pe rând, ı̂n
toate modurile pe care le consideră necesare, ı̂n fanta cititorului de cartele.

Sablon Cartela 1 Cartela 2

Figura 16.1: Cartele1

Cerinţă
Scrieţi un program care determină care dintre cartele permite accesul ı̂n sediul firmei.
Date de intrare
Fişierul de intrare cartele.in conţine pe prima linie două numere naturale N şi C despărţite
printr-un spaţiu. N este dimensiunea tablourilor care reprezintă modelul şablon şi modelele
cartelelelor. C reprezintă numărul de cartele. Urmează C  1 blocuri de câte N linii fiecare.
Primul bloc de N linii codifică şablonul. Următoarele C blocuri de câte N linii codifică fiecare
câte o cartelă. Pe fiecare linie sunt câte N valori ı̂ntregi, despărţite printr-un singur spaţiu.
Celulelor magnetizate le corespunde valoarea 1, iar celorlalte, valoarea 0.

412
CAPITOLUL 16. OJI 2007 16.1. CARTELE 413

Date de ieşire
În fişierul de ieşire cartele.out se vor scrie C linii, câte o valoare pe linie. Pe linia i se va scrie
valoarea 1 dacă cartela i permite accesul ı̂n clădire şi valoarea 0 ı̂n caz contrar.
Restricţii şi precizări
1 $ N, C & 50
Exemplu
cartele.in cartele.out Explicaţii
32 1 Datele de intrare corespund situaţiei din figură.
010 0 Cartela 1 se potriveşte perfect şablonului, dacă
001 se roteşte ı̂n sens trigonometric cu 90 de grade.
100 Cartela 2 nu se potriveşte şablonului, indiferent
100 de modul ı̂n care se introduce ı̂n fantă.
001
010
001
001
010
Timp maxim de execuţie/test: 1 secundă

16.1.1 Indicaţii de rezolvare

Pentru fiecare cartelă, se compară element cu element, matricea care reprezintă sablonul, cu
următoarele tablouri:
1. Cartela
2. Cartela rotită cu 90 grade
3. Cartela rotită cu 180 grade
4. Cartela rotită cu 270 grade
Dacă nu s-a gasit o coincidenţă, se ı̂ntoarce cartela, printr-o operaţie de oglindire faţă de linia
i n©2, (sau faţă de coloana j n©2), după care se compară şablonul cu următoarele tablouri:
5. Cartela oglindită
6. Cartela oglindită rotită cu 90 grade
7. Cartela oglindită rotită cu 180 grade
8. Cartela oglindită rotită cu 270 grade
Rotirile se pot face ı̂n sens trigonometric sau orar.
Dacă s-a găsit o coincidenţă la oricare dintre paşii de mai sus, se opreşte căutarea, se afisează
1 şi se trece la prelucrarea următoarei cartele.
Dacă nici după pasul 8 nu s-a gasit o potrivire exactă, se afişează 0 şi se trece la prelucrarea
următoarei cartele.

16.1.2 Cod sursă

Listing 16.1.1: carteleC.cpp


#include <fstream>
#include <iomanip>

using namespace std;

#define DIM 51

ifstream fin("cartele.in");
ofstream fout("cartele.out");

int a[DIM][DIM], b[DIM][DIM]; // sablonul, cartela


int aux[DIM][DIM];
int n, C;

int Egale();
void Inverseaza(); // intoarce cartela pe partea cealalta
// (oglindire fata de linia i = n/2)
CAPITOLUL 16. OJI 2007 16.1. CARTELE 414

void Roteste(); // rotire 90 grade in sens trigonometric


void Rezolva();

int main()
{
Rezolva();
fin.close();
fout.close();
return 0;
}

void Rezolva()
{
fin >> n >> C;
int i, j, r;
int identice = 1;

for (i = 1; i <= n; i++)


for (j = 1; j <= n; j++)
fin >> a[i][j];

for (int k = 1; k <= C; k++)


{
identice = 1;
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
{
fin >> b[i][j];
if ( b[i][j] != a[i][j] ) identice = 0;
}

for (int f = 1; f <= 2 && !identice; f++) // pentru fata 1 si 2


{
for (r = 1; r <= 4 && !identice; r++) // la a patra rotatie
// se revine la matricea initiala
{
Roteste(); // cu 90 in sens trigonometric
if ( Egale() ) identice = 1;
}

if ( !identice ) Inverseaza();
}

if ( identice )
fout << 1 << ’\n’;
else
fout << 0 << ’\n’;
}
}

int Egale()
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if ( a[i][j] != b[i][j] )
return 0;
return 1;
}

void Inverseaza()
{
int i, j, temp;
for (i = 1; i <= n / 2; i++)
for (j = 1; j <= n; j++)
temp = b[i][j], b[i][j] = b[n-i+1][j], b[n-i+1][j] = temp;
}

void Roteste()
{
int i, j;
for (i = 1; i <= n; i++)
for (j = 1; j<= n; j++)
aux[n-j+1][i] = b[i][j];

for (i = 1; i <= n; i++)


for (j = 1; j <= n; j++)
CAPITOLUL 16. OJI 2007 16.1. CARTELE 415

b[i][j] = aux[i][j];
}

16.1.3 Rezolvare detaliată

(j-1) j direct 90 i (i-1) 180 270


(i-1) (j-1)
i j i j
(i-1) (j-1)
j (j-1) (i-1) i
invers j (j-1) 90 180 (i-1) i 270
(i-1) (j-1)
i j i j
(j-1) (i-1)
i (i-1) (j-1) j

Figura 16.2: Cartele2

Varianta 1:

Listing 16.1.2: cartele1.java


1
2 import java.io.*;
3 class cartele
4 {
5 static final int DIM=51;
6
7 static StreamTokenizer st;
8 static PrintWriter out;
9
10 static int[][] a=new int[DIM][DIM]; // sablonul
11 static int[][] b=new int[DIM][DIM]; // cartela
12 static int[][] aux=new int[DIM][DIM];// auxiliar
13
14 static int n, c;
15
16 public static void main(String[] args) throws IOException
17 {
18 st=new StreamTokenizer(new BufferedReader(new FileReader("cartele.in")));
19 out=new PrintWriter(new BufferedWriter(new FileWriter("cartele.out")));
20
21 rezolva();
22 out.close();
23 }// main(...)
24
25 static void rezolva() throws IOException
26 {
27 int i, j, k, r;
28 boolean identice;
29
30 st.nextToken(); n=(int)st.nval;
31 st.nextToken(); c=(int)st.nval;
32
33 for(i=1;i<=n;i++)
34 for(j=1;j<=n;j++)
35 {
36 st.nextToken(); a[i][j]=(int)st.nval;
37 }
38
39 for(k=1;k<=c;k++)
40 {
41 identice=true;
42 for(i=1;i<=n;i++)
43 for(j=1;j<=n;j++)
CAPITOLUL 16. OJI 2007 16.1. CARTELE 416

44 {
45 st.nextToken(); b[i][j]=(int)st.nval;
46 if(b[i][j]!=a[i][j]) identice=false;
47 }
48
49 for(int f=1;f<=2&&!identice;f++) // pentru fata 1 si 2
50 {
51 for(r=1;r<=4&&!identice;r++) // la a patra rotatie se revine la matricea
initiala
52 {
53 roteste(); // cu 90 in sens trigonometric
54 if(egale()) identice=true;
55 }
56 if(!identice) inverseaza();
57 }
58 if(identice) out.println(1); else out.println(0);
59 }// for k
60 }// rezolva(...)
61
62 static boolean egale()
63 {
64 for(int i=1;i<=n;i++)
65 for(int j=1; j<=n; j++)
66 if(a[i][j]!=b[i][j] ) return false;
67 return true;
68 }// egale(...)
69
70 static void inverseaza()
71 {
72 int i, j, temp;
73 for(i=1;i<=n/2;i++)
74 for(j=1;j<=n;j++) { temp=b[i][j]; b[i][j]=b[n-i+1][j]; b[n-i+1][j]=temp; }
75 } // inverseaza(...)
76
77 static void roteste()
78 {
79 int i, j;
80 for(i=1;i<=n;i++)
81 for(j=1;j<=n;j++) aux[n-j+1][i]=b[i][j];
82
83 for(i=1;i<=n;i++)
84 for(j=1;j<=n;j++) b[i][j]=aux[i][j];
85 }// roteste(...)
86 }// class

Varianta 2:

Listing 16.1.3: cartele2.java


1 import java.io.*;
2 class cartele
3 {
4 static final int DIM=51;
5
6 static StreamTokenizer st;
7 static PrintWriter out;
8
9 static int[][] a=new int[DIM][DIM]; // sablonul
10 static int[][] b=new int[DIM][DIM]; // cartela
11
12 static int n, c;
13
14 public static void main(String[] args) throws IOException
15 {
16 int i, j, k;
17 boolean ok;
18
19 st=new StreamTokenizer(new BufferedReader(new FileReader("cartele.in")));
20 out=new PrintWriter(new BufferedWriter(new FileWriter("cartele.out")));
21
22 st.nextToken(); n=(int)st.nval;
23 st.nextToken(); c=(int)st.nval;
24
25 for(i=1;i<=n;i++) // citesc sablonul
26 for(j=1;j<=n;j++)
27 {
CAPITOLUL 16. OJI 2007 16.1. CARTELE 417

28 st.nextToken(); a[i][j]=(int)st.nval;
29 }
30
31 for(k=1;k<=c;k++)
32 {
33 for(i=1;i<=n;i++) // citesc cartela k
34 for(j=1;j<=n;j++)
35 {
36 st.nextToken(); b[i][j]=(int)st.nval;
37 }
38
39 ok=true;
40 for(i=1;i<=n&&ok;i++) // direct
41 for(j=1;j<=n&&ok;j++)
42 if(a[i][j]!=b[i][j]) ok=false;
43 if(ok) {out.println(1); continue;} // cu alta cartela
44
45 ok=true;
46 for(i=1;i<=n&&ok;i++) // rotit cu 90 (ceas!)
47 for(j=1;j<=n&&ok;j++)
48 if(a[i][j]!=b[j][n-(i-1)]) ok=false;
49 if(ok) {out.println(1); continue;} // cu alta cartela
50
51 ok=true;
52 for(i=1;i<=n&&ok;i++) // rotit cu 180 (ceas!)
53 for(j=1;j<=n&&ok;j++)
54 if(a[i][j]!=b[n-(i-1)][n-(j-1)]) ok=false;
55 if(ok) {out.println(1); continue;} // cu alta cartela
56
57 ok=true;
58 for(i=1;i<=n&&ok;i++) // rotit cu 270 (ceas!) <==> 90 trig
59 for(j=1;j<=n&&ok;j++)
60 if(a[i][j]!=b[n-(j-1)][i]) ok=false;
61 if(ok) {out.println(1); continue;} // cu alta cartela
62
63 ok=true;
64 for(i=1;i<=n&&ok;i++) // invers + direct
65 for(j=1;j<=n&&ok;j++)
66 if(a[i][j]!=b[i][n-(j-1)]) ok=false;
67 if(ok) {out.println(1); continue;} // cu alta cartela
68
69 ok=true;
70 for(i=1;i<=n&&ok;i++) // invers + rotit 90 (ceas!)
71 for(j=1;j<=n&&ok;j++)
72 if(a[i][j]!=b[n-(j-1)][n-(i-1)]) ok=false;
73 if(ok) {out.println(1); continue;} // cu alta cartela
74
75 ok=true;
76 for(i=1;i<=n&&ok;i++) // invers + rotit 180 (ceas!)
77 for(j=1;j<=n&&ok;j++)
78 if(a[i][j]!=b[n-(i-1)][j]) ok=false;
79 if(ok) {out.println(1); continue;} // cu alta cartela
80
81 ok=true;
82 for(i=1;i<=n&&ok;i++) // invers + rotit cu 270 (ceas!) <==> 90 trig
83 for(j=1;j<=n&&ok;j++)
84 if(a[i][j]!=b[j][i]) ok=false;
85 if(ok) {out.println(1); continue;} // cu alta cartela
86
87 out.println(0); // nu s-a potrivit
88 } // for k
89 out.close();
90 }// main
91 }// class

Varianta 3:

Listing 16.1.4: cartele3.java


1 import java.io.*;
2 class cartele
3 {
4 static final int DIM=51;
5
6 static StreamTokenizer st;
7 static PrintWriter out;
CAPITOLUL 16. OJI 2007 16.2. PARITATE 418

8
9 static int[][] a=new int[DIM][DIM]; // sablonul
10 static int[][] b=new int[DIM][DIM]; // cartela
11
12 static int n, c;
13
14 public static void main(String[] args) throws IOException
15 {
16 int i, j, k;
17
18 st=new StreamTokenizer(new BufferedReader(new FileReader("1.in")));
19 out=new PrintWriter(new BufferedWriter(new FileWriter("cartele.out")));
20
21 st.nextToken(); n=(int)st.nval;
22 st.nextToken(); c=(int)st.nval;
23
24 for(i=1;i<=n;i++) // citesc sablonul
25 for(j=1;j<=n;j++)
26 {
27 st.nextToken(); a[i][j]=(int)st.nval;
28 }
29
30 for(k=1;k<=c;k++)
31 {
32 for(i=1;i<=n;i++) // citesc cartela k
33 for(j=1;j<=n;j++)
34 {
35 st.nextToken(); b[i][j]=(int)st.nval;
36 }
37
38 if(egale(1,0,0,0, 0,1,0,0)) {out.println(1); continue;} // direct
39 if(egale(0,1,0,0, 0,0,1,0)) {out.println(1); continue;} // rotit cu 90 (ceas!)
40 if(egale(0,0,1,0, 0,0,0,1)) {out.println(1); continue;} // rotit cu 180 (ceas!)
41 if(egale(0,0,0,1, 1,0,0,0)) {out.println(1); continue;} // rotit cu 270 (ceas!)
42 if(egale(1,0,0,0, 0,0,0,1)) {out.println(1); continue;} // invers + direct
43 if(egale(0,0,0,1, 0,0,1,0)) {out.println(1); continue;} // invers + rotit 90 (ceas
!)
44 if(egale(0,0,1,0, 0,1,0,0)) {out.println(1); continue;} // invers + rotit 180 (
ceas!)
45 if(egale(0,1,0,0, 1,0,0,0)) {out.println(1); continue;} // invers + rotit cu 270 (
ceas!)
46
47 out.println(0); // nu s-a potrivit
48 } // for k
49 out.close();
50 }// main
51
52 static boolean egale(int i1,int j1, int ni1, int nj1, int i2, int j2, int ni2, int nj2
)
53 {
54 int i,j;
55 boolean ok=true;
56 for(i=1;i<=n&&ok;i++)
57 for(j=1;j<=n&&ok;j++)
58 if(a[i][j]
59 !=
60 b[i*i1+j*j1+(n-i+1)*ni1+(n-j+1)*nj1][i*i2+j*j2+(n-i+1)*ni2+(n-j+1)*nj2])
61 ok=false;
62 return ok;
63 }// egale(...)
64 }// class

16.2 Paritate

În vederea asigurării unei transmiteri cât mai exacte a informaţiilor pe reţea, transmiterea se
efectuează caracter cu caracter, fiecare caracter fiind dat prin codul său ASCII, adică o grupă de
8 biţi (octet). Pentru fiecare 8 biţi transmişi se calculează un bit de paritate care are valoarea 0
(dacă codul ASCII al caracterului conţine un număr par de cifre binare 1) sau 1 (ı̂n caz contrar).
Deoarece ı̂n problema noastră se transmit numai caractere ASCII standard, cu codul ASCII
din intervalul [32,127], codul lor ASCII are bitul 7 (primul bit din stn̂ga) egal cu 0. Pe această
poziţie va fi pus bitul de paritate, economisind astfel câte un bit pentru fiecare caracter transmis.
CAPITOLUL 16. OJI 2007 16.2. PARITATE 419

De exemplu, dacă mesajul care trebuie trasmis conţine caracterele ”Paritate”, succesiunea de biţi
transmisă va fi:

01010000 11100001 01110010 01101001 01110100 11100001 01110100 01100101

În plus, pe lângă caracterele amintite, ı̂n mesaj mai poate să apară un caracter special care
indică trecerea la ı̂nceputul unui nou rând. Acest caracter are codul ASCII 10.
Cerinţă
Să se scrie un program care să verifice dacă un text a fost sau nu transmis corect.
Date de intrare
Fişierul de intrare paritate.in are pe prima linie o succesiune de caractere ’0’ şi ’1’ care
reprezintă mesajul transmis. Între caractere nu există spaţii. Linia se termină cu caracterul
marcaj de sfârşit de linie (newline).
Date de ieşire
Fişierul de ieşire paritate.out are pe prima linie mesajul DA dacă textul a fost transmis corect
sau NU ı̂n caz contrar. În cazul ı̂n care mesajul de pe prima linie este DA liniile următoare vor
conţine textul transmis ı̂n clar. În cazul ı̂n care mesajul de pe prima linie este NU linia următoare
va conţine numerele de ordine ale caracterelor care nu au fost transmise corect, ı̂n ordine strict
crescătoare, separate prin câte un spaţiu.
Restricţii şi precizări
a Cei 8 biţi ai codului ASCII a unui caracter se numerotează de la 0 la 7, de la dreapta la
stânga, cel mai din stânga bit fiind bitul 7 iar cel mai din dreapta bitul 0.
a Textul transmis are cel mult 60000 caractere.
a Numărul de caractere ’0’ şi ’1’ din prima linie a fişierului de intrare este multiplu de 8.
a Codurile ASCII ale caracterelor din text aparţin mulţimii r10, 32  127x, codul 10 ı̂nsemnând
trecerea la ı̂nceputul unui rând nou.
a Nici o linie din fişierul de iec sire nu va avea mai mult de 255 caractere.
a Caracterele din text sunt numerotate ı̂ncepând de la 0.
a mesajele DA/NU din prima linie a fişierului de ieşire se scriu cu majuscule.

Exemplul 1:
paritate.in
0101000011100001011100100110100101110100111000010111010001100101
paritate.out Explicaţie
DA Toate codurile sunt
Paritate
Exemplul 2:
paritate.in
1101000011100001111100100110100101110100111000010111010011100101
paritate.out Explicaţie
NU Primul caracter a fost transmis ca succesiunea de biţi 11010000
027 ceea ce ı̂nseamnă că fără bitul de paritate ar fi trebuit să existe
un număr impar de cifre 1, ceea ce este fals. Deci caracterul nu
a fosttransmis corect. Acelaşi lucru se verifică şi pentru
caracterele cu numerele de ordine 2 şi 7.
Exemplul 3:
paritate.in
010000011111101001101001000010100110010100001010011010100110111101101001
paritate.out Explicaţie
DA Toate codurile sunt corecte.
Azi În text există două caractere cu cod ASCII 10
e
joi
Timp maxim de execuţie/test: 1 secundă
CAPITOLUL 16. OJI 2007 16.2. PARITATE 420

16.2.1 Indicaţii de rezolvare

Se utilizează un tablou de caractere care va conţine:


- caracterul corect transmis sau
- caracterul #0 ı̂n cazul ı̂n care transmisia nu s-a efectual corect
În acelaşi timp variabila Eroare va conţine ultima poziţie a unui cod eronat sau 0 dacă nu sunt
erori la transmiterea mesajului.
Deoarece suntem asiguraţi de faptul că numărul de biţi 0/1 transmişi este multiplu de 8, nu
mai fac această verificare şi tratez fiecare grupă de 8 biţi astfel:
- citesc primul caracter separat (este bitul de paritate)
- ı̂l transform ı̂n cifra 0/1
- citesc pe rând ceilalţi 7 biţi si formez codul ASCII corect numărând ı̂n acelaşi timp biţii egali
cu 1
- dacă bitul de paritate este corect (adică am un număr par de cifre 1) pun pe poziţia core-
spunzătoare din tablou caracterul al cărui cod ı̂l am - ı̂n caz contrar pun pe poziţia respectivă
valoarea #0 şi reţin ı̂n variabila Eroare poziţia caracterului eronat
După terminarea acestui proces nu am decât să verific variabila Eroare:
- ı̂n cazul ı̂n care are valoarea 0 (transmisie fără eroare), afişez ’DA’ ı̂n prima linie a fişierului
de ieşire, apoi parcurg vectorul caracter cu caracter şi ı̂l scriu ı̂n fişierul de ieşire, având grijă ca
ı̂n cazul ı̂ntâlnirii caracterului #10 (cod de linie nouă) să trec la o nouă linie
- ı̂n cazul ı̂n care are o valoare ¿0 (transmisie cu erori) afisez ’NU’ ı̂n prima linie a fişierului de
ieşire, apoi parcurg vectorul caracter cu caracter şi, ı̂n cazul ı̂ntâlnirii valorii #0 (caracter eronat)
afişez indicele respectiv.

16.2.2 Cod sursă

Listing 16.2.1: PARITATE.C


#include <stdio.h>

#define MAX 60000

char a[MAX]; //0: eroare; Caracter: corect


char c;
long i, j;
int BitP, Cod, Bit, Nr1;
long Eroare; //va contine ultima pozitie
//a unui cod eronat sau 0 daca nu sunt erori
FILE *f, *g;

int main()
{
f=fopen("paritate.in", "rt");
g=fopen("paritate.out", "wt");

i=-1; Eroare=0; c=0;


fscanf(f, "%c", &c);

while (c!=’\n’)
{
i++; //pozitie caracter
BitP=c-’0’; //bitul de paritate
Cod=0; //aici formez codul
Nr1=0; //cati de 1

for (j=1; j<=7; j++) //citesc ceilalti 7 biti


{
fscanf(f, "%c", &c); //citesc bit
Bit=c-’0’;
if (Bit==1) Nr1++; //daca e 1 il numar
Cod=Cod*2+Bit; //formez codul
}

if ((Nr1+BitP)%2==0) //daca cod corect


a[i]=Cod; //pun caracterul in vector
else //altfel
{
CAPITOLUL 16. OJI 2007 16.2. PARITATE 421

a[i]=1; //pun 1
Eroare=i; //si retin pozitia
}
fscanf(f, "%c", &c);
}

if (Eroare==0) //daca nu sunt erori


{ //scrie DA si
fprintf(g, "DA\n");
for (j=0; j<=i; j++) //afiseaza cele i+1 caractere
if (a[j]==10) //avand grija la caracterul cu codul 10
fprintf(g, "\n");
else //altfel
fprintf(g, "%c", a[j]); //scrie caracterul
// fprintf(g, "\n");
}
else //eroare!!!
{
fprintf(g, "NU\n"); //scrie NU si
for (j=0; j<Eroare; j++)
if (a[j]==1) //cauta erorile - cod 01
fprintf(g, "%ld ", j); //si afiseaza pozitia lor
fprintf(g, "%ld\n", Eroare); //afiseaza pozitia ultimei erori
}

fclose(g);
return 0;
}

16.2.3 Rezolvare detaliată

Varianta 1: Versiunea este o prelucrare a variantei oficiale; comentariile din sursa au rămas
nemodificate pentru că sunt un exemplu bun!

Listing 16.2.2: Paritate.java


1 import java.io.*;
2 class paritate
3 {
4 static final int MAX=60000;
5 static int[] a=new int[MAX]; // 0=eroare
6
7 public static void main(String[] args) throws IOException
8 {
9 int c;
10 int i,j,k;
11 int BitP, Cod, Bit, Nr1;
12 int Eroare; // ultima pozitie a unui cod eronat sau 0 daca nu sunt erori
13
14 //BufferedReader br0=new BufferedReader(new InputStreamReader(System.in));
15 BufferedReader br=new BufferedReader(new FileReader("paritate.in"));
16 PrintWriter out=new PrintWriter(new BufferedWriter(new FileWriter("paritate.out")));
17
18 i=-1;
19 Eroare=0;
20 c=0;
21 String s=br.readLine();
22 //System.out.println("s = "+s);
23 k=0;
24 while(k<s.length()-1)
25 {
26 c=s.charAt(k);
27 //System.out.println(k+" : "+c);
28 //br0.readLine();
29 i++; // pozitie caracter
30 BitP=c-’0’; // bitul de paritate
31 Cod=0; // aici formez codul
32 Nr1=0; // cati de 1
33 for(j=1;j<=7;j++) // citesc ceilalti 7 biti
34 {
35 c=s.charAt(++k); // citesc bit
36 //System.out.println(k+" : "+c);
37 //br0.readLine();
CAPITOLUL 16. OJI 2007 16.2. PARITATE 422

38 Bit=c-’0’;
39 if(Bit==1) Nr1++; // daca e 1 il numar
40 Cod=Cod*2+Bit; // formez codul
41 }
42
43 if((Nr1+BitP)%2==0) // daca cod corect
44 a[i]=Cod; // pun caracterul in vector
45 else // altfel
46 {
47 a[i]=1; // pun 1
48 Eroare=i; // si retin pozitia
49 }
50 ++k;
51 }// while
52
53 if(Eroare==0) // daca nu sunt erori
54 {
55 out.println("DA"); // scrie DA si
56 for(j=0;j<=i;j++) // afiseaza cele i+1 caractere
57 if(a[j]==10) // avand grija la caracterul cu codul 10
58 out.println();
59 else // altfel
60 out.print((char)a[j]); // scrie caracterul
61 }
62 else // eroare!!!
63 {
64 out.println("NU"); // scrie NU si
65 for(j=0;j<Eroare;j++)
66 if(a[j]==1) // cauta erorile - cod 01
67 out.print(j+" "); // si afiseaza pozitia lor
68 out.println(Eroare); // afiseaza pozitia ultimei erori
69 }
70
71 out.close();
72 }// main
73 }// class
Capitolul 17

OJI 2006

17.1 Flori
Cristina Bohm
Fetiţele din grupa mare de la grădiniţă culeg flori şi vor să ı̂mpletească coroniţe pentru festivi-
tatea de premiere. În grădină sunt mai multe tipuri de flori. Fiecare dintre cele n fetiţe culege un
buchet având acelaşi număr de flori, ı̂nsă nu neapărat de acelaşi tip. Pentru a ı̂mpleti coroniţele
fetiţele se ı̂mpart ı̂n grupe. O fetiţă se poate ataşa unui grup numai dacă are cel puţin o floare de
acelaşi tip cu cel puţin o altă fetiţă din grupul respectiv.
Cerinţă
Fiind dat un număr natural n reprezentând numărul fetiţelor şi numărul natural k reprezentând
numărul de flori dintr-un buchet, să se determine grupele care se formează.
Date de intrare
Fişierul de intrare flori.in conţine pe prima linie, separate printr-un spaţiu, numerele naturale
n şi k, reprezentând numărul de fetiţe şi respectiv numărul de flori din fiecare buchet. Fiecare
dintre următoarele n linii conţine, pentru fiecare fetiţă, câte k valori separate prin câte un spaţiu
reprezentând tipurile de flori culese.
Date de ieşire
Fişierul de ieşire flori.out va conţine pe fiecare linie câte o grupă formată din numerele de
ordine ale fetiţelor separate prin câte un spaţiu, ı̂n ordine crescătoare, ca ı̂n exemplu.
Restricţii şi precizări
a 1 & n & 150
a 1 & k & 100
a Tipul unei flori este un număr ı̂ntreg din intervalul 0, 100.
a Într-o grupă numerele de ordine ale fetiţelor trebuie date ı̂n ordine strict crescătoare.
a În fişierul de ieşire grupele vor fi afişate ı̂n ordinea crescătoare a numărului de ordine al
primei fetiţe din grupă.
Exemplu
flori.in flori.out Explicaţie
54 134 Fetiţele 1 şi 3 au cules amândouă flori de tipul 1,
1234 2 iar fetiţele 1 şi 4 au cules amândouă flori de tipurile
5696 5 2,3 şi 4, deci toate cele trei fetiţe (1, 3, 4) se vor afla
1111 ı̂n aceeaşi grupă. Fetiţele 2 şi 5 vor forma fiecare câte
2443 o grupă deoarece nu au cules flori de acelaşi tip cu
7777 nici una dintre celelalte fetiţe.
Timp de rulare/test: 1 secundă

17.1.1 Indicaţii de rezolvare

Soluţia comisiei

- citesc n - numărul de fetiţe şi k - numărul de flori dintr-un buchet

423
CAPITOLUL 17. OJI 2006 17.1. FLORI 424

- construiesc matricea a definita astfel : pe linia i sunt tipurile distincte de flori ale fetiţei cu
numărul de ordine i
- a[i][0] = numărul de elemente de pe linia i; acesta va deveni 0 dacă linia a fost reunită
ı̂n altă linie

- vectorul viz are n elemente şi pe parcursul prelucrării , fetiţele care ajung ı̂n aceeaşi grupă
vor avea aceeaşi valoare ı̂n vectorul viz: de exemplu, dacă fetiţa 3 ajunge ı̂n grupa ı̂n care
e fetiţa 1 atunci viz[3]=viz[1];
- inţial viz[i]=i ı̂nsemnând că fiecare fetiţă e ı̂n grupă doar ea cu ea;
- apelul irelj(i,j) verifică dacă i e ı̂n relaţie cu j: caută pe linia i şi j un tip de floare
comun fetiţelor i şi j
- funcţia reuneste face reuniunea mulţimilor de pe liniile i şi j ı̂n linia i; dacă s-a
făcut o astfel de reuniune, scad i (i  ) şi astfel se rezolvă situaţia ı̂n care de exemplu
i rel j, not ( i rel k) , j rel k; executând i--, k va ajunge tot ı̂n grupă cu
i; altfel k ar ajunge ı̂n altă grupă

- afişarea grupelor presupune selectarea din vectorul viz a poziţiilor care au aceeaşi valoare:
toate poziţiile i care au viz[i]=1 (de exemplu) sunt ı̂n prima grupă; pun pe 0 poziţiile
afişate pentru a nu le mai relua o dată.

17.1.2 Cod sursă

Listing 17.1.1: FLORI.CPP


#include<iostream>
#include<stdio.h>

using namespace std;

FILE *f=fopen("Flori.In","r");
FILE *g=fopen("Flori.Out","w");

int n,k,a[150][150];

int irelj(int i,int j) // verifica daca fata i e in relatie cu j


// (au cel putin o floare in comun)
{
int u,v;
for(u=1;u<=a[i][0];u++) //a[i][0] e numarul de elemente pe linia i
for(v=1;v<=a[j][0];v++)
if (a[i][u]==a[j][v])
return 1;
return 0;
}

int apartine(int val,int linie) //caut val in multimea de pe linia linie


{
int j,lg=a[linie][0];

for(j=1;j<=lg;j++)
if (val==a[linie][j])
return 1;
return 0;
}

void reuneste(int i,int j) //reuneste in linia i linia j


{
int u;
for(u=1;u<=a[j][0];u++)
if(!apartine(a[j][u],i))
{
a[i][0]++;
a[i][ a[i][0] ]=a[j][u];
}
}
CAPITOLUL 17. OJI 2006 17.1. FLORI 425

int main()
{
int viz[150],i,j,val,ok;
fscanf(f,"%d %d",&n,&k);

for(i=1;i<=n;i++)
for(j=1;j<=k;j++)
{
fscanf(f,"%d",&val);
if(!apartine(val,i))
{
a[i][0]++; // pe prima coloana am nr. de tipuri distincte
// de flori
a[i][ a[i][0] ]=val; // in multimea de pe linia i am tipurile
// distincte de flori al fetitei i
}
}

for(i=1;i<=n;i++)
viz[i]=i; //initial exista n grupe

for(i=1;i<=n;i++)
{
ok=0;
if(a[i][0])
{
for(j=i+1;j<=n;j++)
if(irelj(i,j))
{
viz[j]=viz[i]; //j trebuie sa ajunga in grupa cu i
reuneste(i,j); //reunesc in linia i linia j
a[j][0]=0;//consider ca in multimea j am 0 elemente acuma
ok=1;
}
}

if (ok) i--;// faptul ca am reunit in i cel putin o multime j implica


// sa continui cu aceeasi linie i
// daca as lasa i sa se incrementeze conform for-ului,
// ar gresi in sensul ca
// pt. i rel j si i nu e in rel cu k si j rel k
// ar pune j in grupa i dar k ar ajunge in alta grupa
}

for(i=1;i<=n;i++)
if(viz[i])
{
fprintf(g,"%d ",i);
for(j=i+1;j<=n;j++)
if(viz[i]==viz[j])
{
fprintf(g,"%d ",j);
viz[j]=0; //ca sa nu mai fie prelucrat
}

fprintf(g,"\n");
}

fclose(g);
return 0;
}

17.1.3 Rezolvare detaliată

Variantă iterativă:

Listing 17.1.2: flori1.java


1 import java.io.*;
2 class Flori1
3 {
4 static int n,k;
CAPITOLUL 17. OJI 2006 17.1. FLORI 426

5 static int[][] a=new int[151][101];


6 static int[] gf=new int[151];
7 static int[] fgc=new int[101];
8
9 public static void main(String[] args) throws IOException
10 {
11 int i,j,ii,ng,ok,gasit;
12 StreamTokenizer st=new StreamTokenizer(
13 new BufferedReader(new FileReader("flori.in")));
14 PrintWriter out=new PrintWriter(
15 new BufferedWriter(new FileWriter("flori.out")));
16
17 st.nextToken(); n=(int)st.nval;
18 st.nextToken(); k=(int)st.nval;
19
20 for(i=1;i<=n;i++)
21 for(j=1;j<=k;j++) { st.nextToken(); a[i][j]=(int)st.nval; }
22
23 ng=0;
24 for(i=1;i<=n;i++)
25 {
26 if(gf[i]!=0) continue;
27 ng++;
28 for(j=0;j<=100;j++) fgc[j]=0;
29 for(j=1;j<=k;j++) fgc[a[i][j]]=1;
30
31 ok=1;
32 while(ok==1)
33 {
34 ok=0;
35 for(ii=i+1;ii<=n;ii++)
36 {
37 if(gf[ii]!=0) continue;
38 gasit=0;
39 for(j=1;j<=k;j++) if(fgc[a[ii][j]]==1)
40 {
41 gasit=1;
42 break;
43 }
44
45 if(gasit==1)
46 {
47 for(j=1;j<=k;j++) fgc[a[ii][j]]=1;
48 ok=1;
49 gf[ii]=ng;
50 }
51 }//for ii
52 }//while
53
54 out.print(i+" ");
55 for(j=1;j<=n;j++) if(gf[j]==ng) out.print(j+" ");
56 out.println();
57 }//for i
58
59 out.close();
60 }// main
61 }// class

Variantă recursivă:
Listing 17.1.3: flori2.java
1 import java.io.*;
2 class Flori2
3 {
4 static int n,k,ng;
5 static char[][] a=new char[151][101];
6 static int[] gf=new int[151];
7
8 public static void main(String[] args) throws IOException
9 {
10 int i,j,fij;
11 StreamTokenizer st=new StreamTokenizer(
12 new BufferedReader(new FileReader("flori.in")));
13 PrintWriter out=new PrintWriter(
CAPITOLUL 17. OJI 2006 17.2. PLUTON 427

14 new BufferedWriter(new FileWriter("flori.out")));


15
16 st.nextToken(); n=(int)st.nval;
17 st.nextToken(); k=(int)st.nval;
18
19 for(i=1;i<=n;i++)
20 for(j=1;j<=k;j++) { st.nextToken(); fij=(int)st.nval; a[i][fij]=1;}
21
22 ng=0;
23 for(i=1;i<=n;i++)
24 {
25 if(gf[i]!=0) continue;
26 ng++;
27 fata(i);
28 }
29
30 for(i=1;i<=ng;i++)
31 {
32 for(j=1;j<=n;j++) if(gf[j]==i) out.print(j+" ");
33 out.println();
34 }
35
36 out.close();
37 }// main
38
39 static void fata(int i)
40 {
41 int j,ii;
42
43 gf[i]=ng;
44 for(j=0;j<=100;j++)
45 {
46 if(a[i][j]==0) continue;
47 for(ii=1;ii<=n;ii++)
48 {
49 if(ii==i) continue;
50 if(a[ii][j]==1)
51 if(gf[ii]==0) fata(ii);
52 }
53 }
54 }// fata(...)
55 }// class

17.2 Pluton
Marinel Şerban
În timpul acţiunii ”Furtună ı̂n deşert” din cauza unei furtuni de nisip, n soldaţi s-au rătăcit de
plutoanele lor. După trecerea furtunii se pune problema regrupării acestora pe plutoane. Pentru
aceasta se folosesc plăcuţele de identificare pe care soldaţii le poartă la gât. Pe aceste plăcuţe
sunt scrise numere care pot identifica fiecare soldat şi plutonul din care acesta face parte. Astfel,
soldaţii din acelaşi pluton au numărul de identificare format din aceleaşi cifre, dispuse ı̂n altă
ordine şi numerele de identificare sunt unice. De exemplu, numerele de identificare 78003433,
83043073, 33347008 indică faptul că cei trei soldaţi care le poartă fac parte din acelaşi pluton.
Cerinţă
Fiind date cele n numere de pe plăcuţele de identificare, să se regrupeze cei n soldaţi pe
plutoane, indicându-se numărul de plutoane găsite (un pluton refăcut trebuie să aibă minimum
un soldat), numărul de soldaţi din cel mai numeros pluton, numărul de plutoane care au acest
număr maxim de soldaţi precum şi componenţa unui astfel de pluton (cu număr maxim de soldaţi
regrupaţi).
Date de intrare
Fişierul de intrare pluton.in conţine pe prima linie numărul n de soldaţi recuperaţi, iar pe
fiecare dintre următoarele n linii câte un număr de identificare a celor n soldaţi.
Date de ieşire
Fişierul de ieşire pluton.out va conţine pe prima linie numărul de plutoane refăcute. Linia a
doua va conţine numărul de soldaţi din cel mai numeros pluton refăcut. Linia a treia va conţine
numărul de plutoane care au numărul maxim de soldaţi recuperaţi. Linia a patra va conţine
CAPITOLUL 17. OJI 2006 17.2. PLUTON 428

componenţa unui astfel de pluton, cu număr maxim de soldaţi recuperaţi, numerele de identificare
ale soldaţilor din componenţă fiind scrise unul după altul separate prin câte un spaţiu.
Restricţii şi precizări
a 0 $ n & 4000
a 0 $ număr de identificare $ 2.000.000.000
Observaţii
Deoarece linia a patra conţine numerele de identificare ale soldaţilor unuia dintre plutoanele
cu un număr maxim de soldaţi, pot exista mai multe soluţii corecte. Se poate alege oricare dintre
acestea.
Se acordă punctaje parţiale astfel: pentru valoarea corectă de pe prima linie se acordă 30%
din punctaj; pentru valorile corecte de pe prima şi a doua linie se acordă 50% din punctaj, pentru
valorile corecte de pe prima, a doua şi a treia linie se acordă 70% din punctaj, iar pentru rezolvarea
corectă a tuturor cerinţelor se acordă punctajul integral aferent testului.
Exemplu
pluton.in pluton.out Explicaţie
10 6 Au fost recuperaţi soldaţi din 6 plutoane
1223 3 distincte, cei mai mulţi soldaţi recuperaţi
123 2 dintr-un pluton fiind ı̂n număr de 3.
666 321 312 123
321 Există 2 plutoane cu număr maxim de
7890 soldaţi recuperaţi (3), unul dintre ele
2213 fiind format din soldaţii cu numerele
312 321 312 123.
655
1000 De remarcat că şi soluţia
1322 1223 2213 1322 este corectă.
Timp de rulare/test: 1 secundă

17.2.1 Indicaţii de rezolvare

Soluţia comisiei
Soluţia 1:
 ı̂n timpul citirii creez un nou vector care conţine pe poziţiile corespunzătoare numerele de
identificare din vectorul iniţial ı̂n ordinea descrescatoare a cifrelor
 ı̂n etapa a doua se parcurge vectorul nou format grupând toate numerele de identificare
identice; după formarea unui grup (pluton) se determină mărimea acestuia reţinâdu-se acesta
dacă e cel mai numeros găsit până ı̂n acel moment sau contorizându-l dacă numărul de soldaţi
determinat este egal cu cel maxim determinat anterior.
Soluţia 2:
 citesc numerele de identificare ı̂n vectorul a[]

 construiesc 2 vectori ajutători


a vectorul b[] care va conţine numărul de cifre al fiecărui element
a vectorul c[] care va conţine numărul de cifre distincte a fiecărui element
 ordonez cei trei vectori crescător după numărul de cifre distincte (după c[]) şi după numărul
de cifre, deci cheia de sortare va fi un număr construit după formula c[i]*10+b[i]
 formez şi număr plutoanele; plutoanele le voi reţine pe linii distincte a matricei G[MAX][2]
a ı̂n fiecare linie, elementul G[i][0] va conţine numărul de elemente din pluton
a elementul G[i][1] va conţine reprezentantul plutonului, primul care apare ı̂n a[]
a repet până când toate elementele din a au fost verificate
reţinem primul element nepus ı̂ncă din pluton cu caracteristicile lui
verific elementele cu aceleaşi caracteristici să facă parte din acelaşi pluton cu primul
element din pluton pe care l-am reţinut
CAPITOLUL 17. OJI 2006 17.2. PLUTON 429

testez dacă are aceleaşi cifre


· dacă nu are aceleaşi cifre trec mai departe
· altfel ı̂l numar (ı̂ncă face parte din acelaşi pluton)

 detectez numărul maxim de elemente ale unui pluton şi reţin maximul

 afişare cerinţele 1 2 3 folosind valorile aflate


 la cerinţa 4
caut in a[] reprezentantul unui pluton numeros
afişez cele maxe elemente - ı̂n vectorul sortat elementele cu aceleaşi caracteristici (b şi c)
sunt unul după altul, dar mai trebuie verificat să aibă caracteristicile reprezentantului
(ex. 1212 şi 3434 au aceleaşi caracteristici (4,2), (4,2), dar nu fac parte din acelaşi
pluton)
Soluţia 3:

 se utilizeaza notiunea de lista: dintr-o lista fac parte toti membrii unui pluton
 se construieste un vector ajutator care retine pentru fiecare element elementul care il urmeaza
in lista
 pe parcursul formarii listelor se determina lista cea mai numeroasa precum si numarul de
liste de acest tip
 afisarea se face utilizand informatiile din vetorul urm

17.2.2 Cod sursă

Listing 17.2.1: PLUTONC.C


#include <stdio.h>

#define MAX 4001


#define TRUE 1
#define FALSE 0

FILE *Fin, *Fout;

long a[MAX];
unsigned char b[MAX], c[MAX], Pus[MAX];
long G[MAX][2];
unsigned char cifre[10], cifreTest[10];
long n, Nre, NrG, maxe, btest, ctest;
unsigned char OK;

int ToatePuse(void)
{
int i, x;

x = TRUE;
for (i=1; i<=n; i++)
x*=Pus[i];
return x;
}

void Cifrele(long x, unsigned char c[10])


{
int j;

for (j=0; j<=9; j++) c[j]=0;


while (x)
{
c[x%10]++;
x/=10;
}
}
CAPITOLUL 17. OJI 2006 17.2. PLUTON 430

void Citire(void)
{
int i, j;
long x;

fscanf(Fin, "%ld", &n);


for (i=1; i<=n; i++)
{
fscanf(Fin, "%ld", &a[i]);
/*construiesc 2 vectori ajutatori
- vectorul b care va contine numarul de cifre al fiecarui element
- vectorul c care va contine numarul de cifre distincte a fiecarui element*/
for (j=0; j<=9; j++) cifre[j]=0;
x=a[i];
while (x)
{
b[i]++; //numar total de cifre
cifre[x%10]=1;
x/=10;
}
for (j=0; j<=9; j++)
c[i]+=cifre[j]; //numar de cifre distincte
}
}

void Sortare(void)
{
int i, j;
long min, pm, x, aux;

/*ordonez cei trei vectori crescator dupa numarul de cifre (dupa b)


si dupa numarul de cifre distincte, deci cheia de sortare va fi un numar
construit dupa formula b[i]*10+c[i] */
for (i=1; i<=n-1; i++)
{
min=b[i]*10+c[i]; pm=i;
for (j=i+1; j<=n; j++)
{
x=b[j]*10+c[j];
if (x<min)
{
min=x; pm=j;
}
}

aux=c[i]; c[i]=c[pm]; c[pm]=aux;


aux=b[i]; b[i]=b[pm]; b[pm]=aux;
aux=a[i]; a[i]=a[pm]; a[pm]=aux;
}
}

void FormarePlutoane(void)
{
int i, j;

/*formez si numar plutoanele


- plutoanele le voi retine pe linii distincte a matricii G
- in fiecare linie, elementul 0 va contine numarul de elemente din pluton
iar elementul 1 reprezentantul plutonului, primul care apare in a*/
while (!ToatePuse())
{
i=1;
while (Pus[i]) i++; //caut primul nepus inca

//retinem primul element nepus din pluton cu caracteristicile lui


btest=b[i]; ctest=c[i];
NrG=NrG+1; G[NrG][0]=1;
G[NrG][1]=a[i]; Pus[i]=TRUE; //il pun in pluton
Cifrele(a[i], cifreTest); //retin cifrele in cifreTest[]

//verific elementele cu aceleasi caracteristici daca fac parte din


//acelasi pluton cu primul element pe care l-am retinut
i++;
while ((b[i]==btest) && (c[i]==ctest) && (i<=n))
{ //ar putea face parte din acelasi pluton
Cifrele(a[i], cifre);//aflu cifrele in cifre[]
CAPITOLUL 17. OJI 2006 17.2. PLUTON 431

OK=TRUE; //testez daca are aceleasi cifre


for (j=0; j<=9; j++)
if (cifreTest[j]!=cifre[j]) OK=FALSE;
if (OK) //are aceleasi cifre
{
G[NrG][0]++; //un nou soldat in pluton
Pus[i]=TRUE; //l-am pus
}
i++;
}
}
}

void Afisare(void)
{
int i, j;
long x;

fprintf(Fout, "%ld\n", NrG); //cerinta 1

maxe=0; //determin numarul maxim de elemente ale unui pluton


for (i=1; i<=n; i++)
if (G[i][0]>maxe)
{
maxe=G[i][0];
x=G[i][1]; //retin reprezentantul plutonului numeros
}

fprintf(Fout, "%ld\n", maxe); //cerinta 2

Nre=0;
for (i=1; i<=NrG; i++)
if (G[i][0]==maxe)
Nre++;

fprintf(Fout, "%ld\n", Nre); //cerinta 3

i=1;
while (x!=a[i]) i++; //il caut in a
fprintf(Fout, "%ld ", a[i]); //scriu reprezentantul plutonului
btest=b[i]; ctest=c[i]; //retin caracteristicile
Cifrele(a[i], cifreTest); //aflu cifrele

i++; //cerinta 4

while ((b[i]==btest) && (c[i]==ctest) && (i<=n))


{ //ar putea face parte din acelasi pluton
Cifrele(a[i], cifre); //aflu cifrele
OK=TRUE; //testez daca are aceleasi cifre
for (j=0; j<=9; j++)
if (cifreTest[j]!=cifre[j]) OK=FALSE;
if (OK) //are aceleasi cifre
fprintf(Fout, "%ld ", a[i]); //il afisez
i++;
}

fprintf(Fout, "\n");
}

int main(void)
{
Fin=fopen("pluton.in", "rt"); //deschid fisierul ’pluton.in’ pentru citire
Fout=fopen("pluton.out", "wt");//deschid fisierul ’pluton.out’ pentru scriere
Citire();
Sortare();
FormarePlutoane();
Afisare();
fclose(Fin);
fclose(Fout);
return 0;
}

Listing 17.2.2: PLUTCARM.CPP


#include <fstream>
CAPITOLUL 17. OJI 2006 17.2. PLUTON 432

#include <conio.h>
#include <cstring>

using namespace std;

int verif(long int a,long int b)

// verifica daca a si b au aceleasi cifre


{ int x[10],y[10],i;

memset(x,0,sizeof(x)); // x[i] retine numarul de cifre de i din a


memset(y,0,sizeof(y)); // y[i] retine numarul de cifre de i din b

while (a)
{ x[a%10]++;
a/=10;
}

while (b)
{ y[b%10]++;
b/=10;
}

for (i=0;i<10;i++)
if (x[i]!=y[i])
return 0;

return 1;
}

int main()
{ long int x[4000],t;
int y[4000],z[4000],a[4000],b[4000],urm[4000],max,q,i1,j,p;

memset(x,0,sizeof(x)); // numerele de identificare ale soldatilor


memset(y,0,sizeof(y)); // bitul j, j=0,1,...,9, a lui y[i] este 1 daca
// x[i] contine cifra j
memset(z,0,sizeof(z)); // z[i]=numarul de cifre (distincte sau nu) din x[i]
memset(a,0,sizeof(a)); // a[i] = numaarum plutonului din care face parte i

// soldatii i si j fac parte din acelasi pluton daca:


// 1. y[i]=y[j], adica au aceleasi cifre distincte
// 2. z[i]=z[j], adica au acelasi numar total de cifre
// dar aceste doua conditii nu sunt suficiente (vezi de exemplu 112 si 122)
// 3. verif(x[i],y[j])=1, adica au exact aceleasi cifre

memset(b,0,sizeof(b));
memset(urm,0,sizeof(urm)); // urm[i] memoreaza indicele urmatorului soldat
// din acelasi pluton cu i

int n,i,k;

ifstream f("pluton.in");

f>>n; // numarul de soldati

for (i=0;i<n;i++)
{ f>>x[i];
k=0;
t=x[i];
y[i]=0; z[i]=0;
while (t)
{ y[i]=y[i] | (1<<(t%10));
t=t/10; z[i]++;
}
}

t=0; // t = numarul total de plutoane


max=0; // max = numarul maxim de soldati dintr-un pluton
q=0; // p = indicele primului soldat din plutonul cu
// numar maxim de soldati
// q = numarul de putoane cu numar maxim de soldati
for (i=0;i<n;i++)
if (a[i]==0)
{ i1=i; t++;
k=1; a[i]=t;
CAPITOLUL 17. OJI 2006 17.2. PLUTON 433

for (j=i+1;j<n;j++)
if ((a[j]==0)&&(y[i]==y[j])&&(z[i]==z[j]))
{ if (verif(x[i],x[j]))
{ a[j]=t;
urm[i1]=j;
i1=j;
k++;
}
}

if (k>max) { max=k; p=i; q=1; }


else
if (k==max)
q++;
}

ofstream g("pluton.out");
g<<t<<endl;
g<<max<<endl;
g<<q<<endl;

i=p;
do
{ g<<x[i]<<" ";
i=urm[i];
} while (i!=0);

g.close();
f.close();

return 0;
}

Listing 17.2.3: PLUTRADU.CPP


#include<fstream>

using namespace std;

long Nr[4001];//Nr[k]=nr de identificare al soldatului k

long NrSort[4001];//NrSort[k]=nr de identificare al soldatului k


//avand cifrele asezate in ordine descrescatoare - cu ajutorul
//acestui vector se va verifica rapid daca doi soldati i si j fac
//parte din acelasi pluton (adica au numerele de identificare
//Nr[i] si Nr[j] formate din acelasi cifre, insa in alta ordine)
//deoarece este suficient sa testam daca NrSort[i]=NrSort[j]

long n; //numarul de soldati


long nrp; //numarul de plutoane refacute
long maxx; //numarul maxim de soldati dintr-un pluton refacut
long nrmax;//numarul de plutoane refacute cu numar maxim de soldati (max)

long SortareCifre(long x) //functia intoarce numarul obtinut prin asezarea


{ //in ordine descrescatoare a cifrelor numarului x
long aux,c,y; //de exemplu SortareCifre(2810831)=8832110

aux=x; //in aux se salveaza valoarea initiala a numarului x


y=0;
for(c=9;c>=0;c--) //se considera pe rand fiecare cifra c de la 9 la 0
{
while(x) //se ia pe rand fiecare cifra din numarul x
{ //si daca ea este egala cu c se alipeste la
if(x%10==c) y=y*10+x%10; //un alt numar y - astfel numarul y va avea
x=x/10; //la sfarsit aceleasi cifre ca si numarul x,
} //insa asezate in ordine descrescatoare
x=aux; //se reface valoarea initiala a lui x
}
return y;
}

int main()
{
long i,j,p,pmax;
long aux;
CAPITOLUL 17. OJI 2006 17.2. PLUTON 434

fstream f;

f.open("pluton.in",ios::in);
f>>n; //se citesc din fisierul pluton.in numarul n si
for(i=1;i<=n;i++) //elementele vectorului Nr si se completeaza valorile
{ //corespunzatoare in vectorul NrSort
f>>Nr[i];
NrSort[i]=SortareCifre(Nr[i]);
}
f.close();

i=1; //pozitia curenta din vectori Nr si NrSort(soldatul curent i)


nrp=nrmax=maxx=0;
while(i<=n)
{
p=i; //se salveaza pozitia curenta i in variabila p
j=i+1; //se cauta soldati j din acelasi pluton cu soldatul curent i
while(j<=n) //adica soldati pentru care NrSort[j]=NrSort[i]
{
if(NrSort[i]==NrSort[j]) //in cazul in care se gaseste un astfel de soldat
{ //acesta este adus langa soldatul curent i
i++;
aux=Nr[i]; Nr[i]=Nr[j]; Nr[j]=aux;
aux=NrSort[i]; NrSort[i]=NrSort[j]; NrSort[j]=aux;
}
j++;
}

i++; //s-au adus langa soldatul i toti posibilii sai colegi de pluton
nrp++; //deci s-a mai refacut un pluton intre pozitiile p si i-1 din
//cei doi vectori Nr si NrSort

if(i-p>maxx) //daca numarul de soldati din ultimul pluton refacut (adica i-p)
{ //este mai mare strict decat max se actualizeaza valorile
maxx=i-p; //variabilelor max si nrmax
nrmax=1;
pmax=p; //pmax retine pozitia la care incepe in vectorul Nr plutonul
} //cu numar maxim de soldati
else
if(i-p==maxx) nrmax++; //daca numarul de soldati din ultimul pluton refacut
} //este egal cu max se actualizeaza valoarea lui nrmax

f.open("pluton.out",ios::out); //se scriu in fisierul pluton.out rezultatele


f<<nrp<<endl<<maxx<<endl<<nrmax<<endl;
for(i=pmax;i<pmax+maxx;i++) f<<Nr[i]<<" ";
f.close();

return 0;
}

17.2.3 Rezolvare detaliată


Solutie ”incorectă” pentru Borland C++ 3.1 dar care ia 100 puncte !!!

Listing 17.2.4: pluton1.java


1 import java.io.*;
2 class Pluton1
3 {
4 static int n,np;
5 static long[] ni=new long[4001];
6 static long[] nid=new long[4001];
7 static int[] nip=new int[4001];
8 static int[] z=new int[4001];
9 static int[] fc=new int[10];
10 static int[] x=new int[11];
11
12 static long cifre(long nr) // 1230456789 --> 9.876.543.210 !!!
13 {
14 int i,j,k,nc=0;
15 long nrcd=0; // nr cu cifre descrescatoare
16
17 for(i=0;i<=9;i++) fc[i]=0;
18 for(i=0;i<=10;i++) x[i]=0;
CAPITOLUL 17. OJI 2006 17.2. PLUTON 435

19
20 while(nr!=0) { fc[(int)(nr%10)]++; nr=nr/10; nc++; }
21
22 k=0;
23 for(i=9;i>=0;i--)
24 if(fc[i]!=0)
25 for(j=1;j<=fc[i];j++) { k++; x[k]=i; }
26
27 for(i=1;i<=nc;i++) nrcd=nrcd*10+x[i];
28 return nrcd;
29 }// cifre(...)
30
31 public static void main(String[] args) throws IOException
32 {
33 int i,j;
34 int max,npmax,pmax;
35
36 StreamTokenizer st=new StreamTokenizer(
37 new BufferedReader(new FileReader("9-pluton.in")));
38 PrintWriter out=new PrintWriter(
39 new BufferedWriter(new FileWriter("pluton.out")));
40
41 st.nextToken(); n=(int)st.nval;
42 for(i=1;i<=n;i++) {st.nextToken(); ni[i]=(int)st.nval;}
43
44 for(i=1;i<=n;i++) nid[i]=cifre(ni[i]);
45
46 np=0;
47 for(i=1;i<=n;i++)
48 {
49 if(nip[i]!=0) continue;
50 np++;
51 nip[i]=np;
52 for(j=i+1;j<=n;j++)
53 if(nip[j]==0)
54 if(nid[j]==nid[i]) nip[j]=np;
55 }
56
57 out.println(np);
58
59 for(i=1;i<=np;i++)
60 for(j=1;j<=n;j++) if(nip[j]==i) z[i]++;
61
62 max=0;
63 npmax=0;
64 pmax=0;
65
66 for(i=1;i<=np;i++) if(z[i]>max) {max=z[i];pmax=i;}
67 out.println(max);
68
69 for(i=1;i<=np;i++) if(z[i]==max) npmax++;
70 out.println(npmax);
71
72 for(i=1;i<=n;i++) if(nip[i]==pmax) out.print(ni[i]+" ");
73
74 out.println();
75 out.close();
76 }// main
77 }// class

Soluţie ”corectă” şi pentru Borland C++ 3.1


Listing 17.2.5: pluton2.java
1 import java.io.*;
2 class Pluton2
3 {
4 static int n,np;
5 static int[] ni=new int[4001];
6 static int[] nid1=new int[4001];
7 static int[] nid2=new int[4001];
8 static int[] nip=new int[4001];
9 static int[] z=new int[4001];
10 static int[] fc=new int[10];
11 static int[] x=new int[11];
CAPITOLUL 17. OJI 2006 17.2. PLUTON 436

12
13 static void nrcd(int nr,int i0)
14 {
15 int i,j,k,nc;
16 int nrcd1, nrcd2; // nr cu cifre descrescatoare = |nrcd1|nrcd2|
17
18 for(i=0;i<=9;i++) fc[i]=0;
19 for(i=0;i<=10;i++) x[i]=0;
20
21 nc=0;
22 while(nr!=0) { fc[(int)(nr%10)]++; nr=nr/10; nc++; }
23
24 k=0;
25 for(i=0;i<=9;i++)
26 if(fc[i]!=0)
27 for(j=1;j<=fc[i];j++) { k++; x[k]=i; }
28
29 nrcd1=0;
30 for(i=nc;i>=6;i--) nrcd1=nrcd1*10+x[i];
31
32 nrcd2=0;
33 for(i=5;i>=1;i--) nrcd2=nrcd2*10+x[i];
34
35 nid1[i0]=nrcd1;
36 nid2[i0]=nrcd2;
37 }// cifre(...)
38
39 public static void main(String[] args) throws IOException
40 {
41 int i, j, max, npmax, pmax;
42
43 StreamTokenizer st=new StreamTokenizer(
44 new BufferedReader(new FileReader("pluton.in")));
45 PrintWriter out=new PrintWriter(
46 new BufferedWriter(new FileWriter("pluton.out")));
47
48 st.nextToken(); n=(int)st.nval;
49 for(i=1;i<=n;i++) {st.nextToken(); ni[i]=(int)st.nval;}
50 for(i=1;i<=n;i++) nrcd(ni[i],i);
51
52 np=0;
53 for(i=1;i<=n;i++)
54 {
55 if(nip[i]!=0) continue;
56 np++;
57 nip[i]=np;
58 for(j=i+1;j<=n;j++)
59 if(nip[j]==0)
60 if((nid1[j]==nid1[i])&&(nid2[j]==nid2[i])) nip[j]=np;
61 }
62 out.println(np);
63
64 for(i=1;i<=np;i++)
65 for(j=1;j<=n;j++) if(nip[j]==i) z[i]++;
66
67 max=0; npmax=0; pmax=0;
68
69 for(i=1;i<=np;i++) if(z[i]>max) {max=z[i];pmax=i;}
70 out.println(max);
71
72 for(i=1;i<=np;i++) if(z[i]==max) npmax++;
73 out.println(npmax);
74
75 for(i=1;i<=n;i++) if(nip[i]==pmax) out.print(ni[i]+" ");
76
77 out.println(); out.close();
78 }// main
79 }// class
Capitolul 18

OJI 2005

18.1 Numere
Doru Popescu Anastasiu
Mircea este pasionat de programare. El a ı̂nceput să rezolve probleme din ce ı̂n ce mai
grele. Astfel a ajuns la o problemă, care are ca date de intrare un tablou pătratic cu n linii şi
2
n coloane, componente tabloului fiind toate numerele naturale distincte de la 1 la n . Pentru a
verifica programul pe care l-a scris ı̂i trebuie un fişier care să conţină tabloul respectiv. După ce a
creat acest fişier, fratele său, pus pe şotii ı̂i umblă ı̂n fişier şi ı̂i schimbă câteva numere consecutive,
cu numărul 0. Când se ı̂ntoarce Mircea de la joacă constată cu stupoare că nu ı̂i merge programul
pentru testul respectiv. După câteva ore de depanare ı̂şi dă seama că programul lui este corect şi
că fişierul de intrare are probleme.
Cerinţă
Scrieţi un program care să-l ajute pe Mircea, găsindu-i cel mai mic şi cel mai mare dintre
numerele consecutive schimbate de fratele său.
Date de intrare
În fişierul numere.in se dă pe prima linie n, iar pe următoarele n linii elementele tabloului,
câte n elemente pe o linie, separate ı̂ntre ele prin câte un spaţiu, după modificările făcute de fratele
lui Mircea.
Date de ieşire
În fişierul numere.out se va scrie pe un singur rând cu un singur spaţiu ı̂ntre ele numerele
cerute (primul fiind cel mai mic).
Restricţii şi precizări
a 0 $ n & 500.
a Fratele lui Mircea schimbă cel puţin un număr ı̂n fişier.
a Numerele schimbate de fratele lui Mircea sunt mai mici sau cel mult egale cu 60000.

Exemplu
numere.in numere.out Explicaţie
3 24 În fişierul de intrare au fost ı̂nlocuite cu 0
507 numerele 2, 3, 4.
001
698
Timp maxim de execuţie: 1 secundă/test

18.1.1 Indicaţii de rezolvare

Soluţia oficială
Se foloseşte un vector cu componente 0 sau 1, x x1 , ..., xm , unde m este 64000 dacă
2 2
numărul de componente al tabloului (n ) este mai mare decât 64000, respectiv m n ı̂n celălalt
caz.
Iniţial vectorul x are toate componentele 0. Pe măsură ce se citesc numere v din fişier, com-
ponentele corespunzătoare din x se schimbă ı̂n 1 (xv  1).

437
CAPITOLUL 18. OJI 2005 18.1. NUMERE 438

După citirea numerelor din fişier se obţine ı̂n x o secvenţă de 0. Indicii corespunzători acestei
secvenţe formează mulţimea de numere consecutive care au fost ı̂nlocuite cu 0 de fratele lui Mircea.
O altă modalitate de rezolvare constă ı̂n calculul sumei tuturor numerelor din tablou şi
obţinerea astfel a sumei secvenţei de numere consecutive şterse de Mircea. Din păcate sumele
sunt prea mari şi depăşesc tipurile predefinite. Dacă se folosesc implementări pe numere mari se
obţine punctajul maxim, altfel doar jumătate din punctaj.
Soluţie prezentată ı̂n GInfo nr. 15/3
Pentru rezolvarea acestei probleme este suficient să utilizăm un şir de biţi care vor indica dacă
un număr apare sau nu ı̂n fişier.
Vom avea nevoie ı̂ntotdeauna de cel mult 250.000 de biţi, deci 31250 de octeţi.
Iniţial toţi biţii vor avea valoarea 0, iar pe măsură ce sunt citite numerele care formează
matricea, valoarea bitului corespunzător unui număr citit devine 1.
Şirul de biţi va conţine ı̂n final o secvenţă de zerouri care va reprezenta soluţia problemei.
Există şi posibilitatea de a utiliza heap-ul pentru a păstra 250.000 de valori booleene sau ı̂ntregi
care vor permite şi ele determinarea secvenţei care lipseşte.

18.1.2 Cod sursă

Listing 18.1.1: numere.pas


1 program numere;
2
3 var f:text;
4 n,i,j,nr0,x:longint;
5 min,max,s,n1,nr1,m:longint;
6 v:array[0..60000]of byte;
7
8 begin
9 assign(f,’numere.in’);
10 reset(f);
11 s:=0;
12 nr0:=0;
13 readln(f,n);
14 for i:=1 to n do
15 begin
16 for j:=1 to n do
17 begin
18 read(f,x);
19 if (x<>0)and(x<=60000) then v[x]:=1;
20 end;
21 readln(f);
22 end;
23 close(f);
24 {for i:=1 to n*n do write(v[i],’ ’);
25 readln; }
26 if n*n>=60000 then m:=60000 else m:=n*n;
27 i:=1;
28 while (v[i]=1)and(i<=m) do inc(i);
29 min:=i;
30 i:=m;
31 while (v[i]=1)and(i>0) do dec(i);
32 max:=i;
33 assign(f,’numere.out’);
34 rewrite(f);
35 writeln(f,min,’ ’,max);
36 close(f);
37 end.

18.1.3 Rezolvare detaliată

Listing 18.1.2: numere.java


1 import java.io.*;
2 class Numere
3 {
4 static byte[] x=new byte[600001];
CAPITOLUL 18. OJI 2005 18.2. MAXD 439

5 static int n,min=0,max=0;


6
7 public static void main(String[] args) throws IOException
8 {
9 int i,j;
10 long t1,t2;
11 t1=System.currentTimeMillis();
12 StreamTokenizer st=new StreamTokenizer(
13 new BufferedReader(
14 new FileReader("numere.in")));
15 st.nextToken(); n=(int)st.nval;
16 for(i=1;i<=n*n;i++)
17 {
18 st.nextToken(); j=(int)st.nval;
19 if(j<=60000) x[j]=1;
20 }
21 min=1;
22 for(i=1;i<=60000;i++) if(x[i]==0) {min=i; break;}
23 max=min;
24 for(i=min+1;i<=60000;i++)
25 if(x[i]==0) max++; else break;
26
27 PrintWriter out=new PrintWriter(
28 new BufferedWriter(
29 new FileWriter("numere.out")));
30 out.println(min+" "+max);
31 out.close();
32 t2=System.currentTimeMillis();
33 System.out.println("Timp = "+(t2-t1));
34 }
35 }

18.2 MaxD
Maria şi Adrian Niţă
Fiind elev ı̂n clasa a IX-a, George, ı̂şi propune să studieze capitolul divizibilitate cât mai
bine. Ajungând la numărul de divizori asociat unui numă natural, constată că sunt numere ı̂ntr-un
interval dat, cu acelaşi număr de divizori.
De exemplu, ı̂n intervalul 1, 10, 6, 8 şi 10 au acelaşi număr de divizori, egal cu 4. De asemenea,
4 şi 9 au acelaşi număr de divizori, egal cu 3 etc.
Cerinţă
Scrieţi un program care pentru un interval dat determină care este cel mai mic număr din
interval ce are număr maxim de divizori. Dacă sunt mai multe numere cu această proprietate se
cere să se numere câte sunt.
Date de intrare
Fişierul de intrare maxd.in conţine pe prima linie două numere a şi b separate prin spaţiu
(a & b) reprezentând extremităţile intervalului.
Date de ieşire
Fişierul de ieşire maxd.out va conţine pe prima linie trei numere separate prin câte un spaţiu
min nrdiv contor cu semnificaţia:
min = cea mai mică valoare din interval care are număr maxim de divizori
nrdiv = numărul de divizori ai lui min
contor = câte numere din intervalul citit mai au acelaşi număr de divizori egal cu nrdiv
Restricţii şi precizări
a 1 & a & b & 1.000.000.000
a 0 & b  a & 10.000

Punctaj
Dacă aţi determinat corect min, obţineţi 50% din punctaj.
Dacă aţi determinat corect nrdiv, obţineţi 20% din punctaj
Dacă aţi determinat corect contor, obţineţi 30% din punctaj.
Exemple
CAPITOLUL 18. OJI 2005 18.2. MAXD 440

maxd.in maxd.out Explicaţie


200 200 200 12 1 200 are 12 divizori iar in intervalul 200, 200
există un singur număr cu această proprietate
maxd.in maxd.out Explicaţie
2 10 643 6 este cel mai mic număr din interval care are
maxim de divizori egal cu 4 şi sunt 3 astfel de
numere 6, 8, 10
Timp maxim de execuţie: 1 sec/test

18.2.1 Indicaţii de rezolvare


Soluţia oficială
Pentru un număr natural n cuprins ı̂n intervalul a, b, să considerăm descompunerea ı̂n
factori primi:
e e e e
n f1 1 ˜ f2 2 ˜ f3 3 ˜ ... ˜ fk k
Numărul de divizori ai lui n este dat de formula:
e1  1 ˜ e2  1 ˜ e3  1 ˜ ... ˜ ek  1
Se obţine un timp de execuţie mai bun dacă pentru factori (fk ) se realizează un tablou unidi-
mensional format de numerele prime din domeniul int, iar n este verificat - legat de proprietatea
de divizibilitate - doar pe valori numere prime din acest tablou.
Se determină pentru fiecare valoare x din intervalul a, b numărul său de divizori, se reţine
acea valoare ce are număr maxim de divizori; ı̂n caz de valori cu acelaşi număr maxim de divizori
se incrementează contorul ce reţine numărul acestor valori.
Soluţie prezentată ı̂n GInfo nr. 15/3
Pentru fiecare număr ı̂n intervalul dat, vom determina numărul divizorilor acestuia.
Pentru aceasta vom determina descompunerea ı̂n factori primi a fiecărui număr.
Pentru un număr n, descompunerea ı̂n factori primi are forma:
e e e e
n f1 1 f2 2 f3 3 ... fk k ,
iar numărul divizorilor va fi dat de valoarea
e1  1 e2  1 e3  1... ek  1.
Pentru fiecare valoare x din intervalul a, b se determină numărul său de divizori, se reţine
acea valoare care are număr maxim de divizori; ı̂n cazul identificării unor valori cu acelaşi număr
maxim de divizori, se incrementează contorul care reţine numărul acestor valori.
În cazul identificării unei valori cu număr mai mare de divizori, valoarea contorului devine 1
şi se actualizează variabila care conţine numărul cu cei mai mulţi divizori.
În final se va afişa prima valoare x care are cel mai mare număr de divizori, precum şi valoarea
contorului.

18.2.2 *Cod sursă

18.2.3 Rezolvare detaliată

Varianta 1:

Listing 18.2.1: MaxD1.java


1 import java.io.*; //timp = 5600
2 class MaxD
3 {
4 static int[] x;
5 static int a,b;
6
7 public static void main(String[] args) throws IOException
8 {
9 int i;
10 long t1,t2;
11 t1=System.currentTimeMillis();
12 StreamTokenizer st=new StreamTokenizer(
13 new BufferedReader(new FileReader("maxd.in")));
14 st.nextToken(); a=(int)st.nval;
CAPITOLUL 18. OJI 2005 18.2. MAXD 441

15 st.nextToken(); b=(int)st.nval;
16 x=new int[b-a+2];
17 for(i=a;i<=b;i++) x[i-a+1]=descfact(i);
18 int max=-1;
19 int imax=-1;
20 for(i=1;i<=b-a+1;i++)
21 if(x[i]>max) { max=x[i]; imax=i; }
22 int nrmax=0;
23 for(i=1;i<=b-a+1;i++) if(x[i]==max) nrmax++;
24
25 PrintWriter out=new PrintWriter(
26 new BufferedWriter(new FileWriter("maxd.out")));
27 out.println((imax+a-1)+" "+max+" "+nrmax);
28 out.close();
29 t2=System.currentTimeMillis();
30 System.out.println("Timp = "+(t2-t1));
31 }
32
33 static int descfact(int nr)
34 {
35 int d;
36 int nrd;
37 int p=1;
38
39 d=2;
40 nrd=0;
41 while(nr%d==0) { nrd++; nr=nr/d; }
42 p=p*(nrd+1);
43
44 d=3;
45 while(d*d<=nr)
46 {
47 nrd=0;
48 while(nr%d==0) { nrd++; nr=nr/d; }
49 p=p*(nrd+1);
50 d=d+2;
51 }
52 if(nr>1) p*=2;
53 return p;
54 }
55 }

Varianta 2:

Listing 18.2.2: MaxD2.java


1 import java.io.*;
2 class MaxD
3 {
4 public static void main(String[] args) throws IOException
5 {
6 int i;
7 int a,b;
8
9 long t1,t2;
10 t1=System.currentTimeMillis();
11
12 StreamTokenizer st=new StreamTokenizer(
13 new BufferedReader(new FileReader("maxd.in")));
14
15 st.nextToken(); a=(int)st.nval;
16 st.nextToken(); b=(int)st.nval;
17
18 int max=-1;
19 int imax=-1;
20 int nrmax=0;
21 int nd;
22
23 for(i=a;i<=b;i++)
24 {
25 nd=nrDiv(i);
26 if(nd>max) {max=nd; imax=i; nrmax=1;}
27 else if(nd==max) nrmax++;
28 }
29
CAPITOLUL 18. OJI 2005 18.2. MAXD 442

30 PrintWriter out=new PrintWriter(


31 new BufferedWriter(new FileWriter("maxd.out")));
32 out.println(imax+" "+max+" "+nrmax);
33 out.close();
34 t2=System.currentTimeMillis();
35 System.out.println("Timp = "+(t2-t1));
36 }// main(...)
37
38 static int nrDiv(int nr)
39 {
40 int d;
41 int nrd;
42 int p=1;
43
44 d=2;
45 nrd=0;
46 while(nr%d==0) { nrd++; nr=nr/d; }
47 p=p*(nrd+1);
48
49 d=3;
50 while(d*d<=nr)
51 {
52 nrd=0;
53 while(nr%d==0) { nrd++; nr=nr/d; }
54 p=p*(nrd+1);
55
56 d=d+2;
57 }
58
59 if(nr>1) p*=2;
60 return p;
61 }// nrDiv(...)
62 }// class
Capitolul 19

OJI 2004

19.1 Expresie

Se dă un şir de n numere naturale nenule x1 , x2 , ..., xn şi un număr natural m.


Cerinţă
Ó
Să se verifice dacă valoarea expresiei m x1 ...xn exte un număr natural.
În caz afirmativ să se afişeze acest număr descompus ı̂n factori primi.
Date de intrare
În fişierul exp.in se află pe prima linie m, pe linia a doua n, iar pe linia a treia numerele
x1 , x2 , ..., xn separate ı̂ntre ele prin câte un spaţiu.
Date de ieşire
În fişierul exp.out se va scrie pe prima linie cifra 0, dacă valoarea expresiei nu este un număr
natural, respectiv 1 dacă este un număr natural. Dacă valoarea expresiei este un număr natural pe
următoarele linii se vor scrie perechi de forma p e (p este factor prim care apare ı̂n descompunere
la puterea e ' 1). Aceste perechi se vor scrie ı̂n ordine crescătoare după primul număr (adică p).
Restricţii şi precizări
a n - număr natural nenul $ 5000
a xi - număr natural nenul $ 30000, i " 1, 2, ..., n
a m - poate fi una din cifrele 2, 3, 4

Exemple
exp.in exp.out exp.in exp.out
2 0 2 1
4 4 24
32 81 100 19 32 81 100 18 33
51
Timp maxim de execuţie: 1 secundă/test

19.1.1 Indicaţii de rezolvare

Soluţie prezentată de Mihai Stroe, GInfo nr. 14/4


Rezolvarea problemei constă ı̂n descompunerea ı̂n factori primi a numerelor date şi prelu-
crarea acestor factori.
Concret, se poate păstra un vector P de la 1 la 30000, ı̂n care Pi este 0 dacă i nu este prim,
respectiv puterea la care apare i ı̂n descompunerea ı̂n factori primi a produsului dacă i este prim.
Valorile din P nu pot depăşi n log2 30000, deci vor fi mai mici sau egale cu 5000 14 70.000;
astfel, elementele vectorului vor fi de tip longint.
Fiecare din cele n numere este descompus ı̂n factori primi. În momentul ı̂n care se determină
un factor prim F al unui număr din cele date, se incrementează PF cu puterea la care F apare
ı̂n descompunerea numărului respectiv. Nu este necesară memorarea separată a descompunerii
fiecărui număr.
În final, pentru fiecare element din P se verifică dacă este multiplu de m.

443
CAPITOLUL 19. OJI 2004 19.1. EXPRESIE 444

Dacă toate elementele ı̂ndeplinesc această condiţie, expresia este un număr natural şi se trece
la afişare.
Se poate renunţa la vectorul de 30000 de elemente, păstrându-se ı̂n locul acestuia un vector
ı̂n care se memorează numerele prime mai mici decât 30000 şi un vector care arată la ce puteri
apar aceste numere ı̂n descompunere. Această abordare introduce ı̂n plus operaţii pentru a găsi
indicele unui anumit număr prim; se poate folosi cu succes căutarea binară. Pe de altă parte, la
descompunerea ı̂n factori primi se va testa numai ı̂mpărţirea prin numere prime.
Analiza complexităţii Ó
Descompunerea unui număr x ı̂n factori primi are ordinul de complexitate O x, dacă nu se
foloseşte lista de numere prime.
Ó Pasul de descompunere şi completare a vectorului P are deci ordinul de complexitate O n
30000.
Citirea datelor, verificarea dacă expresia este un număr natural şi afişarea au ordinul de com-
plexitate O n.
Ó al algoritmului de rezolvare a acestei probleme este O n,
În concluzie, ordinul de complexitate
dacă facem abstracţie de constanta 30000.

19.1.2 Cod sursă

Listing 19.1.1: EXP.PAS


1 Program Exp;
2
3 type vector = array[1..6000] of integer;
4
5 var x, p, y, f: vector;
6 m, n, dp, i, k, j: integer;
7 sw: boolean;
8 ff: text;
9
10 procedure cit;
11 var i: integer;
12 f: text;
13 begin
14 assign(f, ’exp.in’); reset(f);
15 readln(f, m);
16 readln(f, n);
17 for i:=1 to n do
18 read(f, x[i]);
19 close(f);
20 end;
21
22 function prim(k: integer): boolean;
23 var i: integer;
24 begin
25 prim := true;
26 for i := 2 to trunc(sqrt(k)) do
27 if k mod i=0 then
28 begin
29 prim := false;
30 exit;
31 end;
32 end;
33
34 procedure prime;
35 var i, j: integer;
36 begin
37 p[1] := 2;
38 p[2] := 3;
39 dp := 2;
40 i := 5;
41 while i<30000 do
42 begin
43 if prim(i) then
44 begin
45 inc(dp);
46 p[dp] := i;
47 end;
48 i := i+2;
CAPITOLUL 19. OJI 2004 19.1. EXPRESIE 445

49 end;
50 end;
51
52 begin
53 cit;
54 prime;
55
56 for i := 1 to dp do
57 f[i] := 0;
58 for i := 1 to n do
59 begin
60 j := 1;
61 while x[i]<>1 do
62 begin
63 while x[i] mod p[j]=0 do
64 begin
65 inc(f[j]);
66 x[i] := x[i] div p[j];
67 end;
68 inc(j);
69 end;
70 end;
71 sw := true;
72 for i := 1 to dp do
73 if f[i] mod m<>0 then
74 begin
75 sw := false;
76 break;
77 end;
78
79 assign(ff, ’exp.out’); rewrite(ff);
80 if not sw then
81 writeln(ff, 0)
82 else
83 begin
84 writeln(ff, 1);
85 for i := 1 to dp do
86 if f[i]>0 then
87 writeln(ff, p[i], ’ ’, f[i] div m);
88 end;
89 close(ff);
90 end.

19.1.3 Rezolvare detaliată

Prima variantă:

Listing 19.1.2: Expresie1.java


1 import java.io.*;
2 class Expresie1
3 {
4 static int[] p=new int[30000];
5 static int m,n;
6
7 public static void main(String[] args) throws IOException
8 {
9 int i;
10 StreamTokenizer st=new StreamTokenizer(
11 new BufferedReader(new FileReader("expresie.in")));
12 st.nextToken(); m=(int)st.nval;
13 st.nextToken(); n=(int)st.nval;
14 int[] x=new int[n+1];
15 for(i=1;i<=n;i++) { st.nextToken(); x[i]=(int)st.nval; }
16 for(i=1;i<=n;i++) descfact(x[i]);
17 int ok=1;
18 for (i=2;i<30000;i++)
19 if (p[i]%m==0) p[i]=p[i]/m;
20 else { ok=0; break; }
21 PrintWriter out=new PrintWriter(
22 new BufferedWriter(new FileWriter("expresie.out")));
23 if(ok==0) out.println(0);
24 else
CAPITOLUL 19. OJI 2004 19.1. EXPRESIE 446

25 {
26 out.println(1);
27 for(i=2;i<30000;i++)
28 if(p[i]!=0) out.println(i+" "+p[i]);
29 }
30 out.close();
31 }
32
33 static void descfact(int nr)
34 {
35 int d=2;
36 while(nr%d==0)
37 {
38 p[d]++;
39 nr=nr/d;
40 }
41 d=3;
42 while(d<=nr)
43 {
44 while(nr%d==0) { p[d]++; nr=nr/d; }
45 d=d+2;
46 }
47 }
48 }

A doua variantă:

Listing 19.1.3: Expresie2.java


1 import java.io.*;
2 class Expresie2 // pentru valori < 30.000 sunt 3245 prime 2...29.989
3 { // sunt ˜10% numere prime (+/- 5%)
4 static final int valmax=30000; // valoare maxima pentru x_i
5 static int m,n,nnp; // nnp=nr numere prime < valmax
6 static int[] x;
7 static int[] p=new int[3246]; // numere prime
8 static int[] e=new int[3246]; // exponenti corespunzatori
9 static boolean ok;
10
11 public static void main(String[] args) throws IOException
12 {
13 int i,j;
14 StreamTokenizer st=new StreamTokenizer(
15 new BufferedReader(
16 new FileReader("expresie.in")));
17 PrintWriter out=new PrintWriter(
18 new BufferedWriter(
19 new FileWriter("expresie.out")));
20 st.nextToken(); m=(int)st.nval;
21 st.nextToken(); n=(int)st.nval;
22 x=new int[n+1];
23 for(i=1;i<=n;i++) { st.nextToken(); x[i]=(int)st.nval; }
24 sol();
25 if(!ok) out.println(0);
26 else
27 {
28 out.println(1);
29 for(i=1;i<=nnp;i++)
30 if(e[i]>0) out.println(p[i]+" "+e[i]);
31 }
32 out.close();
33 }// main
34
35 static void sol()
36 {
37 int i,j;
38 prime(); // afisv(p);
39 for(i=1;i<=nnp;i++) e[i]=0; // era initializat implicit !!!
40 for(i=1;i<=n;i++)
41 {
42 j=1; // pozitie in lista numerelor prime p[]
43 while(x[i]!=1)
44 {
45 while(x[i]%p[j]==0) { e[j]++; x[i]/=p[j]; }
46 j++;
47 }
CAPITOLUL 19. OJI 2004 19.1. EXPRESIE 447

48 }
49 ok=true;
50 for(i=1;i<=nnp;i++)
51 if(e[i]%m==0) e[i]=e[i]/m; else {ok=false; break;}
52 }
53
54 static void prime()
55 {
56 int i,j;
57 p[1]=2; p[2]=3; nnp=2;
58 i=5;
59 while(i<valmax)
60 {
61 if(estePrim(i)) p[++nnp]=i;
62 i+=2;
63 }
64 }
65
66 static boolean estePrim(int nr) // folosind lista numerelor prime !
67 {
68 int i=1;
69 while((p[i]*p[i]<nr)&&(nr%p[i]!=0)) i++;
70 if(p[i]*p[i]>nr) return true; return false;
71 }
72 }

A treia variantă:

Listing 19.1.4: Expresie3.java


1 import java.io.*;
2 class Expresie3 // pentru "peste 5.000 de factori"
3 {
4 static int m,n,nf=0,nfc;
5 static int[] x;
6 static int[] f={}, e={};
7 static int[] fc=new int[6]; // 2*3*5*7*11*13=30030 > 30000 pentru x[i]
8 static int[] ec=new int[6]; // exponenti curenti corespunzatori
9 static boolean ok;
10
11 public static void main(String[] args) throws IOException
12 {
13 int i,j;
14 StreamTokenizer st=new StreamTokenizer(
15 new BufferedReader(
16 new FileReader("Expresie.in")));
17 PrintWriter out=new PrintWriter(
18 new BufferedWriter(
19 new FileWriter("Expresie.out")));
20 st.nextToken(); m=(int)st.nval;
21 st.nextToken(); n=(int)st.nval;
22 x=new int[n];
23 for(i=0;i<n;i++) { st.nextToken(); x[i]=(int)st.nval; }
24 sol();
25 if(!ok) out.println(0);
26 else
27 {
28 out.println(1);
29 for(i=0;i<nf;i++) out.println(f[i]+" "+e[i]);
30 }
31 out.close();
32 }// main
33
34 static void sol()
35 {
36 int i;
37 for(i=0;i<n;i++)
38 {
39 if(x[i]==1) continue;
40 descfact(x[i]); interclasare();
41 }
42 ok=true;
43 for(i=0;i<nf;i++)
44 if(e[i]%m==0) e[i]=e[i]/m; else {ok=false; break;}
45 }
46
CAPITOLUL 19. OJI 2004 19.2. REACTIVI 448

47 static void interclasare() // (f cu ff) SI (e cu ee)


48 {
49 int i;
50 if(nf==0)
51 {
52 int[] ff=new int[nfc], ee=new int[nfc];
53 for(i=0;i<nfc;i++) {ff[i]=fc[i]; ee[i]=ec[i];}
54 f=ff; e=ee; nf=nfc; return;
55 }
56 int j, nft=nf+nfc,n;
57 int[] ff=new int[nft], ee=new int[nft];
58 i=0; j=0; n=0; // primul indice in care incarc este 0=zero !!!
59 while((i<nf)&&(j<nfc))
60 {
61 n++;
62 if(f[i]<fc[j])
63 {
64 ff[n-1]=f[i];
65 ee[n-1]=e[i];
66 i++;
67 }
68 else if(f[i]>fc[j])
69 {
70 ff[n-1]=fc[j];
71 ee[n-1]=ec[j];
72 j++;
73 }
74 else
75 {
76 ff[n-1]=f[i];
77 ee[n-1]=e[i]+ec[j];
78 i++; j++;
79 }
80 }
81 while(i<nf) {n++; ff[n-1]=f[i]; ee[n-1]=e[i]; i++;}
82 while(j<nfc) {n++; ff[n-1]=fc[j]; ee[n-1]=ec[j]; j++;}
83 if(n==nft) {f=ff; e=ee; nf=n;}
84 else
85 {
86 int[] fff=new int[n], eee=new int[n];
87 for(i=0;i<n;i++) {fff[i]=ff[i]; eee[i]=ee[i];}
88 f=fff; e=eee; nf=n;}
89 }
90
91 static void descfact(int nr)
92 {
93 // if((nr==0)||(nr==1)) return nr;
94 nfc=0;
95 int d=2;
96 if(nr%d==0)
97 {
98 nfc++; fc[nfc-1]=d; ec[nfc-1]=0;
99 while(nr%d==0) {ec[nfc-1]++; nr=nr/d;}}
100 d=3;
101 while((d*d<=nr)&&(nr!=1))
102 {
103 if(nr%d==0)
104 {
105 nfc++;
106 fc[nfc-1]=d;
107 ec[nfc-1]=0;
108 while(nr%d==0) {ec[nfc-1]++; nr=nr/d;}
109 }
110 d=d+2;
111 }
112 if(nr!=1) {nfc++; fc[nfc-1]=nr; ec[nfc-1]=1;}
113 }// descfact
114 }//class

19.2 Reactivi

Într-un laborator de analize chimice se utilizează N reactivi.


CAPITOLUL 19. OJI 2004 19.2. REACTIVI 449

Se ştie că, pentru a evita accidentele sau deprecierea reactivilor, aceştia trebuie să fie stocaţi
ı̂n condiţii de mediu speciale. Mai exact, pentru fiecare reactiv x, se precizează intervalul de
temperatură minx , maxx  ı̂n care trebuie să se ı̂ncadreze temperatura de stocare a acestuia.
Reactivii vor fi plasaţi ı̂n frigidere.
Orice frigider are un dispozitiv cu ajutorul căruia putem stabili temperatura (constantă) care
va fi ı̂n interiorul acelui frigider (exprimată ı̂ntr-un număr ı̂ntreg de grade Celsius).
Cerinţă
Scrieţi un program care să determine numărul minim de frigidere necesare pentru stocarea
reactivilor chimici.
Date de intrare
Fişierul de intrare react.in conţine:
 pe prima linie numărul natural N , care reprezintă numărul de reactivi;
 pe fiecare dintre următoarele N linii se află min max (două numere ı̂ntregi separate printr-un
spaţiu); numerele de pe linia x  1 reprezintă temperatura minimă, respectiv temperatura maximă
de stocare a reactivului x.
Date de ieşire
Fişierul de ieşire react.out va conţine o singură linie pe care este scris numărul minim de
frigidere necesar.

Restricţii şi precizări


a 1 & N & 8000
a 100 & minx & maxx & 100 (numere ı̂ntregi, reprezentând grade Celsius), pentru orice x de
la 1 la N
a un frigider poate conţine un număr nelimitat de reactivi

Exemple
react.in react.out react.in react.out react.in react.out
3 2 4 3 5 2
-10 10 25 -10 10
-25 57 10 12
20 50 10 20 -20 10
30 40 7 10
78
Timp maxim de execuţie: 1 secundă/test

19.2.1 Indicaţii de rezolvare

Soluţie prezentată de Mihai Stroe, GInfo nr. 14/4


Problema se poate rezolva prin metoda greedy.
O variantă mai explicită a enunţului este următoarea:
”Se consideră N intervale pe o axă. Să se aleagă un număr minim de puncte
astfel ı̂ncât fiecare interval să conţină cel puţin unul dintre punctele alese.”
Facem o primă observaţie: pentru orice soluţie optimă există o soluţie cu acelaşi număr de
puncte (frigidere), ı̂n care fiecare punct să fie sfârşitul unui interval. Aceasta se poate obţine
mutând fiecare punct spre dreapta, până când ar ajunge la sfârşitul intervalului care se termină
cel mai repede, dintre intervalele care ı̂l conţin. Se observă că noua soluţie respectă restricţiile din
enunţ.
În continuare ne vom concentra pe găsirea unei soluţii de acest tip.
Sortăm reactivii după sfârşitul intervalului. Pentru intervalul care se termină cel mai repede,
alegem ultimul punct al său ca temperatură a unui frigider. Se observă că această alegere este
cea mai bună, dintre toate alegerile unui punct ı̂n intervalul respectiv, ı̂n sensul că mulţimea
intervalelor care conţin punctul este mai mare (conform relaţiei de incluziune), decât mulţimea
corespunzătoare oricărei alte alegeri. Acest fapt este adevărat, deoarece mutarea punctului mai
la stânga nu duce la selectarea unor noi intervale.
Intervalele care conţin punctul respectiv sunt eliminate (reactivii corespunzători pot fi plasaţi
ı̂ntr-un frigider), iar procesul continuă cu intervalele rămase, ı̂n acelaşi mod.
Analiza complexităţii
CAPITOLUL 19. OJI 2004 19.2. REACTIVI 450

Notăm cu D numărul de temperaturi ı̂ntregi din intervalul care conţine temperaturile din
enunţ. Se observă că D este cel mult 201.
Citirea datelor de intrare are ordinul de complexitate O N .
Sortarea intervalelor după capătul din dreapta are ordinul de complexitate O N logN .
Urmează F paşi, unde F este numărul de frigidere selectate. Deoarece fiecare frigider este
setat la o temperatură ı̂ntreagă, F & D.
În cadrul unui pas, determinarea intervalului care se termină cel mai repede, pe baza vectorului
sortat, are ordinul de complexitate O 1. Eliminarea intervalelor care conţin un anumit punct
(sfârşitul intervalului care se termină cel mai repede) are ordinul de complexitate O N .
Afişarea rezultatului are ordinul de complexitate O 1.
În concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O N
D  N logN ; deoarece ı̂n general D % logN , considerăm ordinul de complexitate ca fiind O N D.

19.2.2 Cod sursă

Listing 19.2.1: REACT QS.PAS


1 Program Reactivi;
2 {NU l-am dat pe asta ca sursa deoarece intr-a IX-a INCA nu se face QuickSort}
3 Type Frigider = Record
4 min, max: ShortInt
5 End;
6 Var f : Array[1..10000] Of Frigider;
7 r : Array[1..10000] Of Frigider;
8 Fisier : Text;
9 Cate, i, j, N: Integer;
10 min, max : ShortInt;
11
12 Function Maxim(x, y: ShortInt): ShortInt;
13 Begin
14 If x>y Then
15 Maxim := x
16 Else
17 Maxim := y
18 End;
19
20 Function Minim(x, y: ShortInt): ShortInt;
21 Begin
22 If x<y Then
23 Minim := x
24 Else
25 Minim := y
26 End;
27
28 Function Cauta(min, max: ShortInt): Integer;
29 Var i: Integer;
30 Begin
31 Cauta := -1;
32 For i := 1 To Cate Do
33 Begin
34 If (f[i].max>=min) And (f[i].max<=max) Then
35 Begin Cauta := i; Break End
36 Else
37 If (f[i].min>=min) And (f[i].min<=max) Then
38 Begin Cauta := i; Break End
39 Else
40 If (f[i].min<=min) And (f[i].max>=max) Then
41 Begin Cauta := i; Break End
42 Else
43 If (f[i].max<min) Or (f[i].min>max) Then
44 Cauta := -1
45 End;
46 End;
47
48 Procedure Intersectie(j: Integer; min, max: ShortInt);
49 Begin
50 f[j].min := Maxim(min, f[j].min);
51 f[j].max := Minim(max, f[j].max)
52 End;
53
CAPITOLUL 19. OJI 2004 19.2. REACTIVI 451

54 procedure QuickSortCresc(Lo, Hi: Integer);


55
56 procedure Sort(Stanga, Dreapta: Integer);
57 var i, j, x: integer;
58 y: Frigider;
59 begin
60 i := Stanga; j := Dreapta; x := r[(Stanga+Dreapta) DIV 2].min;
61 repeat
62 while r[i].min < x do i := i + 1;
63 while x < r[j].min do j := j - 1;
64 if i <= j then
65 begin
66 y := r[i]; r[i] := r[j]; r[j] := y;
67 i := i + 1; j := j - 1;
68 end;
69 until i > j;
70 if Stanga < j then Sort(Stanga, j);
71 if i < Dreapta then Sort(i, Dreapta);
72 end;
73
74 begin {QuickSort};
75 Sort(Lo,Hi);
76 end;
77
78 procedure QuickSortDescresc(Lo, Hi: Integer);
79
80 procedure Sort(Stanga, Dreapta: Integer);
81 var i, j, x: integer;
82 y: Frigider;
83 begin
84 i := Stanga; j := Dreapta; x := r[(Stanga+Dreapta) DIV 2].max;
85 repeat
86 while r[i].max > x do i := i + 1;
87 while x > r[j].max do j := j - 1;
88 if i <= j then
89 begin
90 y := r[i]; r[i] := r[j]; r[j] := y;
91 i := i + 1; j := j - 1;
92 end;
93 until i > j;
94 if Stanga < j then Sort(Stanga, j);
95 if i < Dreapta then Sort(i, Dreapta);
96 end;
97
98 begin {QuickSort};
99 Sort(Lo,Hi);
100 end;
101
102 Begin
103 Assign(Fisier, ’reactivi.in’); Reset(Fisier);
104 ReadLn(Fisier, N);
105 For i := 1 To N Do
106 ReadLn(Fisier, r[i].min, r[i].max);
107 Close(Fisier);
108
109 QuickSortCresc(1, N);
110
111 i := 1; j := i+1;
112 While j<=N Do
113 Begin
114 While (r[j].min=r[i].min) And (j<=N) Do Inc(j);
115 QuickSortDescresc(i, j-1);
116 i := j; j := i+1
117 End;
118
119 f[1].min := r[1].min; f[1].max := r[1].max; Cate := 1;
120 For i := 2 To N Do
121 Begin
122 min := r[i].min; max := r[i].max;
123 j := Cauta(min, max);
124 If j>0 Then
125 Intersectie(j, min, max)
126 Else
127 Begin
128 Inc(Cate);
129 f[Cate].min := min;
CAPITOLUL 19. OJI 2004 19.2. REACTIVI 452

130 f[Cate].max := max


131 End
132 End;
133 Assign(Fisier, ’reactivi.out’); ReWrite(Fisier);
134 WriteLn(Fisier, Cate);
135 Close(Fisier)
136 End.

Listing 19.2.2: REACTIVI.C


#include <stdio.h>

struct Frigider
{
char min;
char max;
} f[10001], r[10001];

int Cate, i, j, N;
char min, max;

FILE* Fisier;

char Maxim(char x, char y)


{
if (x>y) return x;
else return y;
}

char Minim(char x, char y)


{
if (x<y) return x;
else return y;
}

int Cauta(char min, char max)


{
int i, Unde;
for (i=1; i<= Cate; i++)
{
if ((f[i].max>=min) && (f[i].max<=max)) Unde=i;//am gasit frigiderul i
if ((f[i].min>=min) && (f[i].min<=max)) Unde=i;//am gasit frigiderul i
if ((f[i].min<=min) && (f[i].max>=max)) Unde=i;//am gasit frigiderul i
if ((f[i].max<min) || (f[i].min>max)) Unde=-1; //nu am gasit frigider
}
return Unde;
}

void Intersectie(int j, char min, char max)


{
f[j].min=Maxim(min, f[j].min);//modific temperatura minima din frigider
f[j].max=Minim(max, f[j].max);//modific temperatura maxima din frigider
}

void Ordoneaza(void)
{ //sortare prin selectie
int i, j, ptmin;
char tmax, tmin;
for (i=1; i<=N-1; i++)
{
tmin=r[i].min; tmax=r[i].max; ptmin=i;
for (j=i+1; j<=N; j++)
if (r[j].min<tmin) //crescator dupa temperatura minima
{ tmax=r[j].max; tmin=r[j].min; ptmin=j; }
else
if (r[j].min==tmin)//pentru temperatura minima egala
if (r[j].max>tmax) //descrescator dupa cea maxima
{ tmax=r[j].max; tmin=r[j].min; ptmin=j; }
r[ptmin].min=r[i].min; r[ptmin].max=r[i].max;
r[i].min=tmin; r[i].max=tmax;
}
}

int main()
{
CAPITOLUL 19. OJI 2004 19.2. REACTIVI 453

char x, y;
Fisier=fopen("reactivi.in", "rt");
fscanf(Fisier, "%d", &N);
for (i=1; i<=N; i++)
fscanf(Fisier, "%d %d", &r[i].min, &r[i].max, &x, &y);
Ordoneaza(); //ordonez intervalele de temperatura
//crescator dupa temperatura minima si
//descrescator dupa temperatura maxima

//pun primul reactiv (deci cel cu intervalul cel mai mare)


//in primul frigider
f[1].min=r[1].min; f[1].max=r[1].max; Cate=1;
for (i=2; i<= N; i++) //pentru toate celelalte
{
min=r[i].min; max=r[i].max;
j=Cauta(min, max); //caut un frigider in care a mai fost pus ceva
if (j>0) //daca gasesc
Intersectie(j, min, max);//ajustez temperatura din frigider
else //altfel
{
Cate++; //"deschid" un frigider nou
f[Cate].min=min; //si pun aici reactivul
f[Cate].max=max;
}
}

fclose(Fisier);

Fisier=fopen("reactivi.out", "wt");
fprintf(Fisier, "%d\n", Cate);
fclose(Fisier);

return 0;
}

Listing 19.2.3: reactivp.pas


1 Program Reactivi;
2
3 Type Frigider = Record
4 min, max: ShortInt
5 End;
6 Var f : Array[1..10000] Of Frigider;
7 r : Array[1..10000] Of Frigider;
8 Fisier : Text;
9 Cate, i, j, N: Integer;
10 min, max : ShortInt;
11
12 Function Maxim(x, y: ShortInt): ShortInt;
13 Begin
14 If x>y Then
15 Maxim := x
16 Else
17 Maxim := y
18 End;
19
20 Function Minim(x, y: ShortInt): ShortInt;
21 Begin
22 If x<y Then
23 Minim := x
24 Else
25 Minim := y
26 End;
27
28 Function Cauta(min, max: ShortInt): Integer;
29 Var i: Integer;
30 Begin
31 Cauta := -1;
32 For i := 1 To Cate Do
33 Begin
34 If (f[i].max>=min) And (f[i].max<=max) Then
35 Begin Cauta := i; Break End
36 Else
37 If (f[i].min>=min) And (f[i].min<=max) Then
38 Begin Cauta := i; Break End
CAPITOLUL 19. OJI 2004 19.2. REACTIVI 454

39 Else
40 If (f[i].min<=min) And (f[i].max>=max) Then
41 Begin Cauta := i; Break End
42 Else
43 If (f[i].max<min) Or (f[i].min>max) Then
44 Cauta := -1
45 End;
46 End;
47
48 Procedure Intersectie(j: Integer; min, max: ShortInt);
49 Begin
50 f[j].min := Maxim(min, f[j].min);
51 f[j].max := Minim(max, f[j].max)
52 End;
53
54 Procedure Ordoneaza;
55 Var i, j, ptmin: Integer;
56 tmax, tmin: ShortInt;
57 Begin
58 For i := 1 To N-1 Do
59 Begin
60 tmin := r[i].min; tmax := r[i].max; ptmin := i;
61 For j := i+1 To N Do
62 If r[j].min<tmin Then
63 Begin
64 tmax := r[j].max; tmin := r[j].min; ptmin := j
65 End
66 Else
67 If r[j].min=tmin Then
68 If r[j].max>tmax Then
69 Begin
70 tmax := r[j].max; tmin := r[j].min; ptmin := j
71 End;
72 r[ptmin].min := r[i].min; r[ptmin].max := r[i].max;
73 r[i].min := tmin; r[i].max := tmax
74 End
75 End;
76
77
78 Begin
79 Assign(Fisier, ’reactivi.in’); Reset(Fisier);
80 ReadLn(Fisier, N);
81 For i := 1 To N Do
82 ReadLn(Fisier, r[i].min, r[i].max);
83 Ordoneaza;
84 f[1].min := r[1].min; f[1].max := r[1].max; Cate := 1;
85 For i := 2 To N Do
86 Begin
87 min := r[i].min; max := r[i].max;
88 j := Cauta(min, max);
89 If j>0 Then
90 Intersectie(j, min, max)
91 Else
92 Begin
93 Inc(Cate);
94 f[Cate].min := min;
95 f[Cate].max := max
96 End
97 End;
98 Close(Fisier);
99 Assign(Fisier, ’reactivi.out’); ReWrite(Fisier);
100 WriteLn(Fisier, Cate);
101 Close(Fisier)
102 End.

19.2.3 Rezolvare detaliată

Listing 19.2.4: reactivi.java


1 import java.io.*;
2 class Reactivi
3 {
CAPITOLUL 19. OJI 2004 19.2. REACTIVI 455

4 static int n; // n=nr reactivi


5 static int ni=0; // ni=nr interschimbari in quickSort
6 static int nf=0; // n=nr frigidere
7 static int[] ngf; // ng=nr grade in frigider
8 static int[] x1,x2; // limite inferioara/superioara
9
10 public static void main(String[] args) throws IOException
11 {
12 int i,j;
13 StreamTokenizer st=new StreamTokenizer(
14 new BufferedReader(new FileReader("Reactivi.in")));
15 PrintWriter out=new PrintWriter(
16 new BufferedWriter(new FileWriter("Reactivi.out")));
17 st.nextToken(); n=(int)st.nval;
18 x1=new int[n+1];
19 x2=new int[n+1];
20 ngf=new int[n+1];
21 for(i=1;i<=n;i++)
22 {
23 st.nextToken(); x1[i]=(int)st.nval;
24 st.nextToken(); x2[i]=(int)st.nval;
25 }
26 sol();
27 out.println(nf);
28 out.close();
29 }// main
30
31 static void sol()
32 {
33 int i;
34 quickSort(1,n);
35 i=1; nf=1; ngf[nf]=x2[i];
36 i++;
37 while(i<n)
38 {
39 while((i<=n) && (x1[i]<=ngf[nf])) i++;
40 if(i<n) ngf[++nf]=x2[i++];
41 }
42 }
43
44 static void quickSort(int p, int u)
45 {
46 int i,j,aux;
47 i=p; j=u;
48 while(i<j)
49 {
50 while((i<j) && ((x2[i]<x2[j])||
51 ((x2[i]==x2[j]) && (x1[i]>=x1[j])))) i++;
52 if(i!=j)
53 {
54 aux=x1[i]; x1[i]=x1[j]; x1[j]=aux;
55 aux=x2[i]; x2[i]=x2[j]; x2[j]=aux;
56 }
57 while((i<j) && ((x2[i]<x2[j])||
58 ((x2[i]==x2[j]) && (x1[i]>=x1[j])))) j--;
59 if(i!=j)
60 {
61 aux=x1[i]; x1[i]=x1[j]; x1[j]=aux;
62 aux=x2[i]; x2[i]=x2[j]; x2[j]=aux;
63 }
64 }
65 if(p<i-1) quickSort(p,i-1);
66 if(i+1<u) quickSort(i+1,u);
67 }
68 }
Capitolul 20

OJI 2003

20.1 Text

Vasile lucrează intens la un editor de texte. Un text este format din unul sau mai multe
paragrafe. Orice paragraf se termină cu Enter şi oricare două cuvinte consecutive din acelaşi
paragraf sunt separate prin spaţii (unul sau mai multe). În funcţie de modul de setare a paginii,
numărul maxim de caractere care ı̂ncap ı̂n pagină pe o linie este unic determinat (Max).
Funcţia pe care Vasile trebuie să o implementeze acum este alinierea ı̂n pagină a fiecărui
paragraf din text la stânga şi la dreapta. Pentru aceasta el va trebui să ı̂mpartă fiecare paragraf
ı̂n linii separate de lungime Max (fiecare linie terminată cu Enter).
Împărţirea se realizează punând numărul maxim posibil de cuvinte pe fiecare linie, fără
ı̂mpărţirea cuvintelor ı̂n silabe.
Pentru aliniere stânga-dreapta, Vasile trebuie să repartizeze spaţii ı̂n mod uniform ı̂ntre cu-
vintele de pe fiecare linie, astfel ı̂ncât ultimul caracter de pe linie să fie diferit de spaţiu, iar
numărul total de caractere de pe linie să fie egal cu Max. Excepţie face numai ultima linie din
paragraf, care rămâne aliniată la stânga (cuvintele fiind separate printr-un singur spaţiu, chiar
dacă linia nu este plină).
În general, este puţin probabil ca alinierea să fie realizabilă prin plasarea aceluiaşi număr de
spaţii ı̂ntre oricare două cuvinte consecutive de pe linie. Vasile consideră că este mai elegant ca,
dacă ı̂ntre unele cuvinte consecutive trebuie plasat un spaţiu ı̂n plus faţă de alte perechi de cuvinte
consecutive, acestea să fie plasate la ı̂nceputul liniei.
Cerinţă
Scrieţi un program care să citească lungimea unei linii şi textul dat şi care să alinieze textul
la stânga şi la dreapta.
Date de intrare
Fişierul de intrare text.in conţine pe prima linie Max, lungimea maximă a unui rând. Pe
următoarele linii este scris textul.
Date de ieşire
Fişierul de ieşire text.out conţine textul aliniat stânga-dreapta.
Restricţii şi precizŭri
a 2 & Max & 1000.
a Lungimea maximă a oricărui cuvânt din text este 25 caractere şi nu depăşeşte Max.
a Lungimea unui paragraf nu depăşeşte 1000 de caractere.
a Soluţia este unică.

Exemple
text.in text.out
20 Vasile are multe
Vasile are multe bomboane bune. bomboane bune.
Explicaţie
Pe prima linie au fost plasate câte 3 spaţii ı̂ntre cuvintele consecutive.

456
CAPITOLUL 20. OJI 2003 20.1. TEXT 457

text.in text.out
20 Ana are mere.
Ana are mere. Ion are multe pere
Ion are multe pere galbene? galbene?
Explicaţie
Între Ion şi are există 2 spaţii, ı̂ntre are şi multe - 2 spaţii, iar ı̂ntre multe şi pere - 1 spaţiu.
Observaţi că paragraful Ana are mere. (care are lungimea mai mică decât 20) a rămas aliniat
la stânga, iar ultima linie din fiecare paragraf rămâne aliniată la stânga, cuvintele consecutive
fiind separate printr-un singur spaţiu.
Timp maxim de executare: 1 secundă/test.

20.1.1 Indicaţii de rezolvare

Fiecare paragraf se preia ı̂ntr-un vector de string-uri, elementele vectorului conţinând cuvintele
din paragraf. Se parcurge acest vector, ı̂ncepând cu prima poziţie, determinând cel mai mare
indice i1 care permite plasarea cuvintelor de pe poziţiile 1, ..., i1 pe acelaşi rând. Se destribuie
spaţiile disponibile, conform cerinţei problemei şi se afişează această linie. Se continuă prelucrarea
vectorului ı̂ncepând cu poziţia i1  1, şi aşa mai departe!

20.1.2 Cod sursă

Listing 20.1.1: TEXT.CPP


#include <fstream>
#include <string.h>

using namespace std;

#define InFile "text.in"


#define OutFile "text.out"
#define NMax 1001
#define LgMax 26

char prg[NMax];

ifstream fin(InFile);
ofstream fout(OutFile);

char cuv[NMax][LgMax];
int Max, nrl, LgLinie;
void afiseaza_paragraf(void);
void afiseaza_linie(int);

int main()
{
fin>>Max;
fin.get();

int gata=0;
while (!gata)
{
fin.getline(prg,NMax);
if (!fin.good())
gata=1;
else
afiseaza_paragraf();
}

fin.close();
fout.close();
return 0;
}

void afiseaza_paragraf()
{
char *p;
int LgCuv;
CAPITOLUL 20. OJI 2003 20.1. TEXT 458

LgLinie=-1;
nrl=0;
p=strtok(prg," ");

while (p)
{
LgCuv=strlen(p);
if (LgLinie+LgCuv+1<=Max)
{
strcpy(cuv[nrl],p);
nrl++;
LgLinie+=LgCuv+1;
}
else
{
afiseaza_linie(0);
nrl=1;
LgLinie=LgCuv;
strcpy(cuv[0],p);
}

p=strtok(NULL," ");
}

afiseaza_linie(1);
}

void afiseaza_linie(int u)
{
int i, j, cate, rest, nrsp;

if (u)
for (i=0; i<nrl-1; i++)
fout<<cuv[i]<<’ ’;
else
{
cate=Max-LgLinie;
rest=cate%(nrl-1);
nrsp=cate/(nrl-1);

for (i=0; i<nrl-1;i++)


{
fout<<cuv[i];
for (j=0; j<=nrsp;j++)
fout<<’ ’;
if (rest)
{
fout<<’ ’;
rest--;
}
}
}

if (nrl>0)
fout<<cuv[nrl-1]<<endl;
else
fout<<endl;
}

20.1.3 Rezolvare detaliată

21
E cerul sus
Ca niste-ntinse brate
N-au crengile de ce sa se agate
0000:0000 32 31 0D 0A 45 20 63 65 72 75 6C 20 73 75 73 0D
0000:0010 0A 43 61 20 6E 69 73 74 65 2D 6E 74 69 6E 73 65
0000:0020 20 62 72 61 74 65 0D 0A 4E 2D 61 75 20 63 72 65
0000:0030 6E 67 69 6C 65 20 64 65 20 63 65 20 73 61 20 73
0000:0040 65 20 61 67 61 74 65 0D 0A 1A
CAPITOLUL 20. OJI 2003 20.1. TEXT 459

Sfârşitul de fişier (EOF) este marcat prin 1A.


Sfârşitul de linie (EOL) este marcat prin 0D0A.

Listing 20.1.2: text.java


1 import java.io.*;
2 class Text
3 {
4 static int Max, nc;
5 static String[] s=new String[501]; // cuvintele din paragraf
6 static int[] lgc=new int[501]; // numarul caracterelor cuvintelor
7 static int[] nsp=new int[501]; // numarul spatiilor dupa cuvant
8 static int cs=0, cd=0; // cs=cuvant stanga, cd=cuvant
dreapta
9 static int lglin=0;
10 static PrintWriter out;
11
12 public static void main(String[] args) throws IOException
13 {
14 out=new PrintWriter(new BufferedWriter(new FileWriter("text.out")));
15 StreamTokenizer st=new StreamTokenizer(
16 new BufferedReader(new FileReader("text.in")));
17 st.eolIsSignificant(true);
18
19 st.nextToken(); Max=(int)st.nval;
20 st.nextToken(); // preia EOL-ul existent dupa Max
21 while(st.nextToken()!=StreamTokenizer.TT_EOF)
22 {
23 nc=0;
24 do
25 {
26 s[++nc]=st.sval.toString();
27 } while(st.nextToken()!=StreamTokenizer.TT_EOL);
28 rezolva();
29 }
30 out.close();
31 }
32
33 static void rezolva() throws IOException
34 {
35 cs=0; // primul cuvant din linie (din stanga)
36 cd=0; // ultimul cuvant din linie (din stanga)
37 while(cd<nc)
38 {
39 cs=cd+1;
40 cd=cs;
41 lglin=s[cs].length();
42 while((cd+1<=nc)&&(lglin+1+s[cd+1].length()<=Max))
43 {
44 cd++;
45 lglin=lglin+1+s[cd].length();
46 }
47 if(cd<nc) unRand(); else ultimulRand();
48 }
49 }
50
51 static void unRand() throws IOException
52 {
53 int i,j;
54 int ntsr; // ntsr=nr total de spatii ramase de distribuit
55 int nsr; // nsr=nr spatii ramase de distribuit pentru primele cuvinte
56 int nsf; // nr spatii de adaugat dupa fiecare cuvant cs ... cd-1
57
58 ntsr=Max-lglin;
59 if(cs!=cd)
60 {
61 nsf=ntsr/(cd-cs);
62 nsr=ntsr%(cd-cs);
63 for(i=cs;i<cd;i++) nsp[i]=1+nsf;
64 for(i=1;i<=nsr;i++) nsp[cs+i-1]++;
65 for(i=cs;i<=cd-1;i++)
66 {
67 out.print(s[i]);
68 for(j=1;j<=nsp[i];j++) out.print(" ");
69 }
70 out.println(s[cd]);
CAPITOLUL 20. OJI 2003 20.2. NUMERE 460

71 }
72 }
73
74 static void ultimulRand() throws IOException
75 {
76 int i;
77 out.print(s[cs]);
78 for(i=cs+1;i<=cd;i++) out.print(" "+s[i]);
79 out.println();
80 }
81 }

20.2 Numere

Gigel este un mare pasionat al cifrelor. Orice moment liber şi-l petrece jucându-se cu numere.
Jucându-se astfel, ı̂ntr-o zi a scris pe hârtie 10 numere distincte de câte două cifre şi a observat
că printre acestea există două submulţimi disjuncte de sumă egală.
Desigur, Gigel a crezut că este o ı̂ntâmplare şi a scris alte 10 numere distincte de câte două
cifre şi spre surpriza lui, după un timp a găsit din nou două submulţimi disjuncte de sumă egală.
Cerinţă
Date 10 numere distincte de câte două cifre, determinaţi numărul de perechi de submulţimi
disjuncte de sumă egală care se pot forma cu numere din cele date, precum şi una dintre aceste
perechi pentru care suma numerelor din fiecare dintre cele două submulţimi este maximă.
Date de intrare
Fişierul de intrare numere.in conţine pe prima linie 10 numere naturale distincte separate
prin câte un spaţiu x1 x2 ... x10 .
Date de ieşire
Fişierul de ieşire numere.out conţine trei linii. Pe prima linie se află numărul de perechi de
submulţimi de sumă egală, precum şi suma maximă obţinută, separate printr-un spaţiu. Pe linia
a doua se află elementele primei submulţimi, iar pe linia a treia se află elementele celei de a doua
submulţimi, separate prin câte un spaţiu.
NrSol Smax NrSol - numărul de perechi; Smax - suma maximă
x1 ... xk elementele primei submulţimi
y1 ... yp elementele celei de a doua submulţimi
Restricţii şi precizări
a 10 & xi , yi & 99, pentru 1 & i & 10.
a 1 & k, p & 9.
a Ordinea submulţimilor ı̂n perechi nu contează.
a Perechea de submulţimi determinată nu este obligatoriu unică.

Exemplu
numere.in numere.out
60 49 86 78 23 97 69 71 32 10 130 276
78 97 69 32
60 49 86 71 10
Explicaţie:
130 de soluţii; suma maximă este 276; s-au folosit 9 din cele 10 numere; prima submulţime are
4 elemente, a doua are 5 elemente.
Timp maxim de executare: 1 secundă/test

20.2.1 Indicaţii de rezolvare

Numărul mic al numerelor (numai 10 numere distincte) permite generarea tuturor


submulţimilor, verificarea condiţiilor din problemă pentru fiecare pereche de submulţimi şi de-
terminarea informaţiilor cerute.
CAPITOLUL 20. OJI 2003 20.2. NUMERE 461

20.2.2 Cod sursă

Listing 20.2.1: numere.cpp


#include <fstream>
#include <stdlib.h>

using namespace std;

int a[11], s1max[11], s2max[11], solutii=0, Smax=0, UzMax=0;

ifstream f("numere.in");
ofstream g("numere.out");

void citire()
{
int i;
for (i=1; i<=10; i++) f>>a[i];
f.close();
}

void rezolva()
{
int vi[11]={0,0,0,0,0,0,0,0,0,0,0}, vj[11]={0,0,0,0,0,0,0,0,0,0,0};
int i, ii, j, k, x, gasit, si, sj, jj, disjuncti;
int cati1;

for(ii=1; ii<=1022; ii++)


{
i=10;
while(vi[i]) vi[i--]=0; //adun 1 la vi
vi[i]=1;

si=0;
for (i=1; i<=10; i++)
if (vi[i]) si+=a[i];
for (i=1; i<=10; i++) vj[i]=vi[i];
for (jj=ii+1; jj<=1022; jj++)
{
j=10;
while(vj[j]) vj[j--]=0; //adun 1 la vj
vj[j]=1;

sj=0;
for(j=1; j<=10; j++)
if(vj[j])
sj+=a[j];

if(si==sj)
{
disjuncti=1;

for(k=1; k<=10; k++)


if((vi[k]+vj[k])==2)
disjuncti=0;

if(disjuncti)
{
solutii++;
cati1=0;
for (i=1; i<=10; i++)
cati1+=(vi[i]+vj[i]);

if (cati1>UzMax)
if (si>Smax)
{
UzMax=cati1;
Smax=si;
for (i=1; i<=10; i++)
s1max[i]=vi[i];
for (i=1; i<=10; i++)
s2max[i]=vj[i];
}
}
CAPITOLUL 20. OJI 2003 20.2. NUMERE 462

}
}
}
}

void afisare()
{
int i;

g<<solutii<<" "<<Smax<<endl;

for(i=1; i<=10; i++)


if (s1max[i])
g<<a[i]<<’ ’;
g<<endl;

for(i=1; i<=10; i++)


if (s2max[i])
g<<a[i]<<’ ’;
g<<endl;
g.close();
}

int main()
{
citire();
rezolva();
afisare();
return 0;
}

20.2.3 Rezolvare detaliată

Listing 20.2.2: numere.java


1 import java.io.*;
2 class Numere
3 {
4 static int[] x=new int[10];
5 static PrintWriter out;
6
7 public static void main(String[] args) throws IOException
8 {
9 int i, ia, ib, nsol=0, smax=-1, iamax=123,ibmax=123, sumaia=-1;
10 long t1,t2;
11 t1=System.currentTimeMillis();
12 StreamTokenizer st=new StreamTokenizer(
13 new BufferedReader(new FileReader("numere.in")));
14 out=new PrintWriter(new BufferedWriter(new FileWriter("numere.out")));
15 for(i=0;i<10;i++) {st.nextToken(); x[i]=(int)st.nval;}
16 for(ia=1;ia<=1022;ia++)
17 for(ib=ia+1;ib<=1022;ib++) // fara ordine intre A si B
18 if((ia&ib)==0)
19 {
20 sumaia=suma(ia);
21 if(sumaia==suma(ib))
22 {
23 nsol++;
24 if(sumaia>smax)
25 {
26 smax=sumaia;
27 iamax=ia;
28 ibmax=ib;
29 }
30 }
31 }
32 out.println(nsol+" "+smax);
33 afis(iamax);
34 afis(ibmax);
35 out.close();
36 t2=System.currentTimeMillis();
37 System.out.println(t2-t1);
CAPITOLUL 20. OJI 2003 20.2. NUMERE 463

38 }// main
39
40 static int suma(int i)
41 {
42 int s=0,k=0;
43 for(k=0;k<=9;k++) if( (i&(1<<k)) != 0 ) s+=x[k];
44 return s;
45 }
46
47 static void afis(int i)
48 {
49 int k=0;
50 while(i!=0)
51 {
52 if(i%2!=0) out.print(x[k]+" ");
53 k++;
54 i/=2;
55 }
56 out.println();
57 }
58 }// class
Capitolul 21

OJI 2002

21.1 Poarta

Se consideră harta universului ca fiind o matrice cu 250 de linii şi 250 de coloane. În fiecare
celulă se găseşte o aşa numită poartă stelară, iar ı̂n anumite celule se găsesc echipaje ale porţii
stelare.
La o deplasare, un echipaj se poate deplasa din locul ı̂n care se află ı̂n oricare alt loc ı̂n care
se găseşte o a doua poartă, ı̂n cazul nostru ı̂n orice altă poziţie din matrice.
Nu se permite situarea simultană a mai mult de un echipaj ı̂ntr-o celulă. La un moment dat
un singur echipaj se poate deplasa de la o poartă stelară la alta.
Cerinţă
Dându-se un număr p (1 $ p $ 5000) de echipaje, pentru fiecare echipaj fiind precizate poziţia
iniţială şi poziţia finală, determinaţi numărul minim de deplasări necesare pentru ca toate echipa-
jele să ajungă din poziţia iniţială ı̂n cea finală.
Datele de intrare
Se citesc din fişierul text poarta.in ı̂n următorul format:
 pe prima linie numărul natural p reprezentând numărul de echipaje,
 pe următoarele p linii câte 4 numere naturale, primele două reprezentând coordonatele
poziţiei iniţiale a unui echipaj (linie coloană), următoarele două reprezentând coordonatele poziţiei
finale a aceluiaşi echipaj (linie coloană).
Datele de ieşire
Pe prima linie a fişierului text poarta.out se scrie un singur număr reprezentând numărul
minim de deplasări necesar.
Restricţii şi precizări
 coordonatele poziţiilor iniţiale şi finale ale echipajelor sunt numere naturale din intervalul
1, 250;
 poziţiile iniţiale ale celor p echipaje sunt distincte două câte două;
 poziţiile finale ale celor p echipaje sunt distincte două câte două.

Exemplu
poarta.in poarta.out
3 4
1234
6539
3412

464
CAPITOLUL 21. OJI 2002 21.1. POARTA 465

Figura 21.1: Poarta

Timp maxim de executare: 1 secundă/test

21.1.1 Indicaţii de rezolvare

Fie NrStationare numărul echipajelor staţionare (care au poziţiile iniţiale şi finale egale) şi
NrCircuite numărul circuitelor grafului orientat format astfel: nodurile sunt echipajele şi există
arc de la echipajul i la echipajul j dacă şi numai dacă poziţia finală a echipajului i coincide cu
poziţia iniţială a echipajului j.
Atunci NrMinDeplasari=p+NrCircuite-NrStationare.

21.1.2 *Cod sursă

21.1.3 Rezolvare detaliată

Listing 21.1.1: poarta.java


1 import java.io.*;
2 class Poarta
3 {
4 static int p,nmd,nc=0,ns=0;
5 static int[] xi,yi,xf,yf;
6 static boolean[] ea; // EsteAnalizat deja
7
8 public static void main(String[] args) throws IOException
9 {
10 StreamTokenizer st=new StreamTokenizer(
11 new BufferedReader(new FileReader("poarta.in")));
12
13 st.nextToken(); p=(int)st.nval;
14 xi=new int[p+1];
15 yi=new int[p+1];
16 xf=new int[p+1];
17 yf=new int[p+1];
18 ea=new boolean[p+1]; // implicit este false
19 int i;
20 for(i=1;i<=p;i++)
21 {
22 st.nextToken(); xi[i]=(int)st.nval;
23 st.nextToken(); yi[i]=(int)st.nval;
24 st.nextToken(); xf[i]=(int)st.nval;
25 st.nextToken(); yf[i]=(int)st.nval;
26 }
27
28 for(i=1;i<=p;i++)
29 {
30 if(ea[i]) continue;
31 if((xf[i]==xi[i])&&(yf[i]==yi[i])) { ea[i]=true; ns++;}
32 else if(circuit(i)) nc++;
33 }
34 PrintWriter out=new PrintWriter(
35 new BufferedWriter(new FileWriter("poarta.out")));
36 nmd=p+nc-ns;
CAPITOLUL 21. OJI 2002 21.2. MOUSE 466

37 System.out.println(p+" "+nc+" "+ns+" "+nmd);


38 out.print(nmd);
39 out.close();
40 }
41
42 static boolean circuit(int i)
43 {
44 int j=succesor(i);
45 while((j!=-1)&&(j!=i))
46 {
47 ea[j]=true;
48 j=succesor(j);
49 }
50 if(j==i) return true; else return false;
51 }
52
53 static int succesor(int j) // j --> k
54 {
55 int k;
56 for(k=1;k<=p;k++)
57 if((xf[j]==xi[k])&&(yf[j]==yi[k])) return k;
58 return -1;
59 }
60 }

21.2 Mouse

Un experiment urmăreşte comportarea unui şoricel pus ı̂ntr-o cutie dreptunghiulară, ı̂mpărţită
ı̂n m  n cămăruţe egale de formă pătrată. Fiecare cămăruţă conţine o anumită cantitate de hrană.
Şoricelul trebuie să pornească din colţul 1, 1 al cutiei şi să ajungă ı̂n colţul opus, mâncând cât
mai multă hrană. El poate trece dintr-o cameră ı̂n una alăturată (două camere sunt alăturate dacă
au un perete comun), mănâncă toată hrana din cămăruţă atunci când intră şi nu intră niciodată
ı̂ntr-o cameră fără hrană.
Cerinţă
Stabiliţi care este cantitatea maximă de hrană pe care o poate mânca şi traseul pe care ı̂l poate
urma pentru a culege această cantitate maximă.
Datele de intrare
Fişierul de intrare mouse.in conţine pe prima linie două numere m şi n reprezentând numărul
de linii respectiv numărul de coloane ale cutiei, iar pe următoarele m linii cele m  n numere
reprezentând cantitatea de hrană existentă ı̂n fiecare cămăruţă, câte n numere pe fiecare linie,
separate prin spaţii. Toate valorile din fişier sunt numere naturale ı̂ntre 1 şi 100.
Datele de ieşire
În fişierul de ieşire mouse.out se vor scrie
a pe prima linie două numere separate printr-un spaţiu: numărul de cămăruţe vizitate şi
cantitatea de hrană maximă culeasă;
a pe următoarele linii un traseu posibil pentru cantitatea dată, sub formă de perechi de numere
(linie coloană) ı̂ncepând cu 1 1 şi terminând cu m n.
Exemplu

mouse.in mouse.out
24 7 21
1263 11
3412 21
22
12
13
14
24
Explicaţie
CAPITOLUL 21. OJI 2002 21.2. MOUSE 467

Figura 21.2: Mouse

Timp maxim de executare: 1 secundă/test

21.2.1 Indicaţii de rezolvare

Dacă m şi n sunt pare atunci numărul de cămăruţe vizitate este mn  1 iar cantitatea de hrană
maximă culeasă este suma cantităţilor de hrană din toate cămăruţele cu excepţia celei care are
cea mai mică cantitate şi se află pe linia i şi coloana j şi i  j este număr impar. Traseul este
determinat de o parcurgere pe verticală sau orizontală şi ocolirea acelei cămăruţe.
Dacă m este impar atunci numărul de cămăruţe vizitate este mn iar cantitatea de hrană
maximă culeasă este suma cantităţilor de hrană din toate cămăruţele. Traseul este determinat de
o parcurgere pe orizontală.
Analog pentru situaţia ı̂n care n este impar.

21.2.2 *Cod sursă

21.2.3 Rezolvare detaliată

Listing 21.2.1: mouce.java


1 import java.io.*;
2 class Mouse
3 {
4 static int m,n,imin,jmin,min,s;
5 static int [][]a;
6 static PrintWriter out;
7
8 public static void main(String[] args) throws IOException
9 {
10 int i,j;
11 StreamTokenizer st=new StreamTokenizer(
12 new BufferedReader(new FileReader("mouse.in")));
13 out=new PrintWriter(new BufferedWriter(new FileWriter("mouse.out")));
14
15 st.nextToken();m=(int)st.nval;
16 st.nextToken();n=(int)st.nval;
17 a=new int[m+1][n+1];
18
19 for(i=1;i<=m;i++)
20 for(j=1;j<=n;j++) {st.nextToken(); a[i][j]=(int)st.nval;}
21
22 s=0;
23 for(i=1;i<=m;i++)
24 for(j=1;j<=n;j++) s=s+a[i][j];
25
26 if(m%2==1) mimpar();
27 else if(n%2==1) nimpar();
28 else mnpare();
29 out.close();
30 }//main
31
32 static void mimpar()
33 {
34 int i,j;
35 out.println(m*n+" "+s);
CAPITOLUL 21. OJI 2002 21.2. MOUSE 468

36 i=1;
37 while(i+1<m)
38 {
39 for(j=1;j<=n;j++) out.println(i+" "+j);
40 i++;
41 for(j=n;j>=1;j--) out.println(i+" "+j);
42 i++;
43 }
44 for(j=1;j<=n;j++) out.println(m+" "+j);
45 }
46
47 static void nimpar()
48 {
49 int i,j;
50 j=1;
51 out.println(m*n+" "+s);
52 while(j+1<n)
53 {
54 for(i=1;i<=m;i++) out.println(i+" "+j);
55 j++;
56 for(i=m;i>=1;i--) out.println(i+" "+j);
57 j++;
58 }
59 for(i=1;i<=m;i++) out.println(i+" "+n);
60 }
61
62 static void mnpare()
63 {
64 int i,j;
65 imin=0;jmin=0;min=101;
66 for(i=1;i<=m;i++)
67 for(j=1;j<=n;j++)
68 if((i+j)%2==1)
69 if(a[i][j]<min) { min=a[i][j]; imin=i; jmin=j; }
70 out.println((m*n-1)+" "+(s-a[imin][jmin]));
71
72 j=1;
73 while(j+1<jmin) // stanga
74 {
75 for(i=1;i<=m;i++) out.println(i+" "+j);
76 j++;
77 for(i=m;i>=1;i--) out.println(i+" "+j);
78 j++;
79 }
80
81 i=1;
82 while(i+1<imin) // sus
83 {
84 out.println(i+" " +j);
85 out.println(i+" " +(j+1));
86 out.println((i+1)+" " +(j+1));
87 out.println((i+1)+" " +j);
88 i=i+2;
89 }
90
91 out.println(i+" "+j); // patratel
92 if((i==imin)&&(j+1==jmin)) out.println((i+1)+" " +j);
93 else out.println(i+" " +(j+1));
94 out.println((i+1)+" " +(j+1));
95
96 i=i+2;
97 while (i<m) // jos
98 {
99 out.println(i+" " +(j+1));
100 out.println(i+" " +j);
101 out.println((i+1)+" " +j);
102 out.println((i+1)+" " +(j+1));
103 i=i+2;
104 }
105
106 j=j+2;
107 while(j+1<=n) // dreapta
108 {
109 for(i=m;i>=1;i--) out.println(i+" "+j);
110 j++;
111 for(i=1;i<=m;i++) out.println(i+" "+j);
CAPITOLUL 21. OJI 2002 21.2. MOUSE 469

112 j++;
113 }
114 }
115 }//class
470
Appendix A

”Instalare” C++

Ca să putem ”lucra” cu C++ avem nevoie de

ˆ un compilator pentru C++, şi

ˆ un IDE (Integrated Development Enviroment) pentru C++.

A.1 Kit OJI 2017


Poate că cel mai uşor este să se descarce fişierul
http://www.cnlr.ro/resurse/download/Kit_OJI_2017.rar
https://cdn.kilonova.ro/p/WXbRLG/Kit_OJI_2017.rar
http://olimpiada.info/oji2018/Kit_OJI_2017.rar
https://www.liis.ro/Documents/download/Kit_OJI_2017.rar
folosit de către elevi la şcoală şi la olimpiade.

Fişierele din arhivă sunt:

Figura A.1: Fişierele din Kit OJI 2017

Se lansează ı̂n execuţie fişierul OJIkit 2017.exe.

Instalarea este foarte uşoară (este de tipul ”next -> next -> ... -> next”) iar pe in-
ternet sunt multe site-uri de ajutor. De exemplu:

https://www.pbinfo.ro/?pagina=intrebari-afisare&id=26
https://www.youtube.com/watch?v=CLkWRvAwLO8
https://infoas.ro/lectie/112/tutorial-instalare-codeblocks-usor-introducere-in-in
https://www.competentedigitale.ro/c/oji2019/Kit_OJI_2017.rar

Există numeroase alternative la CodeBlocks: Dev-C++, Microsoft Visual Studio, Eclipse,


NetBeans, CodeLite, CLion, KDevelop, etc.

471
APPENDIX A. ”INSTALARE” C++ A.1. KIT OJI 2017 472

Kitul Kit_OJI_2017 se instalează implicit pe C:¯OJI¯


28
t IDE-ul Code::Blocks ,
t compilatorul pentru C: gcc.exe,
29
t compilatorul pentru C++: g++.exe
t make.exe
t gdb.exe
t şi... altele!

La sfârşitul instalării apar pe ecran două link-uri:

Figura A.2: CodeBlocks & C++ Reference

Figura A.3: Ce conţine C:¯ OJI ¯

A.1.1 Code::Blocks
Pentru versiuni mai noi, de Code::Blocks şi compilatoare, se poate accesa site-ul
http://www.codeblocks.org/downloads/binaries
de unde se poate descărca, de exemplu, codeblocks-20.03mingw-setup.exe.
Versiuni mai vechi, dar foarte bune, se pot descărca de la adresa
28
Code::Blocks este un IDE (integrated development environment) pentru C/C++, un fel de Notepad ... mai
sofisticat, cu multe butoane care lansează ı̂n execuţie diverse programe, de exeplu g++.exe
29
g++.exe este compilatorul de C++, programul care va verifica dacă instrucţiunile noastre sunt ok sau nu ...

şi care ne va supăra mereu cu erorile pe care ni le arată ... ... Este “o mică artă” să ı̂nţelegem mesajele de
eroare pe care le vedem pe ecran şi să fim ı̂n stare să “depanăm” programele noastre!
APPENDIX A. ”INSTALARE” C++ A.1. KIT OJI 2017 473

http://www.codeblocks.org/downloads/source/5
Mai precis:
https://sourceforge.net/projects/codeblocks/files/Binaries/20.03/
http://sourceforge.net/projects/codeblocks/files/Binaries/17.12
http://sourceforge.net/projects/codeblocks/files/Binaries/16.01

A.1.2 Folder de lucru

Figura A.4: Folder de lucru

De preferat este să avem cel puţin două partiţii: C:¯, D:¯, ... şi să “lăsăm ı̂n pace” partiţia C:¯
pentru sistemul de operare şi programele instalate!

În figura de mai sus se poate observa că pe D:¯ există un folder de lucru pentru programele ı̂n
C++, folder care se numeşte Programe C++. Aici vom salva toate programele C++ pe care
le vom scrie, eventual organizate pe mai multe subfoldere.

Acum, să intrăm ı̂n folderul Programe C++.

Click-dreapta cu mouse-ul şi selectăm “New -¿ Text document” ca ı̂n figura următoare.
APPENDIX A. ”INSTALARE” C++ A.1. KIT OJI 2017 474

Figura A.5: New -¿ Text document

Figura A.6: Schimbare nume fişier şi nume extensie fişier


APPENDIX A. ”INSTALARE” C++ A.1. KIT OJI 2017 475

Figura A.7: Confirmare schimbare extensie ı̂n .cpp

Figura A.8: Pregătit pentru Code::Blocks

Dacă vom executa două click-uri pe numele fşierului p01.cpp sau un click pentru a marca
fişierul şi apoi un ¡Enter¿, se va declanşa Code::Blocks cu p01.cpp ı̂n fereastra de editare şi, ce
este şi mai important, cu Programe C++ ca folder curent de lucru pentru Code::Blocks.
Adică, aici vor apărea toate fişiere generate de Code::Blocks pentru p01.cpp.

A.1.3 Utilizare Code::Blocks

Figura A.9: Pregătit pentru a scrie cod de program C++ ı̂n Code::Blocks
APPENDIX A. ”INSTALARE” C++ A.1. KIT OJI 2017 476

Figura A.10: Primul cod de program C++ ı̂n Code::Blocks

Figura A.11: Build - compilare


APPENDIX A. ”INSTALARE” C++ A.1. KIT OJI 2017 477

Figura A.12: 0 error(s), 0 warning(s)

Figura A.13: Run - execuţie


APPENDIX A. ”INSTALARE” C++ A.1. KIT OJI 2017 478

Figura A.14: Executat corect: a făcut “nimic”

A.1.4 Setări Code::Blocks


De preferat este să lăsăm setările implicite (sunt stabilite totuşi de nişte specialişti!) dar, dacă
vrem, putem să umblăm şi noi prin setări!

Figura A.15: Settings  % Compiler


APPENDIX A. ”INSTALARE” C++ A.1. KIT OJI 2017 479

Figura A.16: Toolchain executables

Figura A.17: Unde sunt acele programe


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 480

A.1.5 Multe surse ı̂n Code Blocks


Settings Environment: pe calculatorul meu setările sunt:

Figura A.18: Multe surse ı̂n Code Blocks - setări

Dacă avem fişierele p01.cpp, ..., p05.cpp, ı̂n folderul nostru de lucru, şi facem dublu-click pe
fiecare ... vor apărea toate ...

Figura A.19: Multe surse in Code Blocks - exemple

A.2 winlibs
A.2.1 GCC şi MinGW-w64 pentru Windows
Se descarcă de la
http://winlibs.com/#download-release
unul dintre fişierele:
APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 481

ˆ winlibs-x86_64-posix-seh-gcc-10.2.0-llvm-11.0.0-mingw-w64-8.0.0-r
3.7z dimensiune fişier = 148 MB
ˆ winlibs-x86_64-posix-seh-gcc-10.2.0-llvm-11.0.0-mingw-w64-8.0.0-r
3.zip dimensiune fişier = 324 MB

ˆ winlibs-x86_64-posix-seh-gcc-10.2.0-mingw-w64-8.0.0-r3.7z dimensiune
fişier = 52.1 MB
ˆ winlibs-x86_64-posix-seh-gcc-10.2.0-mingw-w64-8.0.0-r3.zip dimensi-
une fişier = 141 MB

Se dezarhivează şi se mută folderul mingw64 pe C: sau D: sau ...


Eu l-am dezarhivat pe cel mai mic şi l-am pus pe D:

Figura A.20: mingw64 pe D:

A.2.2 PATH
Trebuie pusă ı̂n PATH calea pentru D:\mingw64\bin\. În “Type here to search” scrieţi: path
şi apoi click pe “Edit the system environment variables”, ca ı̂n figura următoare:
APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 482

Figura A.21: search path

Apare fereastra “System properties”:

Figura A.22: System properties –¿ Advanced

Fereastra este poziţionată pe tab-ul “Advanced”. Se selectează “Environment Variables”.


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 483

Figura A.23: Environment Variables

Se selecteaza “Path” şi click pe “Edit”. Apare fereastra “Edit Environment Variables”

Figura A.24: Edit Environment Variables –¿ New

Se selectează “New”, se scrie calea D:¯mingw64¯bin şi se finalizează cu click pe “OK”,


“OK”, ..., “OK” până la sfârşit!
Se verifică calea şi versiunea pentru “gcc”:

ˆ C:¯path

ˆ C:¯gcc –version (Atentie! sunt 2 caractere - consecutive)


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 484

Figura A.25: Calea şi versiunea pentru gcc

Dacă totul este OK atunci se trece la instalarea IDE-ului preferat (Integrated Development
30
Environment ), de exemplu Code::Blocks 20.03 (sau Eclipse, Visual Studio Code, Dev C++,
NetBeans, şi altele).

Observatie: Pentru Windows 11

"Setting the path and variables in Windows 11

In the System > About window,


click the Advanced system settings link
at the bottom of the Device specifications section.

In the System Properties window,


click the Advanced tab,
then click the Environment Variables button
near the bottom of that tab."

A.2.3 CodeBlocks
CodeBlocks se poate descărca de la http://www.codeblocks.org/downloads/26. Sunt
mai multe variante dar eu am descărcat numai codeblocks-20.03-setup.exe care are 35.7 MB.
La instalare am lăsat totul implicit, deci ... Next, Next, ..., Next până la sfârşit!
La prima lansare ı̂n execuţie este necesară setarea locaţiei compilatorului de C++ (gcc-ul pe
care tocmai l-am instalat!).
30
https://en.wikipedia.org/wiki/Integrated_development_environment
https://ro.wikipedia.org/wiki/Mediu_de_dezvoltare
APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 485

Figura A.26: Settings –¿ Compiler

Figura A.27: Toolchain executables –¿ Auto-detect


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 486

Figura A.28: New –¿ Text Document

Figura A.29: New text Document.txt

Figura A.30: Schimbare nume şi extensie


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 487

Figura A.31: Moore apps

Figura A.32: Look for another app


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 488

Figura A.33: Cale pentru codeblocks.exe

Figura A.34: Selectare codeblocks.exe


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 489

Figura A.35: Editare test01.cpp

Figura A.36: Compilare test01


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 490

Figura A.37: Mesaje după compilare

Figura A.38: Execuţie test01


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 491

Figura A.39: Rezultat execuţie test01

Figura A.40: Fişiere apărute după compilare!

Figura A.41: Creare test02.cpp gol! + ¡dublu click¿ sau ¡Enter¿


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 492

Figura A.42: Lista programelor de utilizat

Figura A.43: Selectare Code::Blocks IDE pentru fişierele .cpp

Figura A.44: Editare+Compilare+Execuţie pentru test02


APPENDIX A. ”INSTALARE” C++ A.2. WINLIBS 493

Figura A.45: Selectare tab ce conţine test01.cpp


Appendix B

Exponenţiere rapidă

B.1 Analogie baza 2 cu baza 10

Figura B.1: Analogie B2 cu B10

În figura B.1 este considerat numărul nr 234 care ı̂n baza 2 se scrie sub forma 11101010 (celula
C10).
Pe R3 (rândul 3) este arătat ce se ı̂ntâmplă cu nr atunci când se fac ı̂mpărţiri succesive prin
baza 10: se ”şterge” ulima cifră (care este egală cu nr%10 adică ultima cifră este de fapt restul
ı̂mpărţirii lui nr la baza 10).
Pe R10 (rândul 10) este arătat ce se ı̂ntâmplă cu nr atunci când se fac ı̂mpărţiri succesive prin
baza 2: se ”şterge” ulima cifră (care este egală cu nr%2 adică ultima cifră este de fapt restul
ı̂mpărţirii lui nr la baza 2, la fel cum se ı̂ntâmplă ı̂n baza 10).
Observaţia 1. Cifrele se obţin ı̂n ordine inversă. Prima cifră obţinută ca rest este de fapt ultima
cifră din număr (vezi R4 şi R11).
Pe R6 şi R16 sunt precizate puterile bazei.
Valoarea numărului nr este suma produselor dintre cifre şi puterile corespunzătoare:
ˆ ı̂n baza 10: 4*1 + 3*10 + 2*100 = 234 (R3 şi R6)
ˆ ı̂n baza 2: 0*1 + 1*2 + 0*4 + 1*8 + 0*16 + 1*32 + 1*64 + 1*128 = 234 (R13 şi R16)

494
APPENDIX B. EXPONENŢIERE RAPIDĂ B.2. NOTAŢII, RELAŢII ŞI FORMULE 495

B.2 Notaţii, relaţii şi formule


234 1˜1281˜641˜320˜161˜80˜41˜20˜1
a a
1˜128 1˜64 1˜32 0˜16 1˜8 0˜4 1˜2 0˜1
a ˜ a ˜ a ˜ a ˜ a ˜ a ˜ a ˜ a
128 1 64 1 32 1 16 0 8 1 4 0 2 1 1 0
a  ˜ a  ˜ a  ˜ a  ˜ a  ˜ a  ˜ a  ˜ a  .
k
2
Dacă notăm ak a atunci
234 1 1 1 0 1 0 1 0
a a7  ˜ a6  ˜ a5  ˜ a4  ˜ a3  ˜ a2  ˜ a1  ˜ a0  .
2
Şirul a0 , a1 , a2 , ... se obţine uşor prin ridicări la putere ak ak1 .

2 2 2 4 2 8 2 16
a0 a a1 a0 a  a2 a1 a  a3 a2 a  a4 a3 a 
2 32 2 64 2 128
a5 a4 a  a6 a5 a  a7 a6 a 

Dacă notăm ek ”exponentul (puterea)” la care apare ak ı̂n produs, atunci putem observa
uşor că şirul e0 , e1 , e2 , e3 , ... se obţine prin ı̂mpărţiri succesive ale lui n prin 2 şi preluı̂nd restul
rezultatului. Pentru a folmaliza acest lucru vom considera şirul n0 , n1 , n2 , n3 , ... definit astfel:

n0 n,
w
nk nk1 ©2 (câtul ı̂mpărţirii!) k '1
Folosind aceste notaţii, putem scrie:

ek nk %2 , (restul ı̂mpărţirii!) k '0


ek 1 0
Dacă notăm şi produsul parţial pk ak  ˜ ... ˜ a1  ˜ a0 
obţinem ı̂n final relaţiile:

~
„n0 n;
„
„
„
„a0 a;
„
„
„
„
„
„e n0 %2 " r0, 1x;
„ 0
„
„
„ 1 ˜ a0 , dacă e0 1;
„
„p0
e
a00 sau, altfel scris: p0 w
„
„
„
„ 1, dacă e0 0;
„
„
„
‚
„ (B.2.1)
„
„
„
„
„nk nk1 ©2 k ' 1; nk1 j 0 desigur ! ... dar şi nk1 j 1 
„
„
„ ak1 , k ' 1;
2
„
„ak
„
„
„
„
„ek nk %2 " r0, 1x, k ' 0;
„
„
„ pk1 ˜ ak , k ' 0, dacă ek 1; ak ak modifică produsul pk1 
1
„
„
„
„p w
„ k k ' 0, dacă ek 0; ak 1 nu modifică produsul pk1 
0
€ pk1 ,

B.3 Pregătire pentru scrierea codului!


Relaţia nk nk1 ©2 ı̂nseamnă că nnou nvechi ©2 (sunt explicaţii pentru ı̂ncepători ... nu pentru
avansaţi!) şi se poate programa sub forma n=n/2;
2 2
Relaţia ak ak1 ı̂nseamnă că anou avechi şi se poate programa sub forma a=a*a;
Relaţia pk pk1 ˜ ak ı̂nseamnă că pnou pvechi ˜ anou şi se poate programa sub forma p=p*a;
Relaţia pk pk1 ı̂nseamnă că pnou pvechi şi se poate programa sub forma p=p; care
ı̂nseamnă că nu se schimbă p, deci ... mai bine nu mai scriem nicio instrucţiune!
APPENDIX B. EXPONENŢIERE RAPIDĂ B.4. CODUL 496

B.4 Codul
Codul pentru relaţiile (B.2.1) devine:

Listing B.4.1: exponentiere rapida1.cpp


1 #include<iostream> // actualizare p dupa actualizare a si n
2
3 using namespace std;
4
5 int exponentiere_rapida(int a, int n) // p=aˆn
6 {
7 int nk1, nk;
8 int ak1, ak;
9 int ek1, ek;
10 int pk1, pk;
11
12 int k=0;
13 nk1=n;
14 ak1=a;
15 ek1=nk1%2;
16 if(ek1==1)
17 pk1=ak1;
18 else
19 pk1=1;
20
21 while(nk1>1)
22 {
23 k++;
24 nk=nk1/2;
25 ak=ak1*ak1;
26 ek=nk%2;
27 if(ek==1)
28 pk=pk1*ak;
29 else
30 pk=pk1;
31
32 // devin valori "vechi" inainte de noua actualizare
33 nk1=nk;
34 ak1=ak;
35 ek1=ek;
36 pk1=pk;
37 }
38
39 return pk;
40 }
41
42 int main()
43 {
44 int a=2;
45 //int n=234; // aˆn = prea mare !!!
46 //int n=30;
47 //int n=6; // par: 2ˆ6 = 64 ... usor de verificat!
48 int n=5; // impar: 2ˆ5 = 32 ... usor de verificat!
49
50 int rez=exponentiere_rapida(a,n);
51
52 cout<<a<<"ˆ"<<n<<" = "<<rez<<"\n";
53
54 return 0;
55 }
56 /*
57 2ˆ30 = 1073741824
58
59 Process returned 0 (0x0) execution time : 0.076 s
60 Press any key to continue.
61 */

Observaţia 2. În acest cod actualizarea lui p se face după actualizările pentru a şi n.
Observaţia 3. În codul următor actualizarea lui p se face ı̂naintea actualizărilor pentru a şi n,
corespunzător relaţiilor (B.4.2).
APPENDIX B. EXPONENŢIERE RAPIDĂ B.4. CODUL 497

Listing B.4.2: exponentiere rapida2.cpp


1 #include<iostream> // actualizare p inainte de actualizare a si n
2
3 using namespace std;
4
5 int exponentiere_rapida(int a, int n) // p=aˆn
6 {
7 int nk1, nk;
8 int ak1, ak;
9 int ek1, ek;
10 int pk1, pk;
11
12 nk1=n;
13 ak1=a;
14 pk1=1;
15
16 while(nk1>0)
17 {
18 ek=nk1%2;
19 if(ek==1)
20 pk=pk1*ak1;
21 else
22 pk=pk1;
23 ak=ak1*ak1;
24 nk=nk1/2;
25
26 // devin valori "vechi" inainte de noua actualizare
27 nk1=nk;
28 ak1=ak;
29 pk1=pk;
30 }
31
32 return pk;
33 }
34
35 int main()
36 {
37 int a=2;
38 //int n=234; // aˆn = prea mare !!!
39 //int n=30;
40 int n=6; // par: 2ˆ6 = 64 ... usor de verificat!
41 //int n=5; // impar: 2ˆ5 = 32 ... usor de verificat!
42
43 int rez=exponentiere_rapida(a,n);
44
45 cout<<a<<"ˆ"<<n<<" = "<<rez<<"\n";
46
47 return 0;
48 }
49 /*
50 2ˆ30 = 1073741824
51
52 Process returned 0 (0x0) execution time : 0.076 s
53 Press any key to continue.
54 */

~
„iniţializări:
„
„
„
„
„
„p1 1;
„
„
„
„n1 n;
„
„
„
„
„
„a1 a;
„
„
„
„
„calcul pentru k ' 0:
„
„e
‚
„ k nk1 %2 " r0, 1x; k ' 0 (B.4.2)
„
„ pk1 ˜ ak1 , k ' 0, dacă ek 1; ak1
1
„
„ ak1 modifică produsul pk1 
„
„ k w
p
„ k ' 0, dacă ek 0; ak1
0
„
„ pk1 , 1 nu modifică produsul pk1 
„
„
„actualizări k ' 0:
„
„
„
„
„a ak1 , k ' 0;
2
„
„
„
„
k
„nk nk1 ©2 k ' 0; şi nk1 j 0 desigur ! 
„
€
APPENDIX B. EXPONENŢIERE RAPIDĂ B.4. CODUL 498

Observaţia 4. Instrucţiunile care sunt ”ı̂n plus” se pot elimina. Codul următor arată acest lucru:

Listing B.4.3: exponentiere rapida3.cpp


1 #include<iostream> // actualizare p inainte de actualizare a si n
2 // ... si simplificarea codului ...
3
4 using namespace std;
5
6 int exponentiere_rapida(int a, int n) // p=aˆn
7 {
8 int nk;
9 int ak;
10 int ek;
11 int pk;
12
13 nk=n;
14 ak=a;
15 pk=1;
16
17 while(nk>0)
18 {
19 ek=nk%2;
20 if(ek==1)
21 pk=pk*ak;
22 else
23 pk=pk; // nu are rost ... !!!
24 ak=ak*ak;
25 nk=nk/2;
26 }
27
28 return pk;
29 }
30
31 int main()
32 {
33 int a=2;
34 //int n=234; // aˆn = prea mare !!!
35 //int n=30;
36 int n=6; // par: 2ˆ6 = 64 ... usor de verificat!
37 //int n=5; // impar: 2ˆ5 = 32 ... usor de verificat!
38
39 int rez=exponentiere_rapida(a,n);
40
41 cout<<a<<"ˆ"<<n<<" = "<<rez<<"\n";
42
43 return 0;
44 }
45 /*
46 2ˆ30 = 1073741824
47
48 Process returned 0 (0x0) execution time : 0.076 s
49 Press any key to continue.
50 */

Observaţia 5. Produsul poate deveni foarte mare şi din cauza asta se cere rezultatul modulo un
număr prim. Codul următor arată acest lucru:

Listing B.4.4: exponentiere rapida MOD.cpp


1 #include<iostream>
2
3 using namespace std;
4
5 int MOD=1000;
6 int nropr; // numar operatii (inmultiri) la exponentiere rapida
7
8 int exponentiere_rapida(int a, int n) // p=aˆn
9 {
10 int p = 1;
11 while(n>0)
12 {
13 if(n % 2 == 1) p = (p * a)%MOD;
APPENDIX B. EXPONENŢIERE RAPIDĂ B.5. CHIAR ESTE RAPIDĂ? 499

14 a = (a * a)%MOD;
15 n = n / 2;
16 nropr=nropr+6; // n%2, p*a, a*a, (a * a)%MOD si n/2
17 }
18 return p;
19 }
20
21 int main()
22 {
23 int a=1234;
24 int n=1e+9; // 10ˆ9
25 int rezn; // rezultat exponentiere naiva
26
27 rezn=exponentiere_rapida(a,n);
28
29 cout<<"rezr = "<<rezn<<" nropr = "<<nropr<<"\n";
30
31 return 0;
32 }
33 /*
34 rezr = 376 nropr = 180
35
36 Process returned 0 (0x0) execution time : 0.021 s
37 Press any key to continue.
38 */

B.5 Chiar este rapidă?


De ce se numeşte ”rapidă”?
1 000 000 000
Să presupunem că vrem să calculăm 1234 şi pentru că rezultatul are foarte multe
cifre ... ne vom mulţumi, la fiecare calcul, cu ultimele 3 cifre!
Aceasta este metoda ”naivă”:

Listing B.5.1: exponentiere naiva MOD.cpp


1 #include<iostream>
2
3 using namespace std;
4
5 int MOD=1000;
6 int nropn=0; // numar operatii (inmultiri) la exponentiere naiva
7
8 int exponentiere_naiva(int a, int n) // p=aˆn
9 {
10 int p = 1;
11 for(int k=1; k<=n; k++)
12 {
13 p=(p*a)%MOD;
14 nropn=nropn+1;
15 }
16 return p;
17 }
18
19 int main()
20 {
21 int a=1234;
22 int n=1e+9; // 10ˆ9
23 int rezn; // rezultat exponentiere naiva
24
25 rezn=exponentiere_naiva(a,n);
26
27 cout<<"rezn = "<<rezn<<" nropn = "<<nropn<<"\n";
28
29 return 0;
30 }
31 /*
32 rezn = 376 nropn = 2000000000
33
34 Process returned 0 (0x0) execution time : 21.239 s
35 Press any key to continue.
36 */
APPENDIX B. EXPONENŢIERE RAPIDĂ B.6. REZUMAT INTUITIV! 500

Iar aceasta este metoda ”rapidă”:

Listing B.5.2: exponentiere rapida MOD.cpp


1 #include<iostream>
2
3 using namespace std;
4
5 int MOD=1000;
6 int nropr; // numar operatii (inmultiri) la exponentiere rapida
7
8 int exponentiere_rapida(int a, int n) // p=aˆn
9 {
10 int p = 1;
11 while(n>0)
12 {
13 if(n % 2 == 1) p = (p * a)%MOD;
14 a = (a * a)%MOD;
15 n = n / 2;
16 nropr=nropr+6; // n%2, p*a, ..%MODa*a, (a * a) ... %MOD si n/2
17 }
18 return p;
19 }
20
21 int main()
22 {
23 int a=1234;
24 int n=1e+9; // 10ˆ9
25 int rezn; // rezultat exponentiere naiva
26
27 rezn=exponentiere_rapida(a,n);
28
29 cout<<"rezr = "<<rezn<<" nropr = "<<nropr<<"\n";
30
31 return 0;
32 }
33 /*
34 rezr = 376 nropr = 180
35
36 Process returned 0 (0x0) execution time : 0.021 s
37 Press any key to continue.*/

Numărul de operaţii:
ˆ cu metoda naivă acest număr este 2 000 000 000
ˆ cu metoda rapidă este 180.

Timpul de execuţie (pe calculatorul pe care am testat eu!):


ˆ cu metoda naivă este 21.239 secunde
ˆ cu metoda rapidă este 0.021 secunde

deci ... cam de 1000 de ori mai rapidă!

Iar ca număr de operaţii ... una este să faci 2 miliarde de operaţii şi alta este să faci
numai 180 de operaţii de acelaşi tip (de fapt sunt numai 30 de paşi dar la fiecare pas se fac 5
sau 6 operaţii aritmetice)!

B.6 Rezumat intuitiv!


Revenind la relaţia
234 128 1 64 1 32 1 16 0 8 1 4 0 2 1 1 0
a a  ˜ a  ˜ a  ˜ a  ˜ a  ˜ a  ˜ a  ˜ a 

să privim cu atenţie ce este aici!


Putem calcula uşor acest produs de la dreapta spre stânga!
2 4 8 16 32 64 128
Secvenţa a , a , a , a , a , a , a se poate genera cu instrucţiunea a=a*a;
Puterile expresiilor (marcate cu roşu, cele care sunt ı̂ntre paranteze) se obţin (tot de la dreapta
spre stânga) prin ı̂mpărţiri succesive ale lui n la 2 şi preluând restul. Dacă restul este 0 atunci
0
puterea respectivă este 0 iar ... 1 ... deci, nu mai apare ı̂n produs!
APPENDIX B. EXPONENŢIERE RAPIDĂ B.6. REZUMAT INTUITIV! 501

Asta este tot!


Deci, secvenţa de cod este:

Listing B.6.1: secventa cod.cpp


1 int p = 1;
2 while(n>0)
3 {
4 if(n % 2 == 1) p = (p * a);
5 a = a * a;
6 n = n / 2;
7 }

31
şi nu mai trebuie ”atâtea formule matematice”!

31
Este o glumă!
Appendix C

Căutare binară

C.1 Mijlocul = ?
Care este poziţia (indicele) ”mijlocului” unei zone dintr-un vector?

Figura C.1: căutare binară: mijlocul zonei ı̂n vector

În figura C.1 sunt trei formule pentru determinarea mijlocului unei zone [i1, i2] din vector:
ˆ mij1 = (i1+i2)/2
ˆ mij2 = i1+(i2-i1)/2
ˆ mijdr = (i1+i2+1)/2
Toate trei dau rezultat corect dacă secvenţa [i1, ..., i2] are un număr impar de elemente.
Când secvenţa [i1, ..., i2] are un număr par de elemente apar două elemente la mijloc ... şi ...
primele două formule dau ”mijlocul din stânga” iar a treia formulă dă ”mijlocul din dreapta”.
Atunci când căutăm o valoare ı̂ntr-un vector avem la dispoziţie căutarea secvenţială. Dacă
ştim că vectorul este sortat (şir crescător de numere) atunci este bine să folosim căutarea binară
pentru că aceasta ”găseste” numărul căutat (sau ”spune” că acel număr nu este ı̂n şir)
ˆ ı̂n 10 paşi dacă şirul are 1000 de elemnte (de fapt 1024 dar ... aproximăm!),
ˆ ı̂n 20 de paşi dacă şirul are 1,000,000 de elemente,
ˆ ı̂n 30 de paşi dacă şirul are 1,000,000,000 de elemente,
ˆ ...
Explicaţia simplă este că noi tot ı̂njumătăţim intervalul de căutare până când acest interval
rămâne cu un singur element ... şi vedem dacă acel element este cel căutat (dacă elementul rămas
nu este cel căutat ... ı̂nseamnă că acel element ”căutat” nu există ı̂n şir).
Putem să ne gândim la drumul invers: plecăm de la o secvenţă cu un singur element şi dublăm
secvenţa (invers faţă de cum am făcut ı̂njumătăţind secvenţa) până ajungem la o secvenţă care
are atâtea elemente câte a avut şirul iniţial (ne oprim imediat dacă am depăşit acel număr!).
Lungimile secvenţelor sunt: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, ...

Observăm că ı̂n 10 paşi, plecând din 1, ajungem să depăşim 1000.

502
APPENDIX C. CĂUTARE BINARĂ C.2. POZIŢIE OARECARE 503

C.2 Poziţie oarecare


C.2.1 Varianta iterativă

Listing C.2.1: cautare binara-v1-iterativ.cpp


1 // are rost NUMAI daca vectorul este SORTAT (aici crescator ... !!!)
2 // gaseste o pozitie oarecare din secventa x...x...x (a doua din stanga!)
3 // daca x nu exista --> v[dr] < x < v[st] unde dr=st-1 (initial st < dr)
4
5 #include<iostream>
6
7 using namespace std;
8
9 int nrc=0; // numarul de cautari
10
11 void afisv(int v[], int st, int dr) // afisez v[] intre st si dr
12 {
13 int i;
14 for(i=st; i<=dr; i++)
15 {
16 cout<<v[i]<<" ";
17 }
18 }
19 // ------------------------------------------------------------------
20 bool cautareBinaraIter1(int x, int v[], int st, int dr)
21 {
22 int mij;
23
24 while(st <= dr)
25 {
26 nrc++;
27 mij=(st+dr)/2; // actualizez zona de cautare
28 cout<<"caut "<<x<<" in ";
29 afisv(v,st,dr);
30 cout<<"\n\n";
31
32 if(x == v[mij])
33 {
34 cout<<"am gasit "<<x<<" pe pozitia mij = "<<mij
35 <<" st="<<st<<" dr="<<dr<<"\n";
36 return true;// l-a gasit pe x
37 }
38 else // aici x != v[mij] ...
39 {
40 if(x < v[mij])
41 dr=mij-1; // caut in stanga lui mij, st ramane neschimbat
42 else
43 st=mij+1; // caut in dreapta lui mij, dr ramane neschimbat
44 }
45
46 cout<<"v["<<st<<"] = "<<v[st]<<" v["<<dr<<"] = "<<v[dr]<<endl;
47 }
48
49 if(st>dr)
50 {
51 cout<<x<<" nu exista in vector ...!"<<" st="<<st<<" dr="<<dr
52 <<" v[st]="<<v[st]<<" v[dr]="<<v[dr]<<"\n";
53 //return;
54 return false;
55 }
56
57 return true; // sau return false; ... oricum nu ajunge aici ... !!!
58 }
59 // ------------------------------------------------------------------
60
61 int main()
62 {
63 int x; // caut x in vectorul a[]
64
65 //x=1; // secventa pe primele pozitii
66 x=2; // nu exista 2 ... ultima cautare in (dr,st)
67 //x=3; // exista numai un 3
68 //x=4; // exista secventa de 4
69 //x=9; // secventa pe ultimele pozitii
APPENDIX C. CĂUTARE BINARĂ C.2. POZIŢIE OARECARE 504

70
71 int a[]={1, 1, 1, 3, 3, 4, 4, 4, 4, 4, 4, 4, 7, 7, 8, 8, 9, 9, 9, 9};
72
73 int n=sizeof(a)/sizeof(int);
74
75 cout<<"prima pozitie in vector = 0\n";
76 cout<<"ultima pozitie in vector = "<<n-1<<endl<<endl;
77
78 cautareBinaraIter1(x,a,0,n-1); // caut x in vectorul a[] de dimensiune=n
79
80 cout<<endl;
81 cout<<"nr cautari = "<<nrc<<"\n";
82
83 return 0;
84 }

C.2.2 Varianta recursivă

Listing C.2.2: cautare binara-v1-recursiv.cpp


1 // are rost NUMAI daca vectorul este SORTAT (aici crescator ... !!!)
2 // gaseste o pozitie oarecare din secventa x...x...x (a doua din stanga!)
3 // daca x nu exista --> v[dr] < x < v[st] unde dr=st-1 (initial st < dr)
4
5 #include<iostream>
6
7 using namespace std;
8
9 int nrc=0; // numarul de cautari
10
11 void afisv(int v[], int st, int dr) // afisez v[] intre st si dr
12 {
13 int i;
14 for(i=st; i<=dr; i++)
15 {
16 cout<<v[i]<<" ";
17 }
18 }
19 // ------------------------------------------------------------------
20 void cautareBinaraRec1(int x, int v[], int st, int dr)
21 {
22 nrc++; // o noua cautare,
23
24 cout<<"caut "<<x<<" in ";
25 afisv(v,st,dr);
26 cout<<"\n\n";
27
28 int mij=(st+dr)/2;
29
30 if(st>dr)
31 {
32 cout<<x<<" nu exista in vector ...!"<<" st="<<st<<" dr="<<dr
33 <<" v[st]="<<v[st]<<" v[dr]="<<v[dr]<<"\n";
34 return;
35 //return false;
36 }
37
38 if(x == v[mij])
39 {
40 cout<<"am gasit "<<x<<" pe pozitia "<<mij<<"\n";
41 return;
42 //return true;
43 }
44 else // mai caut
45 {
46 if(x < v[mij])
47 cautareBinaraRec1(x,v,st,mij-1); // caut in stanga lui mij
48 else
49 cautareBinaraRec1(x,v,mij+1,dr); // caut in dreapta lui mij
50 }
51
52 return;
53 }
54 // ------------------------------------------------------------------
APPENDIX C. CĂUTARE BINARĂ C.3. POZIŢIA DIN STÂNGA 505

55 int main()
56 {
57 int x; // caut x in vectorul a[]
58
59 //x=1; // secventa pe primele pozitii
60 x=2; // nu exista 2 ... ultima cautare in (dr,st) dr=st-1
61 //x=3; // exista numai un 3
62 //x=4; // exista secventa de 4
63 //x=9; // secventa pe ultimele pozitii
64
65 int a[]={1, 1, 1, 3, 3, 4, 4, 4, 4, 4, 4, 4, 7, 7, 8, 8, 9, 9, 9, 9};
66
67 int n=sizeof(a)/sizeof(int);
68
69 cout<<"prima pozitie in vector = 0\n";
70 cout<<"ultima pozitie in vector = "<<n-1<<endl<<endl;
71
72 cautareBinaraRec1(x,a,0,n-1); // caut x in vectorul a[] de dimensiune=n
73
74 cout<<endl;
75 cout<<"nr cautari = "<<nrc<<"\n";
76
77 return 0;
78 }

C.3 Poziţia din stânga


C.3.1 Varianta iterativă

Listing C.3.1: cautare binara-v2-iterativ.cpp


1 // are rost NUMAI daca vectorul este SORTAT (aici crescator ... !!!)
2 // gaseste pozitia din stanga din secventa x...x...x
3 // daca x nu exista --> st = dr = prima pozitie spre dreapta cu v[st] > x
4
5 #include<iostream>
6
7 using namespace std;
8
9 int nrc=0; // numarul de cautari
10
11 void afisv(int v[], int st, int dr) // afisez v[] intre st si dr
12 {
13 int i;
14 for(i=st; i<=dr; i++)
15 {
16 cout<<v[i]<<" ";
17 }
18 }
19 // ------------------------------------------------------------------
20 bool cautareBinaraIter1(int x, int v[], int st, int dr)
21 {
22 int mij;
23
24 while(st != dr)
25 {
26 nrc++;
27 mij=(st+dr)/2; // actualizez zona de cautare
28
29 cout<<"caut "<<x<<" in ";
30 afisv(v,st,dr);
31 cout<<"\n\n";
32
33 if(x <= v[mij])
34 dr=mij; // caut in stanga
35 else
36 st=mij+1; // caut in dreapta
37
38
39
40 cout<<"v["<<st<<"] = "<<v[st]<<" v["<<dr<<"] = "<<v[dr]<<endl;
41 }
42
APPENDIX C. CĂUTARE BINARĂ C.3. POZIŢIA DIN STÂNGA 506

43 if(st==dr) // am terminat (gasit cel mai din stanga sau nu exista deloc)
44 {
45 if(v[st]==x)
46 {
47 cout<<"am gasit "<<x<<" pe pozitia "<<st<<"\n";
48
49 return true;
50 }
51
52 else
53 {
54 cout<<x<<" nu exista in vector ...!"<<" st="<<st<<" dr="<<dr
55 <<" v[st]="<<v[st]<<" v[dr]="<<v[dr]<<"\n";
56
57 return false;
58 }
59 }
60
61 return true; // sau return false; ... oricum nu ajunge aici ... !!!
62 }
63
64 // ------------------------------------------------------------------
65
66 int main()
67 {
68 int x; // caut x in vectorul a[]
69
70 //x=1; // secventa pe primele pozitii
71 x=2; // nu exista 2 ... ultima cautare in (dr,st) dr=st-1
72 //x=3; // exista numai un 3
73 //x=4; // exista secventa de 4
74 //x=9; // secventa pe ultimele pozitii
75
76 int a[]={1, 1, 1, 3, 3, 4, 4, 4, 4, 4, 4, 4, 7, 7, 8, 8, 9, 9, 9, 9};
77
78 int n=sizeof(a)/sizeof(int);
79
80 cout<<"prima pozitie in vector = 0\n";
81 cout<<"ultima pozitie in vector = "<<n-1<<endl<<endl;
82
83 cautareBinaraIter1(x,a,0,n-1); // caut x in vectorul a[] de dimensiune=n
84
85 cout<<endl;
86 cout<<"nr cautari = "<<nrc<<"\n";
87
88 return 0;
89 }

C.3.2 Varianta recursivă

Listing C.3.2: cautare binara-v2-recursiv.cpp


1 // are rost NUMAI daca vectorul este SORTAT (aici crescator ... !!!)
2 // gaseste pozitia din stanga din secventa x...x...x
3 // daca x nu exista --> st = dr = prima pozitie spre dreapta cu v[st] > x
4
5 #include<iostream>
6
7 using namespace std;
8
9 int nrc=0; // numarul de cautari
10
11 // ------------------------------------------------------------------
12
13 void afisv(int v[], int st, int dr) // afisez v[] intre st si dr
14 {
15 int i;
16 for(i=st; i<=dr; i++)
17 {
18 cout<<v[i]<<" ";
19 }
20 }
21
22 // ------------------------------------------------------------------
APPENDIX C. CĂUTARE BINARĂ C.4. POZIŢIA DIN DREAPTA 507

23 void cautareBinaraRec2(int x, int v[], int st, int dr)


24 {
25 nrc++; // o noua cautare,
26 cout<<"nrc = "<<nrc<<": caut "<<x<<" in zona indicilor "
27 <<st<<" ... "<<dr<<" : "<<endl;
28 afisv(v,st,dr);
29 cout<<"\n\n";
30 //getchar(); // while si recursivitatea ... pot "scapa" de sub control!
31
32 int mij=(st+dr)/2;
33
34 if(st==dr) // am terminat (gasit cel mai din stanga sau nu exista deloc)
35 {
36 if(v[st]==x)
37 cout<<"am gasit "<<x<<" pe pozitia "<<st<<"\n";
38 else
39 cout<<x<<" nu exista in vector ...!"<<" st="<<st<<" dr="<<dr
40 <<" v[st]="<<v[st]<<" v[dr]="<<v[dr]<<"\n";
41 return;
42 //return false;
43 }
44
45 // mai caut
46 if(x <= v[mij])
47 cautareBinaraRec2(x,v,st,mij); // caut in stanga
48 else// aici x > v[mij]
49 cautareBinaraRec2(x,v,mij+1,dr); // caut in dreapta
50
51 return; // oricum nu ajunge aici !
52 }
53
54 // ------------------------------------------------------------------
55
56 int main()
57 {
58 int x; // caut x in vectorul a[]
59
60 //x=1; // secventa pe primele pozitii
61 //x=2; // nu exista 2 ... ultima cautare in (dr,st) dr=st-1
62 //x=3; // exista numai un 3
63 x=4; // exista secventa de 4
64 //x=9; // secventa pe ultimele pozitii
65
66 int a[]={1, 1, 1, 3, 3, 4, 4, 4, 4, 4, 4, 4, 7, 7, 8, 8, 9, 9, 9, 9};
67 int n=sizeof(a)/sizeof(int);
68
69 cout<<"prima pozitie in vector = 0\n";
70 cout<<"ultima pozitie in vector = "<<n-1<<endl<<endl;
71
72 cautareBinaraRec2(x,a,0,n-1); // caut x in vectorul a[] de dimensiune=n
73
74 cout<<endl;
75 cout<<"nr cautari = "<<nrc<<"\n";
76
77 return 0;
78 }

C.4 Poziţia din dreapta


C.4.1 Varianta iterativă

Listing C.4.1: cautare binara-v3-iterativ.cpp


1 // are rost NUMAI daca vectorul este SORTAT (aici crescator ... !!!)
2 // gaseste pozitia din dreapta din secventa x...x...x
3 // daca x nu exista --> st = dr = prima pozitie spre stanga cu v[st] < x
4
5 #include<iostream>
6
7 using namespace std;
8
9 int nrc=0; // numarul de cautari
10
APPENDIX C. CĂUTARE BINARĂ C.4. POZIŢIA DIN DREAPTA 508

11 void afisv(int v[], int st, int dr) // afisez v[] intre st si dr
12 {
13 int i;
14 for(i=st; i<=dr; i++)
15 {
16 cout<<v[i]<<" ";
17 }
18 }
19
20 // ------------------------------------------------------------------
21
22 bool cautareBinaraIter1(int x, int v[], int st, int dr)
23 {
24 int mij;
25
26 while(st != dr)
27 {
28 nrc++;
29 mij=(st+dr+1)/2; // actualizez zona de cautare
30
31 cout<<"caut "<<x<<" in ";
32 afisv(v,st,dr);
33 cout<<"\n\n";
34
35 if(x >= v[mij])
36 st=mij; // caut in dreapta
37 else
38 dr=mij-1; // caut in stanga
39
40 cout<<"v["<<st<<"] = "<<v[st]<<" v["<<dr<<"] = "<<v[dr]<<endl;
41 //getchar();
42 }
43
44 if(st==dr) // am terminat (gasit cel mai din stanga sau nu exista deloc)
45 {
46 if(v[st]==x)
47 {
48 cout<<"am gasit "<<x<<" pe pozitia "<<st<<"\n";
49 return true;
50 }
51 else
52 {
53 cout<<x<<" nu exista in vector ...!"<<" st="<<st<<" dr="<<dr
54 <<" v[st]="<<v[st]<<" v[dr]="<<v[dr]<<"\n";
55 return false;
56 }
57 }
58
59 return true; // sau return false; ... oricum nu ajunge aici ... !!!
60 }
61 // ------------------------------------------------------------------
62
63 int main()
64 {
65 int x; // caut x in vectorul a[]
66
67 //x=1; // secventa pe primele pozitii
68 x=2; // nu exista 2 ... ultima cautare in (dr,st) dr=st-1
69 //x=3; // exista numai un 3
70 //x=4; // exista secventa de 4
71 //x=9; // secventa pe ultimele pozitii
72
73 int a[]={1, 1, 1, 3, 3, 4, 4, 4, 4, 4, 4, 4, 7, 7, 8, 8, 9, 9, 9, 9};
74 int n=sizeof(a)/sizeof(int);
75
76 cout<<"prima pozitie in vector = 0\n";
77 cout<<"ultima pozitie in vector = "<<n-1<<endl<<endl;
78
79 cautareBinaraIter1(x,a,0,n-1); // caut x in vectorul a[] de dimensiune=n
80
81 cout<<endl;
82 cout<<"nr cautari = "<<nrc<<"\n";
83
84 return 0;
85 }
APPENDIX C. CĂUTARE BINARĂ C.4. POZIŢIA DIN DREAPTA 509

C.4.2 Varianta recursivă

Listing C.4.2: cautare binara-v3-recursiv.cpp


1 // are rost NUMAI daca vectorul este SORTAT (aici crescator ... !!!)
2 // gaseste pozitia din stanga din secventa x...x...x
3 // daca x nu exista --> st = dr = prima pozitie spre stanga cu v[st] < x
4
5 #include<iostream>
6
7 using namespace std;
8
9 int nrc=0; // numarul de cautari
10
11 void afisv(int v[], int st, int dr) // afisez v[] intre st si dr
12 {
13 int i;
14 for(i=st; i<=dr; i++) { cout<<v[i]<<" "; }
15 }
16
17 // ------------------------------------------------------------------
18
19 void cautareBinaraRec2(int x, int v[], int st, int dr)
20 {
21 nrc++; // o noua cautare,
22 cout<<"nrc = "<<nrc<<": caut "<<x<<" in zona indicilor "
23 <<st<<" ... "<<dr<<" : "<<endl;
24 afisv(v,st,dr);
25 cout<<"\n\n";
26 //getchar(); // while si recursivitatea ... pot "scapa" de sub control!
27
28 int mij=(st+dr+1)/2;
29 if(st==dr) // am terminat (gasit cel mai din stanga sau nu exista deloc)
30 {
31 if(v[st]==x) cout<<"am gasit "<<x<<" pe pozitia "<<st<<"\n";
32 else
33 cout<<x<<" nu exista in vector ...!"<<" st="<<st<<" dr="<<dr
34 <<" v[st]="<<v[st]<<" v[dr]="<<v[dr]<<"\n";
35 return;
36 //return false;
37 }
38
39 // mai caut
40 if(x >= v[mij])
41 cautareBinaraRec2(x,v,mij,dr); // caut in dreapta
42 else// aici x < v[mij]
43 cautareBinaraRec2(x,v,st,mij-1); // caut in stanga
44
45 return; // oricum nu ajunge aici !
46 }
47 // ------------------------------------------------------------------
48 int main()
49 {
50 int x; // caut x in vectorul a[]
51
52 //x=1; // secventa pe primele pozitii
53 x=2; // nu exista 2 ... ultima cautare in (dr,st) dr=st-1
54 //x=3; // exista numai un 3
55 //x=4; // exista secventa de 4
56 //x=9; // secventa pe ultimele pozitii
57
58 int a[]={1, 1, 1, 3, 3, 4, 4, 4, 4, 4, 4, 4, 7, 7, 8, 8, 9, 9, 9, 9};
59 int n=sizeof(a)/sizeof(int);
60
61 cout<<"prima pozitie in vector = 0\n";
62 cout<<"ultima pozitie in vector = "<<n-1<<endl<<endl;
63
64 cautareBinaraRec2(x,a,0,n-1); // caut x in vectorul a[] de dimensiune=n
65
66 cout<<endl;
67 cout<<"nr cautari = "<<nrc<<"\n";
68
69 return 0;
70 }
Appendix D

”Vecini” ...

D.1 Direcţiile N, E, S, V

Figura D.1: ”vecini” ı̂n matrice şi sistem Oxy

În figura D.1 sunt două modele (notaţii) utile ı̂n probleme ı̂n care se folosesc directiile N, E, S şi
V (NE, NV, SE şi SV se deduc uşor!).

Figura D.2: Toţi ”pereţii” sunt la N sau la V ı̂n matricea ”bordată”

În figura D.2 ...

510
APPENDIX D. ”VECINI” ... D.1. DIRECŢIILE N, E, S, V 511

Figura D.3: Toţi ”pereţii” sunt codificaţi

În figura D.3 ...


Index

19
10 , 303 exponenţiere rapidă, 18
ˆ=, 172
şir al frecvenţelor, 408 first, 68, 209, 311
şir circular, 385 fracţie continuă, 97
1LL, 88 front(), 88, 209

accumulate, 131 gcd, 37


aib, 205 getchar(), 168
AIB 2D, 203 greedy, 209
algorimul lui Euclid, 379
I.d.k.:
algoritm de interclasare, 245
I don’t know who the author is., v
algoritm de tip ”mars”, 84
insert, 88
algoritm de tip greedy, 204
int32 t, 65, 67, 88
algoritmul lui Eratostene, 264
int64 t, 67
algoritmul lui Euclid, 97
isdigit, 168
auto, 37, 39, 68, 96, 112, 125
auto&, 39, 68, 149 lambda, 50
Liceul Militar Dimitrie Cantemir, vi
back(), 67, 88, 134, 209 liniarizare matrice, 378
backtracking, 14 liniarizarea matricei, 210
begin(), 65, 67, 68, 88, 90, 134, 205, 209, 244 lower bound, 244
bitset, 184, 206
bool, 89, 90, 125, 175, 244 make pair, 311
bool operator, 205 matrice, 102
brute-force, 79 mediana şirului, 399
memset, 26
căutare binară, 7, 14, 84, 104, 135, 146, 159, Mica Teoremă a lui Fermat, 17
164, 204, 232, 246, 294, 385, 502 min, 168
căutare secvenţială, 502 min element, 131, 134
char, 131, 134, 168 multiplu comun, 379
ciclu, 378
CINOR, vi nth element, 247
Ciurul lui Eratostene, 18 numărul de divizori, 440
ciurul lui Eratostene, 60, 157, 181, 184, 260, numeric limits, 67
313, 358 ¡int¿::max(), 67
clear(), 68
cmmdc, 143, 144 operator, 90, 149, 175
const, 90, 112, 149, 175, 205 ordine lexicografică, 210
const auto&, 205
pair, 68, 209, 311
count, 68
pointer, 2
deque, 209 pop(), 311
descompunere ı̂n factori primi, 60, 440 pop back(), 134, 209
distanţa Manhattan, 107 pop front(), 209
doi pointeri, 14, 165 precalcul, 18
prefix, 10
empty(), 209, 311 prefixe, 164
end(), 65, 67, 68, 88, 90, 134, 205, 209, 244 principiul lui Dirichlet, 170
erase, 67, 244 programare dinamică, 203, 206
exponenţiere logaritmică, 372 push, 311

512
INDEX INDEX 513

push back, 37, 39, 65, 68, 81, 88, 90, 112, swap, 209
205, 209, 244 sync with stdio, 88
push front, 209
tehnica ”meet in the middle”, 170
rbegin(), 68 tehnica greedy, 203
recurenţă, 319 template, 168
relaţie de recurenţă, 330 top(), 311
resize, 149 trage cu tunul, iv
reverse, 134 true, 244
sau exclusiv, 170 typedef, 244
second, 68, 209, 311 typename, 168
set, 68
size, 65 unique, 244
size(), 37, 39, 65, 67, 68, 88, 90, 134, 149 Universitatea Ovidius, vi
sort, 65, 67, 68, 90, 131, 168, 175, 205, 209, upper bound, 168, 248
244 using, 89
greater, 131
sortare, 14, 103, 159, 197, 203, 218, 399, 403 valoarea mediană, 7
sortare prin numărare, 197 vector, 37, 39, 65, 67, 68, 81, 88, 90, 112,
srand, 81 125, 131, 134, 149, 205, 206, 244
stack, 311 back(), 125
statistici de ordine, 399 begin(), 131
stivă, 309, 403 end(), 131
strategie de tip Meet In The Middle, 227 pop back(), 125
strategie Greedy, 72, 93 push back, 125
strategie greedy, 145 resize, 125
string, 68, 131, 134 size(), 125
struct, 26, 88, 90, 112, 149, 175, 205, 244 vector de frecvenţă, 14, 78, 104, 164
sufix, 10 vector de tuple, 14
sumă parţială, 7 vector de vizitare, 313
sume parţiale, 160, 164, 288, 330, 385 vectorul frecvenţelor, 399
Bibliografie

[1] Aho, A., Hopcroft, J., Ullman, J.D.; Data strutures and algorithms, Addison Wesley, 1983

[2] Andreica M.I.; Elemente de algoritmică - probleme şi soluţii, Cibernetica MC, 2011
[3] Andonie R., Gârbacea I.; Algoritmi fundamentali, o perspectivă C++, Ed. Libris, 1995
[4] Atanasiu, A.; Concursuri de informatică. Editura Petrion, 1995
[5] Bell D., Perr M.; Java for Students, Second Edition, Prentice Hall, 1999

[6] Calude C.; Teoria algoritmilor, Ed. Universităţii Bucureşti, 1987


[7] Cerchez, E., Şerban, M.; Informatică - manual pentru clasa a X-a., Ed. Polirom, 2000
[8] Cerchez, E.; Informatică - Culegere de probleme pentru liceu, Ed. Polirom, 2002
[9] Cerchez, E., Şerban, M.; Programarea ı̂n limbajul C/C++ pentru liceu, Ed. Polirom, 2005
[10] Cori, R.; Lévy, J.J.; Algorithmes et Programmation, Polycopié, version 1.6;
http://w3.edu.polytechnique.fr/informatique/
[11] Cormen, T.H., Leiserson C.E., Rivest, R.L.; Introducere ı̂n Algoritmi, Ed Agora, 2000
[12] Cormen, T.H., Leiserson C.E., Rivest, R.L.; Pseudo-Code Language, 1994
[13] Cristea, V.; Giumale, C.; Kalisz, E.; Paunoiu, Al.; Limbajul C standard, Ed. Teora, Bucureşti,
1992
[14] Erickson J.; Combinatorial Algorithms; http://www.uiuc.edu/˜jeffe/
[15] Flanagan, D.; Java in a Nutshell, O’Reilly, 1997.
[16] Giumale C., Negreanu L., Călinoiu S.; Proiectarea şi analiza algoritmilor. Algoritmi de
sortare, Ed. All, 1997
[17] Halim S., Halim F., Competitive programming, 2013
[18] Knuth, D.E.; Arta programării calculatoarelor, vol. 1: Algoritmi fundamentali, Ed. Teora,
1999.
[19] Knuth, D.E.; Arta programarii calculatoarelor, vol. 2: Algoritmi seminumerici, Ed. Teora,
2000.
[20] Knuth, D.E.; Arta programarii calculatoarelor, vol. 3: Sortare şi căutare, Ed. Teora, 2001.

[21] Knuth, D.E.; The art of computer programming, vol. 4A: Combinatorial algorithms, Part 1,
Addison Wesley, 2011.
[22] Lambert,K. A., Osborne,M.; Java. A Framework for Programming and Problem Solving,
PWS Publishing, 1999
[23] Laaksonen A.; Guide to competitive programming, Springer, 2017
[24] Livovschi, L.; Georgescu H.; Analiza şi sinteza algoritmilor. Ed. Enciclopedică, Bucureşti,
1986.
[25] Niemeyer, P., Peck J.; Exploring Java, O’Reilly, 1997.

514
BIBLIOGRAFIE BIBLIOGRAFIE 515

[26] Odăgescu, I., Smeureanu, I., Ştefănescu, I.; Programarea avansată a calculatoarelor personale,
Ed. Militară, Bucureşti 1993
[27] Odăgescu, I.; Metode şi tehnici de programare, Ed. Computer Lobris Agora, Cluj, 1998
[28] Popescu Anastasiu, D.; Puncte de articulaţie şi punţi ı̂n grafuri, Gazeta de Informatică nr.
5/1993
[29] Răbâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire
/2007/Info/Lista_probleme_2000-2007.pdf
[30] Răbâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire
/2007/Info/Rezolvari_C09.pdf

[31] Răbâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire


/2007/Info/Rezolvari_C10.pdf
[32] Răbâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire
/2007/Info/Rezolvari_C11.pdf

[33] Răbâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire


/2007/Info/Rezolvari_Baraj.pdf
[34] Skiena S.S., Revilla M.A.; Programming challenges - The Programming Contest Training
Manual, Springer, 2003
[35] Tomescu, I.; Probleme de combinatorică şi teoria grafurilor, Editura Didactică şi Pedagogică,
Bucureşti, 1981
[36] Tomescu, I.; Leu, A.; Matematică aplicată ı̂n tehnica de calcul, Editura Didactică şi Peda-
gogică, Bucureşti, 1982
[37] Tudor, S.; Informatică - profilul real intensiv, varianta C++; Editura L&S, Bucureşti, 2004
[38] Tudor, S.; Hutanu, V,; Informatică intensiv; Editura L&S, Bucureşti, 2006
[39] Văduva, C.M.; Programarea in JAVA. Microinformatica, 1999
[40] Vişinescu, R.; Vişinescu, V.; Programare dinamică - teorie şi aplicaţii; GInfo nr. 15/4 2005
[41] Vlada, M.; Conceptul de algoritm - abordare modernă, GInfo, 13/2,3 2003
[42] Vlada, M.; Grafuri neorientate şi aplicaţii. Gazeta de Informatică, 1993
[43] Vlada, M.; Gândirea Algoritmică - O Filosofie Modernă a Matematicii şi Informaticii, CNIV-
2003, Editura Universităţii din Bucureşti, 2003
[44] Weis, M.A.; Data structures and Algorithm Analysis, Ed. The Benjamin/Cummings Pub-
lishing Company. Inc., Redwoods City, California, 1995.
[45] Winston, P.H., Narasimhan, S.; On to JAVA, Addison-Wesley, 1996
[46] Wirth N.; Algorithms + Data Structures = Programs, Prentice Hall, Inc 1976
[47] *** - Gazeta de Informatică, Editura Libris, 1991-2005
[48] *** - https://github.com/DinuCr/CS/blob/master/Info/stuff%20stuff/Re
zolvari_C09.pdf
[49] *** - https://dokumen.tips/documents/rezolvaric09.html
[50] *** - https://www.scribd.com/doc/266218102/Rezolvari-C09
[51] *** - https://www.scribd.com/document/396362669/Rezolvari-C10
[52] *** - https://www.scribd.com/document/344769195/Rezolvari-C11
[53] *** - https://www.scribd.com/document/364077679/Rezolvari-C11-pdf

[54] *** - https://needoc.net/rezolvari-c11-pdf


BIBLIOGRAFIE BIBLIOGRAFIE 516

[55] *** - https://vdocumente.com/algoritmi-i-structuri-de-date.html


[56] *** - https://pdfslide.net/documents/algoritmi-si-structuri-de-dat
e-1-note-de-cuprins-1-oji-2002-clasa-a-ix-a-1-11.html
[57] *** - https://www.infoarena.ro/ciorna

[58] *** - https://infoarena.ro/olimpici


[59] *** - https://www.infogim.ro/
[60] *** - https://www.pbinfo.ro/

[61] *** - http://www.cplusplus.com/


[62] *** - http://www.cplusplus.com/doc/tutorial/operators/
[63] *** - http://www.info1cup.com/
[64] *** - http://www.olimpiada.info/

[65] *** - http://www.usaco.org/


[66] *** - http://algopedia.ro/
[67] *** - http://campion.edu.ro/
[68] *** - http://varena.ro/
[69] *** - http://rmi.lbi.ro/rmi_2019/
[70] *** - https://codeforces.com/
[71] *** - https://cpbook.net/
[72] *** - https://csacademy.com/
[73] *** - https://gazeta.info.ro/revigoram-ginfo/
[74] *** - https://oj.uz/problems/source/22
[75] *** - https://profs.info.uaic.ro/˜infogim/2019/index.html
[76] *** - https://wandbox.org/
[77] *** - https://en.cppreference.com/w/cpp/language/operator_alternative
[78] *** - https://en.cppreference.com/w/cpp/algorithm
[79] *** - https://www.ejoi2019.si/
[80] *** - https://en.wikipedia.org/wiki/Algorithm
[81] *** - https://en.wikipedia.org/wiki/List_of_algorithms
[82] *** - https://en.wikipedia.org/wiki/List_of_data_structures
[83] *** - https://cs.pub.ro/images/NewsLabsImages/Teste-admitere-informa
tica-UPB-2020.pdf
Lista autorilor
problemelor şi indicaţiilor

Ştefan Manolache, 17 Ion Văduva, vi


Ştefania Ionescu, 17
Lucian Bicsi, 35, 169
Adrian Airinei, 399
Adrian Niţă, 439 Manolache Gheorghe, 51, 164, 313
Alexandru Petrescu, 2, 17 Marcel Drăgan, 152
Alin Burţa, 288 Maria Niţă, 439
Andrei Arhire, 2 Marinel Şerban, 427
Marius Dumitran, 363, 369
Budai István, 227, 299 Mihaela Cişmaru, 16
Mihai Stroe, 443
Carmen Mincă, 277 Mircea Lupşe-Turpan, 159
Carmen Popescu, 395 Mircea Paşoi, 393
Cheşcă Ciprian, 43, 72, 264, 304, 353
Claudiu-Cristian Gorea, 180 Nistor Moţ, 210, 406
Constantin Gălăţan, 84, 231, 408
Pătcaş Csaba, 357, 367, 372
Constantin Tudor, vi
Piţ-Rada Ionel-Vasile, 169, 260, 343
Cosmin Mihai Tutunaru, 286, 296
Posdărăscu Daniel, 72
Cristina Bohm, 423
Cristina Sichim, 367, 369 Robert Hasna, 349

Dan Pracsiu, 157, 173, 290 Sichim Cristina, 353


Doru Popescu Anastasiu, 437 Silaghi Lucian, 269
Sofia Viţelaru, 203
Eugen Nodea, 21, 245 Stelian Ciurea, 157, 293, 357

Filip Cristian Buruiană, 369 Tulbă-Lecu Theodor-Gabriel, 13


Turdean Alexandru, 63
Gemene Narcis-Gabriel, 103
Gheorghe Dodescu, vi Vlad Ionescu, 309

517
What’s the next?
ORNL’s Frontier First to Break the Exaflop Ceiling

H T T P S :// E N . W I K I P E D I A . O R G / W I K I /F R O N T I E R _( S U P E R C O M P U T E R )#/ M E D I A /F I L E :F R O N T I E R _S U P E R C O M P U T E R _(2). J P G

Over time
the following steps
will lead you to the value
you seek for yourself
now!

S-ar putea să vă placă și