Ova stranica je vodič za pripremu za rešavanje zadataka koji se pojavljuju na državnim takmičenjima
iz informatike za osnovne škole. Uz zadatke su pokazani i jednostavni pripremni primeri koji su
sastavni deo većih zadataka. Svaki zadatak ima detaljno uputstvo i C++ rešenje (a gde postoji
efikasnije rešenje – i više njih), koje možeš da prikažeš kada pokušaš da ga rešiš sam. Na kraju strane nalazi se kviz.
U prašumi je nađena čudesna tablica sa 3 reda i 3 kolone ispunjena ciframa. Pored tablice je nađen tekst zagonetke. Zagonetka kaže: „Formirajte prvi trocifreni broj na osnovu cifara sa glavne dijagonale tablice, počev od vrha i idući ka dnu tablice. Zatim formirajte još jedan trocifreni broj koji se sastoji od cifara sa sporedne dijagonale, takođe počevši od vrha do dna. Rešenje zagonetke je proizvod ova dva trocifrena broja.“ Napišite program koji učitava cifre napisane na tablici i ispisuje proizvod dva trocifrena broja koji se formiraju na opisani način.
Pojašnjenje: Glavnu dijagonalu tablice obrazuju redom: prva cifra prvog reda, srednja cifra drugog reda i poslednja cifra trećeg reda. Sporednu dijagonalu obrazuju redom: poslednja cifra prvog reda, srednja cifra drugog reda i prva cifra trećeg reda.
Ulaz
U svakom od tri reda standardnog ulaza date su tri cifre, zapisane bez razmaka.
Izlaz
Na standardnom izlazu program mora da ispiše jedan ceo broj – proizvod dva trocifrena broja koji se dobija na gore opisan način.
Primer
Ulaz
123
456
789
Izlaz
56763
Objašnjenje
Broj formiran od cifara na glavnoj dijagonali je 159, a broj formiran od cifara na sporednoj dijagonali je 357. Njihov proizvod je 56763.
Analiza zadatka
Jedan od načina da se zadatak reši je upotrebom matrica i ugnježdenih petlji. Pošto se ove oblasti često pojavljuju na državnom takmičenju, preporuka je da ih dobro poznajete. Pre rešavanja ovog zadatka preporučuje se da prvo uradite dva pripremna zadatka koja slede.
Pripremni zadatak 1: dijagonale matrice
Učitati 9 brojeva u matricu 3×3. Ispisati vrednosti na glavnoj i na sporednoj dijagonali.
Primer
Ulaz
1 2 3
4 5 6
7 8 9
Izlaz
Glavna dijagonala: 1 5 9
Sporedna dijagonala: 3 5 7
Uputstvo i rešenje
Uputstvo — ideja rešenja
1. Matrica
Koristimo dvodimenzioni niz (matricu) a[3][3] za smeštanje podataka u 3 reda i 3 kolone.
Na glavnoj dijagonali su elementi čiji su redni broj reda i kolone jednaki, tj. a[i][i].
4. Sporedna dijagonala
Na sporednoj dijagonali redni broj kolone je „preslikan“: za red i kolona je 2 − i, tj. a[i][2 - i]. Dovoljna je jedna petlja, bez provere uslova if.
C++ rešenje
#include <iostream>
using namespace std;
int main() {
int a[3][3];
// učitavanje matrice: spoljašnja petlja menja redove, unutrašnja kolone
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
cin >> a[i][j];
}
}
cout << "Glavna dijagonala:";
for (int i = 0; i < 3; i++) {
cout << " " << a[i][i]; // red i, kolona i
}
cout << endl;
cout << "Sporedna dijagonala:";
for (int i = 0; i < 3; i++) {
cout << " " << a[i][2 - i]; // red i, kolona 2 - i
}
cout << endl;
return 0;
}
Objašnjenje: Za matricu veličine n × n glavna dijagonala je a[i][i], a sporedna a[i][n − 1 − i].
Pripremni zadatak 2: sastaviti broj od poznatih cifara
Uneti 4 cela broja koji predstavljaju cifre četvorocifrenog broja. Sastaviti novi broj čije su cifre u obrnutom redosledu.
Primer
Ulaz
4
5
6
7
Izlaz
7654
Uputstvo i rešenje
Uputstvo — ideja rešenja
1. Težina cifara
Poslednja uneta cifra postaje cifra hiljada, pa nju množimo sa 1000. Pretposlednju množimo sa 100, treću od nazad sa 10, a prvu unetu sa 1.
2. Zbir
Traženi broj je zbir ovih proizvoda: 1000·d + 100·c + 10·b + a.
C++ rešenje
#include <iostream>
using namespace std;
int main() {
int a, b, c, d;
cin >> a >> b >> c >> d; // cifre četvorocifrenog broja
// poslednja uneta cifra postaje cifra hiljada, pretposlednja stotina itd.
int broj = 1000 * d + 100 * c + 10 * b + a;
cout << broj << endl;
return 0;
}
Objašnjenje: Isti princip, „cifra puta težina mesta“, koristi se i u glavnom zadatku za formiranje trocifrenih brojeva.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Učitavanje tablice
Tablicu čuvamo u matrici znakova char tablica[3][3]. Cifre su zapisane bez razmaka, ali cin >> znak čita po jedan znak, pa učitavanje radi bez ikakvog dodatnog razdvajanja. Kada bismo cifre učitavali u int, ceo red (npr. 123) bio bi učitan kao jedan broj.
2. Dijagonale
U i-tom redu cifra glavne dijagonale je tablica[i][i], a cifra sporedne dijagonale tablica[i][2 - i].
3. Formiranje brojeva
Broj gradimo cifru po cifru, odozgo nadole: broj = broj * 10 + cifra. Cifru dobijamo iz znaka oduzimanjem znaka nula: znak - '0'.
4. Proizvod
Na kraju ispisujemo proizvod dva dobijena trocifrena broja. Najveći mogući proizvod je 999 · 999 = 998001, što staje u int.
C++ rešenje
#include <iostream>
using namespace std;
int main() {
char tablica[3][3];
// Cifre su zapisane bez razmaka, ali cin >> char čita jedan po jedan znak
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
cin >> tablica[i][j];
}
}
int glavna = 0; // broj od cifara glavne dijagonale
int sporedna = 0; // broj od cifara sporedne dijagonale
for (int i = 0; i < 3; i++) {
// cifra glavne dijagonale u i-tom redu je u koloni i
glavna = glavna * 10 + (tablica[i][i] - '0');
// cifra sporedne dijagonale u i-tom redu je u koloni 2 - i
sporedna = sporedna * 10 + (tablica[i][2 - i] - '0');
}
cout << glavna * sporedna << endl;
return 0;
}
Objašnjenje: Česta greška je čitanje cifara u int matricu – to radi samo kada su cifre razdvojene razmacima. Ovde se cifre čitaju kao znaci, pa rešenje radi za ulaz tačno kakav je dat u zadatku. Takođe nema suvišnog ispisa (praznih redova), zbog kog bi rešenje na takmičenju bilo netačno.
Lenka želi da izračuna zbirove kvadrata neparnih cifara nekoliko brojeva. Pomozi Lenki i napiši program koji sa standardnog ulaza učitava jedan prirodan broj manji od milijardu i na standardni izlaz ispisuje zbir kvadrata njegovih neparnih cifara (ako takvih cifara nema, računamo da je zbir nula). Kvadrat neke cifre jednak je proizvodu te cifre sa samom sobom. Na primer, kvadrat cifre 3 je 9, jer je 3 · 3 = 9.
Ulaz
Sa standardnog ulaza učitava se jedan prirodan broj manji od milijardu.
Izlaz
Na standardni izlaz ispisati zbir kvadrata neparnih cifara tog broja.
Primer 1
Ulaz
1234560
Izlaz
35
Objašnjenje
Neparne cifre su 1, 3 i 5: 1 + 9 + 25 = 35.
Primer 2
Ulaz
11
Izlaz
2
Primer 3
Ulaz
888
Izlaz
0
Objašnjenje
Nema neparnih cifara.
Analiza zadatka
Da bi se izdvojile cifre iz broja, koriste se operatori % i / (ostatak pri deljenju sa 10 i celobrojno deljenje sa 10). Takođe je potrebno znati kako se brojevi sabiraju pomoću petlje.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Učitavanje
Učitamo ceo broj. Zbir kvadrata inicijalizujemo na 0 pre početka petlje.
2. Izdvajanje cifara
Petlja while (broj > 0): cifra na desnom kraju je broj % 10, a broj / 10 uklanja tu cifru. Postupak se ponavlja dok ostatak broja ne postane 0.
3. Provera parnosti
Cifra je neparna ako je cifra % 2 != 0.
4. Zbir
Za svaku neparnu cifru zbiru dodamo njen kvadrat cifra * cifra.
C++ rešenje
#include <iostream>
using namespace std;
int main() {
int broj;
cin >> broj;
int zbir = 0; // zbir kvadrata neparnih cifara, na početku 0
while (broj > 0) {
int cifra = broj % 10; // cifra na desnom kraju
broj = broj / 10; // uklanja cifru na desnom kraju
if (cifra % 2 != 0) { // da li je cifra neparna
zbir += cifra * cifra;
}
}
cout << zbir << endl;
return 0;
}
Objašnjenje: Najveći mogući zbir je 9 · 81 = 729, pa je tip int sasvim dovoljan.
Laza je odlučio da napravi čarobni niz brojeva na sledeći način: počeo je da pravi niz od nekog zadatog broja, a svaki naredni član niza dobija se tako što se prethodnom broju doda broj svih njegovih delilaca. Na primer, ako je neki član niza jednak 6, onda je sledeći član niza 6 + 4, tj. 10, jer postoje tačno 4 delioca broja 6 (to su redom brojevi 1, 2, 3, 6).
Napišite program koji učitava sa standardnog ulaza u prvom redu prvi član niza (0 < a1 ≤ 10000), a u drugom redu prirodan broj N (0 < N ≤ 100) i štampa na standardni izlaz N-ti član Lazinog niza.
Ulaz
U prvom redu je prvi član niza a1 (0 < a1 ≤ 10000), a u drugom redu prirodan broj N (0 < N ≤ 100).
Izlaz
Ispisati N-ti član Lazinog niza.
Primer 1
Ulaz
2
4
Izlaz
9
Objašnjenje
a1 = 2, a2 = 4, a3 = 7, a4 = 9.
Primer 2
Ulaz
3882
93
Izlaz
4878
Analiza zadatka
Zadatak se rešava tako što se u posebnu funkciju izdvoji određivanje broja delilaca datog broja. Za rešavanje je potrebno poznavanje petlji, a opciono i funkcija.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Funkcija za broj delilaca
Napravimo funkciju brojDelilaca(n). Ona petljom prolazi kroz brojeve od 1 do n i broji one koji dele n (uslov n % i == 0).
2. Glavna petlja
Posle učitavanja prvog člana ponavljamo korak N − 1 puta (prvi član je već poznat): novi član je clan + brojDelilaca(clan).
3. Ispis
Posle petlje ispisujemo poslednji dobijeni član.
4. Provera na primeru
Za a1 = 2: 2 ima delioce 1 i 2 (2 + 2 = 4), 4 ima delioce 1, 2, 4 (4 + 3 = 7), 7 ima delioce 1 i 7 (7 + 2 = 9).
C++ rešenje
#include <iostream>
using namespace std;
// Broj delilaca prirodnog broja n
int brojDelilaca(int n) {
int br = 0;
for (int i = 1; i <= n; i++) {
if (n % i == 0) { // i je delilac broja n
br++;
}
}
return br;
}
int main() {
int a1, n;
cin >> a1 >> n;
int clan = a1; // trenutni član niza
for (int i = 1; i < n; i++) { // potrebno je n - 1 koraka do n-tog člana
clan = clan + brojDelilaca(clan);
}
cout << clan << endl;
return 0;
}
Objašnjenje: U funkciji i u glavnom programu ne treba ispisivati pomoćne podatke (sve delioce ili međučlanove) – to bi na takmičenju pokvarilo izlaz. Za brže računanje delilaca može se petlja ograničiti na i * i <= n, pri čemu se svaki delilac i broji zajedno sa parom n / i.
Red čiji je redni broj M (M = 1, 2, 3, ...) formira se zapisivanjem redom prirodnih brojeva od 1 do M, a zatim zapisivanjem brojeva u obratnom smeru od M − 1 do 1. Sada formiramo beskonačni red uzimajući prvi od gore navedenih redova i dodajući mu drugi red, zatim dodajemo treći red i tako dalje. Početak formirane beskonačne serije brojeva izgleda ovako:
Članovi ove serije numerisani su počev od jedan. Napišite program koji pronalazi na kom mestu se dati broj n javlja po k-ti put.
Ulaz
U jedinom redu standardnog ulaza data su dva cela broja n i k, odvojena jednim razmakom (0 < n ≤ 1 000 000, 0 < k ≤ 1 000 000).
Izlaz
Na standardnom izlazu program mora da ispiše ceo broj – poziciju k-te pojave broja n u nizu.
Primer 1
Ulaz
1 1
Izlaz
1
Primer 2
Ulaz
2 2
Izlaz
6
Objašnjenje
Niz je 1 1 2 1 1 2 3 2 1 .... Broj 2 je prvi put na mestu 3, a drugi put na mestu 6.
Primer 3
Ulaz
3 3
Izlaz
14
Analiza zadatka
Uočimo pravilo. Red sa rednim brojem M ima 2M − 1 članova, pa je posle prvih M redova u nizu ukupno M² članova (1 + 3 + 5 + ... + (2M − 1) = M²).
Red M
Sadržaj reda
Dužina reda
Pozicija poslednjeg člana u nizu
1
1
1
1
2
1 2 1
3
4
3
1 2 3 2 1
5
9
4
1 2 3 4 3 2 1
7
16
5
1 2 3 4 5 4 3 2 1
9
25
6
1 2 3 4 5 6 5 4 3 2 1
11
36
Broj n se ne javlja u redovima manjim od n. U redu n javlja se tačno jednom (u sredini), a u svakom narednom redu M > n javlja se tačno dvaput: na n-tom mestu reda i na mestu 2M − n.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Prva pojava
Prva pojava broja n je u sredini reda n. Pre tog reda ima (n − 1)² članova, pa je pozicija (n − 1)² + n. Za k = 1 to je odgovor.
2. Ostale pojave
Posle reda n svaki red ima po dve pojave: prva (leva) je na poziciji (M − 1)² + n, a druga (desna) na poziciji (M − 1)² + 2M − n, gde je M redni broj reda.
3. Koji red
Za k ≥ 2 neka je j = k − 1 broj pojava posle prve. Pojave j = 1, 2 su u redu n + 1, pojave j = 3, 4 u redu n + 2 itd., dakle red je M = n + (j + 1) / 2. Ako je j neparno, tražena je leva pojava, a ako je parno desna.
4. Simulacija ili formula
Rešenje 1 prolazi red po red i „troši“ pojave (po dve u svakom redu), a rešenje 2 direktno računa red i poziciju formulom, bez petlje.
5. Tip podataka
Pozicija može biti reda (1,5·106)² ≈ 2,25·1012, što ne staje u int. Zato koristimo long long.
Rešenje 1: red po red (simulacija)
#include <iostream>
using namespace std;
int main() {
long long n, k;
cin >> n >> k;
// Red M ima 2M - 1 članova, pa je pre reda M ukupno (M-1)^2 članova.
// Broj n se prvi put javlja u redu n (samo jednom, u sredini),
// a u svakom narednom redu M > n javlja se po dva puta:
// na mestu n (sleva) i na mestu 2M - n.
long long red = n; // red u kome tražimo pojavljivanje
long long preostalo = k - 1; // pojavljivanja posle prvog (u redu n)
while (preostalo > 0) {
red++; // prelazimo u sledeći red, koji ima 2 pojavljivanja
if (preostalo >= 2) {
preostalo -= 2; // preskačemo oba pojavljivanja iz ovog reda
} else {
break; // tražena pojava je prva u ovom redu
}
}
long long pre = (red - 1) * (red - 1); // broj članova pre ovog reda
long long pozicija;
if (red == n) {
pozicija = pre + n; // jedina pojava u sredini reda n
} else if (preostalo == 1) {
pozicija = pre + n; // leva pojava u redu
} else {
pozicija = pre + 2 * red - n; // desna pojava u redu
}
cout << pozicija << endl;
return 0;
}
Objašnjenje: Petlja se izvršava najviše oko k / 2 puta, što je za k ≤ 106 sasvim dovoljno brzo. Česta greška je korišćenje tipa int – za velike ulaze dolazi do prekoračenja i rezultat je pogrešan.
Rešenje 2: formula
#include <iostream>
using namespace std;
int main() {
long long n, k;
cin >> n >> k;
long long pozicija;
if (k == 1) {
// prva pojava: sredina reda n
pozicija = (n - 1) * (n - 1) + n;
} else {
long long j = k - 1; // koliko pojava posle prve
long long red = n + (j + 1) / 2; // po dve pojave u svakom redu posle n-tog
long long pre = (red - 1) * (red - 1);
if (j % 2 == 1) {
pozicija = pre + n; // neparno j: leva pojava u redu
} else {
pozicija = pre + 2 * red - n; // parno j: desna pojava u redu
}
}
cout << pozicija << endl;
return 0;
}
Objašnjenje: Rešenje radi u konstantnom vremenu. Provera: za n = 3, k = 3 je j = 2, M = 3 + 1 = 4, j je parno, pa je pozicija 3² + 2·4 − 3 = 14.
Napravimo funkciju koja vraća broj delilaca prosleđenog broja: petljom menjamo delilac d od 1 do samog broja i brojimo one za koje je broj deljiv sa d. U glavnom delu petljom prolazimo od a do b i brojimo one brojeve za koje funkcija vrati neparnu vrednost. Ovo je sporo već za male granice (za b = 1015 bi trajalo godinama).
II način: optimizovan
Delioci se grupišu u parove (d, x / d). Parovi uvek imaju dva različita broja, osim kada je d = x / d, tj. kada je x potpun kvadrat. Dakle neparan broj delilaca imaju samo potpuni kvadrati. Umesto brojanja delilaca, napravimo funkciju koja proverava da li je broj potpun kvadrat. I dalje prolazimo kroz sve brojeve od a do b.
III način: potpuno optimizovan
Ne proveravamo sve brojeve od a do b, nego odredimo najmanji i najveći ceo broj čiji se kvadrat nalazi u intervalu. Zatim brojimo cele brojeve između njih. Petlja ima oko √b ≈ 3·107 koraka, što je prihvatljivo.
IV način: formula
Potpunih kvadrata u [1, x] ima ⌊√x⌋, pa u [a, b] ih ima ⌊√b⌋ − ⌊√(a − 1)⌋. Ovo se računa u konstantnom vremenu.
Pažnja pri korenovanju
Funkcija sqrt radi sa realnim brojevima i za velike vrednosti može dati rezultat koji je za 1 manji ili veći od tačnog. Zato dobijeni ceo broj k proveravamo i korigujemo dok ne važi k² ≤ x < (k + 1)². Za brojeve do 1015 proizvodi staju u long long.
I način (gruba sila)
#include <iostream>
using namespace std;
// Broj delilaca broja x (metoda grube sile)
long long brojDelilaca(long long x) {
long long br = 0;
for (long long d = 1; d <= x; d++) {
if (x % d == 0) br++;
}
return br;
}
int main() {
long long a, b;
cin >> a >> b;
long long brojac = 0;
for (long long x = a; x <= b; x++) {
if (brojDelilaca(x) % 2 == 1) { // neparan broj delilaca
brojac++;
}
}
cout << brojac << endl;
return 0;
}
Objašnjenje: Tačno, ali veoma sporo – dovoljno samo za najmanje test primere.
II način (provera kvadrata)
#include <iostream>
#include <cmath>
using namespace std;
// Samo potpuni kvadrati imaju neparan broj delilaca
bool potpunKvadrat(long long x) {
long long k = (long long)sqrtl((long double)x);
// ispravka eventualne greške pri zaokruživanju
while (k * k > x) k--;
while ((k + 1) * (k + 1) <= x) k++;
return k * k == x;
}
int main() {
long long a, b;
cin >> a >> b;
long long brojac = 0;
for (long long x = a; x <= b; x++) {
if (potpunKvadrat(x)) brojac++;
}
cout << brojac << endl;
return 0;
}
Objašnjenje: Brže od prvog načina, ali i dalje proverava sve brojeve do b.
III način (samo kvadrati)
#include <iostream>
#include <cmath>
using namespace std;
// Najveći ceo broj k takav da je k * k <= x
long long celiKoren(long long x) {
long long k = (long long)sqrtl((long double)x);
while (k * k > x) k--;
while ((k + 1) * (k + 1) <= x) k++;
return k;
}
int main() {
long long a, b;
cin >> a >> b;
// najmanji k sa k * k >= a
long long prvi = celiKoren(a);
if (prvi * prvi < a) prvi++;
// najveći k sa k * k <= b
long long poslednji = celiKoren(b);
// proveravamo samo kvadrate od prvi * prvi do poslednji * poslednji
long long brojac = 0;
for (long long k = prvi; k <= poslednji; k++) {
brojac++; // k * k je u intervalu [a, b]
}
cout << brojac << endl;
return 0;
}
Objašnjenje: Prolazi samo kroz brojeve k čiji je kvadrat u intervalu.
IV način (formula)
#include <iostream>
#include <cmath>
using namespace std;
long long celiKoren(long long x) {
if (x <= 0) return 0;
long long k = (long long)sqrtl((long double)x);
while (k * k > x) k--;
while ((k + 1) * (k + 1) <= x) k++;
return k;
}
int main() {
long long a, b;
cin >> a >> b;
// Kvadrata u [1, b] ima celiKoren(b), a u [1, a - 1] ima celiKoren(a - 1)
cout << celiKoren(b) - celiKoren(a - 1) << endl;
return 0;
}
Sećate se „Bašte sljezove boje“, divne knjige Branka Ćopića i avantura malog Baje, samardžije Petraka, Bajinog đeda... Mali Baja ima nekoliko kartica i na svakoj kartici je upisan ceo broj između 1 i 10. Baja i stari Petrak krenuli su u pohod na Mesec. Za to vreme đed Rade, koji baš ne vidi dobro, poređao je kartice u niz.
Kada se Baja vratio sa neuspešnog pohoda na Mesec, započeo je deljenje kartica. U svakom deljenju želeo je da složi dobijene kartice od najmanje do najveće (ako ima kartica koje se ponavljaju, te kartice se nalaze jedna uz drugu). Pomozite Baji tako što ćete napisati program koji izračunava koliko puta je Baja pogrešio i nije složio karte kako je želeo.
Ulaz
Iz prvog reda standardnog ulaza čita se ceo broj N (1 < N < 100) – broj deljenja koje je Baja igrao, zatim broj k (2 < k < 10) – broj karata u svakom deljenju, a zatim za svako deljenje po k karata navedenih onako kako ih je Baja složio. Svaki broj je naveden u zasebnom redu.
Izlaz
U jednom redu standardnog izlaza program treba da prikaže broj deljenja u kojima Baja nije dobro složio kartice.
Primer
Ulaz
4
3
1
2
3
4
3
4
1
5
5
8
8
8
Izlaz
1
Objašnjenje
Učitani su podaci za 4 deljenja (po 3 karte). Dobro su složena prvo, treće i četvrto deljenje. Nije dobro složeno drugo deljenje sa karticama 4, 3, 4.
Spoljašnja petlja ide kroz N deljenja, a unutrašnja učitava k karata. Svaku kartu poredimo sa prethodnom iz istog deljenja. Ako je tekuća manja od prethodne, deljenje je neispravno. Važno je da se i posle greške učitaju sve preostale karte tog deljenja, da bi se ulaz ispravno čitao.
II način: vektori
Karte jednog deljenja učitamo u vektor. Napravimo kopiju vektora i sortiramo samo kopiju. Ako se kopija razlikuje od originala, original nije bio sortiran, pa je Baja pogrešio.
Brojanje
U oba načina brojač neispravnih deljenja povećavamo za 1 svaki put kada deljenje nije neopadajuće složeno.
I način (ugnježdene petlje)
#include <iostream>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
int neispravnih = 0;
for (int deljenje = 0; deljenje < n; deljenje++) {
int prethodna;
cin >> prethodna; // prva karta u deljenju
bool uredu = true;
for (int i = 1; i < k; i++) {
int tekuca;
cin >> tekuca; // učitavamo sve karte, čak i posle greške
if (tekuca < prethodna) { // nije neopadajuće
uredu = false;
}
prethodna = tekuca;
}
if (!uredu) neispravnih++;
}
cout << neispravnih << endl;
return 0;
}
Objašnjenje: Neopadajući poredak znači da je svaka karta veća ili jednaka prethodnoj, zato se greška otkriva uslovom tekuca < prethodna.
II način (vektori)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
int neispravnih = 0;
for (int deljenje = 0; deljenje < n; deljenje++) {
vector<int> karte(k);
for (int i = 0; i < k; i++) cin >> karte[i];
vector<int> sortirane = karte; // kopija originalnog niza
sort(sortirane.begin(), sortirane.end()); // sortiramo samo kopiju
if (sortirane != karte) { // različiti => Baja je pogrešio
neispravnih++;
}
}
cout << neispravnih << endl;
return 0;
}
Objašnjenje: Poređenje dva vektora operatorom != poredi ih element po element.
Dinosaurus Dino uživa na turniru koji je organizovao poznati internet pregledač. Sećate se igre sa dinosaurusom, tj. igre sakupljanja kaktusa. Svaki učesnik u igri ima pravo da sakuplja redom uzastopne kaktuse, poređane duž prave linije. Državna komisija treba da odredi prag za prolazak takmičara sa prvog nivoa igre na sledeći nivo gledajući isključivo broj skupljenih kaktusa. U ovoj igri učestvuje veliki broj takmičara.
Dino održava tabelu sa poenima i takmičari ga stalno pitaju koliko bi takmičara prošlo dalje kada bi donja granica prolaza bila toliko i toliko poena (dalje se plasiraju svi učesnici čiji je broj poena veći ili jednak donjoj granici). Dino je odlučio da napiše program koji daje odgovor na ta pitanja.
Ulaz
Sa standardnog ulaza učitava se broj takmičara n (1 ≤ n ≤ 50000), a zatim i poeni takmičara (prirodni brojevi), zadati u sortiranom redosledu od najvećeg do najmanjeg i razdvojeni razmacima. Nakon toga se učitava broj m (1 ≤ m ≤ 50000) koji predstavlja broj pitanja na koja Dino treba da odgovori, a zatim i m brojeva razdvojenih razmacima za koje je potrebno dati odgovor koliko bi se takmičara plasiralo kada bi se taj broj uzeo za donju granicu.
Izlaz
Na standardni izlaz ispisati tražene brojeve takmičara koji su se plasirali, u posebnom redu za svaku donju granicu.
Primer
Ulaz
5
79 63 63 46 13
4
85 40 60 5
Izlaz
0
4
3
5
Objašnjenje
Ako je prag 85 poena, niko se nije plasirao. Ako je prag 40 poena, plasirali su se takmičari sa 79, 63, 63 i 46 poena. Ako je prag 60 poena, plasirali su se takmičari sa 79, 63 i 63 poena. Ako je prag 5 poena, svi takmičari su se plasirali.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Učitavanje
Učitamo n i niz poena (opadajući poredak), zatim m i za svako pitanje prag.
2. Šta se traži
Pošto su poeni sortirani od najvećeg ka najmanjem, takmičari koji prolaze čine početak niza. Traženi broj je indeks prve pozicije na kojoj je broj poena manji od praga (ili n ako takve pozicije nema).
3. Linearna pretraga
Za svako pitanje krećemo od početka niza i brojimo takmičare dok je poeni[i] >= prag. U najgorem slučaju to je n · m = 2,5·109 koraka.
4. Binarna pretraga
Zbog sortiranosti možemo da prepolovimo niz u svakom koraku: ako je srednji element ≥ prag, svi levo od njega takođe prolaze, pa traženu granicu tražimo desno; inače levo. Svako pitanje tada traje O(log n).
Rešenje 1: linearna pretraga
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> poeni(n); // poeni su sortirani od najvećeg ka najmanjem
for (int i = 0; i < n; i++) cin >> poeni[i];
int m;
cin >> m;
for (int q = 0; q < m; q++) {
int prag;
cin >> prag;
// linearna pretraga: brojimo takmičare sa poenima >= prag
int broj = 0;
while (broj < n && poeni[broj] >= prag) {
broj++;
}
cout << broj << "\n";
}
return 0;
}
Objašnjenje: Jednostavno, ali u najgorem slučaju sporije.
Rešenje 2: binarna pretraga
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> poeni(n); // opadajući poredak
for (int i = 0; i < n; i++) cin >> poeni[i];
int m;
cin >> m;
for (int q = 0; q < m; q++) {
int prag;
cin >> prag;
// binarna pretraga prve pozicije na kojoj je poeni[pozicija] < prag
int levo = 0, desno = n; // traženi indeks je u [levo, desno]
while (levo < desno) {
int sredina = (levo + desno) / 2;
if (poeni[sredina] >= prag) {
levo = sredina + 1; // prolaze i svi levo od sredine, tražimo dalje desno
} else {
desno = sredina; // sredina je već ispod praga
}
}
cout << levo << "\n"; // levo je ujedno broj takmičara koji prolaze
}
return 0;
}
Objašnjenje: Na kraju petlje levo je prva pozicija sa poenima manjim od praga, a to je ujedno i broj takmičara koji prolaze.
Poznate su visine svih učenika jedne škole. Napiši program koji određuje najvišeg dečaka i najvišu devojčicu u toj školi, određuje ko je od njih viši i ispisuje razliku njihovih visina.
Ulaz
Sa standardnog ulaza unosi se broj učenika (5 ≤ n ≤ 100), a zatim u svakom od n narednih redova podaci za po jednog učenika. Za svakog učenika unosi se visina (ceo broj između 120 i 200) i oznaka pola (m za muški i z za ženski pol), razdvojeni jednim razmakom. Pretpostaviti da postoji bar jedan dečak i bar jedna devojčica.
Izlaz
Ako je najviša devojčica viša od najvišeg dečaka, na standardni izlaz ispisati z, razmak i zatim za koliko je viša. Ako je najviši dečak viši od najviše devojčice, ispisati m, razmak i zatim za koliko je viši. Ako su iste visine, ispisati samo =.
Primer
Ulaz
5
152 z
174 m
165 z
172 m
170 z
Izlaz
m 4
Objašnjenje
Najviši dečak je visok 174, a najviša devojčica 170, pa je dečak viši za 4.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Učitavanje
Učitamo n i petljom n puta učitamo visinu (int) i pol (char).
2. Dva maksimuma
Pamtimo posebno najveću visinu dečaka i posebno najveću visinu devojčica. Za svakog učenika, u zavisnosti od pola, ažuriramo odgovarajući maksimum.
3. Poređenje
Na kraju poredimo dva maksimuma i ispisujemo traženu poruku: oznaku pola višeg učenika i razliku, ili znak =.
C++ rešenje
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int najvisiDecak = 0, najvisaDevojcica = 0;
for (int i = 0; i < n; i++) {
int visina;
char pol;
cin >> visina >> pol;
if (pol == 'm') {
if (visina > najvisiDecak) najvisiDecak = visina;
} else if (pol == 'z') {
if (visina > najvisaDevojcica) najvisaDevojcica = visina;
}
}
if (najvisiDecak > najvisaDevojcica) {
cout << "m " << najvisiDecak - najvisaDevojcica << endl;
} else if (najvisaDevojcica > najvisiDecak) {
cout << "z " << najvisaDevojcica - najvisiDecak << endl;
} else {
cout << "=" << endl;
}
return 0;
}
Objašnjenje: Početna vrednost 0 za oba maksimuma je sigurna jer su sve visine veće od 120.
Nizom a date su dužine n poglavlja jedne knjige. Elementom a0 dat je broj stranica za prvo poglavlje, elementom a1 za drugo i tako redom. Potrebno je knjigu podeliti na dva dela, tako da se ukupni brojevi strana u ta dva dela najmanje razlikuju. Podela se vrši iza nekog od poglavlja. Ako postoje dve moguće podele sa istom najmanjom razlikom, podela se vrši nakon ranijeg od dva poglavlja (tako da prvi deo bude kraći). Napiši program koji određuje nakon kog poglavlja treba izvršiti podelu.
Ulaz
U prvoj liniji standardnog ulaza nalazi se prirodan broj n (1 < n ≤ 50000). U sledećih n linija nalazi se po jedan prirodan broj (između 1 i 1000) – brojevi stranica svakog poglavlja.
Izlaz
Na standardnom izlazu prikazati u jednoj liniji redni broj poglavlja (poglavlja se broje od nule) posle kog treba izvršiti podelu knjige.
Primer
Ulaz
5
100
120
50
150
70
Izlaz
1
Objašnjenje
Kada se podela izvrši posle poglavlja broj 1 (to je drugo poglavlje), prvi deo knjige sadrži 220, a drugi 270 strana, što daje najmanju moguću razliku od 50 strana. Podela posle poglavlja broj 2 (treće poglavlje) takođe bi dala razliku 50 (270 i 220), ali po uslovima zadatka tada se bira da prvi deo knjige bude kraći, pa je rešenje 1.
Analiza zadatka
Neka je levo[i] broj strana u poglavljima od 0 do i, a desno[i] broj strana u svim poglavljima posle poglavlja i. Za primer iz zadatka dobijamo:
i
0
1
2
3
4
poglavlje a[i]
100
120
50
150
70
levo[i]
100
220
270
420
490
desno[i]
390
270
220
70
0
desno[i] − levo[i]
290
50
−50
−350
−490
Traži se podela za koju je apsolutna vrednost poslednjeg reda najmanja. Vrednosti se smanjuju do prelaska iz pozitivnih u negativne, a posle toga rastu po apsolutnoj vrednosti, pa je minimum uvek oko tog prelaza. Kada su dve vrednosti jednake po apsolutnoj vrednosti (kolone 1 i 2), uzima se ranija.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Ukupan broj strana
Učitamo poglavlja u niz i usput sabiramo ukupan broj strana ukupno.
2. Razlika za jednu podelu
Ako je u prvom delu (poglavlja 0 do i) levo strana, u drugom je ukupno − levo. Razlika je |ukupno − 2·levo|.
3. Najbolja podela
Za svako i od 0 do n − 2 (iza poslednjeg poglavlja se ne deli) povećavamo levo i računamo razliku. Pamtimo najmanju razliku i poglavlje na kome je dobijena.
4. Jednake razlike
Poglavlje menjamo samo kada je razlika strogo manja od dotadašnje najmanje. Tako pri jednakosti ostaje ranije poglavlje, kako zadatak zahteva.
C++ rešenje
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> strane(n);
int ukupno = 0;
for (int i = 0; i < n; i++) {
cin >> strane[i];
ukupno += strane[i];
}
int levo = 0; // broj strana u poglavljima 0..i
int najmanjaRazlika = ukupno + 1;
int najboljePoglavlje = 0;
// podela se vrši iza poglavlja i, pa je poslednja moguća podela iza n - 2
for (int i = 0; i < n - 1; i++) {
levo += strane[i];
int desno = ukupno - levo;
int razlika = abs(desno - levo);
// strogo manja razlika: pri jednakosti ostaje raniji (kraći prvi deo)
if (razlika < najmanjaRazlika) {
najmanjaRazlika = razlika;
najboljePoglavlje = i;
}
}
cout << najboljePoglavlje << endl;
return 0;
}
Objašnjenje: Nisu potrebni pomoćni nizovi niti posebna obrada slučajeva: dovoljni su jedna petlja i tekući zbir. Rešenje pravilno rešava i jednake razlike (ostaje raniji) i radi u vremenu O(n).
Napiši program koji među unetim brojevima pronalazi one kojima su cifre strogo rastuće, gledajući od cifre najveće težine (prve cifre sleva).
Ulaz
Sa standardnog ulaza učitava se broj N (1 ≤ N ≤ 5000), a zatim i N prirodnih brojeva većih ili jednakih 10 i manjih ili jednakih 109, svaki u posebnom redu.
Izlaz
Na standardni izlaz ispisati tražene brojeve sa rastućim ciframa, svaki u posebnom redu.
Primer
Ulaz
3
123
222
321
Izlaz
123
Uputstvo i rešenja
Uputstvo — ideja rešenja
Ideja
Cifre su strogo rastuće gledano sleva udesno kada je svaka cifra strogo manja od cifre desno od nje. Ekvivalentno, gledano zdesna ulevo, cifre su strogo opadajuće.
Rešenje 1: cifre sa desna
Cifre izdvajamo od poslednje (x % 10), a x /= 10 je uklanja. Pamtimo prethodnu (desnu) cifru, na početku je to vrednost 10, veća od svake cifre. Ako je tekuća cifra veća ili jednaka prethodnoj, brojevi nisu rastući i petlju prekidamo. Time se izbegava računanje broja cifara.
Rešenje 2: niska
Broj učitamo kao nisku. Susedne znakove poredimo operatorom <=: znakovi cifara su poređani istim redom kao i cifre, pa je poređenje ispravno.
Rešenje 1: cifre sa desna
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int broj;
cin >> broj;
// Idemo od cifre najmanje težine (zdesna ulevo). Cifre su strogo rastuće
// gledano sleva, ako su strogo opadajuće gledano zdesna.
int x = broj;
int prethodna = 10; // veća od svake cifre
bool rastuce = true;
while (x > 0) {
int cifra = x % 10;
if (cifra >= prethodna) { // leva cifra nije manja od desne
rastuce = false;
break;
}
prethodna = cifra;
x /= 10;
}
if (rastuce) {
cout << broj << "\n";
}
}
return 0;
}
Objašnjenje: Stariji pristup pomoću funkcija log10 i pow (broj cifara i stepen broja 10) radi sa realnim brojevima i nepotrebno je složen. Izdvajanje cifara zdesna ne zahteva ni jedno ni drugo.
Rešenje 2: broj kao niska
#include <iostream>
#include <string>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
string broj;
cin >> broj; // broj učitavamo kao nisku cifara
bool rastuce = true;
for (size_t j = 1; j < broj.size(); j++) {
if (broj[j] <= broj[j - 1]) { // cifre se porede kao znakovi
rastuce = false;
break;
}
}
if (rastuce) {
cout << broj << "\n";
}
}
return 0;
}
Objašnjenje: Kratko i pregledno. Pošto su svi brojevi ≤ 109, nema problema ni sa granicama tipova.
U jednoj školi biraju se glumci za školsku predstavu u kojoj likovi koje tumače imaju veliku razliku u visini. Napiši program koji određuje na koliko načina možemo da odaberemo dva glumca iz odeljenja tako da im je razlika visina jednaka datom broju r.
Ulaz
Sa standardnog ulaza unosi se prvo pozitivan prirodan broj r (1 ≤ r ≤ 1000), u narednom redu broj učenika n (1 ≤ n ≤ 50000), a nakon toga u narednih n redova visina svakog učenika (broj između 1 i 100000).
Napomena. U 20 od 25 test-primera sva deca će biti različite visine.
Izlaz
Na standardni izlaz ispisati broj parova koje je moguće formirati.
Primer 1
Ulaz
10
5
150
160
165
170
175
Izlaz
3
Objašnjenje
Moguće je napraviti parove od prvog i drugog deteta (150, 160), od drugog i četvrtog deteta (160, 170) i trećeg i petog deteta (165, 175).
Primer 2
Ulaz
23
5
157
180
157
162
134
Izlaz
4
Objašnjenje
Moguće je napraviti parove od prvog i drugog deteta (157, 180), od petog i prvog (134, 157), od drugog i trećeg (157, 180) i od petog i trećeg deteta (134, 157).
Analiza zadatka
Ako se visine učitaju kao niz, dobro je prvo ga sortirati. Za svaki par visina treba proveriti da li im je razlika jednaka r. To se može uraditi sa dve ugnježdene petlje ili efikasnije, tako što se za fiksiranu prvu visinu druga traži binarnom pretragom, ili tako što se visine unapred prebroje.
Uputstvo i rešenja
Uputstvo — ideja rešenja
Rešenje 1: dve petlje
Sortiramo visine rastuće. Za svaku visinu v[i] prolazimo kroz sledeće v[j]. Ako je razlika jednaka r, uvećamo broj parova, a ako je veća od r, prekidamo unutrašnju petlju jer će sve sledeće razlike biti još veće. Za 25 test-primera sa velikim brojem istih visina ovo može biti sporo.
Rešenje 2: binarna pretraga
Sortiramo visine. Za svaku visinu v[i] tražimo koliko ima elemenata jednakih v[i] + r. Pošto je niz sortiran, ti elementi čine jedan neprekidan blok, a funkcija equal_range binarnom pretragom nalazi njegov početak i kraj. Broj elemenata bloka dodajemo rezultatu. Ovako se pravilno broje i ponovljene visine.
Rešenje 3: brojanje
Visine su ograničene na 100000, pa možemo prebrojati koliko učenika ima svaku visinu u nizu broj[h]. Za svaku visinu h broj parova (h, h + r) je broj[h] · broj[h + r]. Rezultat sabiramo po svim visinama. Nije potrebno sortiranje.
Tip podataka
Broj parova može da bude i veći od 109 (npr. ako polovina učenika ima jednu visinu, a druga polovina visinu za r veću), pa brojač treba da bude long long.
Rešenje 1: dve petlje
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int r, n;
cin >> r >> n;
vector<int> v(n);
for (int i = 0; i < n; i++) cin >> v[i];
sort(v.begin(), v.end()); // rastući poredak visina
long long parova = 0;
for (int i = 0; i < n - 1; i++) {
for (int j = i + 1; j < n; j++) {
int razlika = v[j] - v[i];
if (razlika == r) {
parova++;
} else if (razlika > r) {
break; // sve dalje razlike su još veće
}
}
}
cout << parova << endl;
return 0;
}
Objašnjenje: Najjednostavnije rešenje, ali u najgorem slučaju kvadratno.
Rešenje 2: binarna pretraga
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int r, n;
cin >> r >> n;
vector<int> v(n);
for (int i = 0; i < n; i++) cin >> v[i];
sort(v.begin(), v.end());
long long parova = 0;
for (int i = 0; i < n; i++) {
// binarnom pretragom nalazimo sve elemente jednake v[i] + r
// (oni su uvek desno od i, jer je r >= 1)
auto opseg = equal_range(v.begin(), v.end(), v[i] + r);
parova += opseg.second - opseg.first; // koliko ih ima
}
cout << parova << endl;
return 0;
}
Objašnjenje: Česta greška je da se deca iste visine traže ručnim pomeranjem indeksa, što može da pročita element van niza. Funkcija equal_range to rešava bezbedno. Složenost je O(n log n).
Rešenje 3: prebrojavanje po visinama
#include <iostream>
#include <vector>
using namespace std;
int main() {
int r, n;
cin >> r >> n;
const int MAKS = 100000; // najveća moguća visina
vector<long long> broj(MAKS + 1, 0); // broj[h] = koliko učenika ima visinu h
for (int i = 0; i < n; i++) {
int h;
cin >> h;
broj[h]++;
}
// Za svaku visinu h, parova (h, h + r) ima broj[h] * broj[h + r]
long long parova = 0;
for (int h = 1; h + r <= MAKS; h++) {
parova += broj[h] * broj[h + r];
}
cout << parova << endl;
return 0;
}
Objašnjenje: Rešenje radi u linearnom vremenu O(n + maksimalna visina). Pošto je r ≥ 1, par se nikada ne sastoji od istog učenika.
Poznate su sve cene predmeta koji se prodaju u jednoj prodavnici. Kupac ima na raspolaganju određeni iznos dinara i želi da kupi što skuplje predmete. Redom uzima predmete počev od najskupljeg, dok ima novca. Ako nema novca za najskuplji, uzima najskuplji za koji ima novca.
Napomena. Ova strategija ne garantuje da će predmeti koje kupac kupi biti ukupno najveće moguće vrednosti (na primer, ako ima 5 dinara i ako su cene predmeta 4, 3 i 2 dinara, kupiće samo predmet od 4 dinara, a mogao bi da kupi predmete od 3 i 2 dinara).
Napiši program koji određuje koliko novca kupcu preostaje nakon kupovine na opisani način.
Ulaz
U prvoj liniji standardnog ulaza nalazi se iznos novca (ceo broj) koji ima kupac, u drugoj broj predmeta N (prirodan broj manji od 50000), a zatim se u narednoj liniji unose redom cene predmeta, razdvojene razmacima.
Izlaz
Na standardni izlaz ispisati preostali iznos novca nakon kupovine na opisani način.
Primer 1
Ulaz
1250
5
1010 357 725 1125 115
Izlaz
10
Objašnjenje
Kupac kupuje predmet od 1125 (preostaje 125), zatim ne može da kupi 1010, 725 i 357, a predmet od 115 kupuje (preostaje 10).
Primer 2
Ulaz
10000
6
3010 3005 5725 1265 2075 385
Izlaz
0
Analiza zadatka
Algoritam zadatka prikazan je na slici. Niz sa cenama sortira se po opadajućem redosledu. Ostatak je na početku jednak novcu kojim kupac raspolaže. Zatim se niz obilazi redom: ako je cena predmeta manja ili jednaka tekućem ostatku, predmet se „kupuje“, tj. cena se oduzima od ostatka. Inače se predmet preskače.
Slika 1: Priprema za državna takmičenja 2018: zadatak „Kupovina“ – algoritam
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Sortiranje
Cene sortiramo opadajuće, da bismo ih obrađivali od najskupljeg ka najjeftinijem. U C++ za to služi sort sa funkcijom poređenja greater<int>().
2. Obilazak
Za svaki predmet proveravamo da li je njegova cena manja ili jednaka preostalom novcu. Ako jeste, oduzimamo je od ostatka.
3. Ispis
Posle obilaska svih predmeta ispisujemo ostatak.
C++ rešenje
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main() {
int novac, n;
cin >> novac >> n;
vector<int> cene(n);
for (int i = 0; i < n; i++) cin >> cene[i];
// od najskupljeg ka najjeftinijem
sort(cene.begin(), cene.end(), greater<int>());
int ostatak = novac;
for (int i = 0; i < n; i++) {
if (cene[i] <= ostatak) { // ako ima dovoljno novca, predmet se kupuje
ostatak -= cene[i];
}
}
cout << ostatak << endl;
return 0;
}
Objašnjenje: Svaki predmet se razmatra tačno jednom, najviše jednom se kupuje, a pošto je niz opadajuć, kupac nikad ne propušta skuplji predmet koji je mogao da kupi.
Napisati program kojim se prikazuju svi binarni nizovi (nizovi koji sadrže samo nule i jedinice) dužine n koji ne sadrže dve uzastopne jedinice. Nizove prikazati u leksikografskom poretku (tako da su binarni brojevi formirani od tih nizova poređani rastuće).
Ulaz
Prva linija standardnog ulaza sadrži prirodan broj n (n ≤ 10).
Izlaz
Na standardnom izlazu prikazati tražene binarne nizove u leksikografskom poretku, svaki niz u posebnoj liniji.
Primer
Ulaz
3
Izlaz
000
001
010
100
101
Analiza zadatka
Zadatak se može rešiti upotrebom rekurzivne funkcije koja formira binarne nizove. Funkcija ima jedan parametar – redni broj pozicije i koju trenutno popunjavamo. Ako je početna vrednost i = 0, sledeći poziv ima i = 1 itd.
U svakom pozivu na poziciju i pokušavamo da upišemo nulu i da upišemo jedinicu. Nula se može upisati uvek. Jedinica se sme upisati samo ako prethodni element nije jedinica (po uslovu zadatka ne smeju postojati dve uzastopne jedinice). U oba slučaja funkciju pozivamo za sledeću poziciju i + 1. Kada se dođe do i = n, niz je potpun i ispisuje se. Pošto se uvek prvo upisuje nula, pa tek onda jedinica, nizovi se dobijaju u leksikografskom poretku.
Slika 2: Priprema za državna takmičenja 2018: zadatak „Binarni nizovi“ – stablo rekurzivnih poziva
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Rekurzivna funkcija
Funkcija formiraj(i) popunjava poziciju i, a pozicije 0..i − 1 su već popunjene.
2. Izlaz iz rekurzije
Kada je i == n, svi elementi su postavljeni: ispisujemo niz i vraćamo se nazad.
3. Grananje
Prvo upisujemo 0 i pozivamo formiraj(i + 1). Zatim, ako je i == 0 ili je prethodni element 0, upisujemo 1 i ponovo pozivamo formiraj(i + 1).
4. Redosled
Zbog toga što uvek prvo ispitujemo nulu, a zatim jedinicu, nizovi se ispisuju u rastućem (leksikografskom) poretku.
C++ rešenje
#include <iostream>
using namespace std;
int n;
int niz[10];
// Ispis formiranog binarnog niza
void ispisi() {
for (int i = 0; i < n; i++) {
cout << niz[i];
}
cout << "\n";
}
// Popunjava poziciju i; pozicije 0..i-1 su već popunjene
void formiraj(int i) {
if (i == n) { // niz je potpun
ispisi();
return;
}
niz[i] = 0; // nula se uvek može upisati
formiraj(i + 1);
// jedinica sme samo ako prethodni element nije jedinica
if (i == 0 || niz[i - 1] == 0) {
niz[i] = 1;
formiraj(i + 1);
}
}
int main() {
cin >> n;
formiraj(0);
return 0;
}
Objašnjenje: Pazite na format ispisa: cifre se ispisuju bez razmaka (000), a ne kao 0 0 0, što ne bi odgovaralo zadatku. Takođe nije potrebno koristiti pokazivače – dovoljan je globalni niz.
Vrednost akcija jedne kompanije varirala je iz dana u dan tokom jednog dužeg vremenskog perioda. Napiši program koji određuje koliko ima nizova uzastopnih dana u kojima je ukupan zbir promena vrednosti akcija jednak datom broju z.
Ulaz
Sa standardnog ulaza se u prvoj liniji unosi broj z (ceo broj između −10000 i 10000), zatim, u narednoj liniji, broj dana n u kojima se pratila promena vrednosti akcija (3 ≤ n ≤ 50000) i zatim, u narednoj liniji, promene za svaki od dana u odnosu na prethodni dan (celi brojevi između −100 i 100) razdvojeni razmacima.
Izlaz
Na standardni izlaz ispisati broj perioda u kojima je ukupna promena jednaka z (tj. broj nepraznih segmenata niza promena čiji je zbir jednak z).
Najjednostavnije je proveriti svaki segment (dve petlje), ali je to O(n²), što je za n = 50000 oko 1,25 milijardi sabiranja. Efikasnije se rešava pomoću prefiksnih zbirova.
Uputstvo i rešenja
Uputstvo — ideja rešenja
Rešenje 1: svi segmenti
Fiksiramo početak i, a zatim petljom povećavamo kraj j i na tekući zbir dodajemo promena[j]. Svaki put kada je zbir jednak z, povećavamo brojač. Ovo prolazi manje testove.
Rešenje 2: prefiksni zbirovi
Neka je P[j] zbir prvih j promena (P[0] = 0). Zbir segmenta od dana i + 1 do dana j je P[j] − P[i]. Segment ima zbir z kada je P[i] = P[j] − z.
Brojanje
Kako idemo kroz dane, održavamo mapu videno koja za svaku vrednost prefiksnog zbira pamti koliko se puta do sada pojavila. Za svako j rezultatu dodajemo videno[P[j] − z], a zatim upišemo i P[j]. Na početku je videno[0] = 1 (prazan prefiks).
Tip podataka
Broj segmenata može da bude oko 1,25·109, što prelazi granicu tipa int, pa brojač mora biti long long.
Rešenje 1: svi segmenti
#include <iostream>
#include <vector>
using namespace std;
int main() {
int z, n;
cin >> z >> n;
vector<int> promena(n);
for (int i = 0; i < n; i++) cin >> promena[i];
long long brojac = 0;
for (int i = 0; i < n; i++) { // početak perioda
int zbir = 0;
for (int j = i; j < n; j++) { // kraj perioda
zbir += promena[j];
if (zbir == z) brojac++;
}
}
cout << brojac << endl;
return 0;
}
Objašnjenje: Jednostavno, ali složenosti O(n²).
Rešenje 2: prefiksni zbirovi i mapa
#include <iostream>
#include <map>
using namespace std;
int main() {
int z, n;
cin >> z >> n;
// prefiks[j] = zbir prvih j promena. Period (i, j] ima zbir prefiks[j] - prefiks[i].
// Zato za svaki prefiks tražimo koliko ranijih prefiksa ima vrednost (prefiks - z).
map<int, int> videno;
videno[0] = 1; // prazan prefiks (pre prvog dana)
int prefiks = 0;
long long brojac = 0;
for (int i = 0; i < n; i++) {
int promena;
cin >> promena;
prefiks += promena;
auto it = videno.find(prefiks - z);
if (it != videno.end()) {
brojac += it->second;
}
videno[prefiks]++;
}
cout << brojac << endl;
return 0;
}
Objašnjenje: Složenost je O(n log n). Za primer iz zadatka (z = 11) daje 7.
U svakom zadatku vremensko ograničenje je 1 sekunda, a memorijsko ograničenje je 64 MB.
Data su dva cela broja a, b (0 < a < b < 1015) i ceo broj c (1 < c < 1015). Napisati program (konzolnu aplikaciju) DELJIVI koji će ispisati koliko celih brojeva između a i b (uključujući a i b) je deljivo datim brojem c.
Ulaz
U prvom redu standardnog ulaza dat je prirodan broj a, u drugom redu prirodan broj b, a u trećem redu prirodan broj c.
Izlaz
U jedinom redu standardnog izlaza ispisati traženi broj.
Primer 1
Ulaz
8
40
5
Izlaz
7
Objašnjenje
Brojevi deljivi sa 5 su 10, 15, 20, 25, 30, 35 i 40.
Primer 2
Ulaz
8
40
8
Izlaz
5
Primer 3
Ulaz
8
1234567890
8
Izlaz
154320986
Analiza zadatka
Najjednostavnije rešenje je da se u petlji proveri deljivost svakog broja od a do b sa c. To nije dovoljno efikasno (zbog vremenskog ograničenja ne prolazi sve test primere). Nešto bolje je da se, kada se naiđe na prvi broj deljiv sa c, dalje ide u koracima od c. Najefikasnije je da se broj deljivih odredi formulom.
Uputstvo i rešenja
Uputstvo — ideja rešenja
Rešenje 1: svi brojevi
U petlji od a do b proveravamo uslov i % c == 0 i brojimo. Za b do 1015 to je previše koraka.
Rešenje 2: koraci od c
Prvi umnožak broja c koji je ≥ a je (a + c − 1) / c * c. Od njega idemo u koracima od c do b i brojimo. Za mali delilac c (npr. 2) i dalje ima previše koraka.
Rešenje 3: formula
Umnožaka broja c među brojevima od 1 do x ima x / c (celobrojno deljenje). Zato je broj umnožaka u [a, b] jednak b / c − (a − 1) / c. Računa se u konstantnom vremenu.
Tip podataka
Brojevi dostižu 1015, pa je potreban 64-bitni tip, npr. unsigned long long ili long long.
Čitanje iz datoteke (pri testiranju)
Prilikom vežbanja primere možete čitati iz datoteke umesto sa standardnog ulaza, ako na početak funkcije main dodate freopen("deljivi.01.in", "r", stdin); (i #include <cstdio>). Ovu liniju treba ukloniti pre predaje rešenja.
Rešenje 1: svi brojevi
#include <iostream>
using namespace std;
int main() {
unsigned long long a, b, c;
cin >> a >> b >> c;
unsigned long long brojac = 0;
for (unsigned long long i = a; i <= b; i++) { // proveravamo svaki broj
if (i % c == 0) {
brojac++;
}
}
cout << brojac << endl;
return 0;
}
Objašnjenje: Previše sporo za velike granice.
Rešenje 2: koraci od c
#include <iostream>
using namespace std;
int main() {
unsigned long long a, b, c;
cin >> a >> b >> c;
// prvi umnožak broja c koji nije manji od a
unsigned long long i = (a + c - 1) / c * c;
unsigned long long brojac = 0;
while (i <= b) {
brojac++;
i += c; // sledeći umnožak je za c veći
}
cout << brojac << endl;
return 0;
}
Objašnjenje: Dobro kada je c veliko, ali sporo kada je malo.
Rešenje 3: formula
#include <iostream>
using namespace std;
int main() {
unsigned long long a, b, c;
cin >> a >> b >> c;
// Umnožaka broja c u [1, x] ima x / c (celobrojno deljenje).
// U [a, b] ih ima: (u [1, b]) - (u [1, a - 1]).
unsigned long long rezultat = b / c - (a - 1) / c;
cout << rezultat << endl;
return 0;
}
Objašnjenje: Provera: 40 / 5 − 7 / 5 = 8 − 1 = 7. Česta greška je računanje prvog umnoška izrazom koji greši u graničnim slučajevima, pa mora posebno da se proverava da li je a već deljivo sa c. Ovaj pristup nema posebnih slučajeva.
Državno takmičenje 2024
Zadaci su preuzeti sa petlje, sa web lokacije takprog.petlja.org. Da biste proverili rešenja, ulogujte se na petlju i kliknite na link za određen razred.
Dva mala putnika imaju ukupno četiri teške torbe. Svaki od putnika može da ponese najviše dve torbe, po jednu u svakoj ruci. Sve torbe su zajedničke i bilo koji putnik može da nosi bilo koju torbu. U slučaju da su im torbe preteške, naši putnici žele da koriste što manje tuđe pomoći. Poznata je težina svake torbe, kao i najveći teret koji može da ponese svaki od putnika. Potrebno je odrediti najmanju ukupnu težinu torbi koje oni ne mogu da ponesu.
Ulaz
U prvom redu standardnog ulaza su četiri prirodna broja razdvojena po jednim razmakom – težine torbi. U drugom redu su dva prirodna broja razdvojena jednim razmakom – težine koje mogu da ponesu mali putnici. Svi brojevi su manji od 1000.
Izlaz
Na standardni izlaz ispisati jedan neoznačen ceo broj – najmanju ukupnu težinu torbi za koje je putnicima potrebna pomoć.
Primer 1
Ulaz
1 2 3 4
5 6
Izlaz
0
Objašnjenje
Jedan putnik može da ponese torbe težina 1 i 4, a drugi torbe težina 2 i 3, tako da im nije potrebna pomoć.
Primer 2
Ulaz
2 3 4 5
5 6
Izlaz
3
Objašnjenje
Jedan putnik može da ponese torbu težine 5, a drugi torbe težina 2 i 4, pa im je potrebna pomoć za torbu težine 3.
Primer 3
Ulaz
12 20 15 17
17 14
Izlaz
35
Primer 4
Ulaz
99 56 34 22
3 4
Izlaz
211
Objašnjenje
Nijedna torba nije dovoljno laka za putnike, pa je potrebna pomoć za sve torbe: 99 + 56 + 34 + 22 = 211.
Primer 5
Ulaz
7 5 4 2
7 9
Izlaz
2
Objašnjenje
Prvi putnik nosi torbu težine 7, a drugi torbe težina 5 i 4 (ukupno 9). Ostaje torba težine 2.
Učitamo težine četiri torbe i nosivosti dva putnika.
2. Rekurzija
Funkcija resi(i, kap1, kap2, br1, br2) obrađuje torbu sa rednim brojem i. Parametri kap1 i kap2 su preostale nosivosti putnika, a br1 i br2 broj torbi koje već nose (najviše 2).
3. Tri mogućnosti
Za torbu i razmatramo: (1) niko je ne nosi (njenu težinu dodajemo „neponetom“ delu); (2) prvi putnik je nosi – ako ima slobodnu ruku i dovoljno nosivosti; (3) drugi putnik je nosi, pod istim uslovima. Funkcija vraća najmanji ukupni teret koji ostaje neponet.
4. Kraj rekurzije
Kada su sve četiri torbe obrađene (i == 4), nema više neponetog tereta, pa vraćamo 0.
5. Broj mogućnosti
Postoji najviše 34 = 81 načina da se torbe rasporede, pa je ovakvo potpuno ispitivanje (pretraga svih mogućnosti) veoma brzo.
C++ rešenje
#include <iostream>
#include <algorithm>
using namespace std;
int tezina[4]; // težine torbi
// Minimalna težina torbi koje neće biti poneti, ako su torbe 0..i-1 već raspoređene.
// kap1, kap2 - preostala nosivost putnika, br1, br2 - broj torbi koje već nose
int resi(int i, int kap1, int kap2, int br1, int br2) {
if (i == 4) {
return 0; // sve torbe su obrađene
}
// 1) torbu i niko ne nosi (potrebna je tuđa pomoć)
int najbolje = tezina[i] + resi(i + 1, kap1, kap2, br1, br2);
// 2) torbu nosi prvi putnik (ako ima mesta u ruci i nosivosti)
if (br1 < 2 && tezina[i] <= kap1) {
najbolje = min(najbolje, resi(i + 1, kap1 - tezina[i], kap2, br1 + 1, br2));
}
// 3) torbu nosi drugi putnik
if (br2 < 2 && tezina[i] <= kap2) {
najbolje = min(najbolje, resi(i + 1, kap1, kap2 - tezina[i], br1, br2 + 1));
}
return najbolje;
}
int main() {
for (int i = 0; i < 4; i++) cin >> tezina[i];
int p1, p2;
cin >> p1 >> p2;
cout << resi(0, p1, p2, 0, 0) << endl;
return 0;
}
Objašnjenje: Mogućnost „niko ne nosi torbu“ je neophodna: kada torba ne može da se ponese (ili je bolje da je ne nosi niko), njena težina ulazi u rezultat.
Đole je kvantni fizičar koji je osmislio eksperiment u okviru svojih doktorskih studija. Đole pokušava da određenu česticu dovede u stabilan položaj na sobnoj temperaturi. Da bi proverio da li je uspeo, on meri brzinu te čestice tokom vremena. Da bi se smatralo da je čestica u stabilnom stanju, njena brzina mora tokom nekih k uzastopnih trenutaka da osciluje, tj. da se tokom tih k trenutaka brzina naizmenično povećava i smanjuje. Đole je sjajan fizičar, ali nije tako dobar programer i zamolio je vas da na osnovu niza merenja odredite da li je eksperiment uspeo.
Ulaz
U prvom redu standardnog ulaza nalaze se dva prirodna broja n i k (2 ≤ k ≤ n ≤ 106). U drugom redu nalazi se n pozitivnih realnih brojeva razdvojenih razmacima. Ovi brojevi predstavljaju brzine čestice u svakom od n trenutaka koliko traje eksperiment.
Izlaz
U prvom redu standardnog izlaza ispisati da ili ne (malim latiničnim slovima), u zavisnosti od toga da li je eksperiment uspeo. U drugom redu ispisati dužinu najdužeg podniza trenutaka u kome je brzina čestice oscilovala.
U nizu brzina ne postoji podniz od 7 uzastopnih elemenata u kome brzina osciluje. Najduži takav podniz ima 6 elemenata: 6.9, 7.8, 4.3, 17.11, 2.6, 5.8.
U nizu ne postoji podniz od 4 uzastopna elementa u kome brzina osciluje. Podniz 2.2, 7.5, 3.5, 3.5 ne ispunjava uslov jer su poslednja dva elementa jednaka.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Šta je oscilovanje
Podniz osciluje ako se smer promene stalno menja: pa raste, pa opada, pa raste... Jednaki susedni elementi prekidaju oscilovanje (nema ni povećanja ni smanjenja).
2. Smer promene
Za svaka dva susedna merenja određujemo smer: +1 ako brzina raste, −1 ako opada, 0 ako je ista.
3. Dužina trenutnog podniza
Pamtimo dužinu trenutna trenutnog oscilujućeg podniza i smer poslednje promene. Ako je novi smer 0, dužina se vraća na 1. Ako je novi smer suprotan prethodnom, dužina raste za 1. U ostalim slučajevima (isti smer ili prethodno nije bilo promene) podniz počinje od prethodnog elementa, pa je dužina 2.
4. Najduži podniz
Pamtimo najveću vrednost dužine najduza. Ako je najduza ≥ k, ispisujemo da, inače ne; u drugom redu ispisujemo najduza.
5. Brzina
Brojeva ima do milion, pa uključujemo brže čitanje: ios::sync_with_stdio(false); cin.tie(nullptr);. Nije potrebno čuvati ceo niz – dovoljna je prethodna vrednost.
C++ rešenje
#include <iostream>
using namespace std;
int main() {
ios::sync_with_stdio(false); // brz ulaz, jer ima do milion brojeva
cin.tie(nullptr);
int n, k;
cin >> n >> k;
double prethodna;
cin >> prethodna;
int trenutna = 1; // dužina trenutnog oscilujućeg podniza
int najduza = 1;
int smer = 0; // smer poslednje promene: +1 raste, -1 opada, 0 nema
for (int i = 1; i < n; i++) {
double x;
cin >> x;
int novi; // smer promene prethodna -> x
if (x > prethodna) novi = 1;
else if (x < prethodna) novi = -1;
else novi = 0;
if (novi == 0) {
trenutna = 1; // jednaki susedi prekidaju oscilovanje
} else if (smer != 0 && novi == -smer) {
trenutna++; // smer se promenio: oscilovanje se nastavlja
} else {
trenutna = 2; // novi početak: prethodna i x čine podniz dužine 2
}
smer = novi;
prethodna = x;
if (trenutna > najduza) najduza = trenutna;
}
cout << (najduza >= k ? "da" : "ne") << "\n" << najduza << "\n";
return 0;
}
Objašnjenje: Rešenje obrađuje niz u jednom prolazu, pa je složenost O(n) i memorija O(1).
Popularna prodavnica patika, Patikarnica, odlučila je da svojim kupcima ponudi novu uslugu. Na ulazu u Patikarnicu biće postavljen ekran osetljiv na dodir koji će kupcima predlagati najskuplje modele patika koje se uklapaju u budžet koji su planirali da izdvoje za nove patike. Patikarnica je kupila svu neophodnu opremu da bi korisnicima pružila novu uslugu, ali je zamolila nas da napišemo program koji će na osnovu dostupnih patika u prodavnici i novca koji je kupac spreman da izdvoji prikazati spisak najskupljih modela patika koje kupac može da priušti.
Patikarnica na stanju ima n različitih modela patika opisanih svojim imenom i cenom. Tokom dana u Patikarnicu ulazi k kupaca. Svaki kupac na ulazu unosi količinu novca x koju želi da izdvoji za nove patike i sistem mu prikazuje spisak najskupljih patika koje može da priušti.
Ulaz
U prvom redu učitava se broj n (1 ≤ n ≤ 105) različitih modela patika u ponudi Patikarnice, zatim se u n narednih redova učitava naziv modela patika (niska maksimalne dužine 200 karaktera) i njegova cena (prirodan broj manji od 50000). Nakon toga učitava se broj k (1 ≤ k ≤ 106) kupaca koji su ušli u prodavnicu, a zatim u k sledećih redova budžeti xi (1 ≤ i ≤ k) pojedinačnih kupaca (prirodni brojevi manji od 50000).
Izlaz
Na standardni izlaz za svakog od k kupaca i njihove budžete xi ispisati leksikografski sortiran spisak najskupljih patika koje mogu da priušte (naziv i cena, u istom redu). U slučaju da ne postoje patike koje kupac može da priušti, ispisati nisku nema.
AdidasXPLRBoost 17199
AsicsSkzElite 17199
NikeJordanMaxAura5 17199
nema
Objašnjenje
Prvi kupac je kao svoj budžet uneo iznos od 18000 dinara i najskuplje patike koje može da priušti su AdidasXPLRBoost, AsicsSkzElite i NikeJordanMaxAura5 po ceni od 17199 dinara. Drugi kupac je uneo budžet od 12000 dinara i u ponudi nema patika koje može da priušti, pa se kao rezultat štampa reč nema.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Podaci o patikama
Cene su manje od 50000, pa patike možemo grupisati po ceni: za svaku cenu c čuvamo vektor naziva patika koje koštaju tačno c (vector<vector<string>>).
2. Sortiranje imena
Nazive u svakoj grupi sortiramo leksikografski.
3. Najskuplja koju možemo da priuštimo
Za svaki mogući budžet x unapred izračunamo najskuplja[x] – najveću cenu ≤ x za koju postoji bar jedna patika (ili −1 ako takve nema). To radimo jednim prolazom: ako postoje patike sa cenom x, to je x, a inače vrednost za x − 1.
4. Upiti
Za svakog kupca jednim pristupom nizu dobijamo cenu: ako je −1, ispisujemo nema, inače ispisujemo sve nazive iz te grupe sa cenom.
5. Alternativa
Patike se mogu sortirati po ceni, a zatim se za svaki budžet binarnom pretragom nalazi najskuplja patika koja nije skuplja od budžeta. Prebrojavanje po cenama je jednostavnije jer su cene male.
6. Brzina
Upita ima do milion, pa uključujemo brz ulaz i izlaz i izbegavamo endl (koristimo "\n").
C++ rešenje
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false); // brz ulaz i izlaz zbog velikog broja upita
cin.tie(nullptr);
const int MAKS = 50000; // cene su manje od 50000
vector<vector<string>> poCeni(MAKS); // poCeni[c] = nazivi patika sa cenom c
int n;
cin >> n;
for (int i = 0; i < n; i++) {
string naziv;
int cena;
cin >> naziv >> cena;
poCeni[cena].push_back(naziv);
}
for (int c = 0; c < MAKS; c++) {
sort(poCeni[c].begin(), poCeni[c].end()); // leksikografski poredak unutar iste cene
}
// najskuplja[x] = najveća cena <= x za koju postoji bar jedan model (ili -1)
vector<int> najskuplja(MAKS, -1);
for (int x = 0; x < MAKS; x++) {
if (!poCeni[x].empty()) najskuplja[x] = x;
else if (x > 0) najskuplja[x] = najskuplja[x - 1];
}
int k;
cin >> k;
for (int q = 0; q < k; q++) {
int budzet;
cin >> budzet;
if (budzet >= MAKS) budzet = MAKS - 1;
int cena = najskuplja[budzet];
if (cena == -1) {
cout << "nema\n";
} else {
for (const string &naziv : poCeni[cena]) {
cout << naziv << " " << cena << "\n";
}
}
}
return 0;
}
Objašnjenje: Svaki upit se odgovara u konstantnom vremenu (plus ispis rezultata). Rešenje je i tačno kada više patika ima istu cenu, jer se ispisuju sve, leksikografski sortirane.
Kada se pritisne taster na tastaturi, prvo slovo svake reči u tekstu premešta se na poslednje mesto te reči. Koliko je puta potrebno pritisnuti taster da bi se rečenica vratila u početno stanje?
Ulaz
Sa standardnog ulaza učitava se jedna linija teksta koja je sastavljena od malih slova engleske abecede i razmaka i ima najviše 106 karaktera. Svi primeri su takvi da se rečenica vraća u prvobitno stanje nakon najviše 1018 pritisaka tastera.
U 50% test-primera reči nisu periodične. U 60% test-primera sve reči su kraće od 100 karaktera.
Izlaz
Na standardni izlaz ispisati najmanji broj pritisaka tastera (veći od 0) potrebnih da se rečenica vrati u prvobitno stanje.
Primer 1
Ulaz
mala sirena zivi na dnu mora
Izlaz
12
Objašnjenje
Pritiscima tastera dobijaju se naredne rečenice: mala sirena zivi na dnu mora, alam irenas iviz an nud oram, lama renasi vizi na udn ramo, amal enasir iziv an dnu amor, mala nasire zivi na nud mora, ..., i posle 12 pritisaka dobija se početna rečenica.
Svaka reč se rotira za jedno slovo pri svakom pritisku, nezavisno od ostalih. Cela rečenica se vraća u početno stanje onda kada se sve reči istovremeno vrate u početno stanje.
2. Za jednu reč
Reč dužine L vraća se u početno stanje posle L pritisaka, ali možda i ranije ako je periodična. Na primer reč abcabc ima dužinu 6, ali se posle 3 rotacije dobija ista reč, pa je njen broj pritisaka 3. Broj pritisaka je najmanja perioda reči, ako ona deli dužinu reči, a inače je to dužina reči.
3. Najmanja perioda
Najmanju periodu računamo pomoću prefiks-funkcije (KMP algoritma): ako je pi dužina najdužeg pravog prefiksa koji je ujedno i sufiks reči, perioda je L − pi. Ako L nije deljivo periodom, reč nije periodična i broj pritisaka je L. Za male reči (do 100 slova) dovoljno je jednostavno ispitivanje svih delilaca dužine.
4. Svi zajedno
Ako reč 1 treba p1 pritisaka, reč 2 treba p2 itd., rečenica je u početnom stanju kada je broj pritisaka deljiv svim pi, a najmanji takav broj je njihov NZS. NZS računamo pomoću NZD (Euklidov algoritam): NZS(a, b) = a / NZD(a, b) * b.
5. Tip podataka
Rezultat može biti do 1018, pa koristimo long long. Deljenje radimo pre množenja da ne bi došlo do prekoračenja.
C++ rešenje
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
long long nzd(long long a, long long b) {
while (b != 0) {
long long ostatak = a % b;
a = b;
b = ostatak;
}
return a;
}
// Najmanji broj pomeranja prvog slova na kraj posle kog se reč vraća u početno stanje.
// To je najmanja perioda reči, ako deli dužinu reči; inače je to cela dužina.
int periodReci(const string &r) {
int duzina = r.size();
vector<int> pi(duzina, 0); // prefiks-funkcija (KMP)
for (int i = 1; i < duzina; i++) {
int j = pi[i - 1];
while (j > 0 && r[i] != r[j]) j = pi[j - 1];
if (r[i] == r[j]) j++;
pi[i] = j;
}
int p = duzina - pi[duzina - 1]; // najmanja perioda
if (duzina % p == 0) return p;
return duzina;
}
int main() {
string linija;
getline(cin, linija);
istringstream ulaz(linija);
long long rezultat = 1; // NZS perioda svih reči
string rec;
while (ulaz >> rec) {
long long p = periodReci(rec);
rezultat = rezultat / nzd(rezultat, p) * p;
}
cout << rezultat << endl;
return 0;
}
Objašnjenje: Pažnja: reč abcabc ima dužinu 6 (ne 4), a broj pritisaka joj je 3. Provera: NZS(4, 6, 4, 2, 3, 4) = 12 za prvi primer.
Državno takmičenje 2023
Zadaci su preuzeti sa petlje, sa web lokacije takprog.petlja.org. Da biste proverili rešenja, ulogujte se na petlju i kliknite na link za određen razred.
Niz je pnpn (parno-neparno-parno-neparno...) ako se na parnim pozicijama u nizu (0, 2, 4, ...) nalaze isključivo parni, a na neparnim pozicijama u nizu (1, 3, 5, ...) isključivo neparni brojevi. Napiši program koji za nekoliko učitanih nizova proverava da li su pnpn nizovi.
Ulaz
Sa standardnog ulaza učitavaju se brojevi n (5 ≤ n ≤ 100) i k (5 ≤ k ≤ 100), a zatim iz n narednih redova n nizova od po k elemenata čija je vrednost između 0 i 1000.
Izlaz
Za svaki od učitanih nizova na standardni izlaz ispisati da ako zadovoljava uslov, a u suprotnom ne.
Primer
Ulaz
3 5
1 2 3 4 5
2 3 4 5 6
4 7 0 9 2
Izlaz
ne
da
da
Objašnjenje
U prvom nizu se na poziciji 0, koja je parna, nalazi broj 1, koji je neparan. U drugom nizu se na parnim pozicijama nalaze brojevi 2, 4 i 6, koji su parni, a na neparnim pozicijama brojevi 3 i 5, koji su neparni. U trećem nizu se na parnim pozicijama nalaze brojevi 4, 0 i 2, koji su parni, a na neparnim pozicijama brojevi 7 i 9, koji su neparni.
Spoljašnja petlja ide kroz n nizova, a unutrašnja kroz k elemenata jednog niza.
2. Uslov za jedan element
Element x na poziciji j je ispravan ako x i j imaju istu parnost, tj. x % 2 == j % 2. Zaista: na parnoj poziciji (j % 2 == 0) mora biti paran broj (x % 2 == 0), a na neparnoj neparan.
3. Učitavanje do kraja reda
Čak i kada se utvrdi da niz nije ispravan, moraju se učitati preostali elementi tog niza, da bi sledeći niz bio ispravno učitan. Zato koristimo logičku promenljivu ispravan, a ne prekidamo petlju.
4. Ispis
Posle unutrašnje petlje ispisujemo da ili ne.
C++ rešenje
#include <iostream>
using namespace std;
int main() {
int n, k;
cin >> n >> k;
for (int i = 0; i < n; i++) { // n nizova
bool ispravan = true;
for (int j = 0; j < k; j++) { // k elemenata niza
int x;
cin >> x; // učitavamo sve elemente, i posle greške
// na parnim pozicijama treba da je paran broj, na neparnim neparan:
// ostatak pri deljenju sa 2 mora da bude jednak ostatku pozicije
if (x % 2 != j % 2) {
ispravan = false;
}
}
cout << (ispravan ? "da" : "ne") << "\n";
}
return 0;
}
Objašnjenje: U originalnom tekstu zadatka niz se zvao „ppnn“, a stranica sa analizom koristi naziv „pnpn“. Pravi uslov zadatka je naizmenično parno-neparno, pa je ovde korišćen naziv pnpn.
Napiši program koji implementira srpsko-engleski rečnik. Prvo se učitava spisak reči i njihovih prevoda, a zatim korisnik od sistema traži da mu se prevedu neke reči (bilo sa srpskog na engleski, bilo sa engleskog na srpski).
Ulaz
Sa standardnog ulaza unosi se broj n (1 ≤ n ≤ 50000) parova reči na engleskom i srpskom jeziku (u svakom redu prvo engleska, a zatim srpska reč, kao u primeru). Sve reči na srpskom su međusobno različite i sve reči na engleskom su međusobno različite. Nakon toga unosi se m (1 ≤ m ≤ 50000) upita. Svaki upit sadrži željeni jezik (sr ili en) i zatim reč na suprotnom jeziku koju treba prevesti.
Izlaz
Na standardni izlaz ispisati sve prevode. Ako za neku reč nije unet prevod, ispisati ?.
Primer
Ulaz
3
cat macka
dog pas
mouse mis
4
en pas
sr mouse
sr lion
en mis
Izlaz
dog
mis
?
mouse
Objašnjenje
Upit en pas traži prevod srpske reči pas na engleski: dog. Upit sr mouse traži prevod engleske reči mouse na srpski: mis. Za reč lion nema prevoda.
Uputstvo i rešenja
Uputstvo — ideja rešenja
1. Dve mape
Čuvamo dva rečnika: naSrpski (engleska reč → srpska) i naEngleski (srpska reč → engleska). Za svaki učitani par upisujemo oba smera.
2. Upiti
Ako je traženi jezik en, reč je srpska pa je tražimo u mapi naEngleski. Ako je traženi jezik sr, reč je engleska pa je tražimo u mapi naSrpski.
3. Nepoznata reč
Pretragu radimo funkcijom find. Ako reči nema u mapi, ispisujemo ?. Operator [] ne koristimo za pretragu jer bi za nepoznatu reč dodao novi (prazan) element.
4. Brzina
Koristimo unordered_map (prosečno konstantno vreme pristupa) ili običnu map (logaritamsko) – oba su dovoljna za 50000 reči i upita.
C++ rešenje
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
unordered_map<string, string> naSrpski; // engleska reč -> srpska reč
unordered_map<string, string> naEngleski; // srpska reč -> engleska reč
for (int i = 0; i < n; i++) {
string engleski, srpski;
cin >> engleski >> srpski; // u primeru je prvo engleska, pa srpska reč
naSrpski[engleski] = srpski;
naEngleski[srpski] = engleski;
}
int m;
cin >> m;
for (int i = 0; i < m; i++) {
string jezik, rec;
cin >> jezik >> rec; // jezik je na koji se prevodi
if (jezik == "en") {
auto it = naEngleski.find(rec); // rec je srpska
cout << (it == naEngleski.end() ? "?" : it->second) << "\n";
} else {
auto it = naSrpski.find(rec); // rec je engleska
cout << (it == naSrpski.end() ? "?" : it->second) << "\n";
}
}
return 0;
}
Objašnjenje: U originalnom tekstu zadatka pisalo je da se unose parovi „na srpskom i engleskom“, ali primer pokazuje da je u svakom redu prvo engleska, pa srpska reč. Tako je ovde i napisano.
Kviz: proveri svoje znanje
Kviz – priprema za državno takmičenje
1. Koji elementi matrice a[3][3] čine sporednu dijagonalu?
2. Kako se u zadatku „Prašuma“ najlakše učitavaju cifre napisane bez razmaka (npr. 123)?
3. Koji brojevi imaju neparan broj delilaca?
4. Zašto u zadatku „Mesto K-tog pojavljivanja broja“ koristimo long long?
5. Šta je neophodno da bi binarna pretraga radila ispravno?
6. Koliko je celih brojeva deljivih sa c u intervalu [a, b]?
7. U zadatku „Akcije“ segment ima zbir z. Šta se traži kod prefiksnih zbirova P?
8. Kako se u zadatku „Rotator slova“ dobija konačan odgovor za celu rečenicu?
9. U zadatku „Binarni nizovi“ dozvoljeno je upisati jedinicu na poziciju i samo ako…
10. Zašto strategija iz zadatka „Kupovina“ (uvek uzmi najskuplje što možeš) ne daje uvek najbolje?
11. Kako se u zadatku „Dinosaurus Dino“ ubrzava odgovaranje na pitanja kada su poeni sortirani?
12. U zadatku „Prevod“ šta je dobar izbor za čuvanje parova reč → prevod?
Podešavanja kolačića
Koristimo kolačiće da bismo vam pružili najbolje moguće iskustvo. Takođe nam omogućavaju da analiziramo ponašanje korisnika kako bismo stalno poboljšavali veb lokaciju za vas.