Zadanie

Jedného dňa si naša hviezdna celebrita Taylor Swift povedala, že je čas na dovolenku. Zaletí si svojím osobným tryskáčom na výlet do kozmu a oslávi debut svojho nového albumu.

Už pomaly vylietavala zo stratosféry, keď si zrazu všimla, že niečo nie je v poriadku. Na obrazovke palubného počítača svieti veľkým červeným písmom:

Muhaha ahoj Taylor,

konečne sme ťa dostali. Tvoj počítač je v našich rukách a s ním aj všetky
tvoje nevydané pesničky.

Tvoj disk sme zašifrovali neprelomiteľnou šifrou a je len na tebe, ako budeš
ďalej pokračovať. Ako výkupné požadujeme len málo. Tvoj červený šál.

S pozdravom,
Tvoji najväčší fanúšikovia.

Čo bude teraz robiť? Má síce veľký tým kyberšpecialistov, ktorý by túto šifru určite rozlúskli, no komunikácia so Zemou z takejto diaľky nie je úplne najjednoduchšia.

Nezostáva jej teda nič iné, len obrátiť sa na vás. Pomôžte jej zistiť, kde na disku sa mohol nachádzať názov jej nového albumu, nech nemusí svojim kyberšpecialistom posielať celý obsah tohto disku.

Úloha

Dostanete \(2\) reťazce \(T\) a \(P\), reprezentujúce obsah zašifrovaného disku a názov Taylorinho nového albumu. Disk je zašifrovaný substitučnou šifrou, teda každé z písmeniek abecedy sa zmenilo na nejaké iné tak, že sa žiadne \(2\) nezmenili na rovnaké.

Formálne reťazec \(A\) sa mohol zašifrovať na reťazec \(B\) vtedy, ak majú rovnakú dĺžku a existuje permutácia písmeniek, ktorá z reťazca \(A\) spraví reťazec \(B\). Ak napríklad \(A = "abdda"\) a \(B = "ceffc"\), tak vyhovuje permutácia \(a \to c\), \(b \to e\) a \(d \to f\). Ak ale \(A = "abb"\) a \(B = "ccc"\), tak neexistuje vyhovujúca permutácia, lebo sa dve písmená nemôžu zmeniť na rovnaké písmeno.

Taylor by teraz zaujímalo, na koľko súvislých podreťazcov reťazca \(T\) sa mohol zašifrovať reťazec \(P\).

Formát vstupu

Na prvom riadku vstupu je reťazec \(P\) (\(|P| \leq 2 \cdot 10^5\)), meno nevydaného albumu.

Na druhom riadku vstupu sa nachádza reťazec \(T\) (\(|T| \leq 4 \cdot 10^5\)), obsah zašifrovaného disku.

Oba reťazce obsahujú len písmená malej anglickej abecedy.

V jednotlivých sadách platia navyše takéto obmedzenia:

Sada 1 2 3,4
\(1 \leq |T| \leq\) \(2\,000\) \(4 \cdot 10^5\) \(4 \cdot 10^5\)
\(1 \leq |P| \leq\) \(1\,000\) \(20\,000\) \(2 \cdot 10^5\)

Formát výstupu

Vypíšte jeden riadok a v ňom jedno celé číslo, počet súvislých podreťazcov \(T\), ktoré sa mohli zašifrovať na reťazec \(P\).

Príklad

Input:

ab
abcdd

Output:

3

Možné podreťazce sa nachádzajú na indexoch 0, 1 a 2. Pri prvom výskyte stačí použiť identickú permutáciu na zašifrovanie. Pri druhom môžeme použiť permutáciu b -> a, c -> b. A pri tretej permutáciu c -> a, d -> b

Bruteforce

Prvé pozorovanie, ktoré môžeme spraviť je, že všetkých permutácií, ktorými mohol byť disk zašifrovaný, je strašne veľa. Určite teda nemôžeme vyskúšať všetky permutácie. Čo ale môžeme spraviť je, pre každú pozíciu v \(T\) vyskúšať, či sa na nej môže začínať \(P\).

To môžeme spraviť napríklad tak, že postupne prechádzame písmenkami a konštruujeme permutáciu, pre ktorú sa bude \(P\) nachádzať v \(T\) (počínajúc daným indexom). Časová zložitosť tohto algoritmu bude \(O(|T| |P|)\), čo stačí na prejdenie prvej sady.

Kanonický tvar

Jedno slovo sa nám môže zašifrovať na veľa iných. Preto by sme chceli spomedzi všetkých týchto slov nejaké vybrať a prehlásiť ho za kanonický tvar daného slova. Môžeme si všimnúť, že ak majú dve slová \(A\) a \(B\) rovnaký kanonický tvar \(K\), tak sa jedno mohlo zašifrovať na druhé.

Zo slova \(A\) vieme dostať slovo \(K\) nejakou permutáciou. Rovnako aj zo slova \(B\) vieme dostať \(K\) nejakou permutáciou. Inverz tejto permutácie je tiež permutácia, preto aj z \(K\) vieme nejakou permutáciou dostať \(B\). Keď tieto dve permutácie spolu zložíme, dostaneme ďalšiu permutáciu, ktorá z \(A\) spraví \(B\).

Ak naopak slová \(A\) a \(B\) nemajú rovnaký kanonický tvar, tak sa nemôže jedno zašifrovať na druhé. To platí preto, lebo ak by sa z \(A\) dalo vytvoriť nejakou permutáciou \(B\), tak by množina všetkých slov, na ktoré sa dá \(A\) zašifrovať, bola rovnaká ako množina slov, na ktoré sa dá zašifrovať \(B\). Keďže sú ale tieto množiny rovnaké, tak by \(A\) a \(B\) museli mať rovnaký kanonický tvar, čo je spor.

Ako si vyberieme tento kanonický tvar? Dobre napríklad funguje lexikograficky najmenší reťazec. Ten vieme dostať tak, že postupne prechádzame dané slovo a pamätáme si najmenšie nepoužité písmenko. Ak natrafíme v slove na nové písmenko, tak mu v permutácií priradíme najmenšie nepoužité písmenko a zvýšime ho o \(1\).

Optimálne riešenie

V prípade, že by disk nebol nijakým spôsobom zašifrovaný, tak by táto úloha bola celkom štandardná. Stačí len nájsť vzorku v texte, čo dokážeme pomocou KMP alebo rolling hashov (Rabin-Karp). Ak by sa nám teda podarilo nejak previesť slová na ich kanonické tvary, tak by sa z úlohy stala úloha riešiteľná jedným z týchto algoritmov.

Z týchto algoritmov si vyberieme rolling hash. Previesť reťazec \(P\) do kanonického tvaru a spočítať jeho hash je triviálne. Zaujímavejšie je, ako počítať postupne hashe úsekov reťazca \(T\).

Budeme klasicky počítať hodnoty polynómu \(T_i \cdot p^{k-1} + T_{i+1} \cdot p^{k-2} \cdots + T_{i+k-1} \cdot p^{0} \mod q\) pre každý úsek dĺžky \(|P|\). Keďže ale chceme počítať hodnotu hashu pre kanonickú reprezentáciu substringu \(T[i:i+k]\), tak si ešte potrebujeme udržiavať permutáciu, pomocou ktorej z tohto substringu dostaneme jeho kanonický tvar.

Keď sa toto okno posunie doprava nastanú dve veci. Ako prvé treba odčítať prvý člen polynómu, zvýšiť všetky exponenty o \(1\) a nakoniec pripočítať člen reprezentujúci pribudnuté písmeno. Keďže sa zmenilo prvé písmeno nového reťazca, tak sa mohla aj zmeniť permutácia, ktorou z tohto reťazca dostaneme jeho kaninocký tvar.

Táto permutácia je určená pozíciami prvých výskytov jednotlivých písmen. Preto si budeme navyše udržiavať pre každé písmeno frontu s indexmi v aktuálnom okne, na ktorých sa nachádza dané písmeno. Podľa prvých prvkov z týchto front potom usporiadame permutáciu \(1\)\(26\), čím dostaneme hľadanú permutáciu.

Aby sme teraz vedeli zmeniť písmená, ktoré sú koeficientami v polynóme touto novou permutáciou, rozdelíme si tento polynóm na súčet \(26\) polynómov. Každý z týchto polynómov bude obsahovať rovnaké koeficienty (teda to budú členy prislúchajúce nejakému písmenu).

Keď sa hodnota nejakého písmena zmení v permutácií z \(a\) na \(b\), tak stačí polynóm tohto písmena vynásobiť hodnotou \(a^-1 \cdot b\). To platí preto, lebo všetky koeficienty majú hodnotu \(a\), teda vynásobením touto hodnotou sa zmenia všetky zmenia na \(b\). Keď potom chceme zistiť, či nastala zhoda, stačí hodnoty týchto polynómov sčítať a porovnať s hashom reťazca \(P\).

Keďže abeceda má konštantnú veľkosť, tak substringy, ktoré hashujeme, vieme posúvať v konštantnom čase. Časová zložitosť je preto \(O(|T| + |P|)\). Pamäťová zložitosť je tiež \(O(|T| + |P|)\).

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ť.