Ich grüble derzeit über eine kleine Programmieraufgabe, wo ich mir ziemlich sicher bin dass sie sich mithilfe dynamischer Programmierung lösen lässt und das auch nicht zu schwierig, aber ich scheitere daran die richtige Rekursion dafür aufzustellen. Vielleicht mag ja jemand mit einsteigen. Problem: Es handelt sich um ein 2-Spieler Spiel. Gegeben ist eine natürliche Zahl n (n <= 2.000.000.000). Das Spiel beginnt mit k = 1 und die Spieler (beginnend mit Spieler 1) können die Zahl k entweder mit 2 oder 9 multiplizieren (nach jeder Multiplikation ist der jeweils andere Spieler dran). Wer zuerst die Zahl n erreicht oder überschreitet gewinnt das Spiel. Nun soll man zu einem gegebenen n den Gewinner ermitteln. Meine Ideen bisher: - Wenn eine Strategie für P1 existiert die nicht von P2s Entscheidungen abhängt gewinnt P1, ansonsten P2. - Wer den anderen dazu bringen kann auf n/9 zu kommen, gewinnt das Spiel da er dann nur noch mit 9 multiplizieren muss. - Den aktuellen Zustand kodiere ich mit S(i,j) = 2^i * 9^j ist also wohl ein 2-dimensionales Problem - Wenn S(i,j+1) >= n, gewinnt der aktuelle Spieler, ansonsten muss ich die bessere Wahl zwischen S(i+1,j) oder S(i,j+1) treffen und das ist der Punkt an dem ich scheitere Hat jemand Input für mich?
Ich bin gerade zu faul zum nachdenken, deshalb ein ähnliches Beispiel. Zwei Spieler und es liegen 20 Streichhölzer auf dem Tisch. Sie nehmen abwechselnd 1, 2 oder 3 Streichhölzer weg. Wer das letzte nehmen muss hat verloren. Das ist das gleiche mit Addition und runterzählen und schon allein dadurch viel einfacher. Pro Runde können 2 bis 6 Hölzer weggenommen werden. Der clevere Spieler sorgt dafür, dass es immer 4 sind. Vielleich tkommst du ja damit weiter.
Jo das ist die einfache Version die mir auch schonmal in den Sinn gekommen ist. Leider hat es für den zündenden Funken nich nicht gereicht. Im Wesentlichen kann man sich das ja wie ein Schachbrett vorstellen. Landet man auf einem "schwarzen" Feld dass >= n ist gewinnt der eine Spieler, ist es "weiß" der andere. Der zweite Spieler hat immer nur die Möglichkeit in einer die Runde die vom ersten Spieler vorgegebene richtung beizubehalten oder sie Richtung Diagonale wieder auszugleichen. Mh...
Hallo, das kann man tatsächlich auf das Streichholzspiel zurückführen, einfach logarithmieren. Also als Startwert log(Startzahl) und jeder darf entweder log(2) oder log(9) abziehen. Wer Null erreicht oder einen negativen Wert, gewinnt. Eine Gewinnstellung ist dann die Zahl log(2)+log(9), egal, was der Gegner macht, er erreicht nicht das Ende, man selber danach aber schon. Um diese Stellung sicher zu erreichen, ist 2*(log(2)+log(9)) auch eine Gewinnstellung usw. Also sollte man im Originalspiel n/18 erreichen, vorher n/(18*18), n/(18*18*18) usw. Sind die genauen Zahlen keine ganzen Zahlen, ist die nächst höhere anzustreben. Viel interessanter ist dagegen das NIM-Spiel, mehrere Haufen Streichhölzer (oder Münzen oder sonst was), von einem dieser Haufen kann man soviel wegnehmen, wie man will, mindestens eins und maximal alle. Wer das letzte vom letzten Haufen nehmen muß, verliert. Für ein Computerprogramm ist die Sache extrem einfach, aber man muß erst einmal auf die Strategie kommen... Gruß Andy_W
Narf, manchmal sieht man den Baum vor lauter Wäldern nicht. Es ist eigentlich ganz einfach, die Rekursion aufzubauen: Entweder ich kann mit dem nächsten Zug gewinnen oder der nächste Zug sorgt dafür dass der andere nicht gewinnt. Wenn beides nicht zutrifft gewinnt der andere. Ich hab das mal in ein kleines Ruby-Programm gegossen. Ich denke das sollte passen, was meint ihr?
1 | @table = nil |
2 | |
3 | def rec(i, j, n) |
4 | return @table[i][j] if !@table[i][j].nil? |
5 | |
6 | if (2**i) * (9**j) * 9 >= n #can we win now? |
7 | @table[i][j] = true |
8 | else #can we make a move thus the other player doesn't win? |
9 | @table[i][j] = !rec(i+1, j, n) || !rec(i, j+1, n) |
10 | end |
11 | return @table[i][j] |
12 | end |
13 | |
14 | def first_player_wins?(n) |
15 | @table = Array.new(100) { Array.new(100) { nil } } |
16 | return rec(0, 0, n) |
17 | end |
18 | |
19 | n = ARGV[0].to_i |
20 | puts (first_player_wins?(n) ? "Spieler 1 gewinnt" : "Spieler 2 gewinnt") |
Wenn folgender Ausdruck eine gerade Zahl ergibt, gewinnt der erste Spieler, sonst der zweite:
Für 2≤n≤9 gewinnt der erste Spieler, und für n=1 ist es eine Frage der Definition, da das Spiel ja endet, bevor überhaupt jemand einen Zug gemacht hat. Ich würde sagen, der zweite Spieler gewinnt in diesem Fall. Man sollte allerdings die Logarithmen nicht mit FP-Funktionen berechnen, da durch die Aufrundfunktion schon kleine Rundungsfehler zu falschen Ergebnissen führen. Besser ist es, ganzzahlige Potenzen in einer Schlei- fe hochzuzählen, bis das Argument erreicht oder überschritten ist, z.B. so:
1 | int gewinner(unsigned int n) { |
2 | unsigned long long n4 = 4ULL*n, n36=36ULL*n, p; |
3 | int i, ret; |
4 | |
5 | if(n == 1) |
6 | ret = 2; |
7 | else if(n < 10) |
8 | ret = 1; |
9 | else { |
10 | p = 1; |
11 | while(p < n4) |
12 | p *= 18; |
13 | i = 0; |
14 | while(p < n36) { |
15 | p *= 2; |
16 | i++; |
17 | }
|
18 | ret = i&1 ? 2 : 1; |
19 | }
|
20 | return ret; |
21 | }
|
Die Spielstrategie: Wenn es für die aktuelle Zahl k ein i gibt, so dass
dann multipliziere die Zahl mit 9, sonst mit 2. Vielleicht gibt es etwas einfacheres, mir ist aber nichts eingefallen D. I. schrieb: > Ich hab das mal in ein kleines Ruby-Programm gegossen. Ich denke das > sollte passen, was meint ihr? Das scheint mit meinen Ergebnissen zusammenzupassen:
1 | von bis Gewinner |
2 | —————————————————————————————————— |
3 | 1 1 2 |
4 | 2 9 1 |
5 | 10 18 2 |
6 | 19 36 1 |
7 | 37 72 2 |
8 | 73 162 1 |
9 | 163 324 2 |
10 | 325 648 1 |
11 | 649 1296 2 |
12 | 1297 2916 1 |
13 | 2917 5832 2 |
14 | 5833 11664 1 |
15 | 11665 23328 2 |
16 | 23329 52488 1 |
17 | 52489 104976 2 |
18 | 104977 209952 1 |
19 | 209953 419904 2 |
20 | 419905 944784 1 |
21 | 944785 1889568 2 |
22 | 1889569 3779136 1 |
23 | 3779137 7558272 2 |
24 | 7558273 17006112 1 |
25 | 17006113 34012224 2 |
26 | 34012225 68024448 1 |
27 | 68024449 136048896 2 |
28 | 136048897 306110016 1 |
29 | 306110017 612220032 2 |
30 | 612220033 1224440064 1 |
31 | 1224440065 2448880128 2 |
32 | 2448880129 4294967295 1 |
33 | —————————————————————————————————— |
Das Ganze geht auch mit dynamischer Programmierung, wie du es ursprüng- lich vorgehabt hast. Du macht eine Tabelle mit allen möglichen Zahlen- werten
1 | | 9⁰ 9¹ 9² 9³ ... |
2 | ——————————————————————————— |
3 | 2⁰ | 1 9 81 729 ... |
4 | | |
5 | 2¹ | 2 18 162 1458 ... |
6 | | |
7 | 2² | 4 36 324 2916 ... |
8 | | |
9 | 2³ | 8 72 648 5832 ... |
10 | : | : : : : |
und markierst alle Zahlen, die größer oder gleich n sind, als "Gewinn- zahlen". Danach werden die restlichen Zahlen von rechts nach links und von unten nach oben ebenfalls als Gewinn- oder Verlustzahlen markiert: Eine Zahl ist genau dann eine Gewinnzahl, wenn der rechte und der untere Nachbar beide Verlustzahlen sind. Als letztes wird die 1 markiert. Ist sie eine Gewinnzahl, gewinnt der zweite Spieler, sonst der erste. Letztendlich ist das genau das gleiche wie dein rekursiver Algorithmus, nur von hinten aufgezäumt und eben nicht rekursiv.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.