Zadanie

V Legolande býva aj Indiana Jones, známy archeológ a akčný hrdina. Všetci s ním radi trávia čas, pretože vedia, že priam priťahuje dobrodružstvo. Nikoho teda neprekvapilo, keď sa jedného dňa Indy pri prechádzke po ulici prepadol cez zatarasený vchod do starodávnej jaskyne.

Indyho to samozrejme veľmi potešilo, najmä preto, že na stene okamžite zbadal mapu jaskyne, ktorá celkom jasne ukazuje, že na samom spodku sa nachádza obrovský poklad. Rád by sa k nemu čo najrýchlejšie dostal (a to, či sa potom dostane aj von, ho momentálne vôbec nezaujíma).

Keďže však jaskyňu našiel náhodou (cestou na nákup), nemá so sebou svoj bič, ani žiadne iné vybavenie, okrem masívneho krompáča (pochopiteľne). Takže keď raz niekam v jaskyni zoskočí, nevie už vyliezť vyššie. Môže niektoré steny jaskyne rozbiť, aby sa dostal ďalej, ale kopať s krompáčom je namáhavé (dokonca viac, než ho nosiť všade so sebou), takže by sa tomu chcel čo najviac vyhnúť.

Pomôžte Indymu naplánovať cestu tak, aby sa dostal na spodok jaskyne a pritom musel čo najmenejkrát použiť krompáč. A ak sa tam dostať nedokáže, povedzte mu, nech tam radšej nelezie.

Úloha

Jaskyňa, ktorú Indy objavil, má tvar mriežky s výškou \(V\) a šírkou \(S\). Niektoré jej políčka sú priechodné, ostatné obsahujú kocky. Kocky síce Indy dokáže rozbiť, ale stojí ho to veľa času. Preto by chcel nájsť takú cestu nadol, ktorú dokáže prejsť s čo najmenším počtom rozbitých kociek. Vôbec mu pritom nevadí, že prejde veľa políčok, keďže chodenie je omnoho menej namáhavé, než rozbíjanie.

Indy začína v ľavom vrchnom rohu jaskyne. Pohybovať sa dokáže len o jedno políčko doprava alebo doľava, ak sa nachádza nad prázdnym políčkom, okamžite spadne. Počas cesty nikdy nesmie spadnúť z väčšej výšky, než \(P\) políčok, inak si ublíži.

Rozbíjať dokáže Indy len kocky, ktoré sa od neho nachádzajú o jeden riadok nižšie, o jedno políčko vedľa a políčka nad nimi sú voľné. V príkladoch nižšie sú všetky takéto kocky označené X, ostatné kocky #, prázdne políčka . a Indyho aktuálna poloha I.

..I..   .#I..   ..I..   ##I##
#X#X#   ###X#   #.#X#   ..###

Vašou úlohou je vypísať minimálny počet kociek, ktoré musí Indy rozbiť, aby sa dostal na spodok (do ľubovoľného políčka spodného riadku) jaskyne bez toho, aby si ublížil pádom. Ak sa dolu nevie dostať žiadnym spôsobom, vypíšte “Do tejto jaskyne sa liezt neoplati”.

Formát vstupu

V prvom riadku sú čísla \(V\), \(S\) a \(P\) oddelené medzerou. Platí, že \(P \leq V\). Nasleduje \(V\) riadkov dlhých \(S\) znakov – znaky môžu byť . (prázdne políčko) alebo # (kocka). V ľavom hornom políčku tabuľky je vždy prázdne políčko, pod ním je vždy kocka.

Každá zo štyroch testovacích sád je za dva body. V jednotlivých sadách platia nasledovné obmedzenia:

Sada 1 2 3 4
\(2 \leq V \leq\) \(10\) \(20\) \(50\) \(100\)
\(2 \leq S \leq\) \(10\) \(20\) \(60\) \(100\)
\(1 \leq P \leq\) \(10\) \(20\) \(50\) \(100\)

V prvej a tretej sade zároveň platí \(P = V\), teda Indy nikdy nespadne z tak veľkej výšky, aby sa mu niečo stalo.

Formát výstupu

Vypíšte jeden riadok obsahujúci buď číslo (najmenší možný počet kociek, ktoré musí Indy rozbiť, aby sa dostal do spodného riadku jaskyne), alebo reťazec “Do tejto jaskyne sa liezt neoplati”. Nezabudnite vypísať znak konca riadku.

Príklady

Input:

5 5 5
....#
###..
####.
...##
.####

Output:

2

Input:

5 5 1
.....
####.
####.
...##
.####

Output:

4

Input:

5 5 1
....#
###..
####.
.####
..###

Output:

7

Input:

5 4 5
....
###.
####
####
.###

Output:

Do tejto jaskyne sa liezt neoplati

V prvom príklade stačí Indymu vykopať kocku na súradniciach [2,1] (súradnice x,y číslujeme od nula od ľavého vrchného rohu), zoskočiť na uvoľnené miesto, spraviť krok doprava, vykopať políčko [2,2] a potom už len dôjsť do cieľa vľavo dole.

V druhom príklade by mohol postupovať podobne a vykopať políčka [3,1], [2,1] a [2,2], ale všimnime si, že potom by spadol z výšky 2, čo by mu ublížilo. Namiesto [2,2] teda musí vykopať [3,2] a potom ešte [3,3].

V treťom príklade môže vykopať [2,1], (potom!) [1,1], ďalej [3,2], [2,2], [4,3], [3,3] a potom buď [4,4] alebo [3,4]. Keby napríklad najskôr nič nekopal a zoskočil rovno na políčko [4,2], nemohol by kopať a zasekol by sa. Všimnite si, že príklady 2 a 3 by sa nemohli vyskytnúť v sadách 1 a 3, pretože nespĺňajú \(P = V\).

Vo štvrtom príklade sa na spodok jaskyne dokopať nevieme. Pre zaujímavosť, dokázať to vieme napríklad tak, že v druhom riadku vieme mať voľné najviac tri políčka, v treťom najviac dve a vo štvrtom najviac jedno, ale žiadne z nich nevie byť na ľavom kraji, preto už sa do piateho riadku nedostaneme.

Ako neriešiť úlohu

Pri prvom pohľade by sa mohlo zdať, že úlohu pôjde vyriešiť nejakým jednoduchým prehľadávaním do šírky, pri ktorom skúsime vždy najskôr spraviť krok doprava a doľava a potom kopať. Už z príkladov je však jasné, že to tak jednoduché nebude. Občas totiž môže byť najefektívnejšia možnosť (alebo jediná možnosť) vykopať v jednom riadku niekoľko súvislých kociek, aby sme aj v riadku pod ním mohli vykopať niekoľko ďalších kociek (pričom musíme stáť na predtým vykopaných políčkach), a tak ďalej – ako môžeme vidieť na treťom príklade.

Nestačí nám teda vedieť, ako sme sa na nejaké políčko dostali – dôležité je aj to, ktoré políčka sme cestou vykopali.

V jaskyni sa hodí dynamit

Takýto typ zadaní vedie k myšlienke dynamického programovania, ktorého princípom je, že riešime čiastkové problémy (napríklad ako najefektívnejšie sa vieme dostať na dané políčko) a tieto čiastkové riešenia využijeme na efektívne vyriešenie ďalších podproblémov, až po riešenie pôvodného problému (ako najefektívnejšie sa vieme dostať na políčko v spodnom riadku).

Kľúčovú úlohu zohráva takzvaný stavový priestor – definujeme si stav ako nejakú \(n\)-ticu podstatných informácií, pre každý možný stav potom vyriešime podproblém pomocou iných stavov. Množinu všetkých stavov nazývame práve stavový priestor. Priestor preto, že si ho zvykneme predstavovať \(n\)-rozmerný, s “osami”, ktoré tvoria jednotlivé prvky \(n\)-tice.

Implementujeme ho často ako \(n\)-rozmerné pole – tento prístup funguje, pokiaľ vieme, aké má celý priestor hranice v každej súradnici. Druhá možnosť je pamätať si dosiahnuté stavy v slovníku, tá sa zíde najmä vtedy, keď očakávame, že navštívime omnoho menej stavov, než všetky možné, a preto sa neoplatí vyrábať si veľké pole.

V naivnom nefunkčnom riešení by stav vyzeral ako dvojica súradníc políčka, na ktorom sa práve nachádzame. Stavový priestor by preto tvoril dvojrozmerné pole veľkosti šírka krát výška (teda rovnakého tvaru, ako jaskyňa). Pre každý stav by sme si pamätali najmenší počet kociek, ktoré bolo treba vykopať, aby sme sa do neho dostali.

Stavový priestor je užitočný koncept aj bez dynamického programovania – môžeme ho napríklad prehľadávať spomínaným BFS alebo použiť Djikstru. Pokiaľ však dokážeme vymyslieť takú reprezentáciu, že stavy ukladáme v poli, riešime jeden po druhom a máme istotu, že aktuálny stav závisí len na “predošlých” a nie na “nasledujúcich”, je najjednoduchšie proste prejsť postupne všetky stavy, a to sa nazýva Dynamické programovanie (DP).

Čo bude náš stav

Opäť si pre každý stav budeme pamätať najmenší počet kociek, ktoré treba vykopať, aby sme sa do neho dostali.

Ako sme zistili skôr, definovať stav len pomocou súradníc nestačí, treba vedieť aj to, ako sme kopali, kým sme sa na dané políčko dostali. Mohli by sme teda skúsiť stav reprezentovať ako (súradnica Y, súradnica X, voľné políčka v jaskyni). Keďže však jaskyňa môže mať až \(V \times S\) políčok, mohlo by existovať \(V \times S \times 2^{V \times S}\) stavov – rozhodne by sme teda nedokázali všetky reprezentovať v poli a časovo aj pamäťovo by bolo prehľadávanie priestoru veľmi neefektívne.

V takejto reprezentácií si toho pamätáme zbytočne veľa – vôbec nás totiž nemusí zaujímať, aké políčka sa nachádzajú v iných riadkoch, než je aktuálny riadok a riadok pod ním. Keďže sa nevieme pohybovať smerom dohora, všetky políčka v nižších riadkoch vyzerajú presne tak, ako pôvodná jaskyňa (zatiaľ sme ich nemohli vykopať) a všetky políčka vyššie sme už prešli (a poznáme najefektívnejší spôsob, ako sa cez ne dostať do aktuálneho stavu).

Ďalej si vieme všimnúť, že pokiaľ začneme v jednom riadku kopať, môžeme pokračovať v kopaní už len do jednej strany (cúvať) – nevieme už vykopané políčko preskočiť, môžeme len cezeň spadnúť o riadok nižšie (a vtedy v danom riadku kopať prestaneme).

Takisto nemá zmysel kopať prerušovane, pretože všetky políčka uvoľnené pred preskočenou kockou (prerušením kopania), by sme vykopali zbytočne – v ďalšom riadku sa cez nevykopanú kocku nevieme dostať a v neskorších riadkoch už budú tieto políčka príliš vysoko, aby čokoľvek ovplyvnili. Má teda zmysel kopať len jeden súvislý úsek.

Vďaka tomu si stav vieme definovať ako štvoricu (súradnica Y, súradnica X, začiatok vykopaného úseku v aktuálnom riadku, koniec vykopaného úseku). Tento stavový priestor vieme ešte zmenšiť, keď si uvedomíme, že:

  1. každý úsek prestaneme kopať stojac na políčku, ktoré je na jednom jeho konci (teda stačí riešiť kopanie úsekov končiacich tesne pred políčkom, kde stojíme)
  2. a zároveň nás na každom políčku vo vykopanom úseku zaujíma počet vykopaných kociek len v jednom smere (totiž aj tak budeme z aktuálneho políčka kopať len jedným smerom, pretože podľa 1. chceme, aby koniec kopaného úseku bol vedľa políčka, kde stojíme).

Dostaneme tak stavový priestor (súradnica Y, súradnica X, dĺžka vykopaného úseku, smer kopania) s rozmermi \(V \times S^2 \times 2\) (posledný prvok štvorice môže nadobúdať len dve hodnoty, preto sa do asymptotickej zložitosti nepočíta).

Ako môžeme prechádzať medzi stavmi

Tu by sme mohli zvoliť jednu z dvoch základných možností – pre každý stav vyriešiť buď ako najlepšie sa dá do neho dostať (prechádzaním predošlých stavov), alebo ako najlepšie sa dá dostať do iných stavov z neho (prechádzaním nasledujúcich stavov a prepisovaním ich hodnôt lepšími).

Keďže dosť stavov pravdepodobne bude nedosiahnuteľných, oplatí sa zvoliť si druhú možnosť, ktorá nám dovolí pri navštívení nedosiahnuteľného stavu (čo spoznáme napríklad tak, že si na začiatku do všetkých stavov priradíme väčšiu hodnotu, než kedy vieme dosiahnuť, napríklad \(V \times S\)) tento stav jednoducho preskočiť.

Teraz už treba len vymyslieť prechody medzi stavmi. Stav, v ktorom sa nachádzame nám vraví, že sme na súradniciach \(X, Y\) a smerom (doprava alebo doľava) je určite voľných (predtým vykopaných) \(Z\) políčok (voľných políčok tam samozrejme môže byť viac, než \(Z\), a zároveň nemáme garantované ani to, že tých \(Z\) políčok sa dá dosiahnuť, pretože v riadku nižšie môžu byť diery).

Pohyb bude teda vyzerať tak, že sa skúsime pohnúť doľava aj doprava tak ďaleko, kým to pôjde – teda kým bude na našom riadku vykopané alebo voľné políčko a v nižšom riadku nebude voľné políčko (cez ktoré by sme spadli). V každom navštívenom stave sa pokúsime aktualizovať jeho hodnotu za hodnotu aktuálneho stavu, ak je menšia (našli sme cestu do aktuálneho stavu, ktorá celkovo potrebuje menej kopaní). Pokiaľ sme sa pohli rovnakým smerom, ako vraví aktuálny stav, počet určite voľných políčok (\(Z\)) sa zníži o počet prejdených polí, inak sa o toľko zvýši. Zároveň pre každé dosiahnuté políčko skúsime aktualizovať aj stav s opačným smerom, so zodpovedajúcimi počtami voľných políčok (zvýšenými / zníženými o koľko sme zatiaľ počas pohybu prešli). Keď narazíme na políčko, cez ktoré by sme spadli nižšie, aktualizujeme aj dopadové políčko a skončíme pohyb.

Kopanie je veľmi podobné, skúsime ísť doprava a doľava (rovnako ako pri pohybe), ale budeme aktualizovať políčka o riadok nižšie – ako keby sme prišli k nejakému vzdialenenḿu políčku, vykopali od neho smerom späť k aktuálnemu políčku všetky políčka (vrátane samotného vzdialeného políčka) a potom zoskočili na prvé vykopané políčko, teda sa pohli o jedno doprava alebo doľava.

Ako vyriešime padanie

Hoci sa táto časť môže zdať zložitá (aj keď možno oproti zbytku úlohy ani nie), vieme padanie jednoducho vyriešiť pomocou predpočítania si, kam by sme dopadli, keby sme začali padať z každého políčka.

Pôjdeme od spodku jaskyne po vrch a vždy, keď nájdeme políčko, pod ktorým je kocka, prejdeme postupne všetky voľné políčka nad ním až po prvé políčko s kockou (vrátane, pretože tá by mohla byť v budúcnosti vykopaná), a poznačíme si na ne, o koľko políčok nižšie spadneme, ak sa na nich ocitneme. Môžete si rozmyslieť, že pre každé políčko takto máme len konštantný počet operácií. Dôležité je, že nikdy nenastane situácia, že by sme spadli cez viac než jedno vykopané políčko naraz, keďže sa nevieme hýbať smerom dohora.

Ak potom niekedy zistíme, že by pohyb na voľné políčko alebo pohyb po kopaní spôsobil príliš vysoký pád, jednoducho cieľový stav neaktualizujeme.

Akú to má zložitosť

Pamäťová zložitosť závisí na uložení si jaskyne, výšok pádu (obe zaberajú \(O(V \times S)\) miesta) a najmä samotného stavového priestoru (ten zaberá \(O(V \times S^2)\) miesta, pretože počet vykopaných políčok v každom riadku môže byť až \(O(S)\)). Celková pamäťová zložitosť je teda \(O(V \times S^2)\).

Časová zložitosť závisí na počte stavov (\(O(V \times S^2)\)) a zložitosti prechodov medzi jednotlivými stavmi. V tomto vzoráku sú opísané prechody, ktoré majú zložitosť (pohyb aj kopanie) \(O(S)\), keďže z každého stavu sa pokúšame aktualizovať \(O(S)\) ďalších. Celková časová zložitosť je teda \(O(V \times S^3)\).

Na záver prezradíme, že síce nejde stavový priestor zmenšiť, ale je možné trochu ho zmeniť a vymyslieť zložitejšie prechody, ktoré majú konštantnú zložitosť (čím by sa dala dosiahnuť celková časová zložitosť \(O(V \times S^2)\)). Za odovzdanie takéhoto riešenia v popise bolo možné získať bonusové body.

Diskusia

Tu môžte voľne diskutovať o riešení, deliť sa o svoje kusy kódu a podobne.

Pre pridávanie komentárov sa musíš prihlásiť.