USACO 2018 US Open · Silver · all three problems
Problem 1 — Out of Sorts
Statement
Bessie implements bubble sort. Each outer iteration of her loop prints "moo", then performs a single left-to-right pass that swaps adjacent out-of-order pairs; the loop exits when a pass makes no swaps. Given the initial array A, output how many times "moo" is printed.
Constraints
- 1 ≤ N ≤ 105.
- 0 ≤ A[i] ≤ 109. Elements may repeat.
- Time limit: 2s.
Key idea
The classic identity: a single left-to-right bubble pass moves the largest unsorted element fully to its
correct position, and moves every other element at most one slot left. So the number of passes
needed equals 1 + max over i of (i − possorted(A[i])) — i.e. one plus the largest
leftward displacement any element has to travel. The final "check" pass with no swaps adds one more "moo".
With ties, break the sorted ordering by original index so equal elements keep their relative order.
Complexity target
O(N log N): sort pairs (value, index), take max(i − sortedRank[i]).
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<pair<long long,int>> a(N);
for (int i = 0; i < N; ++i) { cin >> a[i].first; a[i].second = i; }
// Stable sort by value; ties broken by original index.
sort(a.begin(), a.end());
// For each original element now at sorted rank j, its old index was a[j].second.
// Bubble passes >= max over j of (a[j].second - j), then +1 for the silent final pass.
int maxShift = 0;
for (int j = 0; j < N; ++j) maxShift = max(maxShift, a[j].second - j);
cout << maxShift + 1 << "\n";
}
Pitfalls
- Ties matter. Use index as a tiebreaker (a stable sort on values already does this for
std::sorton pairs). - Off-by-one. The answer is max leftward shift + 1 — the +1 is the final passive pass that prints "moo" but swaps nothing.
- Don't simulate. N² = 1010; TLE.
- Already sorted → max shift = 0, answer = 1.
Problem 2 — Lemonade Line
Statement
N cows want lemonade. Cow i has a "patience" wi: it will only join the line if at the moment of its arrival the line already contains at most wi cows; otherwise it leaves forever. FJ chooses the order in which cows arrive. What is the minimum number of cows that will end up in the line?
Constraints
- 1 ≤ N ≤ 105.
- 0 ≤ wi ≤ 109.
- Time limit: 2s.
Key idea
Sort patience values in descending order. Greedily admit the patient cows first: the k-th admitted cow sees k − 1 cows already in line. So admit cow k iff wk ≥ k − 1. Stop the first time the condition fails; the answer is the count admitted up to that point.
Why "minimum"? FJ chooses the arrival order adversarially against admission count. Sending the most patient cows first packs the most "stayers" early; the moment the line has more cows than the next cow's patience, that cow and every less-patient cow leaves.
Complexity target
O(N log N) sort + O(N) scan.
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<long long> w(N);
for (auto& x : w) cin >> x;
sort(w.begin(), w.end(), greater<long long>());
int joined = 0;
for (int i = 0; i < N; ++i) {
if (w[i] >= (long long)joined) ++joined;
else break;
}
cout << joined << "\n";
}
Pitfalls
- Strictly "minimum". Maximum admission is trivially N when patient enough; the problem asks for the order minimizing admissions, which is also the "patient-first" order — but you must stop at the first refusal.
- ≥ vs >. The cow joins if line length ≤ patience. Cow at admission slot k (0-indexed) sees k cows. So condition is
w >= k. - 64-bit not strictly needed (counts fit in int), but patience values up to 109 fit in
long longsafely.
Problem 3 — Multiplayer Moo
Statement
An N×N grid is filled with cow IDs (each cell holds an integer 0..106). A "region" is a 4-connected (up/down/left/right) maximal set of cells all sharing some ID. Output:
- Single-cow: the size of the largest region of identical IDs.
- Two-cow team: the size of the largest 4-connected component using cells of exactly two distinct IDs (you choose which pair to maximize).
Constraints
- 1 ≤ N ≤ 250 (so up to 62,500 cells).
- 0 ≤ ID ≤ 106.
- Time limit: 3s.
Key idea
Step 1 — flood fill to label each maximal single-ID region with a component id. Track size of each
component and the ID it represents. The largest single-cow region is just max(size).
Step 2 — build the "component graph": two components are adjacent if any pair of their cells is orthogonally adjacent in the grid. For each pair of distinct IDs (A, B) that share at least one component-graph edge, BFS over the union of components labelled A or B that are connected through other A/B components. Answer is the max BFS size.
Practical implementation: for each component C with ID A, look at its neighbours in the component graph; for each neighbour ID B ≠ A, start a BFS over the union "components with ID ∈ {A, B}" reachable from C using only A/B edges, summing their cell counts. Cache (C, B) so each pair is processed once.
Complexity target
Up to N² = 62,500 cells. The component graph has at most O(N²) nodes and O(N²) edges. Pair-BFS in the worst case touches every edge for every pair, but the number of distinct (A, B) seed pairs is bounded by the edge count of the component graph, giving overall O(N² · avgDeg) — fast enough.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int N;
vector<vector<int>> g; // grid IDs
vector<vector<int>> comp; // comp id per cell, -1 unset
vector<int> compSize, compID; // per component
vector<set<int>> adj; // adj[c] = component-ids touching c
int dx[4] = {0,0,1,-1}, dy[4] = {1,-1,0,0};
void floodComp(int sr, int sc, int cid) {
queue<pair<int,int>> q; q.push({sr, sc}); comp[sr][sc] = cid;
int v = g[sr][sc], sz = 0;
while (!q.empty()) {
auto [r, c] = q.front(); q.pop(); ++sz;
for (int d = 0; d < 4; ++d) {
int nr = r + dx[d], nc = c + dy[d];
if (nr < 0 || nc < 0 || nr >= N || nc >= N) continue;
if (g[nr][nc] == v && comp[nr][nc] == -1) { comp[nr][nc] = cid; q.push({nr, nc}); }
}
}
compSize.push_back(sz); compID.push_back(v);
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N;
g.assign(N, vector<int>(N));
comp.assign(N, vector<int>(N, -1));
for (auto& row : g) for (auto& x : row) cin >> x;
for (int r = 0; r < N; ++r) for (int c = 0; c < N; ++c)
if (comp[r][c] == -1) floodComp(r, c, compSize.size());
int C = compSize.size();
adj.assign(C, {});
for (int r = 0; r < N; ++r) for (int c = 0; c < N; ++c)
for (int d = 0; d < 4; ++d) {
int nr = r + dx[d], nc = c + dy[d];
if (nr < 0 || nc < 0 || nr >= N || nc >= N) continue;
if (comp[nr][nc] != comp[r][c]) adj[comp[r][c]].insert(comp[nr][nc]);
}
int best1 = *max_element(compSize.begin(), compSize.end());
int best2 = best1; // a single component is a valid degenerate "two-cow" region only if it shares
// an edge with another ID; we'll improve it below.
// BFS in component graph restricted to ID pair {A, B}.
auto pairBfs = [&](int start, int A, int B) {
vector<char> vis(C, 0); queue<int> q; q.push(start); vis[start] = 1;
int tot = 0;
while (!q.empty()) {
int u = q.front(); q.pop(); tot += compSize[u];
for (int v : adj[u]) if (!vis[v] && (compID[v] == A || compID[v] == B)) { vis[v] = 1; q.push(v); }
}
return tot;
};
for (int u = 0; u < C; ++u) for (int v : adj[u]) if (v > u) {
int A = compID[u], B = compID[v];
if (A == B) continue;
best2 = max(best2, pairBfs(u, A, B));
}
cout << best1 << "\n" << best2 << "\n";
}
Pitfalls
- "Exactly two IDs" is permissive. A team region may use components of either of the two IDs in any mix; you only need at least one of each across the union. Verify on the sample.
- Don't recompute connectivity per pair from scratch on the grid. Build the component graph once; BFS in component space is much cheaper.
- Memo per seed pair, not per component pair. Multiple seeds may belong to the same {A, B} super-region; visiting flags within a single BFS handle that automatically.
- Single-ID baseline. Initialise best2 with best1 — a "team" can degenerate when one component is huge and any adjacent component bumps the size.
What Silver US Open 2018 was actually testing
- P1 — sorting insight, not simulation. Pass count = max leftward displacement + 1.
- P2 — sort + greedy. Patient cows first; cutoff at first refusal.
- P3 — flood fill + component graph. Step up from grid BFS to BFS over labelled components.