... v zložení MišoF, Janka, Danka, Monika a YoYo
Nasledujúce riadky nesú nejaké útržky informácií o tom, ako sme čo riešili, ak to stálo za zmienku.
Vyriešené v podstate autorsky, až na to, že inverznú funkciu k t(l,r) sa robí binárnym vyhľadávaním, ktoré je blbuvzdornejšie ako sa zamýšľať nad odmocninami. Môj kód prekladajúci číslo na program je o niečo stručnejší od autorského.
from sys import stdin program = int(stdin.read()) def split(x): # prevedie x=t(l,r) na l,r: prvy krok je binarne vyhladanie l+r, druhy dopocitanie l,r lo, hi = 0, x+1 while hi-lo > 1: med = (lo+hi)//2 if med*(med+1)//2 <= x: lo = med else: hi = med l = x - lo*(lo+1)//2 return l, lo-l def getpow(x,p): ans = 0 while x%p==0: ans, x = ans+1, x//p return ans def parsuj(program): if program%2==0: l,r = split(program//2) if l==0: cmds = [] while r: r,c = split(r) cmds.append(c) for c in cmds[::-1]: parsuj(c) else: print "while X[%d]!=X[%d] do begin" % (getpow(l,2),getpow(l,3)) parsuj(r) print "end;" else: p3, p5, p7 = getpow(program,3), getpow(program,5), getpow(program,7) if p3>0 and p7>0: print "X[%d] := X[%d] * X[%d];" % (p3,p3,p7); if p5>0 and p7>0: print "if X[%d]=0 then X[%d]:=0 else X[%d]:=X[%d]-1;" % (p7,p5,p5,p7) if p3>0 and p5>0: print "X[%d] := X[%d] + %d;" % (p5,p5,p3) parsuj(program)
Hrubá sila, teda obyčajná rekurzia, po každom vnorení surovo skontrolujeme celý hrací plán (just because we can) či náhodou nemáme nejaký viditeľný spor.
hodnoty = [ [0]*6 for r in range(6) ] hodnoty[0][5] = 5 ; hodnoty[1][4] = 4 ; hodnoty[4][1] = 5 ; hodnoty[5][0] = 2 oblasti = [] oblasti.append([ (0,0), (0,1), (0,2), (1,2), (1,3), (1,4) ]) oblasti.append([ (0,3), (0,4), (0,5), (1,5), (2,5), (3,5) ]) oblasti.append([ (1,0), (1,1), (2,1), (3,1), (4,1), (4,2) ]) oblasti.append([ (2,0), (3,0), (4,0), (5,0), (5,1), (5,2) ]) oblasti.append([ (2,2), (2,3), (2,4), (3,2), (3,3), (4,3) ]) oblasti.append([ (3,4), (4,4), (4,5), (5,3), (5,4), (5,5) ]) def check_area(values): for i in range(1,7): if values.count(i) > 1: return False return True def get_row(x): return [ hodnoty[x][i] for i in range(6) ] def get_col(x): return [ hodnoty[i][x] for i in range(6) ] def get_box(x): return [ hodnoty[ oblasti[x][i][0] ][ oblasti[x][i][1] ] for i in range(6) ] def check_everything(): for i in range(6): if not check_area( get_row(i) ): return False if not check_area( get_col(i) ): return False if not check_area( get_box(i) ): return False return True def solve(r,c): if not check_everything(): return 0 if r==6: return 1 if c==5: nr,nc = r+1,0 else: nr,nc = r,c+1 if hodnoty[r][c]>0: return solve(nr,nc) answer = 0 for v in range(1,7): hodnoty[r][c]=v ; answer += solve(nr,nc) hodnoty[r][c] = 0 return answer print solve(0,0)
Dominá najskôr skúšala Janka ručne, no keď ju neposlúchali, napísal na ne YoYo logický program. (Ten momentálne nemám.)
Tu sme skončili asi ako všetci: Mali sme správne zoradených všetkých 6 ukážok do poradia, vedeli sme že záleží na dĺžkach ukážok, aj že treba aj pozerať, aj že treba googliť. Neprišiel krok na YouTube. Časť problému bola asi v tom, že veta "tvé jméno je pojem" bola zjavne adresovaná postave (ktorej meno sme vedeli a bolo jasné, že googliť ho nemá zmysel).
Na jedno kolo prekladu bohate stačil sed:
sed -e 's/.-.-.-\//A/g' -e 's/-....-\//B/g' -e 's/-..-.\//C/g' -e 's/A/./g' -e 's/B/-/g' -e 's/C/\//g'
Monika napísala rekurziu s memoizáciou, z tej je jasné, že ak nie sú všetky písmená rovnaké, odpoveď by mala byť najviac 2. Intuícia potom jasne hovorí, že ak sa to dá zraziť na 1, dá sa to mrte veľa spôsobmi, takže stačí pár náhodne odskúšať. Ak nejaký sadne, sme si istí, že je to 1, ak nie, tak je to asi 2. Skúsime odovzdať, a ak nebude dobre, doladíme, ktorá 2 má byť 1. Dolaďovať samozrejme nebolo treba.
program z prvého kroku riešenia:
#include <iostream> #include <map> using namespace std; map<string, int> M; char treti(char a, char b) { if (a > b) swap(a,b); if (a == 'L' && b == 'O') return 'S'; if (a == 'L' && b == 'S') return 'O'; if (a == 'O' && b == 'S') return 'L'; return '*'; } int komprimuj(string s) { if (M.find(s) != M.end()) return M[s]; M[s] = s.size(); for (unsigned i=0; i+1<s.size(); ++i) { char c = treti(s[i],s[i+1]); if (c == '*') continue; const string ns = s.substr(0,i) + string(1,c) + s.substr(i+2); M[s] = min(M[s], komprimuj(ns)); } return M[s]; } int main(void) { string s; cin >> s; cout << s << " " << komprimuj(s) << endl; }
a program z druhého kroku riešenia:
from sys import stdin from random import choice def nahodne_vyzer(S): n = len(S) if S=='L'*n or S=='O'*n or S=='S'*n: return n i = choice( [ i for i in range(n-1) if S[i] != S[i+1] ] ) for c in ['L','O','S']: if c!=S[i] and c!=S[i+1]: return nahodne_vyzer( S[:i] + c + S[i+2:] ) for line in [ x.strip() for x in stdin.readlines()]: best = len(line) for i in range(1000): best = min(best,nahodne_vyzer(line)) print best
Myšlienka nášho riešenia bola OK, len Mišof mal bug v dekódovaní a našiel si ho až po konci.
Komu by sa chcelo čítať tie strany textu o tom, ako funguje to divné hashovanie? Lepšie je ich preskočiť a vyriešiť to celé bez toho.
Ako sa dešifruje? Použije sa 8-znakový hash, teda 2568 možností. To je veľa. Lenže pŕŕ. Pri dešifrovaní sa ráta modulo 26, takže stačí uvažovať hashe s hodnotami od 0 do 25 na každej pozícii. To už je len 268 možností, čo je omnoho krajšie číslo. Pokojne ich môžeme veľa vyskúšať. Napríklad tak, že vyskúšame prvých 6 znakov hashu (300 miliónov možností). Pomocou nich vieme dešifrovať prvých 6 znakov správy. Zahodíme mizerne znejúce. Dobre znejúce usporiadame a od najnádejnejších spracúvame – teda skúšame všetky možnosti pre siedmy a ôsmy znak hashu.
#include <algorithm> #include <iostream> #include <fstream> #include <string> #include <vector> #include <cmath> using namespace std; string CT = "vlswkfzsilbwdrkkozqcceyffjxuiztmkigvcnzrhyfdrmlpeigkysocbhoswlbuodkuyqqkpxzyhhdrvgjtks"; int tetra[32][32][32][32]; void loadTetragrams() { ifstream fin("tetra.txt"); string buf; int x; while (fin >> buf >> x) tetra[ buf[0]-'a' ][ buf[1]-'a' ][ buf[2]-'a' ][ buf[3]-'a' ] += x; } bool good(const string &S) { return S.find(string("hashovaci")) != string::npos; } string decode(const vector<int> &hash, int letters) { string out; for (int i=0; i<min(letters,(int)CT.size()); ++i) out += 'a' + ( 2600 + (CT[i]-'a') - hash[i%8] - (i?CT[i-1]-'a':0) ) % 26; return out; } double score(const vector<int> &hash, int letters) { double res = 0; string dec = decode(hash,letters); for (int i=0; i<letters-3; ++i) res += log( tetra[ dec[i]-'a' ][ dec[i+1]-'a' ][ dec[i+2]-'a' ][ dec[i+3]-'a' ] + 1. ); return res; } int main() { loadTetragrams(); vector<int> hash(8,0); vector< pair<double, vector<int> > > Z; for (hash[0]=0; hash[0]<26; ++hash[0]) for (hash[1]=0; hash[1]<26; ++hash[1]) for (hash[2]=0; hash[2]<26; ++hash[2]) for (hash[3]=0; hash[3]<26; ++hash[3]) { if (score(hash,4) < 1) continue; // zahodime uplne blbe prve stvorice znakov for (hash[4]=0; hash[4]<26; ++hash[4]) for (hash[5]=0; hash[5]<26; ++hash[5]) { // zoberieme nadejne vyzerajuce prve sestice znakov double s = score(hash,6); if (s>20) Z.push_back( make_pair(s,hash) ); } } sort(Z.begin(),Z.end()); reverse(Z.begin(),Z.end()); for (auto hu : Z) { hash = hu.second; for (hash[6]=0; hash[6]<26; ++hash[6]) for (hash[7]=0; hash[7]<26; ++hash[7]) { string d = decode(hash,CT.size()); if (good(d)) { cout << d << endl; return 0; } } } }
Je jasné, že ide o šachové figúrky. Zadanie je však dosť nejednoznačné (kedy sa kto hýbe? čo presne je pohyb bližšie? atď.) tak to v princípe viedlo k tomu, že YoYo a Danka hľadali nové a nové jednoznačne riešiteľné interpretácie zadania až kým pre jednu z nich (myslím, že štvrtú v poradí) odpoveď nebola uznaná.
Po nejakej tej snahe nájsť steganografickú správu priamo v obrázku nakoniec YoYo objavil hlavičku ZIPu a odtiaľ ďalej to už bolo "smooth sailing". Načo rozmýšľať, čo to je, keď nám to file (až na úvodné html) sám povie? Priebežne počas rozbaľovania sme teda už rovno prečítali "?IFAR" a potom podľa obrázku html5 ešte doklepli to G.
$ file 01.html 02.zip 03.zip 04.zip 05.zip 01.html: JPEG image data, JFIF standard 1.01 02.zip: Audio file with ID3 version 2.3.0, contains: MPEG ADTS, layer III, v1, 128 kbps, 44.1 kHz, Monaural 03.zip: Composite Document File V2 Document, Little Endian, Os: Windows, Version 6.1, Code page: 1250, Title: \300ifra, Author: InterLoS, Keywords: interlos, sifra, los, Template: Normal.dotm, Last Saved By: Spravca, Revision Number: 8, Name of Creating Application: Microsoft Office Word, Total Editing Time: 21:00, Create Time/Date: Fri Nov 16 10:25:00 2012, Last Saved Time/Date: Thu Nov 29 12:50:00 2012, Number of Pages: 1, Number of Words: 5, Number of Characters: 34, Security: 0 04.zip: MS Windows icon resource - 5 icons, 256-colors 05.zip: EBML file, creator matroska
Aha-krok že ide o slovné druhy bol pekný. Chýba zrejme predložka. Aj aha-krok že z ostatných názvov zmizli predložky je pekný. Ale prečo preboha je odpoveď LA? U nás odovzdané až na štvrtý pokus, po "PREDLOZKA", "PREDLOZKY" (oboje sú skutočne odpoveďou na otázku zo zadania, singulár preto, že ostatné slovné druhy v zadaní sú v singulári) a tuším "LKA" z nepozornosti. Toto je podľa mňa šifra, ktorá si zaslúžila upresnítko riešenia (napr. "riešenie má menej ako 5 písmen").
Základ úspechu je využiť už existujúcu hlavičku bitmapy a len prehádzať poradie pixelov za ňou, potom si rovno môžeme pozrieť, čo to spravilo.
#include <iostream> #include <cstdio> #include <cstring> using namespace std; unsigned char buf[30000], out[30000]; int kde[160][160]; int CIEL = 42195, dobehlo = 0; long long A = 42; int hod() { int out=(A%100)+1; A=(A*A)%9876553; return out; } int main() { int z = read(0,buf,30000); memcpy(out,buf,z); while (dobehlo < 160*160) { for (int r=0; r<160; ++r) for (int c=0; c<160; ++c) if (kde[r][c] < CIEL) { kde[r][c] += hod(); if (kde[r][c] >= CIEL) { out[z-25600+160*r+c] = buf[z-25600+dobehlo]; ++dobehlo; } } } write(1,out,z); }
Takmer autorsky, postupne zvyšujeme K a kontrolujeme, či sa dá vhodne rozdeliť zvyšok retiazky.
// napisala Monika, uloha retiazka, Interlos 2012 // vstup: 10 456 789657 4654789 35468794 7258 #include <iostream> using namespace std; int main(void) { long long N; while(cin >> N) { for(long long K=1; K<100; ++K) { long long best = K; bool konec = false; for (int i=0; i<=K; ++i) { best = 2*best+1; if (best >= N) { konec = true; break; } } if (konec) { cout << K; break; } } } cout << endl; }
Najlenivejší možný algoritmus na najkratšie cesty: Bellmanov-Fordov. Stačí.
slova = set( open('slovnicek.txt','r').read().split() ) dist, odkial = {}, {} dist['TEXT'] = 0 def hrany(start): for i in range(len(start)): w = start[:i] + start[i+1:] if w in slova: yield 2,w for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': w = start[:i] + c + start[i+1:] if w in slova: yield 1,w for i in range(len(start)+1): for c in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': w = start[:i] + c + start[i:] if w in slova: yield 2,w while True: for w in slova: if not w in dist: continue for c, z in hrany(w): if (not z in dist) or (dist[w]+c<dist[z]): dist[z], odkial[z] = dist[w]+c, w if 'PISAR' in dist: kde = 'PISAR' while kde != 'TEXT': print kde, ; kde = odkial[kde] print 'TEXT'
Danka začína niečo skladať, po chvíli hlási, že na konci je "ODYL", odovzdávame krokodíla a kocku jej pod hrozbou násilia berieme (dohráš sa po skončení!). Pobavilo, keď sme z autorských riešení zistili, koľko textu sme preskočili :)
Už niekedy zo začiatku lúštenia padali humorné návrhy typu či nespravíme Fourierovu transformáciu. A až keď došlo na frekvenčnú analýzu, tak sme pochopili, že sme ju vlastne v princípe spravili. Bolože to radosti na starom bielidle :)
Skutočná šifrovacia lahôdka na koniec, mňam! Postupne sme odkrývali, čo nám to neuveriteľne minimalistické zadanie hovorí, a každý krok bol krásny, vrátane takých detailov ako že v 2D sa čítajú dvojky a v 3D potom trojky.