Number Mind
A game similar to Mastermind: the player tries to guess a secret 16-digit number. After each guess, the player is told how many digits are in the correct position. Given the following clues, find t...
Problem Statement
This archive keeps the full statement, math, and original media on the page.
\begin {align*} 5616185650518293 &;2 \text { correct} \\ 3847439647293047 &;1 \text { correct} \\ 5855462940810587 &;3 \text { correct} \\ 9742855507068353 &;3 \text { correct} \\ 4296849643607543 &;3 \text { correct} \\ 3174248439465858 &;1 \text { correct} \\ 4513559094146117 &;2 \text { correct} \\ 7890971548908067 &;3 \text { correct} \\ 8157356344118483 &;1 \text { correct} \\ 2615250744386899 &;2 \text { correct} \\ 8690095851526254 &;3\text { correct} \\ 6375711915077050 &;1 \text { correct} \\ 6913859173121360 &;1 \text { correct} \\ 6442889055042768 &;2 \text { correct} \\ 2321386104303845 &;0 \text { correct} \\ 2326509471271448 &;2 \text { correct} \\ 5251583379644322 &;2 \text { correct} \\ 1748270476758276 &;3 \text { correct} \\ 4895722652190306 &;1 \text { correct} \\ 3041631117224635 &;3 \text { correct} \\ 1841236454324589 &;3 \text { correct} \\ 2659862637316867 &;2 \text { correct} \\ \end {align*}
Problem 185: Number Mind
Mathematical Foundation
Definition 1. A constraint satisfaction problem (CSP) consists of a set of variables , each with domain , and a set of constraints. Here, each clue imposes the constraint:
Theorem 1 (Zero-Clue Elimination). If a clue has count 0, then for every position , in any valid solution.
Proof. If the secret number matched at any position , the count would be at least 1, contradicting . Hence for all .
Theorem 2 (Feasibility Pruning). During backtracking with partial assignment (assigning values to positions ), a clue can be pruned if either:
- (a) (too many matches already), or
- (b) (insufficient potential matches remain).
Proof. Condition (a): If the partial assignment already exceeds the required count, no extension can reduce it. Condition (b): Even if every remaining position matches, the total cannot reach . In both cases, no valid completion exists.
Lemma 1 (MRV Heuristic). Selecting the variable with the smallest remaining domain (Minimum Remaining Values) minimizes the expected branching factor at each node of the search tree, reducing the total number of nodes explored.
Proof. This is a standard result in CSP theory. A variable with candidates creates branches. Choosing the variable with smallest minimizes the width of the search tree at the current level, leading to earlier discovery of dead ends and more aggressive pruning.
Editorial
Uses backtracking with constraint propagation. We initialize domains. We then apply zero-clue elimination. Finally, backtracking search. We perform a recursive search over the admissible choices, prune branches that violate the derived constraints, and keep only the candidates that satisfy the final condition.
Pseudocode
Initialize domains
Apply zero-clue elimination
Backtracking search
MRV: pick position with fewest candidates
Complexity Analysis
- Time: Worst case exponential , but constraint propagation and the MRV heuristic reduce the effective search space to a manageable size. The zero-clue constraints alone reduce domains to roughly 4—6 candidates per position, giving an effective branching factor far below 10.
- Space: for the constraint bookkeeping, plus for the recursion stack.
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#include <bits/stdc++.h>
using namespace std;
struct Clue {
string guess;
int correct;
};
vector<Clue> clues;
int secret[16];
set<int> possible[16]; // possible digits at each position
bool check_feasible(int pos) {
// Check all clue constraints given assignment up to position `pos`
// (positions 0..pos are assigned)
for (auto& c : clues) {
int matched = 0, can_match = 0;
for (int i = 0; i < 16; i++) {
if (i <= pos) {
if (secret[i] == (c.guess[i] - '0'))
matched++;
} else {
if (possible[i].count(c.guess[i] - '0'))
can_match++;
}
}
if (matched > c.correct) return false;
if (matched + can_match < c.correct) return false;
}
return true;
}
bool solve(int depth, vector<int>& order) {
if (depth == 16) {
// Verify all constraints exactly
for (auto& c : clues) {
int matched = 0;
for (int i = 0; i < 16; i++)
if (secret[i] == (c.guess[i] - '0'))
matched++;
if (matched != c.correct) return false;
}
return true;
}
int pos = order[depth];
for (int d : possible[pos]) {
secret[pos] = d;
if (check_feasible(pos)) {
// We need to check with all assigned positions, not just up to pos
// Actually check_feasible checks positions 0..pos but order might not be sequential
// Let's fix: check all assigned positions
}
if (solve(depth + 1, order))
return true;
}
secret[pos] = -1;
return false;
}
bool check_feasible_v2(int assigned[], int num_assigned) {
for (auto& c : clues) {
int matched = 0, can_match = 0;
bool is_assigned[16] = {};
for (int i = 0; i < num_assigned; i++)
is_assigned[assigned[i]] = true;
for (int i = 0; i < 16; i++) {
if (is_assigned[i]) {
if (secret[i] == (c.guess[i] - '0'))
matched++;
} else {
if (possible[i].count(c.guess[i] - '0'))
can_match++;
}
}
if (matched > c.correct) return false;
if (matched + can_match < c.correct) return false;
}
return true;
}
int assigned_positions[16];
bool solve2(int depth, vector<int>& order) {
if (depth == 16) {
for (auto& c : clues) {
int matched = 0;
for (int i = 0; i < 16; i++)
if (secret[i] == (c.guess[i] - '0'))
matched++;
if (matched != c.correct) return false;
}
return true;
}
int pos = order[depth];
assigned_positions[depth] = pos;
for (int d : possible[pos]) {
secret[pos] = d;
if (check_feasible_v2(assigned_positions, depth + 1)) {
if (solve2(depth + 1, order))
return true;
}
}
secret[pos] = -1;
return false;
}
int main(){
clues = {
{"5616185650518293", 2},
{"3847439647293047", 1},
{"5855462940810587", 3},
{"9742855507068353", 3},
{"4296849643607543", 3},
{"3174248439465858", 1},
{"4513559094146117", 2},
{"7890971548908067", 3},
{"8157356344118483", 1},
{"2615250744386899", 2},
{"8690095851526254", 3},
{"6375711915077050", 1},
{"6913859173121360", 1},
{"6442889055042768", 2},
{"2321386104303845", 0},
{"2326509471271448", 2},
{"5765889547612327", 0},
{"6880193648553567", 0},
{"8265420455326143", 2},
};
// Initialize possible digits
for (int i = 0; i < 16; i++) {
for (int d = 0; d <= 9; d++)
possible[i].insert(d);
secret[i] = -1;
}
// Apply zero-clue constraints
for (auto& c : clues) {
if (c.correct == 0) {
for (int i = 0; i < 16; i++) {
possible[i].erase(c.guess[i] - '0');
}
}
}
// Order positions by fewest candidates (MRV)
vector<int> order(16);
iota(order.begin(), order.end(), 0);
sort(order.begin(), order.end(), [](int a, int b){
return possible[a].size() < possible[b].size();
});
solve2(0, order);
for (int i = 0; i < 16; i++)
cout << secret[i];
cout << endl;
return 0;
}
"""
Problem 185: Number Mind
Find the unique 16-digit secret number from Number Mind clues.
Uses backtracking with constraint propagation.
"""
def solve():
clues = [
("5616185650518293", 2),
("3847439647293047", 1),
("5855462940810587", 3),
("9742855507068353", 3),
("4296849643607543", 3),
("3174248439465858", 1),
("4513559094146117", 2),
("7890971548908067", 3),
("8157356344118483", 1),
("2615250744386899", 2),
("8690095851526254", 3),
("6375711915077050", 1),
("6913859173121360", 1),
("6442889055042768", 2),
("2321386104303845", 0),
("2326509471271448", 2),
("5765889547612327", 0),
("6880193648553567", 0),
("8265420455326143", 2),
]
# Parse clues: convert guess strings to lists of ints
parsed = [(list(map(int, g)), c) for g, c in clues]
# Possible digits per position
possible = [set(range(10)) for _ in range(16)]
# Apply zero-clue constraints
for guess, count in parsed:
if count == 0:
for i in range(16):
possible[i].discard(guess[i])
# Order positions by MRV (fewest candidates first)
order = sorted(range(16), key=lambda i: len(possible[i]))
secret = [None] * 16
assigned = set()
def feasible():
for guess, count in parsed:
matched = 0
can_match = 0
for i in range(16):
if i in assigned:
if secret[i] == guess[i]:
matched += 1
else:
if guess[i] in possible[i]:
can_match += 1
if matched > count:
return False
if matched + can_match < count:
return False
return True
def backtrack(depth):
if depth == 16:
# Verify exact match
for guess, count in parsed:
matched = sum(1 for i in range(16) if secret[i] == guess[i])
if matched != count:
return False
return True
pos = order[depth]
assigned.add(pos)
for d in possible[pos]:
secret[pos] = d
if feasible():
if backtrack(depth + 1):
return True
secret[pos] = None
assigned.discard(pos)
return False
backtrack(0)
print(''.join(map(str, secret)))
if __name__ == "__main__":
solve()