USACO 2018 US Open · Bronze · all three problems
Problem 1 — Team Tic Tac Toe
Statement
The barn's 3×3 tic-tac-toe board is fully filled — each cell holds a single uppercase letter A–Z, the initial of the cow that claimed it. Count two things:
- The number of single cows who win a row, column, or diagonal (all three cells share that cow's letter).
- The number of unordered pairs of distinct cows {X, Y} that together win some row, column, or diagonal — i.e. every cell of that line is either X or Y, and both letters appear in it.
Constraints
- Board is exactly 3×3, every cell is one of 26 uppercase letters.
- Time limit: 2s. (Trivially small input.)
Key idea
There are exactly 8 winning lines (3 rows + 3 cols + 2 diagonals). For each line, collect the set of
distinct letters appearing in it. If the set has size 1, that cow wins solo. If the set has size 2, that
unordered pair wins as a team. Dedupe across lines using a std::set of letters and a
std::set of pairs.
Complexity target
O(1). It's 8 lines × 3 cells.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<string> g(3);
for (auto& r : g) cin >> r;
// Eight winning lines as triples of (row, col).
vector<array<pair<int,int>,3>> lines = {
{{{0,0},{0,1},{0,2}}}, {{{1,0},{1,1},{1,2}}}, {{{2,0},{2,1},{2,2}}},
{{{0,0},{1,0},{2,0}}}, {{{0,1},{1,1},{2,1}}}, {{{0,2},{1,2},{2,2}}},
{{{0,0},{1,1},{2,2}}}, {{{0,2},{1,1},{2,0}}},
};
set<char> solo;
set<pair<char,char>> team; // ordered pair (min,max) to dedupe
for (auto& ln : lines) {
set<char> s;
for (auto [r, c] : ln) s.insert(g[r][c]);
if (s.size() == 1) solo.insert(*s.begin());
else if (s.size() == 2) {
auto it = s.begin();
char a = *it++; char b = *it;
team.insert({min(a, b), max(a, b)});
}
}
cout << solo.size() << "\n" << team.size() << "\n";
}
Pitfalls
- Solo wins don't also count as team wins. A line of all-A's contributes to solo only; the team set must have two distinct letters.
- Dedupe pairs. If both diagonals are won by the same {A,B} team, that's still one pair, not two.
- Pairs are unordered. {A,B} == {B,A}. Store as (min, max).
- No need to enumerate all 26²/2 pairs. Only pairs that actually appear on a line can win.
Problem 2 — Milking Order
Statement
There are N cows numbered 1..N. Farmer John has observed a fixed hierarchy among M of them — a sequence h1, h2, ..., hM that must appear in that relative order in the final milking order (not necessarily contiguous). Additionally, K cows have hard-coded positions: cow cj must occupy slot pj. Output the smallest slot that cow 1 can occupy in any milking order satisfying both kinds of constraints. A valid order is guaranteed to exist.
Constraints
- 2 ≤ N ≤ 100, 1 ≤ M < N, 1 ≤ K < N.
- Positions pj are distinct, 1 ≤ pj ≤ N.
- Time limit: 2s.
Key idea
N is tiny. Just try each slot s = 1..N in order: place cow 1 at slot s, honor all fixed-position assignments, then greedily fill the remaining slots left-to-right with the lowest hierarchy cow whose preceding hierarchy cows have already been placed. If the hierarchy can be threaded through the free slots in order while respecting cow 1's pinned position, s is feasible. Output the first such s.
Concretely: scan slots 1..N. At each free slot, if the next undelivered hierarchy cow is allowed here (not blocked by a pin), place it. Mark cow 1's slot as forbidden for the hierarchy unless cow 1 is the next hierarchy cow.
Complexity target
O(N²) trials in the worst case — fine for N ≤ 100.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int N, M, K;
vector<int> H; // hierarchy sequence, 1-indexed cow ids
map<int,int> pin; // slot -> cow (hard placements)
map<int,int> pinCow; // cow -> slot
bool feasible(int slotForOne) {
// Build slot assignments: pinned cows + cow 1 at slotForOne.
vector<int> slot(N + 2, 0); // slot[i] = cow at slot i, 0 = empty
for (auto [s, c] : pin) slot[s] = c;
if (slot[slotForOne] != 0 && slot[slotForOne] != 1) return false;
slot[slotForOne] = 1;
// Sweep left-to-right; thread the hierarchy H through the empty slots.
int hi = 0; // next hierarchy index to place
for (int s = 1; s <= N; ++s) {
if (slot[s] != 0) {
// A pinned cow (or cow 1) sits here. If it's the next hierarchy cow, advance.
if (hi < (int)H.size() && slot[s] == H[hi]) ++hi;
continue;
}
// Empty slot: if the next hierarchy cow has no other pinned slot, drop it here.
if (hi < (int)H.size()) {
int c = H[hi];
if (pinCow.count(c)) return false; // hierarchy cow is pinned elsewhere
if (c == 1) return false; // cow 1 must be at slotForOne, not here
++hi;
}
}
return hi == (int)H.size();
}
int main() {
cin >> N >> M >> K;
H.assign(M, 0);
for (auto& x : H) cin >> x;
for (int i = 0; i < K; ++i) {
int c, p; cin >> c >> p;
pin[p] = c; pinCow[c] = p;
}
// If cow 1 is pinned, that's the only feasible slot.
if (pinCow.count(1)) { cout << pinCow[1] << "\n"; return 0; }
for (int s = 1; s <= N; ++s) if (feasible(s)) { cout << s << "\n"; return 0; }
}
Pitfalls
- Hierarchy is a subsequence, not contiguous. Don't require the M cows to be adjacent in the final order.
- Pinned cow 1. If cow 1 is among the K pinned cows, its slot is forced — output that slot immediately.
- A pinned cow that is also in the hierarchy consumes the next hierarchy step when its pin is reached. Otherwise you'll insist on placing it again later.
- Greedy-place at the first empty slot: pushing the next hierarchy cow as far left as possible is what makes cow 1's slot as early as possible.
Problem 3 — Family Tree
Statement
There are N mother-child pairs forming a family tree (each cow has at most one mother in the input). Given two query cows X and Y, classify their relationship as one of:
SIBLINGS— same mother.COUSINS— same depth in the tree relative to their lowest common ancestor, but not siblings.X is the [great-…-]mother of Y(or vice versa) — direct ancestor.X is the [great-…-]aunt of Y(or vice versa) — X is a sibling of an ancestor of Y.NOT RELATED— no common ancestor in the input.
Constraints
- 1 ≤ N ≤ 100. Names are up to 10 uppercase letters.
- Two query names are given; both appear in the input as a mother or child.
- Time limit: 2s.
Key idea
Tiny N — just walk up the chain. For each cow store its mother (or none). For the query (X, Y), compute the list of ancestors of each (including self at depth 0). Find the lowest common ancestor by intersecting: walk from X collecting depths, then walk from Y looking for the first match. Let dX, dY be the depths from the LCA. Then:
- If LCA = X and dX = 0: X is an ancestor of Y → "mother / great-mother / ..." (use dY − 1 "great"s, then "grand-mother" once we hit dY ≥ 2).
- Symmetric if LCA = Y.
- If dX ≥ 1 and dY ≥ 1 and one of them is 1: aunt/great-aunt with min(dX, dY) on the aunt side.
- If dX = dY ≥ 1: siblings (if d = 1) or cousins (if d ≥ 2).
- If no LCA at all: NOT RELATED.
Complexity target
O(N) per query — read N pairs, two walks up the tree.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
map<string, string> mom; // child -> mother
// Return list of (ancestor, depth) starting from x (depth 0) walking up.
vector<pair<string,int>> chain(const string& x) {
vector<pair<string,int>> r;
string cur = x; int d = 0;
while (true) { r.push_back({cur, d}); if (!mom.count(cur)) break; cur = mom[cur]; ++d; }
return r;
}
int main() {
int N; string X, Y; cin >> N >> X >> Y;
for (int i = 0; i < N; ++i) { string m, c; cin >> m >> c; mom[c] = m; }
auto cx = chain(X), cy = chain(Y);
map<string,int> depthInX;
for (auto& [a, d] : cx) depthInX[a] = d;
// Find LCA: first ancestor of Y also present in X's chain.
int dY = -1, dX = -1; string lca;
for (auto& [a, d] : cy) if (depthInX.count(a)) { lca = a; dY = d; dX = depthInX[a]; break; }
if (dY == -1) { cout << "NOT RELATED\n"; return 0; }
if (dX == 0 && dY == 0) { cout << "SIBLINGS\n"; return 0; } // X == Y; problem usually rules this out
if (dX == 0) {
// X is ancestor of Y at depth dY.
if (dY == 1) cout << X << " is the mother of " << Y << "\n";
else { cout << X << " is the "; for (int i = 0; i < dY - 2; ++i) cout << "great-"; cout << "grand-mother of " << Y << "\n"; }
return 0;
}
if (dY == 0) {
if (dX == 1) cout << Y << " is the mother of " << X << "\n";
else { cout << Y << " is the "; for (int i = 0; i < dX - 2; ++i) cout << "great-"; cout << "grand-mother of " << X << "\n"; }
return 0;
}
if (dX == dY) { cout << (dX == 1 ? "SIBLINGS" : "COUSINS") << "\n"; return 0; }
// Aunt case: smaller depth side is the aunt.
const string& aunt = (dX < dY) ? X : Y;
const string& niece = (dX < dY) ? Y : X;
int da = min(dX, dY);
if (da == 1) cout << aunt << " is the aunt of " << niece << "\n";
else { cout << aunt << " is the "; for (int i = 0; i < da - 1; ++i) cout << "great-"; cout << "aunt of " << niece << "\n"; }
}
Pitfalls
- Exact output wording matters. Bronze graders are strict — match the official sample format precisely, including capitalization and hyphens.
- Direction of ancestry. If X is Y's mother, the output names X first. If Y is X's mother, names Y first. Don't hard-code one direction.
- "great-" count. dY = 1 → "mother". dY = 2 → "grand-mother". dY = 3 → "great-grand-mother". (Off-by-one if you're not careful.)
- NOT RELATED also covers cousins of cousins if and only if there is truly no common ancestor in the input.
What Bronze US Open 2018 was actually testing
- P1 — careful enumeration with deduping. Eight lines, two kinds of wins, an unordered-pair set.
- P2 — brute-force a position + greedy feasibility check. N ≤ 100 means O(N²) trials is free.
- P3 — walk the tree, classify cases. The algorithm is easy; the output formatting is the actual challenge.