Professional Documents
Culture Documents
Rjesenja
Rjesenja
Opisi algoritama
Zadatak Oči Autor: Mihael Liskij
Potrebno je pomnožiti brojeve S i K kako bi se dobio ukupan broj bodova koje je Mirko
osvojio u bacanju.
Nakon toga se rezultat bacanja uspoređuje s brojem N i ovisno o rezultatu usporedbe te
posebnim uvjetima navedenima u zadatku ispisuje se odgovarajuća poruka. Konkretno, ako
je rezultat bacanja strogo manji od N-1 ispisuje se ocjena "Dobro", ako je rezultat točno N-1
ili je veći od N ispisuje se ocjena "Lose", a ako je rezultat točno N potrebno je dodatno
provjeriti vrijednost učitanog broja K. Ako je K jednak 2 tada je sukladno uvjetima zadatka,
Mirko ostvario najbolje moguće bacanje i ocjena je "Odlicno", u protivnom ocjena je "Lose".
Kako bi se izračunao Ei, tj. broj Pokémona vrste Pi koje Mirko može evoluirati, potrebno je
implementirati algoritam opisan sljedećim pseudokodom:
Ei = 0
dok je Mi >= Ki :
Mi = Mi - Ki + 2
Ei = Ei + 1
Sada, kada imamo izračunate sve Ei , sve što je potrebno u zadatku je ispisati zbroj svih Ei ,
te prvog Pokémona Pi koji ima najveći Ei.
Pseudokod (pisan u Python 3.x):
N = int(input())
maks = ukupno = 0
for i in range(N):
pokemon = input()
Ki, Mi = map(int, input().split())
evoluiraj = 0
while Mi - Ki >= 0:
evoluiraj += 1
Mi -= Ki
Mi += 2
ukupno += evoluiraj
print(ukupno)
print(koji)
Za 60% bodova tinta je razlivena po samo jednom slovu pa je dovoljno abecedno sortirati
slova koja bi ga mogla zamijeniti i zamijeniti ga X-tim po redu. Ovo je dobar primjer zadatka
u kojem se na jednostavan način može dobiti velik broj bodova.
Postoji više rješenja kojima je moguće dobiti sve bodove. Jedno od njih je da broj X - 1
pretvorimo u broj u brojevnom sustavu s bazom K. Da bismo lakše implementirali dopunimo
ga nulama s prednje strane tako da ima M znamenki. Neka su znamenke dobivenog broja
redom a1, a2, a3 ,..., am, tada je i-to nepoznato slovo potrebno zamijeniti ai-tim slovom u
sortiranom poretku slova koje bi mogla zamijeniti i-to slovo (slova u sortiranom poretku su 0-
indeksirana).
Za 30% bodova zadatak se mogao riješiti iscrpnom pretragom, odnosno mogli smo na sve
moguće načine mijenjati niz i na kraju ispisati najmanji broj poteza koji nas je doveo do
rješenja. Budući da nad nizom duljine N možemo napraviti (N - 1) različitih poteza, a nakon
svakog se duljina niza smanji za točno 1, zaključujemo da ovo rješenje radi u složenosti
O(n!).
Promotrimo prvi i posljednji član niza. Budući da niz želimo u konačnici pretvoriti u
palindrom, prvi i posljednji član na kraju moraju biti jednaki. Ako trenutno nisu jednaki, očito
barem jednog od njih trebamo spojiti sa svojim susjedom. Ovakvo razmišljanje nas navodi
na rekurzivnu formulaciju rješenja. Neka f(l, r) označava najmanji broj poteza koji je potreban
da se elementi A[l], A[l+1], …, A[r - 1], A[r] pretvore u palindrom. Prema prethodnim
primjedbama,
l > r, f(l,r) = 0
l <= r, f(l,r) = min(1 + f(l+1, r), 1 + f(l, r - 1), f(l+1, r-1))
pri čemu masno otisnuti dio uzimamo u obzir samo ako je A[l] = A[r]. Primijetimo da je u
svakom koraku algoritma vrijednost A[l] jednaka sumi svih svojih prethodnika, a A[r] je
jednak sumi svih svojih slijedbenika, dok su elementi u sredini netaknuti. Koristeći dinamičko
programiranje, odnosno tehniku memoizacije, ovo rješenje implementiramo u složenosti
O(n^2) što je dovoljno za 60% bodova na zadatku. Implementacija ovog algoritma nalazi se
u datoteci nizin_n2.cpp.
Za osvajanje svih bodova potrebno je iskoristiti činjenicu da su svi brojevi u ulazu pozitivni.
Kao i u prošlom odlomku, zadatku pristupamo izvana prema unutra, a kad rješavamo neki
interval [l, r] razlikujemo par slučajeva:
Ako je A[l] = A[r], tada nije potrebno krajnje elemente spajati, već nastavljamo rješavati
interval [l+1,r-1].
Ako je A[l] < A[r], tada sigurno ne možemo profitirati spajanjem elemenata A[r] i A[r - 1] zato
što je njihova suma nužno veća od A[l] koji ne može ostati ne spojen. Dakle, spojit ćemo
elemente A[l] i A[l+1] te nastaviti s rješavanjem intervala [l + 1, r]. Analogno rješavamo slučaj
gdje A[l] > A[r].
Budući da ćemo u svakom koraku algoritma smanjiti interval koji rješavamo za najmanje 1,
dolazimo do zaključka da se radi o algoritmu složenosti O(n) koji će osvojiti sve bodove.
Jedino što preostaje je uvjeriti se da se prosjek posljednjeg retka nalazi u tom retku. Čitatelju
ostavljamo za vježbu dokazati da je taj prosjek jednak upravo broju koji se nalazi u
posljednjem retku i pretposljednjem stupcu.
Opišimo prvo rješenje koje nije dovoljno brzo, ali će služiti kao motivacija za pravo rješenje.
Ključno je primijetiti da niz možemo sortirati ako i samo ako za svaku komponentu vrijedi PK
= Q K.
Nakon toga, možemo uočiti kako nije potrebno pamtiti točne multiskupove brojeva nego
samo njihovu hash vrijednost. Ako se u multiskupu S broj 1 nalazi c1 puta, 2 c2 puta, …, n cn
puta onda definiramo hash skupa S kao:
h(S) = c1 * H + c2 * H2 + … + cn - 1* Hn - 1
Kada spajamo komponente, jednostavno zbrojimo hash vrijednosti originalnih komponenata.
Sada vidimo kako na upit možemo odgovoriti održavajući mapu M gdje nam M[d] kaže koliko
postoji čvorova čija komponenta ima razliku P - Q točno d.
Ukorijenit ćemo stablo u čvoru broj 1. Za početak primijetimo da će se nakon i-tog poteza
novčić nalaziti u nekom čvoru na dubini i. Očito će Danielu biti optimalno označiti neki čvor
na dubini i u i-tom potezu. Sada možemo preformulirati zadatak: postoji li skup čvorova, od
kojih niti jedan nije korijen, na različitim dubinama tako da ne postoji čvor na dubini k čiji niti
jedan predak nije označen?
Čvorove na dubini k nazvat ćemo listovima. Možemo ukloniti sve čvorove koji u svojem
podstablu nemaju niti jedan list(ovo uključuje i čvorove na dubini većoj od k) jer Daniel
sigurno pobjeđuje ako se novčić nalazi u takvom čvoru. Sada su listovi zaista listovi u
dobivenom stablu. Odsada ćemo promatrati samo stabla dobivena nakon uklanjanja
nepotrebnih čvorova.
Analizirajmo sljedeći jednostavni algoritam: u i-tom potezu označit ćemo neki čvor na dubini i
te ukloniti sve čvorove u njegovom podstablu. Time smo u i-tom koraku uklonili barem k - i +
1 čvorova, što znači da smo u k koraka uklonili barem čvorova. To znači da u slučaju
≤ znamo da Daniel može sigurno pobijediti.
Pokazali smo da za ≥ Daniel uvijek pobjeđuje, što znači da nam preostaje riješiti slučaj
kada to ne vrijedi. No, tada je < 20, pa ne trebamo tražiti polinomijalno rješenje!
Označimo listove redom kako se pojavljuju u dfs obilasku stabla. Tada svaki čvor u stablu
pokriva neki interval listova, tj. to su oni koji se nalaze u njegovom podstablu. Zadatak ćemo
riješiti dinamičkim programiranjem. Stanje ćemo prikazati brojem i bitmaskom veličine k
bitova. Neka dp[T][mask] označava možemo li pokriti prvih T listova tako da smo označavali
čvorove samo na dubinama koje pišu u mask. Prijelaz je jednostavan, u nekom trenutku
možemo odabrati bilo koji od najviše k čvorova kojima prvi list u podstablu ima oznaku T.
Primijetimo da, iako za neku poziciju T, prijelaz može biti k različitih čvorova, svaki čvor će
se u prijelazu pojavljivat na samo jednoj poziciji.
Ukupna složenost je 2√ ∗ .