Řešení každého příkladu musí obsahovat podrobný popis použitého algoritmu, zdůvodnění jeho správnosti a diskusi o efektivitě zvoleného řešení (tzn. posouzení časových a paměťových nároků programu).
V úlohách P-I-1, P-I-2 a P-I-3 je třeba k řešení připojit odladěný program zapsaný v jazyce Pascal, C nebo C++. Program se odevzdává v písemné formě (jeho výpis je tedy součástí řešení) i elektronicky (na CD, na disketě, případně po dohodě lze i zaslat e-mailem), aby bylo možné otestovat jeho funkčnost. Slovní popis řešení musí být ovšem jasný a srozumitelný, aniž by bylo nutno nahlédnout do zdrojového textu programu. V úloze P-I-4 je nutnou součástí řešení úplný popis všech použitých překládacích strojů.
Řešení úloh domácího kola MO kategorie P vypracujte a odevzdejte buď ve škole, nebo zašlete přímo vaší krajské komisi MO, nejpozději do 15.11.2007. Vzorová řešení úloh naleznete po tomto datu na Internetu na adrese http://mo.mff.cuni.cz/. Na stejném místě jsou stále k dispozici veškeré aktuální informace o soutěži a také archiv soutěžních úloh a výsledků minulých ročníků.
Franta a Pepík se chystali na výlet. Vymysleli si trasu, kudy spolu půjdou, potom každý vytáhnul svoji mapu a začal si v ní trasu prohlížet. Některé body, kterými trasa vedla, měly v mapách vyznačenu nadmořskou výšku.
„To je zajímavé,” řekl po chvíli Franta. „Když se dívám jen na nadmořské výšky bodů, kterými budeme procházet, vypadá to, že půjdeme jenom přes jeden kopec – nejprve nahoru a potom dolů.”
„Ale kdepak, to není pravda,” podivil se Pepík.
Za chvíli zjistili, kde vzniknul problém: měli různě přesné mapy. Některé nadmořské výšky bodů, které byly na Pepíkově mapě vyznačeny, na Frantově mapě chyběly.
Jestliže a1,..,aK jsou výšky vyznačené na Frantově mapě, pak musí splňovat následující podmínku: pro nějaké i z množiny {1,..,K} musí platit ∀j∈{1,..,i-1}: aj < aj+1 a zároveň ∀j∈{i,..,K-1}: aj > aj+1.
vstup: 12 112 247 211 209 244 350 470 510 312 215 117 217 výstup: 9 Jedna možnost, jak mohly vypadat nadmořské výšky na Frantově mapě: 112, 211, 244, 350, 470, 510, 312, 215, 117
„V horách se našlo zlato!” šeptali si lidé v širokém okolí už několik týdnů. Ti s dobrodružnější povahou si už balili věci a mířili přímo do hor, do legendami opředené oblasti zvané Horní Klondík.
Když ale Horní Klondík zaplavila vlna nových zlatokopů, nastal nečekaný problém. Jediný místní vláček, který v Klondíku měli, byl najednou tak přecpaný, že se do něj polovina zájemců ani nevešla. A nedivte se, že zlatokopa, který se žene za co nejvýhodnější parcelou, něco takového pořádně rozzlobí.
Když už se zdálo, že zanedlouho dojde na každé železniční stanici ke krveprolití, dostali železničáři spásonosný nápad. Budou na vláček prodávat místenky.
Váš program musí postupně zpracovat několik požadavků na rezervaci míst. Každý požadavek je tvaru „x lidí chce jet ze stanice y do stanice z.” Pokud je ještě na každém úseku trati mezi stanicemi y a z ve vlaku aspoň x míst volných, váš program takovýto požadavek přijme, v opačném případě ho odmítne.
Následuje P řádků, každý z nich popisuje jeden požadavek na rezervaci v pořadí, v jakém byly tyto požadavky zadány. Přesněji, i-tý z těchto řádků obsahuje tři celá čísla xi, yi, zi oddělená mezerami (1≤xi≤M, 0≤yi<zi≤N). Význam těchto hodnot byl popsán výše.
vstup: 4 6 6 2 1 4 2 1 3 3 2 4 3 1 2 6 0 1 4 3 4 výstup: prijat prijat odmitnut odmitnut prijat prijat
Po přijetí prvních dvou požadavků víme, že v úsecích mezi stanicemi 1 a 2 a mezi stanicemi 2 a 3 zůstanou už jen dvě volná místa. Proto musíme odmítnout následující dvě trojčlenné skupiny, které chtějí cestovat i na těchto úsecích tratě. Poslední dva požadavky opět můžeme splnit. U prvního z nich je to zřejmé, u druhého nám ve stanici 3 vystoupí dostatek lidí na to, aby se na poslední úsek trati uvolnila přesně potřebná čtyři místa.
Fibonacciho posloupnost vypadá následovně:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
Sestrojíme ji tak, že prvními dvěma členy posloupnosti jsou 0 a 1 a každý následující člen posloupnosti je součtem dvou předcházejících. Matematicky zapsáno:
F0 = 0
F1 = 1
Fn+2 = Fn+1 + Fn (∀n≥0)
Podívejme se nyní na to, jak fungují poziční číselné soustavy. Poziční číselná soustava je určena dvěma údaji: množinou používaných cifer a posloupností, která pro každou pozici v zápisu čísla udává hodnotu, jíž se příslušná cifra násobí.
Například naše desítková soustava používá množinu cifer {0,1,2,..,9} a jednotlivé pozice v čísle mají hodnoty (zprava doleva) 1, 10, 100, 1000, .. Fibonacciho číselná soustava je poziční číselná soustava, která používá pouze cifry 0 a 1 a v níž jsou hodnoty jednotlivých pozic postupně rovny členům Fibonacciho posloupnosti (počínaje číslem F2=1). Tedy například zápis 10011 ve Fibonacciho soustavě představuje číslo 1·8 + 0·5 + 0·3 + 1·2 + 1·1 = 11.
Na rozdíl od běžných číselných soustav ve Fibonacciho soustavě nemají některá čísla jednoznačný zápis. Například také zápis 10100 představuje číslo 11.
V části a) je na vstupu jediné přirozené číslo N (1≤N≤1,000,000,000).
V části b) jsou na vstupu tři přirozená čísla k, A a B (1≤k≤30, 1≤A < B ≤1,000,000,000).
V části a) vypište na jeden řádek řetězec nul a jedniček, který představuje pěkný zápis čísla N.
V části b) vypište jedno celé číslo – počet čísel ze zadaného intervalu, která mají ve svém pěkném zápisu právě k jedniček.
vstup 1: 11 výstup 1: 10100 vstup 2: 174591 výstup 2: 1010001000000000100010010
vstup 1: 1 4 13 výstup 1: 3
(Pro k=1 je výstupem počet Fibonacciho čísel v daném rozsahu. V zadaném rozsahu leží Fibonacciho čísla 5, 8 a 13.)
vstup 2: 2 4 13 výstup 2: 6 vstup 3: 10 102000 103000 výstup 3: 86
V tomto ročníku olympiády budeme pracovat s překládacími stroji. Ve studijním textu uvedeném za zadáním úlohy jsou tyto stroje popsané.
Nechť M je libovolná (třeba i nekonečná) množina, jejímiž prvky jsou nějaké řetězce písmen a, e a i. Potom C(B(M))=M. Slovně popsáno: Když vezmeme množinu M, přeložíme ji pomocí stroje B a výsledek přeložíme pomocí C, dostaneme původní množinu.
Pokud toto tvrzení skutečně platí, dokažte ho. Pokud ne, najděte protipříklad.
Nechť Y je množina všech řetězců, které obsahují nejprve několik písmen a a za nimi trojnásobné množství písmen b. Tedy například abbb∈Y, ale aaabab∉Y.
Sestrojte překládací stroj, který přeloží X na Y.
Nové množiny můžeme sestrojovat následujícími operacemi:
Překládací stroj přijímá na vstupu řetězec znaků. Tento řetězec postupně čte a podle předem zvolené soustavy pravidel (tedy podle svého programu) občas nějaké znaky zapíše na výstup. Když stroj zpracuje celý vstupní řetězec a úspěšně ukončí svůj výpočet, vezmeme řetězec znaků zapsaný na výstup a nazveme ho překladem vstupního řetězce.
Výpočet stroje nemusí být jednoznačně určen. Jinými slovy, soustava pravidel může někdy stroji umožnit, aby se rozhodl o dalším postupu výpočtu. V takovém případě se může stát, že některý řetězec bude mít více různých překladů.
Naopak, může se stát, že v určité situací se podle daných pravidel nebude moci v překladu pokračovat vůbec. V takovém případě se může stát, že některý řetězec nebude mít vůbec žádný překlad.
Každý překládací stroj pracuje nad nějakou předem zvolenou konečnou množinou znaků. Tuto množinu znaků budeme nazývat abeceda a značit &Sigma. V soutěžních úlohách bude vždy &Sigma známa ze zadání úlohy. Abeceda nebude nikdy obsahovat znak $, ten budeme používat k označení konce vstupního řetězce.
Stroj si může během překladu řetězce pamatovat informaci konečné velikosti. Formálně tuto skutečnost definujeme tak, že stroj se v každém okamžiku překladu nachází v jednom z konečně mnoha stavů. Nutnou součástí programu překládacího stroje je tedy nějaká konečná množina stavů, v nichž se stroj může nacházet. Tuto množinu označíme K. Kromě samotné množiny stavů je také třeba uvést, ve kterém stavu se stroj nachází na začátku každého překladu. Tento stav nazveme počáteční stav.
Program stroje se skládá z konečného počtu překladových pravidel. Každé pravidlo má tvar čtveřice (p,u,v,q), kde p,q∈K jsou nějaké dva stavy a u,v jsou nějaké dva řetězce znaků z abecedy &Sigma.
Stavy p a q mohou být i stejné. Řetězec u může být tvořen jediným znakem $. Řetězce u a v mohou být i stejné. Některý z těchto řetězců může být případně prázdný. Aby se program lépe četl, budeme místo prázdného řetězce psát symbol ε.
Překladové pravidlo má následující význam: „Když je stroj právě ve stavu p a dosud nepřečtená část vstupu začíná řetězcem u, může stroj tento řetězec ze vstupu přečíst, na výstup zapsat řetězec v a změnit svůj stav na q.” Všimněte si, že pravidlo tvaru (p,ε,v,q) můžeme použít vždy, když se stroj nachází ve stavu p, bez ohledu na to, jaké znaky ještě zůstávají na vstupu.
Ještě potřebujeme stanovit, kdy překlad úspěšně skončil. V první řadě budeme požadovat, aby překládací stroj přečetl celý vstupný řetězec. Kromě toho umožníme stroji „odpovědět”, zda se mu překlad podařil nebo ne. To zařídíme tak, že některé stavy stroje označíme jako koncové stavy. Množinu všech koncových stavů budeme značit F.
Překládací stroj je uspořádaná pětice (K,&Sigma,P,q0,F), kde &Sigma a K jsou konečné množiny, q0∈K, F⊆K a P je konečná množina překladových pravidel popsaných výše. Přesněji, nechť &Sigma* je množina všech řetězců tvořených znaky ze &Sigma, potom P je konečná podmnožina množiny K∏(&Sigma*∪{$})∏&Sigma*∏K.
(Pro každé q∈K budeme množinu pravidel, jejichž první složkou je q, nazývat „překladová pravidla ze stavu q”.)
Chceme-li definovat konkrétní překládací stroj, musíme uvést všech pět výše uvedených objektů.
Když už máme definován konkrétní stroj A=(K,&Sigma,P,q0,F), můžeme určit, jak tento stroj překládá konkrétní řetězec. Uvedeme nejprve formální definici a potom ji slovně vysvětlíme.
Množina platných překladů řetězce u překládacím strojem A je:
A(u) = {
v |
∃n≥0
∃(p1,u1,v1,r1),..,(pn,un,vn,rn) ∈P:
(∀i∈{1,...,n-1}: ri = pi+1)
∧
p1 = q0
∧
rn ∈F
∧
∧
∃k≥0:
u1u2..un = u$..$(k-krát)
∧
v1v2..vn = v }.
Definice stanoví, kdy je řetězec v překladem řetězce u. Vysvětlíme si slovně význam jednotlivých řádků definice:
Navíc stav, v němž se bude stroj nacházet po skončení výpočtu, musí být koncový.
Řetězec, který při použití těchto pravidel stroj přečte ze vstupu, musí být skutečně zadaným řetězcem u, případné může být zprava doplněn vhodným počtem znaků $.
Řetězec, který stroj zapíše na výstup, musí být přesně řetězcem v.
Překládací stroje nám budou sloužit k získání překladu jedné množiny řetězců na jinou množinu řetězců. Jestliže A je překládací stroj a M⊆&Sigma* nějaká množina řetězců, potom překlad množiny M strojem A je množina
A(M) = ∪u∈M A(u).
Jinými slovy, výslednou množinu A(M) sestrojíme tak, že vezmeme všechny řetězce z M a pro každý z nich přidáme do A(M) všechny jeho platné překlady.
Mějme abecedu &Sigma={0,..,9}. Nechť M je množina všech řetězců, které představují zápisy kladných celých čísel v desítkové soustavě. Sestrojíme překládací stroj A, pro který bude platit, že překladem této množiny M bude množina zápisů všech kladných celých čísel, která jsou dělitelná třemi.
Nejjednodušší bude prostě vybrat z M ta čísla, která jsou dělitelná třemi. Náš překládací stroj bude kopírovat cifry ze vstupu na výstup, přičemž si bude pomocí stavu pamatovat, jaký zbytek po dělení třemi dává dosud přečtené (a zapsané) číslo. Nachází-li se po dočtení vstupu ve stavu odpovídajícím zbytku 0, přejde do koncového stavu.
Formálně A bude pětice (K,&Sigma,P,0,F), kde K={0,1,2,end}, F={end} a překladová pravidla vypadají následovně:
P = { (x,y,y,z) | x∈{0,1,2} ∧ y∈&Sigma ∧ z=(10x+y) mod 3 } ∪ { (0,$,ε,end) }.
Mějme abecedu &Sigma={a,e,i,•,-}. Sestrojíme překládací stroj B, pro který bude platit, že překladem libovolné množiny M, která obsahuje pouze řetězce z písmen a, e a i, bude množina stejných řetězců zapsaných v morseovce (bez oddělovačů mezi znaky). Zápisy našich písmen v morseovce vypadají následovně: a je •-, e je • a i je ••.
Například množinu M={ae,eea,ia} by náš stroj měl přeložit na množinu {•-•, •••-}. (Všimněte si, že řetězce eea a ia mají v morseovce bez oddělovačů stejný zápis.)
Překládací stroj B bude číst vstupní řetězec po znacích a vždy zapíše na výstup kód přečteného znaku.
Formálně B bude pětice (K,&Sigma,P,♥,F), kde K={♥}, F={♥} a překladová pravidla vypadají takto:
P = { (♥,a,•-,♥), (♥,e,•,♥), (♥,i,••,♥) }.
Všimněte si, že nepotřebujeme nijak zvlášť kontrolovat, zda jsme na konci vstupu. Během celého překladu je totiž stroj v koncovém stavu, takže jakmile přečte poslední znak ze vstupu, bude vytvořený překlad platný.
Mějme abecedu &Sigma={a,e,i,•,-}. Sestrojíme překládací stroj C, pro který bude platit, že překladem libovolné množiny M, která obsahuje pouze řetězce tvořené znaky • a -, bude množina všech řetězců z písmen a, e a i, jejichž zápisy v morseovce (bez oddělovačů mezi znaky) jsou obsaženy v množině M. Například množinu M= {•-•, •••-} by náš stroj měl přeložit na {ae,eea,ia}.
Našemu překládacímu stroji dáme možnost rozhodnout se v každém okamžiku překladu, že bude číst kód nějakého písmena a zapíše na výstup toto písmeno. Potom každé možnosti, jak lze rozdělit vstupní řetězec na kódy písmen, bude odpovídat jeden platný překlad.
Formálně C bude pětice (K,&Sigma,P,♦,F), kde K={♦}, F={♦} a překladová pravidla vypadají následovně:
P = { (♦,•-,a,♦), (♦,•,e,♦), (♦,••,i,♦) }.
Ukážeme si, jak mohl probíhat překlad řetězců z výše uvedené množiny M. Existují tyto tři možnosti:
Mějme abecedu &Sigma={a,b,c}. Nechť množina X obsahuje právě všechny řetězce, v nichž je obsažen stejný počet znaků a a b. Tedy například abbccac∈X, ale cbaa∉X.
Nechť množina Y obsahuje právě všechny řetězce, které neobsahují žádné a, neobsahují podřetězec bc, a písmen c je dvakrát více než písmen b. Tedy například ccccbb∈Y, ale cccbcb∉Y a acacba∉Y.
Sestrojíme překládací stroj D, který přeloží X na Y.
Budeme překládat jenom některé vhodné řetězce z množiny X. Budou to ty řetězce, které neobsahují žádné c a všechna a v nich předcházejí všem znakům b. Takovýto řetězec přeložíme tak, že nejprve každé a přepíšeme na cc, a potom zkopírujeme na výstup všechna b. Tedy například překladem slova aabb bude slovo ccccbb.
Formálně D bude pětice (K,&Sigma,P,A,F), kde K={A,B}, F={B} a překladová pravidla vypadají takto:
P = { (A,a,cc,A), (A,ε,ε,B), (B,b,b,B) }.
Proč tento překládací stroj funguje? Když vstupní řetězec obsahuje nějaké písmeno c, při jeho překládání se u prvního výskytu c náš stroj zastaví. Proto takové řetězce nemají žádný platný překlad. Podobně nemají platný překlad řetězce, v nichž není dodrženo pořadí písmen a a b. Po přečtení nějaké posloupnosti písmen a přejde stroj pomocí druhého pravidla do stavu B, a pokud se poté ještě objeví na vstupu a, stroj se zastaví.
Platné překlady tedy existují skutečně pouze pro slova výše popsaného tvaru. Je zjevné, že překladem každého z nich získáme nějaký řetězec z Y, takže D(X)⊆Y. Naopak, vybereme-li si libovolné slovo z Y, snadno najdeme slovo z X, které se na něj přeloží. Proto také Y⊆D(X), takže Y=D(X).