USACO 2020 US Open · Silver · all three problems
Problem 1 — Social Distancing
Statement
N cows must each occupy a distinct integer position on a number line. The allowed positions are the union of M mutually-disjoint intervals [aj, bj] (no two intervals overlap or touch). Maximize the minimum pairwise distance between any two cows.
Constraints
- 2 ≤ N ≤ 105.
- 1 ≤ M ≤ 105.
- 0 ≤ a ≤ b ≤ 1018.
- Time limit: 2s.
Key idea
Binary search on the answer D. For a given D, greedily place cows from left to right across
sorted intervals: in each interval, the first feasible position is max(intervalStart,
lastPlaced + D); from there step by D until you exit the interval. Total cows fittable is
monotone-decreasing in D.
Complexity target
O((N + M) log(max coordinate)) — binary search over [1, 1018] with a linear feasibility check.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int N, M;
vector<pair<ll,ll>> iv;
bool ok(ll D) {
ll placed = 0, last = LLONG_MIN / 4;
for (auto& [a, b] : iv) {
ll pos = max(a, last + D);
if (pos > b) continue;
// count placements in [pos, b] stepping by D
ll cnt = (b - pos) / D + 1;
placed += cnt;
last = pos + (cnt - 1) * D;
if (placed >= N) return true;
}
return placed >= N;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N >> M;
iv.resize(M);
for (auto& [a, b] : iv) cin >> a >> b;
sort(iv.begin(), iv.end());
ll lo = 1, hi = iv.back().second - iv.front().first;
while (lo < hi) {
ll mid = lo + (hi - lo + 1) / 2;
if (ok(mid)) lo = mid;
else hi = mid - 1;
}
cout << lo << "\n";
}
Pitfalls
- Coordinates up to 1018. Use
long longeverywhere; intermediatelast + Din the worst case is still ≤ 2·1018, safe for signed 64-bit. - Greedy in batches. Don't loop position-by-position inside an interval — compute the count via division and jump.
- Upper bound for binary search. Span of the outermost interval edges; tighter than 1018 and avoids overflow when stepping.
- D = 0 is trivially feasible. Start
lo = 1.
Problem 2 — Cereal
Statement
Each of N cows has a 1st-choice cereal fi and 2nd-choice cereal si, out of M cereal boxes. Cows arrive in order; each takes its 1st choice if available, else its 2nd choice, else leaves empty. For each starting index k = 0..N−1, output the number of cows that get a cereal if only cows k, k+1, …, N−1 line up (in that order).
Constraints
- 1 ≤ N, M ≤ 105.
- 1 ≤ fi, si ≤ M, fi ≠ si.
- Time limit: 2s.
Key idea
Process cows from last to first. Maintain owner[b] = the cow currently holding cereal b. When cow i tries to grab fi: if free, take it. Else "displace" the current owner, who then recursively tries its second choice s — that displacement chain is bounded amortized because each cow is "kicked" at most once per box, and each cow has only two choices total. So overall O(N+M) across all 1..N suffixes. After processing cow k, the number of cows holding a box is the answer for suffix k.
Complexity target
O((N + M) · α) total work; each cow occupies at most 2 boxes during its lifetime in the chain.
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<int> f(N), s(N);
for (int i = 0; i < N; ++i) cin >> f[i] >> s[i];
vector<int> owner(M + 1, -1); // owner[b] = cow index, or -1
vector<int> ans(N);
int served = 0;
// Insert cow i: try its first choice; if taken, displace and the displaced
// cow tries its second choice (and it has none beyond that, so it leaves).
auto place = [&](int i) {
int box = f[i];
if (owner[box] == -1) { owner[box] = i; ++served; return; }
int displaced = owner[box];
owner[box] = i; // newcomer gets first choice
// displaced now tries its second choice
int b2 = s[displaced];
if (owner[b2] == -1) { owner[b2] = displaced; }
else { --served; } // displaced leaves
};
// Wait — that's only right if we insert in arrival order. We want suffix
// answers, so insert cows from i = N-1 down to 0 and at each step record
// ans[i] = served. But "arrival order" within the suffix is i, i+1, ...
// so equivalently process forward but recompute. Simpler: insert in
// reverse and use the same priority rule (newcomer wins first choice).
for (int i = N - 1; i >= 0; --i) {
place(i);
ans[i] = served;
}
for (int v : ans) cout << v << "\n";
}
Pitfalls
- Suffix vs prefix. The problem asks for suffix-starting-at-k, which is why inserting in reverse with "newcomer wins first choice" produces the right answer for each k.
- One-deep displacement. Each cow has only two choices, so a displaced cow can't displace anyone else along its second-choice slot beyond one more step.
- Don't simulate from scratch per k. That's O(N·M) — TLE for 105.
- Index domain. Boxes are 1-indexed up to M; allocate size M + 1.
Problem 3 — The Moo Particle
Statement
N "moo particles" each have spin (xi, yi). Two particles i, j can annihilate each other when xi ≤ xj and yi ≤ yj (or with i, j swapped). Find the minimum number of particles remaining after performing any sequence of pairwise annihilations.
Constraints
- 1 ≤ N ≤ 105.
- |xi|, |yi| ≤ 109.
- Time limit: 2s.
Key idea
Sort particles by x (break ties by y). Walk through and count "y-descents" — positions where the running max-y resets, i.e., the current y is strictly less than the maximum y seen so far. The answer is 1 + number of such descents. Intuition: particles that share a chain under the partial order (≤ in both coords) all collapse to one survivor; chains correspond to maximal increasing runs in y after sorting by x.
Complexity target
O(N log N) for the sort.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N; cin >> N;
vector<pair<ll,ll>> p(N);
for (auto& [x, y] : p) cin >> x >> y;
sort(p.begin(), p.end()); // by x asc, then y asc
int components = 1;
ll runMaxY = p[0].second;
for (int i = 1; i < N; ++i) {
if (p[i].second < runMaxY) {
// strict descent: current particle is incomparable with the
// maximum-y so far in this x-block — new chain
++components;
}
runMaxY = max(runMaxY, p[i].second);
}
cout << components << "\n";
}
Pitfalls
- Strict vs non-strict. Equal y after sorting by x ascending is still comparable (≤ holds both ways), so don't count it as a descent.
- Ties in x. Sorting by (x, y) means equal-x particles arrive y-ascending — they extend the same chain.
- Empty input. N ≥ 1; if N = 1 the answer is 1.
- Don't run union-find with O(N²) pairs. That's the obvious wrong approach; sort gives O(N log N).
What Silver 2020 US Open was actually testing
- P1 — binary search + greedy. Same shape as Bronze P1 but with 1018 coordinates that force batched placement.
- P2 — amortized data structure. Each cow displaces at most one, giving O((N+M)) total work despite the seeming complexity.
- P3 — partial order on the plane. Sort by one coordinate, observe descents in the other; count chains.