Considérons un graphique quadrillé n par n qui ressemble à ceci.
Il est important de noter que ce graphique est 11 par 11 .
À un moment donné, un homme se tient à une intersection et il ne se déplace que verticalement ou horizontalement d'un pas à la fois jusqu'à la prochaine intersection. Malheureusement, il a un peu trop bu alors il choisit la direction dans laquelle il se déplace au hasard parmi les 4 directions possibles (haut, bas, gauche, droite). Cela peut aller jusqu'à 4 comme s'il se tenait contre un mur, il n'a bien sûr que 3 options et dans un coin, il n'en a que 2.
Il commence dans le coin inférieur gauche et son objectif est de rentrer à la maison qui est le coin supérieur droit. Le temps est simplement le nombre de pas qu'il lui faut.
Cependant, vous êtes un adversaire malveillant qui veut qu'il rentre le plus lentement possible. Vous pouvez supprimer n'importe quel nombre d'arêtes du graphique à tout moment pendant sa marche. La seule restriction est que vous devez toujours lui laisser un moyen de rentrer à la maison et vous ne pouvez pas supprimer un bord qu'il a déjà utilisé.
Le défi consiste à imaginer un adversaire aussi malveillant que possible, puis à le tester sur un graphique
100x10020 x 20 avec un marcheur ivre au hasard. Votre score est simplement le temps moyen qu'il faut au marcheur aléatoire pour rentrer à la maison sur101000 courses.
Vous pouvez utiliser n'importe quelle langue et bibliothèque que vous aimez tant qu'elles sont librement disponibles et facilement installables sous Linux.
De quoi ai-je besoin pour mettre en œuvre?
Vous devez implémenter du code pour le marcheur aléatoire et également pour l'adversaire et le code doit être combiné de sorte que la sortie lors de l'exécution soit simplement la moyenne de 1000 exécutions en utilisant votre code d'adversaire. Le code du marcheur aléatoire devrait être très simple à écrire car il choisit simplement parmi (x-1, y), (x + 1, y), (x, y-1) et (x, y + 1) en s'assurant que aucun de ceux-ci n'a été supprimé ou est hors de portée.
Le code de l'adversaire est bien sûr plus difficile et doit également se souvenir des bords que l'ivrogne a déjà traversés pour qu'il n'essaie pas de supprimer l'un d'eux et de s'assurer qu'il y a toujours un chemin de retour pour l'ivrogne, ce qui est un peu plus délicat. faire vite.
L'addendum 10 ne suffit pas, mais je ne voulais pas punir les gens qui ont réussi à faire de très longues promenades. Je l'ai maintenant augmenté à 1000 en raison d'une demande populaire. Cependant, si votre marche est si longue que vous ne pouvez pas faire 1000 pistes en un temps réaliste, veuillez simplement indiquer le nombre maximum de pistes que vous pouvez.
Tableau des meilleurs scores pour 100 par 100.
- 976124.754 par Optimizer.
- 103000363.218 par Peter Taylor.
Modifier 1. Changement de la taille du graphique à 20 par 20 pour aider à la durée des tests des personnes. Je ferai un nouveau score de table élevé pour cette taille lorsque les gens soumettront les scores.
Tableau des meilleurs scores pour 20 par 20.
230,794.38 (100k runs) by justhalf
227,934 by Sparr
213,000 (approx) by Peter Taylor
199,094.3 by stokastic
188,000 (approx) by James_pic
64,281 by Geobits
Réponses:
230 794,38 sur 20x20, 100 000 courses
Dernière mise à jour: j'ai finalement créé une solution dynamique parfaite à 2 voies. J'ai dit parfait car la version précédente n'est en fait pas symétrique, il était plus facile d'obtenir un chemin plus long si l'ivrogne a pris un chemin par rapport à l'autre. L'actuel est symétrique, il peut donc obtenir un plus grand nombre attendu d'étapes. Après quelques essais, il semble être d'environ 230k, une amélioration par rapport au précédent qui est d'environ 228k. Mais statistiquement parlant, ces chiffres sont toujours dans leur écart énorme, donc je ne prétends pas que ce soit nettement mieux, mais je pense que cela devrait être mieux que la version précédente.
Le code est au bas de cet article. Il est mis à jour pour être beaucoup plus rapide que la version précédente, effectuant 1000 exécutions en 23 secondes.
Ci-dessous, un échantillon et un labyrinthe d'échantillons:
Soumissions précédentes
Enfin, je peux égaler le résultat de Sparr! = D
Sur la base de mes expériences précédentes (voir en bas de cet article), la meilleure stratégie consiste à avoir un double chemin et à en fermer un lorsque l'ivrogne atteint l'un d'eux, et la variable vient de la façon dont nous pouvons prédire dynamiquement où l'ivrogne ira. augmenter les chances de lui de prendre un chemin plus long.
Donc, basé sur ma
DOUBLE_PATH
stratégie, j'en ai construit un autre, qui change le labyrinthe (monDOUBLE_PATH
labyrinthe était facilement modifiable) en fonction du mouvement de l'ivrogne. Comme il emprunte un chemin avec plus d'une option disponible, je vais fermer les chemins afin de ne laisser que deux options possibles (dont une d'où il vient, une autre non explorée).Cela ressemble à ce que Sparr a réalisé, comme le montre le résultat. La différence avec la sienne est trop petite pour qu'elle soit considérée comme meilleure, mais je dirais que mon approche est plus dynamique que lui, car mon labyrinthe est plus modifiable que celui de Sparr =)
Le résultat avec un échantillon de labyrinthe final:
Section Expériences
Le meilleur se révèle être la même stratégie que stokastic, je suis fier d'expérimenter en utilisant différentes stratégies et d'imprimer de belles sorties :)
Chacun des labyrinthes imprimés ci-dessous est le dernier labyrinthe après que l'ivrogne est arrivé à la maison, ils peuvent donc être légèrement différents d'une course à l'autre en raison du caractère aléatoire du mouvement de l'ivrogne et de la dinamicité de l'adversaire.
Je décrirai chaque stratégie:
Chemin unique
Il s'agit de l'approche la plus simple, qui créera un chemin unique de l'entrée à la sortie.
Île (niveau 0)
Il s'agit d'une approche qui tente de piéger l'ivrogne dans une île presque isolée. Ne fonctionne pas aussi bien que prévu, mais c'est une de mes premières idées, donc je l'inclus.
Il y a deux chemins menant à la sortie, et lorsque l'ivrogne se rapproche de l'un d'eux, l'adversaire la ferme, le forçant à trouver l'autre sortie (et peut-être à nouveau pris au piège dans l'île)
Chemin double
C'est la stratégie la plus discutée, qui consiste à avoir deux chemins de longueur égale vers la sortie et à fermer l'un d'eux lorsque l'ivrogne se rapproche de l'un d'eux.
Île (niveau 1)
Inspiré par les multiples chemins de l'île et le nombre élevé de promenades en un seul chemin, nous connectons l'île à la sortie et faisons un labyrinthe à un seul chemin dans l'île, créant au total trois chemins pour sortir, et comme dans le cas précédent, fermez l'un des sortir quand l'ivrogne s'approche.
Cela fonctionne légèrement mieux que le chemin simple pur, mais ne bat toujours pas le chemin double.
Île (niveau 2)
En essayant d'élargir l'idée précédente, j'ai créé une île imbriquée, créant au total cinq chemins, mais cela ne semble pas bien fonctionner.
Île (niveau 3)
Remarquant que le double chemin fonctionne mieux que le chemin simple, faisons l'île en double chemin!
Le résultat est une amélioration par rapport à Island (niveau 1), mais il ne bat toujours pas le double chemin pur.
A titre de comparaison, le résultat du double trajet de la taille de l'île est de 131 134,42 mouvements en moyenne. Cela ajoute donc un nombre assez important de mouvements (environ 40k), mais pas assez pour battre le double chemin.
Île (niveau 4)
Encore une fois, expérimenter avec une île imbriquée, et encore une fois, cela ne fonctionne pas si bien.
Conclusion
Dans l'ensemble, cela prouve que le fait d'avoir un seul chemin long de la position actuelle de l'ivrogne à la sortie fonctionne mieux, ce qui est réalisé par la stratégie à double chemin, car après avoir fermé une sortie, l'ivrogne devra parcourir la distance maximale possible pour se rendre à La sortie.
Cela suggère en outre que la stratégie de base doit toujours être à double chemin, et nous ne pouvons que modifier la façon dont les chemins sont créés, ce qui a été fait par Sparr. Je crois donc que sa stratégie est la voie à suivre!
Code
la source
227934 (20x20)
Ma troisième tentative. Utilise la même approche générale que @stokastic avec deux chemins vers la sortie. Lorsque le promeneur atteint la fin d'un chemin, il se ferme, l'obligeant à revenir pour se rendre à la fin de l'autre chemin pour sortir. Mon amélioration consiste à générer les chemins au fur et à mesure que le marcheur progresse, de sorte que le chemin qu'il progresse plus loin dans la première moitié du processus finira par être plus long que l'autre chemin.
sortie (avec le temps):
exemple de mon labyrinthe, avec des longueurs à peu près égales au chemin, montrant le chemin gauche / inférieur coupé de la sortie (en bas à droite):
PS: Je suis conscient d'une amélioration très mineure de cet algorithme qui nécessite un code plus intelligent pour générer une forme différente pour les deux chemins, des escaliers au lieu de zig zags à hauteur constante.
la source
135.488.307,9 pour 98x98
199094.3 pour 20x20
J'ai mis en œuvre une solution qui crée deux chemins vers la fin et ferme exactement l'un d'eux une fois que l'ivrogne l'atteint. Cela simule une longueur de chemin qui, à tout le moins, sera 1,5 fois la longueur d'un chemin unique du début à la fin. Après 27 runs, j'ai atteint une moyenne d'environ 135 millions. Malheureusement, cela prend plusieurs minutes par marche, je devrai donc le faire fonctionner pendant les prochaines heures. Une mise en garde - mon générateur de double chemin ne fonctionne que si la taille du graphique est sous la forme 4 * n + 2, ce qui signifie que le plus proche possible de 100 est 102 ou 98. Je vais publier des résultats en utilisant 98, ce à quoi je m'attends pour toujours dépasser la méthode du chemin en zigzag. Je travaillerai plus tard sur un meilleur système de cheminement. Affiche actuellement les résultats sous la forme (numSteps, currentAverage) après chaque promenade.
EDIT: corrigé pour que le code fonctionne désormais sur des tailles de graphique qui sont un multiple de 2, plutôt que 4 * n + 2.
Code: (ajoutez l'argument 'True' au constructeur de marcheur à la ligne 187 pour le dessin de tortue du graphique).
Données brutes: (numSteps actuel, moyenne courante)
la source
Approche à 4 voies, 213k
L'approche à sens unique est
et marque une moyenne de
N^2
.L'approche à deux voies est
mais la première fois que l'ivrogne arrive à portée du point final, il est coupé:
Il marque une moyenne de
(N/2)^2 + N^2
.L'approche à quatre voies utilise deux coupes:
Supposons que la boucle externe est de longueur
xN
et la boucle interne de longueur(1-x)N
. Pour plus de simplicité, je vais normaliserN=1
.Du début à la première coupe, la moyenne est de
(x/2)^2
. De la première coupe à la deuxième coupe a deux options, de longueursx
et1-x
; cela donne une moyenne de(1-x)x^2 + x(1-x)^2 = x-x^2
. Enfin le chemin restant donne1
. Donc, le score total estN^2 (1 + x - 3/4 x^2)
.J'ai d'abord supposé que garder les chemins disponibles de longueur égale à chaque étape serait optimal, donc mon approche initiale a utilisé
x = 1/2
un score de1.3125 N^2
. Mais après avoir fait l'analyse ci-dessus, il s'avère que la répartition optimale est donnéex = 2/3
avec le score1.3333 N^2
.avec code
la source
f
àc
dans votre code est d'environN/2
, que ce soit pare
(etd
) ou non, non?N^2
pas2^N
. Et oui, rendre cette dynamique la rendra meilleure, le défi est de savoir comment la rendre dynamique tout en conservant la propriété à quatre chemins. @PeterTaylor: Belles images!J'ai essayé de découper la grille presque entièrement à travers tous les
k
. Ce qu'il convertit efficacement en quelque chose de semblable à une marche aléatoire sur unk
parN * N/k
grille. L'option la plus efficace consiste à découper chaque ligne afin de forcer l'ivrogne à zigzaguer.Pour le boîtier 20x20 (
SIZE=19
) j'aiavec code
la source
Pour ceux qui ne veulent pas réinventer la roue
Ne t'inquiète pas! Je vais le réinventer pour vous :)
C'est en Java, soit dit en passant.
J'ai créé une classe Walker qui traite de la marche au hasard. Il comprend également une méthode utile pour déterminer si un mouvement est valide (s'il a déjà été suivi).
Je suppose que vous tous intelligents pouvez trouver des chiffres aléatoires pour le constructeur, je vous laisse le soin de tester certains cas. Aussi, appelez simplement la fonction walk () pour (vous l'avez deviné!) Faire marcher l'ivrogne (au hasard).
J'implémenterai la fonction canComeHome () une autre fois. De préférence, après avoir recherché la meilleure façon de le faire.
la source
previouslyTraveled.add(new int[]{x,y,move[0],move[1]})
devrait êtrex+move[0]
ety+move[1]
. LeWidth-1
etHeight-1
, et l'inefficacité dans la vérification des chemins supprimés. J'ai édité votre code (avec une fonction supplémentaire pour imprimer le labyrinthe). N'hésitez pas à revenir en arrière si vous pensez que c'est inapproprié.Edge
n'implémente pas correctementComparable<Edge>
. Si vous souhaitez que les arêtes soient égales, même si vous les inversez, vous devez également prendre en compte l'inversion dans le cas différent. La façon la plus simple de procéder serait de changer le constructeur pour garder les points ordonnés.Comparable
?A
etB
sont le même bord inversé etC
différent, vous pouvez obtenirA.compareTo(B) == B.compareTo(A) == 0
maisA.compareTo(C) < 0
etB.compareTo(C) > 0
.canComeHome()
)64,281
Mise à jour depuis la modification de la grille de 100x100 à 20x20 (1000 tests). Le score sur 100x100 (100 tests) était d'environ 36M.
Bien que cela ne puisse pas battre une marche 1D, je voulais jouer avec une idée que j'avais.
L'idée de base est que la grille est divisée en pièces carrées, avec un seul chemin menant «à la maison» de chacune. Le chemin est ouvert selon l'état d' ébriété se rapproche de la dernière , ce qui signifie qu'il doit explorer toutes les sorties possibles, seulement pour tous , mais l' un d'eux a claqué dans son visage.
Après avoir joué avec la taille des pièces, je suis arrivé à la même conclusion que Peter, le couper plus petit est mieux. Les meilleurs scores viennent avec une taille de chambre de 2.
Le code est bâclé, ne vous occupez pas du désordre. Vous pouvez
SHOW
actionner l' interrupteur et il affichera une image des chemins à chaqueSHOW_INT
étape afin que vous puissiez le regarder en action. Une exécution terminée ressemble à quelque chose comme:(Il s'agit de l'image de la grille précédente de 100x100. 20x20 est exactement comme ça, mais, bien, plus petit. Le code ci-dessous a été mis à jour pour de nouvelles tailles / exécutions.)
la source
188k, avec 2 chemins
Les meilleures entrées semblent toutes adopter l'approche consistant à générer 2 chemins, puis à en couper un lorsque l'ivrogne approche de la fin du chemin. Je ne pense pas que je peux battre l'entrée de moitié seulement, mais je ne pouvais pas m'empêcher de me demander: Pourquoi 2 chemins? Pourquoi pas 3, 5 ou 20?
TL; DR : 2 chemins semblent être optimaux
J'ai donc fait une expérience. Basé sur le framework Stretch Maniac, j'ai écrit une entrée pour tester différents nombres de chemins. Vous pouvez modifier le
featureSize
paramètre pour faire varier le nombre de chemins. UnfeatureSize
de 20 donne 1 chemin, 10 donne 2 chemins, 7 donne 3, 5 donne 4, etc.Il y a quelques optimisations que je pourrais faire mais que je n'ai pas faites, et cela ne prend en charge aucune des ruses adaptatives que la moitié utilise.
Quoi qu'il en soit, voici les résultats pour différentes
featureSize
valeurs:Et voici une carte avec 3 chemins:
la source
N
soit la longueur du chemin (qui estn^2-1
), le chemin unique nécessitera en moyenne desN^2
déplacements, tandis que les trois chemins(N/3)^2 + (2N/3)^2 + (2N/3)^2 = N^2
plus une valeur relativement petite, donc trois chemins n'a pas de gain significatif sur un seul chemin, encore moins double chemin. (Le calcul est basé sur le résultat de probabilité qui indique que le déplacement aléatoire sur la voie 1-D de longueurN
nécessite en moyenne deN^2
déplacement d'une extrémité à l'autre.)131k (20x20)
Ma première tentative a été de supprimer tous les bords horizontaux à l'exception des rangées supérieure et inférieure, puis chaque fois que le promeneur atteignait le bas d'une colonne, je retirais le bord devant lui, jusqu'à ce qu'il ait visité le bas de chaque colonne et enfin pouvoir atteindre la sortie. Cela s'est traduit par une moyenne de 1/8 d'autant d'étapes que l'approche de marche 1d de @ PeterTaylor.
Ensuite, j'ai décidé d'essayer quelque chose d'un peu plus détourné. J'ai divisé le labyrinthe en une série de chevrons creux imbriqués et je lui demande de parcourir le périmètre de chaque chevron au moins 1,5 fois. Cela a une durée moyenne d'environ 131k pas.
la source
Ne fais rien
Étant donné que l'homme se déplace au hasard, on pourrait penser que la suppression d'un nœud n'augmentera que ses chances de rentrer chez lui à long terme.
Tout d'abord, examinons le cas unidimensionnel, cela peut être réalisé en supprimant les nœuds jusqu'à ce que vous vous retrouviez avec un chemin ondulé, sans délais ni cycles, qui visite (presque) chaque point de grille. Sur une
N x N
grille, la longueur maximale d'un tel chemin estL = N*N - 2 + N%2
(98 pour une grille 10x10). Marcher le long du chemin peut être décrit par une matrice de transition générée parT1d
.(La légère asymétrie rend difficile la recherche d'une solution analytique, sauf pour les matrices très petites ou infinies, mais nous obtenons une solution numérique plus rapidement qu'il ne faudrait de toute façon pour diagonaliser la matrice).
Le vecteur d'état a un seul
1
à la position de départ et après lesK
étapes(T1d**K) * state
nous donne la distribution de probabilité d'être à une certaine distance du départ (ce qui équivaut à faire la moyenne de toutes2**K
les promenades possibles le long du chemin!)Exécuter la simulation pour les
10*L**2
étapes et enregistrer le dernier élément du vecteur d'état après chaque étape, ce qui nous donne la probabilité d'avoir atteint l'objectif après un certain nombre total d'étapes - la distribution de probabilité cumuléecd(t)
. La différencier nous donne la probabilitép
d'atteindre l'objectif exactement à un certain moment. Pour trouver le temps moyen que nous intégronst*p(t) dt
Le temps moyen pour atteindre l'objectif est proportionnel à
L**2
avec un facteur qui va très vite à 1. L'écart type est presque constant à environ 79% du temps moyen.Ce graphique montre le temps moyen pour atteindre l'objectif pour différentes longueurs de trajet (correspondant à des tailles de grille de 5x5 à 15x15)
Voici à quoi ressemble la probabilité d'atteindre l'objectif. La deuxième courbe semble remplie car à chaque pas de temps impair, la position est étrange et ne peut donc pas être au but.
De cela, nous pouvons voir que la stratégie à double voie équilibrée fonctionne mieux ici. Pour les grilles plus grandes, où les frais généraux liés à la création de plus de chemins sont négligeables par rapport à leur taille, il serait préférable d'augmenter le nombre de chemins, de la même manière que Peter Taylor l'a décrit, mais en gardant les longueurs équilibrées.
Et si nous ne supprimons aucun nœud?
Nous aurions alors deux fois plus de nœuds praticables, plus quatre directions possibles au lieu de deux. Il semblerait que cela rend très peu probable que vous arriviez à quelque chose. Cependant, les simulations montrent le contraire, après seulement 100 étapes sur une grille 10x10, l'homme est très susceptible d'atteindre son objectif, donc le piéger dans les îles est une tentative futile, car vous traitez un
N**2
long chemin sinueux potentiel avec un temps d'achèvement moyen deN**4
pour une île qui passe parN**2
étapesla source