Die wissenschaftliche Forschungsarbeit ist, denke ich, der interessanteste Teil unserer Ausbildung. Die Idee ist, bereits wÀhrend des Studiums in das gewÀhlte Fachgebiet hineinzuschnuppern. Zum Beispiel gehen Studenten der Richtungen Software Engineering und Machine Learning oft in Unternehmen (vor allem JetBrains oder Yandex, aber nicht nur), um Forschungsprojekte zu machen.
In diesem Beitrag werde ich von meinem Projekt im Bereich Informatik berichten. Im Rahmen meiner Arbeit habe ich praktisch AnsÀtze zur Lösung eines der bekanntesten NP-schweren Probleme untersucht und umgesetzt: dem Vertex Cover Problem..
Derzeit entwickelt sich ein interessanter Ansatz zu NP-schweren Problemen sehr schnell - parametrische Algorithmen. Ich werde versuchen, Sie auf den neuesten Stand zu bringen, Ihnen einige einfache parametrische Algorithmen vorzustellen und eine mĂ€chtige Methode zu beschreiben, die mir sehr geholfen hat. Meine Ergebnisse habe ich beim PACE Challenge-Wettbewerb prĂ€sentiert: Nach den Ergebnissen der offenen Tests belegt meine Lösung den dritten Platz, und die endgĂŒltigen Ergebnisse werden am 1. Juli bekannt gegeben.

Ăber mich
Mein Name ist Wassili Alferow, ich beende derzeit das dritte Jahr an der Higher School of Economics in St. Petersburg. Seit meiner Schulzeit interessiere ich mich fĂŒr Algorithmen, als ich in der Moskauer Schule Nr. 179 war und erfolgreich an Informatikwettbewerben teilnahm.
Eine endliche Anzahl von Spezialisten fĂŒr parametrische Algorithmen betritt eine Bar ...
Das Beispiel stammt aus dem Buch
Stellen Sie sich vor, Sie sind der Sicherheitsbeamte einer Bar in einer kleinen Stadt. Jeden Freitag kommt die HĂ€lfte der Stadt in Ihre Bar, um sich zu entspannen, was Ihnen eine Menge Ărger bereitet: Sie mĂŒssen raufenden Kunden aus der Bar werfen, um SchlĂ€gereien zu vermeiden. Irgendwann wird Ihnen das zu langweilig, und Sie beschlieĂen, prĂ€ventive MaĂnahmen zu ergreifen.
Da Ihre Stadt klein ist, wissen Sie genau, welche Paare von GĂ€sten mit hoher Wahrscheinlichkeit in der Bar aneinandergeraten, wenn sie zusammen sind. Sie haben eine Liste von n Personen, die heute Abend in die Bar kommen werden. Sie entscheiden sich, bestimmte BĂŒrger nicht in die Bar zu lassen, sodass es keinen Streit gibt. Gleichzeitig möchte Ihre Chefetage keinen Gewinn verlieren und wird unzufrieden sein, wenn Sie mehr als k Personen nicht in die Bar lassen.
Leider ist das Problem, vor dem Sie stehen, ein klassisches NP-schweres Problem. Vielleicht kennen Sie es als , oder als Problem der Knotenabdeckung. FĂŒr solche Aufgaben sind im Allgemeinen keine Algorithmen bekannt, die in akzeptabler Zeit arbeiten. Genauer gesagt besagt die nicht bewiesene und ziemlich starke Hypothese ETH (Exponential Time Hypothesis), dass dieses Problem nicht in polynomialer Zeit gelöst werden kann.
, das heiĂt, dass man merklich nichts Besseres als vollstĂ€ndige Durchmusterung erfinden kann. Nehmen wir zum Beispiel an, es möchten n = 1000 Menschen in Ihre Bar kommen. Dann betrĂ€gt die vollstĂ€ndige Durchmusterung
Varianten, was ungefÀhr
â unvorstellbar viel ist. Zum GlĂŒck hat Ihr Management Ihnen eine EinschrĂ€nkung gesetzt k = 10, sodass die Anzahl der Kombinationen, die Sie durchprobieren mĂŒssen, viel kleiner ist: die Anzahl der Teilmengen von zehn Elementen betrĂ€gt
. Das ist schon besser, aber trotzdem ist es unmöglich, dies in einem Tag auch nur auf einem leistungsstarken Cluster zu zÀhlen.

Um die Wahrscheinlichkeit von Streitigkeiten bei einer solchen Konfiguration gespannter Beziehungen zwischen den Barbesuchern auszuschlieĂen, dĂŒrfen Sie Bob, Daniel und Fyodor nicht hineinlassen. Es gibt keine Lösung, bei der nur zwei drauĂen bleiben.
Bedeutet das, dass es an der Zeit ist, aufzugeben und alle hereinzulassen? Lassen Sie uns andere Optionen prĂŒfen. Nun, man könnte beispielsweise nur diejenigen nicht hereinlassen, die wahrscheinlich mit vielen Leuten streiten werden. Wenn jemand mit mindestens k + 1 anderen Leuten streiten kann, darf er auf keinen Fall hereingelassen werden â sonst mĂŒssen Sie alle k + 1 BĂŒrger, mit denen er sich streiten könnte, drauĂen lassen, was das Management sicherlich verĂ€rgern wird.
Angenommen, Sie haben alle, die Sie konnten, nach diesem Prinzip ausgeschlossen. Dann können alle anderen nicht mit mehr als k Leuten streiten. Wenn Sie aus ihnen k Menschen ausschlieĂen, können Sie nicht mehr als
Konflikte verhindern. Das bedeutet, wenn mehr als
Menschen an mindestens einem Konflikt beteiligt sind, werden Sie sicher nicht alle verhindern können. Da Sie, wie klar ist, die völlig konfliktfreien Personen auf jeden Fall hineinlassen werden, mĂŒssen Sie alle Teilmengen der GröĂe zehn aus zweihundert Menschen durchprobieren. Das sind ungefĂ€hr
, und eine solche Anzahl von Operationen lÀsst sich bereits auf einem Cluster durchprobieren.
Wenn es möglich ist, völlig unkonfliktuale Persönlichkeiten zu berĂŒcksichtigen, was ist dann mit denen, die nur an einem Konflikt beteiligt sind? TatsĂ€chlich können auch sie herein gelassen werden, indem wir die TĂŒren vor ihrem Gegner schlieĂen. Und tatsĂ€chlich, wenn Alice nur mit Bob in Konflikt steht, gewinnen wir nichts, wenn wir von den beiden Alice hineinlassen: Bob kann andere Konflikte haben, wĂ€hrend Alice sicher keine hat. Es macht also keinen Sinn, beide nicht hereinzulassen. Nach solchen Operationen bleibt nicht mehr als
die GĂ€ste mit ungewisser Schicksal: insgesamt haben wir
Konflikte, an denen jeweils zwei Teilnehmer beteiligt sind und jeder mindestens an zweien teilnimmt. Das bedeutet, wir mĂŒssen nur insgesamt
Varianten durchgehen, was durchaus als einen halben Tag auf einem Laptop gelten kann.
TatsĂ€chlich können wir mit einfachen Ăberlegungen noch attraktivere Bedingungen erreichen. Beachten wir, dass wir alle Streitigkeiten unbedingt klĂ€ren mĂŒssen, das heiĂt, aus jedem Konfliktpaar mindestens eine Person auszuwĂ€hlen, die wir nicht hineinlassen werden. Betrachten wir einen solchen Algorithmus: Wir nehmen einen beliebigen Konflikt, entfernen einen Teilnehmer und starten rekursiv mit dem Rest, anschlieĂend entfernen wir den anderen und starten ebenfalls rekursiv. Da wir bei jedem Schritt jemanden ausschlieĂen, ist der Rekursionsbaum eines solchen Algorithmus ein binĂ€rer Baum der Tiefe k, daher lĂ€uft der Algorithmus insgesamt in 
Das oben angefĂŒhrte Beispiel ist ein Beispiel fĂŒr einen parametrisierten Algorithmus. Parametrisierte Algorithmen sind Algorithmen, die in der Zeit arbeiten f(k) poly(n)
Alle Ăberlegungen bis zu diesem Algorithmus fĂŒhren zum Beispiel von Kernelisierung â eine der allgemeinen Techniken zur Erstellung parametrisierter Algorithmen. Kernelisation ist die Reduzierung der GröĂe eines Problems auf einen Wert, der durch eine Funktion des Parameters begrenzt wird. Das erhaltene Problem wird oft als Kern bezeichnet. So haben wir durch einfache Ăberlegungen zu den Graden der Knoten einen quadratischen Kern fĂŒr das Problem des Vertex Cover erhalten, parametrisiert durch die GröĂe der Antwort. Es gibt auch andere Parameter, die fĂŒr dieses Problem gewĂ€hlt werden können (zum Beispiel Vertex Cover Above LP), aber wir werden genau ĂŒber diesen Parameter sprechen.
Pace Challenge
Wettbewerb (Die Herausforderung der parametrisierten Algorithmen und rechnerischen Experimente) entstand 2015, um eine Verbindung zwischen parametrisierten Algorithmen und den in der Praxis verwendeten AnsÀtzen zur Lösung von Rechenproblemen herzustellen. Die ersten drei Wettbewerbe waren dem Finden der Baumweite eines Graphen (), dem Finden eines Steiner-Baums () und dem Finden eines Satzes von Knoten, der Zyklen schneidet (). In diesem Jahr war eine der Aufgaben, in denen man sein Können versuchen konnte, das oben beschriebene Problem des Vertex Cover.
Der Wettbewerb gewinnt von Jahr zu Jahr an PopularitĂ€t. Wenn man den vorlĂ€ufigen Daten Glauben schenken darf, haben in diesem Jahr alleine beim Wettbewerb zur Lösung des Vertex Cover-Problems 24 Teams teilgenommen. Es ist erwĂ€hnenswert, dass der Wettbewerb nicht nur einige Stunden oder sogar eine Woche dauert, sondern mehrere Monate. Die Teams haben die Möglichkeit, Fachliteratur zu studieren, ihre eigene originelle Idee zu entwickeln und diese umzusetzen. Im Grunde genommen stellt dieser Wettbewerb eine Forschungsarbeit dar. Die Ideen der effektivsten Lösungen und die Preisverleihung der Gewinner finden gemeinsam mit der Konferenz (International Symposium on Parameterized and Exact Computation) im Rahmen der gröĂten jĂ€hrlichen algorithmischen Versammlung in Europa . Detailliertere Informationen zum Wettbewerb selbst finden Sie auf , wĂ€hrend die Ergebnisse der vergangenen Jahre liegen .
Lösungsansatz
Um das Problem der Knotenabdeckung zu lösen, habe ich versucht, parametrisierte Algorithmen anzuwenden. Diese bestehen in der Regel aus zwei Teilen: Vereinfachungsregeln (die idealerweise zu einer Kernelisierung fĂŒhren) und Aufspaltungsregeln. Die Vereinfachungsregeln sind eine Vorverarbeitung des Eingangs in polynomieller Zeit. Das Ziel der Anwendung solcher Regeln besteht darin, das Problem auf eine Ă€quivalente, kleinere Version zu reduzieren. Die Anwendung dieser Vereinfachungsregeln ist der arbeitsintensivste Teil des Algorithmus, und die Anwendung genau dieses Teils fĂŒhrt zu einer Gesamtlaufzeit
anstatt zu einfacher polynomieller Zeit. In unserem Fall basieren die Aufspaltungsregeln darauf, dass fĂŒr jeden Knoten entweder dieser oder sein Nachbar gewĂ€hlt werden muss.
Das allgemeine Schema ist folgendes: Wir wenden die Vereinfachungsregeln an, wĂ€hlen dann einen Knoten aus und machen zwei rekursive Aufrufe: im ersten nehmen wir ihn als Antwort, im anderen nehmen wir alle seine Nachbarn. Dies nennen wir das Aufspalten (Branching) ĂŒber diesen Knoten.
In dieses Schema wird im nÀchsten Absatz genau eine ErgÀnzung aufgenommen.
Ideen fĂŒr Aufspaltungsregeln (Branching)
Lassen Sie uns besprechen, wie man einen Knoten wĂ€hlt, ĂŒber den es zu einem Aufspalten kommt.
Die Hauptidee ist algorithmisch sehr gierig: Lassen Sie uns einen Knoten mit dem höchsten Grad nehmen und genau ĂŒber ihn aufspalten. Warum scheint das besser zu sein? Weil wir im zweiten Zweig des rekursiven Aufrufs auf diese Weise sehr viele Knoten eliminieren. Man kann erwarten, dass ein kleiner Graph ĂŒbrig bleibt, und auf diesem werden wir schnell arbeiten.
Dieser Ansatz mit den bereits besprochenen einfachen Kerntechniken zeigt sich als ziemlich effektiv und löst einige Tests mit mehreren Tausend Knoten. Aber zum Beispiel funktioniert er schlecht bei kubischen Graphen (d.h. Graphen, bei denen der Grad jedes Knotens drei betrÀgt).
Es gibt noch eine Idee, die auf einem ziemlich einfachen Gedanken basiert: Wenn der Graph nicht zusammenhĂ€ngend ist, kann das Problem auf seinen Zusammenhangskomponenten unabhĂ€ngig gelöst werden, indem die Antworten am Ende zusammengefĂŒhrt werden. Das ist ĂŒbrigens die kleine versprochene Modifikation im Schema, die die Lösung erheblich beschleunigen wird: FrĂŒher haben wir in einem solchen Fall mit dem Produkt der Zeiten zur Berechnung der Antworten der Komponenten gearbeitet, nun arbeiten wir mit der Summe. Und um das Branching zu beschleunigen, muss man einen zusammenhĂ€ngenden Graph in einen nicht zusammenhĂ€ngenden umwandeln.
Wie macht man das? Wenn im Graphen ein Schnittpunkt vorhanden ist, muss man genau an diesem schneiden. Ein Schnittpunkt ist ein Knoten, dessen Entfernung die ZusammenhÀngigkeit des Graphen beeintrÀchtigt. Alle Schnittpunkte im Graphen kann man mit einem klassischen Algorithmus in linearer Zeit finden. Dieser Ansatz beschleunigt das Schneiden erheblich.

Wenn einer der hervorgehobenen Knoten entfernt wird, zerfÀllt der Graph in ZusammenhÀngende Komponenten.
Das werden wir machen, aber ich möchte mehr. Zum Beispiel kleine Knoten-Schnitte im Graphen suchen und darauf das Schneiden durchfĂŒhren. Der effektivste mir bekannte Weg, einen minimalen globalen Knoten-Schnitt zu finden, ist die Verwendung des Gomory-Hu-Baums, der in kubischer Zeit aufgebaut wird. Im PACE Challenge ist die typische GröĂe des Graphen mehrere tausend Knoten. In diesem Fall mĂŒssten wir in jedem Knoten des Rekursionsbaumes Milliarden von Operationen durchfĂŒhren. Daher ist es einfach unmöglich, die Aufgabe in der vorgegebenen Zeit zu lösen.
Versuchen wir, die Lösung zu optimieren. Den minimalen Knoten-Schnitt zwischen einem Paar von Knoten kann man mit jedem Algorithmus finden, der den maximalen Fluss berechnet. Man kann ein Netzwerk darauf ansetzen , der in der Praxis sehr schnell funktioniert. Ich vermute, dass man theoretisch eine AbschĂ€tzung fĂŒr die Laufzeit beweisen kann
, was bereits sehr akzeptabel ist.
Ich habe mehrmals versucht, Schnitte zwischen Paaren zufĂ€lliger Knoten zu suchen und den ausgewogensten zu nehmen. Leider hat das in den offenen Tests der PACE Challenge schlechte Ergebnisse geliefert. Ich habe ihn mit einem Algorithmus verglichen, der auf den Knoten mit der höchsten Gradzahl basiert, und sie mit einer Begrenzung fĂŒr die Tiefe gestartet. Nach dem Algorithmus, der versucht hat, den Schnitt auf diese Weise zu finden, lagen gröĂere Graphen zurĂŒck. Das hĂ€ngt damit zusammen, dass die Schnitte sehr unausgewogen waren: Wenn ich 5-10 Knoten entfernte, konnte ich nur 15-20 abtrennen.
Es ist anzumerken, dass in Artikeln ĂŒber theoretisch schnellste Algorithmen deutlich fortgeschrittenere Techniken zur Auswahl von Knoten fĂŒr das Schneiden verwendet werden. Diese Techniken haben jedoch eine sehr komplexe Umsetzung und oft schlechte Laufzeit- und SpeicherschĂ€tzungen. Es ist mir nicht gelungen, davon praktikable zu extrahieren.
Anwendung der Vereinfachungsregeln
Wir haben bereits Ideen zur Kernalisierung. Ich erinnere daran:
- Wenn es einen isolierten Knoten gibt, entferne ihn.
- Wenn es einen Knoten der StÀrke 1 gibt, entfernen Sie ihn und nehmen Sie seinen Nachbarn als Antwort.
- Wenn es einen Knoten mit mindestens k + 1, nehmen Sie ihn als Antwort.
Mit den ersten beiden ist alles klar, aber mit dem dritten gibt es einen Trick. Wenn wir in der lustigen Aufgabe ĂŒber die Bar eine obere Grenze fĂŒr k, gegeben haben, mĂŒssen wir in der PACE Challenge einfach eine minimal groĂe Deckung finden. Das ist eine typische Umwandlung von Suchproblemen in Entscheidungsprobleme; oft macht man zwischen den beiden Arten von Aufgaben keinen Unterschied. In der Praxis, wenn wir einen Solver fĂŒr das Deckungsproblem schreiben, kann es einen Unterschied geben. Zum Beispiel, wie im dritten Punkt.
Aus der Sicht der Implementierung kann man auf zwei Arten vorgehen. Der erste Ansatz wird als Iterative Deepening bezeichnet. Er besteht darin, dass wir mit einer vernĂŒnftigen unteren Grenze fĂŒr die Antwort beginnen und dann unseren Algorithmus ausfĂŒhren, wobei wir diese Grenze als obere Grenze fĂŒr die Antwort verwenden, ohne die Rekursion tiefer abzusteigen als diese Grenze. Wenn wir eine Antwort gefunden haben, ist sie garantiert optimal, andernfalls können wir diese Grenze um eins erhöhen und erneut starten.
Der andere Ansatz besteht darin, eine aktuelle optimale Antwort zu speichern und nach einer kleineren Antwort zu suchen und diesen Parameter bei der Auffindung zu Ă€ndern k um ĂŒberflĂŒssige Zweige in der Suche stĂ€rker abzuschneiden.
Nach einigen nĂ€chtlichen Experimenten habe ich mich fĂŒr eine Kombination dieser beiden Methoden entschieden: ZunĂ€chst starte ich meinen Algorithmus mit einer Begrenzung der Suchtiefe (indem ich sie so wĂ€hle, dass sie im Vergleich zur Hauptlösung vernachlĂ€ssigbare Zeit in Anspruch nimmt) und verwende die beste gefundene Lösung als obere Begrenzung fĂŒr die Antwort â also genau fĂŒr die k.
Knoten der StÀrke 2
Mit Knoten der StĂ€rken 0 und 1 sind wir durch. Es stellt sich heraus, dass das auch mit Knoten der StĂ€rke 2 möglich ist, aber dafĂŒr sind komplexere Operationen am Graphen erforderlich.
Um das zu erklĂ€ren, mĂŒssen wir die Knoten irgendwie kennzeichnen. Nennen wir einen Knoten der StĂ€rke 2 einen Knoten v, und seine Nachbarn â Knoten x und y. Dann haben wir zwei FĂ€lle.
- Wenn x und y â Nachbarn. Dann kann man als Antwort nehmen x und y, und v entfernen. Und in der Tat, aus diesem Dreieck mĂŒssen mindestens zwei Knoten in die Antwort aufgenommen werden, und wir verlieren definitiv nicht, wenn wir nehmen x und y: Sie haben wahrscheinlich noch Nachbarn, wĂ€hrend bei v diese nicht vorhanden sind.
- Wenn x und y â keine Nachbarn. Dann wird behauptet, dass alle drei Eckpunkte zu einem zusammengefĂŒgt werden können. Die Idee ist, dass in diesem Fall eine optimale Antwort vorliegt, in der wir entweder v, oder beide Eckpunkte x und ynehmen mĂŒssen. Im ersten Fall mĂŒssen wir alle Nachbarn in die Antwort aufnehmen x und y, im zweiten Fall ist das nicht unbedingt erforderlich. Dies entspricht genau den FĂ€llen, in denen wir den zusammengefĂŒgten Eckpunkt nicht in die Antwort aufnehmen und wenn wir es tun. Es bleibt nur zu bemerken, dass in beiden FĂ€llen die Antwort durch diese Operation um eins verringert wird.

Es ist erwĂ€hnenswert, dass es ziemlich schwierig ist, einen solchen Ansatz in ehrlicher linearer Zeit sorgfĂ€ltig zu implementieren. Das ZusammenfĂŒgen von Eckpunkten ist eine komplexe Operation, es mĂŒssen Nachbarlisten kopiert werden. Wenn dies nicht sorgfĂ€ltig geschieht, kann es zu asymptotisch suboptimalen Laufzeiten kommen (zum Beispiel, wenn nach jeder ZusammenfĂŒgung viele Kanten kopiert werden). Ich konzentrierte mich auf die Suche nach vollstĂ€ndigen Wegen aus Eckpunkten der Ordnung 2 und die Analyse vieler spezieller FĂ€lle, wie z. B. Zyklen aus solchen Eckpunkten oder aus allen solchen Eckpunkten auĂer einem.
DarĂŒber hinaus muss dieser Vorgang umkehrbar sein, damit wir wĂ€hrend der RĂŒckkehr aus der Rekursion den Graphen in seinen ursprĂŒnglichen Zustand wiederherstellen können. Um dies zu gewĂ€hrleisten, habe ich die Kantenlisten der zusammengefĂŒgten Eckpunkte nicht gelöscht; danach wusste ich einfach, wohin die Kanten gerichtet werden mĂŒssen. Eine solche Implementierung von Grafen erfordert ebenfalls Sorgfalt, bietet jedoch ehrliche lineare Zeit. Und fĂŒr Grafen mit mehreren Zehntausend Kanten passt es gut in den Cache des Prozessors, was Geschwindigkeitsvorteile bietet.
Lineares Kern
SchlieĂlich der interessanteste Teil des Kerns.
ZunÀchst erinnern wir uns daran, dass in bipartiten Grafen das minimale Vertex-Deckung in
gesucht werden kann. Dazu muss der Algorithmus verwendet werden, um dort das maximale Paarung zu finden, und dann das Theorem .
anzuwenden. Die Idee des linearen Kerns ist folgende: ZunĂ€chst teilen wir den Graphen auf, d.h. anstelle jedes Eckpunkts v fĂŒhren wir zwei Eckpunkte ein,
und
und anstelle jeder Kante u â v fĂŒhren wir zwei Kanten ein.
und
Der erhaltene Graph wird bipartit sein. Wir werden darin das minimale Knotencover finden. Einige Knoten des ursprĂŒnglichen Graphen werden dort zweimal erscheinen, einige nur einmal und einige gar nicht. Der Satz von Nemhauser-Trotter besagt, dass wir in diesem Fall Knoten, die kein einziges Mal vorkommen, entfernen können und die diejenigen als Antwort nehmen, die zweimal vorkommen. DarĂŒber hinaus sagt er, dass von den verbleibenden Knoten (denjenigen, die einmal vorkommen) mindestens die HĂ€lfte als Antwort genommen werden sollte.
Gerade haben wir gelernt, im Graphen nicht mehr als 2k Knoten zu belassen. Es stimmt, wenn in der Ăberschussantwort mindestens die HĂ€lfte aller Knoten vorhanden ist, dann sind es insgesamt nicht mehr als 2k.
Hier ist mir ein kleiner Schritt vorwĂ€rts gelungen. Es ist klar, dass der auf diese Weise konstruierte Kern davon abhĂ€ngt, welches minimale Knotencover im bipartiten Graphen wir gewĂ€hlt haben. Ich möchte eines wĂ€hlen, bei dem die Anzahl der verbleibenden Knoten minimal ist. FrĂŒher konnte man das nur in einer Zeit von
. Ich habe jedoch eine Implementierung dieses Algorithmus in einer Zeit von
erfunden, sodass dieser Kern in Graphen mit Hunderttausenden von Knoten in jeder Phase des Branching gesucht werden kann.
Ergebnis
Die Praxis zeigt, dass meine Lösung in Tests mit mehreren Hundert Knoten und einigen Tausend Kanten gut funktioniert. Bei solchen Tests kann man ganz gut erwarten, dass die Lösung innerhalb einer halben Stunde gefunden wird. Die Wahrscheinlichkeit, dass die Antwort in akzeptabler Zeit gefunden wird, steigt grundsĂ€tzlich, wenn der Graph genĂŒgend viele Knoten hohen Grades hat, zum Beispiel Grad 10 und höher.
Um am Wettbewerb teilzunehmen, mussten die Lösungen bis zu eingereicht werden. Anhand der dort vorgestellten nimmt meine Lösung in den offenen Tests den dritten Platz von zwanzig mit groĂem Abstand zum zweiten ein. Um ganz ehrlich zu sein, ist es nicht ganz klar, wie die Lösungen im Wettbewerb selbst bewertet werden: Zum Beispiel besteht meine Lösung aus weniger Tests als die Lösung auf dem vierten Platz, aber sie funktioniert bei denen, die sie besteht, schneller.
Die Ergebnisse der geschlossenen Tests werden am ersten Juli bekannt gegeben.
Quelle: habr.com
