Zoznam úloh

5. Kríky so šípkami

Zadanie

Červená Čiapočka si ďalej kráča lesom, kráča a tu zrazu naďabí na krík s malinami. “Ale však babka by sa nehnevala, keby som zopár malín pozbierala.” A tak Čiapočka zbiera maliny, zbiera, a tu zrazu naďabí na černice. “Ale však babka by sa nehnevala, keby som aj zopár černíc pozbierala.” A tak zbiera černice, zbiera, a tu zrazu naďabí na šípky. “Ale však babka by sa ani vtedy nehnevala, keby som aj zopár šípok pozbierala.” A teda zbiera a zbiera, a tu zrazu - stratila sa v húšti šípkových kerov. Pomôž Červenej Čiapočke nájsť cestu späť k chodníku k babkinmu domu.

Úloha

Šípkové húštie vyzerá ako tabuľka $r \times s$ vyplnená znakmi >, <, ^ a v. Každé políčko je teda šípka hore, doľava, doprava alebo dole. Červená Čiapočka začína na počiatočnej pozícii $x, y$ a v jednom kroku sa posunie v smere aktuálnej šípky o jedno políčko. Ak šípka ukazuje von z tabuľky, tak sa dostane von z húštia a už dokonca zostane vonku. Je daný počet krokov $k$. Zistite, kde sa bude Červená Čiapočka nachádzať danom počte krokov.

Formát vstupu

Políčko v tabuľke vieme označiť jeho súradnicami, teda číslom stĺpcu a riadku, v ktorom sa nachádza. Čísluje sa od nuly, teda napríklad prvý stĺpec a druhý riadok je $0, 1$.

Na vstupe sú na prvom riadku celé čísla $s$, $r$, počet stĺpcov a riadkov tabulky, počet krokov $k$ a štartovné súradnice $x, y$. Čísla sú oddelené medzerou.

Nasleduje $r$ riadkov tabuľky zo znakov >, <, ^ a v, každý po $s$ znakoch.

Obmedzenia na vstupe

Sada 1 2 3
$r, s \leq$ $70$ $1000$ $1000$
$k \leq$ $5000$ $10^6$ $2 \times 10^{12}$

Formát výstupu

Na jeden riadok vypíš súradnice, na ktorých skončila Červená Čiapočka po $k$ krokoch. Ak je v tabuľke, vypíš číslo stĺpcu a riadku, oddelené medzerou. Ak sa dostala von, vypíš VONKU.

Príklad

Vstup

2 2 5 0 0
>v
^<

Výstup

1 0

Začína vľavo hore a chodí do kruhu v smere hodinových ručičiek, takže po piatich krokoch bude vľavo hore.

Vstup

2 2 5 0 0
>v
^>

Výstup

VONKU

Začína vľavo hore, pôjde doprava, dole, doprava, a tým sa dostane von z húštia. Ďalšie kroky už nebude robiť.

Ako prvé nás môže napadnúť spraviť prgram, ktorý bude simulovať kroky Červenej Čiapočky. Uvedomme si, že ak sa Červená Čiapočka nachádza na pozícii $x, y$, teda v $x+1$-om stĺpci a $y+1$-om riadku, tak krok doľava len znamená zmena $x$-ovej súradnice o $-1$, a krok doprava o $+1$. Podobne $y$ súradnicu zvyšujeme o $1$ ak ide hore a znižujeme krokom dole. Takže jednoduchý program čo sleduje Čiapočkine kroky si ukladá premenné $x, y$ - aktuálnu pozíciu Červenej Čiapočky a pre každý z $k$ krokov prečíta znak v tabuľke na súradniciach $(x, y)$. Potom ak je tento znak >, zvýši $x$ o jednu, ak je to <, tak zníži $x$ o jedna, ak prečíta ^, zvýši zas $y$ o $1$ a ak prečíta v, tak zníži $y$ o $1$. Avšak šípkové húštie nieje nekonečné, teda po každom kroku musíme skontrolovať či platí $0 \leq x \leq s - 1$ a $0 \leq y \leq r - 1$, kde $r, s$ sú známy počet riadkov a stĺpcov (pri porovnávaní však musíme odčítavať $1$, keďže prvý riadok a prvý stĺpec sú označené ako nulté). Ak jedna z týchto podmienok nebude platiť, znamená to, že čiapočka vypadla z tabuľky a teda môžeme rovno vypísať VONKU. Ak po $k$ krokoch ešte nevypadneme, vypíšeme aktuálnu hodnotu $x, y$.

Program v pythone by vyzeral napríklad takto:

s, r, k, startX, startY = map(int, input().split()) #Načítame konštanty zo vstupu
tabulka = []

try:
    while True: 
        line = input().strip()
        if not line:
            break
        tabulka.append(line)
except EOFError:
    pass

#Načítame tabuľku zo vstupu

#Začiatočná hodnota x, y bude štatovná pozícia zo vstupu
posX = startX
posY = startY

broke = False #Touto premennou budeme sledovať či sme vypadly z tabuľky. Ak áno, zmneníme na True

for krok in range(k): #Prechádzame k krokov cez tabuľku
    if (tabulka[posY][posX] == ">" and posX < s - 1):
        posX += 1
    elif (tabulka[posY][posX] == ">" and posX == s - 1):
        print("VONKU")
        broke = True
        break
    elif (tabulka[posY][posX] == "<" and posX > 0):
        posX -= 1
    elif (tabulka[posY][posX] == "<" and posX == 0):
        print("VONKU")
        broke = True
        break
    elif (tabulka[posY][posX] == "v" and posY < r - 1):
        posY += 1
    elif (tabulka[posY][posX] == "v" and posY == r - 1):
        print("VONKU")
        broke = True
        break
    elif (tabulka[posY][posX] == "^" and posY > 0):
        posY -= 1
    elif (tabulka[posY][posX] == "^" and posY == 0):
        print("VONKU")
        broke = True
        break

if not broke: #ak sme nevypadly z tabuľky, vypíšeme aktuálnu pozíciu na ktorej sme skončili
    print(*(posX, posY))

Takýto program stačí na vyriešenie prvej a druhej sady, avšak pri tretej zlyháva. To je kvôli tomu, že v tretej sade môže byť počet krokov až $2 \times 10^{12}$, (viac ako druhá mocnina počtu krokov v 2. sade) a ak prechádzame každým krokom tak prekročíme časový limit. Ako teda zistiť kde sa na konci nachádzala červená čiapočka bez toho aby sme museli ísť krok po kroku?

Všimnime si, že ak by v 3. sade prešla Červená Čiapočka nejakým spôsobom každým políčkom práve raz, potrebovala by na to nanajvýš $r \times s \leq 1000 \times 1000 = 10^6$ krokov. Teda ak urobí viac ako $10^6$ krokov a nevypadne pri tom von, znamená to, že sme sa na nejakom mieste zacyklili - vrátili sa na políčko kde sme už boli a od vtedy sa len točíme v cykle. Preto si počas prechádzania tabuľkou budeme pamätať, v ktorom kroku sme ktoré políčko navštívili. Konkrétne si budeme ukladať pre každú pozíciu $(x,y)$ číslo kroku, v ktorom sme sa tam prvýkrát dostali. Na toto vieme použiť dictionary (“slovník”), dátovú štruktúru, ktorá nám umožní ukladať si páry dvoch hodnôt a vyhladávať ich priemerne v konštantnom čase. Zároveň si budeme ukladať zoznam navštívených pozícií v poradí, v akom po nich Čiapočka kráčala.

Program bude podobný ako v jednoduchom riešení vyššie, ale vždy po presune navyše skontrolujeme, či sa nová pozícia už v našom slovníku nachádza. Ak sa dostaneme na políčko, ktoré sme už navštívili, našli sme cyklus. Nech $p$ je krok, v ktorom sme toto políčko navštívili prvýkrát a $c$ je aktuálny počet vykonaných krokov. Potom dĺžka cyklu je $c-p$. Vieme, že všetky ďalšie pohyby budú len opakovaním tohto cyklu. Ak ešte zostáva vykonať $k-c$ krokov, stačí nám zistiť $(k−c) \mod (c−p)$, čo nám povie, o koľko krokov sa máme posunúť v rámci cyklu. Ako finálnu pozíciu vrátime teda súradnice políčka na $(k−c) \mod (c−p)$ mieste v cykle.

Časová zložitosť

V najhoršom prípade prejdeme každým políčkom v tabuľke práve raz, teda časová zložitosť je nanajvýš $O(r \times s)$. Všimnime si, že v každej sade platí $\max(r \times s) \leq \max k$.

Pamäťová zložitosť

Ukladáme celú tabuľku šípiek s pamäťovou zložitosťou $O(r \times s)$ a slovník, ktorý môže v najhoršom prípade obsahovať každé políčko práve raz, teda tiež zabere $O(r \times s)$. Ostatné premenné sú hodnoty s konštantnou pamäťou. Teda pamäťová zložitosť je $O(r \times s)$.

Implementácia

Tu je celý program v pythone:

s, r, k, startX, startY = map(int, input().split()) #Načítame konštanty zo vstupu
tabulka = []

try:
    while True: 
        line = input().strip()
        if not line:
            break
        tabulka.append(line)
except EOFError:
    pass
#Načítame tabuľku zo vstupu

#Začiatočná hodnota x, y bude štatovná pozícia zo vstupu
posX = startX
posY = startY

broke = False #Touto premennou budeme sledovať či sme vypadly z tabuľky. Ak áno, zmneníme na True

visited = {} #slovník na sledovanie navštívených pozícií
at_step = {} #slovník na sledovanie v ktorom kroku sme boli na danej pozícii
posledny_krok = 0
dlzkaCyklu = 0
for krok in range(k): #Prechádzame k krokov cez tabuľku
    posledny_krok = krok
    if (posX, posY) in visited: #ak sme už túto pozíciu navštívili, našli sme cyklus
        dlzkaCyklu = krok - visited[(posX, posY)] #dĺžka cyklu je aktuálny krok mínus krok keď sme túto pozíciu navštívili prvýkrát
        break
    visited[(posX, posY)] = krok #uložíme krok kedy sme navštívili túto pozíciu
    at_step[krok] = (posX, posY) #uložíme pozíciu na ktorú sme prišli v tomto kroku
    if (tabulka[posY][posX] == ">" and posX < s - 1):
        posX += 1
    elif (tabulka[posY][posX] == ">" and posX == s - 1):
        print("VONKU")
        broke = True
        break
    elif (tabulka[posY][posX] == "<" and posX > 0):
        posX -= 1
    elif (tabulka[posY][posX] == "<" and posX == 0):
        print("VONKU")
        broke = True
        break
    elif (tabulka[posY][posX] == "v" and posY < r - 1):
        posY += 1
    elif (tabulka[posY][posX] == "v" and posY == r - 1):
        print("VONKU")
        broke = True
        break
    elif (tabulka[posY][posX] == "^" and posY > 0):
        posY -= 1
    elif (tabulka[posY][posX] == "^" and posY == 0):
        print("VONKU")
        broke = True
        break

if not dlzkaCyklu == 0: #ak sme našli cyklus, vypočítame pozíciou v ňom po k - posledny_krok krokoch
    endPos = at_step[((k - posledny_krok) % dlzkaCyklu) + (posledny_krok - dlzkaCyklu)]
    print(*endPos)
    broke = True

if not broke: #ak sme nevypadly z tabuľky, vypíšeme aktuálnu pozíciu na ktorej sme skončili
    print(*(posX, posY))
Pre odovzdávanie sa musíš prihlásiť.