Problem (Kirkman, 1850). Fifteen schoolgirls walk out three abreast for seven days. Arrange them so that no two walk together twice.
Answer. Possible. In design language: a Steiner triple system STS() is a set of triples of points covering every pair exactly once; it is resolvable (a Kirkman triple system, KTS) if the triples split into parallel classes (days), each partitioning the points. Kirkman’s problem asks for a KTS(15): triples, 5 per day, 7 days.
| Object | Exists iff | Construction |
|---|---|---|
| STS() | (Kirkman, 1847) | Bose (), Skolem () below |
| KTS() | (Ray-Chaudhuri and Wilson, 1971) | KTS(15): resolve the lines of the projective space PG(3, 2) |
| KTS(15) up to isomorphism | 7 solutions (from 4 of the 80 STS(15)) | computer classification |
| number of triples of STS() | ; each point in triples |
The 15-girl schedule. Label the girls by the nonzero vectors of , i.e. as 4-bit masks. The 35 triples are the lines of PG(3, 2): every pair lies in exactly the one line . A short search splits them into 7 days of 5 disjoint lines; with girl written as the letter of A..O, kirkman15() returns:
| Day | Rows of three |
|---|---|
| 1 | ABC, DHL, EJO, FKM, GIN |
| 2 | ADE, BHJ, CMN, FIO, GKL |
| 3 | AFG, BIK, CLO, DJN, EHM |
| 4 | AHI, BLN, CEF, DKO, GJM |
| 5 | AJK, BMO, CDG, EIL, FHN |
| 6 | ALM, BDF, CIJ, EKN, GHO |
| 7 | ANO, BEG, CHK, DIM, FJL |
using Triple = array<int, 3>;
vector<Triple> boseSTS(int v) { // v = 3n, n odd; points x + n * level
int n = v / 3, h = (n + 1) / 2; vector<Triple> T;
auto op = [&](int x, int y) { return (x + y) * h % n; }; // idempotent commutative quasigroup: x o x = x
for (int x = 0; x < n; x++) T.push_back({x, x + n, x + 2 * n});
for (int i = 0; i < 3; i++) for (int x = 0; x < n; x++) for (int y = x + 1; y < n; y++)
T.push_back({x + i * n, y + i * n, op(x, y) + (i + 1) % 3 * n});
return T;
}
vector<Triple> skolemSTS(int v) { // v = 6n + 1; points x + 2n * level, infinity = 6n
int n = (v - 1) / 6, m = 2 * n, inf = 6 * n; vector<Triple> T;
auto op = [&](int x, int y) { int k = (x + y) % m; return k % 2 == 0 ? k / 2 : n + (k - 1) / 2; }; // x o x = x mod n
for (int x = 0; x < n; x++) T.push_back({x, x + m, x + 2 * m});
for (int i = 0; i < 3; i++) for (int x = 0; x < n; x++) T.push_back({inf, n + x + i * m, x + (i + 1) % 3 * m});
for (int i = 0; i < 3; i++) for (int x = 0; x < m; x++) for (int y = x + 1; y < m; y++)
T.push_back({x + i * m, y + i * m, op(x, y) + (i + 1) % 3 * m});
return T;
}
// Split a Steiner triple system on points 0..v-1 into parallel classes (days); empty if it is not resolvable.
vector<vector<Triple>> resolve(int v, const vector<Triple>& T) {
int days = (v - 1) / 2; vector<vector<int>> at(v);
for (int t = 0; t < sz(T); t++) for (int p : T[t]) at[p].push_back(t);
vector<char> used(sz(T), 0); vector<vector<Triple>> res(days);
auto go = [&](auto&& self, int d, ll covered) -> bool { // covered: points already placed on day d
if (d == days) return true;
if (covered == (1LL << v) - 1) return self(self, d + 1, 0);
int p = __builtin_ctzll(~covered); // smallest point still walking alone
for (int t : at[p]) if (!used[t] && !(covered >> T[t][0] & 1) && !(covered >> T[t][1] & 1) && !(covered >> T[t][2] & 1)) {
used[t] = 1; res[d].push_back(T[t]);
if (self(self, d, covered | 1LL << T[t][0] | 1LL << T[t][1] | 1LL << T[t][2])) return true;
used[t] = 0; res[d].pop_back();
}
return false;
};
if (!go(go, 0, 0)) return {};
return res;
}
vector<vector<Triple>> kirkman15() { // girls 0..14 = nonzero 4-bit vectors minus 1
vector<Triple> lines;
for (int a = 1; a < 16; a++) for (int b = a + 1; b < 16; b++) if ((a ^ b) > b) lines.push_back({a - 1, b - 1, (a ^ b) - 1});
return resolve(15, lines);
}Proof: STS(v) exists iff v ≡ 1, 3 (mod 6)
Necessity. A point lies in triples that pair it with the other points two at a time, so is even. The number of triples is , so , i.e. . Together, . A KTS also needs (each day partitions the points into triples), so .
Bose (, odd). is commutative, idempotent () and Latin ( is invertible mod ). Pairs inside a level : lies only in the triple . Pairs across levels : lies in the vertical triple if ; otherwise in for the unique with , and because ; never in both, since would force . Every pair is covered exactly once.
Skolem (). On , with , is commutative and Latin ( is a bijection) and half-idempotent: for , i.e. . Pairs with : and lie in the triple , and these triples contain each finite point once. Same-level pairs lie only in . For a cross pair let be the unique element with . If , the pair lies in the triple ; the vertical and -triples contain only cross pairs with , which would force . If , i.e. , the pair lies in the vertical triple when or in the -triple when , and in no triple of the third kind. Every pair is covered exactly once.
PG(3, 2). For distinct nonzero , is nonzero and differs from both, and is a line iff ; so each pair lies in exactly one line, and the lines form an STS(15). The search
resolvetries, for the smallest point not yet placed today, every unused triple through it that is disjoint from today’s triples: every resolution is reachable this way, so the search is exhaustive and the schedule it returns is valid. Checked: Bose and Skolem systems cover every pair exactly once for all valid , andkirkman15()returns 7 days, each a partition of the 15 girls, with no pair repeated.
Related notes
- Constructive Algorithms
- Latin Squares and Euler’s 36 Officers (quasigroups are Latin squares)
- Exact Cover and Dancing Links