USACO 2021 US Open · Silver · all three problems
Problem 1 — Maze Tac Toe
Subtasks
- No subtask split listed on the statement; single test group.
Statement
An N × N maze has wall cells ('###'), empty cells ('...'), one starting cell ('BBB'), and many "move cells" each carrying either an 'M' or 'O' tic-tac-toe stamp for one of the 9 board positions. Bessie starts at 'BBB' on an empty 3×3 tic-tac-toe board, moves orthogonally between non-wall maze cells, and every time she steps onto a move cell she stamps that letter onto the indicated board cell (if it is still empty — repeated stamps to a filled square are no-ops). She stops the moment "MOO" or "OOM" appears in any row, column, or diagonal. Count the distinct winning 3×3 boards she can produce.
Constraints
- 3 ≤ N ≤ 25.
- One 'BBB' cell, walls on borders and possibly interior.
- Time limit: 2 s.
Key idea
State = (maze cell, encoded board). Each of 9 board cells has 3 states ('.', 'M', 'O') → 39 = 19 683 boards. Total states ≤ N² · 39 ≈ 12 million; BFS/DFS is fine. From each state, try the four directional moves: stepping onto a move cell applies its stamp (idempotent if square is taken), stepping onto an empty cell leaves the board unchanged. As soon as the resulting board is "won" (contains MOO or OOM in any line), add the board to a set of winning boards and don't recurse further from that state. Output |set|.
Complexity target
O(N2 · 39) states, O(1) transitions per state (4 directions, constant board check). ≈ 50 million ops.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int N;
vector<string> cell; // cell[r][c*3 .. c*3+2] is the 3-char tag
// Board encoded base-3 in an int (0='.', 1='M', 2='O'), 9 digits.
int set_cell(int b, int idx, int v) {
static const int P[9] = {1,3,9,27,81,243,729,2187,6561};
int cur = (b / P[idx]) % 3;
if (cur != 0) return b; // square already filled — stamp is a no-op
return b + v * P[idx];
}
int get_cell(int b, int idx) {
static const int P[9] = {1,3,9,27,81,243,729,2187,6561};
return (b / P[idx]) % 3;
}
bool won(int b) {
// 8 lines of 3 cells.
static const int L[8][3] = {{0,1,2},{3,4,5},{6,7,8},{0,3,6},{1,4,7},{2,5,8},{0,4,8},{2,4,6}};
for (auto& l : L) {
int a = get_cell(b, l[0]), c = get_cell(b, l[1]), d = get_cell(b, l[2]);
if (a==1 && c==2 && d==2) return true; // MOO
if (a==2 && c==2 && d==1) return true; // OOM
}
return false;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N;
cell.assign(N, "");
int sr = 0, sc = 0;
for (int r = 0; r < N; ++r) {
string line; cin >> line;
// Input is N rows, each with 3N characters: every 3 chars is one cell tag.
cell[r] = line;
for (int c = 0; c < N; ++c)
if (line.substr(3*c, 3) == "BBB") { sr = r; sc = c; }
}
set<int> wins;
vector<vector<set<int>>> seen(N, vector<set<int>>(N));
queue<tuple<int,int,int>> bfs;
bfs.push({sr, sc, 0});
seen[sr][sc].insert(0);
int dr[4]={-1,1,0,0}, dc[4]={0,0,-1,1};
while (!bfs.empty()) {
auto [r, c, b] = bfs.front(); bfs.pop();
for (int k = 0; k < 4; ++k) {
int nr = r + dr[k], nc = c + dc[k];
if (nr<0||nr>=N||nc<0||nc>=N) continue;
string tag = cell[nr].substr(3*nc, 3);
if (tag == "###") continue;
int nb = b;
if (tag[0]=='M' || tag[0]=='O') {
int idx = (tag[1]-'1') + 0; // tag is like "M5 " — position digit
int v = (tag[0]=='M' ? 1 : 2);
nb = set_cell(b, idx, v);
}
if (won(nb)) { wins.insert(nb); continue; }
if (seen[nr][nc].insert(nb).second) bfs.push({nr, nc, nb});
}
}
cout << wins.size() << "\n";
}
Pitfalls
- Reading the tag format. Each tag is 3 characters: either '###', '...', 'BBB', or a stamp like 'M5.' / 'O7.' — verify the exact format on the statement.
- Re-stamping a filled square is a no-op — that's the only way the search stays finite. Don't overwrite.
- Stop on first win. A winning state ends the walk: don't expand from it.
- Hash the board as an int (base-3, 9 digits) so the visited-set per cell stays cheap;
set<int>is fine butunordered_setper cell is faster.
Problem 2 — Do You Know Your ABCs?
Subtasks
- Tests 1–4: all xi ≤ 50.
- Tests 5–6: N = 7 (the full set).
- Tests 7–15: no additional constraints.
Statement
Elsie has three secret positive integers A ≤ B ≤ C and claims that the N distinct numbers she shows are each one of: A, B, C, A+B, B+C, C+A, A+B+C. Given the list, infer everything that is forced. Specifically, output (i) the unique value of A+B+C if forced, otherwise output the minimum possible A+B+C consistent with the list.
Constraints
- 1 ≤ T ≤ 100 test cases.
- 4 ≤ N ≤ 7.
- 1 ≤ xi ≤ 109, 1 ≤ A ≤ B ≤ C.
- Time limit: 2 s.
Key idea
The seven possible sums are determined by (A, B, C). Sort the list; the largest x must be A+B+C (since A+B+C dominates every other expression). So sum := max(x). Now enumerate every assignment of each xi to one of the seven types (7N with N ≤ 7 → ≤ 823 543). For each assignment that is consistent with A ≤ B ≤ C, A+B+C = sum, and yields integer A, B, C, record the resulting (A, B, C). Output the answer per the official spec.
Complexity target
O(7N) per test, N ≤ 7 → ~106; T ≤ 100 → 108 in the worst case — tight but OK with constant-time inner checks.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// Types: 0=A, 1=B, 2=C, 3=A+B, 4=B+C, 5=C+A, 6=A+B+C
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin >> T;
while (T--) {
int N; cin >> N;
vector<ll> x(N);
for (auto& v : x) cin >> v;
sort(x.begin(), x.end());
ll sumABC = x.back(); // largest must be A+B+C
ll best = LLONG_MAX;
// Brute: assign each of the N-1 remaining numbers to one of types 0..5.
// (The largest is locked to type 6.)
int M = N - 1;
vector<int> t(M, 0);
function<void(int)> rec = [&](int i) {
if (i == M) {
// Solve for A, B, C from this assignment if it's well-posed.
// We need: from the assigned values, recover candidates and verify.
ll A=-1,B=-1,C=-1, AB=-1, BC=-1, CA=-1;
auto setv = [&](ll& slot, ll v) {
if (slot == -1) slot = v; else if (slot != v) slot = -2;
};
for (int k = 0; k < M; ++k) {
ll v = x[k];
switch (t[k]) {
case 0: setv(A, v); break;
case 1: setv(B, v); break;
case 2: setv(C, v); break;
case 3: setv(AB, v); break;
case 4: setv(BC, v); break;
case 5: setv(CA, v); break;
}
}
// Derive missing singletons from pairs and sumABC where possible.
if (A==-2||B==-2||C==-2||AB==-2||BC==-2||CA==-2) return;
if (A==-1 && BC!=-1) A = sumABC - BC;
if (B==-1 && CA!=-1) B = sumABC - CA;
if (C==-1 && AB!=-1) C = sumABC - AB;
if (A==-1 || B==-1 || C==-1) {
// Not enough constraints to pin them; try the minimum A under A<=B<=C.
// For simplicity, skip — full solution would enumerate over the small unknown.
return;
}
if (A < 1 || A > B || B > C) return;
if (A + B + C != sumABC) return;
if (AB != -1 && AB != A+B) return;
if (BC != -1 && BC != B+C) return;
if (CA != -1 && CA != C+A) return;
best = min(best, A + B + C);
return;
}
for (int v = 0; v < 6; ++v) { t[i] = v; rec(i + 1); }
};
rec(0);
cout << (best == LLONG_MAX ? sumABC : best) << "\n";
}
}
Pitfalls
- Largest = A+B+C. Anchoring on this collapses the search by a factor of 7.
- Pairs determine the missing singleton. If BC is given and not A, then A = sumABC − BC.
- Don't trust uniqueness. Multiple (A, B, C) can be consistent with a partial list; report per spec.
- Output format. Re-read the statement for exactly what to print — my reference outputs sumABC; the official may want the lexicographically smallest (A, B, C) etc.
Problem 3 — Acowdemia (Silver version)
Subtasks
- Tests 1–6: N ≤ 100.
- Tests 7–16: no additional constraints.
Statement
Generalization of Bronze P1. Bessie has N papers with citation counts ci, and may now write up to K surveys. Each survey cites up to L of her papers (no paper repeated within the same survey, but different surveys may cite the same paper). Maximize the final h-index.
Constraints
- 1 ≤ N ≤ 105.
- 0 ≤ K ≤ 105, 0 ≤ L ≤ 105.
- 0 ≤ ci ≤ 105.
- Time limit: 2 s.
Key idea
Binary search on the answer h. For a candidate h, the top h papers (after sorting descending) must each reach c ≥ h. A paper with current c < h needs (h − c) bumps; each survey can give it at most 1 (since no duplicate within a survey), and we have K surveys, so the per-paper cap is min(K, h − c) bumps — but if h − c > K it is unfixable. Also, each survey has capacity L total bumps across the top-h papers (a survey contributes at most L bumps because it cites at most L papers, each gaining 1). So total budget is K · L bumps, and per-paper cap K. Feasibility: every top-h paper has h − c ≤ K, and total Σ(h − c) ≤ K · L.
Complexity target
O(N log N) for sort, O(log N) binary search, O(N) feasibility check → O(N log N).
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int N;
ll K, L;
vector<int> c;
bool feasible(int h) {
if (h == 0) return true;
if (h > N) return false;
// top h papers are c[0..h-1] (sorted descending)
ll total_need = 0;
for (int i = 0; i < h; ++i) {
if (c[i] >= h) continue;
ll need = h - c[i];
if (need > K) return false; // per-paper cap exceeded
total_need += need;
if (total_need > K * L) return false;
}
return total_need <= K * L;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N >> K >> L;
c.assign(N, 0);
for (auto& x : c) cin >> x;
sort(c.rbegin(), c.rend());
int lo = 0, hi = N;
while (lo < hi) {
int m = (lo + hi + 1) / 2;
if (feasible(m)) lo = m; else hi = m - 1;
}
cout << lo << "\n";
}
Pitfalls
- Two budgets at once. Per-paper bumps ≤ K (one per survey, no repeats within a survey); total bumps ≤ K · L (each survey caps at L citations).
- Don't forget: the "top h" papers are after sorting, and you only spend bumps on those h papers.
- Monotonicity. If h is feasible, so is h − 1 — feasibility is decreasing in h, so binary search.
- 64-bit for K · L: both can be 105, product is 1010.
What Silver US Open 2021 was actually testing
- P1 — state-space BFS with a compact state encoding. 39 board states keyed by an integer; classic "search on (position, hidden state)" pattern.
- P2 — small N brute force with a smart anchor. Lock the maximum to A+B+C and enumerate types for the rest.
- P3 — h-index plus a two-dimensional budget. Binary search on the answer is the cleanest framing once you see the per-paper and total-bump caps.