Approach (brief): Model each square i as a state with expected turns E[i]. For non-terminal states:E[i] = 1 + (1/6) * sum_{roll=1..6} E[next(i, roll)]If next(i,roll) goes past final square (N-1) treat as absorbing: E[final]=0. This is a linear system (I - P)E = 1-vector. Solve using iterative linear solver (Gauss-Seidel / SOR) which scales to N≈1000 and exploits sparsity. Check for absorbing classes that never reach the final state — then expectation is infinite for states in those classes.Python implementation (Gauss-Seidel with SOR, fallback to numpy for small N):python
import math
import random
def expected_turns(N, jump, max_iters=200000, tol=1e-9, omega=1.2):
# N: number of squares, indexed 0..N-1. start=0, goal=N-1.
# jump: dict mapping index -> new index (after applying forward/backward square), optional
goal = N-1
def next_state(i, roll):
s = i + roll
if s >= N: return goal
return jump.get(s, s)
# Build static neighbors and probabilities
neighbors = [[] for _ in range(N)]
for i in range(N):
if i == goal: continue
freq = {}
for r in range(1,7):
ns = next_state(i, r)
freq[ns] = freq.get(ns, 0) + 1/6.0
neighbors[i] = list(freq.items()) # (next_state, prob)
# Detect states that cannot reach goal via BFS on directed edges
rev = [[] for _ in range(N)]
for i in range(N):
for ns, p in neighbors[i]:
rev[ns].append(i)
reachable = [False]*N
stack = [goal]; reachable[goal]=True
while stack:
u = stack.pop()
for v in rev[u]:
if not reachable[v]:
reachable[v]=True; stack.append(v)
if not reachable[0]:
return math.inf # start cannot reach goal
# Initialize E
E = [0.0 if i==goal else 10.0 for i in range(N)]
# Gauss-Seidel with SOR
for it in range(max_iters):
max_diff = 0.0
for i in range(N):
if i==goal: continue
# if state can't reach goal, expectation is infinite; skip (we handled start)
if not reachable[i]:
E[i] = math.inf
continue
rhs = 1.0
for ns, p in neighbors[i]:
rhs += p * E[ns]
# equation: E[i] = rhs
new_val = rhs
# SOR relaxation
updated = (1-omega)*E[i] + omega*new_val
diff = abs(updated - E[i])
E[i] = updated
if diff > max_diff: max_diff = diff
if max_diff < tol:
break
return E[0]
Key points and reasoning:- We convert probabilistic recurrence into linear equations. E[goal]=0 is absorbing.- Use iterative solver (Gauss-Seidel/SOR) to avoid O(N^3) dense solves; each iteration costs O(N * 6).- Detect absorbing classes: if some states cannot reach goal, E is infinite for those states; return infinity.Complexity:- Per iteration O(N*6) = O(N). Convergence depends on spectral radius; SOR accelerates practical convergence.Edge cases:- Teleport jumps can create cycles with no path to goal (infinite expectation).- If probabilities changed (biased die), just replace 1/6.Alternatives:- Build sparse matrix and use sparse direct or iterative solvers (scipy.sparse + cg/minres) for faster robust convergence.