Passcode Derivation
A common security method for online banking is to ask the user for three random characters from a passcode. Given 50 successful login attempts where each attempt reveals a 3-character subsequence o...
Problem Statement
This archive keeps the full statement, math, and original media on the page.
A common security method used for online banking is to ask the user for three random characters from a passcode. For example, if the passcode was \(531278\), they may ask for the \(2\)nd, \(3\)rd, and \(5\)th characters; the expected reply would be: \(317\).
The text file,
Given that the three characters are always asked for in order, analyse the file so as to determine the shortest possible secret passcode of unknown length.
Problem 79: Passcode Derivation
Mathematical Foundation
Definition 1 (Subsequence Constraint). A login attempt imposes the constraint that characters , , appear in this relative order (not necessarily consecutively) within the passcode.
Definition 2 (Precedence Graph). The precedence graph is a directed graph where is the set of distinct digits appearing in the login data and if and only if some login attempt contains before (i.e., and with in some attempt ).
Assumption (No Repeated Digits). We assume the passcode contains each digit at most once. This is justified a posteriori: if the resulting DAG admits a valid topological ordering of length that satisfies all constraints, then no repetition is needed.
Theorem 1 (Acyclicity). Under the no-repetition assumption, is a directed acyclic graph (DAG).
Proof. Suppose contains a directed cycle . Then the passcode requires to appear before , before , …, and before . By transitivity of the “precedes” relation, must appear strictly before itself. Since each digit appears exactly once, this is impossible. Hence is acyclic.
Theorem 2 (Minimality of Topological Ordering). If is a DAG on vertices, then any topological ordering of yields a string of length respecting all precedence constraints, and no shorter string containing all digits of exists.
Proof. A topological ordering has the property that implies . Thus every precedence constraint is satisfied. The string has length , and any string containing all distinct digits must have length .
Theorem 3 (Uniqueness via Hamiltonian Path). A DAG has a unique topological ordering if and only if it contains a Hamiltonian path. Equivalently, at each step of Kahn’s algorithm, there is exactly one vertex with in-degree .
Proof. () Let be the unique topological order. Suppose edge for some . Then and have no direct precedence constraint between them. Moreover, swapping them yields a sequence . We claim this is also a valid topological ordering: the only pair whose relative order changed is , and since , no constraint is violated. (We also need , which holds since precedes in the original valid ordering.) This contradicts uniqueness. Hence for all , forming a Hamiltonian path.
() Let be a Hamiltonian path. Then is the unique vertex with in-degree in (any other vertex with has the incoming edge from ). Removing and its outgoing edges, becomes the unique vertex with in-degree in the residual graph. By induction, each step of Kahn’s algorithm has a unique choice, producing a unique topological ordering.
Theorem 4 (Correctness of Kahn’s Algorithm). Given a DAG , Kahn’s algorithm outputs a valid topological ordering.
Proof. The algorithm maintains a queue of vertices with in-degree and repeatedly: (i) dequeues a vertex , (ii) appends to the output sequence, and (iii) for each outgoing edge , decrements the in-degree of and enqueues if its in-degree reaches .
We prove the output is a valid topological ordering by contradiction. Suppose but appears before in the output. Then was dequeued before . At the time was dequeued, its in-degree was , meaning every predecessor of (including , since ) had already been removed from the graph. But has not yet been dequeued, so it has not been removed—a contradiction.
Lemma 1 (Extracted Graph for the Given Data). From the 50 login attempts, the distinct digits are (digits and never appear, ). The extracted precedence edges form a DAG with a unique topological ordering: .
Proof. Extracting all ordered pairs from each 3-digit attempt (3 pairs per attempt) and computing in-degrees in the resulting DAG, Kahn’s algorithm proceeds:
- In-degree : only vertex .
- Remove : only vertex has in-degree .
- Remove : only vertex has in-degree .
- Continue: , then , then , then , then .
At each step, exactly one vertex has in-degree , confirming uniqueness by Theorem 3.
Editorial
Each login attempt gives relative-order information, not exact positions. So the natural model is a precedence graph: from a triple we learn that must come before , before , and before . Repeating this over all attempts produces a directed graph on the digits that actually appear.
Once the graph is built, the shortest passcode is just a topological ordering of that graph. No extra digits are needed, because every distinct digit in the graph must appear at least once and a valid topological order already satisfies every subsequence constraint. Kahn’s algorithm generates the passcode by repeatedly taking a digit whose remaining in-degree is zero, appending it to the answer, and deleting its outgoing constraints. In this dataset the choice is unique at every stage, so the passcode is forced.
Pseudocode
Collect the distinct digits that appear in the login attempts.
For each three-digit attempt:
add the precedence edges implied by its relative order
Compute the in-degree of every digit.
Place all digits with in-degree zero into the processing queue.
While the queue is not empty:
remove the next digit from the queue and append it to the passcode
For each digit that must come after it:
decrease that digit's in-degree
if the in-degree becomes zero, place it into the queue
Return the concatenation of the chosen digits.
Complexity Analysis
Time: Graph construction scans 50 attempts, extracting ordered pairs each, with -amortized hash set membership checks. Total: . Kahn’s algorithm runs in . With and (at most directed edges): for this fixed input.
Space: for the adjacency representation and in-degree array.
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<string> attempts = {
"319", "680", "180", "690", "129", "620", "762", "689", "762", "318",
"368", "710", "720", "710", "629", "168", "160", "689", "716", "731",
"736", "729", "316", "729", "729", "710", "769", "290", "719", "680",
"318", "389", "162", "289", "162", "718", "729", "319", "790", "680",
"890", "362", "319", "760", "316", "729", "380", "319", "728", "716"
};
// Build precedence DAG
set<int> digits;
map<int, set<int>> adj;
for (auto& s : attempts) {
int a = s[0] - '0', b = s[1] - '0', c = s[2] - '0';
digits.insert({a, b, c});
adj[a].insert(b);
adj[a].insert(c);
adj[b].insert(c);
}
// Compute in-degrees from the adjacency sets
map<int, int> indeg;
for (int d : digits) indeg[d] = 0;
for (int d : digits)
for (int v : adj[d])
indeg[v]++;
// Kahn's algorithm (topological sort)
queue<int> q;
for (int d : digits)
if (indeg[d] == 0)
q.push(d);
string result;
while (!q.empty()) {
int u = q.front(); q.pop();
result += ('0' + u);
for (int v : adj[u]) {
if (--indeg[v] == 0)
q.push(v);
}
}
cout << result << endl;
return 0;
}
"""
Problem 79: Passcode Derivation
Given 50 successful login attempts (3-digit subsequences), determine the
shortest possible secret passcode via topological sort of the precedence DAG.
Answer: 73162890
"""
from collections import defaultdict, deque
def main():
attempts = [
"319", "680", "180", "690", "129", "620", "762", "689", "762", "318",
"368", "710", "720", "710", "629", "168", "160", "689", "716", "731",
"736", "729", "316", "729", "729", "710", "769", "290", "719", "680",
"318", "389", "162", "289", "162", "718", "729", "319", "790", "680",
"890", "362", "319", "760", "316", "729", "380", "319", "728", "716",
]
# Build the precedence DAG
digits = set()
edges = set()
for s in attempts:
a, b, c = int(s[0]), int(s[1]), int(s[2])
digits.update([a, b, c])
edges.add((a, b))
edges.add((a, c))
edges.add((b, c))
adj = defaultdict(set)
indeg = {d: 0 for d in digits}
for u, v in edges:
adj[u].add(v)
for u in digits:
for v in adj[u]:
indeg[v] += 1
# Kahn's algorithm (topological sort)
queue = deque(d for d in sorted(digits) if indeg[d] == 0)
result = []
while queue:
u = queue.popleft()
result.append(u)
for v in sorted(adj[u]):
indeg[v] -= 1
if indeg[v] == 0:
queue.append(v)
print("".join(str(d) for d in result))
if __name__ == "__main__":
main()