Zadanie

Ahojte moji bratia ktorým sa otvorili oči, ako už všetci isto vieme, mimozemšťania sa snažia získať našu planétu. Našťastie, naša verná kamrátka M.i.š.k.a - Mimoriadne Iniciatívna Šifrovateľská Kamrátka (Asi) - našla spôsob, ako sa brániť. Vymyslela, ako zašifrovať všetky naše dôležité informácie, veľmi zložitým a komplexným spôsobom, že ich mimozemšťania nikdy nerozlúštia. Ako to spravila? Funguje to tak, že ku každému písmenku vymyslela náhodné slovo a následne každé písmenko z našej správy zamenila za toto slovo. Ale potom si povedala, že toto by tí ufóni mohli veľmi ľahko dekódovať. Preto to spravila znovu. Každé písmenko z novej správy vymenila za slovo jemu prislúchajúce, rovnako ako prvý krát. Stále sa jej ale správa nezdala dosť zakódovaná, tak pre istotu tento proces zopakovala ešte \(N\)-krát. Teraz je už správa veľmi dobre zakódovaná, ale keď ju posielala našim spojencom, mimozemšťania nám ukradli jedno písmenko z tejto zakódovanej správy. Pomôžte nám zistiť, ktoré písmenko nám ukradli.

Úloha

Na vstupe dostanete ku každému písmenku z abecedy priradený string nejakej náhodnej dĺžky. Taktiež tam dostanete aj pôvodnú správu a počet \(N\) (koľko krát naši vedci vymenili každé písmenko zo správy za jemu prislúchajúci string). Na záver ešte dostanete aj číslo \(K\). Vašou úlohou je vypísať \(K\)-te písmenko z výslednej správy. Ak výsledná správa je kratšia ako \(K\) tak vypíšte “Neexistuje”. Zároveň existuje aj číslo \(D\), ktoré udáva maximálnu dĺžku stringu priradeného písmenku z abecedy.

Formát vstupu

V prvom riadku vstupu je string, ktorý vedci chcú zakódovať. V tejto úlohe používame iba malé písmenká anglickej abecedy. String bude mať dĺžku aspoň \(1\) a najviac \(10^6\). V druhom riadku sú čísla \(N\) udávajúce koľko krát sa má každé písmenko zmeniť za jemu prislúchajúci string a \(K\) udávajúce koľkaté písmenko z výsledného reťazca chceme vedieť. Následuje \(26\) riadkov kde na \(i\)-tom riadku je string priradený písmenku \(i\)-temu z abecedy (na prvom riadku ku \(a\)-čku na druhom \(b\)-čku atď). V každom riadku sa nachádza alespoň 1 znak. Jediný prípad, kedy v riadku nedostanete pismenko malej abecedy je, keď sa tam bude nachádzať #. Tento znak znamená, že za písmenko vymieňame vždy prázdny string.

V jednotlivých sadách platia nasledujúce obmedzenia (kde \(D\) reprezentuje aký najdlhší môže byť string ktorým nahrádzame jednotlivé písmenká):

Sada 1 2 3 4
\(1 \leq N \leq\) \(10\) \(25\) \(10^4\) \(10^5\)
\(1 \leq \ D \leq\) \(1\,000\) \(10^4\) \(1\,000\) \(1\,000\)
\(1 \leq K \leq\) \(1\,000\) \(10^6\) \(10^6\) \(10^18\)

Zároveň ešte pre jednotlivé sady platí že:

Sada Obmedzenie
1 D^N <= \(10^6\)
2 D*N <= \(1\,000\)
3 D*N <= \(10^5\)
4 D*N <= \(10^5\)

Formát výstupu

Vypíšte buď \(K\)-te písmeno z výslednej správy (číslujúc od \(1\)) alebo “Neexistuje” ak je výsledná správa kratšia ako \(K\).

Príklady

Input:

ab
1 4
abb
cde
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

Output:

c

z a-čka sme spravili “abb” a z b-čka “cde”, čo znamená, že z výsledného stringu “abbcde” chceme 4. písmenko, čo je c

Input:

abc
3 14
a
cdc
dd
eee
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#
#

Output:

Neexistuje

z našeho slova abc sa po prvej zámene stalo “acdcdd”, po druhej sa z neho stalo “addeeeddeeeeee” a po tretej “aeeeeeeeeeeee”, keďže e-čka menime na prázdny string. A keďže sa pýtame na pozíciu, ktorá vo výslednom stringu neexistuje, tak vypíšeme “Neexistuje”

Input:

zwx
4 66
qoyh
bfd
m
dsha
gnif
fyz
#
t
oeea
#
y
f
j
gr
ugf
jal
fei
#
jt
atvw
cj
jp
kpw
b
ax
ma

Output:

a

vývoj stringu: zwx -> makpwb -> jqoyhyjalkpwbfd -> feiugfaxtaxqoyhfyjalkpwbfdfyzdsha -> fyzgnifoeeacjfyzqoyhbatvwqoyhbfeiugfaxtfyzaxqoyhfyjalkpwbfdfyzdshafyzaxmadshajttqoyh

Bruteforce

Prvé čo nám napadne je, že však poďme si to odsimulovať a tým pádom na konci len vypíšeme \(k\)-te pismenko z našho výsledneho stringu. Toto riešenia má časovú zložitosť \(O(D^N)\) kde \(D\) je, že koľko najviac písmenok sa vie stať z jedného písmenka, a \(N\) je počet “zámien” ktoré máme spraviť. Pamäťová zložitosť je \(O(D^N)\), keďže až po takto veľký náš výsledok vie narásť.

Niečo lepšie

Ďalej si môžeme všimnúť, že my si nemusíme odsimulovávať vždy celý string, ale môžme ísť postupne po písmenkách. Čiže namiesto toho, aby sme z “abc” si vyvorili “aakfhjioufdljfskmdklmdscdk”, sa môžme pozrieť iba na prvé písmenko, a na prvé písmenko čo nám vznikne z tohto písmenka. Týmto pádom nám vznikne niečo ako strom kde každý vrchol je písmenko a z neho vedú hrany k písmenkam ktoré z neho môžu vzniknúť. Tento spôsob nám ale dovoľuje aby sme zastali v generovaní vtedy keď sa dostaneme na \(k\)-tu pozíciu a tým pádom nemusíme generovať celý výsledný string. A akú to má časovú zložitosť? Takže je nám jasné, že ak by sme sa pýtali na posledné písmenko zo stringu, tak by sme ho museli vygenerovať celý, a teda worst-case by bola \(O(D^N)\), ale keďže vieme prerušiť generovanie keď dôjdeme k nášmu \(k\)-temu prvku, tak časová zložitosť bude \(O(min(K,D^N))\), čo keď si všimneme obmedzenia by nám malo vedieť prejsť prvými troma sadami. A pamäťová zložitosť sa dá optimalizovať na \(O(K\*D)\), keďže si musíme pamätať ku každému písmenku za čo ho zamieňame a potom už len nejaký zásobník veľkosti &n& aby sme sa vedeli hýbať po našom strome.

Zaujímavá myšlienka

My ale v skutočnosti nepotrebujeme vedieť, aké boli tie písmenká pred našim \(k\)-čkom. To znamená že ak nájdeme rozumný spôsob ako povedať, že koľko písmenok vznikne z tohto nášho za \(J\) vnorení, potom vieme povedať, či sa nám ho oplatí úplne preskočiť, alebo je naše riešenie v ňom a teda ho pôjdeme generovať

Optimálne riešenie

Čiže čo sa snažíme zistiť je, že pre každé písmenko, koľko sa z neho stane po \(J\) vnoreniach. Znie povedome? Áno, vytvorili sme si podproblémy, ktoré na seba rozumne nadväzujú. Je nám totiž jasné, že keď viem pre každé písmenko koľko z neho vznikne iných po \(J\) opakovaniach, tak potom keď chcem to isté vedieť pre niektoré moje písmenko po \(J+1\) opakovaniach, tak sa mi len stačí pozrieť na aké všetky sa zmení po jednej premene a spočítať hodnoty tých, na ktoré by sa premenila, ktoré majú po \(J\) opakovaniach. A keď už všetky tieto informácie mám, tak potom viem postupne prechádzať cez úvodný string, a ak moje \(K\)-te pismenko sa vytvori z toho písmenka, na ktoré sa pozerám, tak si ho rozložím a znova sa pozriem, či sa moje \(k\)-te písmenko nachádza pod prvým alebo druhým atď. To, pod ktorým písmenkom sa nachádza moje, viem povvedať tak, že ak mám spočítané, koľko mi dajú všetky písmenká pred ním, tak ak to písmenko, na ktorom som + tie, ktoré som už prešiel, je väčšia alebo rovná ako \(K\) tak vtedy sa \(k\) vytvorí z tohto môjho písmenka, keďže je nám jasné, že suma tých pred týmto mojim je menej ako \(K\), inak by sme sa zastavili na nejakom predchádzajúcom písmenku. Toto riešenie má časovú zložitosť \(O(D _ N)\), keďže generujeme tak veľkú súčtovú tabuľku a nájdenie výsledku je taktiež najhoršie \(O(D _ N)\), keďže by sme \(N\) krát mohli prejsť cez celý string čo vytvárame z daného písmenka ktorý je maximálne \(D\). A pamäťová zložitosť je taktiež \(O(D\*N)\), keďže túto súčtovú tabuľku si musíme pamätať.

povodny_string = input()
pocet_vnoreni, kolkate_chcem = [int(i) for i in input().split()]
poctova_matica = [[0] * 26 for i in range(pocet_vnoreni)]
pismenk_matica = []
preskocene = 0
kolkate_pismenko = -1
possible = False

# vytvorenie si tabulky na co sa mi meni kazde pismeno
for i in range(26):
    pis = input()
    if pis == "#":
        pismenk_matica.append("")
    else:
        pismenk_matica.append(pis)
    # nahodenie to rovno aj do prveho riadku suctovej tabulky
    poctova_matica[0][i] = len(pismenk_matica[i])

# vytvorenie suctovej tabulky
for i in range(1, pocet_vnoreni):
    for j in range(26):
        poctova_matica[i][j] = sum(
            [poctova_matica[i - 1][ord(ch) - ord("a")] for ch in pismenk_matica[j]]
        )

# prejdenie cez prve slovo
i = -1
for j in range(len(povodny_string)):
    if (
        preskocene + poctova_matica[i][ord(povodny_string[j]) - ord("a")]
        < kolkate_chcem
        or pismenk_matica[ord(povodny_string[j]) - ord("a")] == ""
    ):
        preskocene += poctova_matica[i][ord(povodny_string[j]) - ord("a")]
    else:
        # ak sme nasli pismenko, ktore chceme tak nastavime, ze je to mozne a zapiseme si ktore pismenko to je
        possible = True
        kolkate_pismenko = ord(povodny_string[j]) - ord("a")
        break

if possible:
    # prejdenie az na poslednu vrstvu
    for i in range(pocet_vnoreni - 2, -1, -1):
        j = 0
        # dokym nenajdeme pismenko z ktoreho vznikne nase k-te pismenko
        while (
            preskocene
            + poctova_matica[i][ord(pismenk_matica[kolkate_pismenko][j]) - ord("a")]
            < kolkate_chcem
            or pismenk_matica[kolkate_pismenko] == ""
        ):
            preskocene += poctova_matica[i][
                ord(pismenk_matica[kolkate_pismenko][j]) - ord("a")
            ]
            j += 1
        # zapiseme si ktore pismenko to je
        kolkate_pismenko = ord(pismenk_matica[kolkate_pismenko][j]) - ord("a")

    # z poslednej vrstvy urcenie finalneho pismenka
    kolkate_pismenko = pismenk_matica[kolkate_pismenko][kolkate_chcem - preskocene - 1]

    print(kolkate_pismenko)

else:
    print("Neexistuje")
#include <iostream>
#include <vector>
#include <string>
using namespace std;

struct nefinalne_pismenko{
	long long kolko_este;
	char pismenko;
};

int main(){
	
	string slovo;
	cin >> slovo;
	
	long long n,k;
	cin >> n >> k;
	
	vector<string> rules(26);
	for(long long i = 0; i < 26; i++){
		cin >> rules[i];
		if(rules[i] == "#"){
			rules[i] = "";
		}
	}
	
	vector<vector<long long>> lengths(26);
	for(long long i = 0; i < 26; i++){
		lengths[i].push_back(1);
	}
	
	for(long long i = 1; i <= n; i++){
		for(long long j = 0; j < 26; j++){
			string change = rules[j];
			long long result = 0;
			for(long long k = 0; k < change.length(); k++){
				result += lengths[change[k]-'a'][i-1];
			}
			lengths[j].push_back(result);
		}
	}
	
	vector<nefinalne_pismenko> stack;
	for(long long i = slovo.length()-1; i >= 0; i--){
		nefinalne_pismenko nove;
		nove.pismenko = slovo[i];
		nove.kolko_este = n;
		stack.push_back(nove);
	}
	
	long long este_zjest = k-1;
	
	while(true){
		
		if(stack.empty()){
			cout << "Neexistuje" << endl;
			break;
		}
		
		nefinalne_pismenko dalsie = stack[stack.size()-1];
		long long velkost_dalsieho = lengths[dalsie.pismenko-'a'][dalsie.kolko_este];
		
		if(velkost_dalsieho <= este_zjest){
			stack.pop_back();
			este_zjest -= velkost_dalsieho;
		}else{
			if(dalsie.kolko_este == 0){
				cout << dalsie.pismenko << endl;
				break;
			}
			stack.pop_back();
			long long new_depth = dalsie.kolko_este-1;
			string replacing_string = rules[dalsie.pismenko-'a'];
			for(long long i = replacing_string.length()-1; i >= 0; i--){
				nefinalne_pismenko nahrada;
				nahrada.pismenko = replacing_string[i];
				nahrada.kolko_este = new_depth;
				stack.push_back(nahrada);
			}
		}
		
	}
	
}

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