Project Euler Problem 406: Guessing Game
We are playing a guessing game where I choose a number from the set {1, 2,..., n}. You make guesses, and after each guess I tell you whether your guess is correct, too low, or too high. A guess tha...
Problem Statement
This archive keeps the full statement, math, and original media on the page.
We are trying to find a hidden number selected from the set of integers $\{1, 2, \ldots, n\}$ by asking questions. Each number (question) we ask, we get one of three possible answers:
"Your guess is lower than the hidden number" (and you incur a cost of $a$), or
"Your guess is higher than the hidden number" (and you incur a cost of $b$), or
"Yes, that's it!" (and the game ends).
Given the value of $n$, $a$ and $b$, an optimal strategy minimizes the total cost for the worst possible case.
For example, if $n = 5$, $a = 2$ and $b = 3$, then we may begin by asking $"2"$ as our first question.
If we are told that $2$ is higher than the hidden number (for a cost of $b = 3$), then we are sure that $"1"$ is the hidden number (for $a$ total cost of ).
If we are told that $2$ is lower than the hidden number (for a cost of $a = 2$), then our next question will be $"4"$.
If we are told that $4$ is higher than the hidden number (for a cost of $b = 3$), then we are sure that $"3"$ is the hidden number (for a total cost $2 + 3 = \textcolor{blue}{5}$).
If we are told that $4$ is lower than the hidden number (for a cost of $a = 2$), then we are sure that $"5"$ is the hidden number (for a total cost of $2 + 2 = \textcolor{blue}{4}$).
Thus, we worst-case cost achieved by this strategy is . It can also be shown that this is that lowest worst-case cost that can be achieved. So, in fact, we have just described an optimal strategy for the given values of $n$, $a$ and $b$.
Let $C(n, a, b)$ be the worst-case cost achieved by an optimal strategy for the given values of $n$, $a$ and $b$.
Here are a few examples:
$C(5,2, 3) = 5$
$C(500, \sqrt{2}, \sqrt{3} = 13.22073197\ldots$
$C(20000, 5, 7) = 82$
$C(2000000, \sqrt{5}, \sqrt{7}) = 49.63755955\ldots$
Let $F_k$ be the Fibonacci numbers: $F_k = F_{k - 1} + F_{k - 2}$ with base cases $F_1 = F_2 = 1$.
Find $\sum_{k = 1}^{k = 30} C\left(10^{12}, \sqrt{k}, \sqrt{F_k} \right)$, and give your answer rounded to $8$ decimal places behind the decimal point.
Project Euler Problem 406: Guessing Game
Mathematical Analysis
Optimal Strategy via Dynamic Programming
For a continuous relaxation, the optimal strategy generalizes binary search. When guessing a number in {1, …, n} with asymmetric costs a (guess too low) and b (guess too high), the optimal guess divides the remaining range so that the cost of going left vs. right is balanced.
Key Insight: Generalized Fibonacci Approach
For integer costs, C(n, a, b) satisfies a recurrence. For real-valued costs sqrt(k) and sqrt(F_k), we use a continuous model.
The optimal strategy splits the interval [1, n] at position p such that:
In the continuous limit for large n, the optimal cost is:
More precisely, for the generalized cost model with asymmetric penalties, the optimal strategy uses a generalization of the golden ratio. If we define phi = b/a, the number of guesses follows an analog of the Fibonacci-based analysis where the maximum searchable range grows according to a generalized Fibonacci sequence.
Generalized Fibonacci Recurrence
Define G_0 = 1, G_1 = 1, and the recurrence based on the cost ratio. The optimal cost C(n, a, b) can be computed by finding the smallest q such that a generalized Fibonacci number exceeds n, then computing the cost via the accumulated penalties.
Editorial
The key observation is that for large n, the optimal strategy partitions the search space using ratios determined by a and b. The number of steps q satisfies a generalized Fibonacci-like growth, and the total cost is computed by tracking accumulated penalties at each level. We iterate over each k from 1 to 30, compute a = sqrt(k), b = sqrt(F_k). We then use dynamic programming / generalized Fibonacci approach to compute C(10^12, a, b). Finally, sum all values and round to 8 decimal places.
Pseudocode
For each k from 1 to 30, compute a = sqrt(k), b = sqrt(F_k)
Use dynamic programming / generalized Fibonacci approach to compute C(10^12, a, b)
Sum all values and round to 8 decimal places
Correctness
Theorem. The method described above computes exactly the quantity requested in the problem statement.
Proof. The preceding analysis identifies the admissible objects and derives the formula, recurrence, or exhaustive search carried out by the algorithm. The computation evaluates exactly that specification, so every valid contribution is included once and no invalid contribution is counted. Therefore the returned value is the required answer.
Complexity Analysis
- For each k: O(log(n)) = O(log(10^12)) = O(40) steps to compute C.
- Total: O(30 * 40) = O(1200), essentially constant time.
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#include <bits/stdc++.h>
using namespace std;
/*
* Project Euler Problem 406: Guessing Game
*
* C(n, a, b) = minimum worst-case cost to find a number in {1..n}
* with cost a for "too low" and cost b for "too high".
*
* Key insight: f(W) = max range searchable with worst-case cost <= W.
* f(W) = 0 if W < 0
* f(W) = 1 if 0 <= W < min(a,b) (can only hold 1 number)
* f(W) = f(W-a) + f(W-b) + 1 otherwise
*
* C(n, a, b) = min W such that f(W) >= n.
* Binary search on W.
*
* For computation: states (i,j) meaning i times cost-a deducted,
* j times cost-b deducted. Budget = W - i*a - j*b.
*/
long double solve(long long n, long double a, long double b) {
long double hi = log2l((long double)n) * (a + b) * 2.0L;
long double lo = 0;
for (int iter = 0; iter < 300; iter++) {
long double mid = (lo + hi) / 2.0L;
map<pair<int,int>, long long> memo;
function<long long(int, int)> f = [&](int i, int j) -> long long {
long double budget = mid - i * a - j * b;
if (budget < -1e-12L) return 0LL;
auto key = make_pair(i, j);
auto it = memo.find(key);
if (it != memo.end()) return it->second;
bool cl = (budget >= a - 1e-12L);
bool ch = (budget >= b - 1e-12L);
long long val;
if (!cl && !ch) {
val = 1;
} else if (cl && ch) {
long long fl = f(i + 1, j);
long long fh = f(i, j + 1);
val = min((long long)2e18, fl + fh + 1);
} else if (cl) {
val = f(i + 1, j) + 1;
} else {
val = f(i, j + 1) + 1;
}
memo[key] = val;
return val;
};
long long fval = f(0, 0);
if (fval >= n)
hi = mid;
else
lo = mid;
}
return hi;
}
int main() {
ios_base::sync_with_stdio(false);
vector<long long> fib(31);
fib[1] = 1; fib[2] = 1;
for (int i = 3; i <= 30; i++)
fib[i] = fib[i-1] + fib[i-2];
long long N = 1000000000000LL; // 10^12
long double total = 0.0L;
for (int k = 1; k <= 30; k++) {
long double a = sqrtl((long double)k);
long double b = sqrtl((long double)fib[k]);
long double c = solve(N, a, b);
total += c;
}
printf("%.8Lf\n", total);
return 0;
}
"""
Project Euler Problem 406: Guessing Game
C(n, a, b) = minimum worst-case cost to find a number in {1..n}
with cost a for "too low" and cost b for "too high".
We compute sum of C(10^12, sqrt(k), sqrt(F_k)) for k=1..30.
f(W) = max range searchable with worst-case cost <= W.
f(W) = f(W-a) + f(W-b) + 1 for W >= min(a,b)
f(W) = 1 for 0 <= W < min(a,b)
f(W) = 0 for W < 0
C(n,a,b) = min W such that f(W) >= n.
Binary search on W.
"""
import math
import sys
sys.setrecursionlimit(200000)
def solve(n, a, b):
"""Find minimum W such that f(W) >= n using binary search."""
lo = 0.0
hi = math.log2(n) * (a + b) * 2.0
for _ in range(200):
mid = (lo + hi) / 2.0
memo = {}
def f(i, j):
budget = mid - i * a - j * b
if budget < -1e-12:
return 0
key = (i, j)
if key in memo:
return memo[key]
cl = budget >= a - 1e-12
ch = budget >= b - 1e-12
if not cl and not ch:
val = 1
elif cl and ch:
val = min(10**18, f(i + 1, j) + f(i, j + 1) + 1)
elif cl:
val = f(i + 1, j) + 1
else:
val = f(i, j + 1) + 1
memo[key] = val
return val
fval = f(0, 0)
if fval >= n:
hi = mid
else:
lo = mid
return hi
def main():
fib = [0] * 31
fib[1] = 1
fib[2] = 1
for i in range(3, 31):
fib[i] = fib[i - 1] + fib[i - 2]
N = 10**12
total = 0.0
for k in range(1, 31):
a = math.sqrt(k)
b = math.sqrt(fib[k])
c = solve(N, a, b)
total += c
print(f"{total:.8f}")
if __name__ == "__main__":
main()