Problem (W. Fitch Cheney, 1950). A spectator picks 5 cards from a standard 52-card deck and hands them to the assistant, who hides one and shows the other 4 in an order of their choosing. The magician names the hidden card. How?
Answer. Among 5 cards two share a suit (pigeonhole: 4 suits). On a 13-hour clock of ranks, one of those two is at most 6 steps clockwise from the other. Hide that one; show the other first (it gives the suit and the base rank); order the remaining 3 cards to encode the distance ( orders).
| Fact | Value |
|---|---|
| distance encoding, cards compared by (rank, suit) as low / mid / high | 1 = LMH, 2 = LHM, 3 = MLH, 4 = MHL, 5 = HLM, 6 = HML |
| largest deck for which a 5-card trick exists | (Kleitman and Kwiatkowski, 1976) |
| general: cards chosen, shown in order | deck of up to cards, and no more |
| explicit strategy for the largest deck | hide the card with (); the order of the shown cards encodes the hidden card’s position among the unseen cards |
// Classic trick. Card c in 0..51: suit c / 13, rank c % 13. Returns the 4 cards to show, in order.
vector<int> cheneyEncode(vector<int> hand) {
sort(all(hand));
for (int i = 0; i < 5; i++) for (int j = 0; j < 5; j++) if (i != j && hand[i] / 13 == hand[j] / 13) {
int d = ((hand[j] - hand[i]) % 13 + 13) % 13; // hide hand[j], d steps after hand[i]
if (d < 1 || d > 6) continue;
vector<int> rest; for (int k = 0; k < 5; k++) if (k != i && k != j) rest.push_back(hand[k]);
static const int ord[6][3] = {{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}};
vector<int> shown = {hand[i]};
for (int t = 0; t < 3; t++) shown.push_back(rest[ord[d - 1][t]]); // rest is sorted: L, M, H
return shown;
}
return {}; // impossible: two cards always share a suit
}
int cheneyDecode(const vector<int>& shown) {
vector<int> rest(shown.begin() + 1, shown.end()), s = rest; sort(all(s));
int code = 0; // rank of the permutation, 0..5
static const int ord[6][3] = {{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}};
for (int p = 0; p < 6; p++) if (rest[0] == s[ord[p][0]] && rest[1] == s[ord[p][1]]) code = p;
int suit = shown[0] / 13, rank = (shown[0] % 13 + code + 1) % 13;
return suit * 13 + rank;
}
// General trick: deck 0..N-1 with N <= n! + n - 1; hand of n cards; show n - 1 of them in an order.
vector<int> generalEncode(vector<int> hand) {
int n = sz(hand); sort(all(hand));
ll sum = 0; for (int c : hand) sum += c;
int s = int(sum % n), h = hand[s];
vector<int> shown; for (int i = 0; i < n; i++) if (i != s) shown.push_back(hand[i]);
ll T = sum - h, r = ((-T) % n + n) % n, q = (h - s - r) / n; // h - s = q n + r: position among unseen cards
vector<int> pool = shown, res; // write q in the factorial number system
for (int k = n - 1; k >= 1; k--) {
ll f = 1; for (int t = 1; t < k; t++) f *= t;
int idx = int(q / f); q %= f;
res.push_back(pool[idx]); pool.erase(pool.begin() + idx);
}
return res;
}
int generalDecode(const vector<int>& shown) {
int n = sz(shown) + 1; vector<int> pool = shown; sort(all(pool));
ll q = 0, T = 0;
for (int k = 0; k < n - 1; k++) {
int idx = int(lower_bound(all(pool), shown[k]) - pool.begin());
ll f = 1; for (int t = 1; t < n - 1 - k; t++) f *= t;
q += idx * f; pool.erase(pool.begin() + idx); T += shown[k];
}
ll pos = q * n + ((-T) % n + n) % n; // index among cards not shown
vector<int> s = shown; sort(all(s));
int c = int(pos);
for (int x : s) if (x <= c) c++; // skip the shown cards
return c;
}Proof: the classic trick works
Five cards in four suits: two share a suit (pigeonhole). Their ranks on the 13-cycle satisfy , so one of the two distances is in ; the encoder hides the card that lies steps after the other. The first shown card fixes the suit and the start rank, the three others are distinct and totally ordered by card index, so their order is one of 6 permutations, decoding . The hidden card is (same suit, start rank ). Verified on all hands.
Proof: n! + n - 1 is the largest deck, and the sum strategy achieves it
Upper bound. The assistant’s strategy is an injective map from hands (-subsets) to sequences of distinct cards (the magician must recover the hand from the sequence). So ; dividing, , i.e. .
Construction. Let and ; hide . Exactly shown cards are smaller than , so among the cards the magician cannot see, has index . With the sum of the shown cards, , so : the magician knows , and only must be sent. Since , , which fits in the orders of the shown cards (factorial number system). Decoding reverses each step. Verified exhaustively for () and (), and on random hands for ().
Related notes
- Counting Fundamentals (permutation ranking)
- Socks and Pigeonhole Puzzles
- Poisoned Wine (another information-encoding puzzle)