Zoznam úloh

5. Obrovitánsky smäd

Zadanie

Fipo raz pozeral video o tom, ako sa odlišuje spôsob pitia u psov a mačiek. Uvedomil si pritom, že napriek tomu, že mnohým z nás sa aj tak pri pití podarí nechtiac sa obliať, to v porovnaní so zvieratami nemáme až také ťažké. O koľko jednoduchšie by to však bolo, keby namiesto toho voda tiekla smerom ku nim!

Zrazu si predstavil horu, na ktorej bol prameň, z ktorého cez mnohé údolia tiekol potok smerom nadol. V niektorých údoliach sa ale nachádzali psíky pachtiace za každou kvapkou z prameňa, ktorá k nim stiekla. Nanešťastie bol však prameň už takmer vyschnutý a ešte k tomu sa jeho tok stále menil a prechádzal z údolia do údolia…

Úloha

Hora, ktorú si Fipo predstavil, mala $n$ údolí očíslovaných od $0$ po $n-1$. V údolí $0$ zároveň pramení potok. Z prameňa postupne po jednej steká ďalej $t$ kvapiek. Kvapky tečú údoliami a vždy, keď prídu do nejakého údolia, kde nie je psík, stečú do nejakého iného údolia pod ním. Ak je v nejakom údolí psík, tento psík kvapku vypije.

POZOR: Môže existovať aj vyššie položené údolie ako prameň (teda také, z ktorého by kvapky stiekli do údolia s prameňom).

Pre každé údolie $i$ poznáte zároveň hodnotu $k_i$. Ak $k_i = 0$, tak je v údolí psík. Inak dostanete pre údolie $i$ aj postupnosť údolí $A_i$. Po každých $k_i$ kvapkách, ktoré pritečú do údolia $i$, sa zmení to, do ktorého údolia kvapky odtiaľto potečú. Je to nasledujúce údolie v postupnosti $A_i$, pričom prvých $k_i$ kvapiek potečie do údolia $A_i[0]$, druhých $k_i$ kvapiek do $A_i[1]$, a tak ďalej… Zároveň viete, že postupnosť $A_i$ je dlhá $\lceil t/k_i \rceil$ (teda zaokrúhlené nahor). Všimnite si, že pri takejto dĺžke postupnosti $A_i$ sa nikdy nestane, že by niektorá z uvažovaných $t$ kvapiek už nemala kam ďalej tiecť.

Hoci sú psíky veľmi smädné, fyziku aj napriek tomu ešte zatiaľ nevedia oklamať. Preto voda v každom okamihu steká len z vyššie položeného údolia do nižšie položeného, takže nevie tiecť do cyklu. Zistite, koľko kvapiek sa napokon dostane ku každému psíkovi.

Formát vstupu

V prvom riadku vstupu dostanete medzerou oddelené čísla $n$ a $t$ - počet dolín a počet kvapiek stekajúcich z prameňa.

V každom z nasledujúcich $n$ riadkov je celé číslo $k_i$ ($0 \leq k_i \leq 2\cdot10^9$). Ak $k_i=0$, znamená to, že sa v danom údolí nachádza psík. Inak hodnota $k_i$ určuje, po koľkých kvapkách začne voda stekať do ďalšieho údolia. Na zvyšku riadku je $\lceil t/k_i \rceil$ čísel, pričom $m$-té z nich určuje, do ktorého údolia bude stekať $m \cdot k_i$-ta až $(m+1) \cdot k_i - 1$-vá kvapka, ktorá do tohto údolia pritečie ($m$ číslujeme od nuly).

Nech $z_i$ je počet údolí, do ktorých niekedy bude tiecť voda z údolia $i$. Označme potom $\sum z_i$ súčet hodnôt $z_i$ cez všetky údolia $i$, v ktorých sa nenachádza psík. Sú 4 sady vstupov, pričom v jednotlivých sadách platia nasledujúce obmedzenia:

Sada 1 2 3 4
$1 \leq n \leq$ $1\,000$ $1\,000$ $3\cdot10^4$ $10^5$
$0 \leq \sum z_i \leq$ $3\,000$ $3\,000$ $10^5$ $3\cdot10^5$
$1 \leq t \leq$ $1\,000$ $10^9$ $10^9$ $10^9$

Formát výstupu

Pre každého psíka na samostatný riadok vypíšte, koľko kvapiek sa k nemu dostane. Tieto výsledky vypíšte v rovnakom poradí, v akom sú na vstupe údolia so psíkmi.

Príklady

Input:

4 10
3 1 2 3 2
0
2 3 1 3 1 3
0

Output:

5
5

Prvé tri kvapky pôjdu k prvému psíkovi. Ďalšie tri kvapky potom pôjdu najprv do údolia $2$ a odtiaľ prvé dve k druhému psíkovi a tretia k prvému psíkovi. Ďalšia trojica kvapiek pôjde priamo k druhému psíkovi. Posledná kvapka pôjde najprv do údolia $2$ a odtiaľ k prvému psíkovi, keďže je to už štvrtá kvapka, ktorá sa dostala do údolia $2$.

Input:

3 6
9 2
0
0

Output:

0
6

Z prameňa sa spustí $6$ kvapiek, no až po $9$ kvapkách sa má zmeniť smer odtekania vody, preto všetky kvapky skončia pri druhom psíkovi.

Input:

3 17
0
21 0
0

Output:

17
0

Priamo v údolí s prameňom sa nachádza psík, preto všetky kvapky vypije tento psík. Všimnite si, že údolie s prameňom sa nenachádza v najvyššie položenom údolí, keďže by doň stekala voda z údolia $1$, ak by sa do údolia $1$ nejaké kvapky dostali.

Bruteforce

Najjednoduchšie je odsimulovať zvlášť tok každej kvapky. Vtedy nám stačí udržiavať si pole počtov kvapiek, ktoré už cez jednotlivé údolia pretiekli. Pri každej kvapke potom v každom údolí, do ktorého sa táto kvapka dostane, vieme ľahko spočítať, kam má ďalej tiecť, a to budeme opakovať, až dokým nestečie až k nejakému psíkovi.

Pre všetky údolia na tomto toku si následne zaznačíme, že nimi pretiekla jedna ďalšia kvapka a toto zopakujeme pre každú kvapku.

Takto pre každú z $t$ kvapiek prejdeme potenciálne až tok dĺžky $n$, teda dostávame riešenie s časovou zložitosťou $O(nt)$.

Spracujme viacero kvapiek

naraz

Vo všetkých sadách okrem prvej však musíme spracovať potenciálne až $10^9$ kvapiek, preto nám môže napadnúť, že potrebujeme istým spôsobom spracovať viacero kvapiek naraz. Ako to ale spravíme?

Môžeme si všimnúť, že pri veľkom počte kvapiek sa nám často môže stať, že veľa po sebe nasledujúcich kvapiek tečie presne po tej istej ceste. Pre všetky tieto kvapky tak poznáme odpoveď už po odsimulovaní toku tej prvej z nich - všetky ostatné potom stečú k tomu istému psíkovi ako tá prvá.

Stále však potrebujeme simulovať jednotlivé zmeny toku a po každej zmene toku prejsť celý tok odznova a zistiť, pri ktorom psíkovi tento tok končí.

Vieme si ale všimnúť, že zmien toku v údolí $i$ bude za celú dobu stekania kvapiek najviac $z_i$. Inak povedané, celkový počet zmien toku vo všetkých údoliach dokopy za celú dobu stekania kvapiek bude najviac $\sum z_i$.

Priamočiare riešenie tak mierne vylepšime, aby sme naraz spracovávali všetky po sebe idúce kvapky, ktoré majú úplne identický tok.

Podobne ako v predošlom riešení začneme odsimulovaním toku aktuálnej kvapky. Teraz však pri simulovaní tohto toku súčasne v každom údolí toku určíme, koľko kvapiek doň ešte môže stiecť bez toho, aby sa zmenil smer stekania z daného údolia.

Keď potom vyberieme z týchto hodnôt tú najmenšiu, tak budeme vedieť, koľko ďalších kvapiek ešte stečie po úplne tom istom toku a pri ktorej najbližšej kvapke sa už jej smer stekania v nejakom údolí zmení.

Simulovania celého toku pre tento počet nasledujúcich kvapiek tak vieme preskočiť a všetky tieto kvapky priradiť ku psíkovi, u ktorého skončila aktuálne vyslaná kvapka.

Keď následne budeme chcieť spracovať kvapku, ktorá už má odlišný tok od týchto predošlých, tak musíme prinajhoršom prerátať celý tok odznova (keďže sa napríklad mohol zmeniť smer odtekania už niekde úplne na začiatku toku) a podobne určiť, koľko najbližších kvapiek stečie po tom istom toku. Toto však spravíme nanajvýš $\sum z_i$-krát, keďže nanajvýš toľko zmien v toku kvapiek sa celkovo uskutoční.

Keďže v tomto riešení musíme prerátať celý tok maximálne $\sum z_i$-krát, tak časová zložitosť tohto riešenia je $O(n \cdot \sum z_i)$, čo už stačí aj na druhú sadu.

Optimálne riešenie

Pre získanie plného počtu bodov potrebujeme tok kvapiek simulovať už nie po jednotlivých kvapkách alebo zmenách toku, ale po jednotlivých údoliach. Keď totiž vieme, koľko kvapiek sa celkovo niekedy dostane do nejakého údolia $i$, tak ľahko vieme tento počet rozdeliť do skupín po $k_i$ (ak počet kvapiek v danom údolí nie je deliteľný $k_i$, tak nám ostane jedna posledná skupina, v ktorej bude menej než $k_i$ kvapiek) a každú túto skupinu ďalej pošleme do príslušného ďalšieho údolia, do ktorého bude vtedy voda z tohto údolia stekať.

Vieme si všimnúť, že nám pritom vlastne ani nezáleží na tom, v akom presne čase k nám stečie ktorá kvapka, ale stačí nám vedieť tento celkový počet kvapiek, ktoré do daného údolia niekedy stečú.

Treba si však dať pozor na to, v akom poradí z jednotlivých údolí takto ďalej posielame kvapky. Ak by sme totiž poslali ďalej kvapky z nejakého údolia, do ktorého ale ešte nejaké ďalšie kvapky niekedy neskôr stečú, tak sa nám informácia o týchto kvapkách stratí a tieto kvapky sa nedostanú k jednotlivým psíkom.

Tu sa nám však hodí informácia zo zadania, že voda môže stekať len z vyššie položeného údolia do nižšie položeného údolia a že teda nemôže tiecť do cyklu. Ak by sme si teda celú túto úlohu predstavili ako orientovaný graf, kde vrcholy sú jednotlivé údolia a hrana spája vrcholy $i$ a $j$ práve vtedy, keď niekedy steká voda z údolia $i$ do údolia $j$, tak máme zaručené, že tento graf bude acyklický, teda sa jedná o tzv. DAG (z anglického directed acyclic graph).

Pre každý DAG pritom existuje tzv. topologické usporiadanie jeho vrcholov, teda také usporiadanie jeho vrcholov, pri ktorom sa pre každú hranu jej začiatočný vrchol nachádza v tomto usporiadaní skôr než jej koncový vrchol.

Ak by sme teda v grafe prislúchajúcom tokom kvapiek medzi údoliami našli topologické usporiadanie údolí a posielali by sme kvapky ďalej z jednotlivých údolí v tom poradí, v akom sa tieto údolia nachádzajú v ich topologickom usporiadaní, tak vždy v dobe rozširovania sa z nejakého údolia do ďalších údolí je počet kvapiek v tomto údolí už finálny a vieme ich teda rozposlať ďalej bez toho, aby sa nám nejaké kvapky stratili.

Ostáva nám tak už iba nájsť topologické usporiadanie jednotlivých údolí. To vieme spraviť tak, že pre každé údolie si budeme pamätať, koľko ešte nespracovaných hrán doň smeruje. Pre vrchol, ktorý ako ďalší pridáme do topologického usporiadania, potom musí platiť, že všetky hrany smerujúce doň sme už spracovali a že keď sa k nemu teda v tomto momente dostaneme, tak počet kvapiek v ňom už bude finálny.

Vieme si tak udržiavať pole vrcholov, do ktorých už nesmerujú žiadne nespracované hrany, v každom kroku z tohto poľa nejaký vrchol vybrať a všetky hrany vychádzajúce z neho označiť za spracované tým, že vrcholom, do ktorých smerujú, počet nespracovaných hrán smerujúcich do nich znížime o $1$.

Následne nám už pri tomto aktualizovaní počtu vchádzajúcich nespracovaných hrán stačí len kontrolovať, či pri nejakom vrchole tento počet neklesol na nulu a či ho teda nevieme pridať do poľa vrcholov, ktoré vieme priamo pridať do topologického usporiadania ako ďalšie.

Topologické usporiadanie údolí takto nájdeme v čase lineárnom od počtu vrcholov aj hrán v našom grafe, teda v čase $O(n + \sum z_i)$. Následne údoliu s prameňom priradíme $t$ kvapiek a všetkým ostatným údoliam zatiaľ $0$ kvapiek.

Tieto údolia budeme potom prechádzať v poradí nájdeného topologického usporiadania a počty kvapiek, ktoré do nich stiekli, budeme ďalej rozdeľovať údoliam, do ktorých tieto kvapky stečú. Takto si pre každé údolie so psíkom vo výsledku zaznačíme, koľko kvapiek doň celkovo stieklo a tieto hodnoty vypíšeme.

Aj v tejto časti riešenia s rozdeľovaním kvapiek do ďalších údolí spracujeme každý vrchol a každú hranu len raz, teda aj toto prebehne v čase lineárnom od počtu vrcholov a hrán. Celková časová zložitosť tohto riešenia je tak $O(n + \sum z_i)$.

n, t = map(int, input().split())
K = []
postupnosti = [[] for _ in range(n)]
hrany_do = [0 for _ in range(n)]

for i in range(n):
    A = list(map(int, input().split()))
    K.append(A[0])

    if A[0] != 0:
        for j in range(1, len(A)):
            postupnosti[i].append(A[j])
            hrany_do[A[j]] += 1

toposort = []
for i in range(n):
    if hrany_do[i] == 0:
        toposort.append(i)

kvapky = [0 for _ in range(n)]
kvapky[0] = t

while len(toposort) > 0:
    v = toposort.pop()

    kvapiek = kvapky[v]
    for u in postupnosti[v]:
        hrany_do[u] -= 1
        if hrany_do[u] == 0:
            toposort.append(u)
        if kvapiek > K[v]:
            kvapky[u] += K[v]
            kvapiek -= K[v]
        else:
            kvapky[u] += kvapiek
            kvapiek = 0

for i in range(n):
    if K[i] == 0:
        print(kvapky[i])
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    int n,t; cin >> n >> t;

    vector<vector<int>> graf (n);
    vector<int> menia_sa (n);
    vector<int> inc(n,0);
    vector<int> prislo_kvapiek(n,0);
    prislo_kvapiek[0] = t;

    for (int i = 0; i < n; i++)
    {
        cin >> menia_sa[i];
        if (menia_sa[i])
        {
            for (int g = 0; g< (t-1)/menia_sa[i] + 1; g++)
            {
                int to; cin >> to;
                graf[i].push_back(to);
                inc[to] ++;
            }
        }
    }

    vector<int> stack_udoli;
    for (int i = 0; i < n; i++)
    {
        if (inc[i] == 0)
        {
            stack_udoli.push_back(i);
        }

    }

    int ind = 0;

    while (stack_udoli.size() > ind)
    {
        int vtx = stack_udoli[ind];
        int tecie = menia_sa[vtx];
        int kvap = prislo_kvapiek[vtx];
        if (!tecie)
        {
            ind++;
        } else
        {
            for (int i = 0; i < graf[vtx].size(); i++)
            {
                int kam = graf[vtx][i];
                prislo_kvapiek[kam] += min(kvap,tecie);
                kvap -= min(kvap,tecie);
                inc[kam] -= 1;
                if (!inc[kam])
                {
                    stack_udoli.push_back(kam);
                }
            }
            ind ++; 
        }
    }

    for (int i = 0; i < n; i++)
    {
        if (menia_sa[i] == 0)
        {
            cout << prislo_kvapiek[i] << endl;
        }

    }
}
Pre odovzdávanie sa musíš prihlásiť.
Trojsten

Korešpondenčný seminár z programovania zastrešuje občianske združenie Trojsten.

Kontakt
Ďalšie projekty