abcd
zašifruje na dacb
,
takže f(1)=2 (neboť první písmeno a
se dostalo na druhé
místo), f(2)=4, f(3)=3 a f(4)=1. Zobrazení f je vlastně
permutací prvků {1,2,...,(2N)2}.
Dále označme fj(i) číslo pozice, na níž se dostane i-tý znak
vstupního textu po j-násobném zašifrování. V našem příkladě po
dvou zašifrováních dostáváme text bdca
, tedy například
f2(1)=4. Je zřejmé, že fj můžeme definovat
také rekurzívně:
f0(i)=i a fj+1(i)=f(fj(i)).
Je zřejmé, že najdeme-li takové j, že fj(i)=i pro každé i=1,2,..., (2N)2, po j-násobném zašifrování textu danou mřížkou vždy dostaneme původní text. Důležité je uvědomit si, že pokud naopak existuje i takové, že fj(i)\ne i, pak existuje nezašifrovaný text, který se po j-násobném zašifrování změní - stačí zvolit nějaký text, v němž jsou na pozicích i a f(i) různá písmena. Z uvedené úvahy vyplývá, že hledané číslo k bude nejmenší takové kladné číslo, že pro všechna i platí fk(i)=i. Toto číslo nazýváme řádem permutace f.
Nyní musíme zjistit, jak lze určit řád permutace. Zvolíme si nějaké i a budeme pozorovat, na která políčka se dostane i-tý znak původního textu, když ho budeme opakovaně šifrovat danou mřížkou, tj. zkoumáme posloupnost f0(i), f1(i), f2(i), f3(i), ... Jelikož všechny hodnoty v této posloupnosti jsou z rozsahu {1,2,... , (2N)2}, musí existovat čísla a, b taková, že a < b a fa(i)=fb(i). Zvolme a a b s touto vlastností tak, aby b bylo nejmenší možné.
Sporem snadno dokážeme, že a=0. V opačném případě by totiž platilo, že f(fa-1(i))=f(fb-1(i)). Současně ale fa-1(i) <> fb-1(i) (neboť b je nejmenší takové, že fa(i)=fb(i)). Zobrazení f by tedy pro dva různé vstupy (fa-1(i) a fb-1(i)) nabývalo stejné hodnoty, což nemůže nastat (dvě různá písmena se nemohou zapsat do téhož políčka).
Dokázali jsme tedy, že a=0. Co to ale znamená? i-té písmeno se při opakovaném šifrování nejprve dostane na navzájem různé pozice f1(i), ..., fb-1(i) a po b opakováních se vrátí zpět na pozici i. Celý tento cyklus délky b se bude stále opakovat, tzn. písmeno i se bude nacházet na místě i po 0, b, 2b, 3b, ... opakováních šifrování.
Vezměme nyní některou jinou pozici z cyklu: l=fj(i). Vidíme, že fu(l)=fu(fj(i))=fu+j(i), tedy l bude procházet přes stejné pozice jako i, jenom v jiném pořadí. Lehce ověříme, že l-tý znak se dostane na svoje původní místo právě tehdy, když je počet opakování násobkem čísla b. Množina pozic {1,..., (2N)2} se nám tedy rozpadá na disjunktní cykly, přičemž prvky nacházející se v jednom cyklu si při každém opakování šifrování navzájem cyklicky vyměňují pozice.
Představme si, že pro každé i známe hodnotu bi, která určuje, po kolika opakováních šifrování se i znovu dostane na svoje místo. Platí, že fj(i)=i právě tehdy, když j je násobkem čísla bi. My hledáme nejmenší k takové, že pro všechna i bude platit fk(i)=i. Hledané číslo k bude proto nejmenším společným násobkem čísel bi pro i prvkem {1,..., (2N)2}. Jelikož pro prvky, které patří do jednoho cyklu, je hodnota bi stejná, stačí do nejmenšího společného násobku zahrnout po jednom bi z každého cyklu.
Algoritmus bude pracovat následovně: Nejprve simulací šifrovacího procesu zjistíme hodnoty f(i) (v programu jsou uloženy v poli |perm|). Toto je možné provést v čase O(N2). Potom pro jedno i z každého cyklu určíme hodnotu bi (v programu proměnná |delka|) a z těchto hodnot přímo spočítáme nejmenší společný násobek. Nejmenší společný násobek dvou čísel x,y zjišťujeme vydělením jejich součinu jejich největším společným dělitelem spočteným pomocí Eukleidova algoritmu v čase O(min(x,y)). Jelikož y postupně prochází přes všechny délky cyklů bi a jejich součet je O(N2) (každý prvek patří do jednoho cyklu), je celkový čas strávený výpočty největších společných dělitelů taktéž O(N2). Zbývá popsat, jak určíme hodnoty bi pro jednotlivá i: jednoduše budeme procházet po prvcích cyklu i, f(i), f2(i), ... , dokud nenajdeme fj(i)=i. V tuto chvíli známe bi=j. Přitom si budeme v poli zaznamenávat, přes které prvky jsme v cyklu prošli. Hodnotu bi počítáme jen pro taková i, pro která ještě nemáme zaznamenáno, že již byla v některém cyklu navštívena. Tím si zajistíme, že při výpočtu všech potřebných hodnot bi procházíme každým cyklem právě jednou - pokud později přijdeme k jinému prvku již zpracovaného cyklu, neprocházíme tímto cyklem znovu. Časová složitost této části programu je omezena součtem délek cyklů na O(N2). Celý program tedy má dohromady časovou i paměťovou složitost O(N2).
Přiřadíme-li každé dvojici znaků a,b váhu w(a,b) tak, že všechny dvojice souhláska-samohláska a samohláska-souhláska dostanou váhu 1, dvojice souhláska-souhláska a samohláska-samohláska váhu -3 a ostatní dvojice váhu 0, pak jsou potenciálně smysluplné právě ty klíče, které dají zprávy, v nichž je celková váha všech dvojic sousedních znaků nezáporná. Jinými slovy, pro dekódovanou zprávu ai musí vyjít W = w(a1,a2)+...+w(am/2-1,am/2) >= 0.
Uvažme nejprve jednodušší případ, kdy n je sudé.
Tehdy každý řádek kryptogramu
po zpětném přeházení podle klíče odpovídá celému počtu znaků, takže vyjde:
W= SUMA1 <= j <= d, 1<= i <= n-3, i liché
( d(tk(i)j,tk(i+1)j)+
d(tk(i+2)j,tk(i+3)j) )+
SUMA1 <= j <= d-1
( d(tk(n-1)j,tk(n)j)+
d(tk(1)j+1,tk(2)j+1) )
My si ovšem můžeme předpočítat hodnoty vah za všechny
dvojice znaků vzniklé z každé
čtveřice sloupců a analogické hodnoty pro "posunuté"
dvojice na rozhraní řádků:
Cijkl=SUMA1 <= z <= d
w(d(tiz,tjz),d(tkz,tlz))
C*ijkl=SUMA1 <= z <= d-1
w(d(tiz,tjz),d(tk,z+1,tl,z+1))
(to zvládneme v čase O(n4d)) a
z těchto hodnot pak snadno v čase O(n) získáme W:
W=C*k(n-1),k(n),k(1),k(2)+
SUMA1 <= i <= n-3, i liché
Ck(i),k(i+1),k(i+2),k(i+3)
Pro liché n se navíc musíme umět vyrovnat s tím, že rozhraní mezi lichými
a sudými řádky kryptogramu půlí nikoliv dvojici, nýbrž dokonce znak, zatímco
zbylá rozhraní jsou v půli dvojice. Proto musíme zpracovávat zvlášť sudé
a liché řádky a dva různé typy přechodů přes okraj. Předpočítáme si proto
obecnější hodnoty Cpqijkl odpovídající
celkové váze dvojic
vzniklých ze čtveřice sloupců i,j,k,l na sudých (p=0) resp. lichých (p=1)
řádcích s posunutím přes okraj daným počtem symbolů q přesahujících do dalšího
řádku (q=0 pro vnitřní dvojice, q=2 odpovídá C* pro sudé n, q=1
a q=3 jsou přechody s půlenými znaky pro liché n).
Tyto hodnoty spočteme opět v čase O(n4d) dle vztahu:
Cpqijkl=
SUMA 1 <= z <= d, z má stejnou paritu jako p
w(d(T0qizT1qjz,
d(T2qkzT3qlz),
kde Taqij=tij pro a < 4 - q,
=ti,j+1 pro a >= 4 - q
Z těchto hodnot v čase O(n) spočteme W pro libovolný klíč K, a sice jak pro
sudé, tak pro liché n.
Výsledná časová složitost algoritmu tedy je O(n4d+kn)= O(n3m+kn), paměťová O(n4+m).
Program pracuje přesně podle tohoto algoritmu, pouze si do tabulky
doplní "poposlední" řádek zaplněný neplatnými znaky,
aby se vyhnul ošetřování přesahů z posledního řádku.
Pro a+b=0 (tj. a=b=0) začínající hráč nemůže provést žádný tah a prohrál, tedy tvrzení platí.
Předpokládejme, že tvrzení platí pro libovolná celá nezáporná čísla a, b taková, že a+b < s, kde s je dané přirozené číslo. Nechť nyní a, b jsou libovolná celá nezáporná čísla taková, že a+b=s.
Popsaný postup realizuje následující schéma Markovova algoritmu.
Schéma používá pomocné symboly z abecedy {a,b,c,d,e,f,g}.
(1) 1:11 -> b1
(2) : -> :
(3) 1b1 -> bc
(4) c1 -> 1c
(5) bc -> dce
(6) ec -> ce
(7) e -> fa
(8) cf -> f1
(9) df -> b
(10) b1 -> g
(11) g1 -> g
(12) gc -> g
(13) ga -> 1g
(14) g -> 1
V úvodní formuli (1) se zjišťuje, zda se nejedná o dělení nulou.
Pokud ne, provede se zároveň odstranění "nadbytečných" znaků
1 z dělence i dělitele a oddělovací dvojtečka se změní na znak
b (aby se tato formule nemohla při výpočtu uplatnit vícekrát).
Pokud vstupní data představovala dělení nulou, formule (1) se
neuplatní a výpočet přejde na formuli (2), která zajistí
zacyklení výpočtu pro takto nepřípustná data.
Formule (3) a (4) zajišťují jedno odečtení dělitele od dělence. Pomocí (3) se postupně z dělence odstraňují jednotlivé znaky 1 a za každý z nich je jeden znak 1 z dělitele dočasně přeznačen na symbol c (jako příznak, že už byl použit). Souběžně s tím je pomocí (4) zajištěno přesouvání 1 před c v rámci dělitele. Postupně prováděné odčítání skončí buď tím, že dojdou jedničky v děliteli nebo tím, že dojdou jedničky v dělenci (případně v obou zároveň).
Jestliže se podařilo odečíst od dělence celého dělitele, uplatní se formule (5) a následně se zrealizuje celý blok výpočtu pomocí (5)-(9). Ten slouží k připsání jedničky do výsledného podílu. Podíl je vytvářen ve zpracovávaném slově na konci, ve slově bezprostředně následuje za dělitelem. Aby se snadno odlišil, je zatím vytvářen pomocí symbolů a. Ve formuli (5) je oddělovací znak b dočasně změněn na d (aby se tato formule neuplatnila opakovaně) a zároveň je "vyslán" symbol e, aby pomocí (6) přešel na konec dělitele. Tam formule (7) připíše do výsledku jeden symbol a, zároveň přitom změní e na f. Symbol f postupuje pomocí (8) zpět doleva a cestou přeznačuje znaky c v děliteli opět na 1, aby byl dělitel připraven na další kolo odčítání. Po průchodu celým dělitelem je f zrušen a přitom se oddělovač d změní zpátky na b. Slovo je tak připraveno k zahájení dalšího odčítání pomocí (3).
Pokud jsme při odčítání neodečetli celého dělitele a v dělenci již "došly" jedničky, proces odčítání představovaný formulemi (3)-(4) se zastaví a (5) se neuplatní. Výpočet tedy přejde na "ukončovací" blok formulí (10)-(14). V něm je nejprve oddělovací symbol b, který nyní stojí na začátku slova, změněn na g. Ten pak postupně smaže celého dělitele, který může být v dané chvíli tvořen zčásti symboly 1 a zčásti symboly c (11)-(12). Dále pomocí formule (13) přeměníme znaky a ve výsledném podílu na 1 a nakonec pomocí (14) ještě jednu poslední jedničku do podílu připíšeme a g zrušíme.
Časová složitost: Uvažujme podíl m:n. Bloky Markovova algoritmu (1)-(2) a (10)-(14) se provádějí pouze jednou. Formule (3) se vykoná celkem m-krát, pro každý její výpočet se (4) provede (n-1)-krát. Formule (5) se provádí (m/n)-krát, po každém jejím provedení následuje zhruba 2n kroků (6)-(9). Časově nejnáročnější je tedy opakovaně provádění (4), které určuje výslednou časovou složitost navrženého algoritmu O(mn).