Last Modified: 28.9.2020
zápočtové příklady, odkazy, cvičení 1, cvičení 2, cvičení 3, cvičení 4, cvičení 5, cvičení 6, cvičení 7, cvičení 8, cvičení 9, cvičení 10, cvičení 11, cvičení 12, cvičení 13, cvičení 14.
Pro získání zápočtu z NTIN060 je potřeba získat (2/3 z možných bodů za domácí úkoly zadávané v průběhu semestru).
Pokud není uvedeno jinak, počet přidělených bodů rychle klesá v případě použití časově neoptimálních algoritmů. Za úkoly odevzdané po termínu je možno získat jen poloviční počet bodů. Termínem je vždy 9:00 v datum cvičení, mezi dnem stanovení termínu a termínem je vždy přinejmenším jedno cvičení (výjimky obou pravidel možná budou na konci semestru). U některých úloh nemusí být nastaven termín, zvláště pokud navazují na dosud neprobranou látku. Dokud nacházíte lepší řešení, můžete úlohy odevzdávat opakovaně, o již získané body tím nepřijdete. Domácí úkoly odevzdávejte e-mailem ať ve formě fotografie/skenu rukou napsaného řešení, nebo přímo v nějakém jiném všeobecně rozšířeném formátu (např. pdf, či přímo v textu e-mailu).
Kód | Body | 1. termín | Zadání |
---|---|---|---|
indukce | 10 | 5.3.2020 | Dokažte matematickou indukcí případy rekurence t(n)≤cnα+∑t(ain) (t(n)≤C, pro n≤n0) dle srovnání ∑aαi s 1, užitečné je dokazovat vztah t(n)≤c1nα+c2nβ, kde ∑aβi=1, případně t(n)≤c1nα+c2nαlogn, vhodná ci zjistěte v průběhu důkazu. Hodí se za indukční předpoklad brát, že tvrzení platí pro všechna menší n. |
mult | 10 | 12.3.2020 | Ukažte, dva (podobné) algoritmy, jak je možno násobit n ciferná čísla v čase O(nlog23). |
medián | 10 | 19.3.2020 | Je dán „komprimovaný“ seznam čísel. Nalezněte jejich medián. Komprimace spočívá v tom, že místo čísel dostáváme dvojice čísel, kde první udává hodnotu a druhé počet výskytů dané hodnoty v nekomprimované posloupnosti (stejná hodnota může být ve více dvojicích, dokonce ani není zakázáno její použití v ihned následující dvojici). Nemusíte popisovat jak se hledá medián v nekomprimovaném případě, ale můžete to využít jako podprogram. |
řeka | 10 | 30.4.2020 | Je dáno povodí řeky ve formě orientovaného stromu, v němž každý vrchol je buď soutok nebo molo (či současně obojí). Pro každý úsek mezi sousedícími vrcholy je dána jejich vzdálenost (malé přirozené číslo). Je dáno malé přirozené číslo d. Otázka je, zda v povodí existují dvě mola, kde jedno je dosažitelné po proudu od druhého a jejichž vdálenost je přesně d. Popište algoritmus, který na danou otázku odpoví. |
řeka2 | 10 | 30.4.2020 | Je dáno povodí řeky ve formě orientovaného stromu, v němž každý vrchol je buď soutok nebo molo (či současně obojí). Pro každý úsek mezi sousedícími vrcholy je dána jejich vzdálenost (malé přirozené číslo). Je dáno malé přirozené číslo d. Otázka je, zda v povodí existují dvě mola (mohou být na různých přítocích), jejichž (říční) vdálenost je přesně d. Popište algoritmus, který na danou otázku odpoví. |
rekur | 10 | 9.4.2020 | Zjednodušte popis funkce t (stačí Θ(t)) dané následující rekurencí: t(n)=t(⌈√n⌉)+c, pro n≥3 a kde t(n)=n pro 1≤n≤2. |
rekur2 | 10 | 16.4.2020 | Zjednodušte popis funkce t (stačí Θ(t)) dané následující rekurencí: t(n)=2t(⌈√n⌉)+c, pro n≥3 a kde t(n)=n pro 1≤n≤2. |
perm2n | 5 | 30.4.2020 | Pro danou permutaci čísel od 1 do K nalezněte její „abecední“ pořadí mezi všemi takovými permutacemi (předpokládejte, že máte aritmetiku s dostatečně velkými čísly). |
n2perm | 5 | 30.4.2020 | Nalezněte permutaci která je mezi permutacemi čísel od 1 do K na daném n tém „abecedním“ pořadí (předpokládejte, že máte aritmetiku s dostatečně velkými čísly). |
permBi | 10 | 30.4.2020 | Algoritmicky popište zobrazení a k němu inverzní zobrazení, mezi čísly od 1 do K! a permutacemi čísel od 1 do K (předpokládejte, že máte aritmetiku s dostatečně velkými čísly). |
lavl | 10 | 7.5.2020 | Ukažte, jak je možno z libovolného binárního stromu vytvořit AVL strom (zachovávající inorder pořadí) v lineárním čase a ukažte, že rychleji to nejde. |
barvící pravidla | (12) | Zdůvodněte, proč barvící algoritmus pro hledání minimální kostry nemůže žádnou hranu obarvit podruhé. |
Id studenta | celkem | rozpad bodů |
---|---|---|
C | 60 | indukce(1), mult(10/2), medián(4), rekur(10), rekur2(10), řeka(6), řeka2(4/2), perm2n(4), n2perm(4), permBi(4/2), lavl(10/2), barvící pravidla(7) |
E | 65 | indukce(1), mult(10), medián(4), rekur(10), rekur2(10), řeka(6), řeka2(0), perm2n(4), n2perm(4), permBi(6), lavl(10) |
J | 42 | mult(5), medián(4), rekur2(0), n2perm(4), perm2n(4), řeka(7), permBi(6), řeka2(5), lavl(7) |
K | 36 | mult(10), medián(6), n2perm(0), perm2n(0), řeka(6), řeka2(4), permBi(0), lavl(10) |
M | 25 | mult(10/2), medián(7), řeka(5), n2perm(4/2), perm2n(4/2), řeka2(8/2) |
N | 41 | mult(10), řeka(5), řeka2(0), rekur(4+6/2), rekur2(10), perm2n(4/2), n2perm(4/2), permBi(6/2), lavl(2) |
O | 10 | mult(10) |
A | 76 | indukce(6), mult(10), medián(10), řeka(7), řeka2(5), rekur(10), rekur2(10), permBi(8), perm2n(0), lavl(10) |
B | 70 | indukce(6), mult(10), medián(5), řeka(5), rekur(10), rekur2(10), n2perm(4), perm2n(4), permBi(6), lavl(10) |
D | 77 | indukce(6), mult(10), medián(9), rekur(10), rekur2(10), řeka(6), řeka2(6), perm2n(5), n2perm(5), lavl(10) |
F | 68 | indukce(1), mult(10), medián(6), rekur(10/2), rekur2(10), perm2n(4), n2perm(4), řeka(7), lavl(2+8/2), řeka2(8/2), permbi(10/2), barvící pravidla(6) |
G | 67 | indukce(4), mult(10), medián(5), rekur(10/2), rekur2(10), řeka(7), řeka2(4), n2perm(4+1/2), perm2n(5/2), permBi(10/2), lavl(10) |
H | 85 | indukce(10/2), mult(10), medián(10), řeka(10), řeka2(10), rekur(10), rekur2(10), n2perm(5), perm2n(5), permBi(10) |
I | 68 | mult(5), medián(9), rekur(10), rekur2(10), n2perm(4), perm2n(4), permBi(6), řeka(6), řeka2(4), lavl(10) |
L | 71 | mult(10), medián(4), rekur(10/2), rekur2(4+6/2), řeka(6), řeka2(4), perm2n(4+1/2), n2perm(4+1/2), lavl(10), permBi(8/2), barvící pravidla(12) |
P | 67 | medián(10), rekur(10), rekur2(10), perm2n(4), n2perm(4), permBi(6), lavl(10), barvící pravidla(8), mult(10/2) |
moje loňské cvičení, texty k cvičení Jana Hrice, cvičení Michala Kouteckého, Michala Oplera, Jakuba Pekárka, Jirky Švancary
Na cvičení jsem oznámil podmínky zápočtu a v podstatě celou hodinu jsem budoval oslí můstek k zadání prvního domácího úkolu.
Nejprve jsme si připomenuli, jak se sčítají geometrické posloupnosti, pak jsme se věnovali Fibonacciho posloupnosti (a posloupnostem obdobného typu), ukázali jsme si jak vyjádřit n-tý člen nejen skalárním vzorečkem, ale i maticovým vzorečkem. Maticový tvar měl výhodu pro vyhodnocování, protože mohl pracovat s přesnými čísly, zatímco skalární tvar naráží na reprezentaci algerbaických čísel, které nejsou racionální, při vyhodnocování je pak potřeba velice přesně hlídat aby rozsah kumulované chyby nepřesáhl toleranci (v našem případě, abychom se nedostali blíže k nesprávnému přirozenému číslu).
Nebylo překvapivé, že maticový tvar vůči bázi vlastních vektorů matice velice přesně korespondoval skalárnímu tvaru.
Pak jsme se dostali k řešení rekurence t(n)∈O(nα)+∑ki=1t(ain). Nejprve jsem se snažil poskytnout trochu času na seznámení se s rekurencí, pak jsem měl proslov o efektivitě zápisu. Odbočkou přes zapomínání neintegrovaných členů při několikanásobném integrování per partes a o správné organizaci zápisu jsme rozdělili výpočet na část s cnα a na části s t, které je ještě potřeba rozepsat. Homogenita zápisu v každé části nám umožnila nepsat zbytečně mnoho symbolů a věnovat se místo toho podtatě problému. Ukázalo se, že stěžejní je hodnota S(α)=∑aαi. Pokud je S(α)<1, vyjde t(n)∈O(nα), pokud je S(α)=1, vyjde t(n)∈O(nαlogn) a pokud je S(α)>1, existuje β>α pro něž je S(β)=1 a t(n)∈O(nβ). Zdůvodnění, pro případ S(α)>1 se součtem geometrické posloupnosti chyb odhadu byla trochu magie. Takže všechny odvozené výsledky je lepší zkontrolovat například „matematickou indukcí“. Z povídání o lineárních rekurencích speciálně v souvislosti vícenásobnými kořeny se dá tušit, že řešení jsou v případě S(α)=1. Je tvaru c1nα+c2nαlogn a v opačném případě c1nα+c2nβ. Vzhledem k tomu, že v O notaci můžeme součet funkcí odhadnout asymptoticky větší z nich, dostáváme tvrzení ve shodě s předchozími pozorováními.
Cvičení proběhlo nezvyklou formou při práci trojic u „tabulí“. Optimistickým plánem bylo procvičit následující:
Ukázalo se, že studenti nejsou v logaritmování zběhlí a zvláště 2log4n dělalo naprosté většině z nich problém a tak strávili nad prvním příkladem větší část hodiny. Všechny skupiny nakonec stihly první tři případy, kde druhý byl nejjednodušší. Třetí příklad byl zákeřný v tom, že platnost jedné implikace vyžaduje doplnit předpoklad nezápornosti obou funkcí. Některé skupiny si dále vybrali příklad 7 (a úspěšně jej vyřešily), jiné si vybrali příklad 4 a některé jej dořešily. Jedna skupina si vybrala příklad 5 a dořešení (s malou) nápovědou byla docela blízko.
Na začátku hodiny jsem konstatoval, že vynechání matematické analýzy v prvním semestu má velký vliv na početní dovednosti studentů. V předchozích letech byly s úkolem indukce mnohem menší problémy. Naznačil jsem proto v úvodu hodiny, jak důkaz matematickou indukcí probíhá.
Následně jsme se věnovali příkladům, které jsme nedořešili ve skupinkách minulou hodinu. Probrali jsme worst case algoritmus na hledání k-tého z n prvků, hledání „horního mostu“ metodou prune and search, a celkově hledání konvexniho obalu pomocí Kirkpatrick-Seidel algoritmu. Ve všech třech případech jsme dostali zváštní variantu rekurence. V posledním případě nebylo možno použít vyřčenou větu ale bylo potřeba zopakovat její důkaz. Zajímavý byl trik s rozmísťováním mincí limitující velikost stromu rekurence na h a to, že časově nejhorší průběh algoritmu byl pro vyvážený strom rekurence.
Poslední příklad z minulé hodiny vedl k odvození časové složitosti Strassenova algoritmu O(nlog27), pokud neměříme velikosti vstupu, ale rozměrem matice (vůči velikosti vstupu by šlo o O(n(log27)/2)). Jednu z možných variant algoritmu (snad i snadno zapamatovatelnou) jsme si ukázali včetně důkazu korektnosti. Hodilo se opět použít značení, kde minimalizujeme počet použitých písmen.
Na druhém cvičení nám zbyly ještě 4 minuty času, takže jsme si trošku popovídali o (ne)výhodách BFS a DFS při prohledávání implicitních grafů (zmínil jsem transpoziční tabulky na nespolehlivé značení vrcholů, kterými jsme prošli). Zazněla jejich kombinace IDA, která spojuje jejich výhody a téměř není nijak penalizována. Na grafu rubikovy kostky jsem ještě naznačil užitečnost heuristických dohadů dolního odhadu délky zbytku cesty na zrychlení IDA prohledávání. Užitečnost obdobné heuristiky u BFS jsme nezmiňoval.
Cvičení se kvůli epidemii Covid-19 v přítomnosti studentů nekonalo. Plánem bylo pracovat v malých skupinkách a věnovat se prohledávání grafů. Chtěl jsem v úvodních pár minutách na prvním cvičení říct něco o prohledávání implicitních (velkých grafů). Pak jsem chtěl přistoupit k druhé výhodě prohledávání do šířky a tou je lokalita prohledávání (prohledávání do šířky není vhodná technika pro pokus fyzicky opustit bludiště). Během prohledávání do hloubky si můžete globálně aktualizovat kontext prohledávaného místa, zatímco při prohledávání do šířky musíte všechny potřebné informace kopírovat do fronty a znovu obnovovat.
Měli jsme si nejprve připomenout pojmy jako jsou stromové hrany, zpětné hrany (a v případě orientovaných grafů dopředné a boční), navíc pojmy jako preorder, a postorder.
Měli jsme probrat příklady využívání kontextu v DFS. Na jednoduché úrovni to je počítání low funkce, tedy místa, kam je možno se z DFS podstromu dostat nejvýš jednou zpětnou hranou.
Využití low funkce pro detekci/vypisování komponent hranové/vrcholové dvousouvislosti měla být následná aplikace.
Další jednoduchou aplikací mělo být nalezení dvou stromově nejvzdálenějších vrcholů (neorientovaný strom s nezáporně ohodnocenými hranami).
Poslední, co jsem chtěl stihnout bylo hledání topologického uspořádání silně souvislých komponent orientovaného grafu. Postačilo by dvouprůchodové DFS prohledávání s využitím postorder.
Cvičení proběhlo formou videokonference pomocí sdíleného google docs.
Věnovali jsme se Dijkstrovu algoritmu nalezení nejlacinějších cest(cesty) z daného vrcholu s (do daného vrcholu t) v nezáporně ohodnoceném orientovaném grafu, hlavně jsme se soustředili na oddělení algoritmu a datových struktur.
Vyšel nám následující algoritmus:
Dijkstra(s(,t)){
for(v in V) v.stav=nenavštívený;
s.stav=otevřený; s.dist=0; h=new Heap();
a=s;
while (a!=null) {
for(v in a.successors) {
d = a.dist+cost(a,v); // zbytečné pro hotové, ale nechci psát dvakrát
if (v.stav==nenavštívený) {
v.dist=d;v.prev=a;v.heap=h.Insert(d);
} else if ((v.stav==rozpracovaný && v.dist>d) {
v.dist=d;v.prev=a;h.Decrement(v.heap,d);
}
}
a.stav=hotový;
a=h.FindMin();h.DeleteMin();
(if (a==t) break;)
}
}
Spočetli jsme, že Insert je použit nejvýš n=|V| krát, stejně tak FindMin a DeleteMin. Decrement je použit m−n=|E|−|V| krát. Při implementaci s optimálním interface dostaneme celkový čas n⋅O(1)+n⋅O(1)+n⋅O(logn)+(m−n)⋅O(1)=O(m+nlogn).
Kromě toho jsme vyřešili dva příklady A) hledání nejpropustnější cesty (kde místo součtem byla celková váha určena součinem pravděpodobností), a B) případ kdy optimalizujeme současně podle dvou vlastností.
Na cvičení bylo velkou většinou odhlasováno probrat Fibonacciho haldy. Takže jsem technikou extrémně drahého porovnávání předvedl techniku zavedení 4 potenciálů, a jednoho invariantu, abychom dostali strukturu s dvěmi typy hran a vrcholů a deklarovanými složitostmi operací. Princip decrement byl udržování lokální podmínky na ztrátu nejvýš jedna v každém černě připojeném potomkovi (vrcholy mají tedy buď ztrátu 0 nebo 1).
Zmínil jsem jen ideově, že existují varianty s worst case časovou složitostí jednotlivých operací, kde je místo lokálního globální omezení na ztrátu. Vrcholy bez černé hrany do rodiče jsou uchovávány ve struktůře na detekování stejných řádů, stejně tak vrcholy se ztrátou. Navíc tam jsou keše každé z těchto struktur, abychom rozložili časově náročné operace v čase. Celkovou veliost keší a struktur udržujeme v mezích. Kvůli worstcase jsme nuceni každou operaci končit pomocí FindMin.
Na konci hodiny jsme probrali ještě Bellman Ford. Analýza, nad finálním stromem nejlacinějších cest (vybíráme si v případě víceznačnosti stejnou cestu) je velice jednoduchá.
Na druhém cvičení jsme si ještě předtím stihli zopakovat Floyd Warshal. (Psal jsem to úmyslně tak, že jsem popsal postupně vzorec pro množinu sledů a minium z nich.)
Věnovali jsme se hledáním minimální kostry (v neorientovaných ohodnocených ne nutně souvislých grafech).
Ukázali jsme si obecný nedeterministický barvící algoritmus. Ukázali jsme si Kruskalův algoritmus,
Jarníkův (Prim, Dijkstra) algoritmus a naznačili Borůvkův. Ukázali jsme si, proč je můžeme interpretovat
jako barvící algotimy a zamysleli jsme se nad jejich časovou složitostí a použitím vhodných datových struktur.
V případě Jarníkova algoritmu jsme recyklovali implementaci Dijkstrova algoritmu pro najlacinější cesty
(jen není podmínka na nezápornost a nepřičítáme cesty do a
). V případě Kruskalova algoritmu by se nám hodila
struktura na spojování tříd ekvivalence, kam patří pěstované modré komponenty. Disjoint Find Union jsme neprobírali,
jen jsme si řekli, že bude zvládat jednotlivé operace amortizovaně v čase O(α(n)),
kde α je inverzní funkce k A(n,n) pro Ackermanovu velice rychle rostoucí funkci A(m,n).
Vzhledem k velikosti vesmíru se nedá očekávat, že bychom pracovali s grafy, kde α(n)>5.
Následně jsme srovnali algoritmy na základě toho, zda je lehké setřídit hrany podle velikosti. Pokud ano, vítězí Kruskalův, pokud ne, je lepší algoritmus založený na Jarnikově algoritmu. Nicméně místo O(m+nlogn), kde logn je způsobeno maximální možnou velikostí haldy se nabízí pěstovat haldy jen do omezené velikosti. Výsledný algoritmus Fredman Tarjan pracuje následovně: Zastropuje velikost hald na t=22m/n, pak budování modrých stromů bude trvat jen O(m). Pokud přidáme kontrakční fázi, která zredukuje vícenásobné mezistromové hrany a odstraní hrany uvnitř stromů (zvládneme s přihrádkovým tříděním v O(m)), pak můžeme celý postup zopakovat. Ukazuje se ale, že ni+1ti≤2m, takže ti+1=22m/ni+1≥2ti, takže fází nemůže být mnoho (O(log∗n)), časem nikdy nedojde k tomu, že bychom přestali pěstovat strom kvůli velikosti haldy. Taková fáze ale výpočet minimální kostry dokončí.
Ve zbytku hodiny jsme se věnovali intarface vyhledávacích stromů, rozmysleli jsme si, co by se nám hodilo, abychom mohli v databázi (rozumně efektivně) vyhodnocovat obdélníkové dotazy.
Věnovali jsme se především hashování, jeho randomizaci, hlídání faktoru naplnění a amortizované i deamortizované verze reakcí na neefektivní naplnění. Ke konci hodiny jsme se chvíli věnovali „high-level“ porovnání AVL a RB stromů.
Povídali jsme si o vyvažování a,b stromů a jeho složitosti (částečně v závislosti na a, b). Na prvním cvičení jsme si ještě stihli říct, jak se provádí insert v AVL, na druhém jsme si místo toho povídali o interface vyhledávacích stromů (který je činí prodejnými v konkurenci hashovacích tabulek)
Bavili jsme se na první skupině o DFU a na druhé skupině primárně o Delete v AVL, kromě toho jsme na obou skupinách mírně diskutovali o zadání domácích úloh. A probrali jsme efektivní vyhodnocování velkého MaserMind.
Řešili jsme různé hádanky které mají vzdáleně něco společného s programováním. Lámání čokolády, řezání kvádru, házení vajíček, vězni v zástupu tipující svoje číslo na jarmulce. Vězni na „úplném grafu“ viditelnosti, nezávisle tipující své číslo, kde úkolem je aby se aspoň jeden trefil.
Poslední úlohu jsme nedodělali, ale nápověda již byla dostatečná na vyřešení za neodevzdávaný nebodovaný domácí úkol (domácí úkoly už další zadávány nebudou).
Dořešili jsme hádanku s čepičkama na úplném grafu viditelnosti. Pak jsme řešili hádanku s nalezením n tého součtu dvojic hodnot z dvou uspořádaných polí velikosti n. Výsledná sublineární složitost algoritmu Θ(√nlog2n) je na první pohled překvapivá.
Na konci hodiny jsem dával poměrně silné nápovědy k domácím úlohám.
Na předposledním cvičení (prvním) jsem částečně odpověděl na otázku „optimality“ algoritmů. (Blumova věta říká, že optimální algoritmus nemusí obecně existovat, umíme argumentiovat (někdy) tím, že celý vstup je potřeba ... linearita, že v daném výpočetním modelu nejde řešit rychleji ... jen pro rozhodovací stromy, ale například zjištění unikátnosti čísel na vstupu vyžaduje v tomto modelu logn!, přičemž hashovací tabulky to řeší randomizovaně lineárně. Další možností je transformace problémů/úloh, dokážeme-li pomocí naší úlohy vyřešit něco „náročné“, musí být „náročná“ (je-li překlad nenáročný).)
Na druhém jsem se věnoval úloze hledání nejmenšího opsaného kruhu dané množině bodů. Popisoval jsem randomizovaně lineární Welzelův algoritmus, naznačil jsem ale, že je v něm chyba a jak ji opravit. Blíže jsem odkázal na wiki Smallest-circle_problem, kde je navíc i popis deterministického algoritmu (Megiddo ... několikrát zanořené prune and search) s mnohem horší multiplikativní konstantou. (Stránku jsem podstatně upravoval).
Pak jsem se věnovali dvojici úloh na dynamické programování úloh na infima a suprema dvou prvků ve svazu dle relace možnosti vyškrtávání v binárním zápise čísel. Po odvození rekurentních vzorečků jsme měli možnost vyplňování tabulky pro všechny binární prefixy obou vstupů (přepisováním hodnot je možno místo 2D recyklovat 1D tabulku), alternativou je jako v případě funkcionálních jazyků vyhodnocovat jen potřebné hodnoty, ale „hashovat“ si spočtené mezivýsledky, aby mezivýsledky nebyly počítány opakovaně. Je-li potřebných mezivýsledků o(d2), vychází funkcionální přístup efektivněji časově, než vyplňování tabulky. Naopak, je-li potřebných výsledků ω(d), je metoda přepisování 1D tabulky prostorově efektivnější. Kde d je celková délka binárních zápisů parametrů.
Nakonec jsme se věnovali hádance hledající optimální komunikační strategii n vězňů s k-stavovým (k=2) přepínačem a počítadlem návštěv. (Po dohodnutí strategie budou vězni na samotkách, jedinou možností komunikace bude poloha přepínače v místnosti, do které bude dozorce přivádět jako i-tou návštěvu vězně k s pravděpodobností 1/n. Strategie nastavování přepínače má být zvolena tak, aby byl průměrný počet návštěv, než bude mít některý vězeň důkaz, že v místnosti byli všichni, co možná nejmenší.)
Začali jsme se strategií s konečným průměrným počtem. Následně jsem naznačil možná zrychlení:
Na posledním cvičení jsme se věnovali narozeninovému paradoxu, Splay stromům a Blumovým filtrům. Pak jsme ještě chtěli řešit úlohu dynamického programování související s QR kódy, ale nezbyl na to čas.