Gieriger Ansatz und Spielautomaten. Analyse der Aufgaben der ML-Strecke der Programmiermeisterschaft



Wir veröffentlichen weiterhin Analysen der Aufgaben, die bei der letzten Meisterschaft vorgeschlagen wurden. Als nĂ€chstes folgen Aufgaben aus der Qualifikationsrunde fĂŒr Spezialisten fĂŒr maschinelles Lernen. Dies ist der dritte von vier Tracks (Backend, Frontend, ML, Analytics). Die Teilnehmer mussten ein Modell fĂŒr die Korrektur von Tippfehlern in Texten erstellen, eine Strategie fĂŒr das Spielen an Spielautomaten vorschlagen, ein System von Empfehlungen fĂŒr Inhalte in Erinnerung rufen und mehrere weitere Programme erstellen.

A. Tippfehler


Zustand

Alle Sprachenpython2.7 + numpypython3.5 + numpy
Zeitlimit1 s5 s5 s
Speicherlimit64 MB256 MB256 MB
Geben Sie einStandardeingabe oder input.txt
FazitStandardausgabe oder output.txt
(Epigraph) (aus einem Forum)
- Wer hat diesen Unsinn komponiert?
- Astrophysiker. Sie sind auch Menschen.
- Sie haben 10 Fehler im Wort "Journalisten" gemacht.

Viele Benutzer machen Tippfehler, einige aufgrund von TastenanschlĂ€gen und andere aufgrund ihres Analphabetismus. Wir möchten prĂŒfen, ob der Benutzer tatsĂ€chlich an ein anderes Wort als das von ihm eingegebene denken kann.

Nehmen wir formeller an, dass das folgende Fehlermodell auftritt: Der Benutzer beginnt mit einem Wort, das er schreiben möchte, und macht anschließend eine Reihe von Fehlern. Jeder Fehler ist eine Ersetzung eines Teilstrings des Wortes durch einen anderen Teilstring. Ein Fehler entspricht dem Ersetzen nur an einer Position (dh wenn der Benutzer einen einzelnen Fehler durch die Regel "abc" → "cba" machen möchte, kann er aus der Zeichenfolge "abcabc" entweder "cbaabc" oder "abccba" erhalten). Nach jedem Fehler wird der Vorgang wiederholt. Dieselbe Regel könnte mehrmals in verschiedenen Schritten verwendet werden (im obigen Beispiel könnte beispielsweise "cbacba" in zwei Schritten erhalten werden).

Es ist erforderlich, die Mindestanzahl von Fehlern zu bestimmen, die ein Benutzer machen kann, wenn er ein bestimmtes Wort im Auge hat und ein anderes schreibt.

E / A-Formate und Beispiel

Eingabeformat


Die erste Zeile enthĂ€lt das Wort, das der Benutzer nach unserer Annahme im Sinn hatte (es besteht aus Buchstaben des lateinischen Alphabets in Kleinbuchstaben, die LĂ€nge ĂŒberschreitet 20 nicht).

Die zweite Zeile enthĂ€lt das Wort, das er tatsĂ€chlich geschrieben hat (es besteht auch aus Buchstaben des lateinischen Alphabets in Kleinbuchstaben, die LĂ€nge ĂŒberschreitet 20 nicht).

Die dritte Zeile enthÀlt eine einzelne Zahl N (N <50) - die Anzahl der Ersetzungen, die verschiedene Fehler beschreiben.

Die nĂ€chsten N Zeilen enthalten mögliche Ersetzungen im Format & lt "korrekte" Buchstabenfolge & gt <Leerzeichen> <"fehlerhafte" Buchstabenfolge>. Sequenzen dĂŒrfen nicht lĂ€nger als 6 Zeichen sein.

Ausgabeformat


Es ist erforderlich, eine Zahl zu drucken - die Mindestanzahl von Fehlern, die der Benutzer machen kann. Wenn diese Zahl 4 ĂŒberschreitet oder es unmöglich ist, aus einem Wort ein anderes zu erhalten, drucken Sie -1.

Beispiel

Geben Sie einFazit
mlax
drum
50
lr
mlax gtwt
md
mlax ujoc
ml pq
mf
ml bf
mlax aruq
mlax nqdd
mlax fglm
mlax bfit
mlax mziq
mla hlb
au
mlax vmpa
mw
aw
ax ok
mla kqf
me
xx
ml if
ml gk
le
mla xrh
mj
ac
ab
mq
ax fr
ml sb
mlax gxxx
xm
mlax hczx
lq
la sv
lg
ax eh
lax mjh
la ec
la pv
ml iq
aq
lax jrs
la qn
lax bjo
lo
az
ln
ac
4

Lösung


Versuchen wir, aus der richtigen Schreibweise alle möglichen Wörter mit nicht mehr als 4 Fehlern zu generieren. Im schlimmsten Fall kann O ((Lïč’N) 4 ) vorliegen. In den Grenzen des Problems ist dies eine ziemlich große Zahl, daher mĂŒssen Sie herausfinden, wie Sie die KomplexitĂ€t reduzieren können. Stattdessen können Sie den Meet-in-the-Middle-Algorithmus verwenden: Generieren Sie Wörter mit nicht mehr als 2 Fehlern sowie Wörter, aus denen Sie ein vom Benutzer geschriebenes Wort mit nicht mehr als 2 Fehlern erhalten können. Beachten Sie, dass die GrĂ¶ĂŸe jedes dieser SĂ€tze 10 6 nicht ĂŒberschreitet. Wenn die Anzahl der vom Benutzer gemachten Fehler 4 nicht ĂŒberschreitet, ĂŒberschneiden sich diese SĂ€tze. Ebenso können wir ĂŒberprĂŒfen, ob die Anzahl der Fehler 3, 2 und 1 nicht ĂŒberschreitet.

 struct FromTo { std::string from; std::string to; }; std::pair<size_t, std::string> applyRule(const std::string& word, const FromTo &fromTo, int pos) { while(true) { int from = word.find(fromTo.from, pos); if (from == std::string::npos) { return {std::string::npos, {}}; } int to = from + fromTo.from.size(); auto cpy = word; for (int i = from; i < to; i++) { cpy[i] = fromTo.to[i - from]; } return {from, std::move(cpy)}; } } void inverseRules(std::vector<FromTo> &rules) { for (auto& rule: rules) { std::swap(rule.from, rule.to); } } int solve(std::string& wordOrig, std::string& wordMissprinted, std::vector<FromTo>& replaces) { std::unordered_map<std::string, int> mapping; std::unordered_map<int, std::string> mappingInverse; mapping.emplace(wordOrig, 0); mappingInverse.emplace(0, wordOrig); mapping.emplace(wordMissprinted, 1); mappingInverse.emplace(1, wordMissprinted); std::unordered_map<int, std::unordered_set<int>> edges; auto buildGraph = [&edges, &mapping, &mappingInverse](int startId, const std::vector<FromTo>& replaces, bool dir) { std::unordered_set<int> mappingLayer0; mappingLayer0 = {startId}; for (int i = 0; i < 2; i++) { std::unordered_set<int> mappingLayer1; for (const auto& v: mappingLayer0) { auto& word = mappingInverse.at(v); for (auto& fromTo: replaces) { size_t from = 0; while (true) { auto [tmp, wordCpy] = applyRule(word, fromTo, from); if (tmp == std::string::npos) { break; } from = tmp + 1; { int w = mapping.size(); mapping.emplace(wordCpy, w); w = mapping.at(wordCpy); mappingInverse.emplace(w, std::move(wordCpy)); if (dir) { edges[v].emplace(w); } else { edges[w].emplace(v); } mappingLayer1.emplace(w); } } } } mappingLayer0 = std::move(mappingLayer1); } }; buildGraph(0, replaces, true); inverseRules(replaces); buildGraph(1, replaces, false); { std::queue<std::pair<int, int>> q; q.emplace(0, 0); std::vector<bool> mask(mapping.size(), false); int level{0}; while (q.size()) { auto [w, level] = q.front(); q.pop(); if (mask[w]) { continue; } mask[w] = true; if (mappingInverse.at(w) == wordMissprinted) { return level; } for (auto& v: edges[w]) { q.emplace(v, level + 1); } } } return -1; } 

B. Vielarmiger Bandit


Zustand

Zeitlimit2 s
Speicherlimit64 MB
Geben Sie einStandardeingabe
FazitStandardausgabe
Dies ist eine interaktive Aufgabe.

Sie selbst wissen nicht, wie es passiert ist, aber Sie befanden sich in einer Halle mit Spielautomaten mit einer ganzen TĂŒte Token. Leider lehnen sie es an der Abendkasse ab, Token zurĂŒckzunehmen, und Sie haben beschlossen, Ihr GlĂŒck zu versuchen. Es gibt viele Spielautomaten in der Halle, die Sie spielen können. FĂŒr ein Spiel mit einem Spielautomaten verwenden Sie einen Token. Im Falle eines Gewinns gibt Ihnen die Maschine einen Dollar, im Falle eines Verlustes - nichts. Jede Maschine hat eine feste Gewinnwahrscheinlichkeit (die Sie nicht kennen), die jedoch fĂŒr verschiedene Maschinen unterschiedlich ist. Nachdem Sie die Website des Herstellers dieser Maschinen studiert haben, haben Sie festgestellt, dass die Gewinnwahrscheinlichkeit fĂŒr jede Maschine in der Herstellungsphase zufĂ€llig aus der Beta-Verteilung mit bestimmten Parametern ausgewĂ€hlt wird.

Sie möchten Ihre erwarteten Gewinne maximieren.

E / A-Formate und Beispiel

Eingabeformat


Eine AusfĂŒhrung kann aus mehreren Tests bestehen.

Jeder Test beginnt mit der Tatsache, dass Ihr Programm in der Zeile zwei durch ein Leerzeichen getrennte Ganzzahlen enthĂ€lt: Die Anzahl N ist die Anzahl der Token in Ihrer Tasche und M ist die Anzahl der Maschinen in der Halle (N ≀ 10 4 , M ≀ min (N, 100)). ) Die nĂ€chste Zeile enthĂ€lt zwei reelle Zahlen α und ÎČ (1 ≀ α, ÎČ â‰€ 10) - die Parameter der Beta-Verteilung der Gewinnwahrscheinlichkeit.

Das Kommunikationsprotokoll mit dem PrĂŒfsystem lautet wie folgt: Sie stellen genau N Anforderungen. Drucken Sie fĂŒr jede Anforderung in einer separaten Zeile die Nummer der Maschine aus, die Sie spielen möchten (von 1 bis einschließlich M). Als Antwort wird in einer separaten Zeile entweder "0" oder "1" angezeigt, was jeweils einen Verlust und einen Gewinn in einem Spiel mit dem angeforderten Spielautomaten bedeutet.

Nach dem letzten Test gibt es anstelle der Zahlen N und M zwei Nullen.

Ausgabeformat


Die Aufgabe gilt als erledigt, wenn Ihre Entscheidung nicht viel schlimmer ist als die Entscheidung der Jury. Wenn Ihre Entscheidung erheblich schlechter ist als die Entscheidung der Jury, erhalten Sie das Urteil „falsche Antwort“.

Es ist garantiert, dass die Wahrscheinlichkeit, das Urteil „falsche Antwort“ zu erhalten, 10-6 nicht ĂŒberschreitet, wenn Ihre Entscheidung nicht schlechter ist als die Entscheidung der Jury.

Anmerkungen


Interaktionsbeispiel:

 ____________________  stdin     stdout ____________________ ____________________   5 2   2 2              2    1              1    0              1    1              2    1              2    1 

Lösung


Dieses Problem ist bekannt und kann auf verschiedene Arten gelöst werden. Die Hauptentscheidung der Jury setzte die Thompson-Stichprobenstrategie um. Da jedoch die Anzahl der Schritte zu Beginn des Programms bekannt war, gibt es optimalere Strategien (z. B. UCB1). DarĂŒber hinaus könnte man sogar mit der Epsilon-Greedy-Strategie auskommen: Mit einer bestimmten Wahrscheinlichkeit Δ eine zufĂ€llige Maschine spielen und mit einer Wahrscheinlichkeit (1 - Δ) eine Maschine mit der besten Siegesstatistik spielen.

 class SolverFromStdIn(object): def __init__(self): self.regrets = [0.] self.total_win = [0.] self.moves = [] class ThompsonSampling(SolverFromStdIn): def __init__(self, bandits_total, init_a=1, init_b=1): """ init_a (int): initial value of a in Beta(a, b). init_b (int): initial value of b in Beta(a, b). """ SolverFromStdIn.__init__(self) self.n = bandits_total self.alpha = init_a self.beta = init_b self._as = [init_a] * self.n # [random.betavariate(self.alpha, self.beta) for _ in range(self.n)] self._bs = [init_b] * self.n # [random.betavariate(self.alpha, self.beta) for _ in range(self.n)] self.last_move = -1 random.seed(int(time.time())) def move(self): samples = [random.betavariate(self._as[x], self._bs[x]) for x in range(self.n)] self.last_move = max(range(self.n), key=lambda x: samples[x]) self.moves.append(self.last_move) return self.last_move def set_reward(self, reward): i = self.last_move r = reward self._as[i] += r self._bs[i] += (1 - r) return i, r while True: n, m = map(int, sys.stdin.readline().split()) if n == 0 and m == 0: break alpha, beta = map(float, sys.stdin.readline().split()) solver = ThompsonSampling(m) for _ in range(n): print >> sys.stdout, solver.move() + 1 sys.stdout.flush() reward = int(sys.stdin.readline()) solver.set_reward(reward) 

C. Ausrichtung der SĂ€tze


Zustand

Zeitlimit2 s
Speicherlimit64 MB
Geben Sie einStandardeingabe oder input.txt
FazitStandardausgabe oder output.txt
Eine der wichtigsten Aufgaben fĂŒr das Training eines guten maschinellen Übersetzungsmodells ist ein guter Fall von parallelen SĂ€tzen. In der Regel sind parallele Angebote die Quelle fĂŒr parallele Angebote. Es stellt sich heraus, dass Sie hĂ€ufig nur ihre LĂ€nge kennen mĂŒssen, um ein bestimmtes Korpus paralleler SĂ€tze zu bilden. Insbesondere stellen Sie möglicherweise fest, dass der Satz in der Ausgangssprache umso lĂ€nger ĂŒbersetzt wird, je lĂ€nger er ist. Einige Schwierigkeiten liegen in der Tatsache, dass sich wĂ€hrend der Übersetzung die Anzahl der SĂ€tze im Text Ă€ndern kann: Manchmal können zwei benachbarte SĂ€tze in der Übersetzung zu einem kombiniert werden oder umgekehrt - ein Satz kann in zwei SĂ€tze geteilt werden. In einigen seltenen FĂ€llen können SĂ€tze in einer Übersetzung vollstĂ€ndig weggelassen werden, oder eine Übersetzung kann in einer Übersetzung erscheinen, die nicht im Original enthalten war.

Nehmen wir formeller an, dass das folgende generative Modell fĂŒr parallele GehĂ€use wahr ist. Bei jedem Schritt fĂŒhren wir einen der folgenden Schritte aus:

1. Stoppen Sie

Mit der Wahrscheinlichkeit p h endet die Erzeugung der RĂŒmpfe.

2. [1-0] Angebote ĂŒberspringen

Mit der Wahrscheinlichkeit p d schreiben wir dem Originaltext einen Satz zu. Wir schreiben der Übersetzung nichts zu. Die LĂ€nge des Satzes in der Originalsprache L ≄ 1 wird aus der diskreten Verteilung ausgewĂ€hlt:

.

Hier sind ÎŒs , σs die Verteilungsparameter und αs der so gewĂ€hlte Normalisierungskoeffizient .

3. [0-1] Vorschlag einfĂŒgen

Mit der Wahrscheinlichkeit p i weisen wir der Übersetzung einen Satz zu. Wir schreiben dem Original nichts zu. Die LĂ€nge eines Satzes in einer Übersetzungssprache L ≄ 1 wird aus einer diskreten Verteilung ausgewĂ€hlt:

.

Hier sind ÎŒ t , σ t die Verteilungsparameter und α t der so gewĂ€hlte Normalisierungskoeffizient .

4. Übersetzung

Mit der Wahrscheinlichkeit (1 - p d - p i - p h ) nehmen wir die LĂ€nge des Satzes in der Originalsprache L s ≄ 1 aus der Verteilung p s (mit Aufrundung). Als nĂ€chstes erzeugen wir die LĂ€nge des Satzes in der Übersetzungssprache L t ≄ 1 aus der bedingten diskreten Verteilung:

.

Hier ist α st der Normalisierungskoeffizient, und die verbleibenden Parameter sind in den vorhergehenden AbsÀtzen beschrieben.

Weiter ist ein weiterer Schritt:

1. [2-1] Mit der Wahrscheinlichkeit p split s teilt sich der erzeugte Satz in der Originalsprache in zwei nicht leere auf, so dass sich die Gesamtzahl der Wörter um genau eins erhöht . Die Wahrscheinlichkeit, dass ein Satz der LĂ€nge L s in Teile der LĂ€nge L 1 und L 2 zerfĂ€llt (dh L 1 + L 2 = L s + 1), ist proportional zu P s (L 1 ) ⋅ P s (L 2 ).

2. [1-2] Mit der Wahrscheinlichkeit p split t teilt sich der erzeugte Satz in der Zielsprache in zwei nicht leere SĂ€tze auf, so dass sich die Gesamtzahl der Wörter um genau eins erhöht. Die Wahrscheinlichkeit, dass ein Satz der LĂ€nge L t in Teile der LĂ€nge L1 und L2 zerfĂ€llt (dh L 1 + L 2 = L t + 1), ist proportional zu P t (L 1 ) ⋅ P t (L 2 ).

3. 3. [1-1] Mit einer Wahrscheinlichkeit von (1 - p split s - p split t ) zerfÀllt keiner der generierten SÀtze.

E / A-Formate, Beispiele und Hinweise

Eingabeformat


Die erste Zeile der Datei enthĂ€lt die Verteilungsparameter: p h , p d , p i , p split s , p split t , ÎŒ s , σ s , ÎŒ t , σ t . 0,1 ≀ σ s <σ t ≀ 3. 0 ≀ ÎŒ s , ÎŒ t ≀ 5.

Die nĂ€chste Zeile enthĂ€lt die Zahlen N s und N t - die Anzahl der SĂ€tze im Fall in der Originalsprache bzw. in der Zielsprache (1 ≀ N s , N t ≀ 1000).

Die nÀchste Zeile enthÀlt N s Ganzzahlen - die LÀnge der SÀtze in der Originalsprache. Die nÀchste Zeile enthÀlt N t Ganzzahlen - die LÀnge der SÀtze in der Zielsprache.

Die nĂ€chste Zeile enthĂ€lt zwei Zahlen: j und k (1 ≀ j ≀ N s , 1 ≀ k ≀ N t ).

Ausgabeformat


Es ist erforderlich, die Wahrscheinlichkeit abzuleiten, dass SĂ€tze mit den Indizes j bzw. k in den Texten parallel sind (dh, dass sie in einem Schritt des Algorithmus erzeugt werden und keiner von ihnen das Ergebnis des Zerfalls ist).

Ihre Antwort wird akzeptiert, wenn der absolute Fehler 10 –4 nicht ĂŒberschreitet.

Beispiel 1

Geben Sie einFazit
0.05 0.08 0.07 0.15 0.1 1 0.3 3 0.5
1 1
4
20
1 1
0.975037457809

Beispiel 2

Geben Sie einFazit
0.1 0.2 0.3 0.25 0.3 1 0.3 3 0.5
2 1
3 4
20
2 1
0.247705779810

Beispiel 3

Geben Sie einFazit
0.2 0.2 0.2 0.3 0.3 3 0.3 1 1
5 3
16 35 24 19 23
5 6 7
2 1
0.200961101684

Anmerkungen


Im ersten Beispiel kann die anfÀngliche Folge von Zahlen auf drei Arten erhalten werden:

‱ FĂŒgen Sie zuerst mit der Wahrscheinlichkeit p d einen Satz zum Originaltext hinzu, dann mit der Wahrscheinlichkeit p i einen Satz zur Übersetzung und beenden Sie dann mit der Wahrscheinlichkeit p h die Generierung.

Die Wahrscheinlichkeit dieses Ereignisses ist P 1 = p d * P s (4) * p i * P t (20) * p h .

‱ FĂŒgen Sie zuerst mit der Wahrscheinlichkeit p d einen Satz zum Originaltext hinzu, dann mit der Wahrscheinlichkeit p i einen Satz zur Übersetzung und beenden Sie dann mit der Wahrscheinlichkeit p h die Generierung.

Die Wahrscheinlichkeit dieses Ereignisses ist gleich P 2 = p i * P t (20) * p d * P s (4) * p h .

‱ Mit der Wahrscheinlichkeit (1 - p h - p d - p i ) zwei SĂ€tze erzeugen, dann mit der Wahrscheinlichkeit (1 - p split s - p split t ) alles so lassen, wie es ist (dh das Original oder die Übersetzung nicht in zwei SĂ€tze teilen ) und beenden Sie danach mit der Wahrscheinlichkeit p h die Erzeugung.

Die Wahrscheinlichkeit dieses Ereignisses ist
.

Als Ergebnis wird die Antwort berechnet als .

Lösung


Die Aufgabe ist ein Sonderfall der Ausrichtung mit Hidden-Markov-Modellen (HMM-Ausrichtung). Die Hauptidee ist, dass Sie die Wahrscheinlichkeit berechnen können, mit diesem Modell und dem VorwĂ€rtsalgorithmus ein bestimmtes Dokumentpaar zu generieren: In diesem Fall ist der Status ein Paar von DokumentprĂ€fixen. Dementsprechend kann die erforderliche Wahrscheinlichkeit der Ausrichtung eines bestimmten Paares paralleler SĂ€tze durch den VorwĂ€rts-RĂŒckwĂ€rts- Algorithmus berechnet werden.

Code
 #include <iostream> #include <iomanip> #include <cmath> #include <vector> double p_h, p_d, p_i, p_tr, p_ss, p_st, mu_s, sigma_s, mu_t, sigma_t; double lognorm_cdf(double x, double mu, double sigma) { if (x < 1e-9) return 0.0; double res = std::log(x) - mu; res /= std::sqrt(2.0) * sigma; res = 0.5 * (1 + std::erf(res)); return res; } double length_probability(int l, double mu, double sigma) { return lognorm_cdf(l, mu, sigma) - lognorm_cdf(l - 1, mu, sigma); } double translation_probability(int ls, int lt) { double res = length_probability(ls, mu_s, sigma_s); double mu = mu_t - mu_s + std::log(ls); double sigma = std::sqrt(sigma_t * sigma_t - sigma_s * sigma_s); res *= length_probability(lt, mu, sigma); return res; } double split_probability(int l1, int l2, double mu, double sigma) { int l_sum = l1 + l2; double total_prob = 0.0; for (int i = 1; i < l_sum; ++i) { total_prob += length_probability(i, mu, sigma) * length_probability(l_sum - i, mu, sigma); } return length_probability(l1, mu, sigma) * length_probability(l2, mu, sigma) / total_prob; } double log_prob10(int ls) { return std::log(p_d * length_probability(ls, mu_s, sigma_s)); } double log_prob01(int lt) { return std::log(p_i * length_probability(lt, mu_t, sigma_t)); } double log_prob11(int ls, int lt) { return std::log(p_tr * (1 - p_ss - p_st) * translation_probability(ls, lt)); } double log_prob21(int ls1, int ls2, int lt) { return std::log(p_tr * p_ss * split_probability(ls1, ls2, mu_s, sigma_s) * translation_probability(ls1 + ls2 - 1, lt)); } double log_prob12(int ls, int lt1, int lt2) { return std::log(p_tr * p_st * split_probability(lt1, lt2, mu_t, sigma_t) * translation_probability(ls, lt1 + lt2 - 1)); } double logsum(double v1, double v2) { double res = std::max(v1, v2); v1 -= res; v2 -= res; v1 = std::min(v1, v2); if (v1 < -30) { return res; } return res + std::log(std::exp(v1) + 1.0); } double loginc(double* to, double from) { *to = logsum(*to, from); } constexpr double INF = 1e25; int main(void) { using std::cin; using std::cout; cin >> p_h >> p_d >> p_i >> p_ss >> p_st >> mu_s >> sigma_s >> mu_t >> sigma_t; p_tr = 1.0 - p_h - p_d - p_i; int Ns, Nt; cin >> Ns >> Nt; using std::vector; vector<int> ls(Ns), lt(Nt); for (int i = 0; i < Ns; ++i) cin >> ls[i]; for (int i = 0; i < Nt; ++i) cin >> lt[i]; vector< vector< double> > fwd(Ns + 1, vector<double>(Nt + 1, -INF)), bwd = fwd; fwd[0][0] = 0; bwd[Ns][Nt] = 0; for (int i = 0; i <= Ns; ++i) { for (int j = 0; j <= Nt; ++j) { if (i >= 1) { loginc(&fwd[i][j], fwd[i - 1][j] + log_prob10(ls[i - 1])); loginc(&bwd[Ns - i][Nt - j], bwd[Ns - i + 1][Nt - j] + log_prob10(ls[Ns - i])); } if (j >= 1) { loginc(&fwd[i][j], fwd[i][j - 1] + log_prob01(lt[j - 1])); loginc(&bwd[Ns - i][Nt - j], bwd[Ns - i][Nt - j + 1] + log_prob01(lt[Nt - j])); } if (i >= 1 && j >= 1) { loginc(&fwd[i][j], fwd[i - 1][j - 1] + log_prob11(ls[i - 1], lt[j - 1])); loginc(&bwd[Ns - i][Nt - j], bwd[Ns - i + 1][Nt - j + 1] + log_prob11(ls[Ns - i], lt[Nt - j])); } if (i >= 2 && j >= 1) { loginc(&fwd[i][j], fwd[i - 2][j - 1] + log_prob21(ls[i - 1], ls[i - 2], lt[j - 1])); loginc(&bwd[Ns - i][Nt - j], bwd[Ns - i + 2][Nt - j + 1] + log_prob21(ls[Ns - i], ls[Ns - i + 1], lt[Nt - j])); } if (i >= 1 && j >= 2) { loginc(&fwd[i][j], fwd[i - 1][j - 2] + log_prob12(ls[i - 1], lt[j - 1], lt[j - 2])); loginc(&bwd[Ns - i][Nt - j], bwd[Ns - i + 1][Nt - j + 2] + log_prob12(ls[Ns - i], lt[Nt - j], lt[Nt - j + 1])); } } } int j, k; cin >> j >> k; double rlog = fwd[j - 1][k - 1] + bwd[j][k] + log_prob11(ls[j - 1], lt[k - 1]) - bwd[0][0]; cout << std::fixed << std::setprecision(12) << std::exp(rlog) << std::endl; } 

D. Band mit Empfehlungen


Zustand

Zeitlimit2 s
Speicherlimit64 MB
Geben Sie einStandardeingabe oder input.txt
FazitStandardausgabe oder output.txt
Betrachten Sie einen Feed mit Empfehlungen fĂŒr heterogene Inhalte. Es mischt Objekte verschiedener Typen (Bilder, Videos, Nachrichten usw.). Diese Objekte sind normalerweise nach Relevanz fĂŒr den Benutzer geordnet: Je relevanter (interessanter) das Objekt fĂŒr den Benutzer ist, desto nĂ€her am Anfang der Empfehlungsliste. Bei einer solchen Reihenfolge treten jedoch hĂ€ufig Situationen auf, in denen mehrere Objekte desselben Typs in der Liste der Empfehlungen aufgefĂŒhrt sind. Dies verschlechtert die externe Vielfalt unserer Empfehlungen erheblich und daher gefĂ€llt es den Benutzern nicht. Es ist erforderlich, einen Algorithmus zu implementieren, der gemĂ€ĂŸ der Liste der Empfehlungen eine neue Liste erstellt, die frei von diesem Problem ist und am relevantesten ist.

Eine erste Liste von Empfehlungen sei a = [a 0 , a 1 , ..., a n - 1 ] mit der LĂ€nge n> 0. Ein Objekt mit der Nummer i hat den Typ mit der Nummer b i ∈ {0, ..., m - 1}. ZusĂ€tzlich hat ein Objekt unter der Nummer i die Relevanz r (a i ) = 2 −i . Betrachten Sie die Liste, die aus der ersten Liste erhalten wird, indem Sie eine Teilmenge von Objekten auswĂ€hlen und neu anordnen: x = [a i 0 , a i 1 , ..., a i k - 1 ] mit der LĂ€nge k (0 ≀ k ≀ n). Eine Liste wird als zulĂ€ssig bezeichnet, wenn keine zwei aufeinanderfolgenden Objekte in ihrem Typ ĂŒbereinstimmen, d. H. B i j ≠ b i j + 1 fĂŒr alle j = 0, ..., k - 2. Die Relevanz der Liste wird durch die Formel berechnet  sumj=0k−12−jr(aij). Sie mĂŒssen die Liste der maximalen Relevanz unter allen gĂŒltigen finden.

E / A-Formate und Beispiele

Eingabeformat


In der ersten Zeile werden die Zahlen n und m mit einem Leerzeichen geschrieben (1 ≀ n ≀ 100000, 1 ≀ m ≀ n). Die nĂ€chsten n Zeilen enthalten die Zahlen b i fĂŒr i = 0, ..., n - 1 (0 ≀ b i ≀ m - 1).

Ausgabeformat


Notieren Sie mit einem Leerzeichen die Anzahl der Objekte in der endgĂŒltigen Liste: i 0 , i 1 , ..., i k - 1 .

Beispiel 1

Geben Sie einFazit
1 1
0
0

Beispiel 2

Geben Sie einFazit
2 2
1
1
0

Beispiel 3

Geben Sie einFazit
10 2
1
1
1
0
0
1
0
1
1
1
0 3 1 4 2 6 5

Lösung


Mit einfachen mathematischen Berechnungen kann gezeigt werden, dass das Problem durch einen „gierigen“ Ansatz gelöst werden kann, dh in der optimalen Liste von Empfehlungen hat jeder Punkt das relevanteste Objekt von allen, die am selben Anfang der Liste gĂŒltig sind. Die Implementierung dieses Ansatzes ist einfach: Wir nehmen Objekte in einer Reihe und fĂŒgen sie, wenn möglich, der Antwort hinzu. Wenn ein ungĂŒltiges Objekt gefunden wird (dessen Typ mit dem Typ des vorherigen ĂŒbereinstimmt), legen wir es in einer separaten Warteschlange beiseite, aus der wir es so schnell wie möglich in die Antwort einfĂŒgen. Beachten Sie, dass zu jedem Zeitpunkt alle Objekte in dieser Warteschlange einen ĂŒbereinstimmenden Typ haben. Am Ende verbleiben möglicherweise mehrere Objekte in der Warteschlange. Sie werden nicht in die Antwort aufgenommen.

  std::vector<int> blend(int n, int m, const std::vector<int>& types) { std::vector<int> result; std::queue<int> repeated; for (int i = 0; i < n; ++i) { if (result.empty() || types[result.back()] != types[i]) { result.push_back(i); if (!repeated.empty() && types[repeated.front()] != types[result.back()]) { result.push_back(repeated.front()); repeated.pop(); } } else { repeated.push(i); } } return result; } 

D. Clusterisierung von Zeichenfolgen

Alle Sprachenpython2.7 + numpypython3.5 + numpy
Zeitlimit1 s6 s6 s
Speicherlimit64 MB64 MB64 MB
Geben Sie einStandardeingabe oder input.txt
FazitStandardausgabe oder output.txt
Es gibt ein endliches Alphabet A = {a 1 , a 2 , ..., a K - 1 , a K = S}, a i ∈ {a, b, ..., z}, S ist das Ende der Zeile.

Betrachten Sie die folgende Methode zum Generieren von zufĂ€lligen Zeichenfolgen ĂŒber dem Alphabet A:

1. Das erste Zeichen x 1 ist eine Zufallsvariable mit der Verteilung P (x 1 = a i ) = q i (es ist bekannt, dass q K = 0 ist).
2. Jedes nĂ€chste Zeichen wird basierend auf dem vorherigen gemĂ€ĂŸ der bedingten Verteilung P (x i = a j || x i - 1 = a l ) = p jl erzeugt .
3. Wenn x i = S ist, stoppt die Erzeugung und das Ergebnis ist x 1 x 2 ... x i - 1 .

Der Satz von Linien, die aus einer Mischung von zwei beschriebenen Modellen mit unterschiedlichen Parametern erzeugt werden, ist angegeben. FĂŒr jede Zeile muss der Index der Kette angegeben werden, aus der sie generiert wurde.

E / A-Formate, Beispiel und Notizen

Eingabeformat


Die erste Zeile enthĂ€lt zwei Zahlen 1000 ≀ N ≀ 2000 und 3 ≀ K ≀ 27 - die Anzahl der Zeilen bzw. die GrĂ¶ĂŸe des Alphabets.

Die zweite Zeile enthÀlt eine Zeile, die aus K - 1 verschiedenen Kleinbuchstaben des lateinischen Alphabets besteht und die ersten K - 1 Elemente des Alphabets angibt.

Jede der folgenden N Zeilen wird gemĂ€ĂŸ dem in der Bedingung beschriebenen Algorithmus erzeugt.

Ausgabeformat


In n Zeilen enthĂ€lt die i-te Zeile die Clusternummer (0/1) fĂŒr die Sequenz in der i + 1-ten Zeile der Eingabedatei. Die Übereinstimmung mit der wahren Antwort sollte mindestens 80% betragen.

Beispiel

Geben Sie einFazit
100 3
a
a
aa
a
aaa
a
aaaaaa
aa
a
a
a
aaa
a
a
aaa
aa
aaaa
aaa
a
aaaaa
aa
a
aaaa
a
a
a
a
a
a
aa
aaaa
aaa
a
aa
aaaa
a
a
a
a
a
a
a
a
a
a
aa
aaa
aaa
a
a
bbb
bb
bb
bbbbbbb
bb
bbb
b
bbbbbbb
bbbb
bbb
bb
bbb
bb
bb
bbb
bbbbbb
bbb
b
bbbbbb
b
bbbbb
b
b
bb
b
bb
bb
b
b
b
b
bb
bb
bb
b
b
b
bb
b
bbb
bb
b
bbbbbb
b
bb
bb
bb
b
bb
bbb
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1

Anmerkungen


Hinweis zum Test aus der Bedingung: Darin werden die ersten 50 Zeilen aus der Verteilung generiert
P (x i = a | x i - 1 = a) = 0,5, P (x i = S | x i - 1 = a) = 0,5, P (x 1 = a) = 1; zweite 50 - aus der Verteilung
P (x i = b | x i - 1 = b) = 0,5, P (x i = S | x i - 1 = b) = 0,5, P (x 1 = b) = 1.

Lösung


Das Problem wird mithilfe des EM-Algorithmus gelöst: Es wird angenommen, dass die dargestellte Probe aus einer Mischung von zwei Markov-Ketten generiert wird, deren Parameter wĂ€hrend der Iterationen wiederhergestellt werden. Eine EinschrĂ€nkung von 80% der richtigen Antworten wird vorgenommen, damit die Richtigkeit der Lösung nicht durch Beispiele beeintrĂ€chtigt wird, die in beiden Ketten eine hohe Wahrscheinlichkeit haben. Diese Beispiele können daher bei ordnungsgemĂ€ĂŸer Wiederherstellung einer Kette zugewiesen werden, die hinsichtlich der generierten Antwort falsch ist.

 import random import math EPS = 1e-9 def empty_row(size): return [0] * size def empty_matrix(rows, cols): return [empty_row(cols) for _ in range(rows)] def normalized_row(row): row_sum = sum(row) + EPS return [x / row_sum for x in row] def normalized_matrix(mtx): return [normalized_row(r) for r in mtx] def restore_params(alphabet, string_samples): n_tokens = len(alphabet) n_samples = len(string_samples) samples = [tuple([alphabet.index(token) for token in s] + [n_tokens - 1, n_tokens - 1]) for s in string_samples] probs = [random.random() for _ in range(n_samples)] for _ in range(200): old_probs = [x for x in probs] # probs fixed p0, A = empty_row(n_tokens), empty_matrix(n_tokens, n_tokens) q0, B = empty_row(n_tokens), empty_matrix(n_tokens, n_tokens) for prob, sample in zip(probs, samples): p0[sample[0]] += prob q0[sample[0]] += 1 - prob for t1, t2 in zip(sample[:-1], sample[1:]): A[t1][t2] += prob B[t1][t2] += 1 - prob A, p0 = normalized_matrix(A), normalized_row(p0) B, q0 = normalized_matrix(B), normalized_row(q0) trans_log_diff = [ [math.log(b + EPS) - math.log(a + EPS) for b, a in zip(B_r, A_r)] for B_r, A_r in zip(B, A) ] # A, p0, B, q0 fixed probs = empty_row(n_samples) for i, sample in enumerate(samples): value = math.log(q0[sample[0]] + EPS) - math.log(p0[sample[0]] + EPS) for t1, t2 in zip(sample[:-1], sample[1:]): value += trans_log_diff[t1][t2] probs[i] = 1.0 / (1.0 + math.exp(value)) if max(abs(x - y) for x, y in zip(probs, old_probs)) < 1e-9: break return [int(x > 0.5) for x in probs] def main(): N, K = list(map(int, input().split())) string_samples = [] alphabet = list(input().strip()) + [''] for _ in range(N): string_samples.append(input().rstrip()) result = restore_params(alphabet, string_samples) for r in result: print(r) if __name__ == '__main__': main() 



.

Source: https://habr.com/ru/post/de461273/


All Articles