Problem 605: Pairwise Coin-Tossing Game
View on Project EulerProject Euler Problem 605 Solution
EulerSolve provides an optimized solution for Project Euler Problem 605, Pairwise Coin-Tossing Game, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary In this cyclic pairwise coin-tossing game, the players are visited in a fixed cycle, each round is decided by a fair coin, and a player becomes champion as soon as that same player wins two consecutive rounds in which the player appears. Let \(P_n(k)\) be the probability that player \(k\) wins when there are \(n\) players, and let \(M_n(k)\) be the product of numerator and denominator after \(P_n(k)\) is reduced to lowest terms. The required value is the last eight digits of $$M_{10^8+7}(10^4+7).$$ Mathematical Approach The implementations use a standard reduction of the game to a pattern-matching problem in a fair coin sequence. Once that reduction is made, the rest is an exercise in counting first occurrences and simplifying an arithmetico-geometric series. Step 1: Reduce the Game to the First Occurrence of \(RL\) Let \(T\) be the position where the pattern \(RL\) appears for the first time in an infinite fair-coin sequence. In the standard reformulation of the game, the decisive moment that produces the champion is exactly this first \(RL\), and the winning label depends only on the round number modulo \(n\). Therefore player \(k\) wins exactly when $$T=k+jn$$ for some integer \(j\ge 0\). So the whole problem becomes: compute the distribution of \(T\), then sum the probabilities over one residue class modulo \(n\)....
Detailed mathematical approach
Problem Summary
In this cyclic pairwise coin-tossing game, the players are visited in a fixed cycle, each round is decided by a fair coin, and a player becomes champion as soon as that same player wins two consecutive rounds in which the player appears. Let \(P_n(k)\) be the probability that player \(k\) wins when there are \(n\) players, and let \(M_n(k)\) be the product of numerator and denominator after \(P_n(k)\) is reduced to lowest terms. The required value is the last eight digits of
$$M_{10^8+7}(10^4+7).$$
Mathematical Approach
The implementations use a standard reduction of the game to a pattern-matching problem in a fair coin sequence. Once that reduction is made, the rest is an exercise in counting first occurrences and simplifying an arithmetico-geometric series.
Step 1: Reduce the Game to the First Occurrence of \(RL\)
Let \(T\) be the position where the pattern \(RL\) appears for the first time in an infinite fair-coin sequence. In the standard reformulation of the game, the decisive moment that produces the champion is exactly this first \(RL\), and the winning label depends only on the round number modulo \(n\).
Therefore player \(k\) wins exactly when
$$T=k+jn$$
for some integer \(j\ge 0\). So the whole problem becomes: compute the distribution of \(T\), then sum the probabilities over one residue class modulo \(n\).
Step 2: Count the Probability That the First \(RL\) Ends at Round \(t\)
Fix \(t\ge 2\). For the first \(RL\) to end exactly at round \(t\), the first \(t-1\) symbols must contain no earlier \(RL\), the symbol at position \(t-1\) must be \(R\), and the symbol at position \(t\) must be \(L\).
A binary word with no \(RL\) must have all \(L\)'s first and all \(R\)'s afterward, so every admissible prefix of length \(t-1\) has the form
$$L^aR^{\,t-1-a}\qquad (0\le a\le t-2).$$
There are exactly \(t-1\) such prefixes. Since all length-\(t\) coin strings have probability \(2^{-t}\), we get
$$\Pr(T=t)=\frac{t-1}{2^t}\qquad (t\ge 2).$$
Step 3: Sum Over the Correct Congruence Class
Because player labels repeat every \(n\) rounds, player \(k\) collects exactly the terms with \(t\equiv k\pmod n\). Hence
$$P_n(k)=\sum_{j\ge 0}\Pr(T=k+jn)=\sum_{j\ge 0}\frac{k+jn-1}{2^{k+jn}}.$$
This is the key series used by all three implementations.
Step 4: Convert the Series to an Arithmetico-Geometric Sum
Set
$$q=2^{-n}.$$
Then factor out \(2^{-k}\):
$$P_n(k)=2^{-k}\sum_{j\ge 0}(k-1+jn)q^j.$$
Now use the standard identities
$$\sum_{j\ge 0}q^j=\frac{1}{1-q},\qquad \sum_{j\ge 0}jq^j=\frac{q}{(1-q)^2}.$$
Substituting them gives
$$P_n(k)=2^{-k}\left(\frac{k-1}{1-q}+\frac{nq}{(1-q)^2}\right).$$
Step 5: Simplify to a Closed Rational Formula
Let
$$X=2^n-1.$$
Since \(q=2^{-n}\), we have
$$\frac{1}{1-q}=\frac{2^n}{X},\qquad \frac{q}{(1-q)^2}=\frac{2^n}{X^2}.$$
Therefore
$$P_n(k)=2^{-k}\left(\frac{(k-1)2^n}{X}+\frac{n2^n}{X^2}\right)=\frac{2^{n-k}\big((k-1)X+n\big)}{X^2}.$$
Replacing \(X\) by \(2^n-1\) yields the compact form
$$P_n(k)=\frac{2^{n-k}\big((k-1)2^n+(n-k+1)\big)}{(2^n-1)^2}.$$
Step 6: Pass from \(P_n(k)\) to \(M_n(k)\)
Write
$$Y=(k-1)2^n+(n-k+1).$$
The unreduced fraction is
$$P_n(k)=\frac{2^{n-k}Y}{X^2},\qquad X=2^n-1.$$
Because \(X\) is odd, the factor \(2^{n-k}\) never cancels with the denominator. Any reduction can only come from \(\gcd(Y,X^2)\). Modulo \(X\), we have
$$Y\equiv (k-1)+(n-k+1)\equiv n\pmod X,$$
so
$$\gcd(Y,X)=\gcd(n,X).$$
For the target value \(n=10^8+7\), \(n\) is prime and Fermat's little theorem gives \(2^n\equiv 2\pmod n\). Hence
$$2^n-1\equiv 1\pmod n,$$
which implies \(\gcd(n,2^n-1)=1\), so no reduction is needed for the target instance. Therefore
$$M_n(k)\equiv 2^{n-k}\big((k-1)2^n+(n-k+1)\big)(2^n-1)^2\pmod{10^8}.$$
Worked Example: \(n=6,\ k=2\)
This example shows why the reduction step matters in general, even though the target instance is already coprime.
From the closed form,
$$P_6(2)=\frac{2^{6-2}\big((2-1)2^6+(6-2+1)\big)}{(2^6-1)^2}=\frac{16\cdot 69}{63^2}=\frac{1104}{3969}.$$
Now divide numerator and denominator by \(3\):
$$P_6(2)=\frac{368}{1323}.$$
So
$$M_6(2)=368\cdot 1323=486864,$$
which matches the small checkpoint used by the exact validation logic.
How the Code Works
The C++, Python, and Java implementations do not simulate the game. Instead, they evaluate the closed formula directly modulo \(10^8\), because only the last eight digits are required.
First they compute \(2^n\bmod 10^8\) and \(2^{n-k}\bmod 10^8\) using binary modular exponentiation. Next they build the two closed-form factors \(2^n-1\) and \((k-1)2^n+(n-k+1)\) modulo \(10^8\). Then they multiply
$$2^{n-k}\cdot \big((k-1)2^n+(n-k+1)\big)\cdot (2^n-1)\cdot (2^n-1)\pmod{10^8}.$$
One implementation also verifies small exact examples and checks the coprimality condition that justifies skipping reduction for the target input, while the streamlined implementations rely on the fixed target parameters. The final value is formatted as an eight-digit string, including leading zeros if necessary.
Complexity Analysis
The dominant work is modular exponentiation, which takes \(O(\log n)\) time. All other arithmetic is constant-time modular multiplication and addition on machine-size integers, so the total memory usage is \(O(1)\).
Footnotes and References
- Problem page: https://projecteuler.net/problem=605
- Geometric series: Wikipedia - Geometric series
- Arithmetico-geometric sequence: Wikipedia - Arithmetico-geometric sequence
- Modular exponentiation: Wikipedia - Modular exponentiation
- Fermat's little theorem: Wikipedia - Fermat's little theorem
Problem 605 source code
C++
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <numeric>
// Project Euler 605: waiting for the first "RL" in a fair coin sequence.
// P_n(k) = sum_{j>=0} (k+jn-1)/2^{k+jn} gives a closed form:
// P_n(k) = 2^{n-k} * ((k-1)2^n + (n-k+1)) / (2^n-1)^2.
using u64 = std::uint64_t;
using u128 = unsigned __int128;
static u128 gcd_u128(u128 a, u128 b) {
while (b) {
u128 t = a % b;
a = b;
b = t;
}
return a;
}
static u64 mod_pow(u64 a, u64 e, u64 mod) {
u128 r = 1;
u128 x = a % mod;
while (e) {
if (e & 1) r = (r * x) % mod;
x = (x * x) % mod;
e >>= 1;
}
return (u64)r;
}
static u128 M_small_exact(u64 n, u64 k) {
// Exact only for small n (here used just for statement validations).
const u128 two_n = (u128)1 << n;
const u128 two_nk = (u128)1 << (n - k);
const u128 X = two_n - 1; // 2^n - 1
const u128 den = X * X; // (2^n - 1)^2
const u128 inner = (u128)(k - 1) * two_n + (u128)(n - k + 1);
const u128 g = gcd_u128(inner, den);
const u128 num_red = two_nk * (inner / g);
const u128 den_red = den / g;
return num_red * den_red;
}
int main() {
// Statement validations.
if ((u64)M_small_exact(3, 1) != 588ULL) {
std::cerr << "Validation failed: M_3(1)\n";
return 1;
}
if ((u64)M_small_exact(6, 2) != 486864ULL) {
std::cerr << "Validation failed: M_6(2)\n";
return 1;
}
constexpr u64 MOD = 100000000ULL; // last 8 digits
constexpr u64 n = 100000007ULL;
constexpr u64 k = 10007ULL;
// For this query n is prime, so gcd(n,2^n-1)=1 and the fraction is already reduced.
const u64 two_n_mod_n = mod_pow(2, n, n);
const u64 x_mod_n = (two_n_mod_n + n - 1) % n; // (2^n-1) mod n
if (std::gcd(n, x_mod_n) != 1) {
std::cerr << "Unexpected: reduction needed (not implemented for large n)\n";
return 1;
}
const u64 two_n_mod = mod_pow(2, n, MOD);
const u64 two_nk_mod = mod_pow(2, n - k, MOD);
const u64 x_mod = (two_n_mod + MOD - 1) % MOD;
const u64 inner_mod = (u64)(((u128)(k - 1) * two_n_mod + (u128)(n - k + 1)) % MOD);
u64 ans = (u64)(((u128)two_nk_mod * inner_mod) % MOD);
ans = (u64)(((u128)ans * x_mod) % MOD);
ans = (u64)(((u128)ans * x_mod) % MOD);
std::cout << std::setw(8) << std::setfill('0') << ans << "\n";
return 0;
}
Python
def solve():
MOD = 100000000
n = 100000007
k = 10007
two_n_mod = pow(2, n, MOD)
two_nk_mod = pow(2, n - k, MOD)
x_mod = (two_n_mod + MOD - 1) % MOD
inner_mod = ((k - 1) * two_n_mod + (n - k + 1)) % MOD
ans = (two_nk_mod * inner_mod) % MOD
ans = (ans * x_mod) % MOD
ans = (ans * x_mod) % MOD
return f"{ans:08d}"
if __name__ == '__main__':
print(solve())
Java
public class Euler605 {
static long modPow(long a, long e, long mod) {
long r = 1;
long x = a % mod;
while (e > 0) {
if ((e & 1) == 1)
r = (r * x) % mod;
x = (x * x) % mod;
e >>= 1;
}
return r;
}
public static String solve() {
long MOD = 100000000L;
long n = 100000007L;
long k = 10007L;
long two_n_mod = modPow(2, n, MOD);
long two_nk_mod = modPow(2, n - k, MOD);
long x_mod = (two_n_mod + MOD - 1) % MOD;
long inner_mod = (((k - 1) * two_n_mod % MOD) + (n - k + 1)) % MOD;
long ans = (two_nk_mod * inner_mod) % MOD;
ans = (ans * x_mod) % MOD;
ans = (ans * x_mod) % MOD;
return String.format("%08d", ans);
}
public static void main(String[] args) {
System.out.println(solve());
}
}