USACO 2016 US Open · Silver · all three problems
Problem 1 — Field Reduction
Statement
Same setup as Bronze P3: N cows at integer positions, remove exactly 3 cows to minimize the area of the axis-aligned bounding box of the remaining N−3 cows. Silver bumps N up so a brute O(N3) is no longer free; you must reduce to a small candidate set.
Constraints
- 4 ≤ N ≤ 50 000.
- 1 ≤ xi, yi ≤ 40 000.
- Time limit: 2s.
Key idea
Same observation as Bronze: only cows that hold one of the four extremes (min x, max x, min y, max y) are candidates for removal, and because you remove three, the next-rank extremes matter too. Keep the 3 smallest-x cows, 3 largest-x cows, 3 smallest-y cows, 3 largest-y cows — at most 12 distinct indices. Enumerate all C(≤12, 3) triples and for each recompute the bounding box of the remaining N−3 cows.
Complexity target
O(N) candidate extraction + O(220 · N) evaluation ≈ 107.
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> X(N), Y(N);
for (int i = 0; i < N; ++i) cin >> X[i] >> Y[i];
auto top3 = [&](function<bool(int,int)> cmp) {
vector<int> idx(N); iota(idx.begin(), idx.end(), 0);
partial_sort(idx.begin(), idx.begin()+3, idx.end(), cmp);
return vector<int>{idx[0], idx[1], idx[2]};
};
set<int> S;
for (int i : top3([&](int a, int b){return X[a]<X[b];})) S.insert(i);
for (int i : top3([&](int a, int b){return X[a]>X[b];})) S.insert(i);
for (int i : top3([&](int a, int b){return Y[a]<Y[b];})) S.insert(i);
for (int i : top3([&](int a, int b){return Y[a]>Y[b];})) S.insert(i);
vector<int> C(S.begin(), S.end());
int M = C.size();
long long ans = LLONG_MAX;
for (int a = 0; a < M; ++a)
for (int b = a+1; b < M; ++b)
for (int d = b+1; d < M; ++d) {
int s0=C[a], s1=C[b], s2=C[d];
int xl=INT_MAX, xh=INT_MIN, yl=INT_MAX, yh=INT_MIN;
for (int i = 0; i < N; ++i) {
if (i==s0||i==s1||i==s2) continue;
xl=min(xl,X[i]); xh=max(xh,X[i]);
yl=min(yl,Y[i]); yh=max(yh,Y[i]);
}
ans = min(ans, (long long)(xh-xl)*(yh-yl));
}
cout << ans << "\n";
}
Pitfalls
- Fast I/O. N = 50 000 with
cinworks only withsync_with_stdio(false); otherwise read withscanf. - Don't dedupe by coordinate. Different cows can have identical x or y; remove cows by index.
- Top-3, not top-1. Removing two cows tied for the extreme still leaves the third in place. You need at least 3 candidates per direction.
Problem 2 — Diamond Collector
Statement
Same N diamonds with sizes si. Bessie now has two display cases. She fills each case with a subset of diamonds whose max−min ≤ K, and the two cases must share no diamond. Maximize the total number of diamonds displayed across both cases.
Constraints
- 1 ≤ N ≤ 50 000.
- 0 ≤ K ≤ 109.
- 0 ≤ si ≤ 109.
- Time limit: 2s.
Key idea
Sort sizes. After sorting, each case is a contiguous range, and the two ranges are disjoint — so one lies entirely to the left of the other. For each index i, compute:
L[i]= max size of a valid range ending at i (right pointer i, left pointer found by two-pointer);bestL[i]= max ofL[0..i], the best left-case size when the split point is at most i;R[i]= max size of a valid range starting at i.
Answer = max over i of bestL[i-1] + R[i]. Linear after sorting.
Complexity target
O(N log N) sort + O(N) sweep.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false); cin.tie(nullptr);
int N; long long K;
cin >> N >> K;
vector<long long> s(N);
for (auto& x : s) cin >> x;
sort(s.begin(), s.end());
// R[i] = length of longest valid range starting at i.
vector<int> R(N+1, 0);
int j = 0;
for (int i = 0; i < N; ++i) {
if (j < i) j = i;
while (j < N && s[j] - s[i] <= K) ++j;
R[i] = j - i;
}
// bestL[i] = max over l ≤ i of length(longest valid range ending at l).
vector<int> bestL(N+1, 0);
int lp = 0;
for (int r = 0; r < N; ++r) {
while (s[r] - s[lp] > K) ++lp;
int len = r - lp + 1;
bestL[r+1] = max(bestL[r], len);
}
int ans = 0;
for (int i = 0; i < N; ++i)
ans = max(ans, bestL[i] + R[i]); // left case uses [0..i-1], right uses [i..]
cout << ans << "\n";
}
Pitfalls
- Two cases can be empty? Re-read: each case can hold zero or more diamonds.
bestLstarts at 0 so an empty left case is allowed. - Disjointness after sort = "split point". The whole reduction depends on the fact that, after sorting, two non-overlapping size-intervals must be linearly orderable. Don't try to track two disjoint subsets in the unsorted array.
- Don't recompute the right range from scratch each i. Use the
R[i]array; otherwise you blow up to O(N2).
Problem 3 — Closing the Farm
Statement
The farm has N barns connected by M bidirectional paths. FJ will close barns one at a time in a given order; when a barn is closed, it and all incident paths are removed. After each closure, report whether the remaining open barns still form a connected graph (output "YES" or "NO"). The initial graph (zero closures) is considered first.
Constraints
- 1 ≤ N ≤ 3000.
- 1 ≤ M ≤ 4000.
- Time limit: 2s.
Key idea
Process closures in reverse — the time-reversed sequence is a series of barn openings, where each opening adds a node and the edges connecting it to already-open barns. This is the classic offline "deletions become unions" trick, and a Union-Find handles it in near-linear time. After the reverse sequence, flip the YES/NO answers back to original order.
Complexity target
O((N + M) · α(N)) total.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
struct DSU {
vector<int> p, r;
DSU(int n): p(n), r(n,0) { iota(p.begin(), p.end(), 0); }
int f(int x){ return p[x]==x?x:p[x]=f(p[x]); }
bool u(int a, int b){
a=f(a); b=f(b); if(a==b) return false;
if(r[a]<r[b]) swap(a,b);
p[b]=a; if(r[a]==r[b]) ++r[a]; return true;
}
};
int main() {
ios_base::sync_with_stdio(false); cin.tie(nullptr);
int N, M; cin >> N >> M;
vector<vector<int>> adj(N);
for (int i = 0; i < M; ++i) {
int a, b; cin >> a >> b; --a; --b;
adj[a].push_back(b); adj[b].push_back(a);
}
vector<int> order(N);
for (auto& x : order) { cin >> x; --x; }
DSU dsu(N);
vector<bool> open(N, false);
vector<string> out(N);
int comps = 0;
for (int i = N - 1; i >= 0; --i) {
int v = order[i];
open[v] = true; ++comps;
for (int u : adj[v]) if (open[u]) if (dsu.u(u, v)) --comps;
out[i] = (comps == 1 ? "YES" : "NO");
}
for (auto& s : out) cout << s << "\n";
}
Pitfalls
- "Connected" counts only open barns. The DSU component count is over open nodes only; initialize
compsto 0 and bump on each opening. - Reverse order, then re-reverse the output. A common off-by-one is printing in reverse; store results in an array indexed by the forward step.
- 1-indexed input. All ids are 1-indexed in USACO; convert once at read time.
What Silver 2016 US Open was actually testing
- P1 — candidate-set pruning. Same observation as Bronze, just with N big enough that the naive triple-loop TLEs.
- P2 — sort + prefix/suffix bests. A canonical "two best disjoint intervals" pattern.
- P3 — offline reverse-time DSU. The first time most Silver solvers meet "deletions become unions" — an idea that returns at Gold and Platinum.