Najprv historické okienko: O Rímskej Ríši vieme rádovo dve veci:
Keď ste si toto všetko zapamätali, príbeh tejto úlohy vám už bude dávať zmysel:
Jedného krásneho dňa si plebejci povedali, že už toho majú dosť, a dnes je ten krásny deň, keď Cézarovi ukážu svoju novú zbierku multifunkčných kuchynských nožíkov, ktoré videli v teleshoppingu. Tak išli išli, po ľubovoľnej ceste (mohli si to dovoliť, pretože poznali príslovie o tom, že všetky cesty vedú do Ríma). Nakoniec do toho Ríma prišli, ale jój, beda prebeda! Brutus zabudol, že jeho vrecká sú ušité zo syra, a zbierka nožíkov sa vyrezala von a porozsýpala po celej Rímskej Ríši. Prvý nápad, ktorý plebejci mali, bol ako ľudia, ktorí majú rozum, nožíky proste pozbierať. Lenže tie sa zaborili do zeme čepeľou nahor, a teraz to vyzerá, že by bolelo vyberať ich von, tak sa to nikomu nechce robiť. Našťastie existuje ešte jedna prastará pranostika: Keď nejde hora k Mohamedovi, príde Mohamed k hore. Keď vykonáme substitúciu do tohto tvrdenia, zistíme, že Cézar môže prísť ku zbierke nožíkov. Tak plebejci vymysleli perfektný plán: založia s Cézarom kapelu, stanú sa slávnimi, a potom pôjdu na turné.
Teraz muzikologické okienko: O turné vieme rádovo tri veci:
Už je vám dúfam všetko jasné. Brutus navrhne Cézarovi turné, ten naň pôjde, a bum! Aspoň raz pôjde po ceste, v ktorej je nožík, a pokochá sa ním. Má to ale byť super tajné epické prekvapenie, a preto chce Brutus dať Cézarovi ilúziu výberu, aby si myslel, že má slobodnú vôľu. Preto potrebuje od vás vedieť jediné - počet všetkých rôznych turné danej dĺžky $k$, ktoré prechádzajú po aspoň jednej ceste s nožíkom. Zmodulovaný Cézarovym obľúbeným číslom, samozrejme.
Poznáte $n$ miest v Rímskej Ríši, a $n-1$ ciest, ktoré spájajú niektoré mestá tak, aby boli všetky nejakou postupnosťou ciest spojené. O každej ceste okrem toho aj viete, či ide o hranu noža (teda či je na tejto ceste nožík).
Turné dĺžky $k$ je postupnosť $k$ miest. Mestá v postupnosti nemusia byť priamo spojené cestou. Dokonca môže to isté mesto byť v postupnosti viackrát za sebou.
Cézar si vždy najskôr vyberie turné dĺžky $k$ a následne sa medzi mestami vo zvolenom turné postupne presúva, vždy najkratšou možnou cestou.
Vašou úlohou je zistiť, koľko najviac rôznych turné dĺžky $k$ môžeme Cézarovi prezentovať, aby ho každé jedno z nich viedlo v nejakom bode po hrane noža.
Toto číslo môže byť veľmi veľké, preto odpoveď zmodulujte konštantou $10^9 + 7$.
Na prvom riadku sa nachádzajú čísla $n$ – počet miest a $k$ – dĺžka turné.
Nasleduje $n - 1$ riadkov. V každom z nich sa nachádzajú čísla $a$, $b$, $x$. Tieto čísla hovoria, že existuje cesta medzi mestami $a$ a $b$ a nožík sa na nej nachádza, ak $x = 1$, a nenachádza, ak $x = 0$.
Na výstup vypíšte jedno číslo – počet rôznych turné dĺžky $k$, počas ktorých Cézar prejde po aspoň jednej ceste s nožíkom, modulo $10^9 + 7$.
Sú 4 sady vstupov. Platia v nich nasledovné obmedzenia:
| Sada | 1 | 2–3 | 4 |
|---|---|---|---|
| $2 \leq n \leq$ | $10$ | $100\,000$ | $100\,000$ |
| $2 \leq k \leq$ | $5$ | $10$ | $1\,000\,000$ |
5 4
1 3 0
2 3 0
3 4 1
4 5 0
528
Vhodnými turné sú tu napríklad [1, 5, 3, 2], alebo [3, 3, 5, 3]. Nevhodným je napríklad [1, 3, 2, 1].
3 3
1 2 0
3 2 0
0
Žiadne turné nevyužíva cestu s nožíkom, pretože v Rímskej Ríši žiadna nie je. Brutus v skutočnosti asi zabudol zbierku celú doma.
Na vstupe sme mali strom. Našou úlohou bolo spočítať, koľko existuje postupností vrcholov dĺžky $k$ takých, že počas prechádzania postupnosti prejdeme po aspoň jednej označenej hrane. Takéto postupnosti budeme nazývať dobrými.
Riešenie hrubou silou je priamočiare, nie však jednoduché. Nezľaknite sa preto, ak ho budete čítať. Vzorové riešenie sa ukáže ako omnoho jednoduchšie.
Môžeme si postupne vygenerovať všetky možné postupnosti dĺžky $k$ a pre každú z nich overiť, či je dobrá, alebo nie.
Na implementáciu takéhoto riešenia vieme použiť napríklad rekurzívny backtracking. Keď máme postupnosť vygenerovanú, musíme overiť, či je dobrá. To vieme napríklad tak, že pre každý vrchol v postupnosti pustíme BFS, či DFS. V tomto prehľadávaní overíme, či na najkratšej ceste z aktuálneho vrchola postupnosti do nasledujúceho prejdeme po označenej hrane.
Inou, o niečo krajšou možnosťou by bolo predpočítať si pre každú dvojicu vrcholov, či sa na najkratšej ceste medzi nimi nachádza označená hrana. Potom by sme vedeli už počas rekurzie zisťovať, či sme cez nejakú označenú hranu prešli, alebo nie. Vyhli by sme sa tak overovaniu na konci rekurzie. Toto predpočítanie vieme spraviť drobnou úpravou algoritmu Floyda a Warshalla.
Možných postupností $n$ vrcholov dĺžky $k$ je $n^k$. Každú z nich samostane vygenerujeme a overíme, či je dobrá. V závislosti od implementácie nám to bude trvať niečo medzi $O(kn^{k + 1})$ a $O(n^{k + 3})$. Pamäť bude tiež v závislosti od implementácie $O(n)$ až $O(n^2)$. Závisí to od toho, či si pamätáme iba strom, alebo si niečo predpočítame pre každú dvojicu vrcholov.
Stačí použiť bežný trik. Ak nevieme jednoducho spočítať počet dobrých ciest, možno vieme ľahko zistiť počet zlých. Dobré potom zrátame tak, že od počtu všetkých odpočítame počet zlých.
Už vieme, že počet všetkých postupností je $n^k$.
Ako vyzerá zlá postupnosť? Neprejdeme počas nej po žiadnej označenej hrane. Skúsme teda zo stromu odstrániť všetky označené hrany. Týmto sa nám strom rozpadne na niekoľko komponentov. Každá zlá postupnosť potom musí byť celá v jednom komponente. Totiž, keby zahŕňala zlá postupnosť vrcholy z rôznych komponentov, museli by sme niekedy prejsť z jedného komponentu do druhého. Jedinou možnosťou, ako by sme to vedeli, je označená hrana. To je ale spor s predpokladom, že táto postupnosť je zlá.
Keď teda chceme zistiť počet zlých postupností, stačí nám ich vypočítať pre každý komponent zvlášť. Hodnoty pre jednotlivé komponenty potom iba sčítame.
Koľko zlých postupností dĺžky $k$ je v komponente veľkosti $x$? To už predsa vieme, je to niečo podobné ako počet postupností v celom strome, akurát teraz nemá veľkosť $n$, ale $x$. Bude to teda $x^k$.
Nech sa nám odobratím označených hrán strom rozpadol na $c$ komponentov. Ich veľkosti sú $x_1, \ldots, x_c$. Potom počet dobrých postupností je $n^k - \sum_{i=1}^{c} x_i^k$.
Na zrátanie veľkostí komponentov vieme použiť jedno DFS.
Takéto riešenie by malo získať $6$ bodov. Na plný počet musíme naviac použiť umocňovanie v logaritmickom čase. O tomto jednoduchom triku si môžete viac prečítať tu.
DFS potrebuje $O(n)$ času. Následne veľkosť každého komponentu umocníme na $k$-tu. Komponentov je najviac $n$. Takéto riešenie teda bude mať časovú zložitosť $O(n \log k)$.
Pamätať si potrebujeme celý strom. Pamäťová zložitosť je preto $O(n)$.
Korešpondenčný seminár z programovania zastrešuje občianske združenie Trojsten.
Trojsten, o.z.
FMFI UK, Mlynská dolina
842 48 Bratislava
Programátorská súťaž pre základoškolákov
Materiály a úlohy na výučbu programovania
Intenzívny programátorský zážitok v lete