Documente Academic
Documente Profesional
Documente Cultură
cls11 OJI 2023 11
cls11 OJI 2023 11
Olimpiada - cls11
2023-11
PROBLEME DE INFORMATICĂ
date la olimpiade
OJI
ı̂n
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!
2023-11-6
Figura 1: Descompunerea canonică a lui f
”O imagine valorează
cât 1000 de cuvinte!” 1
x1 x1
x3 x3
x2 x2
elevii f = aleg ”şeful” elevi
ı̂n clasă ı̂n clasă
fac echipe echipei
y1 y1
y3 y3
y2 y2
1
I.d.k.: ”I don’t know who the author is.”
Dedication
( in ascending order! )
2
https://www.femalefirst.co.uk/books/carol-lynne-fighter-1034048.html
3
https://otiliaromea.bandcamp.com/track/dor-de-el
4
https://en.wikipedia.org/wiki/To_be,_or_not_to_be
5
Everyone will go to his star and the stars are very far from each other!
6
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!.
7
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
8 9
dificilă! :-) Vezi, de exemplu, IOI2020 şi IOI2019 , IOI2015 . (Numai GCC a fost mereu!)
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
10
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
11
disponibil şi, oricum, calculatoarele folosite la olimpiade ı̂nainte de 2010 erau ceva mai ’slabe’ şi
iii
În perioada 2017-2020 cele mai puternice calculatoare din lume au fost: ı̂n noiembrie 2017
12
ı̂n China, ı̂n noiembrie 2019 ı̂n SUA şi ı̂n iunie 2020 ı̂n Japonia. În iunie 2022 ”Frontier”
13
depăşeşte pragul ”exascale”! (1.102 Exaflop/s). ”Peta” a fost depăşit ı̂n 2008, ”tera” ı̂n 1997,
14
”giga” ı̂n 1972. . Pentru ce a fost mai ı̂nainte, vezi https://en.wikipedia.org/wiki/Li
st_of_fastest_computers.
15
O mică observaţie: ı̂n 2017 a fost prima ediţie a olimpiadei EJOI ı̂n Bulgaria şi ... tot
16
ı̂n Bulgaria a fost şi prima ediţie a olimpiadei IOI ı̂n 1989. Dar ... prima ediţie a olimpiadei
17
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ă ”ı̂ncepătorilor” interesaţi de aceste teme! Nu se
adresează ”avansaţilor” şi nici cârcotaşilor! Sunt evidente sursele
”de pe net” (şi locurile ı̂n care au fost folosite); cred că nu sunt
necesare ”ghilimele anti-plagiat” şi precizări la fiecare pas!
Şi un ultim gând: criticile şi sfaturile sunt utile dacă au valoare
reală! Dacă sunt numai aşa ... cum critică lumea la un meci de
fotbal ... sau cum, pe bancă ı̂n parc, unul ”ı̂şi dă cu părerea”
despre rezolvarea problemelor economice ale ţării ... atunci ... !!!
18
”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
12
https://www.top500.org/lists/top500/2022/06/
13
https://en.wikipedia.org/wiki/Metric_prefix/
14
https://en.wikipedia.org/wiki/Computer_performance_by_orders_of_magnitude
15
https://ejoi.org/about/
16
https://stats.ioinformatics.org/olympiads/
17
https://en.wikipedia.org/wiki/International_Mathematical_Olympiad
18
https://www.facebook.com/johnwayne/photos/a.156450431041410/2645523435467418/?type=3
”Acknowledgements”
and
and/or
suggestions!
19
I.d.k.: ”I don’t know who the author is.”
20
donation/donaţie:
. name: RABAEA AUREL ADRIAN, IBAN: RO22BRDE060SV11538970600, SWIFT: BRDEROBUXXX
v
Despre autor
Dhawan Sanjeev, Kulvinder Singh, Eduard-Marius Craciun, Adrian Răbâea, Amit Batra;
21
http://opac.biblioteca.ase.ro/opac/bibliographic_view/149021
22
http://www.ionivan.ro/2015-PERSONALITATI/Dodescu.htm
23
http://old.fmi.unibuc.ro/ro/prezentare/promotii/promotia1978informatica_10ani/
24
https://ro.wikipedia.org/wiki/Ion_V%C4%83duva
25
https://fmi.univ-ovidius.ro/
26
https://www.cinor.ro/index.htm
27
https://www.cantemircml.ro/
vi
Next-Cart Recommendation by Utilizing Personalized Item Frequency Information in Online Web
Portals, Neural Processing Letters, 2023; https://doi.org/10.1007/s11063-023-11207-2
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
Cuprins
Prefaţă iii
Cuprins viii
1 OJI 2023 1
1.1 parcare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.1.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 turcane . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 veri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.3.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2 OJI 2022 11
2.1 dulciuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.1.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2 investiţie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3 superhedgy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.3.2 *Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
viii
3.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4 OJI 2020 33
4.1 ateleport . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.2 partit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
4.2.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
4.3 RecycleBin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
4.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
4.3.3 Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
5 OJI 2019 71
5.1 conexidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
5.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
5.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.2 rufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
5.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
5.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.3 tairos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
5.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
5.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
5.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
6 OJI 2018 98
6.1 galeti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
6.1.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
6.1.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
6.1.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
6.2 ramen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
6.2.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
6.2.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
6.2.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.3 aquapark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
6.3.1 Indicaţii de rezolvare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6.3.2 Cod sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
6.3.3 *Rezolvare detaliată . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Index 396
Bibliografie 399
5.1 rufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
5.2 rufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
5.3 rufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
5.4 rufe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
5.5 tairos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
xiv
B.11 Build - compilare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360
B.12 0 error(s), 0 warning(s) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361
B.13 Run - execuţie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 361
B.14 Executat corect: a făcut “nimic” . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
B.15 Settings % Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362
B.16 Toolchain executables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
B.17 Unde sunt acele programe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
B.18 Multe surse ı̂n Code Blocks - setări . . . . . . . . . . . . . . . . . . . . . . . . . . . 364
B.19 Multe surse in Code Blocks - exemple . . . . . . . . . . . . . . . . . . . . . . . . . 364
B.20 mingw64 pe D: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 365
B.21 search path . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366
B.22 System properties –¿ Advanced . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366
B.23 Environment Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367
B.24 Edit Environment Variables –¿ New . . . . . . . . . . . . . . . . . . . . . . . . . . 367
B.25 Calea şi versiunea pentru gcc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368
B.26 Settings –¿ Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369
B.27 Toolchain executables –¿ Auto-detect . . . . . . . . . . . . . . . . . . . . . . . . . . 369
B.28 New –¿ Text Document . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
B.29 New text Document.txt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
B.30 Schimbare nume şi extensie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370
B.31 Moore apps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
B.32 Look for another app . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371
B.33 Cale pentru codeblocks.exe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
B.34 Selectare codeblocks.exe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372
B.35 Editare test01.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373
B.36 Compilare test01 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373
B.37 Mesaje după compilare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374
B.38 Execuţie test01 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 374
B.39 Rezultat execuţie test01 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
B.40 Fişiere apărute după compilare! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375
B.41 Creare test02.cpp gol! + ¡dublu click¿ sau ¡Enter¿ . . . . . . . . . . . . . . . . . . 375
B.42 Lista programelor de utilizat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 376
B.43 Selectare Code::Blocks IDE pentru fişierele .cpp . . . . . . . . . . . . . . . . . . 376
B.44 Editare+Compilare+Execuţie pentru test02 . . . . . . . . . . . . . . . . . . . . . 376
B.45 Selectare tab ce conţine test01.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . 377
4.2 partit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
xvi
Lista programelor
xvii
7.1.2 armonica eugen0.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
7.1.3 armonica eugen1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
7.1.4 armonica eugen2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
7.1.5 armonica VG.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
7.1.6 armonica Zoli.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
7.1.7 armonicaPA.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
7.1.8 armonicaPAbrut.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
7.2.1 eric ninjago.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
7.2.2 fastlikeaninja PA.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134
7.2.3 ninjago eugen.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
7.2.4 ninjago primPA.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137
7.2.5 ninjagoBubbleS Codruta.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
7.2.6 ninjagoCRadix2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
7.2.7 ninjagoCRadixS Codruta.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143
7.2.8 ninjagoPA.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145
7.2.9 ninjagoPMD Codruta.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
7.2.10 ninjagoPrim mat Codruta.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
7.2.11 ninjagoQuickS Codruta.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
7.2.12 ninjagoSTL Codruta.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
7.3.1 back Eric .cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
7.3.2 permutare Eric.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
7.3.3 permutare VG .cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
7.3.4 permutarebrut Zoli .cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
7.3.5 permutarePA.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
7.3.6 permutareZoli.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
8.1.1 elicoptere100 DAP.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
8.1.2 elicoptere100 PA.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
8.1.3 elicoptere100 PA naiv.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
8.2.1 summax 80.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
8.2.2 summax 100 PA.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
9.1.1 2sah brut.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
9.1.2 2sah clasic.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
9.1.3 2sah liniar.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
9.1.4 2sah oficiala.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
9.1.5 2sah smart.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
9.2.1 dragoni.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
9.2.2 Dragoni GD.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
9.2.3 dragoni noGraph int.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190
9.2.4 dragoni WithGraphMLE.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
9.2.5 dragoniBeFo.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
9.2.6 dragoniHeap.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
9.2.7 dragoniRF.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197
10.1.1 AdrianPanaeteFleury2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201
10.1.2 AdrianPanaeteRecursiv1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
10.1.3 DoruAnastasiuPopescucartite.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . 204
10.2.1 fractii2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
10.2.2 fractii2 back.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
10.2.3 fractii2 var2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
11.1.1 biperm.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
11.1.2 biperm exp.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
11.1.3 biperm1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
11.2.1 subsecvente ans build.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
11.2.2 subsecvente arb.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 220
11.2.3 subsecvente arb static arrays.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . 221
11.2.4 subsecvente arb stl.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
11.2.5 subsecvente binary search.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
11.2.6 subsecvente binary search naive.cpp . . . . . . . . . . . . . . . . . . . . . . . . . 224
11.2.7 subsecvente dp.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
11.2.8 subsecvente linear search.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 228
11.2.9 subsecvente log.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
11.2.10 subsecvente naive.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230
11.2.11 subsecvente naive optimized.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
11.2.12 subsecvente radix.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 232
12.1.1 blis70p.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236
12.1.2 blis100p.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237
12.1.3 PA n2blis.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
12.1.4 PA pblis.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
12.2.1 PA parc2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
12.2.2 parc Zoli double.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
13.1.1 suma1 100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
13.1.2 suma2 100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 249
13.1.3 suma3 st.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250
13.2.1 bellmanford.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
13.2.2 dijkstra.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
13.2.3 dp.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 257
13.2.4 royfloyd.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
14.1.1 immortalc c.c . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262
14.1.2 immortal cpp.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 263
14.2.1 joc back1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 266
14.2.2 joc dinamica1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
14.2.3 joc dinamica2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267
14.2.4 joc greedy.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
14.2.5 joc memoizare.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
16.1.1 IEPURI.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279
16.1.2 IEPURI60.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
16.2.1 numar30.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
16.2.2 numar40.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284
16.2.3 numar50.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
16.2.4 numar100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286
17.1.1 numerev1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289
17.1.2 numere1.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 290
17.1.3 numere2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 291
17.2.1 cezar.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294
17.2.2 cezar1a.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296
17.2.3 cezar1b.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 297
17.2.4 cezar2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
18.1.1 GRAF40.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302
18.1.2 graf100.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303
18.1.3 graf1.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305
18.1.4 graf2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308
18.1.5 graf3.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310
18.2.1 stelian.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
18.2.2 cifru1.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
18.2.3 cifru2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
19.1.1 LANT.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
19.1.2 Lant1.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 321
19.1.3 Lant2.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
19.2.1 scara.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326
19.2.2 scaraDP.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 328
19.2.3 Scara1a.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
19.2.4 Scara1b.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
19.2.5 Scara2a.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331
19.2.6 Scara2b.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332
20.1.1 POLIGON.pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335
20.2.1 LANT2.pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338
21.1.1 compus.pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
21.1.2 COMPUS1.pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 344
21.2.1 zmeu.pas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 345
C.1.1 sss1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
C.1.2 sss2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
D.4.1 exponentiere rapida1.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382
D.4.2 exponentiere rapida2.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383
D.4.3 exponentiere rapida3.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
D.4.4 exponentiere rapida MOD.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384
D.5.1 exponentiere naiva MOD.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385
D.5.2 exponentiere rapida MOD.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 386
D.6.1 secventa cod.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
E.2.1 cautare binara-v1-iterativ.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389
E.2.2 cautare binara-v1-recursiv.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
E.3.1 cautare binara-v2-iterativ.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391
E.3.2 cautare binara-v2-recursiv.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392
E.4.1 cautare binara-v3-iterativ.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
E.4.2 cautare binara-v3-recursiv.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . 395
Capitolul 1
OJI 2023
1.1 parcare
Problema 1 - Parcare 100 de puncte
În cel mai recent eveniment al companiei Tesla, Paul Musk a anunţat un nou produs inovativ:
parcarea autonomă. Fiind cunoscut pentru lansările produselor incomplete, nici parcarea nu
este completă, fiind nevoie de o automatizare pentru a atribui câte un loc maşinilor care vor să
folosească parcarea.
Parcarea este formată din N locuri, numerotate de la 1 la N , şi este deschisă timp de T
secunde, ı̂ncepând cu secunda 1.
Pe parcursul zilei, sosesc M maşini care vor să folosească parcarea, pentru fiecare dintre acestea
ştiindu-se timpul de sosire si şi timpul de plecare pi . Maşinile vin ı̂n ordinea timpului de sosire
si şi ocupă locul de parcare ı̂n intervalul de timp si , pi . Pentru fiecare dintre acestea, trebuie
să afişaţi un loc liber de parcare (dacă sunt mai multe, se poate afişa oricare) ı̂n care aceasta se
poate aşeza sau -1 dacă parcarea este plină ı̂n momentul venirii maşinii. Dacă o maşină nu are
loc ı̂n parcare la timpul de sosire, aceasta nu va mai intra ı̂n parcare la niciun timp viitor.
La final, Paul este interesat de maşinile care mai sunt rămase ı̂n parcare la ı̂nchiderea parcării,
de aceea, vă cere să afişaţi configuraţia parcării la timpul T .
Date de intrare
Pe prima linie se găsesc trei numere ı̂ntregi N , M şi T , reprezentând numărul de locuri din
parcare, numărul de maşini care vin să folosească parcarea, respectiv numărul de secunde pentru
care este deschisă parcarea.
Următoarele M linii conţin fiecare câte două numere ı̂ntregi si , pi , reprezentând venirea unei
maşini la secunda si care va pleca la secunda pi .
Maşinile apar ı̂n fişierul de intrare ı̂n ordine crescătoare după timpul de sosire si .
Date de ieşire
Se vor afişa M 1 linii ı̂n total, primele M linii conţinând fiecare câte un număr ı̂ntreg ı̂ntre
1 şi N reprezentând locul de parcare pe care ı̂l va ocupa maşina, sau -1 dacă nu există niciun loc
de parcare disponibil.
Ultima linie va conţine N numere ı̂ntregi, reprezentând configuraţia parcării la ı̂nchidere, unde
cel de-al i-lea număr reprezintă timpul de sosire al maşinii de pe locul de parcare i, sau -1 dacă
locul de parcare i este gol.
1
CAPITOLUL 1. OJI 2023 1.1. PARCARE 2
Dacă există mai multe soluţii, se poate afişa oricare dintre acestea.
# Punctaj Restricţii
1 24 si 1 pi , adică fiecare maşină stă exact o secundă.
2 26 pi % sj , adică toate maşinile vin ı̂nainte ca vreo maşină să plece.
3 26 N & 1 000
4 24 Fără restricţii suplimentare.
Exemple:
Explicaţii:
Astfel, pentru fiecare secundă reţinem una sau două valori: 0 - niciun eveniment, 1 - venirea
unei maşini, 2 x - pleacarea maşinii de pe locul de parcare x.
În acelaşi timp, vom menţine locurile de parcare libere ı̂ntr-o stivă/coadă, pentru a putea găsi
in timp constant un loc de parcare liber sau a vedea dacă parcarea este plină.
Astfel, putem itera prin secunde de la 1 la T şi procesa evenimentele. La venirea unei maşini
la timpul si care va sta până la timpul pi , verificăm dacă stiva este goală, caz ı̂n care afişăm -1.
În caz contrar, vom obţine un loc liber de parcare x (din vârful stivei), pe care ı̂l vom elimina din
stivă, şi ı̂l vom aloca maşinii curente, după care vom updata configuraţia parcării şi informaţiile
despre evenimentul de plecare al maşinii la timpul pi .
La plecarea unei maşini, vom updata configuraţia parcării şi vom insera noul loc gol de parcare
ı̂n vârful stivei. După procesarea evenimentelor din intervalul de timp 1, T , afiş,ăm configuraţia
parcării.
Complexitate: O N M .
1.2 turcane
Problema 2 - Ţurcane 100 de puncte
Pe un câmp asemănător cu o tablă de şah cu M linii şi N coloane, o ţurcană se află ı̂n pătrăţelul
de coordonate 1, 1 aflat ı̂n colţul din stânga-sus al tablei şi vrea să ajungă ı̂n pătrăţelul de
coordonate M, N aflat ı̂n colţul din dreapta-jos al tablei.
Ea poate efectua sărituri de lungime cel mult P la dreapta, de lungime cel mult Q ı̂n jos, de
lungime cel mult R pe diagonală spre dreapta-jos, precum şi săritura calului, adică două pătrăţele
la dreapta şi unul ı̂n jos sau două ı̂n jos şi unul la dreapta. Orice săritură trebuie să schimbe
poziţia ţurcanei.
Se dă un număr ı̂ntreg C.
Dacă C 1, să se determine numărul minim de sărituri necesare pentru a ajunge ı̂n pătrăţelul
de coordonate M, N .
Dacă C 2, să se determine numărul de moduri ı̂n care poate să ajungă ı̂n pătrăţelul de
coordonate M, N , nu neapărat cu număr minim de sărituri.
Se garantează că pentru datele de intrare există cel puţin un mod de a ajunge ı̂n pătrăţelul
M, N .
Date de intrare
Pe prima linie a fişierului de intrare se află numărul C, pe a doua linie numerele ı̂ntregi M şi
N , iar pe a treia linie numerele ı̂ntregi P , Q şi R.
Date de ieşire
dacă P 0 nu poate sări la dreapta, dacă Q 0 nu poate sări ı̂n jos, iar dacă R 0 nu
poate sări pe diagonală
# Punctaj Restricţii
1 26 C 1, M 1
2 15 C 1, M 2
3 7 C 1, M 3
4 7 C 1, 1 & M, N & 200
5 8 C 1, 1 & M, N & 1000
6 11 C 2, 1 & M, N &5
7 12 C 2, 1 & M, N & 200
8 14 C 2, 1 & M, N & 1 000
Exemple:
Pentru primul exemplu, numărul minim de sărituri este 2. Cele şase soluţii cu număr minim
de sărituri sunt ilustrate ı̂n figurile următoare:
Pentru al doilea exemplu, numărul soluţiilor distincte este 8. Pentru fiecare soluţie, săriturile
ţurcanei sunt ilustrate ı̂n figurile următoare:
se efectuează o săritură a calului, dacă este posibil, ajungând ı̂n pătrăţelul (2,3), după care
se aplică formula de la subtask 1;
se efectuează o săritură pe diagonală, dacă este posibil, pentru N 2;
se efectuează o săritură pe verticală, apoi pe orizontală, dacă este posibil, pentru N 2.
Complexitate: O 1.
Subtask 3 (7 puncte). Avem de calculat numărul minim de sărituri pentru o matrice cu
trei linii. Pentru aceasta se va determina minimul dintre numărul de sărituri obţinut ı̂n fiecare
din următoarele situaţii:
se efectuează două sărituri ale calului, dacă este posibil, ajungând ı̂n pătrăţelul (3,5), după
care se aplică formula de la subtask 1;
se efectuează una sau două sărituri pe diagonală, dacă este posibil, pentru a ajunge pe linia
a treia, apoi se aplică formula de la subtask 1;
se efectuează una sau două sărituri pe verticală, dacă este posibil, pentru a ajunge pe linia
a treia, apoi se aplică formula de la subtask 1;
se efectuează o săritură a calului combinată cu o săritură pe verticală sau diagonală pentru
a ajunge pe linia a treia, apoi se aplică formula de la subtask 1.
Complexitate: O 1.
Subtask 4 (7 puncte). Se foloseşte programarea dinamică. Vom nota cu di,j numărul minim
de sărituri necesare pentru a ajunge ı̂n pătrăţelul i, j .
Astfel se iniţializează d1,1 0 şi di, j m n pentru orice indici i, j. Se parcurge matricea d
şi pentru fiecare poziţie i, j se fac actualizările:
di,j k min di,j k , di,j 1, pentru fiecare 1 & k & P cu j k & N ;
dik,j min dik,j , di,j 1, pentru fiecare 1 & k & Q cu i k & M ;
dik,j k min dik,j k , di,j 1, pentru fiecare 1 & k & R cu i k & M, j k & N ;
di1,j 2 min di1,j 2 , di,j 1 şi di2,j 1 min di2,j 1 , di,j 1 , când indicii nu depăşesc
dimensiunile matricei.
Complexitate: O M N .
Soluţie alternativă: Se foloseşte programarea dinamică. Astfel se iniţializează d1,1 1 şi
di,j m n pentru orice indici i, j ı̂n rest. Se parcurge matricea d şi pentru fiecare poziţie i, j
diferită de poziţia 1, 1 se actualizează di,j min a, b, c, e, f , unde
a min di,j k , ©1 & k & P cu 1 & j k,
b min dik,j , ©1 & k & Q cu 1 & i k,
c min dik,j k , ©1 & k & R cu 1 & i k, j k,
e di1,j 2 şi
f di2,j 1 .
Pentru a calcula a, b, c ı̂n O(1) se foloseşte deque pe fiecare linie, coloană şi diagonală.
Complexitate: O M N .
O soluţie asemănătoare, dar care nu trece toate testele, este ca ı̂n locul structurii deque să
folosim set.
Complexitate: O M N log max M, N .
CAPITOLUL 1. OJI 2023 1.3. VERI 6
Subtask 6 (11 puncte). Se foloseşte metoda backtracking pentru a genera toate drumurile
de la pătrăţelul de coordonate 1, 1 la pătrăţelul M, N .
Complexitate: O 2M N min M, N .
Subtask 7 (12 puncte). Se foloseşte programare dinamică. Vom nota cu di,j numărul de
moduri ı̂n care se poate ajunge ı̂n pătrăţelul i, j . Se iniţializează d1,1 1, se parcurge matricea
d şi pentru fiecare poziţie i, j se fac actualizările:
di,j k di,j , pentru fiecare 1 & k & P cu j k & N ;
dik,j di,j , pentru fiecare 1 & k & Q cu i k & M ;
dik,j k di,j , pentru fiecare 1 & k & R cu i k & M, j k & N ;
di1,j 2 di,j şi di2,j 1 di,j , când indicii nu depăşesc dimensiunile matricei.
1.3 veri
Problema 3 - Veri 100 de puncte
Se dă un graf orientat cu n noduri şi m muchii. Fiecare muchie are costul 1 (poate fi parcursă
ı̂ntr-un minut). Doi ”prieteni” (veri) pornesc din nodul S. Unul dintre ei vrea să ajungă ı̂n nodul
A, iar celălalt vrea să ajungă ı̂n nodul B.
Cei doi prieteni se vor plimba ı̂mpreună până când ciclează, adică până când vor ajunge ı̂n
acelaşi nod a doua oară, notat cu Z. După ciclare, ei ı̂şi pot continua drumurile separat. Totuşi,
dacă vor, pot să meargă amândoi ı̂n continuare pe acelaşi drum: doar dispare obligaţia de a merge
ı̂mpreună.
Fiecare dintre ei trebuie să-şi termine drumul doar după ciclare, adică după ce nu mai sunt
obligaţi să meargă ı̂mpreună.
Totuşi, este ı̂n regulă dacă drumul unuia se termină exact ı̂n nodul ı̂n care au ciclat (adică
ciclează ı̂n A sau B).
Care este numărul minim de minute necesar, astfel ı̂ncât să fie posibil ca amândoi să ajungă
la destinaţiile lor, ı̂n timpul alocat, ı̂n A, respectiv B?
Cu alte cuvinte, dacă cei doi veri ciclează pentru prima oară după exact t minute, apoi ı̂şi
continuă drumurile pentru alte tA , respectiv tB minute, vrem să aflăm valoarea minimă a lui
max t tA , t tB .
Există două tipuri de cerinţe, reprezentate printr-un număr c:
Dacă c 1, trebuie calculată valoarea minimă a lui max t tA , t tB .
CAPITOLUL 1. OJI 2023 1.3. VERI 7
Dacă c 2, trebuie afişat un triplet de drumuri care poate fi urmat de cei doi veri (drumul
comun din S până ı̂n Z, drum urmat ulterior de primul văr din Z până ı̂n A, drum urmat
ulterior de al doilea văr din Z până ı̂n B), astfel ı̂ncât valoarea asociată drumurilor, adică
max t tA , t tB să fie minimă. Orice triplet corect cu valoarea asociată minimă poate fi
afişat.
Date de intrare
Pe prima linie se găseşte c. Pe a doua linie se găsesc doi ı̂ntregi n şi m. Pe a treia linie se
găsesc trei ı̂ntregi S, A şi B.
Pe următoarele m linii se găsesc câte doi ı̂ntregi X şi Y , reprezentând că există o muchie
direcţionată de la nodul X la nodul Y, care poate fi parcursă ı̂ntr-un minut (de cost 1).
Date de ieşire
# Punctaj Restricţii
1 30 n & 500, m n şi toate muchiile sunt de forma i i mod n 1, unde i " r1, ..., nx.
2 50 n & 500
3 20 n & 5000 şi m & n.
Exemple:
Explicaţii:
Toate constrângerile de mai sus pot fi rezolvate cu algoritmul Roy-Floyd, cu două menţiuni:
di,i va fi folosit pentru ciclul de lungime minimă care incepe şi se termină ı̂n i, ¾i " r1, .., nx.
Pentru reconstruirea oricărui drum, trebuie să reţinem o matrice suplimentară p. Dacă am
ameliorat lungimea drumului i..j cu ajutorul nodului z, atunci actualizăm pi,j z.
Când vrem să reconstruim drumul i..j de lungime minimă, construim recursiv şi unim dru-
murile i..pi,j şi pi,j ..j.
CAPITOLUL 1. OJI 2023 1.3. VERI 9
2
Trebuie să reconstruim 4 drumuri, fiecare ı̂n O n . Complexitatea totală a algoritmului este
3
O n .
Testele nu sunt grupate, iar o parte din ele din acest subtask au fost create special astfel ı̂ncât
să faciliteze o soluţie ce foloseşte backtracking doar pentru a ı̂nchide cicluri din S. Acestea au
următoarea formă: ı̂n lipsa orientărilor pe muchii, graful este un arbore cu 1 sau 2 muchii ı̂n plus.
Astfel, orice nod din graf poate fi vizitat de maxim 3 ori ı̂ntr-o aplicare a backtracking-ului,
o dată normal şi ı̂ncă de două ori cu ajutorul muchiilor ı̂n plus. În condiţiile problemei, putem
explora maxim n 1 n 3 1 n 4 1 $ 3n " O n muchii pornind din S.
Precalculăm pentru orice nod x valorile dax şi dbx , reprezentând numărul minim de muchii ce
trebuie parcurse din x pentru a ajunge ı̂n A sau B (două BFS-uri pe graful transpus din A sau
B).
Putem să folosim backtracking-ul pentru a ne plimba până la ı̂nchiderea unui ciclu. Când am
ı̂nchis un ciclu, ştim ce distanţă am făcut din S până ı̂ntr-un Z anume (primul drum) şi ne folosim
de vectorii da şi db pentru a calcula ce distanţă o să aibă celelalte două drumuri.
O soluţie optimizată cu ideea de mai sus (tai ramura curentă a backtracking-ului când nu mai
pot să scad max t ta , t tb , chiar dacă as, reuşi cumva să ı̂nchid ciclul ı̂n nodul curent) poate
să ia 62 de puncte.
Subtask 3 (20 puncte). Căutăm ciclul optim i..i astfel: oricum ar arăta ciclul, există un
arbore BFS al grafului astfel ı̂ncât ciclul să fie reprezentat de un lanţ ı̂n jos din rădăcină, ı̂nchis
de un back edge ı̂napoi ı̂n rădăcină.
Constrângerile ne permit să construim fiecare arbore BFS din graf (adică să facem n BFS-uri,
unul pentru fiecare rădăcină posibilă), care vor lua O n n m L O n , deoarece m & 4n.
2
2
Putem reconstrui orice drum ı̂n O n, deci soluţia are complexitatea O n pentru acest sub-
task. Pentru celelalte subtaskuri, soluţia are complexitatea O nm L O n , ı̂ncadrându-se ı̂n
3
limita de timp.
Discuţ ie.
Nu putem să folosim doar arborele BFS cu rădăcina ı̂n S şi să presupunem că unul dintre
ciclurile ı̂nchise de un back edge va fi parte şi dintr-un raspuns corect. Exemplul 2 arată un
caz ı̂n care orice ciclu corect foloseşte doar cross edge-uri din perspectiva lui S. O rezolvare
simplă consideră toate rădăcinile posibile pentru arborele BFS (de fapt ce apare ca soluţie
la subtask-ul 3).
Problema generării de teste pe care să nu treacă soluţii cu backtracking (inclusiv cele care
28
se opresc la un procent din TL şi afişează cea mai bună soluţie găsită) este discutabil mai
grea decât rezolvarea problemei ı̂n sine.
– Dacă folosim prea multe muchii, atunci este foarte probabil ca backtracking-ul să
găsească ı̂n TL un ciclu ı̂ndeajuns de mic pentru răspunsul corect.
– Pentru ultimele două subtaskuri, vrem grafuri care să aibă destule cicluri, toate de
lungime relativ mare.
– De asemenea, am vrea să construim grafuri care să posede natural muchii care ar deveni
de tip cross ı̂n urma unui BFS din S.
A fost folositor ultimul exemplu?
28
TL: time limit
CAPITOLUL 1. OJI 2023 1.3. VERI 10
OJI 2022
2.1 dulciuri
Problema 1 - Dulciuri 100 de puncte
Tsubasa-chan adoră dulciurile! De curând a apărut un nou tip de desert. Astfel decide să
ı̂nfăptuiască o nouă fabrică care să producă acest produs delicios.
6 6
Fabrica conţine un container imens pătratic, plin de aluat, de 10 10 unităţi. Fiecare punct
din container are drept coordonate o pereche de numere reale x, y , unde 0 & x, y & 10 , iar
6
fiecare punct are o dulceaţă. Dulceaţa unui punct este un număr real, iniţial 0. Pentru fabricarea
desertului este nevoie de Q operaţii, care pot fi de următoarele tipuri:
O ı̂ndulcire verticală, determinată de o coordonată xu ı̂ntreagă şi o valoare ı̂ntreagă v. După
această operaţie, toate punctele din container x, y unde xu & x $ xu 1 devin mai dulci
cu v.
O ı̂ndulcire orizontală, determinată de o coordonată yu ı̂ntreagă şi o valoare ı̂ntreagă v.
După această operaţie, toate punctele din container x, y unde yu & y $ yu 1 devin mai
dulci cu v.
¬ ¬
O degustare, determinată de 4 coordonate ı̂ntregi xq , yq , xq , yq . Pentru aceeastă operaţie,
Tsubasa ia o lingură, o pune ı̂n aluat la punctul xq , yq , şi apoi o duce in linie dreaptă la
¬ ¬
punctul xq , yq .
Mişcarea se efectuează ı̂ntr-o secundă, cu viteză constantă. După aceea, Tsubasa gustă deser-
tul, vrând să afle dulceaţa totală a aluatului din lingură. Această valoare se calculează ı̂n felul
următor: dacă lingura trece prin zone de dulceaţă d1 pentru t1 secunde, de dulceaţă d2 pen-
tru t2 secunde, ..., şi de dulceaţă dk pentru tk secunde, atunci dulceaţa totală din lingură este
t1 d1 t2 d2 ... tk dk .
Nu se modifică dulceaţa din container.
Cerinţe
Dându-se toate operaţiile ı̂ntreprinse ı̂n producerea desertului, să se găsească dulceţile totale
ce sunt găsite la toate operaţiile de degustare.
Date de intrare
Date de ieşire
În fişierul de ieşire dulciuri.out, să se afişeze toate rezultatele degustărilor, ı̂n ordine, câte
una pe linie.
11
CAPITOLUL 2. OJI 2022 2.1. DULCIURI 12
Rezultatul unei degustări se consideră a fi corect dacă eroarea absolută sau relativă faţă de
7 29
soluţia comisiei este cel mult 10 .
# Punctaj Restricţii
1 20 Nu se fac ı̂ndulciri orizontale. Q & 2 000.
¬ ¬
2 20 Pentru fiecare degustare, fie xq xq sau yq yq . Q & 2 000
3 10 Se face cel mult o degustare
4 20 Toate degustările se fac după toate ı̂ndulcirile
5 10 Q & 2 000
6 20 Fără restricţii suplimentare
Exemple:
duclciuri.in dulciuri.out
3 35
1 2 60
2 3 60
30034
4 10
1 2 10 0
32021 10
33031
32020
6 128.3076923077
1 4 413 29.0881226054
1 3 234
2 5 244
2 3 777
3 1 2 14 15
3 31 4 2 40
Explicaţii
Situaţia pentru degustarea din primul exemplu este explicată ı̂n diagrama de mai jos.
Dacă soluţiile comisiei şi a concurentului sunt s , s atunci eroarea absolută este ¶s s¶ şi eroarea relativă este
29
¶s s¶©¶s ¶
CAPITOLUL 2. OJI 2022 2.1. DULCIURI 13
Zonele roz sunt zonele ı̂n care s-a aplicat o ı̂ndulcire, şi numerele reprezintă cu cât s-a ı̂ndulcit.
Zona din intersecţia ı̂ndulcirilor
Ó are dulceaţa 120. Linia diagonală punctată reprezintă traseul.
Traseul are lungimea 3 42 5, şi este completatı̂n o secunda-astfel are viteza de 5 unităţi
2
pe secundă. Ô
Ô Segmentul de la 2, 2. 6 la 2.25, 3 are lungimea 2.25 22 2. 6 32 =
2 2
1©4 1©3 = 5©12, şi are dulceaţa 60 - astfel el este traversat ı̂n 5©12 1©5 1©12
secunde, şi contribuie cu 1©12 60 5 la dulceaţaÔtotală.
Segmentul de la 2.25, 3 la 3, 4 are lungimea 3 2.252 4 32 5©4, şi are dulceaţa
120 - astfel el este traversat ı̂n 5©4 1©5 1©4 secunde, şi contribuie cu 1©4 120 30 la
dulceaţa totală.
Astfel, cum segmentul de la 0, 0 la 2, 2. 6 contribuie cu 0, dulceaţa totală este 35.
Situaţia pentru degustările din al doilea exemplu este explicată ı̂n diagrama de mai jos.
În primul traseu (cel din stânga) trecem mereu printr-o zonă cu dulceaţa 10, deci rezultatul
degustării este 10. În al doilea traseu (cel din dreapta) trecem mereu printr-o zonă cu dulceaţa 0,
deci rezultatul degustării este 0. În al treilea traseu, stăm pe loc pentru o secundă ı̂ntr-o zona de
dulceaţă 10, deci răspunsul este 10.
Propusă de: stud. drd. Tamio-Vesa Nakajima, Facultatea de Informatică, Universitatea Oxford
Subtaskul 1-20 puncte. Pentru primul subtask, observăm că soluţia pentru o degustare este
dată de raportul dintre suma ı̂ndulcirilor care au afectat acea degustare, şi distant,a orizontală pe
care o parcurge degustarea. Prima valoare se poate calcula efectiv parcurgând ı̂ndulcirile relevante.
Subtaskul 2-20 puncte. Pentru al doilea subtask, observăm ca soluţia pentru o interogare
verticală este dată de suma ı̂ndulcirilor verticale care afectează acea interogare, plus rezultatul
de la subtaskul 1. Acestea se pot calcula asemănător cu subtaskul 1 - parcurgând efectiv toate
ı̂ndulcirile de până acum. Totodată o interogare orizontală se rezolvă analog cu o interogare
verticală.
Subtaskul 3-10 puncte. Vom face acum câteva observaţii suplimentare.
Observatia 1. Dulceaţa totală este egală cu dulceaţa datorată ı̂ndulcirilor verticale plus
dulceaţa datorată ı̂ndulcirilor orizontale.
Astfel, la fiecare pas ne interesează doar dulceaţa datorată ı̂ndulcirilor orizontale sau verticale
separat - ı̂n alte cuvinte, ı̂ndulcirile verticale şi orizontale sunt independente. Vom descrie cum
se calculează dulceată datorată ı̂ndulcirilor verticale, cele orizontale tratându-se analog. Aşadar,
care este această dulceaţă?
¬ ¬
Observatia 2. Considerăm o interogare de la x, y la x , y . Fie vi suma tuturor ı̂ndulcirilor
¬ ¬
verticale pentru fâşia i & x $ i 1. Presupunem fără pierdere de generalitate că xx . Dacă x x
atunci dulceaţa datorată ı̂ndulcirilor verticale este vx ; altfel este:
< ¬
x 1
vi
i x
x¬ x
CAPITOLUL 2. OJI 2022 2.2. INVESTIŢIE 14
Pentru al treilea subtask, valoarea din observaţia 2 se poate calcula efectiv, iterând prin toate
ı̂ndulcirile.
Subtaskul 4-20 puncte. Pentru al patrulea subtask, valoarea din observaţia 2 se poate
30
calcula folosind sume parţiale peşirul v. Această tehnică se mai numes, te şmenul lui Mars .
Subtaskul 5-10 puncte. Pentru acest subtask observăm că şirul valorile din observaţia 2 se
pot calcula parcurgând toate ı̂ndulcirile precedente.
Soluţie completă. În acest subtask, pentru a calcula valorile din observaţia 2, menţinem o
structură de date ce va reprezenta secvenţa v. Această structură de date trebuie să poată simula
incrementarea unui element vi , şi trebuie să poată calcula suma unei subsecvenţe din şirul vi . Mai
multe structuri de date pot realiza acest lucru suficient de eficient pentru Ó
rezolvarea problemei:
arbori indexaţi binari, arbori de intervale sau ı̂mpărţirea ı̂n bucăţi de n.
2.2 investiţie
Problema 2 - Investiţie 100 de puncte
După o lungă activitate ı̂n domeniul instalaţiilor sanitare, Dorel s-a hotărât să investească
averea acumulată ı̂n acţiuni ale mai multor companii. Astfel, el dispune de o listă cu N companii
la care vrea să cumpere acţiuni, ı̂n M zile consecutive.
În prima zi, suma de bani investită ı̂n compania i este s1i ai, pentru orice i 1, N ,
unde valorile ai sunt date.
Numerele a1, a2, ..., aN reprezintă o permutare a numerelor 1, 2, ..., N .
În ziua a j-a el va investi ı̂n compania i o sumă de bani egală cu sj i sj 1ai,
pentru orice zi j 2, M şi orice companie i 1, N .
Cerinţe
După finalizarea planului de investiţii, Dorel vrea să realizeze Q statistici referitoare la sumele
investite.
Fiind date Q seturi de valori zi , zf , cl , cr , el doreşte să afle ce sumă a investit ı̂n perioada
cuprinsă ı̂ntre zilele zi şi zf (inclusiv acestea), la companiile cu numere de ordine cuprinse ı̂ntre
cl şi cr (inclusiv acestea).
Date de intrare
Pe prima linie a fişierului de intrare investitie.in se află numerele N şi M , separate prin
spaţiu.
Pe a doua linie se află valorile a1, a2, ..., aN , separate prin spaţiu.
Pe a treia linie se află valoarea lui Q.
Pe următoarele Q linii se află câte patru valori, zi , zf , cl , cr , separate prin spaţiu.
Date de ieşire
În fişierul de ieşire investitie.out se vor afişa, pe linii diferite, sumele investite corespunzătoare
fiecăreia din cele Q statistici din fişierul de intrare.
# Punctaj Restricţii
1 10 M 1
2 20 1 & N, M & 100, 1Q 1 000
3 12 101 & N, M & 3 000
4 24 1 & N & 50
5 34 Fără alte restricţii
Exemple:
Propusă de: prof. Mihai Bunget, Colegiul Naţional ”Tudor Vladimirescu”, Târgu Jiu
Rezolvarea problemei presupune cunoştinţe despre sume parţiale pe vector, respectiv matrice,
puterile unei permutări, ciclurile unei permutări, ordinul unei permutări.
Ideea principală pentru rezolvarea acestei probleme, este să observăm că liniile matricei s sunt
de fapt puterile permutării a.
Într-adevăr, avem s1i ai, s2i s1ai aai a2i, ¾i 1, N .
k k
Inductiv, presupunând că sk i a i, deducem că sk 1i sk ai a ai
k1
a i, ¾i 1, N .
Cum liniile matricei s sunt puterile permutării a, puterile unei permutări fiind ı̂n număr infinit
ı̂nsă numărul permutărilor de ordin N este finit (N !), rezultă că deducem că va exista o putere
k1
a care coincide cu permutarea identică a1 , urmând ca mai apoi liniile matricei să se repete (să
cicleze) cu o perioadă k. În cazul ı̂n care k e minim cu această proprietate, el se numeşte ordinul
lui a.
O altă observaţie este faptul că orice permutare se poate descompune ı̂ntr-un produs de cicluri
disjuncte. Un ciclu de lungime k este o secvenţă i1 , i2 , ..., ik astfel ı̂ncât ai1 i2 , ai2 i3 , ...,
aik1 ik ,aik i1 .
Această descompunere ı̂n cicluri se poate face astfel: pentru un indice i care nu a fost procesat
aflăm ai, aai, aaai, ... până când obţinem valoarea i. Valorile astfel obţinute formează un
ciclu, numărul valorilor reprezentând lungimea ciclului. Se poate vedea uşor, prin calculul puterilor
sale, că ordinul unui ciclu este egal cu lungimea ciclului. De asemenea, ordinul unei permutări
este egal cu cel mai mic multiplu comun al lungimilor ciclurilor disjuncte din descompunerea
permutării.
De exemplu, pentru permutarea a 3, 4, 5, 2, 1, dscompunerea ı̂n cicluri o obţinem astfel:
pentru i 1 avem ai 3, aai 5, aaai 1 şi astfel obţinem primul ciclu: 1, 3, 5.
CAPITOLUL 2. OJI 2022 2.2. INVESTIŢIE 16
Primul indice neprocesat este 2, pentru care avem: i 2, ai 4, aai 2 şi obţinem ciclul
(2,4).
Deoarece lungimile celor două cicluri sunt 3, respectiv 2, deducem că ordinul permutării este 6.
Subtaskul 1 - 10 puncte. Pentru a putea calcula ı̂n O 1 cele Q statistici, se precalculează
sumele parţiale pentru vectorul a.
Se formează un nou vector b, astfel ı̂ncât bi < j
i
1 aj , calculul unei statistici pe intervalul
de indici cl , cr presupunând calculul bcr bcl1 .
Complexitate O N Q.
Notă. Pentru a rezolva acest subtask, putem să calculăm statistica cerută iterativ, prin
parcurgerea secvenţei cl , cr , deoarece cr cl & 100.
Subtaskul 2 - 20 puncte. Se generează matricea s a sumelor investite ı̂n cele M zile, apoi
pentru a afla o statistică se calculează suma elementelor submatricei determinată de setul de valori
zi , zf , cl , cr . Aici se aplică metoda ”Brutus”.
Complexitate O M N Q M 100.
Subtaskul 3 - 12 puncte. Se generează matricea s a sumelor investite ı̂n cele M zile şi se
calculează sumele parţiale 2D ale acestei matrice. Fiecare statistică se va calcula ı̂n O 1.
Dacă notăm cu sp matricea sumelor parţiale, atunci ea se poate calcula folosind recurenţa
Complexitate O M N Q.
Notă. Pentru a rezolva acest subtask, se pot calcula sumele parţiale doar pe coloane, iar
pentru a calcula o statistică se află suma pentru fiecare coloană ı̂n parte.
Subtaskul 4 - 24 puncte. Deoarece N are valoare mică, iar liniile matricei s reprezintă
puterile permutării a, sirul a se va repeta ı̂n matricea s după un număr relativ mic de zile.
Intuitiv, dacă N = 50 = 2 + 3 + 5 + 7 + 11 + 13 + 9, avem cmmmc(2,3,5,7,11,13,9) = 90090,
valoarea maximă a ordinului unei permutări de lungime 50 fiind 180180.
Ordinul maxim al unei permutări de lungime N este notat g N şi se numeşte funcţia lui
31
Landau (A000793, OEIS) . Pentru N 50 avem g 50 180180.
Pentru matricea generată până la repetarea şirului a se vor calcula sumele parţiale 2D. Cum M
este mult mai mare, se va folosi periodicitatea acestei matrice, adică se va afla de câte ori se repetă
ı̂n intervalul zi , zf matricea generată, la care se adaugă un rest ce nu completează o matrice
ı̂ntreagă.
Complexitate O N ord a Q, unde ord a este ordinul permutării a.
Notă. Pentru a rezolva acest subtask, nu este necesar să ştim care este ordinul maxim posibil,
ci doar să observăm că liniile matricei ciclează.
Subtaskul 5 - 34 puncte. Pe lângă ideea principală enunţată anterior, mai trebuie observat
că pe fiecare coloană a matricei s se repetă periodic aceleaşi elemente, mai exact elementele
permutării a care formează un ciclu. Într-adevăr, elementele matricei s situate pe coloana i
sunt, ı̂n ordine, ai, aai, aaai, ..., i, care apoi se repetă.
Astfel, vom descompune mai ı̂ntâi permutarea a ı̂n cicluri, pentru fiecare element al ciclu-
lui reţinând poziţia sa ı̂n ciclu. De asemenea, pentru fiecare ciclu vom face sumele parţiale ale
elementelor sale.
Pentru a calcula o statistică, vom afla pentru fiecare coloană cuprinsă ı̂ntre cl şi cr suma
elementelor cuprinse ı̂ntre zilele zi şi zf . Cum aceste elemente sunt elementele unui ciclu care
31
https://oeis.org/A000793
CAPITOLUL 2. OJI 2022 2.3. SUPERHEDGY 17
se repetă de mai multe ori, vom afla de câte ori se repetă şi vom multiplica acest număr cu
suma elementelor ciclului, rămânând şi un rest care se va calcula prin aflarea poziţie ı̂n ciclu a
elementelor rămase. Se vor ı̂nsuma rezultatele obţinute pentru fiecare coloană ı̂n parte, dintre
coloanele cu indicii de ordine de la cl la cr .
Complexitate O N Q 100.
2.3 superhedgy
Problema 3 - SuperHedgy 100 de puncte
Ariciul Găluşcă este un arici obis,nuit pe timp de zi. Noaptea, ı̂nsă, el este de fapt eroul
misterios al oraşului Hedgytown - un oras,mai special, deoarece are clădiri atât deasupra solului,
cât şi sub pamânt, unde gravitat, ia este inversată.
Oraşul poate fi văzut ca o dreaptă (ce reprezintă solul), cu un şir de clădiri dreptunghiulare
lipite deasupra solului, şi un şir de clădiri dreptunghiulare lipite dedesubtul solului. Sunt N clădiri
peste pământ şi M sub pământ. Cele două şiruri ı̂ncep şi se termină la aceleaşi poziţii. Fiecare
clădire este caracterizată de trei valori: L, H şi E. L reprezintă lăţimea clădirii, H reprezintă
ı̂nălţimea clădirii şi E reprezintă efortul necesar pentru a folosi liftul din acea clădire.
Iniţial, Găluşcă se afla pe partea din stânga a oraşului, la nivelul solului (ı̂n dreptul primei
clădiri) şi trebuie să ajungă pe partea din dreapta a oraşului, tot la nivelul solului (ı̂n dreptul
ultimei clădiri). În acest scop, el se poate deplasa pe contururile clădirilor, consumând o unitate
de efort pentru a se deplasa o unitate pe conturul unei clădiri - sau folosind lifturile.
O deplasare cu liftul este mereu de pe conturul unei clădiri până pe conturul clădirii aflate pe
cealaltă parte a solului - cu alte cuvinte, când foloseşte liftul, ariciul nu are voie să se deplaseze
doar până la sol sau să se oprească ı̂n interiorul clădirii. În cazul ı̂n care clădirea pe care se află
ariciul necesită efortul E pentru a folosi liftul şi clădirea de pe partea opusă a solului necesită
¬ ¬
efortul E , atunci efortul total necesar pentru a folosi liftul este E E .
Deplasarea cu liftul se efectuează vertical. Astfel, liftul ı̂l va lăsa pe Găluşcă la aceeaşi distanţă
orizontală faţă de ı̂nceputul oraşului, doar că pe acoperişul clădirii opuse.
Liftul nu poate fi folosit unde se ı̂ntâlnesc două clădiri adiacente orizontal - nici dacă se ı̂ntâlnesc
pe partea solului de unde ı̂ncepe deplasarea, nici dacă se ı̂ntâlnesc pe partea solului unde se termină
deplasarea.
Atenţie, cum Găluşcă este un erou, acesta nu va merge niciodată ı̂napoi - doar va ı̂nainta sau
va folosi lifturile.
Cerinţe
Se cere să se afle numărul minim de unităţi de efort pe care trebuie să le depună ariciul Găluşcă
pentru a ajunge la destinaţia sa.
Date de intrare
Pe următoarele M linii ale fişierului de intrare se dau L, H, şi E, reprezentând datele clădirilor
de dedesubtul solului, ı̂n ordinea ı̂n care sunt plasate ı̂n oraş, ı̂ncepând cu cea mai apropiată clădire
de Găluşcă.
Date de ieşire
În fişierul de ieşire superhedgy.out se va afişa un singur număr natural, reprezentând numărul
minim de unităţi de efort pe care trebuie să le depună Găluşcă pentru a ajunge la destinaţie.
# Punctaj Restricţii
1 20 1 & LT otal & 10
2 20 E 0 pentru toate clădirile din oraş, 1 & LT otal & 100 000
3 40 1 & LT otal & 100 000
4 20 Fără restricţii suplimentare
Exemple:
superhedgy.in superhedgy.out
3 13
125
311
231
4
1 4 10
231
121
211
Explicaţii
Oraşul va arăta ca ı̂n figura de mai jos:
Atenţie! Deplasarea cu liftul nu s-ar fi putut efectua mai la stânga sau mai la dreapta cu 0.5
unităţi.
Propusă de: stud. Mihaela Cismaru, NetRom Software, Universitatea din Craiova
Subtaskul 1 - 20 puncte. Pentru 20 de puncte este suficientă calcularea tuturor traseelor
posibile şi a efortului depus pentru acestea prin metoda backtracking. Se afişează minimul dintre
acestea.
Subtaskul 2-20 puncte. Pentru alte 20 de puncte este necesară o implementare de com-
plexitate O LT otal . Dat fiind că efortul de a folosi liftul va fi mereu 0 putem alege fără ezitare să
schimbăm partea oraşului ı̂n care ne aflăm pentru ca următoarea miscare să aibă valore minimă.
Pentru aceast subtask functionează o abordare de tip greedy.
Subtaskul 3 - 40 puncte. Pentru alte 40 de puncte este necesară o implementare tot de
complexitate O LT otal dar de această dată costul lifturilor poate fi nenul, astfel folosirea lifturilor
nu este mereu optimă. Soluţia va fi implementarea unei dinamici.
Calculăm, pentru 1 & i & N şi 1 & j & M:
Dsus i = efortul minim de a ajunge pe pozitia i in partea de sus a orasului,
Djos i = efortul minim de a ajunge pe pozitia i in partea de jos a orasului.
Pentru a calcula aceste valori vom avea, pentru 1 & i & N şi 1 & j & M , formulele:
¬
Dsus i min Dsus i 1 1, Djos i Ei Ei ,
¬
Djos i min Djos i 1 1, Dsus i Ei Ei .
Subtaskul 4-20 puncte. Pentru alte 20 de puncte este necesar calcularea efortului ı̂n com-
plexitate de doar O N M . Putem realiza acest lucru prin observatia ca putem pastra ı̂n memorie
doar portiunile relevante din oraş, anume portiunile unde se termina şi incepe o nouă clădire.
3.1 Polihroniade
Problema 1 - Polihroniade 100 de puncte
O matrice pătratică de dimensiuni N N cu N par şi elemente din
mulţimea r0, 1x se numeşte tablă de şah dacă oricare două celule vecine
pe o linie sau pe o coloană au valori diferite (cu alte cuvinte, dacă nu
există două valori egale alăturate).
32
De ziua ei, Victor i-a cumpărat Elisabetei o astfel de matrice A,
care nu este neapărat tablă de şah. Aflând despre pasiunea ei, acesta
vrea acum să transforme matricea A ı̂ntr-o tablă de şah. Timpul fiind
limitat, el poate efectua doar următoarele tipuri de operaţii asupra
matricei:
1. Interschimbă liniile i şi j din A (celelalte linii rămân neschimbate,
iar valorile din interiorul liniilor i şi j rămân neschimbate şi ı̂şi
păstrează ordinea). Operaţia are sens pentru 1 & i, j & N .
Figura 3.1: Elisabeta
2. Interschimbă coloanele i şi j din A (celelalte coloane rămân
Polihroniade, 1979 ı̂n
neschimbate, iar valorile din interiorul coloanelor i şi j rămân
Rio de Janeiro
neschimbate şi ı̂şi păstrează ordinea). Operaţia are sens pentru
1 & i, j & N .
Dorind să o impresioneze pe Elisabeta, Victor apelează la voi, programatori renumiţi, să ı̂l
ajutaţi ı̂n a transforma matricea A ı̂ntr-o tablă de şah. Pentru aceasta, Victor are nevoie de
următoarele informaţii:
1. Poate fi transformată matricea A ı̂n tablă de şah?
2. Care este numărul minim de operaţii necesare pentru a duce la ı̂ndeplinire acest scop?
3. Care ar fi o succesiune de operaţii care transformă matricea A ı̂ntr-o tablă de şah?
În cazul ultimei cerinţe, pentru a intra ı̂n graţiile lui Victor va trebui ca numărul de operaţii
efectuate să fie minim. Totuşi, chiar şi un număr neminim de operaţii va fi răsplătit, ı̂nsă nu
ı̂ntr-atât de mult.
Cerinţe
Vi se dau două numere P, T şi matrice A, reprezentând mai multe instanţe ale problemei.
Pentru fiecare matrice A dintre cele T va trebui să rezolvaţi cerinţa cu numărul P " r1, 2, 3x
dintre cele listate mai sus.
Date de intrare
Pe prima linie se găsesc două numere ı̂ntregi pozitive P şi T , reprezentând numr̆ul cerinţei de
rezolvat şi, respectiv, numărul de scenarii pentru care va trebui să rezolvaţi problema.
Urmează cele T instanţe ale problemei, fiecare fiind compusă din N 1 linii: pe prima linie se va
afla numarul N , iar pe următoarele N linii câte N cifre binare neseparate prin spaţii, reprezentând
câte o linie a matricei A.
32
https://ro.wikipedia.org/wiki/Elisabeta_Polihroniade
20
CAPITOLUL 3. OJI 2021 - OSEPI 3.1. POLIHRONIADE 21
Date de ieşire
Pentru fiecare dintre cele T instanţe se va afişa răspunsul, ı̂ncepând de la o linie nouă, după
cum urmează:
1. Dacă P 1, atunci se va afişa pe o singură linie 1 dacă matricea A poate fi transformată ı̂n
tablă de şah, şi 0 altfel.
2. Dacă P 2, atunci se va afişa pe o singură linie un ı̂ntreg reprezentând numărul minim de
interschimbări de linii şi/sau coloane necesare pentru a transforma matricea A ı̂n tablă de
şah.
3. Dacă P 3, atunci se va afişa pe o linie un număr X. Apoi, pe fiecare dintre următoarele
X linii se va afişa câte o interschimbare de linii sau coloane, după următorul format: L i j
are semnificaţia că liniile i şi j se interschimbă, iar C i j are semnificaţia că coloanele i şi j
se interschimbă. Matricea obţinută după aplicarea celor X operaţii asupra lui A ı̂n ordinea
dată trebuie să fie o tablă de şah.
Pentru cerinţele de tip P 2 şi P 3 se garantează că matricea A poate fi transformată ı̂n
tablă de şah folosind interschimbări de linii şi/sau coloane.
Suma valorilor N pentru cele T scenarii nu depăşeşte 2 000.
Pentru 40 de puncte
P 1
Dacă numărul X de operaţii folosite nu este minim, atunci se acordă 50% din punctajul pe
test.
Exemple:
de puncte pe problemă (credem noi, aceasta este cea mai dificilă parte a problemei, şi este, prin
urmare, răsplătită generos ca punctaj).
1.3 Transformarea matricei A ı̂n tabla de şah cu număr minim de operaţii
Pentru restul probemei, facem presupunerea că matricea A se poate transforma ı̂n tabla de
şah, aşa cum este specificat, de altfel, şi ı̂n enunţul problemei.
Cheia rezolvării stă ı̂n următoarea observaţie: atâta timp cât aranjăm ca prima linie a matricei
A să alterneze (0101...01 sau 1010...10) şi ca prima coloană a matricei A să alterneze, restul matricei
va fi, ı̂n mod garantat, o tablă de şah (acest lucru rezultă din observaţia de mai sus legată de tipul
liniilor: avem N2 linii de fiecare tip, şi tipurile sunt complementare; similar pentru coloane).
Mai sus am stabilit că liniile şi coloanele sunt independente, deci cele două probleme se pot
trata separat.
De asemenea, ele se pot reformula mai simplu: dat fiind un şir binar de lungime N format
din N2 caractere 0 şi N2 caractere 1, transformaţi-l ı̂ntr-un şir alternant folosind un numr̆ minim de
operaţii de interschimbare a două caractere.
Există doar două şiruri binare alternante de acest tip: 0101...01 şi 1010...10. Fără a re-
strânge generalitatea, vom ı̂ncerca să ı̂l obţinem pe primul, dar pentru o soluţie completă trebuiesc
ı̂ncercate ambele şi ı̂ntoarsă soluţia care duce la un număr minim de operaţii.
În şirul 0101...01 observăm că toate caracterele 0 sunt pe poziţii pare şi toate caracterele 1
sunt pe poziţii impare. În şirul nostru pe care dorim să ı̂l aducem la această formă există trei
tipuri de caractere:
1. Caractere care sunt la locul lor - adică 0 pe poziţii pare şi 1 pe poziţii impare.
2. Caractere 0 care sunt pe o poziţie impară (deci trebuie obligatoriu să facă parte din cel puţin
o interschimbare). Să notăm mulţimea poziţiilor de acest tip din şirul nostru cu S0 .
3. Caractere 1 care sunt pe o pozitie pară (deci trebuie obligatoriu să facă parte din cel puţin
o interschimbare). Să notăm mulţimea poziţiilor de acest tip din şirul nostru cu S1 .
Reiterând, poziţiile din S0 < S1 trebuie să facă parte dintr-o interschimbare pentru a le aduce
pe o poziţie cu paritatea corectă.
Observăm că sunt exact atâtea caractere 0 care nu sunt la locul lor câte caractere 1 nu sunt
la locul lor (notăm pentru aceasta că numărul de caractere 0 şi de caractere 1 este egal), deci
¶S0 ¶ ¶S1 ¶. Prin urmare, cele de mai sus ne spun că avem nevoie de cel puţin ¶S0 ¶ interschimbări
pentru a rezolva problema. Mai mult, o soluţie cu fix ¶S0 ¶ interschimbări se poate construi:
interschimbăm pe rând câte o poziţie din S0 cu una din S1 până când epuizăm ambele mulţimi
(fiecare interschimbare practic pune la locul lor două caractere). Aşadar, această soluţie este şi
optimă din punctul de vedere al numărului de interschimbări. Bineı̂nteles, trebuie să aplicăm acest
algoritm şi pentru coloane, dar acest lucru nu schimbă conceptual rezolvarea.
În total, soluţia se construieşte ı̂n timp O N după citire. Cum este posibil acest lucru dacă
2
complexitatea de citire a matricei este O N ?
Ei bine, orice matrice care se poate transforma in tabla de şah este unic determinată de prima
sa linie şi prima sa coloană, deci s-ar putea spune că ı̂nsăşi formatul de intrare este inhibitor pentru
obţinerea unei complexităţi timp mai bune (daca ni s-ar fi dat doar prima linie şi prima coloană
problema se putea rezolva ı̂n O N , lucru menţionat aici mai mult ca o curiozitate matematică
decât ca o observaţie necesară pentru rezolvarea problemei). Folosind această soluţie obţinem
restul de 60 de puncte pe problemă.
Obs: https://ebooks.infobits.ro/culegere_OJI_2021.pdf
3.2 Bob
Problema 2 - Bob 100 de puncte
CAPITOLUL 3. OJI 2021 - OSEPI 3.2. BOB 24
Dezamăgiţi de lipsa fotbalului din ultima perioadă, Ştefan şi Georgian şi-au deschis (ı̂n secret)
o afacere cu boabe de cafea, comercializând K tipuri diferite de cafea. Astfel, timp de N zile ei
produc cafea, urmând să formeze din boabele obţinute ı̂n zile consecutive pachete ce conţin toate
tipurile de cafea.
Concret, cei doi ştiu pentru fiecare zi ce tipuri de cafea produc ı̂n acea zi (posibil niciun tip,
caz ı̂n care afacerea ia o pauză), după care ei ı̂mpart zilele ı̂n secvenţe continue astfel ı̂ncât, pentru
fiecare tip de cafea, fiecare secvenţă de zile să conţină cel puţin o zi ı̂n care să fie produs acel tip
de cafea.
Cerinţe
Înainte de a se apuca de ı̂mpachetat boabele, Ştefan şi Georgian ı̂şi pun două ı̂ntrebări:
1. Care este numărul maxim de pachete ce pot fi formate?
2. Care este numărul de moduri de a ı̂mpărţi zilele astfel ı̂ncât să se formeze număr maxim de
pachete valide (ce conţin toate tipurile de cafea)?
Date de intrare
Date de ieşire
Pentru fiecare dintre cele T instanţe se va afişa răspunsul, ı̂ncepând de la o linie nouă, după
cum urmeaza:
1. Dacă P 1, atunci se va afişa pe o singură linie numărul maxim de pachete valide ce pot fi
formate.
2. Dacă P 2, atunci se va afişa pe o singură linie numărul de moduri de a ı̂mpărţi zilele ı̂n
secvenţe continue astfel ı̂ncât să se formeze număr maxim de pachete. Răspunsul va fi afişat
modulo 1 000 000 007.
1&P &2
1&T &3
1 & N & 200 000
1 & K & 20
Se garantează că fiecare tip de cafea apare ı̂n cel puţin una dintre cele N zile.
Punctare
Pentru 6 puncte: P 1, N & 15
Pentru alte 6 puncte: P 1, N & 100
Pentru alte 9 puncte: P 1, N & 2 000
Pentru alte 10 puncte: P 1, N & 200 000
Pentru alte 10 puncte: P 2, K 1, N & 200 000
Pentru alte 4 puncte: P 2, N & 15
Pentru alte 4 puncte: P 2, N & 20
CAPITOLUL 3. OJI 2021 - OSEPI 3.2. BOB 25
Exemple:
ei cel mai micjastfel ı̂ncât intervalul de zilei...jsă conţină fiecare tip de cafea cel puţin o dată
Observaţi cum, de la un punct ı̂ncolo, valoarea ei este nedefinită, deoarce nici măcar intervalul
i...N nu conţine toate tipurile de cafea - ı̂n acest caz luăm ei N 1 prin convenţie , lucru pe
care ı̂l simulăm, pentru simplitate, presupunând existenţa unei zile fictive N 1 ı̂n care se produc
toate tipurile de cafea.
Vom calcula ei ı̂n ordine descrescătoare a zilelor. Mai exact, pe masură ce iterăm i, vom
menţine ı̂ncă un indice j (iniţial egal cu N 1), care va scădea pe masură ce i scade (”tehnica
celor 2 pointeri”). În particular, vom menţine tot timpul invariantul ca intervalul i...j conţine
toate tipurile de cafea cel puţin o dată.
De asemenea, vom reţine un vector de frecvenţă fk = ”ı̂n câte zile din intervalul i...j se produce
tipul de cafea k”, pentru 1 & kleK.
Acum, când ajungem la o nouă valoare a lui i, actualizăm f cu tipurile de cafea din ziua i, şi
apoi ı̂ncercăm să scădem j cât mai mult; adică scădem j cât timp i...j 1 conţine toate tipurile de
cafea (lucru pe care ı̂l verificăm inspectând tipurile de cafea din ziua j şi văzând dacă eliminarea
lor din f ar păstra toate valorile din f pozitive).
Prin construcţie, algoritmul prezentat ne asigură că la finalul fiecărui pas avem j ei .
Pe parcursul execuţiei atât i cât şi j scad de O N ori, deci complexitatea acestui pas din
rezolvare este O N K şi calculează corect vectorul e.
Mai apoi, ne propunem să calculăm
mi numărul maxim de pachete de cafea care se pot obţine considerând doar zilelei...N 1
În mod intuitiv, mN 1 1, deoarce ziua N 1 constituie un pachet de sine stătător. În spiritul
algoritmului Greedy din prima parte a problemei, se remarcă relativ uşor că mi 1 mei 1 , oricare
ar fi 1 & i & N .
Putem, aşadar, calcula vectorul m ı̂n timp O N , iterand i ı̂n ordine descrescătoare.
Bineı̂nţeles, m1 1 reprezintă chiar răspunsul la prima cerinţă, calculat pe o altă cale (scăzătorul
se datorează pachetului fictiv N 1), dar acum avem informaţii suplimentare ce ne vor fi de folos.
În cele din urmă, vrem să calculăm
Deoarece m este monoton descrescător, valorile ei & j & N pentru care mj 1 mi 1 formează
33
un interval j0 , j1 , ale cărui capete pot fi căutate binar ı̂n timp O log N .
Date fiind acestea, putem calcula di prin expresia
j1
di =d. j 1
j j0
2
O implementare naivă a acestei idei duce la o complexitate de O N , şi este insuficientă.
Din fericire, ultimul pas este simplu: suma menţionată poate fi calculată ı̂n O 1 dacă ı̂n timp
ce calculăm valorile di menţinem şi vectorul de sume parţiale pe sufixe ale lui d (suma respectivă
devine apoi o diferenţă a două sume parţiale).
Complexitatea calculului vectorului d este astfel O N log N datorită căutării binare (şi poate
fi adusă la O N folosind ideea mai rafinată din nota de subsol), deci complexitatea pentru toată
problema este O N K N log N , suficientă pentru 100 de puncte.
Obs: https://ebooks.infobits.ro/culegere_OJI_2021.pdf
3.3 Dreptunghi
Problema 3 - Dreptunghi 100 de puncte
Avem la dispoziţie un dreptunghi de dimensiuni N M . Ne este util ca dreptunghiul nostru
să se asemene cu o matrice, de aceea vom considera că are N linii şi M coloane. Vom segmenta şi
numerota dreptunghiul nostru după un anumit cod C. Prin segmentare se ı̂nţelege trasarea unei
linii orizontale sau verticale la o anumită poziţie k, ce va despărţi dreptunghiul nostru ı̂n alte două
dreptunghiuri mai mici:
de dimensiuni k M (cel de sus) şi N k M (cel de jos) - ı̂n cazul unei linii (H) orizontale,
operaţie codificată prin Hk
de dimensiuni N k (cel din stânga) şi N M k (cel din dreapta) - ı̂n cazul unei linii
(V ) verticale, operaţie codificată prin V k
Numerotarea dreptunghiului se realizează cu numerele naturale 1, 2, 3, ..., ı̂n această ordine.
Codul C pentru segmentarea şi numerotarea unui dreptunghi se defineşte recursiv. Dacă C1
şi C2 sunt coduri de segmentare şi numerotare, atunci:
* - ı̂n fiecare căsuţă a dreptunghiului se va scrie valoarea curentă a numerotării. După aceea,
această valoare este incrementată pentru a fi folosită de o ulterioară operaţie de tipul *;
HkC1 C2 - se trasează linia orizontală la poziţia k, se segmentează şi numerotează drep-
tunghiul de sus conform codului C1 , apoi se continuă cu segmentarea şi numerotarea drep-
tunghiului de jos conform codului C2 ;
V kC1 C2 - se trasează linia verticală la poziţia k, se segmentează şi numerotează dreptunghiul
din stânga conform codului C1 , apoi se continuă cu segmentarea şi numerotarea dreptunghi-
ului din dreapta conform codului C2 .
De exemplu, dreptunghiul de dimensiuni 8 6 (8 linii, 6 coloane) segmentat şi numerotat
conform codului C H5H3V 2 V 3 V 5V 2 , va arăta ca ı̂n Figura 1.
33
Pentru cei mai bravi dintre voi, ele pot fi calculate şi ı̂n timp total O N , folosind paradigma celor 2 pointeri,
dar acest lucru nu este necesar pentru punctaj maxim.
CAPITOLUL 3. OJI 2021 - OSEPI 3.3. DREPTUNGHI 28
Cerinţe
Date de intrare
Date de ieşire
Dacă valoarea citită pentru P este 1, atunci la ieşirea standard se va tipări numărul de
subdiviziuni pe care codul C le generează;
CAPITOLUL 3. OJI 2021 - OSEPI 3.3. DREPTUNGHI 29
Dacă valoarea citită pentru P este 2, atunci la ieşirea standard se vor tipări două
numere N şi M separate printr-un spaţiu, dimensiunile unui dreptunghi de arie minimă
pentru care codul C citit este valid. În caz că există mai multe se acceptă oricare;
Dacă valoarea citită pentru P este 3, atunci la ieşirea standard se va tipări numărul
de codificări distincte modulo 1 000 000 007 echivalente cu codul citit (ı̂n acest număr va fi
inclus şi codul C citit).
Dacă valoarea citită pentru P este 4, atunci la ieşirea standard se va tipări primul cod
ı̂n ordine lexicografică echivalent cu cel dat;
Exemple:
Propunător: Szabó Zoltan, Liceul Tehnologic Petru Maior, Reghin / ISJ Mureş, Tg. Mureş
Problema dreptunghi are 4 cerinţe cu diferite grade de dificultate. Însă rezolvarea principală
se bazează pe faptul că observăm că definiţia recursivităţii permite salvarea informaţiilor ı̂ntr-o
structură de arbore binar.
Arborele binar are ca rădăcină informaţia Hk sau V k, apoi se defineşte subarborele stâng, şi
apoi subarborele drept.
Informaţiile din şirul de caractere se găsesc astfel ı̂n preodine, şi putem să construim arborele.
CAPITOLUL 3. OJI 2021 - OSEPI 3.3. DREPTUNGHI 30
Pentru dreptunghiul de mai sus există 4 coduri, aşa cum s-a prezentat ı̂n enunţul problemei.
Fiecărui cod ı̂i corespunde un arbore binar. În continuare vom desena fiecare arbore, iar apoi vom
studia prorpietăţile necesare pentru a rezolva cerinţele problemei.
În continuare prezentăm arborii corespunzători celor 4 coduri de numerotare, luate ı̂n ordine
lexicografică:
1. H3V 2 H2V 3 V 2 V 3
2. H3V 2 H2V 3 V 5V 2
3. H5H3V 2 V 3 V 2 V 3
4. H5H3V 2 V 3 V 5V 2
CAPITOLUL 3. OJI 2021 - OSEPI 3.3. DREPTUNGHI 31
O parte conexă maximală a arborelui ı̂n care toate nodurile sunt etichetate identic cu litera
H respectiv V , au proprietatea că permit rescrierea arborelui pentru o codificare echivalentă,
prin schimbarea raportului Tată-Fiu al rădăcinii şi a descendentului din stânga sau dreapta. Dacă
partea conexă conţine K noduri, atunci numărul arborilor pe care ı̂i putem construi va fi C K ,
fiind numărul lui Catalan de ordin K.
Rezultatul pentru problema noastră va fi produsul numerelor Catalan al numărului de noduri
din fiecare componentă conexă din arbore, etichetate cu aceeaşi literă H sau V .
Întrucât lungimea şirului de caractere ce reprezintă intrarea pentru problema noastră nu
depăşeşte 350 de carctere, rezultă că nu suntem nevoiţi să calculăm optim acest număr, chiar
2
şi o soluţie calculată ı̂n O N va intra satisfăcător ı̂n timpul de execuţie.
Formula propusă este
Obs: https://ebooks.infobits.ro/culegere_OJI_2021.pdf
OJI 2020
4.1 ateleport
Problema 1 - ateleport 90 de puncte
Marian se află ı̂n galaxia OJI-2020 şi este anul 11235. În această galaxie există N planete
diferite şi M canale bidirecţionale de transport de tipul x, y, t care ı̂ţi permit să te deplasezi de
pe planeta x pe planeta y (sau invers) ı̂n t secunde.
Dar Marian este un adevărat inginer şi, pentru că i se pare foarte ineficientă această metodă
de transport, a dezvoltat un dispozitiv care ı̂ţi permite teleportarea de pe o planetă x pe orice
altă planetă y ı̂n P secunde cu condiţia că ai putea ajunge pornind de pe planeta x pe planeta y
folosind maxim L canale de transport.
Acest dispozitiv este momentan doar un prototip, aşa că nu ı̂l poate folosi mai mult de K ori.
Marian se află pe planeta 1 şi te roagă să ı̂i spui care e timpul minim necesar pentru a ajunge pe
planeta N .
Cerinţe
Să se scrie un program care calculează timpul minim necesar pentru a ajunge pe planeta N
pornind de pe planeta 1.
Date de intrare
Date de ieşire
Fişierul de ieşire ateleport.out va conţine o singură valoare pe prima linie care reprezintă
timpul minim necesar pentru a ajunge pe planeta N pornind de pe planeta 1.
a 1 $ N, M & 10 000;
a 0 & L, K & 10;
a 1 $ Ti , P & 100 000;
a 1 $ Xi , Yi & N ;
a ı̂ntre oricare două planete există cel mult un canal;
a pentru teste ı̂n valoare de 30 de puncte se garantează că K 0 şi toate canalele de comunicare
au Ti 1;
a pentru ALTE teste ı̂n valoare de 20 de puncte se garantează că K = 0;
a pentru ALTE teste ı̂n valoare de 20 de puncte se garantează că N & 300;
a se garantează că pentru toate testele există soluţie;
a se acordă 10 puncte din oficiu.
Exemple:
33
CAPITOLUL 4. OJI 2020 4.1. ATELEPORT 34
#include<fstream>
#include<vector>
#include<queue>
ifstream fin("ateleport.in");
ofstream fout("ateleport.out");
struct muchie
{
int nod;
int timp;
int teleport;
};
struct element
{
int nod;
int timp;
int teleportari;
bool operator<(const element &x) const
{
return timp < x.timp;
}
};
struct min_heap
{ /// pentru usurinta se poate folosi priority_queue
element v[1000001];
int size = 0;
element top()
{
return v[1];
}
void pop()
{
if(size == 0)
return;
swap(v[1],v[size]);
size--;
int pos = 1;
while(pos*2+1 <= size)
{
int n,m,p,l,k;
int timp_minim[10001]; // timp_minim[x] = timpul minim pentru a ajunge in
// nodul x (nu ne putem teleporta)
int timp_minim_teleport[10001][11];
vector<muchie> muchii[10001];
min_heap myHeap;
int f[10001];
void adauga_muchii(int nod)
{
queue<pair<int, int> > q;
q.push({nod, 0});
while(q.size())
{
pair<int,int> now = q.front();
q.pop();
if(f[now.first] == nod)
continue;
f[now.first] = nod;
if(now.second < l)
for(int i = 0; i < muchii[now.first].size(); i++)
if(muchii[now.first][i].teleport == 0)
q.push({muchii[now.first][i].nod, now.second + 1});
if(now.first != nod)
{
muchii[nod].push_back(muchie{now.first, p, 1});
muchii[now.first].push_back(muchie{nod, p, 1});
}
}
}
int main()
{
fin >> n >> m >> p >> l >> k;
for(int i = 1; i <= m; i++)
{
int x,y,t;
fin >> x >> y >> t;
muchii[x].push_back(muchie{y,t,0});
muchii[y].push_back(muchie{x,t,0});
}
if(k == 0)
{
for(int i = 1; i <= n; i++)
timp_minim[i] = 2000000000;
timp_minim[1] = 0;
myHeap.push(element{1,0,0});
while(myHeap.size > 0)
{
element x;
x = myHeap.top();
myHeap.pop();
if(x.nod == n)
{
fout << x.timp;
return 0;
}
myHeap.push(y);
}
}
}
}
else
{
for(int i = 1; i <= n; i++)
adauga_muchii(i);
for(int i = 1; i <= n; i++)
for(int j = 0; j <= 10; j++)
timp_minim_teleport[i][j] = 2000000000;
timp_minim_teleport[1][0] = 0;
myHeap.push(element{1,0,0});
while(myHeap.size > 0)
{
element x;
x = myHeap.top();
myHeap.pop();
if(x.nod == n)
{
fout << x.timp;
return 0;
}
return 0;
}
/*
Process returned 0 (0x0) execution time : 1.188 s
*/
#include<fstream>
#include<vector>
ifstream fin("ateleport.in");
ofstream fout("ateleport.out");
struct muchie
{
int nod;
int timp;
};
struct element
{
int nod;
CAPITOLUL 4. OJI 2020 4.1. ATELEPORT 38
int timp;
int muchii_teleportarea_curenta;
int teleportari;
bool operator<(const element &x) const
{
return timp < x.timp;
}
};
struct min_heap
{ /// pentru usurinta se poate folosi priority_queue
element v[1000001];
int size = 0;
element top()
{
return v[1];
}
void pop()
{
if(size == 0)
return;
swap(v[1],v[size]);
size--;
int pos = 1;
while(pos*2+1 <= size)
{
int n,m,p,l,k;
int timp_minim[10001][11][11]; // timp_minim[x][y][z] = timpul minim pentru
// a ajunge in nodul x cu y teleportari si z
// muchii parcurse din teleportarea curenta
vector<muchie> muchii[10001];
min_heap myHeap;
int main()
{
fin >> n >> m >> p >> l >> k;
for(int i = 1; i <= m; i++)
{
int x,y,t;
fin >> x >> y >> t;
CAPITOLUL 4. OJI 2020 4.1. ATELEPORT 39
muchii[x].push_back(muchie{y,t});
muchii[y].push_back(muchie{x,t});
}
myHeap.push(element{1,0,0,0});
timp_minim[1][0][0] = 0;
while(myHeap.size > 0)
{
element x;
x = myHeap.top();
myHeap.pop();
if(timp_minim[x.nod][x.teleportari][x.muchii_teleportarea_curenta] <
x.timp)
continue;//am ajuns intr-o stare in care am mai fost cu un timp mai mic
if(x.nod == n)
{
fout << x.timp;
return 0;
}
return 0;
}
/*
Process returned 0 (0x0) execution time : 0.922 s
*/
#include <stdio.h>
#include <string.h>
int q[kQueueSize];
int d[kMaxN][kMaxSkipSize + 1][kMaxNumSkips + 1];
char in_queue[kMaxN][kMaxSkipSize + 1][kMaxNumSkips + 1];
void read_graph()
{
FILE* f = fopen("ateleport.in", "r");
int num_edges;
fscanf(f, "%d%d%d%d%d", &num_nodes, &num_edges, &skip_time,
&mx_edges_skipped, &skip_limit);
for (int i = 0; i < num_edges; ++i)
{
int u, v, t;
fscanf(f, "%d%d%d", &u, &v, &t); --u; --v;
edge_nodes[i << 1 | 0] = u;
edge_nodes[i << 1 | 1] = v;
initial_edge_time[i] = t;
++count_edges[u];
++count_edges[v];
}
int solve()
{
CAPITOLUL 4. OJI 2020 4.1. ATELEPORT 41
d[0][0][0] = 0;
while (head != tail)
{
int u, skip_size, used_skips;
unpack(q[head++ & (kQueueSize - 1)], &u, &skip_size, &used_skips);
in_queue[u][skip_size][used_skips] = 0;
if (u == num_nodes - 1)
{
update_if_less(d[u][skip_size][used_skips], &answer);
}
int main()
{
read_graph();
print(solve());
return 0;
}
/*
Process returned 0 (0x0) execution time : 7.984 s
*/
#include <bits/stdc++.h>
struct edge
{
int node, cost;
};
CAPITOLUL 4. OJI 2020 4.1. ATELEPORT 42
vector<edge> g[MaxN];
struct inHeap
{
int node, k, l, cost;
bool operator <(const inHeap& aux) const
{
return cost > aux.cost;
}
};
priority_queue<inHeap> q;
int din[MaxN][11][11];
int N, M, P, L, K;
void dijkstra()
{
for (int i = 1; i <= N; i++)
for (int k = 0; k <= K; k++)
for (int l = 0; l <= L; l++)
din[i][k][l] = inf;
din[1][0][0] = 0;
q.push({1, 0, 0, 0});
while (!q.empty())
{
auto aux = q.top();
q.pop();
int node = aux.node;
int k = aux.k;
int l = aux.l;
int cost = aux.cost;
if (din[node][k][l] != cost) continue;
if (k < K)
{
int cost1 = cost;
if (l > 0) cost1 += P;
update(node, k + 1, 0, cost1);
}
for (auto e : g[node])
{
int node1 = e.node;
if (l < L)
{
update(node1, k, l + 1, cost);
}
if (l == 0)
{
int cost1 = cost + e.cost;
update(node1, k, 0, cost1);
}
}
}
}
int main()
{
ifstream fin("ateleport.in");
ofstream fout("ateleport.out");
dijkstra();
fout << din[N][K][0] << "\n";
return 0;
}
/*
Process returned 0 (0x0) execution time : 1.672 s
*/
///Dijkstra
#include <fstream>
ifstream fin("ateleport.in");
ofstream fout("ateleport.out");
int N,M,P,L,K,k,i,x,y,z,c,t,nh,x1,y1,z1,c1;
int start[10002],d[10002][11][11],poz[10002][11][11];
char viz[10002][11][11];
struct legatura
{
int vecin,cost,leg;
} V[20002];
struct nodh
{
int v,k,l,c;
} H[1000002];
void urcare(int q)
{
while(q>=2 && H[q/2].c>H[q].c)
{
poz[H[q].v][H[q].k][H[q].l]=q/2;
poz[H[q/2].v][H[q/2].k][H[q/2].l]=q;
nodh x=H[q/2]; H[q/2]=H[q]; H[q]=x;
q=q/2;
}
}
void coborare(int q)
{
while(2*q<=nh)
{
int r=2*q;
if(r+1<=nh && H[r].c>H[r+1].c)r++;
if(H[q].c>H[r].c)
{
CAPITOLUL 4. OJI 2020 4.1. ATELEPORT 44
poz[H[q].v][H[q].k][H[q].l]=r;
poz[H[r].v][H[r].k][H[r].l]=q;
nodh x=H[q]; H[q]=H[r]; H[r]=x;
q=r;
}
else break;
}
}
int main()
{
fin>>N>>M>>P>>L>>K;
k=0;
for(i=1;i<=M;i++)
{
fin>>x>>y>>t;
k++; V[k]={y,t,start[x]}; start[x]=k;
k++; V[k]={x,t,start[y]}; start[y]=k;
}
for(x=1;x<=N;x++)
{
for(y=0;y<=K;y++)
{
for(z=0;z<=L;z++)
{
d[x][y][z]=1000000000;
}
}
}
d[1][0][0]=0;
nh=1; H[1]={1,0,0,0};
poz[1][0][0]=1;
viz[1][0][0]=1;
do
{
x=H[1].v;
y=H[1].k;
z=H[1].l;
c=d[x][y][z];
viz[x][y][z]=0;
H[1]=H[nh];
poz[H[nh].v][H[nh].k][H[nh].l]=1;
nh--;
coborare(1);
for(int i=start[x];i;i=V[i].leg)
{
///fara teleportare
x1=V[i].vecin;c1=V[i].cost;
if(z==0){y1=y;z1=0;}
else {y1=y+1;z1=0;}
if(c+c1<d[x1][y1][z1])
{
d[x1][y1][z1]=c+c1;
if(viz[x1][y1][z1]==0)
{
nh++; H[nh]={x1,y1,z1,c+c1};
poz[x1][y1][z1]=nh;
urcare(nh);
viz[x1][y1][z1]=1;
}
else
{
H[poz[x1][y1][z1]].c=c+c1;
urcare(poz[x1][y1][z1]);
}
}
if(K>0)
{
///cu teleportare
if(y==K)continue;
if(z==0)c1=P;
else c1=0;
CAPITOLUL 4. OJI 2020 4.1. ATELEPORT 45
y1=y;z1=z+1;
if(z1==L)
{
z1=0;
y1=y+1;
}
if(c+c1<d[x1][y1][z1])
{
d[x1][y1][z1]=c+c1;
if(viz[x1][y1][z1]==0)
{
nh++; H[nh]={x1,y1,z1,c+c1};
poz[x1][y1][z1]=nh;
urcare(nh);
viz[x1][y1][z1]=1;
}
else
{
H[poz[x1][y1][z1]].c=c+c1;
urcare(poz[x1][y1][z1]);
}
}
}
}
} while(nh>0);
int vmin=d[N][K][0];
for(y=0;y<=K-1;y++){
for(z=0;z<=L-1;z++){
vmin=min(vmin,d[N][y][z]);
}
}
fout<<vmin<<"\n";
fout.close(); fin.close();
return 0;
}
/*
Process returned 0 (0x0) execution time : 0.734 s
*/
///Bellman-Ford cu coada
#include <fstream>
ifstream fin("ateleport.in");
ofstream fout("ateleport.out");
int N,M,P,L,K,k,i,x,y,z,c,t,x1,y1,z1,c1,pr,ul,nr;
int start[10002],d[10002][11][11];
char viz[10002][11][11];
struct legatura
{
int vecin,cost,leg;
} V[20002];
struct nodh
{
int v,k,l;
} H[1000002];///coada
int main()
{
fin>>N>>M>>P>>L>>K;
k=0;
for(i=1;i<=M;i++)
{
fin>>x>>y>>t;
CAPITOLUL 4. OJI 2020 4.1. ATELEPORT 46
H[1]={1,0,0};
pr=1;
ul=1;
nr=1;
viz[1][0][0]=1;
do
{
x=H[pr].v;y=H[pr].k;z=H[pr].l;
c=d[x][y][z];
nr--;
pr++;if(pr>1000000)pr=1;
viz[x][y][z]=0;
for(int i=start[x];i;i=V[i].leg)
{
x1=V[i].vecin;c1=V[i].cost;
///fara teleportare
if(z==0){y1=y;z1=0;}
else {y1=y+1;z1=0;}
if(c+c1<d[x1][y1][z1])
{
d[x1][y1][z1]=c+c1;
if(viz[x1][y1][z1]==0)
{
nr++;
ul++; if(ul>1000000)ul=1;
H[ul]={x1,y1,z1};
viz[x1][y1][z1]=1;
}
}
if(K>0)
{
///cu teleportare
if(y==K)continue;
if(z==0)c1=P;
else c1=0;
y1=y;z1=z+1;
if(z1==L)
{
z1=0;y1=y+1;
}
if(c+c1<d[x1][y1][z1])
{
d[x1][y1][z1]=c+c1;
if(viz[x1][y1][z1]==0)
{
nr++;
ul++; if(ul>1000000)ul=1;
H[ul]={x1,y1,z1};
viz[x1][y1][z1]=1;
}
}
}
}
} while(nr>0);
int vmin=d[N][K][0];
CAPITOLUL 4. OJI 2020 4.2. PARTIT 47
for(y=0;y<=K;y++)
{
for(z=0;z<=L;z++)
{
vmin=min(vmin,d[N][y][z]);
}
}
fout<<vmin<<"\n";
fout.close();
fin.close();
return 0;
}
/*
Process returned 0 (0x0) execution time : 4.875 s
*/
4.2 partit
Problema 2 - partit 90 de puncte
O partiţie a unui număr natural n se defineşte ca o mulţime ordonată de numere naturale nenule
p1 , p2 , ..., pk ce conţine cel puţin două elemente, ı̂ndeplinind condiţia: p1 p2 ... pk n.
Să considerăm pentru un număr natural n toate partiţiile luate ı̂n ordine lexicografică.
De exemplu, pentru numărul natural n 4 există 7 partiţii. Le scriem ı̂n ordine lexicografică
ı̂ntr-o listă pe care o vom numi ı̂n continuare tabel lexicografic.
Cerinţe
Date de intrare
Fişierul de intrare partit.in conţine pe prima linie numărul c, reprezentând cerinţa de rezolvat.
Dacă c 1, se va rezolva cerinţa 1, iar dacă c 2, se va rezolva cerinţa 2.
Pe linia a doua se găseşte valoarea lui n - numărul pe care trebuie să ı̂l descompunem.
Pe linia a treia, ı̂n funcţie de valoarea lui c, putem avea
- dacă c 1, pe linia 3 se găseşte un număr natural k, reprezentând un număr de ordine,
- dacă c 2, pe linia 3 se găsesc numere naturale separate prin câte un spaţiu, reprezentând
o partiţie a numărului n.
Date de ieşire
Fişierul de ieşire partit.out va avea următorul conţinut ı̂n funcţie de valoarea lui c:
CAPITOLUL 4. OJI 2020 4.2. PARTIT 48
- dacă c 1, pe prima linie se va tipări partiţia cu numărul k ı̂n ordine lexicografică, numerele
vor fi separate prin câte un spaţiu;
- dacă c 2, pe prima linie se va tipări numărul de ordine k al partiţiei citite.
- 1 $ n $ 10 000
- 0$k $ 1017 (indiferent dacă este cazul c 1 sau c 2)
- pentru teste ı̂n valoare de 18 puncte avem n & 20
- pentru alte teste ı̂n valoare de 36 de puncte avem n $ 10 000 şi k & 1 000 000
- pentru alte teste ı̂n valoare de 18 puncte avem k & 2 000 000 000
- pentru toate testele din fişierele de intrare există soluţie
- se acordă 10 puncte din oficiu
Exemple:
1...
... toate partiţiile lui (n-1) ı̂n ordine lexicografică
1..
2...
... toate partiţiile lui (n-2) ı̂n ordine lexicografică
2...
...
k ...
... toate partiţiile lui n k ı̂n ordine lexicografică
k ...
...
n 1 1 (singura partiţie a lui 1)
n (singura partiţie a lui 0)
Ne interesează numărul partiţiilor lui n. Să notăm acest număr cu P n. Avem următoarea
formulă recursivă:
P n P n 1 P n 2 ... P 1 P 0, pentru orice n % 1, şi P 1 P 0 1
Aplicând formula pentru numărul n 1, obţinem P n 1 P n 2 ... P 1 P 0.
Înlocuind ı̂n prima formulă, obţinem P n P n 1 P n 1 2 P n 1.
Dezvoltând formula mai departe, obţinem:
n1
P n 2 P n 1 2 2 P n 2 2 2 2 P n 3 ... 2 P 1
Avem nevoie de valorile acestui şir până la valoarea n. Elementele şirului sunt cuprinse ı̂n tabelul
de mai jos, pentru valorile lui n, n & 15:
i 0 1 2 3 4 5 6 7 8 9 10 11
P[i] 1 1 2 4 8 16 32 64 128 256 512 1024
n1
Adică este adevărată formula P n 2 , pentru orice n ' 1,
În cazul ı̂n care ţinem cont de faptul că partiţia formată dintr-un singur termen nu se socoteşte,
n1
atunci din valoarea lui P n vom scădea 1, P n 2 1.
În enunţul problemei, pentru n 4 avem doar 7 termeni.
Ultima partiţie este (3,1), iar partiţia 4 - formată dintr-un singur termen - nu s-a mai introdus
ı̂n listă. Acest termen despre care vorbim este ultimul ı̂n ordine lexicografică şi nu afectează
numerele de ordine pentru celelalte partiţii.
Problema se poate rezolva cu programare dinamică folosind datele din tabelul de mai sus.
În continuare vom prezenta o variantă simplificată a algoritmului de mai sus, un algoritm
greedy bazat pe observaţiile şi calculele anterioare.
Pentru a găsi cea de a k partiţie a lui n ı̂n ordine lexicografică, vom ţine cont de faptul că
n1 n2 n2
există 2 partiţii a lui n. Dintre acestea 2 ı̂ncep cu 1, iar celelalte 2 partiţii ı̂ncep cu un
număr mai mare decât 1.
n2
Astfel, dacă k2 atunci primul termen este 1 şi trebuie să căutăm ı̂n continuare partiţia lui
n 1 cu numărul de ordine k.
n2
Dacă k % 2 permutarea va ı̂ncepe cu o valoare mai mare decât 1.
Această valoare creşte cumulativ cu 1 la fiecare pas când numărul ordinii lexicografice se găseşte
ı̂n a doua jumătate a listei, iar numărul de ordine căutat se micşorează cu o putere a lui 2, vom
căuta ı̂ntr-o listă, din care vom elimina numerele care ı̂ncep cu 1.
n1
Datorită faptului că numărul de soluţii 2 depăşeşte limitele tipului unsigned long long pentru
n % 63, trebuie să observăm ı̂ncă o proprietate.
Conform restricţiilor problemei, n $ 10 000, iar k are maximum 18 cifre. Am arătat mai sus,
n2
că dacă k & 2 , atunci primul termen al partiţiei este 1. Asta ı̂nseamnă, că pentru toate valorile
lui n micşorate succesiv la n-1, n-2, n-3, ..., 64 şirul partiţiilor se va completa cu 1, până când
n2
2 devine suficent de mic, să fie comparabil cu valoarea lui k, deci un număr reprezentabil pe
64 de biţi.
Complexitatea algorimtului este O n (Dacă considerăm că numărul partiţiilor lui n este egal
n1
cu M 2 , atunci algoritmul are complexitatea O log M .
O soluţie backtracking, care generează soluţiile ı̂n ordine lexicografică poate să obţină până la
54 de puncte, pentru testele ı̂n care valoarea lui k nu depăşeşte 1 000 000.
CAPITOLUL 4. OJI 2020 4.2. PARTIT 50
#include <fstream>
ifstream fin("partit.in");
ofstream fout("partit.out");
int main ()
{
fin >> cerinta >> n;
if (cerinta == 1)
{
for (int i = n; i >= 63; i--)
fout << "1 "; // In prima parte pot fi multi de 1.
fin >> k;
if (n >= 62)
lim = 62;
else
lim = n;
kn = 2 * (k - 1); // Ca sa avem un bit == 0 la sfarsit.
m = (1LL << (lim - 1)); // masca cu 1 bit.
for (int i = lim, p = 1; i >= 1; i--, m >>= 1)
{
if ((m & kn) != 0)
p++; // Mai facem un pas, cautand bit == 0.
else
{
fout << p << ’ ’; p = 1;
}
}
fout << endl;
}
else
{ // cerinta 2
int i, b = -1, lim;
bool mare = false;
for (i = 1; fin >> a[i]; i++)
{
if (a[i] > 1 and not mare)
{
lim = i; mare = true; // Primul loc unde avem ceva > 1.
}
}
i--;
k = (1LL << (n - lim + 1)) - 1; // multi biti de 1
for (int j = i; j >= lim; j--)
{
b += a[j]; k -= (1LL << b); // Resetam biti.
}
fout << k + 1; // Numaram de la 0.
}
return 0;
}
#include <fstream>
ifstream fin("partit.in");
ofstream fout("partit.out");
int c,n,a;
long long k,p;
int main()
{
fin>>c>>n;
if(c==1)
{
fin>>k;
long long d;
int i;
while(k)
{
while(g(n-1,k)==1)
{
fout<<"1 ";n--;
}
if(k==((long long)1<<(n-1)))
{
i=n; k=0;
}
else
{
if(k==((long long)1<<(n-1))-1)
{
i=n-1; k=0;
}
else
{
d=((long long)1<<(n-2)); i=1;
while(k>=d && d)
{
k=k-d; i++; d=d/2;
}
}
}
fout<<i<<" ";
n=n-i;
}
while(n){fout<<"1 ";n--;}
}
else
{
k=0;
while(fin>>a)
{
long long d=((long long)1<<(n-2));
n=n-a;
while(a>1)
{
k=k+d; d=d/2; a--;
}
}
fout<<k+1;
}
fout.close(); fin.close();
return 0;
}
#include <fstream>
CAPITOLUL 4. OJI 2020 4.2. PARTIT 52
int main()
{
long long n,k,cazuri,t;
int opt;
fin>>opt;
if(opt==1)
{
fin>>n>>k;
t=1;
while (n>63)
{
fout<<1<<" ";
n--;
}
cazuri=1LL<<(n-1);
while(cazuri>=1)
{
if (k==1)
{
fout<<t<<" ";
while (cazuri>1)
{
fout<<1<<" ";
cazuri=cazuri/2;
}
fout<<"\n";
}
else
if (k<=cazuri/2)
{
fout<<t<<" ";
t=1;
}
else
{
t++;
k=k-cazuri/2;
}
cazuri=cazuri/2;
}
}
else
{
fin>>n;
k=0;
while(fin>>t)
{
if(t>1)
{
cazuri=1LL<<(n-1);
for(long long i=1;i<t;i++)
{
cazuri/=2;
k+=cazuri;
}
}
n-=t;
}
fout<<k+1;
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
return dp;
}
while (n > 0)
{
assert(dp[n] >= ord);
return ans;
}
assert(n == 0);
return ord;
}
int main()
{
ifstream in("partit.in");
ofstream out("partit.out");
int cerinta, n;
in >> cerinta >> n;
if (cerinta == 1)
{
i64 ord;
in >> ord;
CAPITOLUL 4. OJI 2020 4.2. PARTIT 54
in.close();
out.close();
return 0;
}
Şi ... cea mai interesantă observaţie pare a fi că ... primul
subgrup component ocupă prima jumătate din configuraţii iar
restul subgrupurilor componente ocupă a doua jumătate din
configuraţii.
De exemplu, ı̂n g 6:
g 5 ocupă prima jumătate, poziţiile 1-16, şi primul număr
din partiţie este 1 Figura 4.1: Partiţii pentru n 6
g 4 ı̂mpreună cu g 3, g 2, g 1 şi g 0 ocupă a doua
jumătate, poziţiile 17-32, şi primul număr din partiţie poate fi 2, 3, 4 sau 5 (poziţia 32 nu poate
apărea din cauza restricţiei din enunţul problemei!). O mică observaţie: aici este vorba de g 4 din
g 6 care nu este ı̂n interiorul lui g 5 (mai este un g 4 ı̂n interiorul lui g 5)! Prin ı̂njumătăţiri
repetate, ajungem să obţinem zona de care suntem interesaţi ı̂n ”prima jumătate” şi să ştim care
este numărul ”de pe prima poziţie” (din partiţie).
34
https://ro.wikipedia.org/wiki/Matrio%C8%99ka
CAPITOLUL 4. OJI 2020 4.2. PARTIT 55
int nrv=0;
int v[10005]; // n < 10 000
int main()
{
int n=6;
genp(1,n);
return 0;
}
Rezultatul este:
1 : 1 1 1 1 1 1
2 : 1 1 1 1 2
3 : 1 1 1 2 1
4 : 1 1 1 3
5 : 1 1 2 1 1
6 : 1 1 2 2
7 : 1 1 3 1
8 : 1 1 4
9 : 1 2 1 1 1
10 : 1 2 1 2
11 : 1 2 2 1
12 : 1 2 3
13 : 1 3 1 1
14 : 1 3 2
15 : 1 4 1
16 : 1 5
17 : 2 1 1 1 1
18 : 2 1 1 2
19 : 2 1 2 1
20 : 2 1 3
21 : 2 2 1 1
22 : 2 2 2
23 : 2 3 1
24 : 2 4
25 : 3 1 1 1
26 : 3 1 2
27 : 3 2 1
28 : 3 3
29 : 4 1 1
30 : 4 2
31 : 5 1
32 : 6
Exemplul 1: Să presupunem că avem cerinţa 1 cu n 6 şi k 27. (Listing 4.2.6)
Partiţiile sunt ı̂ntre poziţiile 1-32.
Prima jumătate este ı̂ntre poziţiile 1-16; Cifra de pe prima poziţie este 1.
A doua jumătate este ı̂ntre poziţiile 17-32. k 27 se află ı̂n aceasta zonă!
Zona de a căuta mai departe este ı̂ntre poziţiile 17-32.
Prima jumătate este ı̂ntre poziţiile 17-24; Cifra de pe prima poziţie este 2.
A doua jumătate este ı̂ntre poziţiile 25-32. k 27 se află ı̂n aceasta zonă!
Zona de a căuta mai departe este ı̂ntre poziţiile 25-32.
Prima jumătate este ı̂ntre poziţiile 25-28; Cifra de pe prima poziţie este 3.
A doua jumătate este ı̂ntre poziţiile 28-32.
k 27 se află ı̂n prima jumătate!
Exemplul 2: Să presupunem că avem cerinţa 1 cu n 6 şi k 21. (Listing 4.2.6)
Partiţiile sunt ı̂ntre poziţiile 1-32.
Prima jumătate este ı̂ntre poziţiile 1-16; Cifra de pe prima poziţie este 1.
A doua jumătate este ı̂ntre poziţiile 17-32. k 21 se află ı̂n aceasta zonă!
CAPITOLUL 4. OJI 2020 4.2. PARTIT 57
//#include<climits> //
int nrv=0;
int v[10005]; // n < 10 000
void genp(int pozv, int nr, long long k, int ngk) // k <= 2ˆngk
{
/*
cout<<
"--> genp : "<<
"pozv = "<<pozv<<
" nr = "<<nr<<
" k = "<<k<<
" ngk = "<<ngk<<
" "<<1<<" < "<<"k"<<" <= "<<(1LL<<(ngk))<<
"\n";
*/
//getchar();
if(nr==0)
{
afisv(pozv-1);
return;
}
if(ngk >= 1)
{
if(k <= (1LL<<(ngk-1))) // sus
{
++v[pozv];
++pozv;
genp(pozv,nr,k,ngk-1);
}
else // jos
{
++v[pozv];
genp(pozv,nr,k-(1LL<<(ngk-1)),ngk-1);
}
}
else
{
++v[pozv];
afisv(pozv);
return;
}
return;
}
CAPITOLUL 4. OJI 2020 4.2. PARTIT 58
int main()
{
int n=6; // n=6 ==> 32=2ˆ5=2ˆ{6-1} ... 2ˆ{10 000} = ???
long long k=21; // k < 10ˆ17 = (10ˆ3)ˆ5 * 100 = (2ˆ10)ˆ5 * 2ˆ7 = 2ˆ57
int poziv=1; // pozitia initiala in vectorul solutie
cout<<"Rezultat: ";
genp(poziv,n,k,ngk);
return 0;
}
Rezultat: 2 2 1 1
Observaţie: Rezolvarea problemei ı̂n sens invers este aproape evidentă! (Listing 4.2.7)
int nrv=0;
long long k=0LL; // k < 10ˆ17 = (10ˆ3)ˆ5 * 100 = (2ˆ10)ˆ5 * 2ˆ7 = 2ˆ57
//int v[]={0, 2, 2, 1, 1}; // n < 10 000
int v[]={0, 1, 1, 2, 1, 1};
//int v[]={0, 3, 2, 1};
if(grk == n) return 1;
int rez=(1<<(n-1));
for(int i=1; i<=grk; i++)
rez=rez/2;
return rez;
}
int main()
{
int nv=sizeof(v)/sizeof(v[0]);
int grk;
int n=0;
for(int i=1; i<nv; i++)
n+=v[i];
cout<<"n = "<<n<<"\n";
//getchar();
cout<<"nrs = "<<nrs<<"\n";
return 0;
}
sfgrk(6 , 1) = 16
sfgrk(6 , 2) = 24
sfgrk(6 , 3) = 28
sfgrk(6 , 4) = 30
sfgrk(6 , 5) = 31
sfgrk(6 , 6) = 32
1 - -1
... sar !!!
1 - -1
... sar !!!
2 - 4
CAPITOLUL 4. OJI 2020 4.2. PARTIT 60
1 - -1
... sar !!!
1 - -1
... sar !!!
nrs = 5
ifstream fin("partit_Teste/1-partit.in");
ofstream fout("partit.out");
int nrv=0;
int v[10005]; // n < 10 000
void genp(int pozv, int nr, long long k, int ngk) // k <= 2ˆngk
{
/*
cout<<
"--> genp : "<<
"pozv = "<<pozv<<
" nr = "<<nr<<
" k = "<<k<<
" ngk = "<<ngk<<
" "<<1<<" < "<<"k"<<" <= "<<(1LL<<(ngk))<<
"\n";
*/
//getchar();
if(nr==0)
{
afisv(pozv-1);
return;
}
if(ngk >= 1)
{
if(k <= (1LL<<(ngk-1))) // sus
{
++v[pozv];
++pozv;
genp(pozv,nr,k,ngk-1);
}
else // jos
{
++v[pozv];
genp(pozv,nr,k-(1LL<<(ngk-1)),ngk-1);
}
}
else
{
++v[pozv];
afisv(pozv);
return;
}
return;
}
CAPITOLUL 4. OJI 2020 4.2. PARTIT 61
if(grk == n) return 1;
int rez=(1<<(n-1));
for(int i=1; i<=grk; i++)
rez=rez/2;
return rez;
}
int main()
{
int c, n;
unsigned long long k, p;
fin>>c>>n;
//cout<<"c = "<<c<<" n = "<<n<<"\n";
if(c==1)
{
fin>>k;
int poziv=1; // pozitia initiala in vectorul solutie
int ngk=0; // nr grup in care este k <= 2ˆngk, ngk = minim !
/*
cout<<"Mesaje de control: "<<
"pozv = "<<1<<
" nr = "<<n<<
" k = "<<k<<
" ngk = "<<ngk<<
" "<<1<<" < "<<"k"<<" <= "<<(1LL<<(ngk))<<
"\n\n";
*/
//cout<<"Rezultat: ";
genp(poziv,n,k,ngk);
return 0;
}
else
{
int nv=1, s=0;
int grk;
while(fin>>v[nv])
{
s=s+v[nv];
nv++;
}
//cout<<"nv = "<<nv<<"\n";
//getchar();
if(s != n)
CAPITOLUL 4. OJI 2020 4.2. PARTIT 62
{
cout<<"Date eronate ... EXIT !!!\n";
return 0;
}
//cout<<"n = "<<n<<"\n";
//getchar();
//cout<<"nrs = "<<nrs<<"\n";
fout<<nrs<<"\n";
fin.close();
fout.close();
return 0;
}
ifstream fin("partit_Teste/4-partit.in");
ofstream fout("partit.out");
int nrv=0;
int v[10005]; // n < 10 000
void genp(int pozv, int nr, long long k, int ngk) // k <= 2ˆngk
{
if(nr==0)
CAPITOLUL 4. OJI 2020 4.2. PARTIT 63
{
afisv(pozv-1);
return;
}
if(ngk >= 1)
{
if(k <= (1LL<<(ngk-1))) // sus
{
++v[pozv];
++pozv;
genp(pozv,nr,k,ngk-1);
}
else // jos
{
++v[pozv];
genp(pozv,nr,k-(1LL<<(ngk-1)),ngk-1);
}
}
else
{
++v[pozv];
afisv(pozv);
return;
}
return;
}
int rez=(1<<(n-1));
for(int i=1; i<=grk; i++)
rez=rez/2;
return rez;
}
int main()
{
int c, n;
unsigned long long k, p;
fin>>c>>n;
if(c==1)
{
fin>>k;
int poziv=1; // pozitia initiala in vectorul solutie
int ngk=0; // nr grup in care este k <= 2ˆngk, ngk = minim !
//cout<<"Rezultat: ";
genp(poziv,n,k,ngk);
CAPITOLUL 4. OJI 2020 4.2. PARTIT 64
return 0;
}
else
{
int nv=1, s=0;
int grk;
while(fin>>v[nv])
{
s=s+v[nv];
nv++;
}
if(s != n)
{
cout<<"Date eronate ... EXIT !!!\n";
return 0;
}
fin.close();
fout.close();
return 0;
}
int main()
{
int argc=4;
char* argv[] =
{
(char*)"checker",
(char*)"partit_Teste/4-partit.in", // input
(char*)"partit_Teste/4-partit.ok", // rezultat corect
(char*)"partit.out", // rezultat de verificat si acordat punctaj
};
cout<<"argc = "<<argc<<"\n";
for(int kk=0;kk<argc;kk++)
cout<<argv[kk]<<"\n";
cout<<"----------------------\n";
compareRemainingLines();
}
CAPITOLUL 4. OJI 2020 4.3. RECYCLEBIN 65
4.3 RecycleBin
Problema 3 - RecycleBin 90 de puncte
Se dă un şir de N numere ı̂ntregi notat cu A. O subsecvenţă a şirului A este un şir
Ai Ai1 Ai2 ...Aj cu 1 & i & j & N , iar lungimea acestei subsecvenţe este egală cu j i 1.
O operaţie constă ı̂n alegerea unei subsecvenţe din şir şi ştergerea acesteia. ı̂n cadrul unei operaţii,
lungimea subsecvenţei alese trebuie să fie o putere de 2. ı̂n cadrul tuturor operaţiilor efectuate pe
şir, lungimile subsecvenţelor şterse trebuie să fie distincte.
Pentru fiecare subsecvenţă din şir considerăm suma elementelor ei. Definim costul unui şir ca
fiind maximul acestor sume, ı̂n cazul ı̂n care şirul conţine cel puţin un număr pozitiv, altfel costul
şirului este egal cu 0.
Putem aplica o succesiune de operaţii (eventual niciuna) pe şirul A. ı̂n urma acestor
operaţii se vor şterge anumite elemente din şir, obţinându-se astfel o mulţime de şiruri M
¬ ¬ ¬
rA, A1 , A2 , A3 , ...x.
Cerinţe
Să se determine costul maxim posibil ce se poate obţine dintr-un şir al mulţimii M .
Date de intrare
Date de ieşire
Exemple:
Pentru că răspunsul este de fapt suma maximă a unei subsecvenţe din şirul rezultat, este de
ajuns să luăm valoarea maximă din tabela calculată. Se poate face o analogie cu algoritmul care
calculează suma maximă a unei subsecvenţe ı̂n timp liniar: tabela dinamicii reţine suma curentă,
iar răspunsul este maximul dintre valorile sumei după fiecare iteraţie.
///O(n*n*log(n))
#include <fstream>
ifstream fin("recyclebin.in");
ofstream fout("recyclebin.out");
int n,v,i,j,k,dp[1002][1024],vmax;
int main()
{
fin>>n;
for(i=1;i<=n;i++)
{
fin>>v;
for(k=0;k<=i-1;k++)
{
dp[i][k]=max(dp[i][k],v+dp[i-1][k]);
vmax=max(vmax,dp[i][k]);
}
for(j=1;j<=i-1;j=j*2)
{
for(k=0;k<=i-j;k++)
{
if((j&k)==0)
{
dp[i][(k|j)]=max(dp[i][(k|j)],dp[i-j][k]);
vmax=max(vmax,dp[i][(k|j)]);
}
}
}
}
fout<<vmax; fout.close(); fin.close(); return 0;
}
CAPITOLUL 4. OJI 2020 4.3. RECYCLEBIN 67
// O(Nˆ2 * log(N))
#include <vector>
#include <fstream>
int main()
{
ifstream f("recyclebin.in");
ofstream g("recyclebin.out");
int n; f >> n;
vector<int> a(n + 1);
vector<vector<int>> best(n + 1, vector<int>(n));
return 0;
}
#include <bits/stdc++.h>
using namespace std;
if (j & b)
dp[i][j] = max(dp[i][j], dp[i - b][j - b]);
}
}
return ans;
}
int main()
{
ifstream in("recyclebin.in");
ofstream out("recyclebin.out");
int n;
in >> n;
return 0;
}
Să ne gândim să rezolvăm subsecvenţe de forma A1 A2 ...Ai cu 1 & i & N . Vom folosi setul de
date din enunţul problemei şi Figura 4.3.
Rezolvarea este uşoară când dimensiunea problemei este foarte mică!
i 1: Pentru subsecvenţa A1 13 costul este desigur 13.
i 2: Pentru subsecvenţa A1 A2 13, 19 costul este desigur 13 şi se obţine prin eliminarea
lui 19.
i 3: Pentru subsecvenţa A1 A2 A3 13, 19, 13 costul este desigur 26 şi se obţine prin
eliminarea lui 19.
35
Şi uite aşa, tot lungind subsecvenţa, sperăm să ajungem cu bine la i N.
Să presupunem că, deocamdată, am ajuns pe undeva pe la mijloc, să zicem i 6 (subsecvenţa
este marcată cu albastru ı̂n Figura 4.3) şi vrem să facem ı̂ncă un pas şi să obţinem rezultatul
pentru i 7 (toate rezultatele pentru valori strict mai mici decât 7 sunt considerate cunoscute!).
Subsecvenţele noastre ı̂ncep toate ı̂n A1 şi se termină ı̂n Ai , 1 & i & N .
Revenind la exemplul nostru: A7 20 şi pentru acest element sunt două situaţii:
face parte din suma care dă rezultatul pentru subsecvenţa A1 A2 ...A7
nu face parte din suma care dă rezultatul pentru subsecvenţa A1 A2 ...A7
35
Eu ı̂ncerc să le explic ”ı̂ncepătorilor” ... nu ”avansaţilor”!
CAPITOLUL 4. OJI 2020 4.3. RECYCLEBIN 69
Ce ı̂nseamnă că A7 20 ”face parte din suma care dă rezultatul pentru subsecvenţa A1 A2 ...A7 ”?
Desigur, ı̂nseamnă că A7 20 se adună la rezultatul de la subsecvenţa A1 A2 ...A6 .
Ce ı̂nseamnă că A7 20 ”nu face parte din suma care dă rezultatul pentru subsecvenţa A1 A2 ...A7 ”?
Desigur, ı̂nseamnă că A7 20 a fost eliminat. Mai precis, a fost eliminat ı̂ntr-o subsecvenţă
de lungime 1 sau 2 sau 4 (8 este deja prea mare!), subsecvenţă care se termină ı̂n A7 20 şi
ı̂ncepe ... de unde trebuie ... ca să fie lungimea despre care am vorbit aici (1 sau 2 sau 4).
Poate că ı̂n ı̂nţelegerea faptului că ”a fost eliminat ı̂ntr-o subsecvenţă ... care se termină
ı̂n A7 20 ” şi că ... nu trebuie să ne ı̂ngrijoreze faptul că ... s-ar putea termina mai târziu
... este ”cheia succesului” nostru ı̂n rezolvarea acestei probleme!
Totuşi, o ı̂ntrebare firească: cum să se termine mai târziu dacă am considerat subsecvenţa
A1 A2 ...A7 care se termină ı̂n A7 ? Este adevărat că A7 20 poate fi eliminat mai târziu
... ı̂n analiza/algoritmul nostru ... de exeplu ı̂n subsecvenţa de lungime 4 care se termină ı̂n
A9 10 şi care va ştege subsecvenţa A6 A7 A8 A9 dar ... asta este o ”altă poveste”!
Trebuie să nu uităm că nu avem voie să folosim două ştergeri de aceeaşi lungime! Deci, de
exemplu, dacă analizăm situaţia ı̂n care A7 este eliminat printr-o subsecvenţă de lungime
2, trebuie să verificăm că ı̂n subsecvenţa A1 A2 A3 A4 A5 nu a fost folosită nicio ştergere de
lungime 2.
Pentru i 7 lungimile permise pentru ”ştergeri” sunt 1 sau 2 sau 4 dar ... pentru un i arbitrar,
care sunt lungimile permise?
Evident, sunt puteri ale lui 2 care nu-l depăşesc pe i. Iar i nu-l depăşeşte pe N . Şi N nu-l
9 10
depăşeşte pe 1 000 (scrie ı̂n Restricţii şi precizări). Pentru că 2 512 şi 2 1024,
0 1 2 9
rezultă că lungimile permise sunt numai 2 1, 2 2, 2 4, ..., 2 512.
Nu este suficient un spaţiu de memorie pentru a ı̂nregistra numai că a fost folosită o putere sau
alta ... pentru că ... se pot folosi la aceeaşi subsecvenţă A1 A2 ...Ai mai multe ştergeri (de lungimi
0 2 6
diferite!) de exemplu: de lungimi 1 2 şi 4 2 şi 64 2 . Putem folosi reprezentarea binară a
10
unui număr ı̂ntreg cu valori ı̂ntre 0 şi 2 1 1023 şi astfel putem ı̂nregistra ce puteri ale lui 2
au fost folosite la un moment dat.
Să considerăm un număr a cărui reprezentare ı̂n baza 2 este:
indicele puterii ... 9 8 7 6 5 4 3 2 1 0
valoarea puterii ... 512 256 128 64 32 16 8 4 2 1
cifre ... 1 1 1 1 1 1 1 1 1 1
Valoarea numărului este 1*512+1*256+...+1*4+1*2+1*1 = 1023. Această combinaţie nu este
realistă ı̂n problema noastră pentru că ar trebui să şteargă 1023 de numere şi noi am putea avea
cel mult 1000 de numere!
Să considerăm un număr a cărui reprezentare ı̂n baza 2 este:
indicele puterii ... 9 8 7 6 5 4 3 2 1 0
valoarea puterii ... 512 256 128 64 32 16 8 4 2 1
cifre ... 0 0 0 1 1 1 1 1 1 1
Valoarea numărului este 1*64+1*32+1*16+1*8+1*4+1*2+1*1 = 127. Această combinaţie
este realistă ı̂n problema noastră numai pentru subsecvenţe de cel puţin 127 de numere!
Să considerăm un număr a cărui reprezentare ı̂n baza 2 este:
indicele puterii ... 9 8 7 6 5 4 3 2 1 0
valoarea puterii ... 512 256 128 64 32 16 8 4 2 1
cifre ... 0 0 0 1 0 1 0 1 1 1
Valoarea numărului este 1*64+0*32+1*16+0*8+1*4+1*2+1*1 = 87. Această combinaţie este
realistă ı̂n problema noastră numai pentru subsecvenţe de cel puţin 87 de numere! Ea descrie faptul
că s-au folosit ştergeri de lungimi 1, 2, 4, 16 şi 64. Numărul 87 identifică lungimile ştergerilor care
s-au efectuat; ı̂l vom nota cods (de la ”codul ştergerilor”).
Când vrem să ştim dacă s-a folosit o anumită ştergere (de o anumită lungime) trebuie să
verificăm dacă pe poziţia corespunzătoare (din reprezentarea binară a setului de ştergeri deja
efectuate) este 1. Verificarea o putem face uşor folosind operatorul ”şi” pe biţi.
În următorul exemplu verificarea ne arată că nu putem să facem o ştergere de lungime 16
pentru că a fost făcută deja una!
CAPITOLUL 4. OJI 2020 4.3. RECYCLEBIN 70
În următorul exemplu verificarea ne arată că putem să facem o ştergere de lungime 16 pentru
că a nu a fost făcută niciuna ı̂ncă!
indicele puterii ... 9 8 7 6 5 4 3 2 1 0
valoarea puterii ... 512 256 128 64 32 16 8 4 2 1
cifre (un set de ştergeri) ... 0 0 0 1 0 1 0 1 1 1
cifre (o ştergere validă) ... 0 0 0 0 0 0 1 0 0 0
Adăugarea unei ştergeri valide la setul de ştergeri existent se poate face folosind operatorul
”sau” pe biţi!
Concluzie: În această problemă avem doi parametri:
i care reprezintă lungimea subsecvenţei A1 A2 ...Ai , 1 & i & N
cods care reprezintă lungimile subsecvenţelor şterse, 0 & cods & i
Ne gândim să folosim o matrice 1001*1001 ı̂n care să putem depozita valori egale cu cel mult
6 9
1000 10 10 dacă se dau toate numerele pozitive şi de valori maxime permise. O reprezentare
pe 4 octeţi este suficientă! În această matrice, pentru completarea liniilor de indici mici este
necesară numai completarea coloanelor de indici mici dar ... nu merită să ne complicăm cu
această economie de memorie pentru că ni s-au acordat 32M de memorie!
Cred că aceste detalii vor ajuta la ı̂nţelegerea soluţiei oficiale şi a programelor ataşate (mă
refer la cei care sunt ”ı̂ncepători”)!
Parcă seamănă un pic şi cu problema rucsacului!
Un exemplu:
4
1 -3 2 -5
Şterg -3 şi rămâne ı̂n final 1 2 -5 ı̂n care subsecvenţa de sumă maximă este 1 2.
Capitolul 5
OJI 2019
5.1 conexidad
Problema 1 - conexidad 90 de puncte
Fie un graf neorientat cu N noduri şi M muchii, care NU este conex.
Cerinţe
Să i se adauge grafului un număr minim de muchii, astfel ı̂ncât acesta să devină conex.
Fie extrai numărul de muchii nou-adăugate care sunt incidente cu nodul i, iar max extra cea
mai mare dintre valorile extra1 , extra2 , ..., extraN . Mulţimea de muchii adăugate trebuie să
respecte condiţia ca valoarea max extra să fie minimă.
Date de intrare
Pe prima linie a fişierului de intrare conexidad.in se află două numere naturale N şi M , iar
pe fiecare dintre următoarele M linii se află câte o pereche de numere a, b, semnificând faptul că
există muchia a, b. Numerele aflate pe aceeaşi linie a fişierului sunt separate prin câte un spaţiu.
Date de ieşire
Fişierul de ieşire conexidad.out va conţine pe prima linie valoarea max extra. Pe a doua
linie va conţine valoarea K reprezentând numărul de muchii nou-adăugate ı̂n graf. Fiecare dintre
următoarele K linii va conţine câte o pereche de numere c, d, separate prin câte un spaţiu,
semnificând faptul că se adaugă grafului muchia c, d.
Exemple
71
CAPITOLUL 5. OJI 2019 5.1. CONEXIDAD 72
- Numărul minim de muchii necesare pentru a conecta graful este C 1, unde C este numărul
de componente conexe. Componentele conexe se pot determina printr-o parcurgere ı̂n lăţime sau
ı̂n adâncime.
- Observăm că este posibil ı̂ntotdeauna să conectăm componentele ı̂ntr-un lanţ, selectând câte
un nod din fiecare componentă iar apoi legând aceste noduri secvenţial.
- Această construcţie produce o valoare max extra cel mult egală cu 2. Valoarea lui max extra
nu poate fi niciodată 0, deci ı̂n continuare rămâne să analizăm cazurile ı̂n care este posibil să
obţinem max extra 1.
- Observăm că dacă toate componentele conexe au mărime cel puţin 2, este posibil să le legăm
ı̂n lanţ cu valoarea max extra 1, folosind pentru fiecare componentă două noduri diferite pentru
a duce muchia către componenta precedentă, respectiv către componenta următoare. Rămâne să
analizăm cazul componentelor de mărime 1 (numite şi noduri izolate).
- La modul general, o soluţie care urmăreşte max extra 1 nu va duce muchie ı̂ntre două
noduri izolate, fiindcă conectarea ulterioară a acestora cu restul grafului va adăuga sigur o a doua
muchie cel puţin unuia dintre noduri. Excepţie face graful alcătuit din doar două noduri, ambele
izolate.
- În consecinţă, dorim să conectăm nodurile izolate cu componente mari (de mărime cel puţin
2) prin noduri ale acestor componente care nu au fost ı̂ncă folosite pentru a duce muchii.
- Având B componente mari, exact 2 B 1 noduri ale acestora vor fi folosite pentru a
conecta componentele mari ı̂ntre ele. Restul nodurilor sunt considerate libere şi pot fi legate
cu noduri izolate. Dacă există suficiente noduri libere pentru a acoperi toate nodurile izolate,
max extra 1, altfel max extra 2.
- Soluţia poate fi implementată ı̂n timp O N M , dar limitele datelor de intrare sunt suficient
3 2
de mici pentru a oferi punctaj maxim unor soluţii de complexitate O N sau O N .
void solve(file_pointer& f)
{
int node[2] = {-1, -1};
for (int lap: {OUTSIDE_COMPONENT, IN_COMPONENT})
for (int i = 0; i < N; ++i)
if ((root(i) == root(0)) == lap
&& (node[lap] == -1 || extra[node[lap]] > extra[i]))
node[lap] = i;
int main()
{
file_pointer f(fopen("conexidad.in", "r"), &fclose);
fscanf(f.get(), "%d%d", &N, &M);
iota(p, p + N, 0);
for (int _ = 0; _ < M; ++_)
{
int x, y; fscanf(f.get(), "%d%d", &x, &y); --x; --y;
join(x, y);
}
f.reset(fopen("conexidad.out", "w"));
solve(f);
return 0;
}
ifstream in("conexidad.in");
ofstream out("conexidad.out");
void dfs(int x)
{
CAPITOLUL 5. OJI 2019 5.1. CONEXIDAD 74
int main()
{
in >> n >> m;
int nr = k - 1;
if(n2 == 0)
{//daca am numai noduri izolate
out << "2\n" << k - 1 << "\n";
for(int i = 1; i < n; i++)
out << i << " " << i+1 << "\n";//leg nodurile izolate intr-un lant
}
else
{
int max_elem;
if(k == 1)
max_elem = 0;// avem o singura componenta conexa
else
{
if(n1 == 0)
max_elem = 1;// daca nu am noduri izolate pot folosi
// cate un singur nod
else
if(n1 > n - n1 - 2 - (n2 - 2) * 2)
max_elem = 2;//daca nodurile izolate sunt mai multe decat
// nodurile ramase libere dupa ce leg
// componentele conexe
else
max_elem = 1;
}
out << max_elem << "\n" << k - 1 << "\n";
int main()
{
ifstream cin("conexidad.in");
ofstream cout("conexidad.out");
vector<vector<int>> g(n);
for(int i = 0; i < m; i += 1)
{
int a, b; cin >> a >> b;
a -= 1, b -= 1;
g[a].push_back(b);
g[b].push_back(a);
}
while(not Q.empty())
{
int node = Q.front();
Q.pop();
seen[node] = 1;
for(auto v : g[node])
if(not seen[v])
{
Q.push(v);
seen[v] = 1;
}
}
};
for(int i = 0; i < n; i += 1)
if(not seen[i])
{
bfs(i, min_node[i], max_node[i]);
components += 1;
if(min_node[i] == max_node[i])
singletons += 1;
}
int ans = 0;
for(int i = 0; i < n; i += 1)
if(min_node[i] == max_node[i])
for(int j = 0; j < n; j += 1)
if(not used[j] and min_node[j] != max_node[j])
{
edges.push_back({i, j});
used[j] = 1;
break;
}
}
else
{
int last = -1;
for(int i = 0; i < n; i += 1)
if(min_node[i] <= max_node[i])
{
if(last >= 0)
edges.push_back({i, last});
last = i;
}
}
cnt[edge.first] += 1;
cnt[edge.second] += 1;
}
ifstream in ("conexidad.in");
ofstream out ("conexidad.out");
int n, m;
int lst[Q + 1], val[2 * Q * Q + 1], nxt[2 * Q * Q + 1];
int main()
{
in >> n >> m;
int a, b;
for (int i = 0; i < m; i++)
{
in >> a >> b;
add_edge(a, b);
add_edge(b, a);
}
int last = 0;
vector<int> actual;
actual.clear();
dfs(i, 0, actual);
if (actual.size() == 1)
{
isolated.push_back(actual[0]);
continue;
}
if (normals.size() == 0)
{
normals = actual;
continue;
}
rez.push_back(make_pair(i, normals.back()));
normals.pop_back();
normals.insert(normals.end(), actual.begin() + 1, actual.end());
}
last = normals.back();
start = normals.size() - 1;
}
else
{
last = isolated[0];
start = 1;
}
return 0;
}
5.2 rufe
Problema 2 - rufe 90 de puncte
Alex vrea să ı̂şi usuce rufele pe balcon. El a spălat K tricouri şi o şosetă. Uscătorul lui Alex are
N niveluri, iar fiecare nivel are M locuri unde poate atârna câte un singur obiect de ı̂mbrăcăminte.
CAPITOLUL 5. OJI 2019 5.2. RUFE 79
Alex usucă hainele ı̂ntr-un mod specific: ı̂ncepe prin a pune şoseta pe nivelul A, locul B, iar
apoi aduce coşul de rufe cu cele K tricouri şi le aşază pe rând, mereu alegând o poziţie liberă cât
mai depărtată de locul unde a pus şoseta. Metrica pe care o găseşte ca fiind cea mai potrivită
când vine vorba de uscatul rufelor este distanţa Manhattan, astfel ı̂ncât distanţa de la nivelul r1,
locul c1 la nivelul r2, locul c2 are valoarea expresiei ¶r1 r2¶ ¶c1 c2¶.
Cerinţe
Aflaţi distanţa dintre poziţia unde a atârnat ultimul tricou şi poziţia unde se usucă şoseta.
Date de intrare
Pe prima linie a fişierului de intrare rufe.in se vor afla 5 numere ı̂ntregi N , M , A, B, şi K, cu
semnificaţia din enunţ, separate prin câte un spaţiu.
Date de ieşire
În fişierul de ieşire rufe.out se va afla o singură linie care să conţină valoarea cerută.
1 & N, M & 10
9
a
a 1&A&N
a 1&B&M
a 1&K &N M 1
Pentru teste ı̂n valoare de 13 puncte se garantează că N, M & 10 .
3
a
Pentru alte teste ı̂n valoare de 12 puncte se garantează că N & 10 .
6
a
Pentru alte teste ı̂n valoare de 12 puncte se garantează că M & 10 .
6
a
Pentru alte teste ı̂n valoare de 18 puncte se garantează că K & 10 .
6
a
a Pentru alte teste ı̂n valoare de 7 puncte se garantează că A B 1.
Exemple
a verde = cadranul 1
a violet = cadranul 2
a gri = cadranul 3
a galben = cadranul 4
Cu puţină atenţie, putem rezolva independent cadranele. În cadrul
unui cadran vom număra celulele care nu fac parte din rombul albastru.
Se observă că figura după ce eliminăm acele celule va fi un dreptunghi
sau trapez, dar cel din urmă este doar un caz particular al ceiluilalt.
Pentru a calcula numărul de celule din trapez, ı̂l vom ı̂mpărţi ca ı̂n
urmatorul grafic:
Regiunile verzi şi albastre vor fi două dreptunghiuri, ale căror număr
de celule este uşor de aflat, iar regiunea roşie este un triunghi drep-
tunghic; pentru acesta trebuie să calculam suma primelor p numere
naturale.
long long n, m, x, y, k;
long long sum(long long from, long long to, long long i0)
{
if (from > to) return 0LL;
return (to - from + 2 * i0) * (to - from + 1) / 2;
}
int main()
{
unique_ptr<FILE, decltype(&fclose)> f(fopen("rufe.in", "r"), &fclose);
fscanf(f.get(), "%lld%lld%lld%lld%lld", &n, &m, &x, &y, &k);
f.reset(fopen("rufe.out", "w"));
fprintf(f.get(), "%lld\n", l);
CAPITOLUL 5. OJI 2019 5.2. RUFE 82
return 0;
}
ifstream in("rufe.in");
ofstream out("rufe.out");
long long n, m, k, a, b;
//in jos
if(d > n - a + 1)
{
poz = d - (n - a + 1);
total = total - poz * (poz + 1) + poz;
}
//stanga
if(d > b)
{
poz = d - b;
total = total - poz * (poz + 1) + poz;
}
//dreapta
if(d > m - b + 1)
{
poz = d - (m - b + 1);
total = total - poz * (poz + 1) + poz;
}
//dreapta - sus
if(d > a + m - b)
{
poz = d - (a + m - b) - 1;
total = total + poz * (poz + 1) / 2;
}
//stanga - jos
if(d > n - a + b)
{
poz = d - (n - a + b) - 1;
total = total + poz * (poz + 1) / 2;
}
//dreapta jos
if(d > n - a + m - b + 1)
{
CAPITOLUL 5. OJI 2019 5.2. RUFE 83
poz = d - (n - a + m - b + 1) - 1;
total = total + poz * (poz + 1) / 2;
}
return total;
}
int main()
{
in >> n >> m >> a >> b >> k;
if(k == 1)
out << max(max(a - 1 + b - 1, a - 1 + m - b),
max(n - a + b - 1, n - a + m - b));
else
if(k >= n * m - 4)
out << 1;
else
{
long long l = 1, r = n + m, mid, sol;
bool stop = false;
while(l <= r and stop == false)
{
mid=l+(r-l)/2;//distanta la care se aseaza ultimul tricou
return 0;
}
ifstream in("rufe.in");
ofstream out("rufe.out");
long long n, m, k, a, b;
//in jos
if(d > n - a + 1)
{
poz = d - (n - a + 1);
total = total - poz * (poz + 1) + poz;
}
CAPITOLUL 5. OJI 2019 5.2. RUFE 84
//stanga
if(d > b)
{
poz = d - b;
total = total - poz * (poz + 1) + poz;
}
//dreapta
if(d > m - b + 1)
{
poz = d - (m - b + 1);
total = total - poz * (poz + 1) + poz;
}
//dreapta - sus
if(d > a + m - b)
{
poz = d - (a + m - b) - 1;
total = total + poz * (poz + 1) / 2;
}
//stanga - jos
if(d > n - a + b)
{
poz = d - (n - a + b) - 1;
total = total + poz * (poz + 1) / 2;
}
//dreapta jos
if(d > n - a + m - b + 1)
{
poz = d - (n - a + m - b + 1) - 1;
total = total + poz * (poz + 1) / 2;
}
return total;
}
int main()
{
in >> n >> m >> a >> b >> k;
long long d = max(max(a - 1 + b - 1, a - 1 + m - b),
max(n - a + b - 1, n - a + m - b));
while(k > n * m - pozitii_libere(a, b, d))
d = d - 1;
out << d;
return 0;
}
ifstream f("rufe.in");
ofstream g("rufe.out");
int64_t lo,hi,mi,r,R,a[4],b[4];
void read(),solve();
return D<=A ?
D*(D+1)/2:D<=B?A*(A+1)/2+(D-A)*A :
D>=A+B?A*B:A*B-(A+B-D)*(A+B-D-1)/2;
}
int main()
{
read();solve();
return 0;
}
void read()
{
int64_t n,m,i,j,k;
f>>n>>m>>i>>j>>k;
hi=n+m+1,R=m*n-k-1;
a[0]=i;
b[0]=j-1;
a[1]=n-i;
b[1]=j;
a[2]=i-1;
b[2]=m-j+1;
a[3]=n-i+1;
b[3]=m-j;
for(int p=0;p<4;p++)
if(a[p]>b[p])
swap(a[p],b[p]);
}
void solve()
{
while(hi-lo>1)
{
mi=(lo+hi)/2,r=R;
for(int p=0;p<4;p++)
r-=drep(a[p],b[p],mi);
if(r>=0)
lo=mi;
else
hi=mi;
}
g<<hi;
}
ifstream fin("rufe.in");
ofstream fout("rufe.out");
int main()
{
long long n,m,t,a,b,k;
fin>>n>>m>>a>>b>>k;
fout<<distanta(n,m,a,b,k)<<"\n";
return 0;
}
ifstream fin("rufe.in");
ofstream fout("rufe.out");
while (stanga<=dreapta)
{
mij=(stanga+dreapta)/2;
d=mij-1;
k1= 2*(d+1)*d; /// calculez numarul tricourilor la distanta <=d
if (d>st)
{v=d-st;k1=k1-v*v;}; /// elimin locurile din stanga dreptunghiului
if (d>jos)
{v=d-jos;k1=k1-v*v;}; /// elimin locurile de sub dreptunghi
CAPITOLUL 5. OJI 2019 5.3. TAIROS 87
if (d>dr)
{v=d-dr;k1=k1-v*v;}; /// elimin locurile din dreapta dreptunghiului
if (d>st+sus+1)
{v=d-st-sus-1;k1=k1+v*(v+1)/2;}///adaug locurile eliminate de doua ori
if (d>st+jos+1)
{v=d-st-jos-1;k1=k1+v*(v+1)/2;}
if (d>dr+jos+1)
{v=d-dr-jos-1;k1=k1+v*(v+1)/2;}
if (d>dr+sus+1)
{v=d-dr-sus-1;k1=k1+v*(v+1)/2;}
int main()
{
long long n,m,a,b,k;
fin>>n>>m>>a>>b>>k;
fout<<distanta(n,m,a,b,k)<<"\n";
return 0;
}
5.3 tairos
Problema 3 - tairos 90 de puncte
Se dă un arbore cu N noduri, numerotate de la 1 la N .
Arborele se va transforma astfel: la oricare etapă fiecare nod de gradul 1 diferit de rădăcină
din arborele actual se ı̂nlocuieşte cu un arbore identic cu cel dat iniţial, iar la următoarea etapă
procedeul se va relua pentru arborele obţinut, formându-se astfel un arbore infinit. În următoarele
3 imagini se prezintă un exemplu de arbore dat iniţial, arborele obţinut după prima etapă de
prelungire a frunzelor şi arborele obţinut după 2 etape de prelungire a frunzelor.
CAPITOLUL 5. OJI 2019 5.3. TAIROS 88
Cerinţe
Date de intrare
Date de ieşire
Fişierul de ieşire tairos.out va conţine un singur număr, şi anume restul ı̂mpărţirii numărului
de noduri cerut la numărul 1.000.000.007.
Exemple
Initial V 0 1 şi V i 0 oricare i j 0 (caz particular de pornire. Dacă extindem un nod
izolat folosind tehnica din enunţ vom obţine exact arborele iniţial).
Putem parcurge de la 0 la D vectorul V pentru a genera frunze la dreapta ı̂n vector folosind
tehnica programării dinamice. Mai exact, o recurenţă ı̂nainte de forma:
fiind la poziţia i şi având deja calculată valoarea V i
V i 1 V i F R1
V i 2 V i F R2
.....
V i adancime arbore initial V i F Radancime arbore initial
În cuvinte, dacă la distanta i avem V i noduri şi ştim că ı̂n arborele iniţial aveam F Rk
frunze la distanţa k de rădăcina 1, atunci prelungind toate cele V i noduri aflate la distanţa i,
vom obţine V i F Rk noi frunze la distanţa V i k .
Acum, pentru a afla exact câte noduri sunt la distanţa D de rădăcina 1, trebuie să nu uităm
de nodurile din arborele iniţial care nu sunt frunze, cele reţinute ı̂n vectorul N T .
Răspunsul = V D V D 1 N T 1 V D 2 N T 2 ... V D a a i N T a a i,
unde a a i este adâncimea arborelui iniţial.
Clarificare pentru formula de mai sus:
V D reprezintă numărul de noduri foste frunze aflate la distanţa D de rădăcina (acestea clar
trebuie numărate).
V D 1 N T 1 reprezintă nodurile-funze aflate la distanţa D 1 de rădăcina, pe care le
prelungim pentru ultima dată. Pentru fiecare arbore astfel prelungit trebuie să numaram câte
noduri nefrunze (deoarece frunzele au fost deja numărate) are la distanţa 1 - informaţie retinută
ı̂n N T 1.
V D k N T k exact ca mai sus.
Complexitate timp finală O M AXD M AXN , adică D adancime arbore initial
Aveţi grijă permanent sa nu uitaţi de modulo.
bool A[MAX_N][MAX_N];
int p[MAX_N], d[MAX_N];
int dp[2][MAX_D + 1];
int N;
int main()
{
unique_ptr<FILE, decltype(&fclose)> f(fopen("tairos.in", "r"), fclose);
CAPITOLUL 5. OJI 2019 5.3. TAIROS 91
int D;
fscanf(f.get(), "%d%d", &N, &D);
for (int _ = 0; _ < N - 1; ++_)
{
int x, y;
fscanf(f.get(), "%d%d", &x, &y);
--x;
--y;
A[x][y] = A[y][x] = true;
}
fill(p + 1, p + N, -1);
while (true)
{
bool changed = false;
for (int i = 0; i < N; ++i) if (p[i] != -1)
for (int j = 0; j < N; ++j) if (A[i][j] && p[j] == -1)
{
p[j] = i;
d[j] = d[i] + 1;
changed = true;
}
if (!changed) break;
}
dp[0][0] = 1;
for (int s = 0; s <= D; ++s)
{
for (int i = 0; i < N; ++i) if (s + d[i] <= D)
{
(dp[!is_leaf(i)][s + d[i]] += dp[0][s]) %= MOD;
}
}
f.reset(fopen("tairos.out", "w"));
fprintf(f.get(), "%d\n", dp[1][D]);
return 0;
}
#define M 1000000007
ifstream in("tairos.in");
ofstream out("tairos.out");
int n, d;
int a[101][101];//listele de adiacenta
int viz[101];//viz[i] = 1, daca nodul i a fost vizitat, 0 in caz contrat
int h;//inaltimea arborelui initial
{
if(viz[a[k][i]] == 0)
{//daca exista noduri nevizitate
ok = true;//retin ca nu e frunza
dfs(a[k][i], niv + 1);
}
}
if(ok == true)
{//daca a avut descendenti
f[niv] = f[niv] - 1;//nu e frunza
nf[niv] = nf[niv] + 1;//il trecem la nefrunze
}
else//daca e frunza
if(niv < dmin)
dmin = niv;//actualizez nivelul minim
int main()
{
in >> n >> d;
for(int i = 1; i < n; i++)
{
int x, y;
in >> x >> y;
a[x][0] = a[x][0] + 1;
a[x][a[x][0]] = y;//adaug muchia (x, y)
a[y][0] = a[y][0] + 1;
a[y][a[y][0]] = x;//adaug muchia (y, x)
}
dfs(1, 0);
if(h == 1)
{//daca avem un arbore stea frˆd
long long ans = 1;
for(int i = 1; i <= d; i++)
ans = (ans * f[h]) % M;
out << ans;
return 0;
}
if(dmin == h)
{//daca toate frunzele sunt la acelasi nivel
for(int i = 0; i <= h; i++)
sol[i] = f[i] + nf[i];
long long p = f[h];
for(int i = h + 1; i <= d; i++)
sol[i] = (p * sol[i - h]) % M;
return 0;
}
vector<int> mem;
vector<vector<int>> g;
void dfs(int node, int target, vector<int> &seen, int &ans, int depth)
{
seen[node] = 1;
if(depth == target)
{
ans = (ans + 1) % MOD;
return;
}
for(auto v : g[node])
{
if(seen[v])
continue;
any = true;
dfs(v, target, seen, ans, depth + 1);
}
if(not any)
{
ans += solve(target - depth);
ans %= MOD;
}
}
int solve(int d)
{
if(mem[d] >= 0)
return mem[d];
int ans = 0;
vector<int> seen(g.size(), 0);
dfs(0, d, seen, ans, 0);
return (mem[d] = ans);
}
int main()
{
ifstream cin("tairos.in");
ofstream cout("tairos.out");
g = vector<vector<int>> (n);
for(int i = 0; i < n - 1; i += 1)
{
CAPITOLUL 5. OJI 2019 5.3. TAIROS 94
ifstream in ("tairos.in");
ofstream out ("tairos.out");
int N, D;
int fst[N_MAX + 1], val[2 * N_MAX + 1], nxt[2 * N_MAX + 1];
int leafs[N_MAX + 1];
int not_leafs[N_MAX + 1];
// Vom folosi vectorul sol, unde sol[i] va reprezenta numarul de foste frunze
//aflate la distanta i de radacina modulo R.
int no_nodes_visited = 0;
is_leaf = false;
dfs(val[p], node, depth + 1);
}
if (is_leaf)
leafs[depth] ++;
else
not_leafs[depth] ++;
}
int main()
{
int a, b;
in >> N >> D;
assert (N > 1 && N <= 100);
assert (D > 0 && D <= 10000);
in >> a >> b;
assert (a > 0 && a <= N);
assert (b > 0 && b <= N);
add_edge(a, b);
add_edge(b, a);
}
// Facem un DFS ca sa gasim numarul de frunze de la
// fiecare nivel de adancime in arbore.
dfs(1, 0, 0);
assert (no_nodes_visited == N);
ifstream in ("tairos.in");
ofstream out ("tairos.out");
int N, D;
int fst[N_MAX + 1], val[2 * N_MAX + 1], nxt[2 * N_MAX + 1];
int solution = 0;
void dfs(int node, int dad, int depth)
{
if (depth == D)
{
solution ++;
return;
}
is_leaf = false;
CAPITOLUL 5. OJI 2019 5.3. TAIROS 96
int main()
{
int a, b;
in >> N >> D;
assert (N > 1 && N <= 100);
assert (D > 0 && D <= 10000);
ifstream fin("tairos.in");
ofstream fout("tairos.out");
int main()
{
citire(N,D,tata);
for(int i=1;i<=N; i++)
if (steril[i])
sterile[lungime[i]]++; /// creste numarul nodurilor sterile de o
/// anumita distanta
else
frunze[lungime[i]]++; /// creste numarul frunzelor de o
/// anumita distanta
nfr=0;
for(int i=1;i<=N;i++)
if(frunze[i]) /// daca exista frunze la distanta i de radacina
{
nfr++;
nrfrunze[nfr]=frunze[i]; /// numarul frunzelor la
distanta[nfr]=i; /// distanta data
}
nrsol[0]=1;
for (int i=1;i<=maxadanc;i++)
for (int j=1; j<=nfr; j++)
if (distanta[j]<=i)
nrsol[i]=(nrsol[i]+(nrsol[i-distanta[j]]*nrfrunze[j])%R)%R;
/// construiesc valorile initiale pana la maxadanc
fout<<nrsol[D];
return 0;
}
OJI 2018
6.1 galeti
Problema 1 - galeti 90 de puncte
Avem n găleţi, numerotate de la stânga la dreapta cu numere de la 1 la n. Fiecare găleată
conţine iniţial 1 litru de apă. Capacitatea fiecărei găleţi este nelimitată. Vărsăm găleţile una
ı̂n alta, respectând o anumită regulă, până când toată apa ajunge ı̂n prima găleată din stânga.
Vărsarea unei găleţi presupune un anumit efort.
Regula după care se răstoarnă găleţile este următoarea: se aleg două găleţi astfel ı̂ncât orice
găleată situată ı̂ntre ele să fie goală. Se varsă apa din găleata din dreapta ı̂n găleata din stânga.
Efortul depus este egal cu volumul de apă din găleata din dreapta (cea care se varsă).
Formal, dacă notăm ai volumul de apă conţinut ı̂n găleata cu numărul i, regula de vărsare a
acestei găleţi ı̂n găleata cu numărul j poate fi descrisă astfel:
1. j $ i
2. ak 0 pentru orice k astfel ı̂ncât j $ k $ i
3. efortul depus este ai
4. după vărsare aj aj ai şi ai 0
Cerinţe
Cunoscând numărul de găleţi n şi un număr natural e, să se determine o succesiune de vărsări
ı̂n urma căreia toată apa ajunge ı̂n găleata cea mai din stânga şi efortul total depus este exact e.
Date de intrare
Fişierul de intrare galeti.in conţine pe prima linie două numere naturale, n şi e, ı̂n această
ordine, separate prin spaţiu. Primul număr n reprezintă numărul de găleţi. Al doilea număr e
reprezintă efortul care trebuie depus pentru a vărsa toată apa ı̂n găleata din stânga.
Date de ieşire
Fişierul de ieşire galeti.out trebuie să conţină n 1 linii care descriu vărsările, ı̂n ordinea ı̂n
care acestea se efectuează, pentru a vărsa toată apa ı̂n găleata din stânga cu efortul total e. Fiecare
dintre aceste linii trebuie să conţină două numere i şi j, separate prin spaţiu, cu semnificaţia că
apa din găleata cu numărul i se varsă ı̂n găleata cu numărul j.
98
CAPITOLUL 6. OJI 2018 6.1. GALETI 99
Exemple
galeti.in galeti.out Explicaţii
44 21 Iniţial fiecare galeată conţine câte un litru de apă.
4331 1 1 1 1.
Prima dată vărsăm un litru de apă din găleata 2 ı̂n găleata 1 cu efort 1 =¿
2 0 1 1.
Apoi vărsăm un litru de apă din găleata 4 ı̂n găleata 3 cu efort 1 =¿
2 0 2 0.
În final vărsăm cei doi litri de apă din găleata 3 ı̂n găleata 1 cu efort 2 =¿
4000
O altă variantă corectă ar fi fost:
43
21
31
Observaţi că următoarea succesiune de vărsări este greşită:
42
21
31
Deşi efortul depus este 4 si cei 4 litri ajung ı̂n prima găleata, la primul
pas vărsarea unui litru de apă din găleata 4 ı̂n găleata 2 nu este permisă
deoarece ı̂ntre acestea se găseşte găleata 3 care conţine apă.
Vom demonstra prin inducţie că efortul depus pentru a vărsa toată apa ı̂n prima găleată din
n n1
cele n este o valoare En cu proprietatea că n 1 & En & 2 .
Fie ei efortul depus când vărsăm găleata cu numărul i. În momentul vărsării găleata coţine
cel puţin apa conţinută iniţial ( 1 litru ) şi cel mult toata apa conţinută ı̂n toate galeţile de la
găleata i la găleata n (n i 1 litri) .
1 & ei & n i 1
Obţinem
n n1
En e2 e3 ... en1 en & n 1 n 2 ... 2 1 2
n n1
Să demonstrăm ı̂n continuare că pentru orice valoare En cu proprietatea că n 1 & En & 2
există cel puţin o soluţie.
1 11
Pentru n 1 efortul depus este E1 0 dar 1 1 & 0 & 2 .
2 21
Pentru n 2 efortul depus este E2 1 dar 2 1 & 1 & 2
.
Să presupunem că pentru n 1 găleţi ştim deja că putem vărsa toată apa ı̂n prima găleată cu
n1 n2
orice efort En1 cu proprietatea că n 2 & En1 & 2
.
Pentru n găleţi considerăm două cazuri particulare:
Cazul 1.
Vărsăm primele n 1 găleţi ı̂n prima şi la final vărsăm ultima găleată ı̂n prima.
Efortul depus la ultima vărsare este 1 şi pentru celelalte este En1 şi astfel putem vărsa toată
apa ı̂n prima găleată pentru
n1 n2
n 1 n 2 1 & En & 2
1
Cazul 2.
Vărsăm ultimele n 1 ı̂n a doua şi la final vărsăm a doua găleată ı̂n prima găleată pentru
n1 n2 n n1
2n 3 n 2 n 1 & En & 2
n1
2
CAPITOLUL 6. OJI 2018 6.1. GALETI 100
Pentru a arăta că ı̂n cele două cazuri acoperim toate valorile posibile ale lui En ı̂ntre n 1 şi
n n1
2
este sufficient să dovedim că valoarea minimă din al doilea caz nu poate depăşi valoarea
n1 n2
maximă din primul caz cu mai mult de o unitate adică 1 & 2n 3 1
2
Ultima inegalitate este echivalentă cu n 3 n 4 & 0 şi este evident adevărată pentru orice
valoare ı̂ntreagă n.
Demostraţia de până aici ne sugerează si o metodă prin care se poate realize succesiunea de
vărsări construind această succcesiune. Reluăm raţionamentul pe oricare interval de găleţi st, dr
conţinând m dr st 1 găleţi. Ne propunem să aducem toată apa ı̂n st cu un efort cunoscut
ef .
Dacă ef ' 2m 3 atunci procedăm ca ı̂n cazul 2. Mai precis efectuăm toate operaţiile de
vărsare care aduc apa din intervalul st 1, dr ı̂n st 1 cu efortul ef m 1 după care efectuăm
vărsarea din st 1 ı̂n st cu efortul m 1.
În caz contrar prcedăm ca ı̂n cazul 1 şi mutăm toată apa din intervalul st, dr 1 ı̂n st cu
efort ef 1 după care vărsăm dr ı̂n st cu efort 1.
Implementarea se poate face ı̂n două moduri:
Recursiv: implementăm o functie cu parametrii st, dr, ef care va fi apelată recursiv ı̂n funcţie
de valoarea lui ef fie cu valorile st 1, dr, ef m 1 fie cu valorile st, dr 1, ef 1. La revenire
se afişeaza st 1 st ı̂n cazul primului apel respectiv dr st ı̂n cazul celuilalt.
Iterativ: ı̂n loc să implementăm funcţia recursivă simulăm stiva de apeluri memorând vărsările
ı̂n ordine inversa şi trecând de la un pas la altul prin modificarea a 3 variabile st, dr şi ef care
au iniţial valorile st 1, dr n, ef e si rulam o instrucţiune repetitivă pâna când dr st sau
echivalent ef 0.
Algoritmul este liniar ı̂n ambele cazuri atât ca timp de executare cât şi ca memorie utilizată
Notă:
Structuri de date utilizate:
In cazul implementării recursive nu se utilizeaza structuri de date.
In cazul implemetării iterative este necesară implementarea stivei care conţine răspunsurile.
Pentru aceasta se pot utiliza doi vectori standard iar ca alternativă se pot utiliza structuri spe-
cializate din STL ( vector¡pair¡int,int¿¿ stack¡pair¡int,int¿¿)
Tipul problemei:
Ad-hoc, algoritm constructiv, greedy
In funcţie de rezolvarea abordată - recursivitate, two-pointers, structuri de date (stiva)
Gradul de dificultate: 2
ifstream f("galeti.in");
ofstream g("galeti.out");
int N;
long long E;
int main()
{
f>>N>>E;
if(RECURSIV)
{
CAPITOLUL 6. OJI 2018 6.1. GALETI 101
solve_recursiv(1,N,E);
return 0;
}
solve_iterativ();
return 0;
}
void solve_iterativ()
{
int ST=1,DR=N;
stack<pair<int,int>> sol;
while(ST<DR)
{
int n=DR-ST+1;
if(E>2LL*n-3LL)
{
sol.push(make_pair(ST+1,ST));
ST++;
E-=1LL*n-1LL;
}
else
{
sol.push(make_pair(ST,DR));
DR--,E-=1LL;
}
}
for(;sol.size();sol.pop())
g<<sol.top().first<<’ ’<<sol.top().second<<’\n’;
}
ifstream fin("galeti.in");
ofstream fout("galeti.out");
Int N,C,a[1000010],prec[1000010];
}
}
int main()
{
fin>>N>>C;
Int st=1,dr=N-1;
for(Int i=2;i<=N;i++)
prec[i]=i-1;
for(Int i=1;i<=N-1;i++)
{
fout<<a[i]<<" "<<prec[a[i]]<<"\n";
prec[a[i]+1]=prec[a[i]];
}
return 0;
}
ifstream fin("galeti.in");
ofstream fout("galeti.out");
int N,C;
int main()
{
fin>>N>>C;
galeata(2,C,1);
return 0;
}
#define RECURSIV 0
ifstream f("galeti.in");
ofstream g("galeti.out");
int N,From[1000010],To[1000010];
long long E;
int main()
{
f>>N>>E;
if(RECURSIV){solve_recursiv(1,N,E);return 0;}
solve_iterativ();
return 0;
}
void solve_iterativ()
{
int ST=1,DR=N,k=0;
while(ST<DR)
{
int n=DR-ST+1;
if(E>=2LL*n-3LL)
{
From[++k]=ST+1;
To[k]=ST;
ST++;E-=1LL*n-1LL;
}
else
{
From[++k]=DR;
To[k]=ST;
DR--,E-=1LL;
}
}
for(;k;k--)
g<<From[k]<<’ ’<<To[k]<<’\n’;
}
int main()
{
ifstream cin("galeti.in");
ofstream cout("galeti.out");
int chain_length = 1;
int64_t current = n - 1;
while(current < c)
{
current += chain_length;
chain_length += 1;
}
if(current > c)
{
chain_length -= 1;
current -= chain_length;
extra = c - current;
}
if(extra > 0)
cout << 2 + chain_length << " " << now << "\n";
while(now != 1)
{
cout << now << " " << now - 1 << "\n";
now -= 1;
}
while(where <= n)
{
cout << where << " " << 1 << "\n";
where += 1;
}
}
6.2 ramen
Problema 2 - ramen 90 de puncte
Ai deschis recent un restaurant cu specific japonez, iar lucrurile nu merg grozav. Uneori clienţii
ajung să aştepte foarte mult mâncarea comandată, iar acum crezi că ai ı̂nţeles de ce se ı̂ntâmplă
acest lucru.
Restaurantul nu are mese, ci un singur bar foarte lung dotat cu o bandă rulantă care transportă
porţiile de mâncare de la bucătărie la client. Barul are 500.000.000 de scaune numerotate ı̂n ordine
crescătoare, scaunul 1 fiind cel mai apropiat de bucătărie. Uneori clienţii fac noi comenzi. O
comandă făcută la secunda T de către clientul aflat pe scaunul cu numărul P va ajunge instant la
bucătărie. Prepararea mâncării va dura D secunde, iar apoi mâncarea va fi pusă pe bandă şi va
dura exact P secunde ca aceasta să ajungă la client. ı̂n acest timp, mâncarea va trece prin faţa
scaunelor 1, 2, ... P 1. Dacă dintr-un anumit motiv clientul nu ı̂şi ridică mâncarea de pe bandă,
aceasta va continua să se deplaseze. ı̂n caz contrar, clientul ı̂n cauză se aşteaptă ca mâncarea să
ajungă la scaunul său la secunda T D P .
Deocamdată restaurantul serveşte un singur fel de mâncare: ramen. Astfel, comenzile făcute
de clienţi ajung să fie uşor interschimbabile, iar aceştia se arată foarte deschişi la a profita de pe
urma acestui fapt.
CAPITOLUL 6. OJI 2018 6.2. RAMEN 105
Se cunosc următoarele:
a Un client poate avea zero sau mai multe comenzi ı̂n aşteptare.
a Un client care are zero comenzi ı̂n aşteptare este complet inactiv.
a Numărul de comenzi ı̂n aşteptare ale unui client care face o comandă la secunda T va creşte
cu o unitate exact la secunda T .
a Un client care are ı̂n aşteptare cel puţin o comandă va ridica de pe bandă prima porţie
de ramen care trece prin faţa sa, indiferent dacă aceasta ı̂i era destinată sau nu. Dacă va face
acest lucru la momentul T , numărul său de comenzi ı̂n aşteptare va scădea cu o unitate exact la
momentul T .
Spre exemplu, analizăm situaţia următoare cu două comenzi:
Durata de preparare a ramenului este D 2 secunde. Această valoare este o constantă şi se
aplică identic fiecărei comenzi.
La secunda T 1 10, persoana de pe scaunul cu numarul P 1 8 (să o numim A) comandă o
porţie de ramen. La secunda T 1 D 12, porţia sa este pusă pe bandă.
La secunda T 2 16, persoana de pe scaunul cu numarul P 2 6 (să o numim B) comandă o
porţie de ramen. La secunda T 2 D 18, porţia sa este pusă pe bandă.
La secunda 19 porţia destinată clientului A trece prin faţa scaunului 6, iar clientul B, fiind el
ı̂nsuşi ı̂n aşteptarea unei comenzi o va lua şi o va mânca. El va mânca deci la secunda 19 şi va
ignora apoi propria sa comandă, care va trece pe lângă el.
La secunda 28 porţia destinată clientului B va ajunge la clientul A, iar acesta o va lua şi o va
mânca. El va mânca deci la secunda 28.
Se observă că ı̂n general, ı̂n ciuda ı̂ntârzierilor create, fiecare client va consuma exact câte
porţii a comandat.
Cerinţe
Pentru a evalua impactul acestui obicei asupra timpilor de aşteptare, ai obţinut date despre
toate comenzile date ı̂n ziua curentă. ı̂ţi propui să afli, pentru fiecare comandă următoarea valoare:
dacă respectiva comandă este a N R-a făcută de clientul respectiv, care este secunda la care clientul
ı̂n cauză va mânca pentru a N R-a oară?
Date de intrare
Fişierul de intrare ramen.in va conţine pe prima linie numărul de comenzi N , respectiv timpul
de preparare a unei porţii de ramen D. Următoarele N linii vor conţine câte o pereche de numere:
T i, secunda la care este făcută a i-a comandă, respectiv P i, numărul scaunului de la care
s-a făcut a i-a comandă. Se garantează că timpii de comandă sunt distincţi şi sunt crescători ı̂n
ordinea citirii lor.
Date de ieşire
Fişierul de ieşire ramen.out va conţine N linii, fiecare conţinând o singură valoare naturală:
a i-a valoare va reprezenta răspunsul cerut pentru a i-a comandă ı̂n ordinea citirii.
Exemple
????? - ?????
Se pot obţine 32 de puncte simulând comenzile şi mişcarea porţiilor pe bandă secundă cu
secundă.
Încercând să obţinem o soluţie mai rapidă, observăm că procesarea comenzilor ı̂n ordinea
timpilor nu este cea mai oportună: odată ce se primesc comenzi noi, acestea pot afecta orice
altă comandă precedentă. Ne putem pune problema găsirii unei ordini de procesare care să aibă
proprietatea că odată rezolvată o comandă, răspunsul pentru această comandă rămâne acelaşi
până la finalul programului.
O ordonare care se pretează natural la această cerinţă este sortarea ı̂n ordine crescătoare după
poziţia clienţilor. Odată ce am aflat răspunsurile pentru acele N R comenzi care implică clienţii
cei mai apropiaţi de bucătărie, putem afla răspunsul şi pentru a N R 1-a comandă iar acesta
va fi permanent, fiindcă ı̂n general o anumită comandă poate fi afectată doar de comenzi făcute
de pe poziţii mai mici.
Să vedem deci cum determinăm răspunsul pentru comanda de poziţie minimă. Dacă această
comandă este definită de valorile T T , P P , atunci ea ar putea fi satisfăcută cu orice comandă care
are T i D P P ' T T (orice porţie cu T i mai mic ar fi trecut deja de scaunul ţintă ı̂nainte
ca acesta să fi făcut comanda). Dintre acestea, răspunsul va fi dat de comanda cu T i minim.
Cu alte cuvinte avem nevoie să găsim cel mai mic timp T i cu proprietatea că:
T i ' T T D P P
Răspunsul va fi apoi T i D P P . Apoi va trebui să stergem această comandă din mulţimea
comenzilor care mai pot constitui răspunsuri.
Soluţiile care caută şi şterg aceste valori ı̂n timp liniar pot obţine 57 de puncte. Pentru o
soluţie mai rapidă este necesar ca aceste operaţii să se efectueze ı̂n timp logaritmic. Acest lucru
se poate realiza cu un arbore de intervale sau cu container-ul STL std::set¡¿.
O altă soluţie, ı̂ntr-un sens duală celei precedente, presupune crearea a două tipuri de eveni-
mente: apariţia unei comenzi pe bandă, respectiv ı̂nceperea disponibilităţii unui client de a lua
orice porţie din faţa sa. Aceste evenimente vor fi apoi parcurse ı̂n ordine crescătoare a timpilor,
iar pentru fiecare comandă se va afla la ce client va ajunge. În general, o comandă va ajunge
la clientul cu indice minim dintre cei disponibili la momentul respectiv. Apoi, clientul respectiv
trebuie şters din mulţimea clienţilor disponibili.
Soluţiile care caută acest minim şi ı̂l şterg ı̂n timp liniar pot obtine deasemenea 57 de puncte.
O soluţie mai rapidă se poate obţine efectuând aceste operaţii cu ajutorul unui heap sau cu
container-ul STL std::set¡¿.
CAPITOLUL 6. OJI 2018 6.2. RAMEN 107
class Event
{
public:
int type;
int time;
pair<int, int> who;
Event(int _type, int _time, pair<int, int> _who = pair<int, int> ()):
type(_type),
time(_time),
who(_who)
{}
sort(events.begin(), events.end());
return sol;
}
int main()
{
ifstream cin("ramen.in");
ofstream cout("ramen.out");
int n, T;
cin >> n >> T;
set<int> times;
CAPITOLUL 6. OJI 2018 6.2. RAMEN 108
assert(int(times.size()) == n);
class Event
{
public:
int type;
int time;
pair<int, int> who;
Event(int _type, int _time, pair<int, int> _who = pair<int, int> ()):
type(_type),
time(_time),
who(_who)
{}
vector<Event> events;
map<int, vector<int>> orders;
map<int, vector<int64_t>> answers;
sort(events.begin(), events.end());
vector<pair<int, int>> waiting;
return sol;
}
int main()
{
ifstream cin("ramen.in");
ofstream cout("ramen.out");
int n, T;
cin >> n >> T;
set<int> times;
for(int i = 0; i < n; ++i)
{
cin >> t[i] >> p[i];
times.insert(t[i]);
}
assert(int(times.size()) == n);
int rem = n;
while(rem)
{
for(int i = 0; i < n; ++i)
if(t[i] == time)
{
food.push_back(-T);
wait[p[i]] += 1;
}
vector<int> redo;
for(auto& elem : food)
if(elem >= 0 and wait[elem] > 0)
CAPITOLUL 6. OJI 2018 6.2. RAMEN 110
{
wait[elem] -= 1;
rem -= 1;
answers[elem].push_back(time);
}
else
redo.push_back(elem + 1);
food = redo;
time += 1;
}
return sol;
}
int main()
{
ifstream cin("ramen.in");
ofstream cout("ramen.out");
int n, T;
cin >> n >> T;
ifstream f("ramen.in");
ofstream g("ramen.out");
int main()
{
f >> n >> T;
sort(x+1,x+n+1,crit);
CAPITOLUL 6. OJI 2018 6.2. RAMEN 111
z=1;
while(z<n) z*=2;
z--;
for(i=1;i<=n;i++) tmax[z+i]=tp[i]+T+1;
sort(tmax+z+1,tmax+z+n+1);
copy(tmax+z+1,tmax+z+n+1,tmin+z+1);
for(i=z;i>=1;i--)
updt(i);
for(i=1;i<=n;i++)
sol[x[i]]=cauta(1,tp[x[i]]-po[x[i]]+1)+po[x[i]]-1;
for(i=1;i<=n;i++)
g<<sol[i]<<’\n’;
return 0;
}
st=2*nod;dr=st+1;
if(!tmin[st])
ret=cauta(dr,timp);
else
if(!tmin[dr])
ret=cauta(st,timp);
else
if(tmax[st]>=timp)
ret=cauta(st,timp);
else
ret=cauta(dr,timp);
updt(nod);
return ret;
}
ifstream f("ramen.in");
ofstream g("ramen.out");
int main()
{
f >> n >> T;
for(auto it:clienti)
{
tie(po, tp, i)=it;
set<int>::iterator IT = farfurii.lower_bound(tp - po - T);
sol[i] = *IT + T + po;
farfurii.erase( *IT );
}
return 0;
}
6.3 aquapark
Problema 3 - aquapark 90 de puncte
Pentru a atrage turiştii, primăria unui oraş a hotărât că va construi un parc acvatic imens cu
n piscine. Parcul va avea o zonă acoperită şi va fi ı̂nconjurat de un spaţiu deschis pentru activităţi
ı̂n aer liber.
Zona ı̂nchisă va fi acoperită de o singură clădire de forma unui poligon, iar piscinele vor fi
proiectate ı̂n vârfurile poligonului şi vor putea comunica ı̂ntre ele prin m căi de acces care nu se
vor intersecta. Căile de acces ı̂ntre două piscine pot fi de tipul 1: canal umplut cu apă ı̂n interiorul
clădirii, sau de tipul 2: o alee ı̂n afara clădirii.
În exemplul alăturat prin linie punctată se delimitează
partea acoperită a parcului. Avem 5 piscine, există 6 căi de
acces: (1,2), (2,5), (1,4), (1,3), (3,4), (3,5), dintre care 2 sunt
canale (tipul 1): (1,3) şi (1,4), respectiv 4 sunt alei (tipul 2):
(1,2), (2,5), (3,4) şi (3,5).
Un alt proiect ce păstrează aceleaşi căi de acces, dar diferă
prin tipul acestora, este să construim 4 canale: (1,2), (3,4),
(3,5), (2,5) respectiv 2 alei: (1,3), (1,4).
În total putem construi 8 proiecte distincte cu aceste căi
Figura 6.1: aquapark
de acces. Două proiecte se consideră distincte dacă există cel
puţin o cale de acces ce are tipuri diferite pe cele două proiecte.
Cerinţe
Cunoscând căile de acces ı̂ntre piscine, să se determine una dintre cerinţele următoare:
1. - o modalitate de construire a căilor de acces, precizând tipul fiecăreia;
2. - numărul proiectelor distincte.
Date de intrare
Fişierul de intrare aquapark.in conţine pe prima linie trei numerele separate prin câte un
spaţiu c n m reprezentând ı̂n această ordine tipul cerinţei, numărul piscinelor respectiv numărul
căilor de acces. Următoarele m linii conţin câte două numere x şi y, reprezentând o cale de acces
ı̂ntre piscina x şi piscina y.
Date de ieşire
Fişierul de ieşire aquapark.out va conţine ı̂n funcţie de valoarea lui c următoarele informaţii:
- dacă c 1: pe m linii se vor tipări câte trei numere separate prin câte un spaţiu x y
t, semnificând că ı̂ntre piscina x şi piscina y există o cale de acces de tipul t (1-canal, 2-alee).
Fiecare muchie citită din fişierul de intrare va trebui să apară exact o dată ı̂n fişierul de ieşire,
indiferent de ordinea citirii.
- dacă c 2: se va tipări un singur număr ce va semnifica numărul proiectelor distincte modulo
666013.
CAPITOLUL 6. OJI 2018 6.3. AQUAPARK 113
Exemple
Dacă două muchii interioare se intersectează, una dintre ele trebuie să devină exterioară.
În acest fel unel muchii vor fi condiţionate de alte muchii. Putem construi un graf bipartit din
muchiile grafului.
Pornind de la o muchie, cu ajutorul unei cozi putem eticheta fiecare muchie. Prima va fi
etichetată cu 1, toate cele care se intersectează cu aceasta vor fi etichetate cu 2, cu ajutorul
unei cozi vom eticheta toate muchiile care se intersectează cu o muchie deja etichetată. Enunţul
CAPITOLUL 6. OJI 2018 6.3. AQUAPARK 114
problemei ne garantează că de fiecare dată muchiile nou etichetate nu vor intra ı̂n conflict cu
muchii deja etichetate.
Numărul proiectelor disctincte este egal cu 2 la puterea numărului de componente conexe ale
grafului bipartit.
2
Această rezolvare are complexitate O m
Soluţie pentru 100 de puncte
Soluţia de 100 de puncte se bazează pe aceleaşi observaţii ca şi cea de 75 de puncte, anume
că este nevoie de construcţia acelui graf bipartit. Ce este necesar de observat este că acest graf
bipartit area prea multe muchii, multe din ele nenecesare.
De exemplu dacă avem ı̂n acest graf bipartit 2 vârfuri deoparte şi 2 de altă parte, atunci nu
sunt necesare niciodată toate cele 4 muchii, sunt suficiente oricare 3.
Se poate folosi o baleiere pe baza evenimentelor de forma
a La poziţia x apare o muchie x, y
a La poziţia y dispare o muchie x, y
La orice moment se menţin muchiile care au apărut şi nu au dispărut ı̂nsă, şi deci când dispare
o muchie x, y se poate itera prin această listă de muchii de la cea mai recent aparută spre cea
mai puţin recentă.
Orice muchie de forma z, t care a apărut dar nu a dispărut ı̂ncă are cu siguranţă t % y,
deci dacă z % x atunci aceste două muchii se intersectează dacă sunt amândouă ı̂n interior sau ı̂n
exterior (deci se poate trage o muchie ı̂n graful bipartit).
Implementarea directă a acestei idei funcţionează ı̂n complexitate
O numărul de intersecţii ale muchiilor, şi nu este ı̂ncă suficient de rapid, ı̂nsă o mică observaţie
va reduce complexitatea la O M logM .
Dacă atunci când dispare muchia x, y , se descoperă că ea se intersectează cu muchiile z1 , t1 ,
z2 , t2 , ..., zk , tk cu z1 & z2 & ... & zk atunci tk & tk1 & ... & t2 & t1 altfel nu ar exista soluţie.
Deci orice altă muchie ar mai dispărea ulterior x, y (evident y ' y) şi care se intersectează cu un
zi , ti se intersectează şi cu z1 , t1 . Dar se ştie deja ı̂n graful bipartit că z1 , t1 şi zi , ti sunt
de aceeaşi parte, deci nu mai merită considerată muchia zi , ti dacă i ' 2. Astfel putem elimina
toate muchiile zi, ti cu i ' 2 atunci când procesăm muchia x, y .
Aceste muchii z, t pot fi ţinute ı̂ntr-o stivă, operaţiile reducându-se la eliminarea unor ele-
mente din vârful stivei. Cum pentru orice eveniment ı̂n care apare o muchie x, y doar se adaugă
ı̂n capul stivei muchia, iar un element poate fi şters din stivă cel mult o dată ı̂nseamnă că după
sortarea evenimentelor (pentru a se putea folosi baleierea) complexitatea este O M .
Din cauza sortării complexitatea finală devine O M logM .
struct Edge
{
int from, to, index;
};
struct Event
{
bool opening;
Edge edge;
CAPITOLUL 6. OJI 2018 6.3. AQUAPARK 115
void dfs(const vector< vector<int> > &edges, vector<int> &colors, int node)
{
for (auto &neighbor : edges[node])
{
assert(colors[neighbor] != colors[node]);
if (colors[neighbor] == 0)
{
colors[neighbor] = 3 - colors[node];
dfs(edges, colors, neighbor);
}
}
}
int main()
{
ifstream cin("aquapark.in");
ofstream cout("aquapark.out");
vector<Event> events;
events.reserve(M * 2);
vector<Edge> edges(M);
for (int i = 0; i < M; ++i)
{
int x, y;
assert(cin >> x >> y);
assert(1 <= x && x <= N);
assert(1 <= y && y <= N);
assert(x != y);
if (x > y)
swap(x, y);
edges[i] = Edge{x, y, i};
}
if (a.from != b.from)
return a.from < b.from;
return a.to < b.to;
});
sort(events.begin(), events.end());
conflict[event.edge.index].push_back(it->second);
conflict[it->second].push_back(event.edge.index);
if (kept)
S.erase(it);
else
kept = true;
}
S.erase(make_pair(event.edge.from, event.edge.index));
}
}
if (type == 1)
{
for (auto &edge : edges)
cout << edge.from << " " << edge.to << " " <<
colors[edge.index] << "\n";
}
else
{
int as_power = 1;
for (int i = 0; i < answer; ++i)
as_power = (2 * as_power) % kModulo;
cout << as_power << "\n";
}
}
CAPITOLUL 6. OJI 2018 6.3. AQUAPARK 117
struct Edge
{
int from, to, index;
};
struct Event
{
bool opening;
Edge edge;
if (opening)
{
if (edge.to != that.edge.to)
return edge.to > that.edge.to;
return edge.index < that.edge.index;
}
else
{
if (edge.from != that.edge.from)
return edge.from > that.edge.from;
return edge.index > that.edge.index;
}
}
};
void dfs(const vector< vector<int> > &edges, vector<int> &colors, int node)
{
for (auto &neighbor : edges[node])
{
assert(colors[neighbor] != colors[node]);
if (colors[neighbor] == 0)
{
colors[neighbor] = 3 - colors[node];
dfs(edges, colors, neighbor);
}
}
}
int main()
{
ifstream cin("aquapark.in");
ofstream cout("aquapark.out");
vector<Event> events;
events.reserve(M * 2);
vector<Edge> edges(M);
for (int i = 0; i < M; ++i)
{
int x, y; assert(cin >> x >> y);
assert(1 <= x && x <= N);
assert(1 <= y && y <= N);
assert(x != y);
if (x > y)
swap(x, y);
edges[i] = Edge{x, y, i};
events.push_back(Event{true, edges[i]});
events.push_back(Event{false, edges[i]});
}
if (i > 1)
assert(edges[i - 2].from != edges[i].from ||
edges[i - 2].to != edges[i].to);
}
sort(events.begin(), events.end());
stack< pair<int, int> > S;
if (!S.empty() &&
S.top() == make_pair(event.edge.from, event.edge.index))
{
S.pop();
}
if (last_erased.first != -1)
S.emplace(last_erased);
}
}
{
colors[i] = 1;
dfs(conflict, colors, i);
++answer;
}
if (type == 1)
{
for (auto &edge : edges)
cout << edge.from << " " << edge.to << " "
<< colors[edge.index] << "\n";
}
else
{
int as_power = 1;
for (int i = 0; i < answer; ++i)
as_power = (2 * as_power) % kModulo;
cout << as_power << "\n";
}
}
ifstream fin("aquapark.in");
ofstream fout("aquapark.out");
int caz,a[1000],k,n,m,x,y,p;
bool viz[4000];
struct muchie
{
int x,y,cost;
} b[4000];
int main()
{
p=1;
fin>>caz>>n>>m;
k=0;
for (int i=1; i<=m; ++i)
{
fin>>x>>y;
if (x>y) swap(x,y);
b[++k].x=x;
CAPITOLUL 6. OJI 2018 6.3. AQUAPARK 120
b[k].y=y;
}
sort(b+1,b+k+1,comp);
int st=1,dr=0;
bool ok;
int c[4000];
for(int i=1; i<=k; i++)
if (!viz[i])
{
ok=false;
++dr;
viz[i]=true;
c[dr]=i;
b[i].cost=1;
if (b[i].x==b[i+1].x and b[i].y==b[i+1].y)
/// daca avem doua muchii identice atunci
/// una va fi interna cealalalta externa
{
++dr;
viz[i+1]=true;
c[dr]=i+1;
b[i+1].cost=2;
st+=2;
}
if (st<=dr) ok=true;
while (st<=dr)
{
for (int j=1; j<=k; j++)
if(!viz[j])
if (intersect(b[c[st]],b[j]))
{
++dr;
viz[j]=true;
c[dr]=j;
b[c[dr]].cost=3-b[c[st]].cost;
/// muchia ce se intersecteaza va fi
/// pe malul celalalt
}
st++;
}
if (caz==2)
fout<<p<<"\n";
else
tipar(b,k);
return 0;
}
OJI 2017
7.1 armonica
Problema 1 - armonica 100 de puncte
Spunem că trei numere a, b, c sunt ı̂n progresie armonică dacă b este media armonică a
numerelor a şi c, adică
2 2ac
b 1 1 ac
a c
Cerinţe
Cunoscând un număr natural b să se determine toate perechile de numere naturale a, c pentru
care a, b, c sunt ı̂n progresie armonică.
Date de intrare
Date de ieşire
Exemple
armonica.in armonica.out Explicaţii
3 3 Numărul 3 este medie armonică a numerelor 3 şi 3. Avem
33 progresia armonică (3,3,3)
26 Numărul 3 este medie armonică a numerelor 2 şi 6. Avem
62 progresiile armonice (2,3,6) şi (6,3,2)
121
CAPITOLUL 7. OJI 2017 7.1. ARMONICA 122
ifstream f("armonica.in");
ofstream g("armonica.out");
int main()
{
int i, k = 0;
CAPITOLUL 7. OJI 2017 7.1. ARMONICA 123
f >> b;
for(a=1; a<=b; ++a)
{
if (2*a - b > 0)
if ((b*a) % (2*a-b) == 0)
{
c = (b*a) / (2*a - b);
if (c <= 0) break;
if (a == c) A[++k] = a, C[k] = a;
else A[++k] = a, C[k] = c, A[++k] = c, C[k] = a;
}
}
g << k << endl;
for(i=1; i<=k; ++i)
g << A[i] << " " << C[i] << endl;
return 0;
}
ifstream f("armonica.in");
ofstream g("armonica.out");
long long a, b, c;
vector <long long > A;
int m = A.size();
while(p > 1)
{
for(int i=0; i < m; ++i)
{
a = A[i] * p;
if (a > 0 && a <= z)
if (y%a == 0) A.push_back(a);
}
p /= d;
}
d++;
if (d*d > x) d = x;
}
}
int main()
{
int i, k, ok = 0;
long long K;
f >> b;
if (b%2 == 0)
CAPITOLUL 7. OJI 2017 7.1. ARMONICA 124
{
ok = 1;
K = b/2;
divizori(K);
}
else divizori(b);
k = A.size();
g << 2*k - 1 << ’\n’;
for(i=0; i<(int)A.size(); ++i){
if (ok)
{
a = K + A[i];
c = K + (K*K) / A[i];
g << a << " " << c << ’\n’;
if (a != c) g << c << " " << a << ’\n’;
}
else
{
a = (b + A[i]) / 2;
c = (b + (b*b) / A[i]) / 2;
g << a << " " << c << ’\n’;
if (a != c) g << c << " " << a << ’\n’;
}
}
return 0;
}
ifstream f("armonica.in");
ofstream g("armonica.out");
int main()
{
int i, k = 0;
f >> b;
for(a=1; a<=b; ++a)
{
if (2*a - b > 0)
if ((b*a) % (2*a-b) == 0)
{
c = (b*a) / (2*a - b);
if (c <= 0) break;
if (a == c) A[++k] = a, C[k] = a;
else A[++k] = a, C[k] = c, A[++k] = c, C[k] = a;
}
}
ifstream f("armonica.in");
ofstream g("armonica.out");
CAPITOLUL 7. OJI 2017 7.1. ARMONICA 125
long long a, b, c;
int main()
{
int i, k, ok = 0;
long long K;
f >> b;
if (b%2 == 0)
{
ok = 1;
K = b/2;
b /= 2;
divizori(K*K);
}
else divizori(b*b);
k = A.size();
g << 2*k - 1 << ’\n’;
for(i=0; i<(int)A.size(); ++i){
if (ok)
{
a = K + A[i];
c = K + (K*K) / A[i];
g << a << " " << c << ’\n’;
if (a != c) g << c << " " << a << ’\n’;
}
else
{
a = (b + A[i]) / 2;
c = (b + (b*b) / A[i]) / 2;
g << a << " " << c << ’\n’;
if (a != c) g << c << " " << a << ’\n’;
}
}
return 0;
}
int n;
vector<long long> divisors;
vector<int> primes, powers;
if (n > 1)
{
primes.push_back(n);
powers.push_back(2);
}
back(0, 1);
}
int main()
{
freopen("armonica.in", "r", stdin);
freopen("armonica.out", "w", stdout);
scanf("%d", &n);
if(n%2)
{
getDivisorsOfN2(n);
printf("%d\n", divisors.size());
for(auto dv : divisors)
{
long long d1 = dv;
long long d2 = 1LL*n*n/dv;
return 0;
}
struct fractie
{
long long x,y;
};
CAPITOLUL 7. OJI 2017 7.1. ARMONICA 127
int main()
{
long long b,d,bb,i,nv,sf,p;
fractie v[10000];
fin>>b;
p=1;
v[0].x=v[0].y=1;
nv=1;
d=2;bb=b;
if (bb%2==0) bb/=2;
while(bb>1)
{
p=1;
while (bb%d==0)
{
p*=d;
bb/=d;
}
sf=nv;
while (p>1)
{
v[sf].x=v[0].x*p;
v[sf].y=v[0].y;
sf++;
for (i=1;i<nv;++i)
{
v[sf].x=v[i].x*p;
v[sf].y=v[i].y;
sf++;
v[sf].x=v[i].x;
v[sf].y=v[i].y*p;
sf++;
}
p/=d;
}
d++;
if (d*d>bb) d=bb;
nv=sf;
}
fout<<2*nv-1<<"\n";
fout<<b<<" "<<b<<"\n";
for (i=1;i<nv;++i)
{
fout<<b*(v[i].x+v[i].y)/(2*v[i].x)<<" "
<<b*(v[i].x+v[i].y)/(2*v[i].y)<<’\n’;
fout<<b*(v[i].x+v[i].y)/(2*v[i].y)<<" "
<<b*(v[i].x+v[i].y)/(2*v[i].x)<<’\n’;
}
return 0;
}
ifstream f("armonica.in");
ofstream g("armonica.out");
tip b;
void factorizare(tip),bkt(tip,tip);
vector<pair<tip,tip>>F,sol;
CAPITOLUL 7. OJI 2017 7.1. ARMONICA 128
vector<pair<int,int>> V;
int main()
{
f>>b;
if(b%2)
factorizare(b);
else
factorizare(b/2);
bkt(0,1);
g<<sol.size()<<’\n’;
for(auto it:sol)
g<<it.first<<’ ’<<it.second<<’\n’;
return 0;
}
void factorizare(tip k)
{
for(tip i=2;i*i<=k;i++)
if(k%i==0)
{
tip e=0;
while(k%i==0)
{
e++;
k/=i;
}
F.push_back({i,2*e});
}
if(k>1)
F.push_back({k,2});
}
//g<<d<<’ ’<<x<<’\n’;
sol.push_back({a,c});
return;
}
tip i=F[p].first,j=F[p].second,m=1;
for(tip q=0;q<=j;q++)
{
bkt(p+1,d*m);
m*=i;
}
}
ifstream f("armonica.in");
ofstream g("armonica.out");
tip b,i,j,A[20000],B[20000];
int main()
{
f>>b;
for(i=b-1;2*i-b>0;i--)
if((i*b)%(2*i-b)==0)
A[++j]=i;
g<<2*j+1<<’\n’;
g<<b<<’ ’<<b<<’\n’;
for(i=1;i<=j;i++)
{
g<<A[i]<<’ ’<<b*A[i]/(2*A[i]-b)<<’\n’;
g<<b*A[i]/(2*A[i]-b)<<’ ’<<A[i]<<’\n’;
}
return 0;
}
7.2 ninjago
Problema 2 - ninjago 100 de puncte
După ce eroii ninja l-au ı̂nvins pe Nadakhan, de ziua celor dispăruţi Zane trebuia să păzească
cele n păpuşi din muzeu. Între aceste păpuşi există m coridoare pe care se poate circula ı̂n ambele
sensuri. Se garantează faptul că pe cele m coridoare Zane poate ajunge la fiecare dintre cele n
păpuşi.
Skulkiu, având la dispoziţie 5 tipuri de obstacole A, B, C, D, E, ı̂ncearcă să-l oprească pe
Zane punând pe fiecare coridor câte 4 obstacole.
Zane poate distruge obstacolele de tip A, B, C şi D, dar nu poate să distrugă obstacolele de
tipul E. Pentru a distruge un obstacol de tipul A arma lui Zane are nevoie de 1 unitate de energie,
pentru a distruge un obstacol de tipul B de 2 unităţi de energie, pentru a distruge un obstacol
de tipul C de 3 unităţi de energie, iar pentru a distruge un obstacol de tipul D de 4 unităţi de
energie.
Datorită dispozitivului cu care Skulkiu amplasează obstacolele pe coridor, cele patru obstacole
de pe acelaşi coridor au o adâncime din ce ı̂n ce mai mare, ceea ce implică faptul că pentru a
distruge al doilea obstacol amplasat pe coridor este nevoie de 5 ori mai multă energie decât cea
obişnuită, pentru a distruge cel de-al treilea obstacol amplasat pe coridor este nevoie de 25 ori mai
multă energie decât cea obişnuită, iar pentru a distruge al patrulea obstacol amplasat pe acelaşi
coridor este nevoie de 125 de ori mai multă energie decât cea obişnuită.
Indiferent de sensul de parcurgere al coridorului de către Zane pentru a ı̂nlătura obstacolele,
energia consumată este aceeaşi, aceasta depinzând doar de ordinea ı̂n care au fost amplasate
obstacolele de către Skulkiu.
Zane nu va ı̂nlătura obstacolele de pe toate coridoarele ci doar strictul necesar pentru a avea
acces la fiecare păpuşă.
Zane doreşte să-i lase pe ceilalţi ninja să se antreneze aşa că face ı̂n aşa fel ı̂ncât ajutorul pentru
distrugerea obstacolelor de tip E să fie minim şi apoi ca el să utilizeze un număr minim de unităţi
de energie.
Pentru coridoarele pe care se află obstacole de tip E Zane consumă energie doar pentru
obstacolele de tip A, B, C şi D. Iniţial Zane se află lângă păpuşa 1.
Cerinţe
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 130
1) Precizaţi la câte dintre cele n păpuşi poate ajunge Zane ı̂nainte de a cere ajutorul celorlalţi
ninja.
2) Precizaţi pentru eliberarea câtor coridoare trebuie să ceară ajutor extern pentru a reuşi să
ajungă la toate cele n păpuşi şi câte obstacole de tip E sunt ı̂n total pe aceste coridoare.
3) Precizaţi care este numărul minim de unităţi de energie utilizate.
Date de intrare
Fişierul ninjago.in conţine pe prima linie un număr natural v care poate avea doar valorile
1, 2 sau 3 reprezentând cerinţa care va fi rezolvată. Fişierul ninjago.in conţine pe a doua linie
numerele naturale n şi m separate printr-un spaţiu, iar pe următoarele m linii pentru fiecare
coridor două numere naturale separate printr-un spaţiu reprezentând cele două păpuşi ı̂ntre care
se circulă pe coridorul respectiv urmate de un spaţiu şi patru litere corespunzătoare celor patru
tipuri de obstacole ı̂n ordinea ı̂n care Skulkiu le-a amplasat pe coridorul respectiv. Între cele patru
litere nu se află nici un spaţiu.
Date de ieşire
Dacă valoarea lui v este 1 atunci fişierul de ieşire ninjago.out va conţine pe prima linie numai
numărul păpuşilor la care poate ajunge Zane ı̂nainte de a cere ajutorul celorlalţi ninja.
Dacă valoarea lui v este 2 atunci fişierul de ieşire ninjago.out va conţine pe prima linie numărul
de coridoare pe care nu le poate elibera singur şi pe a doua linie numărul total de obstacole de tip
E de pe aceste coridoare.
Dacă valoarea lui v este 3 atunci fişierul de ieşire ninjago.out va conţine pe prima linie numai
numărul minim de unităţi de energie utilizate.
Exemple
Rezolvarea problemei presupune determinarea arborelui parţial de cost minim ı̂ntr-un graf
neorientat cu n noduri şi m muchii.
Costurile muchiilor vor fi numere naturale scrise ı̂n baza 5 formate din 4 sau 5 cifre, dar reţinute
ı̂n baza 10.
În momentul citirii datelor se transformă codificarea obstacolelor ı̂n numere naturale astfel:
pentru fiecare dintre cele 4 obstacole pe poziţia corespunzătoare se ı̂nlocuiesc literele A, B, C
şi D cu cifrele 1, 2, 3, 4, iar pentru muchiile corespunzătoare coridoarelor care conţin obstacole
de tip E se asociează ca şi cost un număr natural scris ı̂n baza 5 format din 5 cifre, prima cifra
reprezentând numărul de obstacole de tip E, iar cifra corespunzătoare obstacolului de tip E se
ı̂nlocuieşte cu 0.
De exemplu pentru secvenţa de obstacole ECEB se va genera numărul 22030 scris ı̂n baza 5,
deci costul reprezentat ı̂n baza 10 va fi 1515. Transformarea numărului format din ultimele 4 cifre
din baza 5 ı̂n baza 10 (restul ı̂mpărţirii numărului reţinut la 625) va reprezenta numărul unităţilor
de energie cosumate de arma lui Zane pentru a distruge obstacolele de pe coridorul corespunzător,
iar câtul ı̂mpărţirii costului la 6254 va reprezenta numărul de obstacole de tip E.
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 132
0 1
Cu aceste codificări cel mai mic cost posibil va fi 156 (0AAAA =¿ 01111 =¿ 1 5 1 5
2 3 4
1 5 1 5 0 5 = 1 5 25 125 0 = 156), iar cel mai mare cost posibil va fi 2500
0 1 2 3 4
(4EEEE =¿ 40000 =¿ 0 5 0 5 0 5 0 5 4 5 4 625 = 2500). Astfel se poate
evita ordonarea muchiilor păstrând muchiile ı̂ntr-un tablou de liste simplu ı̂nlănţuite a cărui indici
reprezintă costul muchiilor. Parcurgând acest vector vom avea muchiile ı̂n ordinea de care avem
nevoie: cele care nu conţin obstacole de tip E la ı̂nceput ordonate crescător dupa cost (de la 156
la 624), iar la sfârşit cele care conţin obstacole de tip E ordonate după numărul de obstacole de
tip E şi apoi după cost.
Se ı̂ncepe contruirea arborelui parţial de cost minim folosind algoritmul lui Kruskal cu păduri
de mulţimi disjuncte, ı̂mpărţit ı̂n două etape: muchiile fără obstacole de tip E şi apoi muchiile ce
conţin obstacole de tip E. După prima etapă se analizează muchiile alese ı̂n arboreal parţial de
cost minim şi se pot da uşor şi rapid răspunsurile:
a la câte dintre cele n papuşi poate ajunge Zane ı̂nainte de a cere ajutorul celorlalţi ninja
a pentru eliberarea câtor coridoare trebuie să ceară ajutor extern pentru a reuşi să ajunga la
toate cele n papuşi
Apoi se termină determinarea arborelui parţial de cost minim folosind muchii ce conţin obsta-
cole de tip E, calculând simultan numărul total de obstacole de tip E. Numărul minim de unităţi
de energie utilizate se calculează ı̂n timp ce se construieşte arborele parţial de cost minim ı̂n cele
două etape.
int getCost(char c)
{
return c - ’A’ + 1;
}
int getCost(string s)
{
int ret = 0;
int p = 1;
return ret;
}
struct Edge
{
int f, t;
int cost;
int E;
};
int n, m;
vector<Edge> E;
vector<int> under;
vector<int> TT;
if (x == y)
return false;
int countReachableFrom1()
{
int ret = 0;
for (int i = 1; i <= n; ++i)
ret += (getUp(i) == getUp(1));
return ret;
}
int main()
{
ifstream in("ninjago.in");
int v;
in >> v;
in >> n >> m;
E.reserve(m);
under.resize(n + 1);
TT.resize(n + 1);
for(int i = 1; i <= n; ++i)
{
under[i] = 1;
TT[i] = i;
}
assert(s.length() == 4);
for (auto c: s)
assert(strchr("ABCDE", c) != NULL);
E.push_back({f, t, s});
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 134
in.close();
sort(E.begin(), E.end(), [](const Edge &a, const Edge &b) -> bool
{
return a.asECost() < b.asECost();
});
int energyUsed = 0;
size_t at = 0;
while (at < E.size() && E[at].E == 0)
{
if (unite(E[at].f, E[at].t))
energyUsed += E[at].cost;
at++;
}
int badEdges = 0;
int takenEs = 0;
while (at < E.size())
{
bool used = unite(E[at].f, E[at].t);
badEdges += used;
takenEs += E[at].E * used;
energyUsed += used * E[at].cost;
at++;
}
ofstream out("ninjago.out");
switch (v)
{
case 1:
out << from1 << "\n";
break;
case 2:
out << badEdges << "\n" << takenEs << "\n";
break;
case 3:
out << energyUsed << "\n";
break;
default:
assert(false);
break;
}
return 0;
}
ifstream f("ninjago.in");
ofstream g("ninjago.out");
void APM();
bitset<40000> viz;
int main()
{
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 135
f>>q>>n>>m;
if(q==1)
{
for(;m;m--)
{
f>>x>>y>>s;
if(!strchr(s.c_str(),’E’))
{
v[x].push_back(y);
v[y].push_back(x);
}
}
g<<DFS(1);
return 0;
}
for(; m; m--)
{
f>>x>>y>>s;
p=1,e=0,c=0;
for(j=0; j<4; j++,p*=5)
{
if(s[j]==’E’)
s[j]=’A’-1,e++;
c+=p*(s[j]-’A’+1);
}
M[e][c].push_back({x,y});
}
G=0,O=0,C=0;
for(i=1; i<=n; i++)
t[i]=i;
for(n--,e=0; e<=4&&n; e++)
for(j=e>0,c=0; c<625&&n; c++)
for(k=M[e][c].size()-1;k>=0&&n;k--)
if((x=root(M[e][c][k].first)) != (y=root(M[e][c][k].second)))
t[x]=y,C+=c,G+=j,O+=e,n--;
q==2 ? g<<G<<’\n’<<O : g<<C;
return 0;
}
return ret;
}
#define NM 31210
ifstream f("ninjago.in");
ofstream g("ninjago.out");
struct edge
{
int x, y, c;
char e;
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 136
int Radacina(int i)
{
int k = i;
while (t[k] > 0)
k = t[k];
return i;
}
void APM()
{
edge w;
int main()
{
int i, j, v;
edge w;
string s;
w.e = e;
w.c = ct;
G.push_back(w);
if (ok)
{
G1[w.x].push_back(w.y);
G1[w.y].push_back(w.x);
}
}
if (v == 1)
{
DFS(1);
if (n1 > 1) --n1;
g << n1 << ’\n’;
return 0;
}
APM();
if (v == 2)
{
int obs = 0, nre = 0;
for(i=0; i<(int) sol.size(); ++i)
{
if (sol[i].c >= 625)
{
obs++;
nre += sol[i].e;
}
}
if (v == 3)
{
g << ctotal << ’\n’;
return 0;
}
return 0;
}
#include <bits/stdc++.h>
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 138
ifstream f("ninjago.in");
ofstream g("ninjago.out");
int c,n,m,x,y,i,gal,obs,cost,sol,GLUE(int,int,int),root(int),used[N],t[N];
string s;
vector<int> v[N];
void DFS(int),APM(),edge(),SPLIT(int&,int&,int&,int);
bitset<40000> viz;
int main()
{
for(f>>c>>n>>m,i=1;i<=m;i++)
edge();
if(c==1)
{
DFS(1);
g<<sol;
}
else
APM();
return 0;
}
void APM()
{
priority_queue<int> Q;
used[1]=1;
for(auto it:v[1])
Q.push(MASK30-it);
for(int na=n-1;na;)
{
int e,cst,y;
SPLIT(e,cst,y,MASK30-Q.top());
Q.pop();
if(!used[y])
{
if(e)gal++,obs+=e;
cost+=cst;
used[y]=1;
na--;
for(auto it:v[y])
if(!used[it&MASK16])
Q.push(MASK30-it);
}
}
void edge()
{
int x,y,p=1,e=0,cst=0,j=0;
string s;
for(f>>x>>y>>s,j=0;j<4;j++,p*=5)
if(s[j]<’E’)
cst+=p*(s[j]-’A’+1);
else
e++;
v[x].push_back(GLUE(e,cst,y));
v[y].push_back(GLUE(e,cst,x));
}
short v;
int n,m;
struct muchie
{
int e1,e2,c;
};
muchie g[31202];
void citire()
{
ifstream fin("ninjago.in");
int i,j,p5;
char o;
fin >> v;
fin >> n >> m;
void Sortare()
{ int i,mf;
muchie aux;
do
{ mf=0;
for (i=1;i<m;i++)
if (g[i].c>g[i+1].c)
{ aux=g[i];
g[i]=g[i+1];
g[i+1]=aux;
mf=1;
}
} while (mf);
}
int main()
{ ofstream fout("ninjago.out");
}
i++;
}
if (ma==n-1)
switch (v)
{ case 1:
fout<<n<<’\n’; break;
case 2:
fout<<"0" << endl <<"0"; break;
case 3: fout << cost;
}
else
{
if (v==1)
{
nn=0;
if (s[j]==vf)
s[j]=s[g[i].e1];
}
i++;
}
fout.close();
return 0;
}
short v;
int n,m,e1[31202],e2[31202],c[31202];
void citire()
{
ifstream fin("ninjago.in");
int i,j,p5;
char o;
fin >> v;
fin >> n >> m;
fin.close();
}
void sortare()
{
int cat[5], b[5];
int y[31202],oe1[31202],oe2[31202], poz[31202];
short cif, j;
int p5;
p5=1;
for (cif = 1; cif <= 5; cif++)
{ for (j = 0; j <= 4; j++)
cat[j]=0;
for (j = 1; j <= m; j++)
{ cat[(c[j]/p5)%5]++;
poz[j]=cat[(c[j]/p5)%5];
}
b[0]=0;
for (j = 1; j <= 4; j++)
b[j]=b[j-1]+cat[j-1];
for (j = 1; j <= m; j++)
{ y[b[(c[j]/p5)%5]+poz[j]]=c[j];
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 142
oe1[b[(c[j]/p5)%5]+poz[j]]=e1[j];
oe2[b[(c[j]/p5)%5]+poz[j]]=e2[j];
}
for (j = 1; j <= m; j++)
{
c[j]=y[j];
e1[j]=oe1[j];
e2[j]=oe2[j];
}
p5*=5;
}
}
int main()
{
ofstream fout("ninjago.out");
if (ma==n-1)
switch (v)
{ case 1:
fout<<n<<’\n’; break;
case 2:
fout<<"0" << endl <<"0"; break;
case 3: fout << cost;
}
else
{
if (v==1)
{
nn=0;
for (j = 1; j <= n; j++)
if (s[j]==s[1]) nn++;
fout << nn;
}
else
{
nn=n-1-ma;
while (ma<n-1)
{
if(s[e1[i]]!=s[e2[i]])
{
ma++;
cost+=c[i]% 625;
obst+=c[i]/625;
vf=s[e2[i]];
for (j = 1; j <= n; j++)
if (s[j]==vf)
s[j]=s[e1[i]];
}
i++;
}
}
}
fout.close();
return 0;
}
short v;
int n,m,e1[31202],e2[31202],c[31202];
void citire()
{
ifstream fin("ninjago.in");
int i,j,p5;
char o;
fin >> v;
fin >> n >> m;
void sortare()
{
int cat[5], b[5];
int y[31202],oe1[31202],oe2[31202], poz[31202];
short cif, j;
int p5;
p5=1;
for (cif = 1; cif <= 5; cif++)
{ for (j = 0; j <= 4; j++)
cat[j]=0;
for (j = 1; j <= m; j++)
{ cat[(c[j]/p5)%5]++;
poz[j]=cat[(c[j]/p5)%5];
}
b[0]=0;
for (j = 1; j <= 4; j++)
b[j]=b[j-1]+cat[j-1];
for (j = 1; j <= m; j++)
{ y[b[(c[j]/p5)%5]+poz[j]]=c[j];
oe1[b[(c[j]/p5)%5]+poz[j]]=e1[j];
oe2[b[(c[j]/p5)%5]+poz[j]]=e2[j];
}
for (j = 1; j <= m; j++)
{
c[j]=y[j];
e1[j]=oe1[j];
e2[j]=oe2[j];
}
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 144
p5*=5;
}
}
int main()
{
ofstream fout("ninjago.out");
citire();
sortare();
i=1;
while (ma<n-1 && c[i]<625)
{
if (s[e1[i]]!=s[e2[i]])
{
ma++;
cost+=c[i];
vf=s[e2[i]];
for (j = 1; j <= n; j++)
if (s[j]==vf)
s[j]=s[e1[i]];
}
i++;
}
if (ma==n-1)
switch (v)
{ case 1:
fout<<n<<’\n’; break;
case 2:
fout<<"0" << endl <<"0"; break;
case 3: fout << cost;
}
else
{
if (v==1)
{
nn=0;
for (j = 1; j <= n; j++)
if (s[j]==s[1]) nn++;
fout << nn;
}
else
{
nn=n-1-ma;
while (ma<n-1)
{
if(s[e1[i]]!=s[e2[i]])
{
ma++;
cost+=c[i]% 625;
obst+=c[i]/625;
vf=s[e2[i]];
for (j = 1; j <= n; j++)
if (s[j]==vf)
s[j]=s[e1[i]];
}
i++;
}
fout.close();
return 0;
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 145
ifstream f("ninjago.in");
ofstream g("ninjago.out");
int c,n,m,x,y,i,rx,ry,sol,root(int),t[N];
string s;
tuple<int,int,int,int> M[N],edge();
vector<int> v[N];
void DFS(int),APM();
bitset<40000> viz;
int main()
{
for(f>>c>>n>>m,i=1;i<=m;i++) M[i]=edge();
if(c==1){DFS(1);g<<sol;} else APM();
return 0;
}
void APM()
{
int x,y,e,cst, gal=0,obs=0,capm=0;
string s;
sort(M+1,M+m+1);
for(n--,i=1;n;i++)
{ // http://en.cppreference.com/w/cpp/utility/tuple/tie
tie(e,cst,x,y)=M[i];//A std::tuple object containing lvalue references
if((rx=root(x))!=(ry=root(y)))
t[rx]=ry,capm+=cst,gal+=e>0,obs+=e,n--;
}
tuple<int,int,int,int> edge()
{
int x,y,p=1,e=0,cst=0,j=0;
string s;
for(f>>x>>y>>s,j=0;j<4;j++,p*=5)
if(s[j]<’E’)
cst+=p*(s[j]-’A’+1);
else
e++;
if(!e)
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 146
{
v[x].push_back(y);
v[y].push_back(x);
}
return make_tuple(e,cst,x,y);
}
short v;
int n,m;
struct nod
{ int e1,e2;
nod* urm;
};
nod* lm[2501];
void citire()
{
ifstream fin("ninjago.in");
int i,j,p5,c;
char o;
nod *p;
fin >> v;
fin >> n >> m;
for (i=156;i<=2500;i++)
lm[i]=NULL;
p->urm=lm[c];
lm[c]=p;
}
fin.close();
}
int radacina(int x)
{
int r,y;
r=x;
while (T[r]!=r)
r=T[r];
while (T[x]!=x)
{
y=T[x];
T[x]=r;
x=y;
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 147
}
return r;
}
if (H[x]==H[y])
H[y]++;
}
int main()
{ ofstream fout("ninjago.out");
for (i=1;i<=n;i++)
{
T[i]=i;
H[i]=1;
}
i=156;
while (ma<n-1 && i<625)
{ p=lm[i];
while (ma<n-1 && p)
{
if (radacina(p->e1)!=radacina(p->e2))
{
ma++;
cost+=i;
UnesteArbori(radacina(p->e1),radacina(p->e2));
}
p=p->urm;
}
i++;
}
if (ma==n-1)
switch (v)
{ case 1:
fout<<n<<’\n’; break;
case 2:
fout<<"0" << endl <<"0"; break;
case 3: fout << cost;
}
else
{
if (v==1)
{
nn=0;
r1=radacina(1);
for (j = 1; j <= n; j++)
if (radacina(j)==r1) nn++;
fout << nn;
}
else
{
nn=n-1-ma;
while (ma<n-1)
{
p=lm[i];
while (ma<n-1 && p)
{ if (radacina(p->e1)!=radacina(p->e2))
{
ma++;
cost+=i%625;
obst+=i/625;
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 148
UnesteArbori(radacina(p->e1),radacina(p->e2));
}
p=p->urm;
}
i++;
}
fout.close();
return 0;
}
short v;
int n,m;
int c[2000][2000];
const int Infinit = 3200;
int nuE,p1,ncE,noE;
long cost;
void citire()
{
ifstream fin("ninjago.in");
int i,j,p5,x,y;
char o;
fin >> v;
fin >> n >> m;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i==j)
c[i][j]=0;
else
c[i][j]=c[j][i]=Infinit;
c[y][x]=c[x][y];
}
fin.close();
}
nuE=0;
ncE++;
noE+=c[x][y]/625;
}
cost+=c[x][y]% 625;
void APMPrim()
{ int i,j,k, Min;
int s[2000];
for(i=2;i<=n;i++)
s[i]=1;
for(k=1;k<=n-1;k++)
{
Min=Infinit;
for(i=1;i<=n;i++)
if(s[i])
if(Min>c[s[i]][i])
{
Min=c[s[i]][i];
j=i;
}
adaug(s[j],j);
for(i=1;i<=n;i++)
if(s[i] && c[i][s[i]]>c[i][j])
s[i]=j;
s[j]=0;
}
}
int main()
{ ofstream fout("ninjago.out");
citire();
nuE=1;
p1=1;
ncE=0;
noE=0;
cost=0;
APMPrim();
switch (v)
{ case 1:
fout<<p1<<’\n’; break;
case 2:
fout<<ncE << endl <<noE; break;
case 3: fout << cost;
}
fout.close();
return 0;
}
short v;
int n,m;
struct muchie
{
int e1,e2,c;
};
muchie g[31202];
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 150
void citire()
{
ifstream fin("ninjago.in");
int i,j,p5;
char o;
fin >> v;
fin >> n >> m;
while (i <= j)
{
while (g[i].c < pivot)
i++;
while (g[j].c > pivot)
j--;
if (i <= j)
{
tmp = g[i];
g[i] = g[j];
g[j] = tmp;
i++;
j--;
}
};
if (left < j)
quickSort(left, j);
if (i < right)
quickSort(i, right);
}
int main()
{
ofstream fout("ninjago.out");
cost+=g[i].c;
vf=s[g[i].e2];
for (j = 1; j <= n; j++)
{
if (s[j]==vf)
s[j]=s[g[i].e1];
}
i++;
}
if (ma==n-1)
switch (v)
{ case 1:
fout<<n<<’\n’; break;
case 2:
fout<<"0" << endl <<"0"; break;
case 3: fout << cost;
}
else
{
if (v==1)
{
nn=0;
for (j = 1; j <= n; j++)
if (s[j]==s[1]) nn++;
fout << nn;
}
else
{
nn=n-1-ma;
while (ma<n-1)
{
if(s[g[i].e1]!=s[g[i].e2])
{
ma++;
cost+=g[i].c% 625;
obst+=g[i].c/625;
vf=s[g[i].e2];
for (j = 1; j <= n; j++)
if (s[j]==vf)
s[j]=s[g[i].e1];
}
i++;
}
fout.close();
return 0;
}
#include<bits/stdc++.h>
short v;
int n,m;
struct muchie
{
int e1,e2,c;
};
muchie g[31202];
CAPITOLUL 7. OJI 2017 7.2. NINJAGO 152
void citire()
{
ifstream fin("ninjago.in");
int i,j,p5;
char o;
fin >> v;
fin >> n >> m;
int main()
{ ofstream fout("ninjago.out");
citire();
sort(g+1,g+m+1,ComparMuchii);
}
}
i++;
}
if (ma==n-1)
switch (v)
{ case 1:
fout<<n<<’\n’; break;
case 2:
fout<<"0" << endl <<"0"; break;
case 3: fout << cost;
}
else
{
if (v==1)
CAPITOLUL 7. OJI 2017 7.3. PERMUTARE 153
{
nn=0;
for (j = 1; j <= n; j++)
if (s[j]==s[1]) nn++;
fout << nn;
}
else
{
nn=n-1-ma;
while (ma<n-1)
{
if(s[g[i].e1]!=s[g[i].e2])
{
ma++;
cost+=g[i].c% 625;
obst+=g[i].c/625;
vf=s[g[i].e2];
for (j = 1; j <= n; j++)
if (s[j]==vf)
s[j]=s[g[i].e1];
}
i++;
}
fout.close();
return 0;
}
7.3 permutare
Problema 3 - permutare 100 de puncte
Definim o permutare dublă de ordin n ca fiind un şir format din primele 2n numere naturale
nenule:
a1 , a2 , ..., an , an1 , an2 , ..., a2n .
Această permutare dublă este de trei ori ı̂n creştere, dacă sunt adevărate următoarele trei
proprietăţi:
1. secvenţa formată din primele n elemente este crescătoare: a1 $ a2 $ ... $ an
2. secvenţa formată din ultimele n elemente este crescătoare: an1 $ an2 $ ... $ a2n
3. perechile ordonate formate din elementele aflate pe poziţii identice ale celor două secvenţe
sunt de asemenea ı̂n ordine crescătoare: a1 $ an1 , a2 $ an2 , ... , an $ a2n .
De exemplu permutarea 1, 3, 4, 2, 5, 6 este o permutare dublă de ordin 3, de trei ori ı̂n creştere,
pentru că secvenţele 1, 3, 4 şi 2, 5, 6 formează şiruri crescătoare, iar toate perechile formate din
elementele de pe poziţii identice: 1, 2, 3, 5, 4, 6 formează deasemenea şiruri crescătoare.
Următoarele permutări duble nu au proprietatea de trei ori ı̂n creştere:
1, 4, 3, 2, 5, 6 - secvenţa 1, 4, 3 nu este crescătoare,
1, 3, 4, 2, 6, 5 - secvenţa 2, 6, 5 nu este crescătoare,
1, 4, 5, 2, 3, 6 - perechea 4, 3 nu este crescătoare.
Pentru simplificare ı̂n continuare permutarea dublă de trei ori ı̂n creştere se va numi permutare.
Vom considera toate permutările de ordin n ordonate lexicografic, numerotate ı̂ncepând cu 1.
Tabelul de mai jos conţine datele pentru n 3:
CAPITOLUL 7. OJI 2017 7.3. PERMUTARE 154
poziţie permutare
1 123456
2 124356
3 125346
4 134256
5 135246
Există două tipuri de ı̂ntrebări:
1. Ce permutare se află pe o poziţie dată?
2. Pe ce poziţie se află o permutare dată?
Prima ı̂ntrebare este codificată astfel: 1 n p şi se compune din valorile
1 - tipul ı̂ntrebării,
n - ordinul permutării,
p - poziţia permutării cerute.
A doua ı̂ntrebare este codificată astfel: 2 n a1 a2 ... a2n şi se compune din valorile
2 - tipul ı̂ntrebării,
n - ordinul permutării,
a1 a2 ... a2n - elementele permutării.
Exemple
Întrebarea 1 3 2 ı̂nseamnă:
”Ce permutare de ordin 3 se află pe poziţia 2 ı̂n ordine lexicografică?” şi are răspunsul: 1 2 4
3 5 6.
Întrebarea 2 3 1 3 5 2 4 6 ı̂nseamnă:
”Pe ce poziţie se află permutarea de ordin 3: 1 3 5 2 4 6?” şi are răspunsul: 5.
Cerinţe
Să se răspundă corect la un set de ı̂ntrebări.
Date de intrare
Fişierul permutare.in conţine pe fiecare linie câte o ı̂ntrebare de orice tip.
Date de ieşire
Fişierul permutare.out va conţine pe câte o linie câte un răspuns la fiecare ı̂ntrebare din
fişierul de intrare, ı̂n ordinea ı̂ntrebărilor.
Restricţii şi precizări
- 2 $ n $ 1000;
- 0 $ p & 1000000000 (ı̂n cazul ı̂ntrebărilor de tip 1);
- răspunsul la ı̂ntrebările de tip 2 este & 1000000000;
- fişierele de intrare vor conţine cel mult 2000 de ı̂ntrebări;
- pentru teste ı̂n valoare de 20 de puncte numărul de ı̂ntrebări va fi 1000 iar numerele de ordine
ce intervin ı̂n calcule vor fi mai mici decât 5000;
- pentru teste ı̂n valoare de 30 de puncte ı̂ntrebările vor fi de tipul 1;
- pentru teste ı̂n valoare de 30 de puncte ı̂ntrebările vor fi de tipul 2;
- pentru teste ı̂n valoare de 30 de puncte ı̂ntrebările vor fi mixte.
- problema va fi evaluată pe teste in valoare de 90 de puncte.
- se vor acorda 10 puncte din oficiu.
Exemple
permutare.in permutare.out Explicaţii
132 124356 a doua permutare de ordin 3 (1,2,4,3,5,6)
23135246 5 permutarea (1,3,5,2,4,6) are poziţia 5
141 12345678 prima permutare de ordin 4 (1,2,3,4,5,6,7,8)
2412345678 1 Permutarea (1,2,3,4,5,6,7,8) are poziţia 1
Pentru simplitate ”permutarea dublă de trei ori ı̂n creştere” o vom numi ”permutare”. Prin
definiţia permutării observăm că pentru fiecare element din a doua secvenţă, există un element
corespunzător din prima secvenţă cu valoare mai mică.
Astfel, dacă pentru fiecare permutare construim un şir de caractere ı̂n care pe poziţiile indicate
de elementele primei secvenţe punem caracterul ’(’ iar pentru poziţiile indicate de elementele celei
de a doua secvenţe punem caracterul ’)’, obţinem o parantezare formată din n perechi de paranteze.
Ordinea lexicografică a tuturor parantezărilor coincide cu cea a permutărilor.
Problema se poate rezolva cu programare dinamică bazată pe formula de recurenţă
P i, j P i 1, j P i, j 1
ofstream out("permutare.out");
int ffst[MAX_N];
int fsnd[MAX_N];
int fst[MAX_N];
int snd[MAX_N];
int type;
int n;
int at = 0;
int p;
bool check()
{
at++;
if (type == 1 && at == p)
{
for (int i = 1; i <= n; ++i)
out << fst[i] << " ";
if (type == 2)
{
for (int i = 1; i <= n; ++i)
if (fst[i] != ffst[i] || snd[i] != fsnd[i])
return false;
return false;
CAPITOLUL 7. OJI 2017 7.3. PERMUTARE 156
if (f <= n)
{
fst[f] = (f + s - 1);
if (back(f + 1, s))
return true;
}
if (s < f)
{
snd[s] = (f + s - 1);
if (back(f, s + 1))
return true;
}
return false;
}
if (type == 1)
{
ss >> n >> p;
back(1, 1);
}
else
{
ss >> n;
for(int i = 1; i <= n; ++i)
ss >> ffst[i];
for(int i = 1; i <= n; ++i)
ss >> fsnd[i];
back(1, 1);
}
}
int main()
{
ifstream in("permutare.in");
string s;
while (true)
{
getline(in, s);
if (in.eof())
break;
process(s);
}
}
ofstream out("permutare.out");
void precalc()
CAPITOLUL 7. OJI 2017 7.3. PERMUTARE 157
{
for (int i = 1; i <= MAX_N; ++i)
{
DP[i][0] = 1;
for (int j = 1; j <= i; ++j)
{
DP[i][j] = DP[i - 1][j] + DP[i][j - 1];
int at = 1;
snd[j] = at;
j++;
u--;
}
else
{
fst[i] = at;
i++;
v--;
}
at++;
}
void solve2(int n)
{
int i = 1, j = 1;
int at = 1;
int u = n, v = n;
int p = 1;
p += DP[u][v - 1];
u--;
CAPITOLUL 7. OJI 2017 7.3. PERMUTARE 158
j++;
}
at++;
}
int type;
ss >> type;
if (type == 1)
{
int n, p;
ss >> n >> p;
solve1(n, p);
}
else
{
int n;
ss >> n;
int main()
{
precalc();
ifstream in("permutare.in");
string s;
while (true)
{
getline(in, s);
if (in.eof())
break;
process(s);
}
return 0;
}
int n, m;
int d[maxn][maxn];
int sol[2*maxn];
int main()
{
freopen("permutare.in", "r", stdin);
freopen("permutare.out", "w", stdout);
d[0][0]=1;
CAPITOLUL 7. OJI 2017 7.3. PERMUTARE 159
return 0;
}
bool egal()
{
for (int i=1;i<=2;++i)
for (int j=1;j<=n;++j)
if (a[i][j]!=p[i][j])
return 0;
return 1;
}
int main()
{
ifstream fin ("permutare.in");
ofstream fout("permutare.out");
while (fin>>t)
if (t==2)
{
fin>>n;
for (int i=1;i<=2;++i)
for (int j=1;j<=n;++j)
fin>>a[i][j];
gasit=false;
nrsol=0;
back2(1,1,1);
fout<<nrsol<<"\n";
}
else
{
fin>>n>>k;
gasit=false;
nrsol=0;
CAPITOLUL 7. OJI 2017 7.3. PERMUTARE 161
back1(1,1,1);
for (int j=1;j<=n;++j)
fout<<a[1][j]<<" ";
for (int j=1;j<=n;++j)
fout<<a[2][j]<<" ";
fout<<endl;
}
return 0;
}
ifstream f("permutare3.in");
ofstream g("permutare.out");
int main()
{
D[0][0]=1;
for(i=1; i<1000; i++)
{ D[i][0]=1;
for(k=1; k<=i; k++)
D[i][k]=min(D[i-1][k]+D[i][k-1],oo);
}
while(f>>c>>n)
{
u=v=n, p=1, q=n+1, m=2*n;
if(c==1)
{
cout<<"i : u v k D[u][v-1]\n";// u=1 , v=0
f>>k;
for(i=1; i<=m; i++)
{
cout<<i<<" : "<<u<<" "<<v<<"\t"<<k<<" \t"<<D[u][v-1]<<"\n";
if(k <= D[u][v-1]) x[p++]=i,v--;
else x[q++]=i, k-=D[u--][v-1];
}
cout<<’\n’;
for(i=1; i<=m; i++) cout<<x[i]<<’ ’;
cout<<’\n’;
continue;
}
g<<k<<’\n’;
cout<<"\nk = "<<k<<"\n";
}
return 0;
}
unsigned p[1002][1002],a[3][1002],t,n,i,j,nr,m,d,
poz,linie,k,j1,j2,elementcurent;
int main()
{
ifstream fin ("permutare.in");
ofstream fout("permutare.out");
p[1][1]=1;
linie=1;
while (fin>>t)
if (t==2)
{
fin>>n;
for (i=1;i<=2;++i)
for (j=1;j<=n;++j)
fin>>a[i][j];
for (i=linie+1;i<=n+1;++i)
{
p[i][1]=1;
for (j=2;j<=i;++j)
{
if (p[i-1][j]+p[i][j-1]>2000000005)
p[i][j]=2000000005;
else
p[i][j]=p[i-1][j]+p[i][j-1];
}
}
linie=n+1;
nr=0;
m=n+1; // cel mai mare prim-element posibil de pe linia a 2-a
d=0; // diferenta pentru prelucrare recursiva,
// dupa fiecare pozitie scadem 2
poz=1; // pozitia elementului curent de pe linia a doua a matricei
while (1)
{
if (a[2][poz]-d==m)
{
nr++;
break;
}
nr+=p[n+1][m-a[2][poz]+d];
++poz;
d+=2;
m--;
n--;
}
fout<<nr<<"\n";
}
else
{
fin>>n>>k;
p[1][1]=1;
for (i=linie+1;i<=n;++i)
CAPITOLUL 7. OJI 2017 7.3. PERMUTARE 163
{
p[i][1]=1;
for (j=2;j<=i;++j)
if (p[i-1][j]>2000000005-p[i][j-1])
p[i][j]=2000000005;
else
p[i][j]=p[i-1][j]+p[i][j-1];
}
linie=n;
d=0; // diferenta pentru prelucrare recursiva,
// se mareste din 2 in 2
j1=1;
j2=1;
elementcurent=1;
for (i=n;i>0 and k;i--)
{
m=i+2;
nr=0;j=1;
while(nr+p[i][j]<k)
{
nr+=p[i][j];
// fout<<nr<<endl;
m--;
j++;
}
m--;
j++;
for(j=elementcurent;j<m+d;++j)
a[1][j1++]=elementcurent++;
a[2][j2++]=elementcurent++;
k=k-nr;
d=d+2;
}
while (j2<=n)
{
a[2][j2]=a[2][j2-1]+1;
j2++;
}
return 0;
}
OJI 2016
8.1 elicoptere
Problema 1 - elicoptere 100 de puncte
Arhipelagul Zopopan este format din n insule de formă triunghiulară numerotate de la 1 la n.
Fiecare insulă este localizată prin coordonatele carteziene ale vârfurilor.
Administraţia doreşte să cumpere elicoptere pentru a realiza transportul ı̂ntre insule. Un
elicopter va putea să asigure o rută ı̂ntre două insule pe distanţa minimă obţinută pe orizontală
sau verticală (paralel cu axele de coordonate). În plus, datorită capacităţii rezervorului o astfel
de rută nu poate să depăşească o valoare k - număr natural. Elicopterele parcurg rutele ı̂n ambele
sensuri.
Investiţia trebuie să ı̂ndeplinească următoarele condiţii:
1. Numărul de elicoptere cumpărate să fie minim.
2. Numărul de perechi de insule ı̂ntre care se poate realiza transportul, folosind unul sau mai
multe elicoptere să fie maxim.
3. Suma lungimii tuturor rutelor să fie minimă.
Cerinţe
Să se scrie un program care pentru n, k şi coordonatele vârfurilor insulelor cunoscute, deter-
mină:
1. numărul minim de elicoptere ce vor fi cumpărate de administraţie;
2. numărul perechilor neordonate de insule ı̂ntre care se poate realiza transportul prin eli-
coptere direct sau indirect;
3. suma distantelor parcurse de toate elicopterele cumpărate (distanţa parcursă de un elicopter
se consideră distanţa dintre insulele ı̂ntre care acesta asigură transportul).
Date de intrare
Fişierul de intrare elicoptere.in conţine pe prima linie o valoare v ce poate fi 1, 2, sau 3, ı̂n
funcţie de cerinţa ce va fi rezolvată, pe linia a doua numerele naturale n şi k separate printr-un
spaţiu, cu semnificaţia de mai sus, iar pe următoarele n linii se află câte şase numere naturale x1 ,
y1 , x2 , y2 , x3 şi y3 separate prin spaţiu reprezentând coordonatele celor trei vârfuri ale insulelor
ı̂n formatul (abscisă, ordonată).
Date de ieşire
Dacă valoarea lui v este 1 atunci fişierul de ieşire elicoptere.out va conţine pe prima linie
numai numărul minim de elicoptere, ce vor fi cumpărate de administraţie.
Dacă valoarea lui v este 2 atunci fişierul de ieşire elicoptere.out va conţine pe prima linie
numai numărul maxim de perechi de insule ı̂ntre care se poate realiza transportul prin elicoptere.
Dacă valoarea lui v este 3 atunci fişierul de ieşire elicoptere.out va conţine pe prima linie
suma minimă a lungimii rutelor parcurse de elicoptere.
164
CAPITOLUL 8. OJI 2016 8.1. ELICOPTERE 165
Exemple
elicoptere.in elicoptere.out Explicaţii
1 3 Datele corespund figurilor anterioare:
6 11 v = 1, deci se rezolvă NUMAI prima cerinţă.
100 20 100 30 105 30
20 20 30 30 20 30 Perechile de insule cu transport direct cu eli-
200 20 200 30 205 30 coptere: (1,4) (2,6), (6,5) şi obţinem astfel 3
100 40 100 50 105 40 elicoptere.
10 40 5 40 10 50
10 20 5 30 10 30
2 4 Datele corespund figurilor anterioare:
6 11 v = 2, deci se rezolvă NUMAI a doua cerinţă.
100 20 100 30 105 30
20 20 30 30 20 30 Perechile de insule cu transport direct cu eli-
200 20 200 30 205 30 coptere: (1,4) (2,6), (6,5) şi obţinem astfel 3
100 40 100 50 105 40 elicoptere.
10 40 5 40 10 50 Insula 3 rămâne izolată, astfel avem două grupuri
10 20 5 30 10 30 de insule. Primul grup conţine insulele 1 şi 4, iar al
doilea insulele 2, 5, 6. Din primul grup se numara
perechea (1,4), iar din al doilea grup se numara
perechile de insule (2,5), (2,6) si (5,6). In total
4 perechi de insule intre care se poate deplasa cu
elicopterul direct sau cu escala (indirect).
3 30 Datele corespund figurilor anterioare:
6 11 v = 3, deci se rezolvă NUMAI a treia cerinţă.
100 20 100 30 105 30 Perechile de insule cu transport direct cu elicoptere:
20 20 30 30 20 30 (1,4) (2,6), (6,5) şi obţinem astfel 3 elicoptere.
200 20 200 30 205 30 Insula 3 rămâne izolată, astfel avem două grupuri
100 40 100 50 105 40 de insule. Primul grup conţine insulele 1 şi 4, iar al
10 40 5 40 10 50 doilea insulele 2, 5, 6.
10 20 5 30 10 30 Elicopeterele asigură transportul direct ı̂ntre in-
sulele:
1 şi 4 cu distanţa verticală egală cu 10;
2 şi 6 cu distanţa orizontală egală cu 10;
5 şi 6 cu distanţa verticală egală cu 10;
ı̂n total liniile de transport au distanţa 30.
Mai ı̂ntâi trebuie să determinăm distanţa dintre orice două insule, adică distanţa dintre două
triunghiuri disjuncte folosind segmente orizontale sau verticale. Distanţa se va calcula folosind pe
rând câte un vârf al fiecărui triunghi şi orizontala prin el, respectiv verticala spre celălalt triunghi.
Folosind distanţele calculate se aleg cele care au valoarea mai mică sau egală cu k şi se con-
struieşte matricea costurilor pentru un graf neorientat care conţine ı̂n fiecare nod câte un triunghi.
Se determină componentele conexe ale grafului construit şi arborele parţial de cost minim pentru
fiecare dintre acestea.
n - numărul de componente conexe reprezintă valoarea ce va fi afişată pentru cerinta 1).
Suma costurilor APM-urilor este numărul de la cerinţa 3).
Pentru cerinţa 2) se calculează numărul de perechi de triunghiuri din fiecare componentă
2
conexă, adică N r Cnr , unde nr reprezintă numărul de triunghiuri din componenta conexă,
suma acestor combinări reprezintă numărul cerut (dacă nr 1, atunci N r 0).
ifstream fin("elicoptere.in");
ofstream fout("elicoptere.out");
tri T[1001];
int n,k,c[1001],v[1001],u,nrc,w;
double a[105][105];
int b[105][105];
long long perechi;
void cit()
{
int i;
fin>>w;
fin>>n>>k;
for(i=1;i<=n;i++)
fin>>T[i].A.x>>T[i].A.y>>T[i].B.x>>T[i].B.y>>T[i].C.x>>T[i].C.y;
fin.close();
}
if(P.y==Q.y)
return 1000000;
N.y=M.y;
CAPITOLUL 8. OJI 2016 8.1. ELICOPTERE 167
N.x=(-b*M.y-c)/a;
if((P.y<=M.y && M.y<=Q.y)||(Q.y<=M.y && M.y<=P.y))
return sqrt((M.x-N.x)*(M.x-N.x)+(M.y-N.y)*(M.y-N.y));
return 1000000;
}
if(P.x==Q.x)
return 1000000;
N.x=M.x;
N.y=(-a*M.x-c)/b;
if((P.x<=M.x && M.x<=Q.x)||(Q.x<=M.x && M.x<=P.x))
return sqrt((M.x-N.x)*(M.x-N.x)+(M.y-N.y)*(M.y-N.y));
return 1000000;
}
if(d<Min)
Min=d;
d=distLaturaVerticala(W.B,V.B,V.C);
if(d<Min)
Min=d;
d=distLaturaVerticala(W.C,V.A,V.B);
if(d<Min)
Min=d;
d=distLaturaVerticala(W.C,V.A,V.C);
if(d<Min)
Min=d;
d=distLaturaVerticala(W.C,V.B,V.C);
if(d<Min)
Min=d;
return Min;
}
void matriceCost(){
int i,j;
double x1,x2,x;
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)
{
x1=min(distTriOrizontala(T[i],T[j]),distTriVerticala(T[i],T[j]));
x2=min(distTriOrizontala(T[j],T[i]),distTriVerticala(T[j],T[i]));
x=min(x1,x2);
if(x<=k)
a[j][i]=a[i][j]=x;
else
a[j][i]=a[i][j]=1000000;
}
}
void ad(int k)
{
int i;
u++;c[u]=k;v[k]=1;
for(i=1;i<=n;i++)
if(v[i]==0 && a[k][i]<1000000 && a[k][i]!=0)
ad(i);
}
void componente()
{
int i,j;
for(i=1;i<=n;i++)
if(v[i]==0)
{
u=0;nrc++;
ad(i);
b[nrc][0]=0;
for(j=1;j<=u;j++)
{
b[nrc][0]++;
b[nrc][b[nrc][0]]=c[j];
}
perechi+=comb2(b[nrc][0]);
}
}
double apm(int q)
{
int s[105],i,j,k;
double ct=0,d[105],Min;
s[b[q][1]]=1;
for(i=2;i<=b[q][0];i++)
{
s[b[q][i]]=0;
d[b[q][i]]=a[b[q][i]][b[q][1]];
}
CAPITOLUL 8. OJI 2016 8.1. ELICOPTERE 169
for(k=1;k<b[q][0];k++)
{
Min=1000000;
for(i=1;i<=b[q][0];i++)
if(s[b[q][i]]==0 && Min>d[b[q][i]])
{
Min=d[b[q][i]];
j=i;
}
s[b[q][j]]=1;
ct+=Min;
for(i=1;i<=b[q][0];i++)
if(s[b[q][i]]==0 && d[b[q][i]]>a[b[q][i]][b[q][j]])
d[b[q][i]]=a[b[q][i]][b[q][j]];
}
return ct;
}
int main()
{
double s=0,t;
cit();
matriceCost();
componente();
int i,j;
/*
for(i=1;i<=nrc;i++){
for(j=1;j<=b[i][0];j++)
fout<<b[i][j]<<" ";
fout<<’\n’;
}
*/
for(i=1;i<=nrc;i++){
t=apm(i);
s+=t;
}
if(w==1)
fout<<n-nrc<<’\n’;
if(w==2)
fout<<perechi<<’\n’;
if(w==3)
fout<<fixed<<setprecision(10)<<s<<’\n’;
fout.close();
return 0;
}
ifstream f("elicoptere.in");
ofstream g("elicoptere.out");
pair<re,pair<int,int>> M[5000];
vector<int> V[101];
re K,L,dst(int,int,int,int,int,int);
int p,n,k,i,j,m,x[101][3],y[101][3],v[101],st[201],e,c,s;
int main()
{
f>>p>>n>>k;K=(re)k;
for(i=1;i<=n;i++)for(j=0;j<3;j++)f>>x[i][j]>>y[i][j];
for(i=1;i<n;i++)for(j=i+1;j<=n;j++)conect(i,j);
if(p<3)
{
for(i=1;i<=n;i++)
if(!v[i]){s=0;df(i);e+=s-1;if(s>1)c+=s*(s-1)/2;}
g<<(p==1?e:c);return 0;
}
sort(M+1,M+m+1);K=0.0;
for(i=1;i<=n;i++)v[i]=i;
for(auto it:M)
{
L=it.fi;i=it.se.fi;j=it.se.se;
for(;;){st[++k]=i;if(v[i]==i)break;i=v[i];}
for(;;){st[++k]=j;if(v[j]==j)break;j=v[j];}
if(i!=j)K+=L;for(;k;k--)v[st[k]]=i;
}
g<<fixed<<setprecision(10)<<K;
return 0;
}
ifstream f("elicoptere.in");
ofstream g("elicoptere.out");
int n,v,i,j,m,e,viz[102],L,R,q,Q[102],sol1,sol2,a[102][102],A[102],U,W;
tip k,d[102][102],x[602],cost[10010],u[10010],w[10010],sol3,
edge(int,int),dst1(tip*,tip*,tip*),dst2(tip*,tip*,tip*);
void solve12(),solve3();
int main()
{
f>>v>>n>>k;
for(i=1;i<=n;i++)
for(j=1;j<=6;j++)
f>>x[m++];
for(i=0;i<n;i++)
{
d[i][i]=oo;
CAPITOLUL 8. OJI 2016 8.1. ELICOPTERE 171
for(j=i+1;j<n;j++)
{
d[i][j]=min(edge(i,j),edge(j,i));
if(d[i][j]>k)d[i][j]=oo;
d[j][i]=d[i][j];
if(d[i][j]!=oo) { e++; cost[e]=d[i][j];u[e]=i;w[e]=j;}
}
}
solve12();
if(v<3)
{
v==1?g<<sol1:g<<sol2;
return 0;
}
for(i=1;i<e;i++)
{
q=i;
for(j=i+1;j<=e;j++)
if(cost[j]<cost[q])
q=j;
if(q>i){swap(cost[i],cost[q]);swap(u[i],u[q]);swap(w[i],w[q]);}
}
for(i=0;i<n;i++)
a[i][i]=1;
i=1;
while(sol1)
{
U=u[i];W=w[i];
if(!a[U][W])
{
sol3+=cost[i];sol1--;
}
for(j=0;j<n;j++)A[j]=a[U][j]||a[W][j];
for(j=0;j<n;j++)
if(A[j])
for(q=0;q<n;q++)
if(A[q])
a[j][q]=1;
i++;
}
g<<fixed<<setprecision(10)<<sol3;
return 0;
}
if(y1==y2)return min(fabs(x1-x0),fabs(x2-x0));
return abs((x0*y1+x1*y2+x2*y0-x0*y2-x2*y1-x1*y0)/(y2-y1));
}
void solve12()
{
for(i=0;i<n;i++)
if(!viz[i])
{
L=R=1;Q[L]=i;viz[i]=1;
while(L<=R)
{
j=Q[L++];
for(q=0;q<n;q++)
if(d[j][q]<oo&&!viz[q])
{
Q[++R]=q;viz[q]=1;
}
}
sol1+=R-1;
sol2+=R*(R-1)/2;
}
}
8.2 summax
Problema 2 - summax 100 de puncte
Avem o matrice triunghiulară cu n linii, cu elemente numere ı̂ntregi.
În această matrice putem construi un traseu după următoarea regulă:
- primul element al traseului este elementul a1,1
- dacă elementul ai,j aparţine traseului, atunci următorul element
al traseului poate fi doar ai1,j sau ai1,j 1 , pentru orice & j & i $ n.
Traseul se va codifica cu numerele de ordine ale coloanelor, par- Figura 8.1: summax
curgând liniile de la 1 la n. Valoarea traseului este egală cu suma
elementelor ce ı̂l formează.
Traseul evidenţiat ı̂n exemplul din dreapta are valoarea 5 4 6 5 4 24, şi se codifică cu
1, 2, 3, 3, 4.
Fie mulţimea tuturor traseelor de valoare maximă generate ı̂n ordine
lexicografică şi numerotate. Pentru exemplul alăturat avem şase trasee
de lungime maximă:
traseul 1. 1 1 1 1 2 (5+2+7+6+4=24)
traseul 2. 1 1 1 2 2 (5+2+7+6+4=24)
traseul 3. 1 2 2 2 2 (5+4+5+6+4=24)
traseul 4. 1 2 3 3 4 (5+4+6+5+4=24) Figura 8.2: summax
traseul 5. 1 2 3 4 4 (5+4+6+5+4=24)
traseul 6. 1 2 3 4 5 (5+4+6+5+4=24)
Cerinţe
Cunoscând dimensiunea şi elementele unei matrice triunghiulare, respectiv două numere nat-
urale st şi dr (st & dr), se cere să se determine:
1. Numărul total al traseelor de valoare maximă. În cazul ı̂n care această valoare depăşeşte
2000000000, se va tipări valoarea 2000000001;
2. Traseele cu numerele de ordine st, st 1, ..., dr.
Date de intrare
Fişierul summax.in conţine pe prima linie un număr natural v. Pentru toate testele de intrare,
numărul v poate avea doar valoarea 1 sau 2.
CAPITOLUL 8. OJI 2016 8.2. SUMMAX 173
A doua linie conţine trei numere naturale n, st şi dr, separate prin spaţiu. Următoarele n linii
conţin câte o linie a matricei triunghiulare astfel: linia i conţine i elemente, şi anume valorile ai,1
ai,2 ... ai,i pentru orice 1 & i & n.
Date de ieşire
Exemple
fişierul de v n st dr
intrare
0 1 20 4 10
1 1 900 50 100
2 1 1300 2000 3000
3 1 1700 20000 21000
4 2 20 6 9
5 2 30 20 000 000 20 001 000
6 2 60 40 030 000 40 031 000
7 2 100 139 876 543 139 876 999
8 2 500 137 987 000 137 988 000
9 2 700 123 456 789 123 457 777
10 72 900 100 000 000 100 001 000
CAPITOLUL 8. OJI 2016 8.2. SUMMAX 174
b. cele două jumătăţi de matrice se vor combina ı̂ntr-o singură matrice de dimensiuni n n 1
Memoria disponibilă de 16M o permite valoarea maximă a lui n 2000
int main()
{
ifstream fin("summax.in");
ofstream fout("summax.out");
fin>>p>>n>>st>>dr;
for (i=1;i<=n;++i)
for(j=1;j<=i;++j)
fin>>a[i][j];
for (j=1;j<=n;++j)
a[j-1][n]=1;
for (i=n-1;i>0;--i)
for (j=1;j<=i;++j)
if (a[i+1][j]>a[i+1][j+1])
{
a[i][j]=a[i][j]+a[i+1][j];
a[j-1][i]=a[j-1][i+1];
}
else
{
a[i][j]=a[i][j]+a[i+1][j+1];
a[j-1][i]=a[j][i+1];
if (a[i+1][j]==a[i+1][j+1] )
if (a[j-1][i]+a[j-1][i+1]>2000000000)
a[j-1][i]=2000000001;
else
a[j-1][i]+=a[j-1][i+1];
}
if (p==1)
fout<<a[0][1]<<"\n";
else
{
for (k=st;k<=dr;++k)
{
fout<<1;
dif=k;
col=1;
for (i=2;i<=n;++i)
if (a[col-1][i]>=dif and a[i][col]>=a[i][col+1])
{
fout<<" "<<col;
}
else
{
if (a[i][col]==a[i][col+1])
dif=dif-a[col-1][i];
col++;
fout<<" "<<col;
}
fout<<"\n";
}
}
return 0;
}
#include <fstream>
ifstream f("summax.in");
ofstream g("summax.out");
unsigned p,n,x,y,i,j,*a[2001],*b[2001],oo=2000000001,D,s[2001];
void afiseaza(unsigned,unsigned);
int main()
{
f>>p>>n>>x>>y;
for(i=0;i<=n;i++)
{
a[i]=new unsigned[i+3];
b[i]=new unsigned[i+3];
for(j=1;j<=i;j++)
f>>a[i][j];
}
for(j=1;j<=n;j++)
b[n][j]=a[n][j];
for(i=n-1;i>=1;i--)
for(j=1;j<=i;j++)
b[i][j]=a[i][j]+max(b[i+1][j],b[i+1][j+1]);
for(j=1;j<=n;j++)
a[n][j]=1;
for(i=n-1;i>=1;i--)
for(j=1;j<=i;j++)
{
if(b[i+1][j]>b[i+1][j+1])
a[i][j]=a[i+1][j];
else
if(b[i+1][j]<b[i+1][j+1])
a[i][j]=a[i+1][j+1];
else
a[i][j]=min(a[i+1][j]+a[i+1][j+1],oo);
}
if(p==1)
{
g<<a[1][1];
return 0;
}
afiseaza(1,1);
return 0;
}
s[I]=J;
if(I==n)
{
D++;
for(unsigned k=1;k<=n;k++)
g<<s[k]<<’ ’;
g<<’\n’;
return;
}
if(b[I+1][J]>=b[I+1][J+1])
{
afiseaza(I+1,J);
if(D==y)return;
CAPITOLUL 8. OJI 2016 8.2. SUMMAX 177
if(b[I+1][J+1]>=b[I+1][J])
afiseaza(I+1,J+1);
}
OJI 2015
9.1 2sah
Problema 1 - 2sah 100 de puncte
Se dă o tablă de şah cu n 1 linii (numerotate de sus ı̂n jos ı̂ncepând cu 1) şi 2n 1 coloane
(numerotate de la stânga la dreapta ı̂ncepând cu 1). Pe prima linie pătratul din mijloc conţine
1 gram de fân, iar celelalte pătrate de pe prima linie nu conţin nimic. Începând cu linia a doua
fiecare pătrat conţine o cantitate de fân obţinută prin adunarea cantităţilor de fân din cele 3
pătrate ale liniei anterioare cu care se ı̂nvecinează (pe verticală şi diagonală). De exemplu dacă
n 3 tabla are 4 linii, 7 coloane şi următoarea configuraţie.
Un cal pleacă de pe prima linie, de pe o coloana k & n, sare din orice poziţie i, j ı̂n poziţia
i 1, j 2 atât timp cât este posibil şi mănâncă tot fânul din pătratele prin care trece. De
exemplu, pentru n 3 şi k 2, pătratele prin care trece calul sunt marcate cu asterisc ( * )
Cerinţe
Date de intrare
Date de ieşire
178
CAPITOLUL 9. OJI 2015 9.1. 2SAH 179
Exemple
2sah.in 2sah.out Explicaţii
1 3 t=1, deci se rezolvă prima cerinţă.
32 Pe linia a doua există 3 pătrate care conţin fiecare câte un gram de
fân.(vezi desenul din enunţ)
2 2 t=2, deci se rezolvă doar a doua cerinţă.
32 Traseul calului este: (1,2) -¿ (2,4) -¿ (3,6) adică exact pătrăţelele
marcate cu asterisc ı̂n desenul din enunţ. Prima poziţie nu conţine
fân, iar celelalte două conţin câte un gram de fân. Deci calul mănâncă
2 grame de fân.
Pentru un calcul mai simplu vom considera j n 1 k obţinănd sirul Tj Sk şir care respectă
relaţia de recurenţă
Tj Tj 1 Tj 2 Tj 3
cu termenii iniţiali
T0 T1 1, T2 2
Acest şir mai este cunoscut şi sub denumirea de şir Tribonacci iar pentru calculul termenilor
săi se pot aplica tehnici similare calculului pentru termenii şirului Fibonacci (sau mai general
pentru oricare şiruri definite prin formule de recurenţă liniară).
Pentru punctaj parţial se poate folosi generarea termenilor folosind liniar formula de recurenţă.
CAPITOLUL 9. OJI 2015 9.1. 2SAH 180
Pentru punctaj maxim trebuie aplicată o metodă cu timp de execuţie logaritmic. Observăm
că din formula de recurenţă se poate deduce că (ı̂n termeni de operaţii cu matrice) avem:
0 1 0 Tj Tj 1
0 0 1
Tj 1
Tj 2
1 1 1 Tj 2 Tj 3
0 1 0
A
0 0 1
1 1 1
Avem
Tj T0
j
Tj 1
A
T1
Tj 2 T2
j
Iar matricea A se va calcula folosind ridicarea la putere ı̂n timp logaritmic.
ifstream f("2sah.in");
ofstream g("2sah.out");
int t,n,k,i,j,m,s,a[1005][2010];
int main()
{
f>>t>>n>>k;
a[1][n+1]=1;
if(t==1)
{
for(i=2;i<=k;i++)
for(j=1;j<=2*n+1;j++)
a[i][j]=(a[i-1][j-1]+a[i-1][j]+a[i-1][j+1])%MOD;
s=0;
for(j=1;j<=2*n+1;j++)
s=(s+a[k][j])%MOD;
g<<s;
return 0;
}
for(i=2;i<=n+1;i++)
for(j=1;j<=2*n+1;j++)
a[i][j]=(a[i-1][j-1]+a[i-1][j]+a[i-1][j+1])%MOD;
i=1;
j=k;
s=0;
m=2*n+1;
n++;
while(i<=n&&j<=m)
{
s=(s+a[i][j])%MOD;
i++;j+=2;
}
g<<s;
return 0;
}
CAPITOLUL 9. OJI 2015 9.1. 2SAH 181
ifstream f("2sah.in");
ofstream g("2sah.out");
int main()
{
f>>t>>n>>k;
if(t==1)
{
sol=1;
k--;
k%=(MOD-1); //utilizand teorema Fermat
for(;k;k--)
{
sol=(3*sol)%MOD;
}
g<<sol;
return 0;
}
k=n+2-k;
for(;k;k>>=1)
{
if(k&1)
{
for(i=0;i<3;i++)
for(j=0;j<3;j++)
for(m=0;m<3;m++)
A[i][j]+=S[i][m]*P[m][j];
for(i=0;i<3;i++)
for(j=0;j<3;j++)
{
S[i][j]=A[i][j]%MOD;
A[i][j]=0;
}
}
for(i=0;i<3;i++)
for(j=0;j<3;j++)
for(m=0;m<3;m++)
A[i][j]+=P[i][m]*P[m][j];
for(i=0;i<3;i++)
for(j=0;j<3;j++)
{
P[i][j]=A[i][j]%MOD;
A[i][j]=0;
}
g<<S[2][0]<<’\n’;
return 0;
}
ifstream f("2sah.in");
ofstream g("2sah.out");
CAPITOLUL 9. OJI 2015 9.1. 2SAH 182
int main()
{
f>>t>>n>>k;
if(t==1)
{
ans=1;
for(k--;k;k--)
ans=(ans*3)%MOD;
g<<ans;
return 0;
}
k=n+2-k;
if(k<3){g<<1;return 0;}
t1=t2=1;
for(i=3;i<=k;i++)
{
t3=(t0+t1+t2)%MOD;
t0=t1;t1=t2;t2=t3;
}
g<<t3;
return 0;
}
ifstream f("2sah.in");
ofstream g("2sah.out");
tip t,n,k,A[3][3],S[3][3],C[3][3];
void sol1(),sol2();
int main()
{
f>>t>>n>>k;
if(t==1)sol1();
else sol2();
return 0;
}
void sol1()
{
tip ans=1,p=3;
for(k--;k;k>>=1)
{
if(k%2)ans=(ans*p)%MOD;
p=(p*p)%MOD;
}
g<<ans;
}
void sol2()
{
int i,j,q;
k=n+2-k;
for(i=0;i<3;i++)
{
S[i][i]=1;
A[2][i]=1;
CAPITOLUL 9. OJI 2015 9.1. 2SAH 183
A[0][1]=A[1][2]=1;
for(;k;k>>=1)
{
if(k%2)
{
for(i=0;i<3;i++)
for(j=0;j<3;j++)
{
C[i][j]=0;
for(q=0;q<3;q++)
C[i][j]=(C[i][j]+S[i][q]*A[q][j])%MOD;
}
for(i=0;i<3;i++)
for(j=0;j<3;j++)
S[i][j]=C[i][j];
for(i=0;i<3;i++)
for(j=0;j<3;j++)
{
C[i][j]=0;
for(q=0;q<3;q++)
C[i][j]=(C[i][j]+A[i][q]*A[q][j])%MOD;
}
for(i=0;i<3;i++)
for(j=0;j<3;j++)
A[i][j]=C[i][j];
}
g<<S[1][2];
}
ifstream f("2sah.in");
ofstream g("2sah.out");
map<int,int> T;
set<int> S;
void solve(int);
long long t,n,k,sol,p;
int main()
{
f>>t>>n>>k;
if(t==1)
for(k--,sol=1,p=3;k;k/=2)
{
if(k&1)
sol=(sol*p)%MOD;
p=(p*p)%MOD;
}
else
{
T[0]=0;
T[1]=1;
T[2]=1;
T[3]=2;
for(int i=0;i<4;i++)
S.insert(i);
n=n+2-k;
CAPITOLUL 9. OJI 2015 9.2. DRAGONI 184
solve(n);
sol=T[n];
}
g<<sol<<’\n’;
return 0;
}
void solve(int M)
{
if(S.find(M)!= S.end()) return;
int m=M/2;
solve(m+1);
solve(m);
solve(m-1),
solve(m-2);
S.insert(M);
if(M%2)
T[M]=(1LL*T[m]*T[m-1]+1LL*T[m]*T[m-1]+
1LL*T[m]*T[m]+1LL*T[m+1]*T[m+1])%MOD;
else
T[M]=(1LL*T[m]*T[m+1]+1LL*T[m-1]*T[m]+
1LL*T[m-2]*T[m]+1LL*T[m-1]*T[m-1])%MOD;
}
9.2 Dragoni
Problema 2 - Dragoni 100 de puncte
Supăraţi că lansarea părţii a treia a filmului lor preferat s-a amânat până ı̂n iunie 2018, Henry
şi Hetty s-au gândit la propriul scenariu pentru finalul trilogiei:
Într-o lume ı̂n care vikingii pot zbura cu dragonii există N insule. Hiccup, şeful tribului de
vikingi aflat pe insula 1, ştie M rute directe de zbor bidirecţionale ı̂ntre insule. Pentru fiecare j
ı̂ntre 1 şi M , ruta j uneşte insulele Aj şi Bj şi are lungimea Dj .
Pe fiecare insulă i, (1 & i & n) există dragoni din specia i care pot zbura fără a se opri pentru
odihnă o distanţă maximă Dmaxi . Cu alte cuvinte, dragonii de pe insula i vor putea parcurge
orice rută j, (1 & j & m) pentru care Dj & Dmaxi , indiferent de ce alte drumuri au făcut anterior.
Hiccup doreşte să ajungă de pe insula 1 pe insula N pentru a-l salva pe Toothless, dragonul
lui. Pentru a ajunge acolo, el va lua iniţial un dragon din specia 1 (de pe insula 1). Apoi, dacă
la un moment dat Hiccup se află pe o insula i, (1 & i & n) având cu el un dragon din specia t, el
poate:
1. Să zboare de pe insula i pe o altă insulă x cu dragonul pe care ı̂l are, folosind o rută directă
j ı̂ntre insulele i si x, bineı̂nţeles doar dacă Dj & Dmaxt .
2. Să schimbe dragonul din specia t pe care ı̂l are cu un dragon din specia i aflat pe insula
respectivă.
Cerinţe
a. Să se determine distanţa maxima Dmaxi caracteristică unui dragon la care Hiccup poate
ajunge fără a schimba dragonul pe care l-a luat iniţial de pe insula 1.
b. Să se determine distanţa minimă pe care Hiccup trebuie să o parcurgă pentru a ajunge de
pe insula 1 pe insula N .
Date de intrare
Fişierul de intrare dragoni.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 se găsesc două
numere naturale N şi M reprezentând numărul de insule, respectiv numărul de rute directe ı̂ntre
insule. Pe a treia linie se găsesc N numere naturale, al i-lea dintre acestea reprezentând distanta
maximă Dmaxi pe care o poate zbura un dragon de pe insula i. Pe următoarele M linii sunt
CAPITOLUL 9. OJI 2015 9.2. DRAGONI 185
descrise cele M rute directe. Pe fiecare dintre aceste linii se găsesc câte trei numere naturale A,
B şi D cu semnificaţia că există rută bidirecţională de lungime D ı̂ntre insulele A şi B .
Date de ieşire
Exemple
void dijkstra()
{
for(int i=1; i<=n*n; ++i)
dist[i]=inf;
dist[1]=0;
h.push_back(make_pair(0, 1));
int nod;
long long cdist;
while(!h.empty())
{
nod=h[0].second;
if(nod==n*n)
break;
cdist=-h[0].first;
pop_heap(h.begin(), h.end());
h.pop_back();
if(f[nod])
continue;
f[nod]=1;
int fiu, dragon = (nod-1)/n+1, node = (nod-1)%n+1;
int cerinta1()
{
int best=dmax[1];
CAPITOLUL 9. OJI 2015 9.2. DRAGONI 188
return best;
}
int main()
{
freopen("dragoni.in", "r", stdin);
freopen("dragoni.out", "w", stdout);
scanf("%d", &cer);
scanf("%d%d", &n, &m);
for(int i=1; i<=n; ++i)
scanf("%d", &dmax[i]);
dijkstra();
if(cer==1)
printf("%d\n", cerinta1());
else
printf("%lld\n", dist[n*n]);
return 0;
}
ifstream f("dragoni.in");
ofstream g("dragoni.out");
int t,n,m,P[1005][1005],D[1005],Q1[1005],q1[1005],b,LH,T[1005][1005];
vector<int> V[1005];
vector<pair<int,int> > v[1005];
deque<int> Q;
void cerinta1(),cerinta2();
int main()
{
f>>t>>n>>m;
for(int i=1;i<=n;i++) f>>D[i];
if(t==1)
cerinta1();
else
cerinta2();
return 0;
}
void cerinta1()
CAPITOLUL 9. OJI 2015 9.2. DRAGONI 189
{
int x,y,d,sol;
for(;m;m--)
{
f>>x>>y>>d;
if(d<=D[1])
{
V[x].push_back(y);
V[y].push_back(x);
}
}
Q1[1]=1;
q1[1]=1;
t=b=1;
for(;b<=t;)
{
x=Q1[b++];
for(vector<int>::iterator it=V[x].begin();it!=V[x].end();it++)
if(!q1[ *it ])
{
Q1[++t]=*it;
q1[ *it ]=1;
}
}
sol=D[1];
for(int i=1;i<=n;i++)
if(q1[i])
sol=max(sol,D[i]);
g<<sol;
}
void cerinta2()
{
int x,y,d,i,j,k,oo=1000000000;
pair<int,int> Per;
for(int i=1;i<=m;i++)
{
f>>x>>y>>d;
v[x].push_back(make_pair(d,y));
v[y].push_back(make_pair(d,x));
}
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++) T[i][j]=oo;
T[i][i]=0;
Q.push_back(i);
q1[i]=1;
for(;Q.size();)
{
j=Q.front();
Q.pop_front();
q1[j]=0;
for(vector<pair<int,int> >::iterator it=v[j].begin();
it!=v[j].end();
it++)
if(D[i]>=it->first)
if(T[i][it->second]>T[i][j]+it->first)
{
T[i][it->second]=T[i][j]+it->first;
if(!q1[it->second])
{
q1[it->second]=1;
Q.push_back(it->second);
}
}
}
}
for(i=1;i<=n;i++) D[i]=oo;
D[1]=0;
Q.push_back(1);
q1[1]=1;
for(;Q.size();)
CAPITOLUL 9. OJI 2015 9.2. DRAGONI 190
{
j=Q.front();
Q.pop_front();
q1[j]=0;
for(k=1;k<=n;k++)
if(D[k]>D[j]+T[j][k])
{
D[k]=D[j]+T[j][k];
if(!q1[k])
{
q1[k]=1;
Q.push_back(k);
}
}
}
g<<D[n];
}
void dijkstra()
{
for(int i=1; i<=n*n; ++i)
dist[i]=inf;
dist[1]=0;
h.push_back(make_pair(0, 1));
int nod;
long long cdist;
while(!h.empty())
{
nod=h[0].second;
if(nod==n*n)
break;
cdist=-h[0].first;
pop_heap(h.begin(), h.end());
h.pop_back();
if(f[nod])
continue;
f[nod]=1;
int fiu, dragon = (nod-1)/n+1, node = (nod-1)%n+1;
h.push_back(make_pair(-dist[fiu], fiu));
push_heap(h.begin(), h.end());
}
int cerinta1()
{
int best=dmax[1];
return best;
}
int main()
{
freopen("dragoni.in", "r", stdin);
freopen("dragoni.out", "w", stdout);
scanf("%d", &cer);
scanf("%d%d", &n, &m);
for(int i=1; i<=n; ++i)
scanf("%d", &dmax[i]);
dijkstra();
if(cer==1)
printf("%d\n", cerinta1());
else
printf("%d\n", dist[n*n]);
return 0;
}
void buildGraph()
{
for(int i=1; i<=m; ++i)
for(int j=1; j<=n; ++j)
if(d[i]<=dmax[j])
{
addEdge(a[i], j, b[i], j, d[i]);
addEdge(b[i], j, a[i], j, d[i]);
}
void dijkstra()
{
for(int i=1; i<=n*n; ++i)
dist[i]=inf;
dist[1]=0;
h.push_back(make_pair(0, 1));
int nod;
long long cdist;
while(!h.empty())
{
nod=h[0].second;
if(nod==n*n)
break;
cdist=-h[0].first;
pop_heap(h.begin(), h.end());
h.pop_back();
if(f[nod])
continue;
f[nod]=1;
int fiu;
int cerinta1()
{
int best=dmax[1];
return best;
}
int main()
{
freopen("dragoni.in", "r", stdin);
freopen("dragoni.out", "w", stdout);
scanf("%d", &cer);
scanf("%d%d", &n, &m);
for(int i=1; i<=n; ++i)
scanf("%d", &dmax[i]);
for(int i=1; i<=m; ++i)
scanf("%d%d%d", &a[i], &b[i], &d[i]);
buildGraph();
dijkstra();
if(cer==1)
printf("%d\n", cerinta1());
else
printf("%d\n", dist[n*n]);
return 0;
}
ifstream f("dragoni.in");
ofstream g("dragoni.out");
deque<pair<int,int> > Q;
void cerinta1(),cerinta2();
int main()
{
f>>t>>n>>m;
for(i=1;i<=n;i++)
f>>D[i];
if(t==1)cerinta1();
else cerinta2();
return 0;
}
void cerinta1()
{
for(;m;m--)
{
f>>x>>y>>d;
if(d<=D[1])
{
V[x].push_back(y);
V[y].push_back(x);
}
}
CAPITOLUL 9. OJI 2015 9.2. DRAGONI 194
Q1[1]=1;q1[1]=1;t=b=1;
for(;b<=t;)
{
x=Q1[b++];
for(vector<int>::iterator it=V[x].begin();it!=V[x].end();it++)
if(!q1[ *it ])
{
Q1[++t]=*it;
q1[ *it ]=1;
}
}
sol=D[1];
for(i=1;i<=n;i++)
if(q1[i])
sol=max(sol,D[i]);
g<<sol;
}
void cerinta2()
{
//creez listele de vecini ca perechi (distanta,vecin)
for(i=1;i<=m;i++)
{
f>>x>>y>>d;
v[x].push_back(make_pair(d,y));
v[y].push_back(make_pair(d,x));
}
ifstream f("dragoni.in");
ofstream g("dragoni.out");
int t,n,m,P[1005][1005],D[1005],Q1[1005],q1[1005],b,LH,T[1005][1005];
vector<int> V[1005];
vector<pair<int,int> > v[1005];
pair<int,int> H[1000005];
void cerinta1(),cerinta2(),Hd(int),Hu(int);
int main()
{
f>>t>>n>>m;
for(int i=1;i<=n;i++) f>>D[i];
if(t==1)
cerinta1();
else
cerinta2();
return 0;
}
void cerinta1()
{
int x,y,d,sol;
for(;m;m--)
{
f>>x>>y>>d;
if(d<=D[1])
{
V[x].push_back(y);
V[y].push_back(x);
}
}
Q1[1]=1;
q1[1]=1;
t=b=1;
for(;b<=t;)
{
x=Q1[b++];
for(vector<int>::iterator it=V[x].begin();it!=V[x].end();it++)
if(!q1[ *it ])
{
Q1[++t]=*it;
q1[ *it ]=1;
CAPITOLUL 9. OJI 2015 9.2. DRAGONI 196
}
}
sol=D[1];
for(int i=1;i<=n;i++)
if(q1[i])
sol=max(sol,D[i]);
g<<sol;
}
void cerinta2()
{
int x,y,d,ins,dra,oo=2000000000;
pair<int,int> Per;
for(int i=1;i<=m;i++)
{
f>>x>>y>>d;
v[x].push_back(make_pair(d,y));
v[y].push_back(make_pair(d,x));
}
for(ins=1;ins<=n;ins++)
sort(v[ins].begin(),v[ins].end());
for(ins=1;ins<=n;ins++)
for(dra=1;dra<=n;dra++)
{
LH++;
H[LH].IN=ins;
H[LH].DR=dra;
P[ins][dra]=LH;
T[ins][dra]=oo;
}
T[1][1]=0;
for(;;)
{
Per=H[1];
if(Per.IN==n)
{
g<<T[Per.IN][Per.DR];
return;
}
P[Per.IN][Per.DR]=n+1;
H[1]=H[LH--];
P[H[1].IN][H[1].DR]=1;
Hd(1);
for(int i=0; i<v[Per.IN].size(); ++i)
{
pair<int, int> it = v[Per.IN][i];
if(D[Per.DR]<it.first) break;
ins=it.second;
dra=D[Per.DR]>D[ins] ? Per.DR : ins;
if(P[ins][dra]>LH||(T[ins][dra]<=T[Per.IN][Per.DR]+it.first))
continue;
T[ins][dra]=T[Per.IN][Per.DR]+it.first;
Hu(P[ins][dra]);
}
}
}
ifstream f("dragoni.in");
ofstream g("dragoni.out");
int t,n,m,P[1005][1005],D[1005],Q1[1005],q1[1005],b,LH,T[1005][1005];
vector<int> V[1005];
vector<pair<int,int> > v[1005];
deque<int> Q;
void cerinta1(),cerinta2();
int main()
{
f>>t>>n>>m;
for(int i=1;i<=n;i++)f>>D[i];
if(t==1)cerinta1();else cerinta2();
return 0;
}
void cerinta1()
{
int x,y,d,sol;
for(;m;m--)
{
f>>x>>y>>d;
if(d<=D[1])
{
V[x].push_back(y);
V[y].push_back(x);
}
}
Q1[1]=1;
q1[1]=1;
t=b=1;
for(;b<=t;)
{
x=Q1[b++];
for(vector<int>::iterator it=V[x].begin();it!=V[x].end();it++)
if(!q1[ *it ])
{
Q1[++t]=*it;
q1[ *it ]=1;
}
CAPITOLUL 9. OJI 2015 9.2. DRAGONI 198
sol=D[1];
for(int i=1;i<=n;i++)
if(q1[i])
sol=max(sol,D[i]);
g<<sol;
}
void cerinta2()
{
int x,y,d,i,j,k,oo=1000000000;
pair<int,int> Per;
for(int i=1;i<=m;i++)
{
f>>x>>y>>d;
v[x].push_back(make_pair(d,y));
v[y].push_back(make_pair(d,x));
}
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++) T[i][j]=oo;
T[i][i]=0;
Q.push_back(i);
q1[i]=1;
for(;Q.size();)
{
j=Q.front();
Q.pop_front();
q1[j]=0;
for(vector<pair<int,int> >::iterator it=v[j].begin();
it!=v[j].end();
it++)
if(D[i]>=it->first)
if(T[i][it->second]>T[i][j]+it->first)
{
T[i][it->second]=T[i][j]+it->first;
if(!q1[it->second])
{
q1[it->second]=1;
Q.push_back(it->second);
}
}
}
}
for(k=1;k<=n;k++)
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
if(i!=k&&i!=j&&k!=j&&T[i][j]>T[i][k]+T[k][j])
T[i][j]=T[i][k]+T[k][j];
g<<T[1][n];
}
OJI 2014
10.1 cartite
Problema 1 - cartite 100 de puncte
Cârtiţele sunt animale de dimensiuni mici care ı̂şi duc traiul pe suprafeţe de teren deschis, având
ca duşman principal vulpea. Lângă o pădure se află o zonă agricolă ı̂n formă dreptunghiulară,
ı̂mpărţită ı̂n pătrăţele de aceeaşi dimensiune. Zona agricolă este reprezentată printr-un tablou
bidimensional cu m linii şi n coloane, având liniile şi coloanele numerotate ı̂ncepând cu 1. În
această zonă agricolă trăieşte o cârtiţă şi k vulpi.
Pentru cârtiţă cunoaştem coordonatele ei (linia şi coloana) pe care se găseşte, la fel şi pentru
vulpi, care stau la pândă pentru a ataca cârtiţa ı̂n momentele ei de neatenţie.
Pe suprafaţa terenului cârtiţa se poate deplasa din pătrăţelul ı̂n care se află doar ı̂ntr-unul
dintre cele 4 pătrăţele vecine pe direcţiile nord, sud, est sau vest.
Vulpile pot ataca instantaneu pe o raza de acţiune de lungime 0, 1 sau 2 pe orizontală şi
verticală, inclusiv ı̂n poziţia unde se găsesc, după care tot instantaneu se ı̂ntorc ı̂n poziţiile iniţiale.
În figura de mai jos sunt desenate pătrăţele unde poate ataca o vulpe poziţionată ı̂n pătrăţelul cu
cifra reprezentând raza de acţiune.
Pentru a micşora riscul de deplasare ı̂n zona agricolă cârtiţa sapă ı̂n pământ un sistem de
g galerii, care leagă ı̂ntre ele pătrăţele din zona agricolă. Aceste galerii nu se intersectează sub
pământ, ci doar la suprafaţă, trecerea dintr-o galerie ı̂n alta, care se intersectează ı̂n acelaşi pătrăţel
făcându-se printr-un sistem ce nu ı̂i pune viaţa ı̂n pericol.
Galeriile sunt indicate prin coordonatele pătrăţelelor pe care le unesc. Acestea sunt săpate
astfel ı̂ncât, dacă pornim dintr-un capăt al unei galerii le putem parcurge pe toate. Nu există
două galerii care să pornească din acelaşi pătrăţel şi să ajungă tot ı̂n acelaşi pătrăţel (galeriile
sunt distincte).
Cârtiţa doreşte să se plimbe prin toate galeriile de sub teren trecând o singură dată prin fiecare,
dar pentru acest lucru trebuie să ajungă nevătămată mergând la suprafaţa terenului la un pătrăţel
de unde să intre ı̂n sistemul de galerii.
Cerinţe
Determinaţi:
a) cel mai apropiat pătrăţel de poziţia iniţială a cârtiţei prin care ea poate să intre ı̂n galerie
pentru a se plimba, precum şi lungimea traseului parcurs la suprafaţă asfel ı̂ncât fiecare pătrăţică
de pe traseu să nu fie atacată de nicio vulpe;
199
CAPITOLUL 10. OJI 2014 10.1. CARTITE 200
b) traseul de plimbare numai prin galerii, specificat prin coordonatele pătrăţelelor care consti-
tuie capetelor acestora.
Date de intrare
Date de ieşire
Dacă valoarea lui p este 1, se va afişa numai rezultatul de la punctul a) din cerinţă. În acest
caz, ı̂n fişierul de ieşire cartite.out se vor scrie trei numere naturale separate ı̂ntre ele prin câte un
spaţiu, reprezentând coordonatele pătrăţelului unde va intra cârtiţa ı̂n galerii şi lungimea traseului
parcurs la suprafaţă.
Dacă valoarea lui p este 2, se va afişa numai rezultatul de la punctul b) din cerinţă. În acest
caz, fişierul de ieşire cartite.out va conţine coordonatele pătrăţelelor traseului de plimbare prin
galerii (coordonatele câte unui pătrăţel pe câte o linie, ı̂ncepând cu prima linie a fişierului de ieşire).
Pătrăţelul de pornire trebuie să fie acelaşi cu cel ı̂n care ajunge cârtiţa la sfârşitul plimbării şi nu
este obligatoriu să fie acelaşi cu cel ı̂n care ea intră ı̂n galerii de la suprafaţa terenului.
Exemple
2 1 1 p=2
6 4 3 2 Traseul de plimbare prin galerii este următorul:
6 3 4 2 (1,1) (3,2) (4,2) (1,3) (1,4) (4,2)(3,3)(1,1), cu
3 1 3 observaţia că se putea alege şi alt pătrăţel de
5 10 1 4 plecare.
3 41 4 2
4 30 3 3 Atenţie! Pentru acest test se va afişa doar
7 1 1 rezultatul la cerinţa b).
1 1 3 2
1 3 1 4 Se observă că ı̂n acest caz trebuie să se citească
1 1 3 3 toate datele din fişier.
1 4 4 2
4 2 3 3
4 2 1 3
4 2 3 2
Cerinţa a)
Folosind poziţiile vulpilor şi razele lor de acţiune se construieste un tablou cu elemente 0 şi 1,
0 - poziţie accesibilă, 1 - poziţie inaccesibilă cârtiţei. Pentru acest tablou se determină distanţa
minimă de la o poziţie dată (cea a cârtiţei) la o mulţime de poziţii mergând ı̂n cele 4 directii
(Nord, Sud, Est, Vest) cu o poziţie la fiecare pas (algoritmul lui Lee). Dimensiunile reduse ale
datelor de intrare şi timpul maxim de executie/test nu conduc la probleme de memorie s-au timp
de execuţie.
Cerinta b)
În rezolvarea acestei cerinţe este necesar să se construiască un graf neorientat ce are drept
noduri, capetele galeriilor, iar drept muchii galeriile. Pentru acest lucru va trebui să se determine
pătratelele distincte ce sunt capete de galerie şi să li se asocieze noduri ı̂n graf. Apoi, problema
se reduce la determinarea unui ciclu eulerian ı̂ntr-un graf neorientat. Graful are un număr mic de
noduri (maxim 200), datorită numărului mic de galerii (cel mult 100) şi algoritmul din teorema
lui Euler (pentru ciclu eulerian) este suficient pentru a obţine punctajul maxim.
#define N 1<<16
#define I(x,y) (x<<8)|y
#define Afis(x) printf("%d %d\n",x>>8,x&255)
int n,m,i,j,X,Y,vulpe=-1,capat=-2,baza,top,vulpi[N],capete[N],Lee[N],Q[N],
k,p,e,D[5]={-256,256,-1,1,0},*d,x[1001],y[1001],used[1001],cx,cy,lg,caz;
vector<int> v[N];
void fill(int,int),DF(int);
int main()
{
CAPITOLUL 10. OJI 2014 10.1. CARTITE 202
freopen("cartite.in","r",stdin);
freopen("cartite.out","w",stdout);
scanf("%d",&caz);
scanf("%d%d",&n,&m);
m++;
n++;
scanf("%d%d",&i,&j);
X=I(i,j);
for(j=0;j<=m;j++) vulpi[I(0,j)]=vulpi[I(n,j)]=1;
for(i=0;i<=n;i++) vulpi[I(i,0)]=vulpi[I(i,m)]=1;
scanf("%d",&k);
for(;k;k--)
{
scanf("%d%d%d",&i,&j,&p);
fill(I(i,j),p);
}
scanf("%d",&e);
for(k=1;k<=e;k++)
{
scanf("%d%d",&i,&j);
x[k]=I(i,j);
v[x[k]].push_back(k);
scanf("%d%d",&i,&j);
y[k]=I(i,j);
v[y[k]].push_back(k);
capete[x[k]]=capete[y[k]]=1;
}
Q[1]=X;
top=1;
Lee[X]=1;
for(baza=1;baza<=top;baza++)
{
i=Q[baza];
for(d=D;*d;d++)
{
if(vulpi[i+*d]) continue;
if(Lee[i+*d]) continue;
Lee[i+*d]=Lee[i]+1;
Q[++top]=i+*d;
if(capete[i+*d])break;
}
if(capete[i+*d])
{
cx=(i+*d)>>8;
cy=(i+*d)&255;
lg=Lee[i+*d]-1;
break;
}
}
if(caz==1)
{
printf("%d %d %d\n",cx,cy,lg);
return 0;
}
for(i=1;i<=e;i++)
{
if(!vulpi[x[i]]){DF(x[i]); break;}
if(!vulpi[y[i]]){DF(x[i]); break;}
}
return 0;
}
DF(x[edge]+y[edge]-nod);
}
Afis(nod);
}
vulpi[x]=1;
#define N 1<<16
#define I(x,y) (x<<8)|y
#define Afis(x) printf("%d %d\n",x>>8,x&255)
int n,m,i,j,X,Y,vulpe=-1,capat=-2,baza,top,
vulpi[N],capete[N],Lee[N],Q[N],k,p,e,
D[5]={-256,256,-1,1,0},*d,x[1001],y[1001],used[1001],cx,cy,lg,caz;
vector<int> v[N];
void fill(int,int),DF(int);
int main()
{
freopen("cartite.in","r",stdin);
freopen("cartite.out","w",stdout);
scanf("%d",&caz);
scanf("%d%d",&n,&m);
m++;
n++;
scanf("%d%d",&i,&j);
X=I(i,j);
for(j=0;j<=m;j++)
vulpi[I(0,j)]=vulpi[I(n,j)]=1;
for(i=0;i<=n;i++)
vulpi[I(i,0)]=vulpi[I(i,m)]=1;
scanf("%d",&k);
for(;k;k--)
{
scanf("%d%d%d",&i,&j,&p);
fill(I(i,j),p);
}
scanf("%d",&e);
for(k=1;k<=e;k++)
{
scanf("%d%d",&i,&j);
x[k]=I(i,j);
v[x[k]].push_back(k);
scanf("%d%d",&i,&j);
y[k]=I(i,j);
v[y[k]].push_back(k);
capete[x[k]]=capete[y[k]]=1;
}
Q[1]=X;
top=1;
Lee[X]=1;
CAPITOLUL 10. OJI 2014 10.1. CARTITE 204
for(baza=1;baza<=top;baza++)
{
i=Q[baza];
for(d=D;*d;d++)
{
if(vulpi[i+*d]) continue;
if(Lee[i+*d]) continue;
Lee[i+*d]=Lee[i]+1;
Q[++top]=i+*d;
if(capete[i+*d]) break;
}
if(capete[i+*d])
{
cx=(i+*d)>>8;
cy=(i+*d)&255;
lg=Lee[i+*d]-1;
break;
}
}
if(caz==1)
{
printf("%d %d %d\n",cx,cy,lg);
return 0;
}
for(i=1;i<=e;i++)
{
if(!vulpi[x[i]]){DF(x[i]);break;}
if(!vulpi[y[i]]){DF(x[i]);break;}
}
return 0;
}
#define N 1<<16
#define I(x,y) (x<<8)|y
#define Afis(x) printf("%d %d\n",x>>8,x&255)
int n,m,i,j,X,Y,vulpe=-1,capat=-2,baza,top,
vulpi[N],capete[N],Lee[N],Q[N],k,p,e,
CAPITOLUL 10. OJI 2014 10.1. CARTITE 205
D[5]={-256,256,-1,1,0},*d,x[1001],y[1001],used[1001],cx,cy,lg,caz;
vector<int> v[N];
void fill(int,int),DF(int);
int main()
{
freopen("cartite.in","r",stdin);
freopen("cartite.out","w",stdout);
scanf("%d",&caz);
scanf("%d%d",&n,&m);
m++;
n++;
scanf("%d%d",&i,&j);
X=I(i,j);
for(j=0;j<=m;j++)
vulpi[I(0,j)]=vulpi[I(n,j)]=1;
for(i=0;i<=n;i++)
vulpi[I(i,0)]=vulpi[I(i,m)]=1;
scanf("%d",&k);
for(;k;k--)
{
scanf("%d%d%d",&i,&j,&p);
fill(I(i,j),p);
}
scanf("%d",&e);
for(k=1;k<=e;k++)
{
scanf("%d%d",&i,&j);
x[k]=I(i,j);
v[x[k]].push_back(k);
scanf("%d%d",&i,&j);
y[k]=I(i,j);
v[y[k]].push_back(k);
capete[x[k]]=capete[y[k]]=1;
}
Q[1]=X;
top=1;
Lee[X]=1;
for(baza=1;baza<=top;baza++)
{
i=Q[baza];
for(d=D;*d;d++)
{
if(vulpi[i+*d]) continue;
if(Lee[i+*d]) continue;
Lee[i+*d]=Lee[i]+1;
Q[++top]=i+*d;
if(capete[i+*d]) break;
}
if(capete[i+*d])
{
cx=(i+*d)>>8;
cy=(i+*d)&255;
lg=Lee[i+*d]-1;
break;
}
}
if(caz==1)
{
printf("%d %d %d\n",cx,cy,lg);
return 0;
}
for(i=1;i<=e;i++)
{
if(!vulpi[x[i]]){DF(x[i]);break;}
CAPITOLUL 10. OJI 2014 10.2. FRACTII2 206
if(!vulpi[y[i]]){DF(x[i]);break;}
}
return 0;
}
10.2 fractii2
Problema 2 - fractii2 100 de puncte
Numărul 1 poate fi scris ı̂n diverse moduri ca sumă de fracţii cu numărătorul 1 şi numitorul o
putere a lui 2. De exemplu:
1 1 1 1 1 1 1 1 1 1
1
2 2 2 4 8 8 8 4 2 8
Două scrieri nu sunt considerate distincte dacă folosesc aceleaşi fracţii scrise ı̂n altă ordine. În
exemplul de mai sus ultimele două scrieri nu sunt distincte.
Cerinţe
Date de intrare
Fişierul de intrare fractii2.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 se găseşte un singur număr N natural - reprezentând numărul de fracţii.
Date de ieşire
Dacă valoarea lui p este 1, se va rezolva numai punctul a) din cerinţă. În acest caz, ı̂n fişierul
de ieşire fractii2.out se vor scrie, pe o singură linie, N numere naturale separate prin câte un
CAPITOLUL 10. OJI 2014 10.2. FRACTII2 207
spaţiu reprezentând cei N exponenţi ai lui 2 din scrierea solicitată ı̂n prima cerinţă. Astfel, dacă
numerele afişate sunt m1 , m2 , ..., mN atunci există scrierea
1 1 1
1 ... .
2m1 2m2 2mN
Dacă valoarea lui p este 2, se va rezolva numai punctul b) din cerinţă. În acest caz, ı̂n fişierul de
ieşire fractii2.out se va scrie un număr natural reprezentând răspunsul la a doua cerinţă, adică
numărul de scrieri distincte a numărului 1 ca sumă de N fracţii cu numărătorul 1 şi numitorul o
putere a lui 2 (modulo 100003).
Exemple
Deci se poate spune că descompunerea va fi obţinută alegând o altă descompunere ı̂n care
fracţia de numitor maxim este 2k11 de cel puţin p ori prin ı̂nlocuirea a p astfel de fracţii cu 2p
fracţii 21k şi se observă că descompunerea astfel obţinută conţine cu exact p fracţii mai mult.
Observăm că astfel avem o metodă standard prin care se poate forma scrierea dorită.
Iniţial plecăm de la scrierea unică 1 12 12 221 apoi pentru fiecare i, 1 & i $ k păstrăm exact
ai fracţii 21i iar pe fiecare dintre cele rămase le transformăm ı̂n câte două fracţii 2i11 .
Numărarea descompunerilor se poate face prin metoda programării dinamice plecând de la
observaţia de mai sus.
Notăm Dmi numărul de descompuneri formate din m fracţii dintre care 2i au numitor
maxim.
Pentru oricare astfel de descompunere putem alege acum orice j, 1 & j & 2i şi scriem j dintre
fracţiile de numitor maxim ca sumă de două fracţii cu numitorul dublat. Se va obţine o nouă
descompunere formată din m j fracţii dintre care 2j au numitor maxim.
Aplicăm dinamică ı̂nainte adunând Dmi la Dm j j pentru orice j cu 1 & j & 2i dacă
mj $N
Soluţia finală se va obţine prin adunarea valorii Dmi de fiecare dată când obţinem m j
N.
int n,i,j,m,cod,left,right,from,sol[2010][1010];
int main()
{
freopen("fractii2.in","r",stdin);
freopen("fractii2.out","w",stdout);
scanf("%d%d",&cod,&n);
if(cod==1)
{
for(i=1;i<n;i++)printf("%d ",i);
printf("%d\n",n-1);
return 0;
}
sol[2][1]=1;
for(i=3;i<=n;i++)
{
m=i/2;
for(j=1;j<=m;j++)
{
from=i-j;
left=(j+1)/2-1;
right=(i-j)/2;
sol[i][j]=sol[i][j-1]+sol[i-j][right]-sol[i-j][left];
if(sol[i][j]<0)sol[i][j]+=MOD;
if(sol[i][j]>=MOD)sol[i][j]-=MOD;
}
//printf("Total %d = %d\n",i,sol[i][m]);
}
printf("%d",sol[n][n/2]);
return 0;
}
#include <cstdio>
#include <vector>
#include <utility>
int n,cod,Sol,Contor[10000];
void back(int,int);
int main()
{
freopen("fractii2.in","r",stdin);
freopen("fractii2.out","w",stdout);
scanf("%d%d",&cod,&n);
if(cod==1)
{
printf("%d ",n-1);
for(int i=n-1;i;i--)
printf("%d ",i);
return 0;
}
Contor[1]=2;
back(2,1);
printf("%d",Sol);
return 0;
}
int Next_Exponent=Last_Exponent+1;
int Steps=Contor[Last_Exponent];
for(int i=1;i<=Steps;i++)
{
Contor[Last_Exponent]=Steps-i;
Contor[Next_Exponent]=2*i;
back(Fraction_Number+i,Next_Exponent);
}
}
int n,i,j,k,m,cod,U[N],D[N][N];
int main()
{
freopen("fractii2.in","r",stdin);
freopen("fractii2.out","w",stdout);
scanf("%d%d",&cod,&n);
if(cod==1)
{
CAPITOLUL 10. OJI 2014 10.2. FRACTII2 210
for(i=1;i<n;i++)printf("%d ",i);printf("%d\n",n-1);
return 0;
}
n-=2;
U[n+1]=2;
for(i=n+1;i>=2;i--)
for(m=U[i],j=1;j<=m;j++)
U[i-j]=max(U[i-j],min(2*j,i-j));
D[0][0]=D[1][1]=1;
for(i=2;i<=n;i++)
{
m=U[i];
for(j=1;j<=m;j++)
{
k=min(2*j,i-j);
D[i][j]=D[i][j-1]+D[i-j][k]>=MOD ?
D[i][j-1]+D[i-j][k]-MOD :
D[i][j-1]+D[i-j][k];
}
}
printf("%d",D[n][2]);
return 0;
}
OJI 2013
11.1 biperm
Problema 1 - biperm 100 de puncte
Pentru un număr natural nenul n, să considerăm toate numerele naturale nenule mai mici sau
egale cu n, luând fiecare număr de câte două ori: 1, 1, 2, 2, 3, 3, ... , n, n. Aceste numere le
amestecăm aleator, şi le aranjăm pe două linii a câte n elemente. Structura astfel obţinută o vom
numi o bipermutare. În figurile 1, 2 şi 3 avem câte un exemplu de bipermutare pentru n 5.
O bipermutare este perfectă, dacă ambele linii ale structurii reprezintă câte o permutare (vezi
figurile 2 şi 3).
Prin mutare pe poziţia p, ı̂nţelegem interschimbarea elementelor de pe aceeaşi coloană p. În
exemplele de mai jos, bipermutarea perfectă din figura 2 s-a obţinut din bipermutarea din figura
1, aplicând o mutare pe poziţa 2. Bipermutarea perfectă din figura 3 s-a obţinut din bipermutarea
din figura 1, aplicând mutări pe poziţiile 1, 2, 4 şi 5.
Cerinţe
Date de intrare
Fişierul de intrare biperm.in conţine pe prima linie valoarea lui n. Următoarele două linii
conţin, fiecare, câte n elemente separate prin câte un spaţiu, formând o bipermutare.
Date de ieşire
a 2 $ n & 10000;
a calculul corect al numărului bipermutărilor perfecte distincte valorează 30% din punctaj;
a calculul corect al numărului minim de mutări valorează 10% din punctaj;
211
CAPITOLUL 11. OJI 2013 11.1. BIPERM 212
a tipărirea unei bipermutări perfecte valorează 60% din punctaj. Pot exista mai multe soluţii,
se va admite orice soluţie corectă;
a se garantează că numărul bipermutărilor perfecte distincte nu depăşeşte 2 000 000 000 pentru
niciun test.
a acordarea punctajului la un răspuns corect este condiţionată de existenţa răspunsurilor
anterioare, indiferent de corectitudinea lor;
a pentru 40% din teste n & 20
a pentru 40% din teste 20 $ n & 400
a pentru 20% din teste 400 $ n & 10000
Exemple
biperm.in biperm.out Explicaţii
5 41 Sunt 4 permutări perfecte. Numărul minim de mutări este 1 şi
15534 12534 există două soluţii cu număr minim de mutări:
32241 35241 12534 15234
3 5 2 4 1 şi 3 2 5 4 1
Celelalte două soluţii, ce nu se obţin din număr minim de mutări
sunt:
32541 35241
1 5 2 3 4 şi 1 2 5 3 4
Pentru a treia cerinţă oricare dintre cele 4 soluţii este acceptată.
int viz[2][10003],a[2][10003],b[2][10003];
int main()
{
int n,i,j,p,nrmin,invers,l,poz,ant,aux,urmviz;
CAPITOLUL 11. OJI 2013 11.1. BIPERM 213
ifstream fin("biperm.in");
ofstream fout("biperm.out");
fin>>n;
for (i=0;i<2;++i) // matricea viz[x][i] are semnificatia:
for (j=1;j<=n;++j)
{
fin>>a[i][j]; // numarul i apare in bipermutare pe pozitiile
if (viz[0][a[i][j]]==0) // viz[0][i] si viz[1][i]
viz[0][a[i][j]]=j;
else
viz[1][a[i][j]]=j;
}
for (i=1;i<=n;++i)
if (b[0][i]==0)
{
l=1; invers=0;poz=i; // reconstruim ciclurile: l lungimea ciclului
// invers= cate arce sunt in ciclu cu sensul invers
b[0][poz]=a[0][poz]; // poz = pozitia curenta in ciclu
b[1][poz]=a[1][poz]; // ant = pozitita anterioara in ciclu
ant=poz; // in b se construieste permutarea dubla ceruta
urmviz=b[1][poz]; // urmatorul nod spre vizitare in lant
if (viz[0][urmviz]==poz)
poz=viz[1][urmviz];
else
poz=viz[0][urmviz]; // lant continua este muchia a[0][poz] si
// a[1][poz]
while (!b[0][poz])
{
if (a[0][poz]==b[1][ant])
{
b[0][poz]=a[0][poz];
b[1][poz]=a[1][poz];
l++;
ant=poz;
urmviz=b[1][poz];
if (viz[0][urmviz]==poz)
poz=viz[1][urmviz];
else
poz=viz[0][urmviz];
}
else
{
b[0][poz]=a[1][poz];
b[1][poz]=a[0][poz];
l++;invers++; // am gasit un nou arc invers
ant=poz; //
urmviz=b[1][poz];
if (viz[0][urmviz]==poz)
poz=viz[1][urmviz];
else
poz=viz[0][urmviz];
}
}
if (l>1)
p*=2;
if (invers>l-invers)
nrmin+=l-invers;
else
nrmin+=invers;
}
fout<<p<<" "<<nrmin<<"\n";
for(i=0;i<2;++i)
{
for(j=1;j<n;++j)
fout<<b[i][j]<<" ";
fout<<b[i][n]<<"\n";
}
CAPITOLUL 11. OJI 2013 11.1. BIPERM 214
fin.close();
fout.close();
return 0;
}
int a[2][60],b[2][60];
for (j=1;j<=n;++j)
if(perm[a[0][j]]==0)
perm[a[0][j]]=1;
else
return 0;
for (j=1;j<=n;++j)
perm[j]=0;
for (j=1;j<=n;++j)
if(perm[a[1][j]]==0)
perm[a[1][j]]=1;
else
return 0;
return 1;
}
int main()
{
int n,i,j,k,sup,nr,p,aux, nrsol=0,min,mutari,egal;
if (n<30)
{
for (i=0;i<2; ++i)
for (j=1;j<=n;++j)
fin>>a[i][j];
sup=1<<n;
min=sup;
for (i=0;i<sup;++i)
{
mutari=0;
nr=i;
k=1;
egal=0;
while(nr>0)
{ if (a[0][k]==a[1][k] && nr%2)
egal=1;
if (nr%2)
{
aux=a[0][k];
a[0][k]=a[1][k];
a[1][k]=aux;
++mutari;
}
k++;
nr/=2;
}
{
++nrsol;
if (mutari<min)
{
min=mutari;
for (p=0;p<2; ++p)
for (j=1;j<=n;++j)
b[p][j]=a[p][j];
}
}
nr=i;
k=1;
while(nr>0)
{
if (nr%2)
{
aux=a[0][k];
a[0][k]=a[1][k];
a[1][k]=aux;
}
k++;
nr/=2;
}
}
}
fout<<nrsol<<" "<<min<<"\n";
for (i=0;i<2;++i)
{ for (j=1;j<n;++j)
fout<<b[i][j]<<" ";
fout<<b[i][n]<<"\n";
}
fin.close();
fout.close();
return 0;
}
int a[2][60],b[2][60];
for (j=1;j<=n;++j)
if(perm[a[0][j]]==0)
perm[a[0][j]]=1;
else
return 0;
for (j=1;j<=n;++j)
perm[j]=0;
for (j=1;j<=n;++j)
if(perm[a[1][j]]==0)
perm[a[1][j]]=1;
else
return 0;
return 1;
}
int main()
{
int n,i,j,k,sup,nr,p,aux, nrsol=0,min,mutari,egal;
CAPITOLUL 11. OJI 2013 11.1. BIPERM 216
if (n<30)
{
for (i=0;i<2; ++i)
for (j=1;j<=n;++j)
fin>>a[i][j];
sup=1<<n;
min=sup;
for (i=0;i<sup;++i)
{
mutari=0;
nr=i;
k=1;
egal=0;
while(nr>0)
{ if (a[0][k]==a[1][k] && nr%2)
egal=1;
if (nr%2)
{
aux=a[0][k];
a[0][k]=a[1][k];
a[1][k]=aux;
++mutari;
}
k++;
nr/=2;
}
nr=i;
k=1;
while(nr>0)
{
if (nr%2)
{
aux=a[0][k];
a[0][k]=a[1][k];
a[1][k]=aux;
}
k++;
nr/=2;
}
}
}
fout<<nrsol<<" "<<min<<"\n";
for (i=0;i<2;++i)
{ for (j=1;j<n;++j)
fout<<b[i][j]<<" ";
fout<<b[i][n]<<"\n";
}
fin.close();
fout.close();
return 0;
}
11.2 subsecvente
Problema 2 - subsecvente 100 de puncte
Fie n un număr natural şi M rS1 , S2 , ..., Sn x o mulţime de şiruri de caractere nevide. Fie
¬ ¬¬ ¬
Sk un şir de caractere din M . Atunci, orice caracter al lui Sk aparţine mulţimii r a , b x. Notăm
prin ¶Sk ¶ numărul caracterelor şirului Sk sau, echivalent, lungimea sa. O subsecvenţă Sk i j
a lui Sk este formată din caracterele situate pe poziţiile consecutive i, i 1, .., j. Astfel, dacă
¬ ¬ ¬ ¬ ¬ ¬
Sk abbbaababa , atunci Sk 3 6 bbaa sau subsecvenţa evidenţiată: abbbaababa .
Cerinţe
Fiind dată o mulţime M , se cere să se determine lungimea maximă a unei subsecvenţe care se
găseşte ı̂n toate şirurile din M .
Date de intrare
Date de ieşire
Pe prima linie a fişierului de ieşire subsecvente.out se va scrie un singur număr natural egal
cu lungimea subsecvenţei găsite.
a 1$n$5
a Dacă ¶S ¶ ¶S1 ¶ ¶S2 ¶ ... ¶Sn ¶, atunci ¶S ¶ $ 50001
a Se garantează că va exista ı̂ntotdeauna soluţie
a Se garantează că rezultatul nu va depăşi 60
a Pentru 30% din teste: ¶S ¶ $ 101
a Pentru 55% din teste: ¶S ¶ $ 3501
a Pentru 80% din teste: ¶S ¶ $ 10001
Exemple
subsecvente.insubsecvente.out Explicaţii
4 5 Lungimea unei subsecvenţe comune de lungime maximă este 5.
abbabaaaaabb
aaaababab În exemplu subsecvenţa comună de lungime 5 este aaaab:
bbbbaaaab abbabaaaaabb, aaaababab, bbbbaaaab, aaaaaaabaaab.
aaaaaaabaaab
subsecvenţe pot fi reprezentate ca numere ı̂ntregi pe cel mult 64 de biţi. Astfel, putem considera
toate lungimile unei subsecvenţe, ı̂n complexitate O L, considera apoi toate subsecvenţele de
¬
această lungime din cel mai scurt şir, ı̂n complexitate O ¶S ¶, şi căuta ı̂n restul şirurilor această
subsecvenţă, ı̂n complexitate O ¶S ¶ L. O subsecvenţă, reprezentată de un număr ı̂ntreg, se
poate căuta ı̂ntr-un şir considerând toate subsecvenţele acestuia consecutiv, deplasând biţii săi
la stânga şi ı̂nlăturându-i pe cei mai semnificativi, operaţii ce se pot efectua ı̂n O 1. Astfel,
¬
complexitatea totală este O L ¶S ¶ ¶S ¶, care ı̂n cazul devarobabil, menţionat anterior, devine
2
O L ¶S ¶ . În practică, căutarea curentă se comportă puţin mai slab decât căutarea anterioară
datorită operaţiilor pe numere de 64 biţi.
Soluţie 40p
O altă restricţie a problemei ce ne poate ajuta este cardinalul mulţimii M ce poate fi maxim
4. În cazul ı̂n care ¶M ¶ 2, problema se reduce la a determina cea mai lungă subsecvenţă comună
2
a două şiruri. O soluţie a acestei probleme se poate obţine ı̂n complexitate O ¶S ¶ cu metoda
programării dinamice. Recurenţa poate fi următoarea:
d[i][j] = {
d[i - 1][j - 1] + 1, dac\u a S1[i] = S2[j]
0, altfel
}
Soluţia este max dij , iterând după toţi indicii i, j). Analog, se mai poate adăuga o
¶M ¶
dimensiune dacă avem trei şiruri. În cazul general, complexitatea este O ¶S ¶ . Implementarea
recurenţei cu număr variabil de şiruri vă va aduce acest punctaj.
Soluţie 55p
¬
Observăm că dacă o subsecvenţă S se găseşte ı̂n toate şirurile, atunci orice subsecvenţă a sa, se
¬
găseşte de asemenea ı̂n toate şirurile. În particular, orice prefix al lui S se găseşte ı̂n toate şirurile.
Se poate astfel căuta binar lungimea unei subsecvenţe. Iar dacă extindem prima soluţie ı̂nlocuind
2
căutarea liniară, obţinem o soluţie cu complexitatea finală ı̂n cazul defavorabil: O log L ¶S ¶ .
Soluţie 55p, Adrian Panaete
Putem folosi o metodă constructivă ı̂ncepând cu subsecvenţa vidă corespunzătoare lungimii 0
şi extinzând orice subsecvenţă comună ı̂n cele două moduri posibile (adăugând la final caracterul
¬ ¬ ¬ ¬
a sau caracterul b ). Dacă prin extindere se obţine subsecvenţă comună, atunci se realizează o
ı̂mbunătăţire a soluţiei curente şi se păstrează subsecvenţa extinsă. Algoritmul se ı̂ncheie dacă
pentru subsecvenţele de o anumită lungime L nu se mai poate aplica nicio extindere şi L va fi exact
soluţia problemei. Deoarece la fiecare pas corespunzător subsecvenţelor de lungime 0, 1, ..., L 1
¬
vom avea de procesat numai subsecvenţe comune, atunci numărul acestora nu va putea depăşi ¶S ¶.
În realitate acest număr va fi mai mic deoarece subsecvenţele identice ale celui mai scurt string
¬
vor fi luate o singură dată. Complexitatea supraestimată a acestui algortim este O L ¶S ¶ ¶S ¶.
Soluţie 80p
Ţinând cont că pentru 80% din teste o complexitate de memorie O L ¶S ¶ este accept-
¬
abilă, putem menţine ı̂ntr-o structură de date toate subsecvenţele lui S de lungime cel mult L.
Subsecvenţele trebuie ţinute pe numere ı̂ntregi de 64 de biţi pentru a nu consuma memorie ı̂n
exces şi a compara două subsecvenţe ı̂n timp constant. Astfel, iterând celelalte şiruri, vom calcula
din nou toate subsecvenţele, dar vom trece mai departe doar cele care se găsesc ı̂n şirul anterior.
Astfel, setul de subsecvenţe descreşte semnificativ. Condiţia pentru a obţine acest punctaj este
ca structura de date ce menţine subsecvenţele să permită operaţii de inserare şi ştergere ı̂n timp
logaritmic. Atunci complexitatea finală este O L ¶S ¶ log L ¶S ¶.
Soluţie 100p, Adrian Panaete
Se concatenează cele n stringuri obţinând astfel un string de lungime ¶S ¶. Pentru fiecare poziţie
din şirul concatenat memorăm un cod binar de cel mult 4 biţi care ne indică din ce string iniţial
face parte poziţia respectivă (pentru primul sir codul 1 0001 pentru al doilea codul 2 0010
pentru al treilea codul 4 0100 si pentru al patrulea codul 8 1000).
Vom ı̂ncepe să sortăm poziţiile utilizând drept criteriu ordinea lexicografică a subsecvenţelor
ce ı̂ncep din poziţia respectivă. Pentru sortare vom folosi ideea algoritmului radix-sort grupând
poziţiile ı̂n buchete care conţin toate poziţiile cu o aceeaşi subsecvenţă de o anumită lungime
CAPITOLUL 11. OJI 2013 11.2. SUBSECVENTE 219
şi descompunând fiecare buchet ı̂n cate două buchete corespunzătoare prelungirii subsecvenţei
¬ ¬ ¬ ¬
respective fie cu caracterul a fie cu caracterul b .
Odată cu introducerea unei poziţii ı̂n unul dintre buchete adăugăm la un cod binar al buchetului
şi codul poziţiei din care provine subsecvenţa corespunzătoare. În acest fel putem urmări dacă un
buchet mai merită sa fie descompus (ı̂n cazul ı̂n care codul buchetului are la final toţi cei n biţi
deci conţine subsecvenţe din toate cele n stringuri iniţiale).
Tot codurile binare ale poziţiilor ne semnalizează dacă la un moment dat ı̂ncercăm să prelungim
o subsecvenţă cu un caracter care nu mai aparţine stringului. În acel caz poziţia respectivă nu
va mai fi plasată ı̂n niciunul dintre cele două buchete. Cum fiecare buchet descrie de fapt o
subsecvenţă de o anumită lungime odată cu definitivarea unui buchet dacă se constată că el conţine
poziţii din toate stringurile se poate folosi lungimea respectivă pentru a ı̂mbunătăţi eventual soluţia
problemei.
Deoarece fiecare poziţie poate fi utilizată de cel mult L ori (L=soluţia testului) vom obţine un
algoritm de complexitate O L ¶S ¶.
Soluţie 100p, Adrian Panaete
¬ ¬ ¬ ¬
Construim un arbore binar, ı̂n care fiul stâng este a iar fiul dreapt este b . Considerăm toate
subsecvenţele de lungime L şi le inserăm ı̂n acest arbore binar. Inserarea se face pornind din
¬ ¬
rădăcină şi mergând spre stânga dacă primul caracter al subsecvenţei este a sau ı̂n dreapta dacă
¬ ¬
acesta este b . Recursiv se procedează pentru restul nodurilor din arbore şi restul subsecvenţei.
Complexitatea acestei operaţii este O L ¶S ¶.
În acest arbore binar, fiecare nod memorează câte o subsecvenţă de lungime egală cu distanţa
de la nod la rădăcină. Pentru fiecare nod din arbore memorăm pe un număr de cel mult ¶M ¶ biţi
din ce string face parte subsecvenţa corespunzătoare. Luăm ulterior celelalte şiruri şi parcurgem
arborele cu toate subsecvenţele sale de lungime cel mult 60. Însă, de data aceasta, avem două
situaţii de ı̂ntrerupere a parcurgerii:
1: parcurgerea se ı̂ntrerupe deoarece un nod nu are continuare pe o literă; acest lucru ı̂nseamnă
de fapt că subsecvenţa corespunzătoare nu e ı̂n primul şir, deci implicit, orice continuare a ei nu
va fi ı̂n primul şir;
2: parcurgerea se ı̂ntrerupe pentru că informaţia din nod ı̂mi dovedeşte că un alt string anterior,
altul decât primul şir, nu conţine subsecvenţa corespunzătoare şi, implicit, nici continuările ei.
Dacă am orice altceva ı̂nsemna că de fapt subsecvenţa nu a fost marcată ı̂ntr-un şir anterior.
În cazul preferabil, când informaţia mă asigură că sunt pe o cale bună, actualizez informaţia din
nod adăugând un bit corespunzător şirului curent.
Soluţia se actualizează ı̂n timp real odată cu introducerea subsecvenţelor noului şir ı̂n ar-
borele binar ı̂n funcţie de cât de adânc reuşesc acestea să pătrundă fără ca eventual să apară o
ı̂ntrerupere de cele două tipuri. Soluţia de lungime maximă a ultimului şir este răspunsul corect.
Complexitatea finală este O L ¶S ¶.
char A[50010];
vector<char> S[5],aux;
vector<vector<char> > V[2];
int n,i,c,v,L;
int main()
{
freopen("subsecvente.in","r",stdin);
freopen("subsecvente.out","w",stdout);
V[0].push_back(aux);
c=0;
v=1;
for(L=0;;)
{
int i;
vector<vector<char> >::iterator it;
V[v].resize(0);
for(it=V[c].begin();it!=V[c].end();it++)
{
aux=*it;
aux.push_back(’a’);
for(i=0;i<n;i++)
if(search(S[i].begin(),
S[i].end(),
aux.begin(),aux.end())
==S[i].end())
break;
if(i==n)
V[v].push_back(aux);
aux=*it;
aux.push_back(’b’);
for(i=0;i<n;i++)
if(search(S[i].begin(),
S[i].end(),aux.begin(),
aux.end())
==S[i].end())
break;
if(i==n)
V[v].push_back(aux);
}
if(!V[v].size())break;
L++;
c=1-c;
v=1-v;
}
cout<<L;
return 0;
}
FILE *fin;
struct nod
{
int inf;
nod *F[2];
nod()
{
inf=0;
F[0]=F[1]=0;
}
};
CAPITOLUL 11. OJI 2013 11.2. SUBSECVENTE 221
nod *root,*q,*aux;
int pow,mask,lg,i,j,sol,start;
char a[50010],*p;
int main()
{
freopen("subsecvente.in","r",stdin);
freopen("subsecvente.out","w",stdout);
root=new nod;
pow=mask=1;
int n;
scanf("%d\n", &n);
scanf("%s",a+1);
lg=strlen(a+1);
for(i=1;i<=lg;i++)
for(j=0,p=a+i,q=root; j<60 && *p; j++,p++)
{
if(!q->F[ *p-’a’ ])
{
aux=new nod;
aux->inf=1;
q->F[ *p-’a’ ]=aux;
}
q=q->F[ *p-’a’ ];
}
for(;scanf("%s",a+1)+1;)
{
pow<<=1;
mask|=pow;
sol=0;
lg=strlen(a+1);
for(i=1;i<=lg;i++)
for(j=0,p=a+i,q=root;j<60&&*p;j++,p++)
{
if(!q->F[ *p-’a’ ]) break;
q=q->F[ *p-’a’ ];
q->inf|=pow;
if(q->inf!=mask) break;
if(j+1>sol)
{
start=i;
sol=j+1;
}
}
if(!sol) break;
}
printf("%d\n",sol);
//a[start+sol]=0;printf("%s",a+start);
return 0;
}
int p2,lg,mask,i,j,k,p,q,n,sol(int),s[2][NODES];
char cod[NODES];
char a[50010];
int main()
{
CAPITOLUL 11. OJI 2013 11.2. SUBSECVENTE 222
freopen("subsecvente.in","r",stdin);
freopen("subsecvente.out","w",stdout);
int n;
scanf("%d\n", &n);
for(p2=1,n=1; scanf("%s",a+1)+1; p2<<=1)
{
lg=strlen(a+1);
mask|=p2;
for(i=1;i<=lg;i++)
{
j=min(i+59,lg);
for(k=i,p=1;k<=j;k++)
{
q=a[k]-’a’;
if(!s[q][p])
s[q][p]=++n;
p=s[q][p];
cod[p]|=p2;
}
}
}
cod[1]=mask;
printf("%d\n",sol(1)-1);
return 0;
}
int p2,lg,mask,i,j,k,p,q,n,sol(int);
vector<int>s[2],cod;
char a[50010];
int main()
{
freopen("subsecvente.in","r",stdin);
freopen("subsecvente.out","w",stdout);
int n;
scanf("%d\n", &n);
s[0].push_back(0);
s[1].push_back(0);
cod.push_back(0);
s[0].push_back(0);
s[1].push_back(0);
cod.push_back(0);
for(p2=1,n=1; scanf("%s",a+1)+1; p2<<=1)
{
lg=strlen(a+1);
mask|=p2;
for(i=1;i<=lg;i++)
{
j=min(i+59,lg);
for(k=i,p=1;k<=j;k++)
{
CAPITOLUL 11. OJI 2013 11.2. SUBSECVENTE 223
q=a[k]-’a’;
if(!s[q][p])
{
s[0].push_back(0);
s[1].push_back(0);
cod.push_back(0);
s[q][p]=++n;
}
p=s[q][p];
cod[p]|=p2;
}
}
}
cod[1]=mask;
printf("%d\n",sol(1)-1);
return 0;
}
string main_str;
vector <string> strs;
void read_in()
{
ifstream fin(iname);
int n;
fin >> n;
fin.close();
}
return number;
}
CAPITOLUL 11. OJI 2013 11.2. SUBSECVENTE 224
if (!numbers_match)
return 0;
}
return 1;
}
int main(void)
{
read_in();
write_out(k);
return 0;
}
{
ifstream fin(iname);
int n; fin >> n;
int main(void)
{
string main_str; vector <string> strs;
read_in(main_str, strs);
write_out(k);
return 0;
}
va_end(args_list);
if (dp[curr_state] == -1)
{
// Call all other states.
for (size_t i = 0; i < args.size(); ++ i) if (args[i] > 0)
{
args[i] -= 1;
call_dp(args);
args[i] += 1;
CAPITOLUL 11. OJI 2013 11.2. SUBSECVENTE 227
if (dp[curr_state] == -1)
dp[curr_state] = eq ? 1 : 0;
}
return dp[curr_state];
}
int call_2dim_dp(void)
{
int ans = 0;
dp2[0][0] = 1;
for (size_t i = 0; i < strs[0].size(); ++ i)
for (size_t j = 0; j < strs[1].size(); ++ j)
if (strs[0][i] == strs[1][j])
{
dp2[i + 1][j + 1] = max(dp2[i + 1][j + 1], dp2[i][j] + 1);
ans = max(ans, dp2[i + 1][j + 1]);
}
else
dp2[i + 1][j + 1] = 0;
return ans;
}
int main(void)
{
read_in(strs);
if (strs.size() > 2)
{
// Build dp matrix.
vector <int> dp_indexes; u64 dp_size = 1;
for (size_t i = 0; i < strs.size(); ++ i)
{
dp_size *= strs[i].size();
dp_base = max(dp_base, (u64)strs[i].size() + 1);
dp_indexes.push_back((int) strs[i].size() - 1);
}
dp.resize(dp_size + 5);
dp.assign(dp.size(), -1);
// Get answer.
int ans = 0;
for (int i = 1; i <= hash_obj_index; ++ i)
ans = max(ans, dp[i]);
write_out(ans);
}
else
write_out(call_2dim_dp());
return 0;
}
if (!numbers_match)
return 0;
CAPITOLUL 11. OJI 2013 11.2. SUBSECVENTE 229
return 1;
}
int main(void)
{
string main_str; vector <string> strs;
read_in(main_str, strs);
size_t length_ans = 0;
for (size_t length = 62; length >= 1 && !length_ans; -- length)
{
i64 number_mask = (1LL << length) - 1;
write_out(length_ans);
return 0;
}
{
u64 number = 0;
int main(void)
{
read_in(strs);
int step_ans = 0;
if (numbers[step].size() == 0)
break;
}
if (numbers[step].size() > 0)
{
step_ans = step;
break;
}
}
write_out(max_length);
return 0;
}
int main(void)
{
string main_str; vector <string> strs;
read_in(main_str, strs);
string number_ans;
for (size_t i = 0; i < main_str.size(); ++ i)
for (size_t j = i; j < main_str.size(); ++ j)
{
string number_str = main_str.substr(i, j-i+1);
if (search_string(number_str, strs))
if (number_ans.size() < number_str.size())
number_ans = number_str;
}
write_out(number_ans.size());
return 0;
}
fin.close();
}
return true;
}
int main(void)
{
string main_str; vector <string> strs;
read_in(main_str, strs);
string number_ans;
for (int length = 61; length >= 1 && !number_ans.size(); -- length)
for (int i=0; i<=(int)main_str.size()-length&&!number_ans.size();++i)
{
string number_str = main_str.substr(i, length);
if (search_string(number_str, strs))
number_ans = number_str;
}
write_out(number_ans.size());
return 0;
}
char A[50100],B[50100];
int n,C[50100],V[2][50100],x,M,k,S,SOL;
void radix(int,int,int,int);
int main()
{
freopen("subsecvente.in","r",stdin);
freopen("subsecvente.out","w",stdout);
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",B);
x=1<<i;M|=x;
for(k=0;B[k];k++)
{
A[++S]=B[k];
C[S]=x;
V[0][S]=S;
}
}
radix(1,S,0,0);
printf("%d",SOL);
return 0;
}
for(w=ST;w<=DR;w++)
{
u=V[p][w];
if(C[u]!=C[u+LG]) continue;
if(A[u+LG]==’a’)
{
DA++;
MA|=C[u];
V[q][DA]=u;
}
else
{
SB--;
MB|=C[u];
V[q][SB]=u;
}
}
if(MA==M)
{
SOL=max(SOL,LG+1);
radix(SA,DA,LG+1,q);
}
if(MB==M)
{
SOL=max(SOL,LG+1);
radix(SB,DB,LG+1,q);
}
}
OJI 2012
12.1 blis
Problema 1 - blis 100 de puncte
Se consideră un şir de biţi şi un număr natural K. Şirul se ı̂mparte ı̂n secvenţe astfel ı̂ncât
fiecare bit din şir să aparţină unei singure secvenţe şi fiecare secvenţă să aibă lungimea cel puţin 1
şi cel mult K. După ı̂mpărţire, fiecare secvenţă de biţi se converteşte ı̂n baza 10, obţinându-se un
şir de valori zecimale. De exemplu, pentru şirul de biţi 1001110111101010011 şi K 4, se poate
obţine 1 0011 101 111 0 1010 011, apoi ı̂n baza 10: 1, 3, 5, 7, 0, 10, 3. O altă ı̂mpărţire poate fi 1
00 1 1 10 11 110 1010 011, adică 1, 0, 1, 1, 2, 3, 6, 10, 3.
Cerinţe
Scrieţi un program care:
a determină valoarea maximă (ı̂n baza 10) care se poate obţine dintr-o secvenţă de cel mult
K biţi
a ı̂mparte şirul iniţial ı̂n secvenţe de cel mult K biţi astfel ı̂ncât şirul zecimal obţinut să conţină
un subşir strict crescător de lungime maximă posibilă.
Date de intrare
Prima linie a fişierului de intrare blis.in conţine numărul natural K, iar pe linia a doua se află
şirul de biţi, şirul neconţinând spaţii.
Date de ieşire
Fişierul de ieşire blis.out va conţine pe prima linie un număr natural reprezentând valoarea
maximă care se poate obţine dintr-o secvenţă de cel mult K biţi, iar pe linia a doua un singur
număr natural reprezentând lungimea maximă a subşirului strict crescător care se poate obţine
din şirul de biţi prin ı̂mpărţirea sa ı̂n secvenţe de cel mult K biţi.
Restricţii şi precizări
a3 & lungimea şirului de biţi & 100000
apentru 70% din teste, lungimea şirului de biţi & 1000
a 1 & K & 30
a Un subşir se obţine dintr-un şir prin eliminarea a zero, unul, două sau mai multe elemente;
a O secvenţă este formată din elemente aflate pe poziţii consecutive ı̂n şir;
a Pentru rezolvarea corectă a primei cerinţe se acordă 20% din punctaj, iar pentru rezolvarea
corectă a celei de-a doua se acordă 80
Exemple
blis.in blis.out Explicaţii
4 15 Secvenţa de valoare maximă de cel mult 4 biţi este 1111,
1001110111101010011 6 adică 15 ı̂n baza 10.
Pentru cerinţa a doua, şirul binar se ı̂mparte astfel:
1 00 1 1 10 11 110 1010 011,
Se obţine şirul zecimal:
1, 0, 1, 1, 2, 3, 6, 10, 3.
Subşirul strict crescător maximal de lungime 6 este
0, 1, 2, 3, 6, 10
234
CAPITOLUL 12. OJI 2012 12.1. BLIS 235
char s[dim];
int t[dim], n, k, valm;
int a[dim][dim], d[dim];
void Citire()
{
ifstream fin(inFile);
fin >> k;
fin >> s;
fin.close();
void Calcul()
{
int i, j, x, p;
// prima cerinta
// determin valoarea maxima de cel mult k biti
if (k >= n) // iau toti bitii
{
valm = 0;
for (i = 1; i <= n; i++)
valm |= (t[i] << (n - i));
}
else
{
// iau primii k biti
x = 0;
p = 0;
for (i = 1; i <= k; i++)
{
x |= (t[i] << (k - i));
p = (p << 1) + 1;
}
p >>= 1;
valm = x;
// iau pe rand urmatorii biti de la k+1 la n
for (i = k + 1; i <= n; i++)
{
x = x & p;
x = (x << 1) + t[i];
if (valm < x) valm = x;
}
}
// a doua cerinta
a[0][1] = 100001;
d[0] = 1;
a[1][1] = t[1];
d[1] = 1;
ofstream fout(outFile);
fout << valm << "\n" << d[n] << "\n";
fout.close();
}
int main()
{
Citire();
Calcul();
return 0;
}
#define N 100010
int n,k,K,i,j,v,big,ST,DR,MI,BST[N];
int oo=(1<<30)+100;
int main()
{
freopen("7-blis.in","r",stdin);
freopen("blis.out","w",stdout);
scanf("%d%s",&k,B+1);
n=strlen(B+1);
for(i=1;i<=n+1;i++)
BST[i]=oo;
BST[0]=-1;
for(i=1;i<=n;i++)
{
v=0;
K=min(n,i+k-1);
for(j=i;j<=K;j++)
{
v=(v<<1)|(B[j]-’0’);
CAPITOLUL 12. OJI 2012 12.1. BLIS 238
big=max(big,v);
for(ST=0,DR=i+1;DR-ST-1;)
{
MI=(ST+DR)/2;
if(BST[MI]<v)
ST=MI;
else
DR=MI;
}
if(BST[DR]>v)
UPD[j].push_back(make_pair(DR,v));
}
printf("%d\n",big);
for(ST=0,DR=n+1;DR-ST-1;)
{
MI=(ST+DR)/2;
if(BST[MI]<oo)
ST=MI;
else
DR=MI;
}
printf("%d\n",ST);
return 0;
}
int main()
{
freopen("blis.in","r",stdin);
freopen("blis.out","w",stdout);
scanf("%d",&k);
scanf("%s",B+1);
n=strlen(B+1);
for(i=1;i<=n+1;i++)
BST[i]=oo;
BST[0]=-oo;
LMAX=0;
for(i=1;i<=n;i++)
{
v=0;
K=i+k-1<n?i+k-1:n;
for(j=i;j<=K;j++)
{
v<<=1;
v|=B[j]-’0’;
sol=v>sol?v:sol;
for(DR=LMAX+1;DR>=0;DR--)
if(BST[DR-1]<v)
break;
if(v<BST[DR])
{
paux=new nod;
CAPITOLUL 12. OJI 2012 12.1. BLIS 239
paux->val=v;
paux->lg=DR;
paux->urm=P[j];
P[j]=paux;
}
}
for(;P[i];)
{
paux=P[i];
P[i]=P[i]->urm;
if(BST[paux->lg]>paux->val)
BST[paux->lg]=paux->val;
delete paux;
}
while(BST[LMAX+1]<oo) LMAX++;
}
printf("%d\n%d",sol,LMAX);
return 0;
}
struct nod
{
int val,lg;
nod *urm;
};
nod *P[100010],*paux;
int k,n,i,v,j,K,ST,DR,MI,LMAX,sol,BST[100010],oo=2000000000;
char B[100010];
int main()
{
freopen("blis.in","r",stdin);
freopen("blis.out","w",stdout);
scanf("%d",&k);
scanf("%s",B+1);
n=strlen(B+1);
for(i=1;i<=n+1;i++)
BST[i]=oo;
BST[0]=-oo;
LMAX=0;
for(i=1;i<=n;i++)
{
v=0;
K=i+k-1<n?i+k-1:n;
for(j=i;j<=K;j++)
{
v<<=1;
v|=B[j]-’0’;
sol=v>sol?v:sol;
for(ST=0,DR=LMAX+1;DR-ST-1;)
{
MI=(ST+DR)>>1;
if(BST[MI]<v)
ST=MI;
else
DR=MI;
}
if(v<BST[DR])
{
paux=new nod;
CAPITOLUL 12. OJI 2012 12.2. PARC 240
paux->val=v;
paux->lg=DR;
paux->urm=P[j];
P[j]=paux;
}
}
for(;P[i];)
{
paux=P[i];
P[i]=P[i]->urm;
if(BST[paux->lg]>paux->val)
BST[paux->lg]=paux->val;
delete paux;
}
while(BST[LMAX+1]<oo)
LMAX++;
}
printf("%d\n%d",sol,LMAX);
return 0;
}
12.2 parc
Problema 2 - parc 100 de puncte
Un parc de formă dreptunghiulară este format din zone pietonale
şi piste de biciclete. Reprezentând harta parcului ı̂ntr-un sistem
cartezian, cu coordonata colţului stânga-jos 0, 0, pistele de biciclete
sunt reprezentate prin dungi orizontale sau verticale colorate cu gri, iar
zonele pietonale au culoarea albă, ca ı̂n figura din dreapta.
Vizitatorii parcului se pot plimba liber pe zonele pietonale ı̂n orice
direcţie, ı̂nsă pistele de biciclete se vor traversa, ı̂n linie dreaptă, paralel
cu axele. În figura alăturată avem un parc de dimensiuni 10 8, cu
Figura 12.1: parc
piste de biciclete verticale ı̂ntre 2 şi 4 respectiv 5 şi 8, şi orizontale ı̂ntre
0 şi 1 respectiv ı̂ntre 2 şi 4. Gigel se află ı̂n punctul A 1, 1 şi poate sa ajungă pe drumul cel
mai scurt la prietenul lui, ı̂n punctul B 8, 7 deplasându-se astfel: porneşte din punctul (1, 1) şi
parcurge un traseu format din segmente cu extremităţile ı̂n punctele de coordonate (1.5 , 2) (1.5,
4) (2 , 5) (4 , 5) (5 , 7) şi ı̂n final ajunge ı̂n punctul de coordonate (8 , 7).
Lungimea totală a drumului va fi aproximativ 11.4721359.
Cerinţe
Cunoscând dimensiunile parcului, coordonatele lui Gigel, coordonatele prietenului lui şi
poziţiile pistelor de biciclete, să se calculeze lungimea drumului minim şi numărul drumurilor
distincte de lungime minimă.
Date de intrare
Fişierul parc.in conţine pe prima linie două numere naturale Xparc şi Y parc separate prin
spaţiu, reprezentând dimensiunile parcului ı̂n direcţiile Ox respectiv Oy. Linia a doua va conţine
patru numere separate prin spaţiu xG, yG, xpr şi ypr ce reprezintă coordonatele lui Gigel şi
coordonatele prietenului lui. Linia a treia va conţine un număr natural m, reprezentând numărul
pistelor verticale. Următoarele m linii vor conţine perechi de valori de pe axa Ox ce delimitează
câte o pistă de biciclete verticală. Următoarea linie va conţine un număr natural n, reprezentând
numărul pistelor orizontale. Următoarele n linii vor conţine perechi de valori de pe axa Oy ce
delimitează câte o pistă de biciclete orizontală.
Date de ieşire
CAPITOLUL 12. OJI 2012 12.2. PARC 241
Fişierul parc.out va conţine pe prima linie lungimea minimă a drumului cerut de problemă, un
număr real. Linia a doua va conţine numărul drumurilor minime distincte, un număr natural.
- 0 & xG, xpr & Xparc & 30000, 0 & yG, ypr & Y parc & 30000;
- 0 $ m, n $ 2000;
- perechile de numere naturale ce definesc o pistă nu sunt ordonate; - pistele orizontale, şi cele
verticale nu sunt ordonate ı̂n fişierul de intrare;
- două piste de aceeaşi direcţie nu se suprapun;
- Gigel şi prietenului lui sunt pe zone pietonale (incluzând şi marginile acestora);
- două drumuri sunt distincte dacă diferă prin cel puţin un punct;
- numărul de drumuri distincte nu va depăşi 1 000 000 000;
- lungimea drumului din fişierul de ieşire este un număr real ce se va accepta cu eroare maxima
de 0.01;
- nu se admite formatul ştiinţific pentru afişarea numerelor reale;
- prima cerinţă valorează 40% din punctaj, iar a doua valorează 60% din punctaj.
Exemple
Cel mai scurt drum dintre două puncte ı̂n plan se obţine pe linie dreaptă. Astfel dacă eliminăm
porţiunile de traversare a pistelor de biciclete, traseul lui Gigel către prietenul lui trebuie să fie
un segment. Această lungime se calculează cu teorema lui Pitagora, la care se adaugă lungimile
de traversare orizontală şi verticală.
Soluţia nu este unică de fiecare dată! Dacă traseul lui Gigel intersectează punctul de intâlnire
a două piste de biciclete, numărul soluţiilor se dublează, fiindcă Gigel poate traversa zona ı̂n
două moduri: orizontal-vertical sau vertical-orizontal. În ambele cazuri două laturi alăturate ale
aceluiaşi dreptunghi, deci ambele sunt soluţii corecte. Astfel, numărul soluţiilor distincte pentru
orice teste de intrare este o putere a lui 2.
int XP,YP,xg,yg,xp,yp,xc,yc,XL,XR,YD,YU,m,n,i,j,M,N,L,R,U,D,X[20010],Y[20010];
double SOL;
void read(),solve();
int main()
{
read();
solve();
return 0;
}
void read()
{
freopen("parc.in","r",stdin);
freopen("parc.out","w",stdout);
scanf("%d%d",&XP,&YP);
XP++;
YP++;
scanf("%d%d",&xg,&yg);
scanf("%d%d",&xp,&yp);
int aux;
scanf("%d",&m);
m*=2;
X[++R]=0;
for(;m;m--)
{
scanf("%d",&aux);
X[++R]=aux;
}
X[++R]=XP;
L=1;
scanf("%d",&m);
m*=2;
Y[++U]=0;
for(;m;m--)
{
scanf("%d",&aux);
Y[++U]=aux;
}
Y[++U]=YP;
D=1;
}
void solve()
{
if(xp==xg){YP=yg>yp?yg-yp:yp-yg;printf("%d\n1\n",YP);return;}
if(yp==yg){XP=xg>xp?xg-xp:xp-xg;printf("%d\n1\n",XP);return;}
if(xg>xp){xg=XP-xg;xp=XP-xp;for(i=L;i<=R;i++)X[i]=XP-X[i];}
if(yg>yp){yg=YP-yg;yp=YP-yp;for(i=D;i<=U;i++)Y[i]=YP-Y[i];}
sort(X+L,X+R+1);
for(;;L+=2)if(X[L]<=xg&&xg<=X[L+1])break;
for(;;R-=2)if(X[R-1]<=xp&&xp<=X[R])break;
X[L]=xg;X[R]=xp;
sort(Y+D,Y+U+1);
for(;;D+=2)if(Y[D]<=yg&&yg<=Y[D+1])break;
for(;;U-=2)if(Y[U-1]<=yp&&yp<=Y[U])break;
Y[D]=yg;Y[U]=yp;
int CX=0,CY=0;double cx,cy;
for(i=L;i<R;i+=2)CX+=X[i+1]-X[i];
for(i=D;i<U;i+=2)CY+=Y[i+1]-Y[i];
SOL+=(double)(X[R]-X[L]-CX);
CAPITOLUL 12. OJI 2012 12.2. PARC 243
SOL+=(double)(Y[U]-Y[D]-CY);
cx=(double)CX;cy=(double)CY;
SOL+=sqrt(cx*cx+cy*cy);
printf("%lf\n",SOL);
for(n=0,i=L+1;i<=R;i+=2,n++)X[n+1]=X[n]+X[i]-X[i-1];
for(m=0,i=D+1;i<=U;i+=2,m++)Y[m+1]=Y[m]+Y[i]-Y[i-1];
for(D=X[n],U=Y[m];U;){R=D%U;D=U;U=R;}
xg=X[n]/D;yg=Y[m]/D;
for(i=1,j=1,U=1;i<n&&j<m;)
{
if(X[i]%xg){i++;continue;}
if(Y[j]%yg){j++;continue;}
if(X[i]/xg<Y[j]/yg){i++;continue;}
if(X[i]/xg>Y[j]/yg){j++;continue;}
i++;j++;
U*=2;
printf("%d\n",U);
}
int main()
{ int pv[2][10000],po[2][10000],xp,yp,xg,yg,xpr,ypr,m,n,i,j,p2,
sv[10000],so[10000];
int startv, finishv,starto,finisho,xinv=0,yinv=0,d_oriz,d_vert,
cx,cy,semnx=1,semny=1;
int kx,ky,segmx[10000],segmy[10000];
double lung;
freopen("parc.in","r",stdin);
freopen("parc.out","w",stdout);
scanf("%d %d\n",&xp,&yp);
scanf("%d %d %d %d\n",&xg,&yg,&xpr,&ypr);
CAPITOLUL 12. OJI 2012 12.2. PARC 244
if (ypr<yg)
{ yinv=1;
semny=-1;
yg=yp-yg;
ypr=yp-ypr;
}
if (xpr==xg)
{ printf("%d\n1\n",ypr-yg);
return 0;
}
if (ypr==yg)
{ printf("%d\n1\n",xpr-xg);
return 0;
}
scanf("%d\n",&m);
if (xinv)
for (i=1;i<=m;++i)
{ scanf("%d %d\n",&pv[0][i],&pv[1][i]);
if (pv[0][i]<pv[1][i]) swap(pv[0][i],pv[1][i]);
pv[0][i]=xp-pv[0][i]; // citesc pistele verticale (x-urile)
// si daca e nevoie
pv[1][i]=xp-pv[1][i]; // transform in simetricul lor
// sa le prelucrez de la st. da dr.
}
else
for (i=1;i<=m;++i)
{ scanf("%d %d\n",&pv[0][i],&pv[1][i]);
if (pv[0][i]>pv[1][i]) swap(pv[0][i],pv[1][i]);
}
startv=1;
while (xg>pv[0][startv]) ++startv; // ma uit cate piste verticale
// sunt intre Gigel si prietenul lui
finishv=m;
while (xpr<pv[1][finishv]) --finishv;
CAPITOLUL 12. OJI 2012 12.2. PARC 245
starto=1;
while (yg>po[0][starto])
++starto; // ma uit cate piste orizontale sunt intre Gigel si
// prietenul lui
finisho=n;
while (ypr<po[1][finisho]) --finisho;
d_oriz=sv[finishv]-sv[startv-1]; // grosimea pistelor verticale
d_vert=so[finisho]-so[starto-1]; // grosimea pistelor verticale
cx=xpr-xg-d_oriz; // cea mai scurta distanta dintre doua puncte
// se calculeaza dintr-un triunghi
cy=ypr-yg-d_vert; // dreptunghic cu catele cx si cy
//cx si cy reprezinta lungimile catetelor triunghiului dreptunghic
// fara pistele orizontale si verticale
lung=sqrt((float)(cx*cx+cy*cy))+d_oriz+d_vert; // la lungimea ipotenuzei
// se adauga distantele orizontale si verticale
segmx[0]=pv[0][startv]-xg;kx=0;
for (i=startv+1;i<=finishv;++i)
{
++kx;
segmx[kx]=segmx[kx-1]+pv[0][i]-pv[1][i-1];
}
segmy[0]=po[0][starto]-yg;ky=0;
for (i=starto+1;i<=finisho;++i)
{
++ky;
segmy[ky]=segmy[ky-1]+po[0][i]-po[1][i-1];
}
p2=1;
i=j=0;
while(i<=kx && j<=ky)
if (segmx[i]*cy==segmy[j]*cx)
{
p2*=2;
++i;++j;
}
else
if (segmx[i]*cy>segmy[j]*cx)
++j;
else
++i;
printf("%lf\n",lung);
printf("%d\n",p2);
return 0;
}
OJI 2011
13.1 suma
Problema 1 - suma 100 de puncte
Constructorii angajaţi de faraonul Keops au terminat construirea pi-
ramidei ı̂n trepte mult visată. Măreaţa piramidă are n camere identice de
formă cubică, numerotate de la 1 la n, dispuse pe m niveluri astfel:
- camera din vârful piramidei formează nivelul 1 şi are numărul 1;
- nivelul 2 al piramidei este format din următoarele 4 camere care ı̂n
secţiune cu un plan paralel cu baza au aspectul unei matrice cu 2 linii şi 2
coloane; camerele de pe nivelul 2 sunt numerotate de la 2 la 5 ı̂n ordinea
crescătoare a liniilor matricei, iar pe aceeaşi linie ı̂n ordinea crescătoare a Figura 13.1: suma
coloanelor matricei;
....................
- nivelul m al piramidei este format din m m camere şi au, ı̂n secţiune cu un plan paralel
cu baza, aspectul unei matrice cu m linii şi m coloane; camerele de pe nivelul m sunt numerotate
ı̂n continuarea celor de pe nivelurile 1, 2, ..., m 1, ı̂n ordinea crescătoare a liniilor matricei de
secţiune, iar pe aceeaşi linie ı̂n ordinea crescătoare a coloanelor matricei. De exemplu, piramida
din desenul de mai sus are n 30, m 4 iar camerele sunt numerotate şi dispuse pe niveluri
astfel:
Nivelurile de camere sunt poziţionate astfel ı̂ncât camerele de pe prima linie şi prima coloană
a fiecărui nivel să se suprapună. Pentru exemplul dat, camerele 1, 2, 6 şi 15 sunt situate una sub
alta, ı̂n această ordine.
Accesul ı̂n oricare din camerele piramidei, situate pe diferite niveluri, se realizează prin drumuri
construite astfel:
a intrarea ı̂n piramidă se face doar prin camera din vârful ei, cea cu numărul 1;
a din camera cu numărul k de pe un drum se poate intra ı̂ntr-una din cele patru camere situate
pe nivelul imediat următor al piramidei şi anume: camera situată sub cea cu numărul k sau una din
cele trei camere vecine acesteia ı̂n secţiune (ı̂n direcţiile Est, Sud-Est, Sud, considerând secţiunile
poziţionate ca ı̂n imaginile de mai sus). De exemplu, din camera cu numărul 10 se poate intra
ı̂ntr-una din camerele cu numerele: 20, 21, 24 sau 25.
Faraonul priveşte cu mândrie şi tristeţe la frumoasa piramidă. Banii din visterie s-au ı̂mpuţinat
iar camerele piramidei trebuie finisate şi decorate. Scribul său favorit a refăcut toate calculele, a
eliminat obiectele inutile şi a stabilit pentru fiecare cameră k un cost ck aferent finisării şi decorării
ei (1 & k & n).
246
CAPITOLUL 13. OJI 2011 13.1. SUMA 247
Însă, suma totală necesară fiind ı̂ncă mare, faraonul i-a cerut scribului să aleagă un drum,
dintre cele construite, care să treacă prin toate nivelurile piramidei astfel ı̂ncât suma s a tuturor
costurilor aferente finisării şi decorării camerelor de pe acest drum să fie minimă. Deocamdată,
doar aceste camere vor fi aranjate...
Cerinţe
Scrieţi un program care să determine numărul m de niveluri ale piramidei, suma minimă s
a tuturor costurilor aferente finisării şi decorării camerelor de pe un drum ce trece prin toate
nivelurile piramidei, construit ı̂n modul descris ı̂n enunţ, precum şi un astfel de drum pentru care
se obţine suma minimă, putând fi ales de scrib.
Date de intrare
Fişierul de intrare suma.in conţine pe prima linie numărul natural nenul n reprezentând
numărul de camere din piramidă. A doua linie conţine n numere naturale nenule c1 , c2 , ..., cn ,
separate prin câte un spaţiu, reprezentând costurile aferente finisării şi decorării camerelor, ı̂n
ordinea numerotării lor.
Date de ieşire
Fişierul de ieşire suma.out va conţine pe prima linie două numere naturale m şi s, separate
printr-un singur spaţiu, cu semnificaţia din enunţ. Cea de-a doua linie va conţine, separate prin
câte un spaţiu, ı̂n ordinea parcurgerii lor, numerele camerelor de pe un drum ce trece prin toate
nivelurile piramidei, drum pentru care se obţine suma minimă s.
Exemple
suma.in suma.out Explicaţii
14 3 13 Piramida conţine 14 camere dispuse pe m 3 trei
78455842778316 138 niveluri. Nivelurile conţin valorile:
2 m m 1 2m 1
n 1 4 9 ... m
6
Pentru a memora costurile asociate camerelor precum şi sumele minime asociate drumurilor con-
struite pentru fiecare cameră, drumuri ce ajung pe ultimul nivel al piramidei, se pot folosi două
tablouri tridimensionale.
Fie A masivul ce memorează costurilor camerelor pe secţiuni şi T masivul sumelor minime.
Pentru orice cameră situată pe un nivel k al piramidei, linia i şi coloana j, suma minimă a costurilor
asociate unui drum ce porneşte din această cameră şi se opreşte pe ultimul nivel al piramidei se
poate calcula astfel:
T[m][i][j]=A[m][i][j], pentru i=1, 2, ...,m şi j=1, 2, 3, ..., m
T[k][i][j]=A[k][i][j]+min T[k+1][i][j], T[k+1][i][j+1], T[k+1][i+1][j], T[k+1][i+1][j+1] ,
pentru k=m-1,m-2, ...,1; i=1,2, ...,k; j=1,2, ...,k.
Suma minimă va fi memorată ı̂n T[1][1][1]. Pentru a afişa numerele camerelor se află pe cel
mai mic drum, din punct de vedere lexicografic, ale căror costuri formează suma minimă T[1][1][1]
refacem ”traseul” prin care a fost obţinută această sumă ı̂n T, pornind de la T[1][1][1] sau se
memorează coordonatele k, i şi j ale camerelor ı̂n timpul construirii sumelor parţiale minime.
int a[58][58][58],t[58][58][58];
int n,k;
ifstream f("suma.in");
ofstream g("suma.out");
void citeste()
{
int i,l,c,x;
k=0;
f>>n;
for(i=1;i<=n;i++)
while (n>0)
{ k++;
for(l=1;l<=k;l++)
for(c=1;c<=k;c++)
{ f>>x;
t[k][l][c]=a[k][l][c]=x;
}
n=n-k*k;
}
void suma()
{ int i,l,c,x;
for(i=k-1;i>=1;i--)
for(l=1;l<=i;l++)
for(c=1;c<=i;c++)
CAPITOLUL 13. OJI 2011 13.1. SUMA 249
{ if(t[i+1][l][c]<=t[i+1][l][c+1]) x=t[i+1][l][c];
else x=t[i+1][l][c+1];
if(x>t[i+1][l+1][c])x=t[i+1][l+1][c];
if(x>t[i+1][l+1][c+1])x=t[i+1][l+1][c+1];
t[i][l][c]=t[i][l][c]+x;
}
}
void drum()
{int c,l,i=1,s,nr=0;
s=t[1][1][1];
g<<s;
g<<endl<<1;
l=c=1;
do
{ nr=nr+i*i;
s=s-a[i][l][c]; i++;
if(s)
{if(t[i][l][c]==s) {l=l;c=c;}
else
if(t[i][l][c+1]==s)c=c+1;
else
if(s==t[i][l+1][c])l=l+1;
else {l=l+1;c=c+1;}
g<<’ ’<<nr+(l-1)*i+c; }
} while(s);
g<<endl;
g.close();
}
int main()
{citeste();
suma();
drum();
}
int N, C[NMax];
int pd[NMax], next[NMax], from[MAX_LEV], to[MAX_LEV], lev;
int main()
{
int i, j, sub, l, c;
scanf("%d", &N);
for (i = 1; i <= N; ++i)
scanf("%d", &C[i]);
pd[j] = INF;
update(j, sub);
update(j, sub+1);
update(j, sub+i+1);
update(j, sub+i+2);
}
}
return 0;
}
int a[58][58][58],t[58][58][58];
int n,k;
void citeste()
{
freopen("suma.in","r",stdin);
int i,l,c,x;
k=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
while (n>0)
{ k++;
for(l=1;l<=k;l++)
for(c=1;c<=k;c++)
{ scanf("%d",&x);
t[k][l][c]=a[k][l][c]=x;
}
n=n-k*k;
}
}
void suma()
{ int i,l,c,x;
for(i=k-1;i>=1;i--)
for(l=1;l<=i;l++)
for(c=1;c<=i;c++)
{ if(t[i+1][l][c]<=t[i+1][l][c+1]) x=t[i+1][l][c];
else x=t[i+1][l][c+1];
if(x>t[i+1][l+1][c])x=t[i+1][l+1][c];
if(x>t[i+1][l+1][c+1])x=t[i+1][l+1][c+1];
t[i][l][c]=t[i][l][c]+x;
}
}
void drum()
{int c,l,i=1,s,nr=0;
freopen("suma.out","w",stdout);
s=t[1][1][1];
printf("%d ",k);//nr niveluri
CAPITOLUL 13. OJI 2011 13.2. UBUNTZEI 251
printf("%d\n1",s);
l=c=1;
do
{ nr=nr+i*i;
s=s-a[i][l][c]; i++;
if(s)
{if(t[i][l][c]==s) {l=l;c=c;}
else
if(t[i][l][c+1]==s)c=c+1;
else
if(s==t[i][l+1][c])l=l+1;
else {l=l+1;c=c+1;}
printf(" %d",nr+(l-1)*i+c); }
} while(s);
printf("\n");
}
int main()
{citeste();
suma();
drum();
}
13.2 ubuntzei
Problema 2 - ubuntzei 100 de puncte
Trei ubuntzei au hotărât ca anul acesta să petreacă ziua de 1 Mai pe malul Mării Negre
ı̂mpreună cu prietenii lor, motiv pentru care au pus la cale o excursie pe un traseu care să plece
din oraşul lor Cluj-Napoca spre Vama Veche, unde nisipul ı̂i aşteaptă.
În ţara ubuntzeilor există N localităţi, numerotate de la 1 la N , legate ı̂ntre ele prin M şosele
bidirecţionale de diferite lungimi. Localitatea de plecare a ubuntzeilor, oraşul Cluj-Napoca, este
numerotată cu 1, iar localitatea destinaţie, Vama Veche, cu N . Între oricare două localităţi există
cel mult o şosea. Fiecare şosea uneşte două localităţi distincte şi se poate călători ı̂ntre oricare
două localităţi circulând numai pe şosele.
Prietenii ubuntzeilor locuiesc ı̂n K localităţi distincte, diferite de Cluj-Napoca şi Vama Veche.
Pentru a nu călători singuri, cei trei ubuntzei vor să treacă prin cele K localităţi ı̂n care locuiesc
prietenii lor, şi apoi, ı̂mpreună cu aceştia, să-şi continue excursia către mare.
Nerăbdători să ajungă cât mai repede la destinaţie, ubuntzeii s-au hotărât să ı̂şi stabilească
un traseu de lungime minimă care să treacă prin toate cele K localităţi.
Cerinţe
Scrieţi un program care să determine, pentru ubuntzei, lungimea minimă L a unui traseu de
la Cluj-Napoca la Vama Veche ce trece prin toate cele K localităţi.
Date de intrare
Prima linie a fişierului de intrare ubuntzei.in conţine două numere naturale N M , separate
printr-un spaţiu, cu semnificaţia din enunţ. A doua linie a fişierului conţine K 1 numere naturale
distincte: K C1 C2 ... CK , separate prin câte un spaţiu, K având semnificaţia din enunţ iar C1 ,
C2 , ..., CK reprezentând cele K localităţi ı̂n care locuiesc prietenii. Fiecare din următoarele M
linii conţine câte trei numere naturale x y z, separate prin câte un spaţiu, reprezentând o şosea
care leagă localitatea x de localitatea y şi are lungimea z.
Date de ieşire
Exemple
Ulterior, enumerăm toate permutările P ale celor K noduri, calculăm pentru fiecare distanţa
d 1, P1 d P1 , P2 ...d PK 1 , PK d PK , N şi alegem cea mai mică valoare, ce va fi răspunsul.
3
Complexitate finală: O N K!.
Soluţie 20 de puncte
Se va calcula lungimea drumului minim de la nodul 1 la nodul N , utilizând orice algoritm de
drum minim cunoscut.
int rf[MAX_N][MAX_N];
int main(void)
{
int nodeCount, edgeCount, subsetCount, u, v, cost;
ifstream in(iname);
in >> nodeCount >> edgeCount >> subsetCount;
subset.resize(subsetCount);
idxSubset.resize(nodeCount);
idxSubset.assign(idxSubset.size(), -1);
for (int i = 0; i < subsetCount; ++ i)
{
in >> u; -- u;
assert(0 < u && u < nodeCount - 1);
subset[i] = u;
idxSubset[u] = i;
inSubset[u] = true;
}
if (subsetCount > 0)
{
for (int i = 0; i < nodeCount; ++ i)
{
for (int j = i + 1; j < nodeCount; ++ j)
{
assert(rf[i][j] < INF);
if ((inSubset[i] || !i || i == nodeCount - 1) &&
(inSubset[j] || !j || j == nodeCount - 1))
{
adj[i].push_back(j);
adj[j].push_back(i);
}
}
}
while (!que.empty())
{
int u = que.front().first;
int s = que.front().second;
que.pop();
inque[u][s] = false;
if (!inSubset[v])
{
if (dist[v][s] > dist[u][s] + rf[u][v])
{
dist[v][s] = dist[u][s] + rf[u][v];
if (inque[v][s] == false)
{
que.push(make_pair(v, s));
inque[v][s] = true;
}
}
}
else if (!(s & (1 << idxSubset[v])))
{
int t = s | (1 << idxSubset[v]);
return 0;
}
d[src] = 0;
for (que.push(make_pair(-d[src], src)); !que.empty(); )
{
int u = que.top().second;
int dq = -que.top().first;
que.pop();
if (d[u] < dq) continue ;
priority_queue < pair <int, pair <int, int> > > queK;
int s = queK.top().second.second;
int d = -queK.top().first;
queK.pop();
if (dist[u][s] < d) continue;
int main(void)
{
int nodeCount, edgeCount, subsetCount, u, v, cost;
ifstream in(iname);
in >> nodeCount >> edgeCount >> subsetCount;
int src = 0;
int sink = nodeCount - 1;
vector <int> subset(subsetCount), idxSubset(nodeCount);
bitset <MAX_N> inSubset;
subset.push_back(src),
idxSubset[src] = (int) subset.size() - 1,
inSubset[src] = true;
subset.push_back(sink),
idxSubset[sink] = (int) subset.size() - 1,
inSubset[sink] = true;
adj[u].push_back(make_pair(v, cost));
adj[v].push_back(make_pair(u, cost));
}
in.close();
vector <int> d;
if (subsetCount > 0)
{
for (size_t i = 0; i < subset.size(); ++ i)
if (subset[i] != src && subset[i] != sink)
{
d = go_dijkstra(subset[i], nodeCount, adj);
for (size_t j = 0; j < d.size(); ++ j) if (inSubset[j])
{
assert(d[j] < INF);
if (subset[i] != (int) j)
adjK[i].push_back(make_pair(idxSubset[j], d[j]));
}
}
return 0;
}
int N, M, K, C[KMAX];
int source[NMAX], path[KMAX][NMAX];
vector<pii> G[NMAX];
int pd[1<<KMAX][KMAX];
set<pii> q;
sol[sursa] = 0;
q.clear();
int main()
{
int i, j, k, newCost;
for (; M; --M)
{
scanf("%d %d %d", &i, &j, &k);
G[i].pb(mp(j,k));
G[j].pb(mp(i,k));
}
dijkstra(1, source);
if (K == 0)
{
printf("%d\n", source[N]);
return 0;
}
printf("%d\n", bst);
return 0;
}
#include <queue>
#include <bitset>
#include <cassert>
#include <memory.h>
#include <algorithm>
int main(void)
{
int nodeCount, edgeCount, subsetCount, u, v, cost;
ifstream in(iname);
in >> nodeCount >> edgeCount >> subsetCount;
subset.resize(subsetCount);
for (int i = 0; i < subsetCount; ++ i)
{
in >> subset[i];
subset[i] --;
}
if (subsetCount > 0)
{
sort(subset.begin(), subset.end());
int src = 0, sink = nodeCount - 1;
int answer = INF;
do {
int temp = d[src][subset.front()] + d[subset.back()][sink];
return 0;
}
CAPITOLUL 13. OJI 2011 13.2. UBUNTZEI 260
OJI 2010
14.1 immortal
Problema 1 - immortal 100 de puncte
Cei care au văzut filmul Nemuritorul, ştiu că fraza cu care nemuritorii ı̂ncep lupta este ”Nu
poate să rămână decât unul singur”. Să ı̂ncercăm să simulăm povestea nemuritorilor.
Într-o zonă dreptunghiulară formată din n linii (numerotate de la 1 la n) şi m coloane (nu-
merotate de la 1 la m) se află maxim n m 1 nemuritori.
Doi nemuritori vecini se ”luptă” ı̂ntre ei şi cel care pierde lupta este eliminat. ”Lupta” constă
ı̂n săritura unuia dintre nemuritori peste celălalt, dacă această săritură se poate face. Săritura se
poate face pe orizontală sau verticală şi nemuritorul peste care s-a sărit dispare.
Prin vecin al nemuritorului din poziţia i, j ı̂nţelegem un nemuritor din una dintre poziţiile
i 1, j , i 1, j , i, j 1, i, j 1. Deci, după luptă nemuritorul din câmpul i, j se va găsi
ı̂n una dintre poziţiile: i 2, j , i 2, j , i, j 2 sau i, j 2, dacă această poziţie este liberă
şi este ı̂n interiorul zonei.
Cerinţe
Se cere să se determine o succesiune a luptelor ce pot fi purtate, astfel ı̂ncât la final să rămână
un singur nemuritor.
Date de intrare
Fişierul de intrare immortal.in conţine pe prima linie trei valori naturale n m I, separate prin
câte un spaţiu, reprezentând numărul de linii, numărul de coloane ale zonei descrise şi respectiv
numărul de nemuritori existenţi iniţial.
Următoarele I linii conţin fiecare câte două numere naturale x y separate printr-un spaţiu,
reprezentând poziţiile unde se găsesc iniţial cei I nemuritori (linia şi coloana).
Date de ieşire
Fişierul de ieşire immortal.out va conţine I 1 linii, fiecare linie descriind o ”luptă”. Luptele
vor fi scrise ı̂n ordinea ı̂n care au avut loc.
O linie va conţine 4 numere naturale care indică: primele două poziţia de pe care pleacă un
nemuritor la ”luptă”, ultimele două poziţia pe care acesta ajunge după ”luptă”.
Pentru ca ”lupta” să fie corectă, ı̂n poziţia peste care nemuritorul ”sare” trebuie să existe un
nemuritor care va ”muri”. O poziţie va fi specificată prin indicele de linie urmat de indicele de
coloană.
Valorile scrise pe aceeaşi linie vor fi separate prin spaţii.
1 $ n, m & 20
1 $ I & minr15, n m 1x
Pentru datele de test există ı̂ntotdeauna soluţie.
261
CAPITOLUL 14. OJI 2010 14.1. IMMORTAL 262
Exemple
immortal.in immortal.out Explicaţii
344 3331
12 3111 1 2 3 4
21 1113 ================= (3,3) --> (3,1) dispare (3,2)
32 1| | * | | | (3,1) --> (1,1) dispare (2,1)
33 |---------------| (1,1) --> (1,3) dispare (1,2)
2| * | | | |
|---------------|
3| | * | * | |
=================
int T[NMax][NMax];
int n, m, nr;
struct Poz {int x, y;} P[LgMax];
struct Lupta {struct Poz v, m;} sol[LgMax];
int ok=0;
int mort[LgMax];
int dx[]={-1, 0, 1, 0};
int dy[]={ 0, 1, 0, -1};
void citire(void);
void bkt(int);
void afisare(void);
int main()
{
citire();
bkt(0);
return 0;
CAPITOLUL 14. OJI 2010 14.1. IMMORTAL 263
void citire()
{
int i, j, x, y, k=0;
FILE * fin=fopen(InFile,"r");
fscanf(fin,"%d %d %d", &n, &m, &nr);
for (i=1; i<=nr; i++)
{fscanf(fin,"%d %d", &x, &y);
T[x+1][y+1]=i;}
for (i=2; i<=n+1; i++)
for (j=2; j<=m+1; j++)
if (T[i][j])
{P[++k].x=i; P[k].y=j; T[i][j]=k;}
for (i=0; i<n+4; i++) T[i][0]=T[i][1]=T[i][m+3]=T[i][m+2]=-1;
for (i=0; i<m+4; i++) T[0][i]=T[1][i]=T[n+2][i]=T[n+3][i]=-1;
}
void bkt(int k)
//s-au desfasurat k lupte sol[0], ..., sol[k-1]
{
int i, d, x, y, moare;
if (!ok)
if (k==nr-1)
{ok=1; afisare();}
else
for (i=1; i<=nr; i++)
if (!mort[i])
{
//poate sari i?
x=P[i].x; y=P[i].y;
for (d=0; d<4; d++)
if (T[x+dx[d]][y+dy[d]]>0 && T[x+2*dx[d]][y+2*dy[d]]==0)
{
//un vecin peste care poate sari
moare=T[x+dx[d]][y+dy[d]];
sol[k].m.x=x+2*dx[d]; sol[k].m.y=y+2*dy[d];
sol[k].v.x=x; sol[k].v.y=y;
P[i].x=x+2*dx[d]; P[i].y=y+2*dy[d];
T[x][y]=0; T[x+2*dx[d]][y+2*dy[d]]=i;
mort[T[x+dx[d]][y+dy[d]]]=1;
T[x+dx[d]][y+dy[d]]=0;
bkt(k+1);
P[i].x=x; P[i].y=y;
T[x][y]=i; T[x+2*dx[d]][y+2*dy[d]]=0;
T[x+dx[d]][y+dy[d]]=moare;
mort[T[x+dx[d]][y+dy[d]]]=0;
}
}
}
void afisare()
{
int i;
FILE * fout=fopen(OutFile,"w");
for (i=0; i<nr-1; i++)
fprintf(fout,"%d %d %d %d\n",
sol[i].v.x-1,sol[i].v.y-1,sol[i].m.x-1,sol[i].m.y-1);
fclose(fout);
}
/* backtracking neoptimizat */
int T[M][M],I,x[M],v[M],px[M],py[M],mx[M],my[M],sens[M],sf;
void scrie()
CAPITOLUL 14. OJI 2010 14.2. JOC 264
{FILE *f;
int i;
f=fopen("immortal.out","wt");
if(I==1) {fprintf(f,"0\n"); fclose(f); return;}
for(i=1;i<I;i++)
{fprintf(f,"%d %d ",mx[i],my[i]);
switch(sens[i])
{case 1: my[i]+=2; break;
case 2: my[i]-=2; break;
case 3: mx[i]+=2; break;
case 4: mx[i]-=2; break;}
fprintf(f,"%d %d\n",mx[i],my[i]);}
fclose(f);
return;}
void back(int k)
{int i,X,Y,z;
if(sf) return;
if(k==I) {sf=1; scrie(); return;}
for(i=1;i<=I;i++) if(v[i]==0)
{X=px[i]; Y=py[i];
if((z=T[X][Y-1])>0 && T[X][Y+1]==0)
{v[i]=1; sens[k]=1; mx[k]=X; my[k]=Y-1; py[z]+=2;
T[X][Y]=0; T[X][Y-1]=0; T[X][Y+1]=z; back(k+1);
v[i]=0; py[z]-=2; T[X][Y]=i; T[X][Y+1]=0; T[X][Y-1]=z;}
int main()
{FILE *f;
int i,j,k,n,m;
f=fopen("immortal.in","r");
fscanf(f,"%d %d %d",&n,&m,&I);
for(i=0;i<=n+1;i++) T[i][0]=T[i][m+1]=-1;
for(i=1;i<=m;i++) T[0][i]=T[n+1][i]=-1;
for(i=1;i<=I;i++)
{fscanf(f,"%d %d",&j,&k); T[j][k]=i; px[i]=j; py[i]=k;}
fclose(f);
back(1);
return 0;
}
14.2 joc
Problema 2 - joc 100 de puncte
Jocul nostru presupune parcurgerea unui tablou bidimensional cu două linii şi n coloane, format
din 2 n celule pătratice.
Fiecare celulă are asociată câte o valoare ı̂ntreagă v care nu se modifică pe durata desfăşurării
jocului. Jucătorii trebuie să găsească un drum de la celula de plecare la celula de sosire care
respectă următoarele condiţii:
- celula de plecare este cea din linia 1 şi coloana 1, iar celula de sosire este cea din linia 2 şi
coloana n.
- nu trece decât cel mult odată prin oricare celulă.
CAPITOLUL 14. OJI 2010 14.2. JOC 265
- deplasarea se poate face din celula curentă spre oricare altă celulă ı̂nvecinată cu ea pe ori-
zontală sau verticală.
- conţine cel mult k celule consecutive aflate pe aceeaşi linie.
Pentru un astfel de drum se calculează punctajul acestuia ca fiind egal cu suma valorilor
asociate celulelor prin care trece drumul.
Cerinţe
Cunoscând valorile asociate celulelor tabloului, scrieţi un program care determină punctajul
maxim care poate fi obţinut ı̂n acest joc.
Date de intrare
Fişierul de intrare joc.in va conţine pe prima linie două numere naturale n şi k separate
printr-un spaţiu cu semnificaţiile din enunţ. Pe fiecare dintre următoarele două linii se găsesc câte
n numere ı̂ntregi, reprezentând valorile asociate celor 2 n celule ale tabloului.
Date de ieşire
Fişierul de ieşire joc.out va conţine pe prima linie numărul ı̂ntreg p, reprezentând punctajul
maxim care se poate obţine.
Exemple
Observăm că starea curentă este caracterizată de parametrii s, i, j, d, c, unde i şi j reprezintă
linia, respectiv coloana, d este direcţia din care s-a intrat ı̂n celula actuală, c este numărul de celule
consecutive parcurse pe linia i până la coloana j, iar s este suma maximă obţinută. Observı̂nd
că o parcurgere exhaustivă a tuturor drumurilor duce inevitabil la trecerea repetată prin anumite
stări, se va evita recalcularea aceloraşi valori prin memorarea lor.
Alternativele sunt programare dinamică sau o abordare recursivă cu memoizare. Pentru aceste
tipuri de soluţie se primesc 100 puncte. Complexitatea aşteptată: O N K
Soluţii bazate pe backtracking, primesc ı̂ntre 20 şi 40 de puncte, ı̂n funcţie de optimizări.
CAPITOLUL 14. OJI 2010 14.2. JOC 266
#define sus 0
#define dr 1
#define jos 2
ifstream fin("joc.in");
ofstream fout("joc.out");
int a[2][5001];
int sol[2][5001];
int Smax = -99999;
int n;
int K, k;
void Read();
void Rec(int l, int c, int s);
int OK(int i, int j, int d);
int main()
{
Read();
k = 1;
Rec(0, 0, 0);
fout << Smax << ’\n’;
fout.close();
}
if ( sol[i][j] == 1 ) return 0;
return 1;
}
void Read()
{
fin >> n >> K;
for ( int i = 0; i < 2; ++i )
for ( int j = 0; j < n; ++j )
fin >> a[i][j];
fin.close();
int main()
{int n,k,i,j,j1,s,max,a[2][M],b[2][M];
FILE *f1,*f2;
f1=fopen("9-joc.in","r");
fscanf(f1,"%d %d",&n,&k);
for(i=0;i<2;i++)
for(j=0;j<n;j++) fscanf(f1,"%d",&a[i][j]);
fclose(f1);
b[0][0]=a[0][0]; b[1][0]=0;
for(i=1;i<n;i++)
for(j=0;j<2;j++)
{ max=-11111; s=a[j][i];
for(j1=1;j1<k&&j1<=i;j1++)
{ s+=a[j][i-j1];
if(s+b[1-j][i-j1]>max) max=s+b[1-j][i-j1];}
b[j][i]=max; }
f2=fopen("9-joc.out","wt");
if(b[0][n-1]+a[1][n-1]>b[1][n-1]) b[1][n-1]=b[0][n-1]+a[1][n-1];
fprintf(f2,"%d\n",b[1][n-1]);
fclose(f2);
return 0;
}
#include <vector>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <bitset>
#include <stack>
#include <list>
#include <numeric>
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <sstream>
#include <iomanip>
CAPITOLUL 14. OJI 2010 14.2. JOC 268
#include <cctype>
#include <cmath>
#include <ctime>
#include <cassert>
#define SZ size()
#define BG begin()
#define EN end()
#define CL clear()
#define X first
#define Y second
#define RS resize
#define PB push_back
#define MP make_pair
#define ALL(x) x.begin(), x.end()
int n, k;
VVI a;
vector <VVI> dp;
int Solve()
{
dp.RS(3, VVI(n + 1, VI(k + 1)));
dp[1][1][1] = a[1][1];
dp[2][1][1] = a[1][1] + a[2][1];
FOR(j, 2, n)
{
FOR(i, 1, 2)
FOR(q, 2, k)
{
if (q > j) break;
dp[i][j][q] = dp[i][j - 1][q - 1] + a[i][j];
}
FOR(i, 1, 2)
{
int maxVal = -INF;
FOR(q, 2, k)
{
if (q > j) break;
maxVal = max(maxVal, dp[3 - i][j][q]);
}
dp[i][j][1] = maxVal + a[i][j];
}
}
int ans = -INF;
FOR(q, 1, k) ans = max(ans, dp[2][n][q]);
return ans;
}
int main()
CAPITOLUL 14. OJI 2010 14.2. JOC 269
{
//Read data
fin >> n >> k;
a.RS(3, VI(n + 1));
FOR(i, 1, n) fin >> a[1][i];
FOR(i, 1, n) fin >> a[2][i];
fin.close();
//Write data
fout << Solve();
fout.close();
return 0;
}
#include <vector>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <bitset>
#include <stack>
#include <list>
#include <numeric>
#include <algorithm>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <cctype>
#include <cmath>
#include <ctime>
#include <cassert>
#define SZ size()
#define BG begin()
#define EN end()
#define CL clear()
#define X first
#define Y second
#define RS resize
#define PB push_back
#define MP make_pair
#define ALL(x) x.begin(), x.end()
CAPITOLUL 14. OJI 2010 14.2. JOC 270
int n, k;
VVI a;
int Solve()
{
int ans = 0, i = 1, j = 1, nCons = 1;
bool changeRow = false;
while (j < n)
{
ans += a[i][j];
if ((a[i][j + 1] > a[3 - i][j] || changeRow) && (nCons < k))
{
++j;
++nCons;
changeRow = false;
}
else
{
i = 3 - i;
nCons = 1;
changeRow = true;
}
}
ans += a[i][n];
if (i == 1) ans += a[2][n];
return ans;
}
int main()
{
//Read data
fin >> n >> k;
a.RS(3, VI(n + 1));
FOR(i, 1, n) fin >> a[1][i];
FOR(i, 1, n) fin >> a[2][i];
fin.close();
//Write data
fout << Solve();
fout.close();
return 0;
}
ifstream fin("9-joc.in");
ofstream fout("joc.out");
int a[2][5001];
int n;
int K;
int s[2][5001][3][11]; // s[l][c][dir][k] - suma maxima pana la linia l,
// coloana c, daca s-a intrat in
CAPITOLUL 14. OJI 2010 14.2. JOC 271
int main()
{
Read();
fout.close();
return 0;
}
if ( dir == sus )
r = Sum(l, c - 1, dr, k + 1);
}
if ( l == 0 )
{
if ( dir == dr )
if ( k < K )
r = max(Sum(l, c - 1, dr, k + 1), Sum(l + 1, c, sus, 1));
else
r = Sum(l + 1, c, sus, 1);
void Read()
{
int i(0), j(0);
fin >> n >> K;
for ( i = 0; i < 2; ++i )
for ( j = 0; j < n; ++j )
fin >> a[i][j];
fin.close();
CAPITOLUL 14. OJI 2010 14.2. JOC 272
OJI 2009
15.1 cerc
Problema 1 - cerc 100 de puncte
Se desenează n cercuri distincte ı̂n plan, numerotate cu numerele de la 1 la n. Pentru fiecare
cerc k (k " r1, 2, ..., nx) se cunosc: raza cercului, rk , şi coodonatele xk , yk ale centrului cercului,
coordonate referitoare la reperul cartezian xOy cu originea ı̂n punctul O din plan. Din punctul
O, se desenează m drepte distincte, astfel ı̂ncât pentru fiecare dreaptă, dintre cele m desenate, să
existe cel puţin un cerc, dintre cele n, al cărui centru să fie situat pe această dreaptă şi pentru
fiecare cerc desenat, să existe o singură dreaptă, dintre cele m desenate, care să treacă prin centrul
lui.
Cerinţe
Date de intrare
Date de ieşire
Fişierul de ieşire cerc.out va conţine o singură linie pe care se vor scrie cele trei numere
naturale m, q şi p, separate prin câte un spaţiu.
a 1 & n & 2000; 1 & x1 , x2 , ..., xn & 1000 ; 1 & y1 , y2 , ..., yn & 1000; 1 & r1 , r2 , ..., rn & 70
a dacă două cercuri, dintre cele n, au centrele ı̂n acelaşi punct din plan, atunci razele lor sunt
distincte
a două cercuri sunt exterioare dacă nu au niciun punct comun şi nici interioarele lor nu au
puncte comune
a Pentru rezolvarea cerinţei a) se acordă 20% din punctaj, pentru cerinţa b) 50% din punctaj
şi pentru cerinţa c) 30% din punctaj.
Exemple
273
CAPITOLUL 15. OJI 2009 15.1. CERC 274
Se sortează tabloul c, crescător după valorile câmpului x (abscisele centrelor cercurilor) şi după
tangenta unghiului dintre axa Ox şi dreapta care trece prin O şi centrele cercurilor prin Qsort.
Plecând de la primul cerc, stabilim care dintre cercurile memorate ı̂n c la dreapta celui curent,
au centru situat pe dreapta care trece prin centrul primului cerc. Două cercuri, de raze r1 respectiv
r2 , au centrele x1 , y1 şi x2 , y2 situate pe aceeaşi dreaptă d care trece prin punctul O dacă este
satisfăcută relaţia:
y2
y1
x1
x2 y2 x1 y1 x2
Pentru toate aceste cercuri se va memora ı̂n câmpul d acelaşi număr care reprezintă numărul
dreptei curente (pentru primul cerc, numărul este 1). Se procedează analog pentru toate cercurile
care nu au iniţializat câmpul d. La final, fiecare ck .d va memora un număr natural nenul cel
mult egal cu m.
Două cercuri, cu centrele x1, y1 şi x2, y2, şi razele r1, respectiv r2, sunt exterioare dacă
este satisfăcută relaţia:
Începând cu penultimul cerc, se determină pentru fiecare cerc i numărul maxim al cercurilor
j (j % i) cu centrele pe dreapta d care conţine şi centrul cercului i şi sunt exterioare două câte
două. Acest număr se va memora ı̂n ci.e (iniţial ci.e 1, i 1, 2, ..., n):
Cel mai mare număr memorat ı̂n câmpul e al tabloului c reprezintă numărul q maxim de
cercuri exterioare două câte două ale căror centre aparţin aceleiaşi drepte care trece prin punctul
O. Numărul componentelor tabloului c care memorează ı̂n câmpul e valoarea q reprezintă numărul
p al dreptelor care conţin centrele a câte q cercuri exterioare două câte două. (sursele cerc c.cpp
şi cerc p.pas obţin 100p).
O altă soluţie se poate obţine reducând cercurile la intervalele ı̂nchise rezultate prin inter-
sectarea cercurilor cu dreptele pe care se află centrele lor, obţinându-se pentru fiecare dreaptă un
şir de intervale ı̂nchise. Problema se reduce la a determina numărul maxim de intervale disjuncte
două câte două pentru fiecare dreaptă. Cu o abordare gen ”problema subşirului maximal”, se
obţine punctajul maxim (sursa cerc100.cpp). O determinare prin Greedy, abordare gen ”prob-
lema spectacolelor”, obţine punctajul maxim (sursa cerc g.cpp).
Cerinţe
Date de intrare
CAPITOLUL 15. OJI 2009 15.2. PROJECT MANAGEMENT 276
Exemple
pm.in pm.out
7 11
2353332 03
0 00
0 33
12 25
11 25
11 88
3345 89
13
OJI 2008
16.1 iepuri
Problema 1 - iepuri 100 de puncte
Un gospodar are N iepuri (pe care i-a numerotat de la 1 la N ) şi foarte mulţi morcovi. Ce
e mai deosebit ı̂n această gospodărie este că iepurii sunt organizaţi ierarhic, ı̂n funcţie de vârstă,
autoritate şi nevoile nutriţionale. Astfel, fiecare iepure are exact un şef direct (exceptându-l pe
Rilă Iepurilă, care este şeful cel mare, şeful tuturor iepurilor). Orice iepure poate avea 0, 1 sau
mai mulţi subordonaţi direcţi. Orice iepure-şef va mânca cel puţin un morcov mai puţin decât
fiecare dintre subordonaţii săi direcţi.
Gospodarul nu se poate hotărı̂ câţi morcovi să dea fiecărui iepure şi ar vrea să ştie ı̂n câte
moduri poate ı̂mpărţi morcovi la iepuri ştiind că fiecare iepure poate să mănânce minim un morcov
şi maxim K morcovi.
Cerinţe
Scrieţi un program care calculează numărul de posibilităţi modulo 30011 de a ı̂mpărţi morcovi
la cei N iepuri ştiind că orice iepure poate mânca ı̂ntre 1 şi K morcovi şi trebuie să mănânce cu
cel puţin un morcov mai puţin decât fiecare dintre iepurii care ı̂i sunt subordonaţi direcţi.
Date de intrare
Fişierul de intrare iepuri.in conţine:
- pe prima linie două numere naturale N şi K, separate printr-un spaţiu, reprezentând numărul
de iepuri, respectiv numărul maxim de morcovi ce pot fi mâncaţi de un iepure.
- pe fiecare din următoarele N 1 linii se află câte două numere naturale distincte a şi b,
cuprinse ı̂ntre 1 şi N , separate printr-un spaţiu, cu semnificaţia că iepurele a este şeful direct al
iepurelui b.
Date de ieşire
Fişierul de ieşiere iepuri.out va conţine numărul de moduri de a ı̂mpărţi morcovii conform
condiţiilor specificate ı̂n enunţ, modulo 30011.
Restricţii şi precizări
a 1 & N, K & 100
a Numărul ce trebuie scris ı̂n fişierul de ieşire va fi afişat modulo 30011.
Exemple
iepuri.in iepuri.out
94 9
72
73
74
35
36
58
59
61
Timp maxim de executare/test: 1.0 secunde
278
CAPITOLUL 16. OJI 2008 16.1. IEPURI 279
unsigned N, K, arb[NMAX][NMAX],
rad, p[NMAX];
void readinput()
{
unsigned a, b;
CAPITOLUL 16. OJI 2008 16.1. IEPURI 280
unsigned m[NMAX][KMAX];
void writeoutput()
{
unsigned sum = 0;
int main()
{
readinput();
go( rad );
writeoutput();
return 0;
}
ifstream f("iepuri.in");
ofstream fout("iepuri.out");
struct nod
{
int inf;
CAPITOLUL 16. OJI 2008 16.2. NUMAR 281
nod * urm;
};
nod * prim[nmax];
int t[nmax], i,n,k,rad;
long rez;
void citire()
{ int a,b;
f>>n>>k;
for(i=1;i<n;i++)
{
f>>a>>b;
nod *p= new nod;
p->inf=a;
p->urm=prim[b];
prim[b]=p;
nod * q=new nod;
q->inf=b;
q->urm=prim[a];
prim[a]=q;
t[b]=a;
}
}
int main()
{
citire();
rez = 0;
for (rad=1;rad<=n;rad++)
if (t[rad]==0)
break;
rez = df(rad,1);
fout<<rez<<endl;
return 0;
}
16.2 numar
Problema 2 - numar 90 de
puncte
Presupunem că avem n numere prime notate a1 , a2 , ..., an sortate strict crescător. Formăm un
şir strict crescător b ale cărui elemente sunt toţi multiplii acestor n numere prime astfel ı̂ncât,
multipli comuni apar o singură dată. Presupunem că numerotarea poziţiilor elementelor din şirul
b ı̂ncepe tot cu 1.
Cerinţe
CAPITOLUL 16. OJI 2008 16.2. NUMAR 282
Scrieţi un program care citeşte din fişierul de intrare valoarea lui n şi apoi cele n elemente ale
şirului a, determină elementul de pe poziţia m din şirul b şi afişează ı̂n fişierul de ieşire valoarea
acestuia.
Date de intrare
Date de ieşire
Fişierul de ieşire numar.out va conţine pe prima linie o singură valoare care reprezintă ter-
menul de pe poziţia m din şirul b.
Exemple
m x©a1 x©a2 ... x©an x© a1 a2 ... x© an1 an x© a1 a2 a3 ...
Noi ı̂l ştim pe m şi trebuie să ı̂l determinăm pe x. Aceasta se poate face prin căutarea binară
a lui x ı̂n intervalul 1..m a1 .
Totuşi ne lovim de următoarea problemă: trebuie să generăm produsele de la numitorii care
apar ı̂n expresia de mai sus. E evident că acestea se pot calcula generând o singură dată
submulţimile mulţimii ra1 , a2 , ..., an x şi reţinând valorile corespunzătoare produselor. Nu este
ı̂nsă necesar să generăm toate submulţimile ci doar pe cele care reprezintă produse mai mici decât
m a1 !!! Se poate demonstra sau observa că şi ı̂n cazul cel mai defavorabil, când şirul a este
format din cele mai mici numere prime iar m 15000, numărul de produse care ı̂ndeplinesc acest
criteriu este mai mic decât 10000. În consecinţă, vom utiliza un backtracking optimizat.
În final, trebuie să avem ı̂n vedere că prin căutarea binară s-ar putea să determinăm un număr
x1 % x, care să conducă la aceeaşi valoare a expresiei la care ne conduce şi x, dar care să nu fie ı̂n
şirul b !!
CAPITOLUL 16. OJI 2008 16.2. NUMAR 283
long a[1001];
long n,m,i,j,k,h,nr1,lim;
char ct[255001];
ifstream fi("numar.in");
ofstream fout("numar.out");
int main()
{ //cout<<endl;
fi>>n>>m;
for (i=1;i<=n;i++)
fi>>a[i];
for (k=1;k<=n;k++)
for (i=1;i*a[k]<=lim;i++)
ct[i*a[k]]=1;
j=0;
for (i=1;j!=m;i++)
if (ct[i]==1)
{
// cout<<i<<’ ’;
j++;
}
fout<<i-1<<endl;
return 0;
}
CAPITOLUL 16. OJI 2008 16.2. NUMAR 284
struct nod
{
int info;
int hi;
nod *st, *dr;
};
long a[1000],ct,n,m,sol,lim,mult;
int max2(int a, int b)
{ if (a>b) return a; return b;}
void rotst(nod*&x)
{ nod *y = x->dr;
x->dr = y->st;
y->st = x;
x->hi = 1+max2(high(x->st),high(x->dr));
y->hi = 1+max2(high(y->st),high(y->dr));
x = y;
}
void rotdr(nod*&y)
{ nod *x = y->st;
y->st = x->dr;
x->dr = y;
y->hi = 1+max2(high(y->st),high(y->dr));
x->hi = 1+max2(high(x->st),high(x->dr));
y = x;
}
void rotdst(nod*&x)
{
rotdr(x->dr);
rotst(x);
}
void rotddr(nod*&y)
{
rotst(y->st);
rotdr(y);
}
}
CAPITOLUL 16. OJI 2008 16.2. NUMAR 285
else
{ ins(p->st,vl);
}
}
void inord(nod* p)
{ if (p!=NULL)
{ inord(p->st);
ct++;
// cout<<p->info<<’ ’;
if (ct==m)
sol = p->info;
inord(p->dr);
}
}
int main()
{ int i;
nod *root=NULL;
ifstream f("numar.in");
ofstream fout("numar.out");
f>>n>>m;
for (i=0;i<n;i++)
f>>a[i];
lim = a[1]*m;
for (i=0;i<n;i++)
{ mult = a[i];
while (mult<=lim)
{ ins(root,mult);
mult += a[i];
}
}
inord(root);
fout<<sol<<endl;
fout.close();
return 0;
}
long a[1001];
long n,m,i,j,k,h,nr1,lim;
long sir1[10001],sir2[10001];
ifstream fi("numar.in");
ofstream fout("numar.out");
int main()
{ //cout<<endl;
fi>>n>>m;
for (i=1;i<=n;i++)
CAPITOLUL 16. OJI 2008 16.2. NUMAR 286
fi>>a[i];
k = n; //interclasare de la n la 1
while (a[k]>lim) k--;
for (i=1;i*a[k]<=lim;i++)
sir1[i]=i*a[k];
nr1 = i;
for (k=k-1;k>=1;k--)
{i=1;
j=1;
h=1;
while (h<=m && j*a[k]<=lim)
if (sir1[i]<j*a[k])
{sir2[h]=sir1[i];i++;h++;}
else
if (sir1[i]==j*a[k])
{sir2[h]=sir1[i];j++;i++;h++;}
else
{sir2[h]=j*a[k];j++;h++;}
nr1 = h-1;
for (i=1;i<=nr1;i++)
sir1[i] = sir2[i];
}
/* for (i=1;i<=m;i++)
cout<<sir1[i]<<’ ’;
*/
fout<<sir1[m]<<endl;
fout.close();
return 0;
}
long a[1001];
long st,dr,mj,vl,n,m,i,j,k,h,nr1,lim,ok,pro,np;
long prod[13000];
int s[1001];
long f(long x)
{ long vl = 0;
for (i=1;i<=np;i++)
if (prod[i]!=0)
vl = vl + x/prod[i];
else
cout<<"upsi";
return vl;
}
if (p==k)
{ pro=1;
for (j=1;j<=k;j++)
{ if (pro <= lim/a[s[j]])
pro = pro*a[s[j]];
else return;
//if (pro>lim) return;
}
if (pro<=lim)
{ np++;
//cout<<np<<’ ’;
if (k%2==1)
prod[np]=pro;
else
prod[np]=-pro;
}
else return;
}
else
back(p+1,pr*a[i]);
}
}
ifstream fi("numar.in");
ofstream fout("numar.out");
int main()
{ cout<<endl;
fi>>n>>m;
for (i=1;i<=n;i++)
fi>>a[i];
np=0;
for (k=1;k<=n;k++)
back(1,1);
st = 1;
dr = lim;
do
{ mj = (st+dr) / 2;
vl = f(mj);
if (vl==m)
{ //cout<<mj<<endl;
break;
}
if (vl<m) st = mj+1;
else dr = mj-1;
}
while (st<=dr);
for (k=mj;k>=0;k--)
{
for (i=1;i<=n;i++)
if (k%a[i]==0)
{ fout<<k<<endl;
return 0;
}
}
return 0;
}
OJI 2007
17.1 Numere
Exemple:
288
CAPITOLUL 17. OJI 2007 17.1. NUMERE 289
s1,s2,...,s9 b
Ab
s1!s2!...s9!
unde s1 este numărul de cifre de 1 care apar ı̂n a, s2 este numărul de cifre de 2 care apar ı̂n
a etc.
- plecând de la observaţia evidentă că numărul de cifre de 1 este mult mai mare ı̂n comparaţie
cu numărul celorlalte cifre (mai ales pentru valori mari ale lui b), se simplifică ı̂nainte de a ı̂ncepe
calculul propriu-zis ı̂n formula anterioară b! cu s1!.
- pentru a evita calcule repetate, ı̂nainte de apelarea subprogramului care implementează
bactracking-ul descris anterior, se precalculează ı̂n vectorul f act factorialele numerelor de la 2 la 13
(13 fiind factorialul maxim care apare la numitorul aranjamentelor cu repetiţii după simplificarea
descrisă mai sus), precum şi puterile cifrelor de la 1 la 13 ı̂n matricea pow.
int N, K, ndiv;
int nrpos1[MAX], nrpos2[MAX], diviz[MAX];
int main()
{
fscanf(f, "%d %d", &N, &K);
int ok = 1, i, j, p;
if(ok)
{
for(i = 1; i <= ndiv && diviz[i] <= 9 ; ++i)
nrpos1[diviz[i]] = 1;
for(i = 2; i <= N; ++i)
{
for(j = 1; j <= ndiv; ++j)
nrpos2[diviz[j]] = 0;
for(j = 1; j <= ndiv; ++j)
{
for(p = 1; p <= ndiv && diviz[p] <= 9; ++p)
if(diviz[j] % diviz[p] == 0)
nrpos2[diviz[j]] = (nrpos2[diviz[j]] +
nrpos1[diviz[j]/diviz[p]]) % CONST;
}
for(j = 1;j <= ndiv; ++j)
nrpos1[diviz[j]] = nrpos2[diviz[j]] % CONST;
return 0;
}
Varianta 1:
38 {
39 ok=false;
40 break;
41 }
42
43 for(i=1;i<=pc;++i) if((pc%i)==0) div[++ndiv]=i;
44
45 if(ok)
46 {
47 for(i=1; i<=ndiv && div[i]<=9 ; ++i) nrpos1[div[i]]=1;
48
49 for(i=2;i<=nc;++i)
50 {
51 for(j=1;j<=ndiv;++j) nrpos2[div[j]]=0;
52
53 for(j=1;j<=ndiv;++j)
54 {
55 for(p=1;p<=ndiv && div[p]<=9;++p)
56 if(div[j]%div[p]==0)
57 nrpos2[div[j]]=(nrpos2[div[j]]+nrpos1[div[j]/div[p]])%9973;
58 }
59
60 for(j=1;j<=ndiv;++j) nrpos1[div[j]]=nrpos2[div[j]]%9973;
61 }
62
63 rez=nrpos1[pc];
64 }
65 else rez=0;
66 }
67
68 public static void main(String[] args) throws IOException
69 {
70 st=new StreamTokenizer(new BufferedReader(new FileReader("numere.in")));
71 out=new PrintWriter(new BufferedWriter(new FileWriter("numere.out")));
72
73 citire();
74 solutia();
75
76 //System.out.println(rez);
77 out.println(rez);
78 out.close();
79 }// main
80 }// class
81 %\end{verbatim}
Varianta 2:
102 }
103
104 public static void main(String[] args) throws IOException
105 {
106 st=new StreamTokenizer(new BufferedReader(new FileReader("numere.in")));
107 out=new PrintWriter(new BufferedWriter(new FileWriter("numere.out")));
108
109 citire();
110 solutia();
111
112 System.out.println(rez);
113 out.println(rez);
114 out.close();
115 }// main
116 }// class
117 %\end{verbatim}
17.2 Cezar
În Roma antică există n aşezări senatoriale distincte, câte una pentru fiecare dintre cei n
senatori ai Republicii. Aşezările senatoriale sunt numerotate de la 1 la n, ı̂ntre oricare două
aşezări existând legături directe sau indirecte. O legătură este directă dacă ea nu mai trece
prin alte aşezări senatoriale intermediare. Edilii au pavat unele dintre legăturile directe dintre
două aşezări (numind o astfel de legătură pavată ”stradă”), astfel ı̂ncât ı̂ntre oricare două aşezări
senatoriale să existe o singură succesiune de străzi prin care se poate ajunge de la o aşezare
senatorială la cealaltă.
Toţi senatorii trebuie să participe la şedinţele Senatului. În acest scop, ei se deplasează cu
lectica. Orice senator care se deplasează pe o stradă plăteşte 1 ban pentru că a fost transportat
cu lectica pe acea stradă.
La alegerea sa ca prim consul, Cezar a promis că va dota Roma cu o lectică gratuită care să
circule pe un număr de k străzi ale Romei astfel ı̂ncât orice senator care va circula pe străzile
respective, să poată folosi lectica gratuită fără a plăti. Străzile pe care se deplasează lectica
gratuită trebuie să fie legate ı̂ntre ele (zborul, metroul sau teleportarea nefiind posibile la acea
vreme).
În plus, Cezar a promis să stabilească sediul sălii de şedinţe a Senatului ı̂ntr-una dintre aşezările
senatoriale aflate pe traseul lecticii gratuite. Problema este de a alege cele k străzi şi amplasarea
sediului sălii de şedinţe a Senatului astfel ı̂ncât, prin folosirea transportului gratuit, senatorii, ı̂n
drumul lor spre sala de şedinţe, să facă economii cât mai ı̂nsemnate. În calculul costului total de
transport, pentru toţi senatorii, Cezar a considerat că fiecare senator va călători exact o dată de
la aşezarea sa până la sala de şedinţe a Senatului.
Cerinţă
Scrieţi un program care determină costul minim care se poate obţine prin alegerea adecvată
a celor k străzi pe care va circula lectica gratuită şi a locului de amplasare a sălii de şedinţă a
Senatului.
Date de intrare
Fişierul cezar.in conţine
a pe prima linie două valori n k separate printr-un spaţiu reprezentând numărul total de
senatori şi numărul de strazi pe care circulă lectica gratuită
a pe următorele n 1 linii se află câte două valori i j separate printr-un spaţiu, reprezentând
numerele de ordine a două aşezări senatoriale ı̂ntre care există stradă.
Date de ieşire
Pe prima linie a fişierului cezar.out se va scrie costul total minim al transportării tuturor
senatorilor pentru o alegere optimă a celor k străzi pe care va circula lectica gratuită şi a locului
unde va fi amplasată sala de şedinţe a Senatului.
Restricţii şi precizări
a 1 $ n & 10000, 0 $ k $ n
a 1 & i, j & n , i j j
a Oricare două perechi de valori de pe liniile 2, 3, ..., n din fişierul de intrare reprezintă două
străzi distincte.
CAPITOLUL 17. OJI 2007 17.2. CEZAR 294
a Perechile din fişierul de intrare sunt date astfel ı̂ncât respectă condiţiile din problemă.
a Pentru 25% din teste n & 30, pentru 25% din teste 30 $ n & 1000, pentru 25% din teste
1000 $ n & 3000, pentru 10% din teste 3000 $ n & 5000, pentru 10% din teste 5000 $ n & 10000.
Exemplu
struct NOD
{
int nod;
NOD* next;
};
CAPITOLUL 17. OJI 2007 17.2. CEZAR 295
NOD *dp[10000];
long s; int *c,n,k,h[10001],lh;
void citire()
{int i,x,y;
ifstream f("cezar.in");
f>>n>>k;
c=new int[10000];
for (i=0;i<n;i++){
*(c+i)=1;dp[i]=0;h[i]=n;
}
for (i=0;i<n-1;i++){
f>>x>>y;
add(dp[x-1],y-1);add(dp[y-1],x-1);
}
for (i=0;i<n;i++)
if (!dp[i]->next)h[++lh]=i;
*(c+n)=20000;h[n]=n;
f.close();
}
void desfrunzire()
{ int ii,i,j,pmin;
long min;
s=0;
for (ii=n-1;ii>=k+1;ii--)
{
pmin=h[1];s+=*(c+pmin);
j=dp[pmin]->nod;
*(c+j)+=*(c+pmin);
remove(dp[j],pmin);
if(!dp[j]->next)h[1]=j;
else {h[1]=h[lh];h[lh]=n;lh--;}
i=1;
while (2*i<=lh)
{
if ( *(c+h[2*i]) < *(c+h[2*i+1])) j=2*i; else j=2*i+1;
if ( *(c+h[i]) > *(c+h[j]))
{int aux=h[i];h[i]=h[j];h[j]=aux;i=j;}
else i=n;
}
}
}
int main()
{ citire();
desfrunzire();
ofstream f("cezar.out");
f<<s<<’\n’;
f.close();
CAPITOLUL 17. OJI 2007 17.2. CEZAR 296
return 0;
}
Varianta 1a: Cu test incorect, dar care obţine 17 rezultate corecte din 20.
62 else
63 if( (v2[k]==i) && (g[v1[k]] > 0)) {t=v1[k]; break;}
64 }
65 return t;
66 }
67
68 static void eliminm() // frunze(g=1) cu cost=min
69 {
70 int i, imin, timin;
71 int min;
72
73 min=Integer.MAX_VALUE;
74 imin=-1; // initializare aiurea ...
75 for(i=1;i<=n;i++) // mai bine cu heapMIN ...
76 if(g[i]==1) // i=frunza
77 if(ca[i]+nde[i]<min) // aici este testul INCORECT ...
78 {
79 min=ca[i]+nde[i];
80 imin=i;
81 }
82
83 timin=tata(imin);
84
85 g[imin]--; g[timin]--;
86
87 ca[timin]=ca[timin]+ca[imin]+nde[imin]+1;
88 nde[timin]=nde[timin]+nde[imin]+1;
89
90 //System.out.println(" Elimin nodul "+imin+" timin = "+timin+
91 // " ca = "+ca[timin]+" nde = "+nde[timin]);
92 }// elimin()
93
94 public static void main(String[] args) throws IOException
95 {
96 int k,senat=0,cost=0;
97 st=new StreamTokenizer(new BufferedReader(new FileReader("cezar20.in")));
98 out=new PrintWriter(new BufferedWriter(new FileWriter("cezar.out")));
99
100 citire();
101
102 for(k=1;k<=n-1-ns;k++)
103 {
104 //System.out.print(k+" : ");
105 eliminm();
106 }
107
108 //afisv(c,1,n);
109
110 for(k=1;k<=n;k++)
111 if(g[k]>0)
112 {
113 cost+=ca[k];
114 senat=k;
115 }
116
117 System.out.println("\n"+cost+" "+senat);
118
119 out.println(cost+" "+senat);
120 out.close();
121 }// main
122 }// class
85
86 System.out.println("\n"+cost+" "+senat);
87 out.println(cost+" "+senat);
88 out.close();
89 //afisv(g,1,n);
90 }// main
91 }// class
Varianta 2:
63 return t;
64 }
65
66 static void eliminm() // frunze(g=1) cu cost=min
67 {
68 int i, imin, timin;
69 int min;
70
71 min=Integer.MAX_VALUE;
72 imin=-1; // initializare aiurea ...
73 for(i=1;i<=n;i++) // mai bine cu heapMIN ...
74 if(g[i]==1) // i=frunza
75 if(ca[i]<min) // cost acumulat
76 {
77 min=ca[i];
78 imin=i;
79 }
80
81 timin=tata(imin);
82
83 g[imin]--; g[timin]--;
84
85 ca[timin]=ca[timin]+min;
86 s+=min;
87
88 //System.out.println(" Elimin nodul "+imin+
89 // " timin = "+timin+" ca = "+ca[timin]);
90 }// elimin()
91
92 public static void main(String[] args) throws IOException
93 {
94 int k,senat=0;
95 st=new StreamTokenizer(new BufferedReader(new FileReader("cezar20.in")));
96 out=new PrintWriter(new BufferedWriter(new FileWriter("cezar.out")));
97
98 citire();
99
100 for(k=1;k<=n-1-ns;k++)
101 {
102 eliminm();
103 }
104
105 //afisv(c,1,n);
106
107 for(k=1;k<=n;k++)
108 if(g[k]>0)
109 {
110 senat=k;
111 break;
112 }
113
114 System.out.println("\n"+s+" "+senat);
115
116 out.println(s+" "+senat);
117 out.close();
118 }// main
119 }// class
Capitolul 18
OJI 2006
18.1 Graf
Victor Manz
Se ştie că ı̂ntr-un graf neorientat conex, ı̂ntre oricare două vârfuri există cel puţin un
lanţ iar lungimea unui lanţ este egală cu numărul muchiilor care-l compun. Definim noţiunea lanţ
optim ı̂ntre două vârfuri X şi Y ca fiind un lanţ de lungime minimă care are ca extremităţi vrfurile
X şi Y. Este evident că ı̂ntre oricare două vârfuri ale unui graf conex vom avea unul sau mai multe
lanţuri optime, depinzând de configuraţia grafului.
Cerinţă:
Fiind dat un graf neorientat conex cu N vârfuri etichetate cu numerele de ordine 1,2, ...,N şi
două vârfuri ale sale notate X şi Y (1 & X, Y & N, X j Y ), se cere să scrieţi un program care
determină vârfurile care aparţin tuturor lanţurilor optime dintre X şi Y.
Date de intrare:
Fişierul graf.in conţine
- pe prima linie patru numere naturale reprezentând: N (numărul de vârfuri ale grafului), M
(numărul de muchii), X şi Y (cu semnificaţia din enunţ).
- pe următoarele M linii câte două numere naturale nenule Ai , Bi (1 & Ai, Bi & N , Ai j Bi ,
pentru 1 & i & M ) fiecare dintre aceste perechi de numere reprezentând câte o muchie din graf.
Date de ieşire
Fişierul graf.out va conţine
- pe prima linie, numărul de vârfuri comune tuturor lanţurilor optime dintre X şi Y;
- pe a doua linie, numerele corespunzătoare etichetelor acestor vârfuri, dispuse ı̂n ordine
crescătoare; ı̂ntre două numere consecutive de pe această linie se va afla câte un spaţiu.
Restricţii
a 2 & N & 7500; 1 & M & 14000
a pentru 50% din teste N & 200
Exemple
graf.in graf.out graf.in graf.out
6714 3 3213 2
12 145 12 13
13 31
16
25
35
56
54
Timp maxim de execuţie/test: 1 secundă.
Soluţia oficială
301
CAPITOLUL 18. OJI 2006 18.1. GRAF 302
I Dacă definim distanţa ı̂ntre două vârfuri ale unui graf neorientat ca fiind lungimea celui
mai scurt lanţ dintre lanţurile care au drept capete vârfurile, atunci putem să observăm că un
vârf oarecare Z se află pe un lanţ de lungime minimă dintre X şi Y dacă şi numai dacă d X, Z
d Z, Y d X, Y , pentru cazul ı̂n care considerăm lungimea lanţului ca fiind numărul muchiilor
şi d X, Z d Z, Y d X, Y 1, pentru cazul ı̂n care considerăm lungimea ca fiind numărul
vârfurilor.
Stabilim prin câte o parcurgere ı̂n lăţime distanţele tuturor vârfurilor faţă de X şi respectiv Y
(capetele lanţului, citite din fişier). Vedem care dintre vârfurile ce aparţin cel puţin unui lanţ de
lungime minimă ı̂ntre X şi Y au proprietatea că sunt singurele aflate la o anumită distanţă de X.
Acestea sunt vârfurile care aparţin tuturor lanţurilor de lungime minimă dintre X şi Y .
Algoritmul are complexitate O n m.
II Facem o parcurgere ı̂n lăţime din X şi o parcurgere ı̂n lăţime pornind din Y , ı̂n urma cărora
determinăm pentru fiecare vârf z distanta dintre X şi z, şi Y şi z - notate d X, z şi respectiv
d Y, z şi numărul de drumuri optime dintre X şi z, notat nr X, z şi dintre Y şi z - nr Y, z .
Un vârf z are proprietatea de a aparţine tuturor drumurilor optime dintre X şi Y dacă şi numai
dacă:
d X, z d Y, z d X, Y şi nr X, z nr Y, z nr X, y .
Această soluţie este ı̂nsă dificil de implementat pentru grafuri cu un numar mare de noduri -
se poate ajunge la operaţii cu numere mari;
III Calculăm lungimea minimă a unui lanţ de la X la Y . Eliminam apoi succesiv câte un
nod din arbore (cu excepţia nodurilor X şi Y ) şi recalculăm lungimea minimă a unui lanţ de la
X la Y . Dacă această lungime diferă faţă de cea iniţială, rezultă că toate lanţurile de lungime
minimă trec prin nodul eliminat. Soluţie de complexitate O n n m care rezolvă corect 50%
din teste.
IV Se pot da şi solutii bazate pe backtracking, de exemplu:
- se determină (printr-o parcurgere ı̂n lăţime a grafului) numărul de vârfuri aflate pe lanţul de
lungime minimă dintre X şi Y ; fie K numărul de vârfuri ”intermediare” (fară X şi Y );
- generăm toate lanţurile de lungime minimă dintre X şi Y (inclusiv capetele) (algoritm
de generare a aranjamentelor de n luate câte K cu verificarile corespunzatoare de adiacenţă)
çontorizăm pentru fiecare vârf numărul de lanţuri ı̂n care apare;
- vârfurile care formează soluţia vor avea contorul egal cu contorii vârfurilor X şi Y .
În funcţie de implementare şi de optimizările aduse algoritmilor de backtracking, programele
bazate pe astfel de soluţii pot primi maxim 40 puncte.
char a[nmax][nmax];
int s[nmax+1],ct[nmax+1],n,k,m,lung,coada[nmax+1],
beginn,endd,v1,v2,dist[nmax+1];
int out()
{ beginn++; return coada[beginn-1];}
int isempty()
{return beginn==endd;}
void parc()
{ int i;
int vf = out();
// cout<<vf<<’ ’;
for(i=1;i<=n;i++)
if (a[vf][i]==1 && ct[i]==0)
CAPITOLUL 18. OJI 2006 18.1. GRAF 303
{ dist[i]=dist[vf]+1;
ct[i]++;in(i);
}
if (!isempty())
parc();
}
void print()
{ int i;
for (i=0;i<=k;i++)
{ //cout<<s[i]<<’ ’;
ct[s[i]]++;
}
// cout<<v2<<endl;
}
int valid(int p)
{ int i;
if(a[s[p]][s[p-1]]==0) return 0;
for(i=0;i<p;i++)
if (s[p]==s[i]) return 0;
if (p==k && a[s[p]][v2]==0) return 0;
return 1;}
void back(int p)
{ int i;
for (i=1;i<=n;i++)
{s[p]=i;
if (valid(p))
if (p==k)
print();
else
back(p+1);
}
}
int main()
{ ifstream f("graf.in");
int i,i1,i2;
f>>n>>m>>v1>>v2;
for (i=1;i<=m;i++)
{ f>>i2>>i1;
a[i1][i2]=a[i2][i1]=1;
}
in(v1);
ct[v1]=1;
parc();
// for (i=1;i<=n;i++)
// cout<<pred[i]<<’ ’;
// cout<<endl;
k=dist[v2];
// cout<<k<<endl;
k--;
memset(ct,0,nmax*sizeof(int));
s[0]=v1;
back(1);
ofstream fo("graf.out");
int kt=0;
ct[v2]=ct[v1];
for (i=1;i<=n;i++)
if(ct[v1]==ct[i] )
kt++;
fo<<kt<<endl;
for (i=1;i<=n;i++)
if(ct[v1]==ct[i])
fo<<i<<’ ’;
return 0;
}
nod v[nmax];
int *dx, *dy, *num, *vertex, *at, l[nmax];
int n,m,x,y,i,j,xx,yy,nr;
nod p;
ifstream fi("graf.in");
int main()
{
fi>>n>>m>>x>>y;
for (i=1;i<=m;i++)
{ fi>>xx>>yy;
p = new node;
p->x = yy;
p->next = v[xx];
v[xx] = p;
p = new node;
p->x = xx;
p->next = v[yy];
v[yy] = p;
}
dx = new int[n+1];
bfs(x, dx);
dy = new int[n+1];
bfs(y,dy);
num = new int[n+1];
for (i=0;i<=n;i++)
num[i]=0;
for (i=0;i<=n;i++)
vertex[i]=0;
for (i=1;i<=n;i++)
if (dx[i] + dy[i] == dx[y]+1)
{ num[dx[i]]++;
vertex[dx[i]] = i;
}
CAPITOLUL 18. OJI 2006 18.1. GRAF 305
at = new int[n+1];
for (i=0;i<=n;i++)
at[i]=0;
nr = 0;
for (i=1;i<=n;i++)
if (num[i]==1)
{ nr++;
at[vertex[i]]=1;
}
ofstream fo("graf.out");
fo<<nr<<endl;
for (i=1;i<=n;i++)
if (at[i]==1)
fo<<i<<’ ’;
return 0;
}
Varianta 1:
48
49 ai=new int[2*m+1];
50 ne=new int[n+1];
51
52 for(k=1;k<=m;k++)
53 {
54 st.nextToken(); i=(int)st.nval;
55 st.nextToken(); j=(int)st.nval;
56
57 v1[2*k-1]=i; v2[2*k-1]=j;
58 v1[2*k]=j; v2[2*k]=i;
59 }
60 }
61
62 static void calculd(int[] d, int nods) // nodstart
63 {
64 int ic, sc, k, z;
65
66 for(k=1;k<=n;k++) d[k]=-1;
67
68 q[0]=nods;
69 d[nods]=0;
70
71 ic=0;
72 sc=1;
73
74 while(ic != sc)
75 {
76 z=q[ic];
77 q[ic]=0;
78 ic++;
79
80 for(k=ai[z]; k<=ai[z]+ne[z]-1; k++)
81 {
82 if(d[v2[k]] == -1)
83 {
84 q[sc]=v2[k];
85 sc++;
86 d[v2[k]]=1+d[z];
87 }// if
88 }// for
89 }// while
90 }// calculd()
91
92
93 static void hs(int[] x, int[] y, int n)
94 {
95 int t,f,fs,fd,aux,k;
96
97 // construiesc heap
98
99 for(k=2;k<=n;k++)
100 {
101 // urc ...
102 f=k;
103 t=f/2;
104 while(t>0)
105 {
106 if(x[t]<x[f])
107 {
108 aux=x[t]; x[t]=x[f]; x[f]=aux;
109 aux=y[t]; y[t]=y[f]; y[f]=aux;
110 f=t;
111 t=f/2;
112 }
113 else break;
114 }//while
115 }
116
117 // sortez: 1) permut varaf<-->ultimul; 2) cobor ...
118
119 for(k=n-1;k>=1;k--) // k = dim heap dupa x[1]<-->x[k+1]
120 {
121 // max <--> la coada ...
122 aux=x[1]; x[1]=x[k+1]; x[k+1]=aux;
123 aux=y[1]; y[1]=y[k+1]; y[k+1]=aux;
CAPITOLUL 18. OJI 2006 18.1. GRAF 307
124
125 // cobor ... f=max(fs,fd) ... daca exista fd ...
126 t=1;
127 fs=2; fd=3;
128
129 f=fs;
130 if(fd<=k) if(x[fs]<x[fd]) f=fd;
131
132 while(f<=k)
133 {
134 if(x[t]<x[f])
135 {
136 aux=x[t]; x[t]=x[f]; x[f]=aux;
137 aux=y[t]; y[t]=y[f]; y[f]=aux;
138
139 t=f;
140
141 if(t>0x7fffff/2) break; // fs > MAXINT ...
142
143 fs=2*t; fd=fs+1;
144 f=fs;
145
146 if(fs<0x7fffff) // ca sa existe fd ...
147 if(fd<=k) if(x[fs]<x[fd]) f=fd;
148 }
149 else break;
150 }// while
151 }// for k
152 }
153
154 static void calcaine()
155 {
156 int i,j,k;
157
158 ai[v1[1]]=1;
159 ne[v1[1]]=1;
160
161 for(k=2;k<=2*m;k++)
162 if(v1[k]==v1[k-1]) ne[v1[k]]++;
163 else
164 {
165 ai[v1[k]]=k;
166 ne[v1[k]]=1;
167 }
168 }
169
170 static void solutia()
171 {
172 int i;
173
174 fdx=new int[n]; // distante = 0,1,...,n-1 !!!
175 vdm=new int[n+1]; // varf pe drum minim x...y
176
177 for(i=0;i<=n-1;i++) fdx[i]=0;
178 for(i=1;i<=n;i++) vdm[i]=0;
179
180 for(i=1;i<=n;i++)
181 if(dx[i]+dy[i] == dx[y])
182 {
183 fdx[dx[i]]++;
184 vdm[i]=1;
185 }
186
187 //afisv(fdx,0,n-1);
188 //afisv(vdm,1,n);
189
190 nv=0;
191 for(i=0;i<=n-1;i++) if(fdx[i]==1) nv++;
192
193 out.println(nv);
194
195 for(i=1;i<=n;i++)
196 if((vdm[i]==1)&&(fdx[dx[i]]==1))
197 {
198 out.print(i+" ");
199 }
CAPITOLUL 18. OJI 2006 18.1. GRAF 308
200
201 out.println();
202 }
203
204 public static void main(String[] args) throws IOException
205 {
206 st=new StreamTokenizer(new BufferedReader(new FileReader("graf.in")));
207 out=new PrintWriter(new BufferedWriter(new FileWriter("graf.out")));
208
209 citire();
210
211 //afisv(v1,1,2*m);
212 //afisv(v2,1,2*m);
213
214 hs(v1,v2,2*m);
215
216 //afisv(v1,1,2*m);
217 //afisv(v2,1,2*m);
218
219 calcaine();
220
221 //afisv(ai,1,n);
222 //afisv(ne,1,n);
223
224 q=new int[n];
225
226 calculd(dx,x);
227 calculd(dy,y);
228
229 //afisv(dx,1,n);
230 //afisv(dy,1,n);
231
232 solutia();
233
234 out.close();
235 }// main
236 }// class
Varianta 2:
33
34 static void citire() throws IOException
35 {
36 int i,j,k;
37
38 st.nextToken(); n=(int)st.nval;
39 st.nextToken(); m=(int)st.nval;
40 st.nextToken(); x=(int)st.nval;
41 st.nextToken(); y=(int)st.nval;
42
43 v1=new int[2*m+1];
44 v2=new int[2*m+1];
45
46 gi=new int[n+1];
47 ge=new int[n+1];
48
49 for(i=1;i<=n;i++) gi[i]=ge[i]=0;
50
51 for(k=1;k<=m;k++)
52 {
53 st.nextToken(); i=(int)st.nval;
54 st.nextToken(); j=(int)st.nval;
55 v1[k]=i; v2[k]=j;
56 gi[j]++; ge[i]++;
57 }
58
59 a=new int[n+1][1]; // apoi modific 1 ...
60
61 for(i=1;i<=n;i++)
62 {
63 a[i]=new int[gi[i]+ge[i]+1]; // am modificat ...
64 a[i][0]=0;
65 }
66
67 for(k=1;k<=m;k++)
68 {
69 i=v1[k]; j=v2[k];
70 a[i][0]++; a[j][0]++;
71
72 a[i][a[i][0]]=j;
73 a[j][a[j][0]]=i;
74 }
75 }// citire(...)
76
77 static void calculd(int[] d, int nods) // nodstart
78 {
79 int ic, sc, k, z;
80
81 for(k=1;k<=n;k++) d[k]=-1;
82
83 q[0]=nods;
84 d[nods]=0;
85
86 ic=0;
87 sc=1;
88
89 while(ic != sc)
90 {
91 z=q[ic];
92 q[ic]=0;
93 ic++;
94
95 for(k=1; k<=a[z][0]; k++)
96 {
97 if(d[a[z][k]] == -1)
98 {
99 q[sc]=a[z][k];
100 sc++;
101 d[a[z][k]]=1+d[z];
102 }// if
103 }// for
104 }// while
105 }// calculd()
106
107 static void solutia()
108 {
CAPITOLUL 18. OJI 2006 18.1. GRAF 310
109 int i;
110
111 fdx=new int[n]; // distante = 0,1,...,n-1 !!!
112 vdm=new int[n+1]; // varf pe drum minim x...y
113
114 for(i=0;i<=n-1;i++) fdx[i]=0;
115 for(i=1;i<=n;i++) vdm[i]=0;
116
117 for(i=1;i<=n;i++)
118 if(dx[i]+dy[i] == dx[y])
119 {
120 //System.out.println("i = "+i+" dx[i] = "+dx[i]);
121 fdx[dx[i]]++;
122 vdm[i]=1;
123 }
124
125 //afisv(fdx,0,n-1);
126 //afisv(vdm,1,n);
127
128 nv=0;
129 for(i=0;i<=n-1;i++) if(fdx[i]==1) nv++;
130
131 out.println(nv);
132
133 for(i=1;i<=n;i++)
134 if((vdm[i]==1)&&(fdx[dx[i]]==1))
135 {
136 out.print(i+" ");
137 }
138
139 out.println();
140 }
141
142 public static void main(String[] args) throws IOException
143 {
144 st=new StreamTokenizer(new BufferedReader(new FileReader("graf.in")));
145 out=new PrintWriter(new BufferedWriter(new FileWriter("graf.out")));
146
147 citire();
148
149 //afisv(v1,1,2*m);
150 //afisv(v2,1,2*m);
151
152 q=new int[n];
153
154 calculd(dx,x);
155 calculd(dy,y);
156
157 //afisv(dx,1,n);
158 //afisv(dy,1,n);
159
160 solutia();
161
162 out.close();
163 }// main
164 }// class
Varianta 3:
90 sc=1;
91
92 while(ic != sc)
93 {
94 z=q[ic];
95 q[ic]=0;
96 ic++;
97
98 for(k=1; k<=a[z][0]; k++)
99 {
100 if(d[a[z][k]] == -1)
101 {
102 q[sc]=a[z][k];
103 sc++;
104 d[a[z][k]]=1+d[z];
105 }// if
106 }// for
107 }// while
108 }// calculd()
109
110 static void solutia()
111 {
112 int i;
113
114 fdx=new int[n]; // distante = 0,1,...,n-1 !!!
115 vdm=new int[n+1]; // varf pe drum minim x...y
116
117 for(i=0;i<=n-1;i++) fdx[i]=0;
118 for(i=1;i<=n;i++) vdm[i]=0;
119
120 for(i=1;i<=n;i++)
121 if(dx[i]+dy[i] == dx[y])
122 {
123 //System.out.println("i = "+i+" dx[i] = "+dx[i]);
124 fdx[dx[i]]++;
125 vdm[i]=1;
126 }
127
128 //afisv(fdx,0,n-1);
129 //afisv(vdm,1,n);
130
131 nv=0;
132 for(i=0;i<=n-1;i++) if(fdx[i]==1) nv++;
133
134 out.println(nv);
135
136 for(i=1;i<=n;i++)
137 if((vdm[i]==1)&&(fdx[dx[i]]==1))
138 {
139 out.print(i+" ");
140 }
141
142 out.println();
143 }
144
145 public static void main(String[] args) throws IOException
146 {
147 st=new StreamTokenizer(new BufferedReader(new FileReader("graf.in")));
148 out=new PrintWriter(new BufferedWriter(new FileWriter("graf.out")));
149
150 citire();
151
152 //afisv(v1,1,2*m);
153 //afisv(v2,1,2*m);
154
155 q=new int[n];
156
157 calculd(dx,x);
158 calculd(dy,y);
159
160 //afisv(dx,1,n);
161 //afisv(dy,1,n);
162
163 solutia();
164
165 out.close();
CAPITOLUL 18. OJI 2006 18.2. CIFRU 313
166 }
167 }
18.2 Cifru
Stelian Ciurea
Un criptolog amator ı̂şi propune să construiască o maşină de cifrat care să cripteze un
text alcătuit din exact N simboluri distincte. Cifrarea se realizează prin permutarea simbolurilor
ce formează textul.
Criptologul nostru doreşte ca reconstituirea textului iniţial să poată fi realizată trecând textul
cifrat ı̂ncă de K 1 ori prin procedura de cifrare. Cu alte cuvinte, dacă textul rezultat din prima
cifrare este cifrat ı̂ncă o dată, rezultatul este cifrat din nou şi aşa mai departe, plecând de la textul
iniţial şi aplicând ı̂n total K operaţii de cifrare successive, trebuie să obţină textul iniţial.
Criptologul nostru ar vrea să afle, cunoscând N şi K, numărul de moduri distincte ı̂n care
poate fi realizată maşina de cifrat. Două moduri de realizare a maşinii diferă dacă, există cel
puţin un text ı̂n urma cifrării căruia, ı̂n cele două texte obţinute există cel puţin o poziţie ı̂n care
se află simboluri diferite.
Cerinţă
Scrieţi un program care determină restul ı̂mpărţirii numărului de moduri distincte ı̂n care
poate fi realizată maşina de cifrat la 19997.
Date de intrare
Fişierul cifru.in conţine pe prima (şi singura) linie, două valori numerice naturale separate
printr-un spaţiu, N şi K (cu semnificaţia din enunţ).
Date de ieşire
Fişierul cifru.out va conţine pe prima linie, numărul de moduri distincte de realizare a maşinii
de cifrat modulo 19997.
Restricţii
Exemple
Soluţia oficială
I Problema se poate reformula astfel: fie mulţimea r1, 2, ..., nx şi fie σ o permutare a acestei
mulţimi care ı̂ndeplineşte condiţia:
- Pentru ı̂nceput, vom da soluţia pentru un caz particular al problemei şi anume pentru K 3:
să notăm cu f n numărul acestor permutări, şi calculăm ı̂n câte moduri 1 (primul element al
mulţimii) poate să revină pe prima poziţie după K permutări. Există două posibilităţi şi anume:
CAPITOLUL 18. OJI 2006 18.2. CIFRU 314
1) σ 1 1 şi atunci evident că şi după K permutări successive 1 va rămâne pe prima poziţie;
ı̂n acest caz, pentru celelalte n 1 elemente ale mulţimii vor exista f n 1 permutări care
ı̂ndeplinesc condiţia cerută;
2) elementul 1 formează un ciclu de lungime trei (evident cu ı̂ncă alte două elemente din
mulţime, fie acestea a şi b. Astfel σ 1 a, σ a b, σ b 1 şi atunci aceste trei elemente revin
pe poziţiile lor după trei permutări successive. În acest caz, cele două elemente a şi b putem să le
2
alegem ı̂n n 1 n 2 An moduri, iar pentru restul de n 3 elemente ale mulţimii vom avea
f n 3 permutări care corespund cerinţei din enunţ.
Având ı̂n vedere aceste două posibilităţi putem scrie următoarea relaţie de recurenţă:
- Dacă K nu este număr prim, atunci raţionamentul anterior trebuie efectuat pentru fiecare divizor
a lui K.
De exemplu, dacă K 6, atunci elementul 1 poate să revină pe prima poziţie după K permutări
succesive dacă
- σ 1 1;
- elementul 1 formează un ciclu de lungime 2 (atunci revine pe prima poziţie după două
permutări, după patru permutări şi ı̂n final după şase permutări); celălalt element al acestui
1
ciclu poate fi ales ı̂n An iar pentru mulţimea formată din celelalte n 2 elemente avem f n 2
permutări;
- elementul 1 formează un ciclu de lungime 3 (atunci revine pe prima poziţie după trei permutări
şi după şase permutări);
- elementul 1 formează un ciclu de lungime 6 (atunci revine pe prima poziţie dupa şase per-
mutări;
Obţinem astfel pentru cazul n 6, relaţia:
sau
1 2 5
f n f n 1 An f n 2 An f n 3 An f n 6
şi
f n 1pentrun & 1
Generalizâd, putem scrie recurenţa
= A f
k
d 1
f n f n 1 n 1 n d
d 2
pentru toţii divizorii d ai lui K care sunt mai mici sau egali cu n.
Programul constă ı̂n implementarea acestei formule. Pentru a obţine punctaj maxim, trebuie
făcute âteva optimizări cum ar fi:
d2 d1
- calculul An1 unde d2 este un divizor a lui K trebuie făcut plecând de la valoarea An1
calculată ı̂n prealabil, unde d1 $ d2 şi d1 divizor al lui K;
- reţinerea valorilor calculate pentru funcţia f ı̂ntr-un vector, pentru a evita calcularea ı̂n mod
repetat a acestora.
Fără aceste optimizări, un program care implementează soluţia descrisă primeşte 50 puncte.
II O soluţie bazată pe backtracking, care generează toate permutările şi verifică pentru fiecare
dacă ı̂ndeplineşte condiţia din enunţ este corectă, dar se n̂cadrează ı̂n timpul maxim de rulare/test
doar pentru valori mici ale lui n şi k. Pentru datele de test propuse, o astfel de soluţie primeşte
20 puncte;
III Se pot lua 30 puncte cu o astfel de soluţie dacă se rulează programul ı̂n timpul concursului
petru toate combinaţiile posibile sugerate de observaţia că
ifstream fi("cifru.in");
ofstream fo("cifru.out");
long k,n;
long m[5005];
long f(int n)
{ long d,i,rez=0,t;
if (n<k)
{if (n<=1) return 1;
if (m[n]!=0) return m[n];
long da = 1, p=1;
for (d=2;d<=n;d++)
if (k%d==0)
{for(i=n-da;i>=n-d+1;i--)
p = (p*i) % prim;
t = p*f(n-d);
rez = (rez + t) % prim;
da = d;
}
return m[n] = (f(n-1) + rez) % prim;
}
int main()
{ fi>>n>>k;
fo<<f(n);
return 0;
}
Varianta 1:
Varianta 2:
Listing 18.2.3: cifru2.java
1 import java.io.*;
2 class cifru2
3 {
4 static StreamTokenizer st;
5 static PrintWriter out;
6
7 static int[] x=new int[5005];
8 static int n, k, rezsol, prim=19997;
9
10 public static void main(String[] args) throws IOException
11 {
12 st=new StreamTokenizer(new BufferedReader(new FileReader("cifru.in")));
13 out=new PrintWriter(new BufferedWriter(new FileWriter("cifru.out")));
14
15 citire();
16 solutia();
17
18 out.println(rezsol);
19 out.close();
20 }
21
22 static void citire() throws IOException
23 {
24 st.nextToken(); n=(int)st.nval;
25 st.nextToken(); k=(int)st.nval;
26 }
27
28 static int f(int n)
29 {
30 int d,i,rez=0,t;
31
32 if (n<k)
33 {
34 if(n<=1) return 1;
35 if(x[n]!=0) return x[n];
36
CAPITOLUL 18. OJI 2006 18.2. CIFRU 317
OJI 2005
19.1 Lanţ
Emanuela Cerchez
Ion este un lingvist pasionat. Recent el a descoperit un text scris ı̂ntr-o limbă necunoscută.
Textul este scris pe mai multe linii şi este format din cuvinte scrise cu litere mici din alfabetul
latin, separate prin spaţii sau/şi semne de punctuaţie (,:;.!?-).
Ion a fost frapat că există multe similitudini ı̂ntre cuvintele din text. Fiind foarte riguros, Ion
defineşte similitudinea a două cuvinte după cum urmează.
Fie c1 şi c2 două cuvinte. Cuvântul c1 poate fi obţinut din cuvântul c2 printr-o succesiune de
operaţii elementare. Operaţiile elementare ce pot fi folosite sunt:
Operaţia Efect Exemplu
move(c1, c2) Mută primul Dacă c1=”alba” şi c2=”neagra”,
caracter din c1 după efectuarea operaţiei c1 va fi
la sfârşitul lui c2 ”lba”, iar c2 va fi ”neagraa”
insert(c1, x) Inserează Dacă c1=”alba” şi x=”b”,
caracterul x la după efectuarea operaţiei
ı̂nceputul lui c1 c1 va fi ”balba”
delete(c1) Şterge Dacă c1=”alba”
primul caracter după efectuarea operaţiei
din c1 c1 va fi ”lba”
Definim similitudinea dintre c1 şi c2 ca fiind numărul minim de operaţii insert şi delete ce
trebuie să fie executate pentru a transforma cuvântul c1 ı̂n cuvântul c2 (operaţiile move nu se
numără).
Fie c0 primul cuvânt din text. Începând cu c0 putem construi lanţuri de k-similitudine.
Un lanţ de k-similitudine este o succesiune de cuvinte distincte din text cu următoarele pro-
prietăţi:
- dacă cuvântul x apare ı̂n lanţ ı̂naintea cuvântului y, atunci prima apariţie a lui x ı̂n text
precedă prima apariţie a lui y ı̂n text;
- dacă x şi y sunt cuvinte consecutive ı̂n lanţ (ı̂n ordinea x y) , atunci similitudinea dintre x şi
y este k;
- lanţul este maximal (adică nu putem adăuga ı̂ncă un cuvânt la sfârşitul acestui lanţ, astfel
ı̂ncât să fie respectate proprietăţile precedente).
Cerinţă
Scrieţi un program care să determine numărul de lanţuri de k-similitudine care ı̂ncep cu c0.
Date de intrare
Fişierul de intrare lant.in conţine pe prima linie valoarea k. Pe următoarele linii se află textul
dat.
Date de ieşire
Fişierul de ieşire lant.out va conţine o singură linie pe care va fi scris număul de lanţuri de
k-similitudine care ı̂ncep cu c0.
Restricţii
Lungimea unei linii din text nu depăşeşte 1000 de caractere.
Lungimea unui cuvânt nu depăşeşte 30 de caractere.
318
CAPITOLUL 19. OJI 2005 19.1. LANŢ 319
Soluţia oficială
1. Se citeşte textul şi se memorează cuvintele distincte din text ı̂ntr-un tablou c.
Fie nc numărul de cuvinte distincte determinate. Fiecare cuvânt este numerotat de la 0 la
nc 1 (indicii din tabloul c). Observaţi că numerotarea cuvintelor respectă ordinea primei apariţii
ı̂n text a cuvintelor.
2. Asociem problemei un graf orientat astfel:
nodurile grafului sunt cuvintele distincte din text;
există arc de la nodul i la nodul j (i $ j) dacă numărul minim de operaţii insert şi delete
necesare pentru a transforma cuvântul ci ı̂n cuvântul cj este & k.
Observaţi că graful asociat problemei nu conţine circuite.
Pentru a determina arcele grafului trebuie să rezolvăm următoarea subproblemă: să se de-
termine numărul minim de operaţii delete şi insert necesare pentru a transforma cuvântul x ı̂n
cuvântul y.
Rezolvăm această subproblemă prin programare dinamică.
Fie dij numărul minim de operaţii insert şi delete necesare pentru a transforma sufixul
lui x care ı̂ncepe la poziţia i ı̂n sufixul lui y care ı̂ncepe la poziţia j.
Fie n lungimea cuvântului x şi m lungimea cuvântului y.
a dnj m j, pentru orice j 0, m
a dim n i, pentru orice i 0, n
a dij minrdi 1j 1, dacă pi q j ; - move
1 dij 1 - insert
1 di 1j - delete }
Soluţia este d00.
3. Numărul de lanţuri de k-similitudine este egal cu numărul de drumuri care ı̂ncep cu nodul
0 şi se termină ı̂ntr-un nod terminal al grafului (nod cu gradul exterior 0).
Să notăm: nri numărul de lanţuri de k-similitudine care ı̂ncep cu cuvântul i.
Determinăm numărul folosind următoarea relaţie de recurenţă:
a nr i 1, dacă nodul i este terminal
a nr i nri1 nri2 ... nrik , unde i1 , i2 , ..., ik sunt noduri din graf cu proprietatea
că există arc de la i la ij , pentru j 1, k.
#define LgMaxC 31
#define NrMaxC 151
Cuvant c[NrMaxC];
/* contine cuvintele distincte din text, in ordinea primei aparitii */
int nc, k;
int a[NrMaxC][NrMaxC];
int d[LgMaxC+1][LgMaxC+1];
long int nr[NrMaxC];
/* nr[i]= numarul de lanturi de k similitudine care incep cu cuvantul i */
void Citire();
void ConstrGraf();
void Numara(int);
int main()
{
int i;
ofstream fout(OutFile);
Citire();
ConstrGraf();
Numara(0);
fout<<nr[0]<<endl;
fout.close();
return 0;
}
void Citire()
{ifstream fin(InFile);
char s[1001], *p;
fin>>k; fin.get();
while (!fin.eof())
{fin.getline(s,1001);
if (fin.good())
{p=strtok(s," ,.:;?!-");
while (p)
{Adauga_Cuvant(p);
p=strtok(NULL," ,.:;?!-"); }
}
}
fin.close();}
d[i][j]=d[i+1][j+1];}
return d[0][0];
}
/*
void ConstrGraf()
{int i, j;
for (i=0; i<nc; i++)
{a[i]=(int *) malloc(sizeof(int));
a[i][0]=0;}
for (i=0; i<nc; i++)
for (j=i+1; j<nc; j++)
if (dist(c[i],c[j])<=k)
{
a[i][0]++;
a[i]=(int *)realloc(a[i],(a[i][0]+1)*sizeof(int));
a[i][a[i][0]]=j;
}
}*/
void ConstrGraf()
{int i, j;
for (i=0; i<nc; i++)
for (j=i+1; j<nc; j++)
if (dist(c[i],c[j])<=k)
{
a[i][0]++;
a[i][a[i][0]]=j;
}
}
Prima variantă:
22
23 static void citire() throws IOException
24 {
25 StreamTokenizer st=new StreamTokenizer(
26 new BufferedReader(new FileReader("lant.in")));
27
28 st.nextToken(); k=(int)st.nval;
29
30 st.whitespaceChars(’,’,’,’);
31 st.whitespaceChars(’:’,’:’);
32 st.whitespaceChars(’;’,’;’);
33 st.whitespaceChars(’.’,’.’);
34 st.whitespaceChars(’!’,’!’);
35 st.whitespaceChars(’?’,’?’);
36 st.whitespaceChars(’-’,’-’);
37
38 while(StreamTokenizer.TT_EOF!=st.nextToken())
39 {
40 c[++nc]=st.sval;
41 if(c[nc]==null) {--nc;continue;} // "EOL" returneaza "null"
42 if(primaPozitie(c[nc])<nc) nc--;
43 else System.out.println(nc+" : "+c[nc]);
44 }
45 System.out.println();
46 }// citire()
47
48 static void solutie()
49 {
50 int i,j;
51
52 for(i=1;i<=nc-1;i++)
53 for(j=i+1;j<=nc;j++)
54 cs[i][j]=dist(c[i],c[j]);
55
56 afism(cs); // matricea costurilor similitudine
57
58 x[1]=1; // lanturi care incep cu primul cuvant
59 for(j=2;j<=nc;j++)
60 if(cs[1][j]<=k) cuvant(2,j); //(pozitie,cuvant)
61
62 System.out.println();
63 }// solutie()
64
65 static int dist(String x, String y)
66 {
67 int i,j,n,m;
68 n=x.length();
69 m=y.length();
70 int[][] d=new int[n+1][m+1]; // este initializata cu zero
71 for(i=0;i<=n;i++) d[i][m]=n-i;
72 for(j=0;j<=m;j++) d[n][j]=m-j;
73
74 for(i=n-1;i>=0;i--)
75 for(j=m-1;j>=0;j--)
76 {
77 d[i][j]=min(1+d[i][j+1],1+d[i+1][j]);
78 if(x.charAt(i)==y.charAt(j)) d[i][j]=min(d[i][j],d[i+1][j+1]);
79 }
80 return d[0][0];
81 }// dist()
82
83 static void cuvant(int pozi, int cuvi) // plasez cuvi pe pozi si ...
84 {
85 int i,cuvj;
86 boolean esteMaximal=true;
87
88 x[pozi]=cuvi;
89 for(cuvj=cuvi+1;cuvj<=nc;cuvj++)
90 if(cs[cuvi][cuvj]<=k)
91 {
92 esteMaximal=false;
93 cuvant(pozi+1,cuvj);
94 }
95
96 if(esteMaximal)
97 {
CAPITOLUL 19. OJI 2005 19.1. LANŢ 323
98 nlks++;
99 System.out.print(nlks+" : ");
100 for(i=1;i<=pozi;i++) System.out.print(c[x[i]]+" ");
101 System.out.println();
102 }
103 }// cuvant(...)
104
105 static int primaPozitie(String s) // in care apare s in text
106 {
107 int i,j,pp=-1,ns=s.length();
108 boolean ok=false;
109 for(i=1;i<nc;i++)
110 {
111 pp=i;
112 if(c[i].length()!=ns) continue; // difera prin lungime
113 ok=true;
114 for(j=0;j<ns;j++) // s[0] ... s[ns-1]
115 if(c[i].charAt(j)!=s.charAt(j)) { ok=false; break;}
116 if(ok) break;
117 }
118 if(ok) return pp; else return nc;
119 }// primaPozitie(...)
120
121 static int min(int a, int b) { if(a<b) return a; else return b; }
122
123 static void afism(int[][] a)
124 {
125 int i,j;
126 for(i=1;i<=nc;i++)
127 {
128 for(j=1;j<=nc;j++) System.out.print(a[i][j]+" ");
129 System.out.println();
130 }
131 System.out.println();
132 }// afism(...)
133
134 static void scrieSol() throws IOException
135 {
136 PrintWriter out = new PrintWriter(
137 new BufferedWriter(new FileWriter("lant.out")));
138 out.println(nlks);
139 out.close();
140 }// scrieSol()
141 }// class
142 /*-----------------------------------------------
143 Lant.in 5
144 ana are mere,banane,
145 pere si castane.
146
147 Lant.out: 6
148
149 Ecran:
150
151 1 : ana
152 2 : are
153 3 : mere
154 4 : banane
155 5 : pere
156 6 : si
157 7 : castane
158
159 0 1 0 3 0 0 2 0 4 7 3 7 5 6 1 : ana are mere pere
160 0 0 2 2 2 0 2 0 0 3 5 3 5 6 2 : ana are banane castane
161 0 0 0 1 3 0 1 0 0 0 8 2 6 9 3 : ana are pere
162 0 0 0 0 1 0 4 0 0 0 0 8 8 5 4 : ana are si
163 0 0 0 0 0 0 1 0 0 0 0 0 6 9 5 : ana banane castane
164 0 0 0 0 0 0 1 0 0 0 0 0 0 7 6 : ana si
165 0 0 0 0 0 0 0 0 0 0 0 0 0 0
166 -----------------------------------------------------------------*/
A doua variantă:
3 {
4 static final int NMAX=150;
5 static String[] c=new String[NMAX+1];
6 static int k,nc=0;
7 static int[][] cs=new int[NMAX+1][NMAX+1]; // costuri editare
8 static int[] x=new int[NMAX+1]; // x[i]=nr cuvinte care incep cu c[i
]
9
10 public static void main(String[] args) throws IOException
11 {
12 long t1,t2;
13 t1=System.currentTimeMillis();
14
15 citire();
16 solutie();
17 scrieSol();
18
19 t2=System.currentTimeMillis();
20 System.out.println("Timp = "+(t2-t1));
21 }// main()
22
23 static void citire() throws IOException
24 {
25 StreamTokenizer st=new StreamTokenizer(
26 new BufferedReader(new FileReader("lant.in")));
27
28 st.nextToken(); k=(int)st.nval;
29
30 st.whitespaceChars(’,’,’,’);
31 st.whitespaceChars(’:’,’:’);
32 st.whitespaceChars(’;’,’;’);
33 st.whitespaceChars(’.’,’.’);
34 st.whitespaceChars(’!’,’!’);
35 st.whitespaceChars(’?’,’?’);
36 st.whitespaceChars(’-’,’-’);
37
38 while(StreamTokenizer.TT_EOF!=st.nextToken())
39 {
40 c[++nc]=st.sval;
41 if(c[nc]==null) {--nc;continue;} // "EOL" returneaza "null"
42 if(primaPozitie(c[nc])<nc) nc--;
43 }
44 }// citire()
45
46 static void solutie()
47 {
48 int i,j;
49 boolean esteTerminal;
50
51 for(i=1;i<=nc-1;i++)
52 for(j=i+1;j<=nc;j++)
53 cs[i][j]=dist(c[i],c[j]);
54
55 for(i=2;i<=nc;i++)
56 {
57 esteTerminal=true;
58 for(j=i+1;j<=nc;j++)
59 if(cs[i][j]<=k) {esteTerminal=false; break;}
60 if(esteTerminal) x[i]=1;
61 }
62
63 for(i=nc-1;i>=1;i--)
64 for(j=i+1;j<=nc;j++)
65 if(cs[i][j]<=k) x[i]+=x[j];
66 }// solutie()
67
68 static int dist(String x, String y)
69 {
70 int i,j,n,m;
71 n=x.length();
72 m=y.length();
73 int[][] d=new int[n+1][m+1]; // este initializata cu zero
74 for(i=0;i<=n;i++) d[i][m]=n-i;
75 for(j=0;j<=m;j++) d[n][j]=m-j;
76
77 for(i=n-1;i>=0;i--)
CAPITOLUL 19. OJI 2005 19.2. SCARA 325
78 for(j=m-1;j>=0;j--)
79 {
80 d[i][j]=min(1+d[i][j+1],1+d[i+1][j]);
81 if(x.charAt(i)==y.charAt(j)) d[i][j]=min(d[i][j],d[i+1][j+1]);
82 }
83 return d[0][0];
84 }// dist(...)
85
86 static int primaPozitie(String s) // in care apare s in text
87 {
88 int i,j,pp=-1,ns=s.length();
89 boolean ok=false;
90 for(i=1;i<nc;i++)
91 {
92 pp=i;
93 if(c[i].length()!=ns) continue; // difera prin lungime
94 ok=true;
95 for(j=0;j<ns;j++) // s[0] ... s[ns-1]
96 if(c[i].charAt(j)!=s.charAt(j)) { ok=false; break;}
97 if(ok) break;
98 }
99 if(ok) return pp; else return nc;
100 }// primaPozitie(...)
101
102 static int min(int a, int b) { if(a<b) return a; else return b; }
103
104 static void scrieSol() throws IOException
105 {
106 PrintWriter out = new PrintWriter(
107 new BufferedWriter(new FileWriter("lant.out")));
108 out.println(x[1]);
109 out.close();
110 }// scrieSol()
111 }// class
19.2 Scara
Stelian Ciurea
Domnul G are de urcat o scară cu n trepte. În mod normal, la fiecare pas pe care ı̂l face, el
urcă o treaptă. Pe k dintre aceste trepte se află câte o sticlă cu un număr oarecare de decilitri
de apă, fie acesta x. Dacă bea toată apa dintr-o astfel de sticlă, forţa şi mobilitatea lui G cresc,
astfel ı̂ncât, la următorul pas el poate urca până la x trepte, după care, dacă nu bea din nou ceva,
revine la ”normal”. Sticlele cu apă nu costă nimic. Cantitatea de apă conţinută de aceste sticle
poate să difere de la o treaptă la alta.
Pe j trepte se află câte o sticlă cu băutură energizantă. Şi pentru aceste sticle, cantitatea de
băutură energizantă poate să difere de la o treaptă la alta. Să presupunem că ı̂ntr-una dintre
aceste sticle avem y decilitri de băutură energizantă. Dacă bea q (q & y) decilitri dintr-o astfel de
sticlă, la următorul pas G poate urca până la 2q trepte, după care şi ı̂n acest caz, dacă nu bea din
nou ceva, el revine la ”normal”. Însă băutura energizantă costă: pentru o cantitate de q decilitri
consumaţi, G trebuie să plătească q lei grei.
Pot exista trepte pe care nu se află nici un pahar, dar şi trepte pe care se află atât o sticlă
cu apă cât şi una cu băutură energizantă. În astfel de situaţii, nu are rost ca G să bea ambele
băuturi deoarece efectul lor nu se cumulează; el poate alege să bea una dintre cele două băuturi
sau poate să nu bea nimic.
Cerinţă
Determinaţi p, numărul minim de paşi pe care trebuie să ı̂i facă G pentru a urca scara, precum
şi suma minimă pe care trebuie să o cheltuiască G pentru a urca scara ı̂n p paşi.
Date de intrare
Fişierul text de intrare scara.in conţine:
- pe prima linie un număr natural n, reprezentând numărul total de trepte;
- pe cea de a doua linie un număr natural k, reprezentând numărul de trepte pe care se află
sticle cu apă;
- pe fiecare dintre următoarele k linii câte două numere naturale separate printr-un spaţiu,
reprezentând numărul de ordine al treptei pe care se află o sticlă cu apă şi respectiv cantitatea de
apă din acea sticlă exprimată ı̂n decilitri;
CAPITOLUL 19. OJI 2005 19.2. SCARA 326
- pe următoarea linie un număr natural j, reprezentând numărul de trepte pe care se află sticle
cu băutură energizantă;
- pe fiecare dintre următoarele j linii câte două numere naturale separate printr-un spaţiu,
reprezentând numărul de ordine al treptei pe care se află o sticlă cu băutură energizantă şi respectiv
cantitatea de băutură energizantă din acea sticlă exprimată ı̂n decilitri.
Date de ieşire
Fişierul text de ieşire scara.out va conţine o singură linie pe care vor fi scrise două numere
naturale p c separate printr-un spaţiu, p reprezentând numărul minim de paşi, iar c suma minimă
cheltuită.
Restricţii
n & 120
0&k&n
0&j&n
Cantitatea de apă aflată ı̂n oricare sticlă este 1 & x & 100
Cantitatea de băutură energizantă aflată ı̂n oricare sticlă este 1 & y & 100
Exemple
scara.in scara.out scara.in scara.out
6 32 6 41
1 1
12 12
2 2
41 41
12 11
Timp maxim de execuţie: 1 secundă/test.
Soluţia oficială
Se construieşte un graf orientat cu n noduri (care corespund celor n trepte) la arcele căruia se
ataşează costuri urmărind ca:
ı̂ntre două noduri costul să fie cu atât mai mic cu cât nodurile sunt ”mai depărtate”
costul pentru salturi efectuate după ce se bea băutura energizantă să fie mai mare decât
costul salturilor făcute după ce se bea apă ı̂ntre aceleaşi noduri, dar acest cost să nu depaşească
costul saltului ı̂ntre două noduri mai apropiate.
În calculul costurilor ı̂ntre două noduri am folosit formulele:
costii 1 999000
costij 1000000 j i pentru salturi când se bea apă j % i
costij 1000000 j i q pentru salturi când se bea energizantă j % i, 1 & q & y
Pe acest graf se aplică apoi un algoritm de aflare a drumului minim de sursă unică (Dijkstra)
sau având ı̂n vedere caracterul aciclic al grafului rezultat, algoritmul Dijkstra adaptat pentru astfel
de grafuri.
int main()
{ifstream fi("scara.in");
ofstream fo("scara.out");
CAPITOLUL 19. OJI 2005 19.2. SCARA 327
int n,k,j,i,x,cx,i1,nod1,nod2,y,p,v;
fi>>n;
for (i=0;i<n+1;i++)
for (j=0;j<n+1;j++)
cost[i][j]=LONG_MAX;
fi>>k;
for (i=0;i<k;i++)
{ fi>>p>>x;
if (! fi.good() || x>100) cout<<"apa incorect\n";
nod1=p;
for (nod2=nod1+2,i1=2;i1<=x;i1++,nod2=nod2+1)
if (nod2<=n)
cost[nod1][nod2]=1000000-100*(nod2-nod1);
}
for (i=1;i<=n;i++)
if (eng[i]!=0)
{
nod1=i;
for (nod2=nod1+1,i1=1;i1<=eng[i];i1++,nod2=nod2+2)
{if (nod2<=n)
if (cost[nod1][nod2]>1000000-100*(nod2-nod1)+i1)
cost[nod1][nod2]=1000000-100*(nod2-nod1)+i1;
if (nod2+1<=n)
if (cost[nod1][nod2+1]>1000000-100*(nod2-nod1+1)+i1)
cost[nod1][nod2+1]=1000000-100*(nod2-nod1+1)+i1;
}
}
/* cout<<endl;
for (i=0;i<n;i++)
{ for (j=0;j<n+1;j++)
if (cost[i][j]<MAXLONG) fo<<cost[i][j]<<" ";
else fo<<-1<<" ";
fo<<endl;}
cout<<endl;
*/
//Dijkstra
for (i=1;i<n+1;i++)
{ c[i]=1;
d[i]=cost[0][i];
}
pd[1]=0;
for (j=0;j<n-1;j++)
{ minn=LONG_MAX;
// for (i=1;i<=n;i++)
// fo<<d[i]<<’ ’;
// fo<<endl;
// for (i=1;i<=n;i++)
// fo<<pd[i]<<’ ’;
// fo<<endl;
for (i=1;i<n+1;i++)
if (c[i]!=0)
if (minn>d[i])
{minn=d[i];
v=i;
CAPITOLUL 19. OJI 2005 19.2. SCARA 328
}
c[v]=0;
// fo<<min<<’ ’<<v<<endl;
for (i=1;i<n+1;i++)
if (c[i]!=0)
if (d[i]>d[v]+cost[v][i])
{
d[i]=d[v]+cost[v][i];
pd[i]=v;
}
}
/*
cout<<d[n+1]<<endl;
for (i=0;i<=n;i++)
cout<<pd[i]<<’ ’;
cout<<endl;
*/
i=n;
int ct=0,cst=0;
while (i!=0)
{j=pd[i];
// cout<<j<<’ ’;
ct++;
if (cost[j][i]%100!=0)
cst = cst+cost[j][i]%100;
i=j;
}
fo<<ct<<’ ’<<cst<<endl;
fo.close();
return 0;
}
#define cmp(a, b, c, d) (((a) < (c )) || ((a) == (c) && (b) < (d)))
void citire(void);
void afisare(void);
void dinamic(void);
void citire ()
{
int i, j, K;
FILE * fin=fopen (FIN, "r");
fscanf (fin, "%d", &N);
fscanf (fin, "%d", &K);
fclose (fin);
}
void afisare ()
{
FILE * fout=fopen(FOUT, "w");
fprintf(fout, "%d %d\n", nrpasi[N], sum[N]);
fclose (fout);
}
void dinamic ()
{
int i, q;
nrpasi[1]=1;
for (i=2; i<=N; i++) nrpasi[i]=INF;
Varianta 1a:
11 {
12 long t1,t2;
13 t1=System.currentTimeMillis();
14
15 int k,j;
16 int i,treapta, cantitate;
17 int pmin;
18 StreamTokenizer st=new StreamTokenizer(
19 new BufferedReader(new FileReader("scara.in")));
20 PrintWriter out=new PrintWriter(
21 new BufferedWriter(new FileWriter("scara.out")));
22
23 st.nextToken(); n=(int)st.nval;
24 a=new int[n+1];
25 e=new int[n+1];
26 p=new int[n+1];
27
28 st.nextToken(); k=(int)st.nval;
29 for(i=1;i<=k;i++)
30 {
31 st.nextToken(); treapta=(int)st.nval;
32 st.nextToken(); cantitate=(int)st.nval;
33 a[treapta]=cantitate;
34 }
35
36 st.nextToken(); j=(int)st.nval;
37 for(i=1;i<=j;i++)
38 {
39 st.nextToken(); treapta=(int)st.nval;
40 st.nextToken(); cantitate=(int)st.nval;
41 e[treapta]=cantitate;
42 }
43
44 p[1]=1;
45 for(k=2;k<=n;k++)
46 {
47 pmin=k;
48 for(i=1;i<=k-1;i++)
49 {
50 pmin=min(pmin,p[i]+(k-i)); // in cel mai rau caz !
51 if(i+a[i]>=k) pmin=min(pmin,p[i]+1);
52 if(i+2*e[i]>=k) pmin=min(pmin,p[i]+1);
53 }
54 p[k]=pmin;
55 }
56 out.println(p[n]);
57 out.close();
58
59 t2=System.currentTimeMillis();
60 System.out.println("Timp = "+(t2-t1));
61 }// main
62
63 static int min(int a, int b)
64 {
65 if(a<b) return a; else return b;
66 }
67 }// class
Varianta 1b:
Varianta 2a:
13 t1=System.currentTimeMillis();
14
15 int k,j;
16 int i,treapta, cantitate;
17 int pmin,cmin;
18 StreamTokenizer st=new StreamTokenizer(
19 new BufferedReader(new FileReader("scara.in")));
20 PrintWriter out=new PrintWriter(
21 new BufferedWriter(new FileWriter("scara.out")));
22
23 st.nextToken(); n=(int)st.nval;
24 a=new int[n+1];
25 e=new int[n+1];
26 p=new int[n+1];
27 c=new int[n+1];
28
29 st.nextToken(); k=(int)st.nval;
30 for(i=1;i<=k;i++)
31 {
32 st.nextToken(); treapta=(int)st.nval;
33 st.nextToken(); cantitate=(int)st.nval;
34 a[treapta]=cantitate;
35 }
36
37 st.nextToken(); j=(int)st.nval;
38 for(i=1;i<=j;i++)
39 {
40 st.nextToken(); treapta=(int)st.nval;
41 st.nextToken(); cantitate=(int)st.nval;
42 e[treapta]=cantitate;
43 }
44
45 for(k=1;k<=n;k++) p[k]=k+1; // initializare pentru min !!
46
47 p[1]=1; c[0]=0;
48 for(k=1;k<=n-1;k++) // propag informatia de pe treapta k in sus !
49 {
50 if(a[k]>0)
51 for(i=k+1;i<=min(k+a[k],n);i++) // propag apa in sus !
52 if(p[k]+1<p[i]) p[i]=p[k]+1;
53
54 for(i=k+a[k]+1;i<=n;i++) // pas cu pas !
55 if(p[i-1]+1<p[i]) p[i]=p[i-1]+1;
56
57 if(e[k]>0)
58 for(j=k+1;j<=min(k+2*e[k],n);j++) // propag energizant in sus
59 if(p[k]+1<p[j]) p[j]=p[k]+1;
60
61 for(j=k+2*e[k]+1;j<=n;j++) // pas cu pas !
62 if(p[j-1]+1<p[j]) p[j]=p[j-1]+1;
63 }
64
65 out.println(p[n]+" "+c[n]); out.close();
66 t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1));
67 }// main
68
69 static int min(int a, int b)
70 {
71 if(a<b) return a; else return b;
72 }
73 }// class
Varianta 2b:
11 {
12 long t1,t2;
13 t1=System.currentTimeMillis();
14
15 int k,j;
16 int i,treapta, cantitate;
17 int pmin,cmin;
18 StreamTokenizer st=new StreamTokenizer(
19 new BufferedReader(new FileReader("scara.in")));
20 PrintWriter out=new PrintWriter(
21 new BufferedWriter(new FileWriter("scara.out")));
22
23 st.nextToken(); n=(int)st.nval;
24 a=new int[n+1];
25 e=new int[n+1];
26 p=new int[n+1];
27 c=new int[n+1];
28
29 st.nextToken(); k=(int)st.nval;
30 for(i=1;i<=k;i++)
31 {
32 st.nextToken(); treapta=(int)st.nval;
33 st.nextToken(); cantitate=(int)st.nval;
34 a[treapta]=cantitate;
35 }
36
37 st.nextToken(); j=(int)st.nval;
38 for(i=1;i<=j;i++)
39 {
40 st.nextToken(); treapta=(int)st.nval;
41 st.nextToken(); cantitate=(int)st.nval;
42 e[treapta]=cantitate;
43 }
44
45 for(k=1;k<=n;k++) p[k]=k+1; // initializare pentru min !!
46
47 p[1]=1; c[0]=0;
48 for(k=1;k<=n-1;k++) // propag informatia de pe treapta k in sus !
49 {
50 if(a[k]>0)
51 for(i=k+1;i<=min(k+a[k],n);i++) // propag apa in sus
52 if(p[k]+1<p[i]) { p[i]=p[k]+1; c[i]=c[k]; } //apa=free
53 else
54 if(p[k]+1==p[i]) c[i]=min(c[i],c[k]);
55
56 for(i=k+a[k]+1;i<=n;i++) // pas cu pas !
57 if(p[i-1]+1<p[i]) { p[i]=p[i-1]+1; c[i]=c[i-1];}
58 else
59 if(p[i-1]+1==p[i]) c[i]=min(c[i],c[i-1]); // ???
60
61 if(e[k]>0)
62 for(j=k+1;j<=min(k+2*e[k],n);j++) // propag energizant in sus
63 if(p[k]+1<p[j]) { p[j]=p[k]+1; c[j]=c[k]+(j-k+1)/2;}
64 else
65 if(p[k]+1==p[j]) c[j]=min(c[j],c[k]+(j-k+1)/2);
66
67 for(j=k+2*e[k]+1;j<=n;j++) // pas cu pas !
68 if(p[j-1]+1<p[j]) {p[j]=p[j-1]+1; c[j]=c[j-1];}
69 else
70 if(p[j-1]+1==p[j]) c[j]=min(c[j],c[j-1]);
71 }
72
73 out.println(p[n]+" "+c[n]); out.close();
74 t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1));
75 }// main
76
77 static int min(int a, int b)
78 {
79 if(a<b) return a; else return b;
80 }
81 }// class
Capitolul 20
OJI 2004
20.1 Moşia
Păcală a primit, aşa cum era ı̂nvoiala, un petec de teren de pe moşia boierului. Terenul este
ı̂mprejmuit complet cu segmente drepte de gard ce se sprijină la ambele capete de câte un par
zdravăn. La o nouă prinsoare, Păcală iese iar ı̂n câştig şi primeşte dreptul să strămute nişte pari,
unul câte unul, cum i-o fi voia, astfel ı̂ncât să-şi extindă suprafaţa de teren. Dar ı̂nvoiala prevede
că fiecare par poate fi mutat ı̂n orice direcţie, dar nu pe o distanţă mai mare decât o valoare dată
(scrisă pe fiecare par) şi fiecare segment de gard, fiind cam şuubred, poate fi rotit şi prelungit de
la un singur capăt, celălalt rămânând nemişcat.
Cunoscnd poziţiile iniţiale ale parilor şi valoarea ı̂nscrisă pe fiecare par, se cere suprafaţa
maximă cu care poate să-şi extindă Păcală proprietatea. Se ştie că parii sunt daţi ı̂ntr-o ordine
oarecare, poziţiile lor iniţiale sunt date prin numere ı̂ntregi de cel mult 3 cifre, distanţele pe care
fiecare par poate fi deplasat sunt numere naturale strict pozitive şi figura formată de terenul iniţial
este un poligon neconcav.
Date de intrare
Fişierul MOSIA.IN conţine n 1 linii cu următoarele valori:
n - numărul de pari
x1 y1 d1 - coordonatele iniţiale şi distanţa pe care poate fi mutat parul 1
x2 y2 d2 - coordonatele iniţiale şi distanţa pe care poate fi mutat parul 2
. . .
xn yn dn - coordonatele iniţiale şi distanţa pe care poate fi mutat parul n
Date de ieşire
În fişierul MOSIA.OUT se scrie un număr real cu 4 zecimale ce reprezintă suprafaţa maximă
cu care se poate mări moşia.
Restricţii şi observaţii:
3 $ N & 200 număr natural
1000 $ xi , yi $ 1000 numere ı̂ntregi
0 $ di & 20 numere ı̂ntregi
poligonul neconcav se defineşte ca un poligon convex cu unele vârfuri coliniare
poziţiile parilor sunt date ı̂ntr-o ordine oarecare
poligonul obţinut după mutarea parilor poate fi concav
poziţiile finale ale parilor nu sunt ı̂n mod obligatoriu numere naturale
Exemplu
Pentru fişierul de intrare
4
-3 0 2
3 0 3
0 6 2
0 -6 6
se va scrie ı̂n fişierul de ieşire valoarea 30.0000
Explicaţie: prin mutarea parilor 1 şi 2 cu câte 2 şi respectiv 3 unităţi, se obţine un teren
având suprafaţa cu 30 de unităţi mai mare decât terenul iniţial.
Timp limită de executare: 1 sec./test
334
CAPITOLUL 20. OJI 2004 20.1. MOŞIA 335
66 inc(vf);s[vf]:=i
67 end;
68 if vf-1<>n then begin n:=vf-1;writeln(’Date eronate’) end
69 end;
70
71 procedure ssort;
72 var i:integer;
73 p1:puncte;o1:sir;
74 begin
75 for i:=1 to n do begin
76 p1[i]:=p[s[i]];o1[i]:=o[s[i]]
77 end;
78 p:=p1;o:=o1
79 end;
80
81 function dist(a,b:punct):real;
82 begin
83 dist:=sqrt(sqr(a.x-b.x)+sqr(a.y-b.y))
84 end;
85
86 procedure dinamic;
87 var i,j,k:integer;sum:real;
88 ds:array[1..maxi]of real;
89 d:array[1..2,1..maxi] of record s:real;i:byte end;
90 begin
91 for i:=1 to n do ds[i]:=p[i].d/2*dist(p[i-1],p[i+1]);
92 d[1,n].s:=ds[n];d[1,n].i:=1;
93 d[1,n+1].s:=0;d[1,1].i:=0;
94 for i:=n-1 downto 2 do
95 if d[1,i+2].s+ds[i]>d[1,i+1].s then begin
96 d[1,i].s:=d[1,i+2].s+ds[i];d[1,i].i:=1
97 end
98 else begin
99 d[1,i].s:=d[1,i+1].s;d[1,i].i:=0
100 end;
101 d[1,1].s:=d[1,2].s;
102 d[2,n].s:=0;d[2,n].i:=0;
103 d[2,n+1].s:=0;
104 for i:=n-1 downto 1 do
105 if d[2,i+2].s+ds[i]>d[2,i+1].s then begin
106 d[2,i].s:=d[2,i+2].s+ds[i];d[2,i].i:=1
107 end
108 else begin
109 d[2,i].s:=d[2,i+1].s;d[2,i].i:=0
110 end;
111 if d[1,1].s>d[2,1].s then j:=1 else j:=2;
112 i:=1;k:=0;
113 while i<=n do
114 if d[j,i].i=0 then inc(i)
115 else begin inc(k);s[k]:=i;inc(i,2) end;
116 for i:=1 to k do write(o[s[i]],’ ’);
117 writeln;writeln(d[j,1].s:20:6);
118 assign(f,fo);rewrite(f);write(f,d[j,1].s:20:6);close(f)
119 end;
120
121 begin
122 citire;
123 qsort(1,n);
124 convex;
125 ssort;
126 p[0]:=p[n];p[n+1]:=p[1];
127 dinamic;
128 end.
20.2 Lanterna
Un agent secret are o hartă pe care sunt marcate N obiective militare. El se află, iniţial, lângă
obiectivul numerotat cu 1 (baza militară proprie) şi trebuie să ajungă la obiectivul numerotat cu
CAPITOLUL 20. OJI 2004 20.2. LANTERNA 337
N (baza militară inamică). În acest scop, el va folosi drumurile existente, fiecare drum legând 2
obiective distincte. Fiind o misiune secretă, deplasarea agentului va avea loc noaptea; de aceea,
el are nevoie de o lanternă. Pentru aceasta, el are de ales ı̂ntre K tipuri de lanterne - o lanternă
de tipul W (1 & W & K) are baterii care permit consumul a W waţi, după consumul acestor
waţi, lanterna nu mai luminează. Din fericire, unele dintre obiective sunt baze militare prietene,
astfel că, o dată ajuns acolo, el ı̂şi poate reı̂ncărca bateriile complet. Agentul trebuie sa aibă
grijă ca, ı̂nainte de a merge pe un drum ı̂ntre două obiective, cantitatea de waţi pe care o mai
poate consuma să fie mai mare sau egală cu cantitatea de waţi pe care o va consuma pe drumul
respectiv.
Cunoscând drumurile dintre obiective şi, pentru fiecare drum, durata necesară parcurgerii
drumului şi numărul de waţi consumaţi de lanternă, determinaţi tipul de lanternă cu numărul cel
mai mic, astfel ı̂ncât durata deplasării sa fie minimă (dintre toate tipurile de lanternă cu care se
poate ajunge ı̂n timp minim la destinaţie, interesează lanterna cu consumul cel mai mic).
Date de intrare
Pe prima linie a fişierului lanterna.in se află numerele ı̂ntregi N şi K, separate printr-un
spaţiu. Pe următoarea linie se află N numere ı̂ntregi din mulţimea r0, 1x. Dacă al i-lea număr
este 1, aceasta ı̂nseamnă că obiectivul cu numărul i este o bază militară prietenă (adică agentul
ı̂şi poate reı̂ncărca bateriile lanternei dac a ajunge la acest obiectiv); dacă numărul este 0, agentul
nu ı̂şi va putea reı̂ncărca bateriile. Primul număr din linie este 1, iar ultimul este 0. Pe cea de-a
treia linie a fişierului se află numărul M de drumuri dintre obiective. Fiecare din următoarele M
linii conţine câte 4 numere ı̂ntregi separate prin spaţii: a b T W , având semnificaţia că există un
drum bidirecţional ı̂ntre obiectivele a şi b (a j b), care poate fi parcurs ı̂ntr-un timp T şi cu un
consum de W waţi.
Date de ieşire
În fişierul lanterna.out se vor afişa două numere ı̂ntregi, separate printr-un spaţiu : T min şi
W min. T min reprezentând durata minimă posibilă a deplasării de la obiectivul 1 la obiectivul N ,
iar W min reprezintă tipul de lanternă cu numărul cel mai mic pentru care se obţine acest timp.
Restricţii şi precizări
2 & N & 50
1 & K & 1000
1 & M & N N 1©2
Între două obiective diferite poate exista maximum un drum direct.
Pentru fiecare drum, durata parcurgerii este un număr ı̂ntreg ı̂ntre 1 şi 100, iar numărul de
waţi consumaţi este un număr ı̂ntreg ı̂ntre 0 şi 1000
Se garantează că există cel puţin un tip de lanternă pentru care deplasarea să fie posibilă.
Punctajul pentru un test se va acorda ı̂n felul următor:
- 30% : dacă este determinat corect T min
- 100% : dacă sunt determinate corect atât T min, cât şi W min
Exemplu:
lanterna.in lanterna.out
7 10 27 6
1010000
7
1 2 10 3
1455
2 3 10 3
4 3 15 1
3643
6522
5710
Timp maxim de executare: 1 secundă/test
75 close(output);
76 end;
77
78 function MinT(kmax : integer) : integer;
79 var i, j, k, p, q, tN : integer;
80
81 begin
82 for i := 1 to N do
83 for j := 0 to kmax do
84 begin
85 Tmin[i]ˆ[j] := infinit;
86 end;
87
88 Tmin[1]ˆ[0] := 0;
89 inq[1, 0] := true;
90
91 new(cst);
92 cstˆ.i := 1;
93 cstˆ.k := 0;
94 cstˆ.urm := nil;
95 cfin := cst;
96
97 tN := infinit;
98
99 while (cst <> nil) do
100 begin
101 i := cstˆ.i;
102 k := cstˆ.k;
103
104 if (Tmin[i]ˆ[k] < tN) then
105 for j := 1 to N do
106 if (T[i,j] < infinit) and (k + W[i,j] <= kmax) then
107 begin
108 p := Tmin[i]ˆ[k] + T[i,j];
109 q := k + W[i,j];
110
111 if (charge[j]) then
112 q := 0;
113
114 if (p < Tmin[j]ˆ[q]) then
115 begin
116 Tmin[j]ˆ[q] := p;
117
118 if (j = N) and (Tmin[j]ˆ[q] < tN) then
119 tN := Tmin[j]ˆ[q];
120
121 if (not inq[j, q]) and (Tmin[j]ˆ[q] < tN) then
122 begin
123 inq[j, q] := true;
124
125 new(aux);
126 auxˆ.i := j;
127 auxˆ.k := q;
128 auxˆ.urm := nil;
129 cfinˆ.urm := aux;
130 cfin := aux;
131 end;
132 end;
133 end;
134
135 inq[i, k] := false;
136 aux := cst;
137 cst := cstˆ.urm;
138 dispose(aux);
139 end;
140
141 MinT := tN;
142 end;
143
144 begin
145 readdata;
146
147 TOK := MinT(K);
148 bestK := K;
149
150 li := 1; ls := K-1;
CAPITOLUL 20. OJI 2004 20.2. LANTERNA 340
OJI 2003
21.1 Compus
La ultima expediţie pe Marte a fost descoperit un compus organic necunoscut. Acest compus este
acum studiat ı̂n laboratoarele NASA. Cercetătorii au descoperit că acest compus este constituit
numai din atomi de hidrigen (H), ixigen (I) şi carbin (C) şi are masa moleculară M .
Se ştie că regulile de formare a compuşilor organici pe Marte sunt următoarele:
- un atom de carbin se poate lega de oricare dintre atomii de C, H şi I cu oricâte dintre cele 4
legături pe care le are (astfel, ı̂n combinaţia H C C primul atom de carbin se leagă prin două
legături de alt atom de carbin şi cu o legătură de alt atom de hidrigen)
- un atom de hidrigen se poate lega numai de un atom de carbin cu singura legătură pe care
o posedă
- un atom de ixigen se poate lega numai de atomi de carbin cu cele două legături pe care le
posedă
- un compus este un ansamblu cu proprietatea că toţi atomii de carbin sunt legaţi conex ı̂ntre
ei şi nu există vreun atom cu una sau mai multe legături libere (nelegate de un alt atom).
Combinaţia H C C nu este un compus deoarece atomii de carbin mai au legături libere.
Cercetătorii au ı̂n vedere studiul categoriilor de compuşi, făcând distincţie ı̂ntre doi compuşi
numai dacă aceştia diferă prin numărul de atomi de carbin, de ixigen sau de hidrigen.
Cerinţă
Scrieţi un program care să determine câţi compuşi distincţi formaţi din atomi de carbin,
hidrigen şi ixigen (cel puţin unul din fiecare) şi care au masa moleculară M există.
Date de intrare
Fişierul de intrare compus.in conţine pe prima linie masa moleculară a compusului.
Date de ieşire
Fişierul de ieşire compus.out conţine o singură linie pe care se află numărul de compuşi
determinat.
Restricţii şi precizări
30 & M & 1000000
Masa atomului de H este 1, masa atomului de C este 5, iar masa atomului de I este 3. Masa
moleculară a unui compus este egală cu suma maselor atomilor din care este constituit compusul
respectiv.
Ordinea ı̂n care sunt ”utilizate” legăturile unui atom nu contează. De asemenea, nici ordinea
atomilor sau legăturile interne dintre ei nu contează atâta timp cât respectă regulile de formare
enunţate.
Exemple
Există un singur compus cu masa moleculară 10: cel format cu un atom de C, doi atomi de
H si un atom de I (5 2 1 3 10), compus ale cărui legături pot fi reprezentate astfel:
H C I
341
CAPITOLUL 21. OJI 2003 21.1. COMPUS 342
Se pot obţine 3 compuşi cu masa moleculară 40: 5C, 6H, 3I , 6C, 4H, 2I , 7C, 2H, 1I :
H H H H I C C I H C C C
I C C C C C I C C C C C C C C
H H I H H H H I H
Reprezentarea cu legături a oricăruia dintre compuşi nu este unică. Orice altă combinaţie
corespunzătoare aceluiaşi triplet nu se consideră un compus distinct.
Exemple
compus.in compus.out compus.in compus.out
40 3 125 28
Timp maxim de executare/test: 1 secundă
Idei de baza:
-> Avem:
maxim 2c + 2 capete de legaturi disponibile pentru o si h
minim = 4 capete (trebuie sa punem obligatoriu un I si 2 H)
Ideea 1
-------
c intre cMin = (m - 3) / 8 si cMax = (m - 4) / 5
Ideea 2
-------
M = 5*c + 3*i + h = 5 * c + 3 * i’ + 3 + h’ + 2
(am scos separat un I si 2 H care trebuie sa existe oricum in orice compus)
acum i’ >= 0, h’ >=0
M = 5 * c + 3 * i’ + h’ + 5
dar 3 * o’ + h’ = N, deci
0 <= N - i’ <= 2 * c - 2
CAPITOLUL 21. OJI 2003 21.1. COMPUS 343
Algoritmul devine
c intre cMin si cMax
daca n > 0
i intre oMin si oMax
verific daca h = M - 5c - 3i este par si pozitiv
Ideea 3 (compus.pas)
--------------------
h = N - 3*i trebuie sa fie par;
deci N are aceeasi partitate cu 3 * i -> N are aceeasi paritate cu i;
pot sa stabilesc paritatea la inceput si apoi sa cresc din 2 in 2;
asa nu trebuie sa mai verific paritatea lui h
if ((N mod 2) <> (i mod 2)) then inc(i);
h := N - i * 3;
Ideea 4 (compus1.pas)
---------------------
In momentul asta pot sa observ ca while-ul e de fapt degeaba, si se reduce la
if ((N mod 2) <> (i mod 2)) then inc(o);
h := N - o * 3;
23 o := max(0, N - 2 * c + 2);
24 oMax := min(c, N);
25 sMax := 2 * (c - 1);
26
27 if ((N mod 2) <> (o mod 2)) then inc(o);
28 h := N - o * 3;
29
30 while ((h >= 0) and (o <= oMax)) do
31 begin
32 inc(sol);
33 o := o + 2;
34 h := h - 6;
35 end
36 end
37 end;
38 writeln(sol)
39 end.
21.2 Zmeu
Un zmeu cu n capete călătoreşte din poveste ı̂n poveste, iar ı̂n poveştile tradiţionale ı̂ntâlneşte câte
un Făt Frumos care-l mai scurtează de câteva capete, ı̂n timp ce ı̂n poveştile moderne salvează
omenirea mâncând ı̂n timp record, cu toate capetele lui, insecte ucigaşe apărute prin mutaţii
genetice. Într-o seară, el ı̂şi planifică o seccesiune de poveşti cărora să le dea viaţă. El ştie p
poveşti numerotate de la 1 la p, durata fiecăreia şi numărul de capete pe care le pierde ı̂n fiecare
CAPITOLUL 21. OJI 2003 21.2. ZMEU 345
poveste. Mai ştie o mulţime de k perechi de poveşti, semnificând faptul că a doua poveste din
pereche nu poate fi spusă după prima poveste din pereche.
Cerinţă
Ştiind că trebuie să ı̂nceapă cu povestea 1 şi să ı̂ncheie succesiunea cu povestea p, ajutaţi bietul
zmeu să aleagă una sau mai multe poveşti intermediare astfel ı̂ncât durata totală să fie minimă şi
să rămână cu cel puţin un cap la sfârşitul tuturor poveştilor.
Date de intrare
Fişierul de intrare zmeu.in conţine pe prima linie numerele n, p şi k despărţite prin câte un
spaţiu. Pe fiecare din următoarele p linii se află câte o pereche de numere di şi ci (separate prin
câte un spaţiu) ce reprezintă durata şi numărul de capete tăiate pentru fiecare poveste. Iar pe
ultimele k linii se află câte o pereche de numere pi şi pj (separate prin câte un spaţiu) ce semnifică
faptul că povestea pj nu poate fi spusă după povestea pi .
Date de ieşire
Fişierul de ieşire zmeu.out conţine o singură linie pe care se află un număr natural
reprezentând durata (minimă) a succesiunii de poveşti sau valoarea 1 dacă nu există o astfel
de succesiune.
Restricţii şi precizări
2 & N & 500
1 & P & 200
1 & k & 30000
Valorile reprezentând duratele şi numărul de capete sunt numere naturale (duratele fiind strict
pozitive), nedepăşind valoarea 10.
Exemple
zmeu.in zmeu.out
10 4 2 9
26
40
13
33
32
43
Timp maxim de executare/test: 1 secundă
85 new(d[1]ˆ.next);d[1]ˆ.nextˆ.next:=nil;
86 d[1]ˆ.nextˆ.time:=t[1,1];
87 d[1]ˆ.nextˆ.cap:=n-t[2,1];
88 new(c);cˆ.next:=nil;
89 cˆ.pov:=1;pc[1]:=true;
90 u:=c;
91 while c<>nil do begin
92 pov:=cˆ.pov;
93 for i:=1 to p do
94 if a[pov,i]=1 then begin
95 if inser(pov,i) and not pc[i] then begin
96 new(uˆ.next);u:=uˆ.next;
97 uˆ.pov:=i;uˆ.next:=nil;pc[i]:=true
98 end
99 end;
100 pc[pov]:=false;
101 r:=c;c:=cˆ.next;dispose(r)
102 end
103 end;
104
105 begin
106 citeste;
107 calc;
108 min:=maxint;
109 q:=d[p]ˆ.next;
110 while q<>nil do begin
111 if qˆ.time<min then min:=qˆ.time;
112 q:=qˆ.next
113 end;
114 scrie
115 end.
OJI 2002
22.1 Urgenţa
Autorităţile dintr-o zonă de munte intenţionează să stabilească un plan de urgenţă pentru a
reacţiona mai eficient la frecventele calamităţi naturale din zonă. În acest scop au identificat N
puncte de interes strategic şi le-au numerotat distinct de la 1 la N . Punctele de interes strategic
sunt conectate prin M căi de acces având priorităţi ı̂n funcţie de importanţă. Între oricare două
puncte de interes strategic există cel mult o cale de acces ce poate fi parcursă ı̂n ambele sensuri şi
cel puţin un drum (format din una sau mai multe căi de acces) ce le conectează.
În cazul unei calamităţi unele căi de acces pot fi temporar ı̂ntrerupte şi astfel ı̂ntre anumite
puncte de interes nu mai există legătură. Ca urmare pot rezulta mai multe grupuri de puncte ı̂n
aşa fel ı̂ncât ı̂ntre oricare două puncte din acelaşi grup să existe măcar un drum şi ı̂ntre oricare
două puncte din grupuri diferite să nu existe drum.
Autorităţile estimează gravitatea unei calamităţi ca fiind suma priorităţilor căilor de acces
distruse de aceasta şi doresc să determine un scenariu de gravitate maximă, ı̂n care punctele de
interes strategic să fie ı̂mpărţite ı̂ntr-un număr de K grupuri.
Date de intrare
Fişierul de intrare URGENTA.IN are următorul format:
N M K
i1 j1 p1 - ı̂ntre punctele i1 şi j1 există o cale de acces de prioritate p1
i2 j2 p2 - ı̂ntre punctele i2 şi j2 există o cale de acces de prioritate p2
...
iM jM pM - ı̂ntre punctele iM şi jM există o cale de acces de prioritate pM
Date de ieşire
Fişierul de ieşire URGENTA.OUT va avea următorul format:
gravmax - gravitatea maximă
C - numărul de căi de acces ı̂ntrerupte de calamitate
k1 h1 - ı̂ntre punctele k1 şi h1 a fost ı̂ntreruptă calea de acces
k2 h2 - ı̂ntre punctele k2 şi h2 a fost ı̂ntreruptă calea de acces
...
kC hC - ı̂ntre punctele kC şi hC a fost ı̂ntreruptă calea de acces
Restricţii şi precizări
0 $ N $ 256
N 2 $ M $ 32385
0$K $N 1
Priorităţile căilor de acces sunt ı̂ntregi strict pozitivi mai mici decât 256.
Un grup de puncte poate conţine ı̂ntre 1 şi N puncte inclusiv.
Dacă există mai multe soluţii, programul va determina una singură.
Exemplu
348
CAPITOLUL 22. OJI 2002 22.2. NUNTA 349
URGENTA.IN URGENTA.OUT
7 11 4 27
121 8
132 13
173 17
243 24
342 34
351 37
361 45
375 56
455 67
564
673
Timp maxim de executare: 1 secundă / test
22.2 Nunta
În faţa palatului Prinţesei Mofturoase se află N peţitori aşezaţi la coadă, unul ı̂n spatele celuilalt.
Fiecare poartă sub mantie un număr de pietre preţioase pe care doreşte să le ofere prinţesei ca
dar de nuntă. Pentru a nu semăna vrajbă ı̂n rândurile lor, prinţesa a decis să-i determine ca
N 1 dintre ei să renunţe ı̂n chip paşnic, peţitorul rămas devenind alesul prinţesei (indiferent de
numărul de pietre preţioase deţinute de acesta).
Doi peţitori vecini la coadă se pot ı̂nţelege ı̂ntre ei astfel: cel care are mai puţine pietre preţioase
pleacă de la coadă primind de la celălalt un număr de pietre astfel ı̂ncât să plece acasă cu un număr
dublu de pietre faţă de câte avea. Dacă doi peţitori au acelaşi număr de pietre, unul din ei (nu
contează care) pleacă luând toate pietrele vecinului său.
Un peţitor se poate ı̂nţelege la un moment dat cu unul singur dintre cei doi vecini ai săi. După
plecarea unui peţitor, toţi cei din spatele lui avansează.
De exemplu: pentru configuraţia alăturată de 5 peţitori, un şir posibil de negocieri care conduc
la reducerea cozii la un singur peţitor este: se ı̂nţeleg vecinii 4 cu 5 şi pleacă 4, se ı̂nţeleg apoi 1
cu 2 şi pleacă 1, se ı̂nţeleg apoi 3 cu 2 şi pleacă 3, se ı̂nţeleg 2 cu 5 şi pleacă 5. Astfel peţitorul 2
câştigă mâna preafrumoasei prinţese, oferindu-i 0 pietre preţioase ca dar de nuntă.
CAPITOLUL 22. OJI 2002 22.2. NUNTA 350
1 2 3 4 5
1 4 3 6 6
1 2 3 5
1 4 3 0
2 3 5
3 3 0
2 5
0 0
2
Fie P numărul de pietre preţioase pe care le are peţitorul care va deveni alesul prinţesei. Se
cer valorile distincte ale lui P la care se poate ajunge prin toate succesiunile de negocieri posibile.
Fişierul de intrare nunta.in conţine:
- pe prima linie numărul de peţitori: n (1 & n & 50).
- pe a doua linie, n numere naturale din intervalul 0, 20, reprezentând numărul de pietre
preţioase pe care le deţin peţitorii, ı̂n ordinea ı̂n care stau la coadă.
Fişierul de ieşire nunta.out va conţine:
- pe prima linie numărul m de valori distincte ce pot fi obţinute
- pe a doua linie cele m valori ordonate crescător, reprezentând valorile care se pot obţine.
Exemplu:
nunta.in nunta.out
4 3
1426 135
Timp maxim de executare: 1 secundă / test
352
APPENDIX A. PROGRAMA OLIMPIADEI - GIMNAZIU A.3. CLASA A VII-A 353
6. Tablouri bidimensionale
– Prelucrări elementare ale tablourilor bidimensionale (de exemplu, parcurgeri pe linii/-
coloane/diagonale/ı̂n spirală, generări, transpunere, bordare)
– Prelucrări specifice tablourilor bidimensionale pătratice (de exemplu, diagonale şi zone
determinate de diagonale)
– Căutări secvenţiale ı̂n tablouri bidimensionale (de exemplu, a unui element, a unei
secvenţe de valori, a unei submatrice)
– Utilizarea vectorilor de direcţie
7. Simulări
– reprezentarea sistemului de simulat, starea sistemului
– bucla de evenimente ce modifică starea sistemului
– Permutări
– Combinări
– Aranjamente
– Utilizarea funcţiilor din biblioteca STL pentru permutări
A.6 Note
Exceptând clasa a V-a, programa fiecărei clase include şi programele pentru toate clasele
precedente.
Barajul de selecţie a lotului naţional lărgit include programele pentru clasele V-VIII, precum
şi temele suplimentare specificate.
Pentru barajele de selecţie a echipelor reprezentative ale României vor fi abordate teme
suplimentare.
Appendix B
”Instalare” C++
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
355
APPENDIX B. ”INSTALARE” C++ B.1. KIT OJI 2017 356
B.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
36
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
37
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 B. ”INSTALARE” C++ B.1. KIT OJI 2017 357
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
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.
Click-dreapta cu mouse-ul şi selectăm “New -¿ Text document” ca ı̂n figura următoare.
APPENDIX B. ”INSTALARE” C++ B.1. KIT OJI 2017 358
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.
Figura B.9: Pregătit pentru a scrie cod de program C++ ı̂n Code::Blocks
APPENDIX B. ”INSTALARE” C++ B.1. KIT OJI 2017 360
Dacă avem fişierele p01.cpp, ..., p05.cpp, ı̂n folderul nostru de lucru, şi facem dublu-click pe
fiecare ... vor apărea toate ...
B.2 winlibs
B.2.1 GCC şi MinGW-w64 pentru Windows
Se descarcă de la
http://winlibs.com/#download-release
unul dintre fişierele:
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
B.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 B. ”INSTALARE” C++ B.2. WINLIBS 366
Se selecteaza “Path” şi click pe “Edit”. Apare fereastra “Edit Environment Variables”
C:¯path
Dacă totul este OK atunci se trece la instalarea IDE-ului preferat (Integrated Development
38
Environment ), de exemplu Code::Blocks 20.03 (sau Eclipse, Visual Studio Code, Dev C++,
NetBeans, şi altele).
B.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!).
38
https://en.wikipedia.org/wiki/Integrated_development_environment
https://ro.wikipedia.org/wiki/Mediu_de_dezvoltare
APPENDIX B. ”INSTALARE” C++ B.2. WINLIBS 369
int main()
{
fin>>c;
fin>>n;
// instructiuni
return 0;
}
Chiar dacă utilizarea funcţiilor definite de utilizator este prevăzută ı̂ncepând cu clasa a VII-a,
este bine să le folosim ı̂ncă din clasa a V-a! Asta pentru că utilizarea lor permite o mai bună
40
lizibilitate a programelor! Şi oricum ... participanţii la olimpiadă citesc mai mult decât este
41
prevăzut ı̂n programa olimpiadei! Vom folosi modelul de la funcţia main().
,
int calcul()
{
// instructiuni
39
https://sepi.ro/page/oni2022
40
https://ro.wikipedia.org/wiki/Lizibilitate
41
tot ce nu este interzis, este permis!
378
APPENDIX C. MAIN(), CIN, COUT, FIN, FOUT C.1. FUNCŢIA MAIN() 379
return 0;
}
int main()
{
fin>>c;
fin>>n;
calcul();
return 0;
}
Appendix D
Exponenţiere rapidă
În figura D.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)
380
APPENDIX D. EXPONENŢIERE RAPIDĂ D.2. NOTAŢII, RELAŢII ŞI FORMULE 381
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:
~
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;
(D.2.1)
nk nk1 ©2 k ' 1; nk1 j 0 desigur ! ... dar şi nk1 j 1
ak1 , k ' 1;
2
a k
e k 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 ,
D.4 Codul
Codul pentru relaţiile (D.2.1) devine:
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 (D.4.2).
APPENDIX D. EXPONENŢIERE RAPIDĂ D.4. CODUL 383
~
iniţializări:
p1 1;
n 1 n;
a1 a;
calcul pentru k ' 0:
e
k nk1 %2 " r0, 1x; k ' 0 (D.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 D. EXPONENŢIERE RAPIDĂ D.4. CODUL 384
Observaţia 4. Instrucţiunile care sunt ”ı̂n plus” se pot elimina. Codul următor arată acest lucru:
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:
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 */
Numărul de operaţii:
cu metoda naivă acest număr este 2 000 000 000
cu metoda rapidă este 180.
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)!
42
şi nu mai trebuie ”atâtea formule matematice”!
42
Este o glumă!
Appendix E
Căutare binară
E.1 Mijlocul = ?
Care este poziţia (indicele) ”mijlocului” unei zone dintr-un vector?
În figura E.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.
388
APPENDIX E. CĂUTARE BINARĂ E.2. POZIŢIE OARECARE 389
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 }
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 }
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 }
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 E. CĂUTARE BINARĂ E.4. POZIŢIA DIN DREAPTA 395
396
INDEX INDEX 397
[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
[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.
399
BIBLIOGRAFIE BIBLIOGRAFIE 400
[26] Odăgescu, I., Smeureanu, I., Ştefănescu, I.; Programarea avansată a calculatoarelor person-
ale, 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
Adrian Panaete, 84, 99, 122, 179, 207, 218 Lukacs Sandor, 73, 82, 83, 91
Alexandra-Mihaela Nicola, 2
Alexandru Turdean, 34 Marius Stroe, 217, 252
Andrei Constantinescu, 22 Mihaela Cismaru, 19
Andrei-Costin Constantinescu, 4 Mihai Bunget, 4, 15
Mihai Calancea, 72, 75, 93
Bogdan Ciobanu, 39, 66, 79, 81, 90 Mureşan Codruţa, 131
Bogdan Sitaru, 2
Nistor Moţ, 263
Carmen Mincă, 248, 274
Constantin Gălăţan, 265, 274
Constantin Tudor, vi Ovidiu Roşca, 50
Costin-Andrei Oncescu, 26
Pătcaş Csaba, 267, 276
Dan Pracsiu, 235 Panaete Adrian, 182, 241
Doru Anastasiu Popescu, 165, 201
Radu Muntean, 77, 89, 94, 95
Emanuela Cerchez, 262, 318
Stelian Ciurea, 279, 280, 283, 289, 325
Filip Buruiană, 249
Szabo Zoltan, 29, 48, 85, 86, 96, 113, 155,
George Chichirim, 41 174, 212, 241
Gheorghe Dodescu, vi
Tamio-Vesa Nakajima, 13
Ilie Vieru, 288 Theodor-Pierre Moroianu, 52
Iolanda Popa, 279 Tudor-Ştefan Măgirescu, 2
Ion Văduva, vi
Ionel-Vasile Piţ-Rada, 43 Vlad Gavrilă, 186
Irina Dumitrascu, 342 Vlad-Adrian Ulmeanu, 8
402
What’s the next?
ORNL’s Frontier First to Break the Exaflop Ceiling
Over time
the following steps
will lead you to the value
you seek for yourself
now!