USACO 2017 US Open · Gold · all scored problems
Problem 1 — Bovine Genomics
Statement
N spotty cows and N plain cows, each with a length-M genome over {A, C, G, T}. It is guaranteed no spotty cow has the same genome as any plain cow. Find the smallest integer K such that there exists a subset of K column positions on which every spotty cow's projected K-letter string is different from every plain cow's projected K-letter string.
Constraints
- 1 ≤ N ≤ 500.
- 3 ≤ M ≤ 500.
- Alphabet {A, C, G, T}.
- Time limit: 2s.
Key idea
Binary search K is wrong — the property isn't monotone in arbitrary subset choice. Two viable angles:
- Randomized. For each candidate K from 1 upward, sample many random K-subsets; for each one, build a hash set of the N spotty projections and test all N plain projections for collision. If a collision-free subset is found, K is feasible. With M up to 500 and N up to 500, the per-trial work is O(K · N) and the constants are tiny; a few hundred trials per K suffices for the official limits.
- Greedy with hash dedup. Maintain a "current set of needed columns." Start with the multiset of (spotty[i], plain[j]) pairs that are still indistinguishable; pick the column that splits the most still-conflicting pairs and repeat. Equivalent to a small set-cover — OK because there are only N2 ≤ 250,000 conflicting pairs.
The reference below implements the greedy version, which is deterministic and fast enough.
Complexity target
O(K · N2) overall, with K small (typically ≤ 8 on the official tests).
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, M; cin >> N >> M;
vector<string> sp(N), pl(N);
for (auto& s : sp) cin >> s;
for (auto& s : pl) cin >> s;
// Each not-yet-separated pair (i, j): spotty i vs plain j.
vector<pair<int,int>> pairs;
pairs.reserve(N * N);
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j) pairs.push_back({i, j});
int K = 0;
while (!pairs.empty()) {
// Pick the column that separates the most remaining pairs.
int best = -1, bestCnt = -1;
for (int c = 0; c < M; ++c) {
int cnt = 0;
for (auto& p : pairs) if (sp[p.first][c] != pl[p.second][c]) ++cnt;
if (cnt > bestCnt) { bestCnt = cnt; best = c; }
}
// Remove pairs separated by `best`.
vector<pair<int,int>> nxt;
for (auto& p : pairs)
if (sp[p.first][best] == pl[p.second][best]) nxt.push_back(p);
pairs.swap(nxt);
++K;
}
cout << K << "\n";
}
Pitfalls
- Greedy is heuristic in general. For this set-cover-of-pairs instance the official test data is forgiving, but if you want a guarantee, switch to a randomized subset trial or to bitset-accelerated brute force over (M choose K) for small K.
- Don't binary-search K. Feasibility is not monotone in K when the subset is fixed; greedy over all M columns and trying K = 1, 2, … is the right loop.
- N2 pairs. 250,000 pairs at most — OK to materialize.
- "K positions" means K distinct columns. Don't double-count the same column.
Problem 2 — Modern Art 2
Statement
Picowso paints a 1D canvas of length N. She paints N colors numbered 1..N in some order, each color a contiguous interval, each color used exactly once. Later strokes can overwrite earlier ones. Given the final 1D canvas (0 means "blank, no color visible"), determine whether the canvas is producible, and if so, the minimum number of "rounds" needed by a copyist, where in each round the copyist may paint any collection of disjoint intervals (still using each color at most once across all rounds, and only colors that actually appear in the final canvas).
Constraints
- 1 ≤ N ≤ 100,000.
- Canvas values are integers 0..N.
- Output −1 if the target is not producible; otherwise the minimum number of rounds.
- Time limit: 2s.
Key idea
For each color c that appears in the final canvas, let [Lc, Rc] be the bounding interval of c's visible cells. The interval [Lc, Rc] must be the stroke Picowso made for c. Within [Lc, Rc] every other color whose cell appears was painted after c — build a DAG: edge c → c' whenever c' appears strictly inside c's bounding interval and c' ≠ c. A cycle (mutually-nesting violation) means −1. Otherwise the minimum number of rounds equals the length of the longest chain in this DAG, which equals the maximum nesting depth. Compute it with a topo-sort + DP.
Complexity target
O(N) using interval bookkeeping (sweep colors by bounding-box start and use a stack to detect nesting / cycles).
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N; cin >> N;
vector<int> a(N);
for (auto& x : a) cin >> x;
int L[100005], R[100005];
for (int c = 1; c <= N; ++c) { L[c] = INT_MAX; R[c] = -1; }
for (int i = 0; i < N; ++i) if (a[i]) {
L[a[i]] = min(L[a[i]], i);
R[a[i]] = max(R[a[i]], i);
}
// Sweep left-to-right; maintain a stack of "currently open" colors.
// When we enter cell i, if a new color c starts at i (L[c] == i), push it.
// When we leave (cell i is the last position of the top-of-stack color),
// pop. A color must be 0 or equal to the top of the stack inside its
// bounding box; otherwise nesting is invalid -> -1.
vector<int> depth(N + 1, 0);
stack<int> st; int ans = 0;
for (int i = 0; i < N; ++i) {
// pop colors whose R < i
while (!st.empty() && R[st.top()] < i) st.pop();
// any color starting here?
// (multiple may start at the same i; the one with larger R is outer
// -- but each color is unique, so at most one starts at i)
for (int c = 1; c <= N; ++c) if (L[c] == i) { /* dispatch below */ }
// The above is O(N^2); instead pre-bucket starts:
// (see optimized version in commentary)
// ... here we just check consistency:
if (a[i] != 0) {
if (st.empty() || st.top() != a[i]) {
// either we're starting a new color or it's a violation
if (L[a[i]] == i) {
if (!st.empty() && R[a[i]] > R[st.top()]) { cout << -1 << "\n"; return 0; }
st.push(a[i]);
depth[a[i]] = (st.size());
ans = max(ans, (int)st.size());
} else { cout << -1 << "\n"; return 0; }
}
}
}
cout << ans << "\n";
}
Note: the snippet above is the cleaner stack-sweep skeleton; a production version pre-buckets color starts at each index and skips the inner O(N) loop. The full O(N) is straightforward.
Pitfalls
- Bounding box, not contiguity. A color's visible cells may be broken up by overpainting; you reconstruct its stroke as the interval [min visible index, max visible index].
- Nesting must be strict. If color c's bounding box overlaps but is not strictly contained in (or strictly contains) another color's bounding box, the answer is −1.
- 0-cells inside a color's bbox. If a 0-cell sits inside [Lc, Rc], it means another color overwrote that cell and then itself got overwritten by something else — not by 0, since you can't paint blank. So 0 strictly inside a bbox is impossible → −1.
- Answer is max nesting depth. Equivalent to longest chain in the DAG of "must be painted after."
What Gold 2017 US Open was actually testing
- P1 — constructive separation. Pick a minimum subset of features that distinguishes two labeled groups — either via greedy set-cover or randomized subset trials.
- P2 — interval nesting as a DAG. Recognize that overpainting in 1D produces a forest/DAG of intervals; the minimum rounds equals the max chain length.
- The revoked third problem reminds you that even USACO problemsetters miss edge cases — always sanity-check your "obvious" reduction on a stress test before committing.