Initially, n ≥ 2 chips are on a table. Two players alternate taking some chips off. The player who takes the last chip wins.
Rules:
The first player takes i,
where 1 ≤ i ≤ n-1.
Thereafter, if a player takes p then the next play is q,
where 1 ≤ q ≤ 2p.
There are no draws possible, as each move reduces the numebr of reminaing chips. The question is how to characterize those values of n for which the first player cam ensure a win.
A position in this game is not specified merely by the number of chips on the table, but also by the upper limit on the next move.
The initial position = <n, n-1>
A general move: <i, j>
→
(1 ≤ k ≤ j)
<i-k, min{2k, i-k}>
Receiving position <0,0> means that the player loses.
Each position can be characterized as winning or losing. Winning means that, despite what your opponent may do, if you are clever then you will win. Losing means that, unless your opponent should do something stupid, you will lose.
The following are three functionally equivalent procedures that determine whether <n, p> is winning, and which differ by their time complexity.
Recursion, suffers from recalculating some values many times.
Rec(i,j)
ans := LOSE
for k := 1 to j do
if Rec(i-k, min{2k,i-k})=LOSE then
ans := WIN
break
return ans
Dynamic programming, suffers from calculating some values that are never needed.
Dyn(n,p)
G[0,0] := LOSE
for i := 1 to n do
for j := 1 to i do
G[i,j] := LOSE
for k := 1 to j do
if G[i-k, min{2k,i-k}]=LOSE then
G[i,j] := WIN
break
return G[n,p]
Recursion with memory function, takes the advantages and avoids the disadvantages.
Nim(i,j)
initialize G[*,*] to Unknown
/** can avoid O(ij) time at cost of extra space **/
return Nim1(i,j)
Nim1(i,j)
if G[i,j] is not Unknown then
return G[i,j]
for k := 1 to j do
if Nim1(i-k, min{2k,i-k})=LOSE then
G[i,j] := WIN
return WIN
G[i,j] := LOSE
return LOSE