Open Source · 7 Languages · 900+ Problems

Project Euler Solutions

Complete solutions in C++, Python & Java — with step-by-step mathematical explanations

All Problems

Problem 610: Roman Numerals II

View on Project Euler

Project Euler Problem 610 Solution

EulerSolve provides an optimized solution for Project Euler Problem 610, Roman Numerals II, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary At each step we sample one symbol from \(\{I,V,X,L,C,D,M,\#\}\). Each Roman letter has probability \(0.14\), while \(\#\) has probability \(0.02\). A sampled letter is appended only if the new string is still a valid minimal Roman numeral; otherwise that letter is ignored and the current string stays unchanged. The process ends at the first \(\#\), and the empty string counts as value \(0\). The task is to compute the expected final value, rounded to eight decimal places. The difficulty is that the random stream is infinite in principle, but the validity rule collapses the process to a small structured set of states. Mathematical Approach The C++, Python, and Java implementations model the process as a finite expectation system for the non-thousands part, plus one extra equation for the unbounded prefix of \(M\)'s. Step 1: Use Minimal Roman Numerals as States For each integer \(r\in\{1,2,\dots,999\}\), let \(s(r)\) be its unique minimal Roman numeral. These \(999\) strings form the finite state space for all numerals that do not begin with a leading run of thousands. For a letter \(\ell\in\{I,V,X,L,C,D,M\}\), append \(\ell\) to the current minimal numeral \(s(r)\). If the result is itself the minimal Roman numeral of some integer \(v\) with \(1\le v\le 999\), then we move to state \(v\). Otherwise the letter is rejected and we remain at \(r\)....

Detailed mathematical approach

Problem Summary

At each step we sample one symbol from \(\{I,V,X,L,C,D,M,\#\}\). Each Roman letter has probability \(0.14\), while \(\#\) has probability \(0.02\). A sampled letter is appended only if the new string is still a valid minimal Roman numeral; otherwise that letter is ignored and the current string stays unchanged. The process ends at the first \(\#\), and the empty string counts as value \(0\).

The task is to compute the expected final value, rounded to eight decimal places. The difficulty is that the random stream is infinite in principle, but the validity rule collapses the process to a small structured set of states.

Mathematical Approach

The C++, Python, and Java implementations model the process as a finite expectation system for the non-thousands part, plus one extra equation for the unbounded prefix of \(M\)'s.

Step 1: Use Minimal Roman Numerals as States

For each integer \(r\in\{1,2,\dots,999\}\), let \(s(r)\) be its unique minimal Roman numeral. These \(999\) strings form the finite state space for all numerals that do not begin with a leading run of thousands.

For a letter \(\ell\in\{I,V,X,L,C,D,M\}\), append \(\ell\) to the current minimal numeral \(s(r)\). If the result is itself the minimal Roman numeral of some integer \(v\) with \(1\le v\le 999\), then we move to state \(v\). Otherwise the letter is rejected and we remain at \(r\).

This turns the Roman numeral rule into a deterministic transition map \(T(r,\ell)\) on a fixed set of states.

Step 2: Accepted Appends Always Increase the Value

If a letter is accepted, it extends the current minimal Roman numeral to a longer valid minimal numeral. That can only increase the represented number, so every accepted transition satisfies

$T(r,\ell)>r.$

Therefore the directed graph of accepted transitions on \(\{1,\dots,999\}\) is acyclic when states are ordered by value. This is why the expectation system can be solved by backward substitution from \(999\) down to \(1\).

Rejected letters are different: they do not create new states, they create self-loops.

Step 3: Write the Expectation Equation for One State

Let \(E_r\) be the expected final value when the current string already equals the minimal Roman numeral for \(r\). On the very next draw, either \(\#\) appears and we stop with value \(r\), or one of the seven letters is sampled.

So for \(1\le r\le 999\),

$E_r=0.02\,r+0.14\sum_{\ell\in\{I,V,X,L,C,D,M\}} E_{T(r,\ell)}.$

If exactly \(q_r\) letters are rejected at state \(r\), then those \(q_r\) terms equal \(E_r\) itself. Moving them to the left gives

$E_r=\frac{0.02\,r+0.14\sum_{\ell:\,T(r,\ell)\ne r} E_{T(r,\ell)}}{1-0.14\,q_r}.$

This is the key linear relation used by the implementation.

Step 4: Separate the Unlimited Leading \(M\)-Run

Minimal Roman numerals are not globally finite, because any number of leading \(M\)'s is allowed. Instead of creating infinitely many states \(1000,2000,3000,\dots\), we isolate that part.

Let \(E_0\) be the expected final value from the empty string. If the first accepted letter is \(M\), we gain \(1000\) immediately and are again in the same “prefix of only \(M\)'s” situation. If the first accepted non-\(M\) letter is one of \(I,V,X,L,C,D\), then we enter one of the finite states \(1,5,10,50,100,500\).

Conditioning on the first draw gives

$E_0=0.14\left(E_1+E_5+E_{10}+E_{50}+E_{100}+E_{500}+(1000+E_0)\right).$

Rearranging,

$0.86\,E_0=140+0.14\left(E_1+E_5+E_{10}+E_{50}+E_{100}+E_{500}\right).$

The coefficient \(0.86\) is the probability that the next symbol is not \(M\), and the term \(140\) is the one-step expected contribution \(0.14\times1000\).

Step 5: Worked Example for the State \(X\)

Take the current numeral \(X\), which represents \(10\). Appending \(I,V,X,L,C\) gives the valid minimal numerals \(XI,XV,XX,XL,XC\), so these moves go to \(11,15,20,40,90\).

Appending \(D\) or \(M\) does not yield a valid minimal continuation, so those letters are rejected. Thus \(q_{10}=2\), and the state equation becomes

$E_{10}=0.02\cdot10+0.14\left(E_{11}+E_{15}+E_{20}+E_{40}+E_{90}+2E_{10}\right).$

Equivalently,

$0.72\,E_{10}=0.2+0.14\left(E_{11}+E_{15}+E_{20}+E_{40}+E_{90}\right).$

This small example shows the two recurring ingredients of the full solution: accepted forward moves and rejected self-loops.

Step 6: Solve in Descending Order

Because every accepted move from \(r\) goes to a larger state, \(E_{999}\) depends only on itself, \(E_{998}\) depends only on itself and already computed larger states, and so on. Therefore we can compute

$E_{999},E_{998},\dots,E_1$

in that order without any iterative numerical method. Once these \(999\) values are known, the empty-state equation yields the final expectation immediately.

How the Code Works

The C++, Python, and Java implementations first generate the minimal Roman numeral for every integer from \(0\) to \(999\) using the standard greedy token system \(1000,900,500,400,\dots,1\). They then test every state \(r\in\{1,\dots,999\}\) against each of the seven letters by appending that letter, evaluating the candidate numeral numerically, and accepting the transition exactly when the candidate string matches the minimal Roman numeral of its value and stays within \(1\) to \(999\).

After the transition table is built, the implementation processes states from \(999\) downward. For each state it counts rejected letters, sums the expectations of accepted successor states, and solves the rearranged one-line equation for \(E_r\). Finally it substitutes the six entry states \(1,5,10,50,100,500\) into the separate equation for \(E_0\) and prints the result rounded to eight decimal places.

Complexity Analysis

The finite part of the state space has exactly \(999\) states and each state checks exactly \(7\) letters. Building the transitions therefore costs \(O(999\cdot7)\), and solving the expectations costs another \(O(999\cdot7)\). Memory usage is also \(O(999\cdot7)\) if the full transition table is stored, or \(O(999)\) for the expectation array itself. Since \(999\) and \(7\) are fixed constants, the practical complexity is constant time and constant memory.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=610
  2. Roman numeral background: Project Euler — About Roman Numerals
  3. Roman numerals overview: Wikipedia — Roman numerals
  4. Markov chain fundamentals: Wikipedia — Markov chain
  5. Conditioning and expectation identities: Wikipedia — Law of total expectation

Mathematical approach · C++ solution · Python solution · Java solution

Previous: Problem 609 · All Project Euler solutions · Next: Problem 611

Need help with a problem? Ask me! 💡
e
✦ Euler GLM 5.2
Hi! I'm Euler. Ask me anything about Project Euler problems, math concepts, or solution approaches! 🧮