USACO 2021 December · Silver · all three problems
Problem 1 — Closest Cow Wins
Statement
A number line has N cows at positions pi, each with a tastiness ti. Farmer Nhoj already planted M patches at positions fj. Bessie now plants K patches at integer positions of her choice. A cow goes to the patch closest to it; ties go to Nhoj. Maximize the total tastiness of cows that go to a Bessie patch.
Constraints
- 1 ≤ N, M ≤ 2 · 105, 1 ≤ K ≤ 2 · 105.
- Positions in [1, 109]; 1 ≤ ti ≤ 109.
- Time limit: 2s.
Key idea
Sort Nhoj's patches; they cut the line into M+1 "gaps". Within one gap [L, R] between two Nhoj patches, placing one Bessie patch in the middle captures all cows in [L+1, midpoint−1] ∪ [midpoint+1, R−1] — i.e. all cows strictly inside the half-gap on each side. With more Bessie patches in the same gap, additional patches each capture another sub-segment. The marginal gain of the first patch in a gap is large; later ones are smaller. Precompute prefix tastiness sums; for every gap compute the score of placing 1 Bessie patch and the score of placing 2 patches (which is the whole gap minus boundary cows). Put all "first patch" gains and "second patch extra gain" values in a max-heap and pick the top K.
Complexity target
O((N + M) log(N + M) + K log M).
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 K, M, N;
cin >> K >> M >> N; // [verify input order] cpid=1158
vector<pair<ll, ll>> cows(N);
for (auto& [x, t] : cows) cin >> x >> t;
vector<ll> f(M);
for (auto& x : f) cin >> x;
sort(f.begin(), f.end());
sort(cows.begin(), cows.end());
// For a half-open range of cow indices [lo, hi), tastiness sum via prefix.
vector<ll> pref(N + 1, 0);
for (int i = 0; i < N; ++i) pref[i + 1] = pref[i] + cows[i].second;
auto sumIn = [&](ll a, ll b) -> ll {
// tastiness of cows with position in [a, b]
int lo = lower_bound(cows.begin(), cows.end(), make_pair(a, 0LL)) - cows.begin();
int hi = upper_bound(cows.begin(), cows.end(), make_pair(b, LLONG_MAX)) - cows.begin();
return pref[hi] - pref[lo];
};
priority_queue<ll> pq;
// Process M+1 gaps. Outside boundaries: cows left of f[0] and right of f[M-1].
for (int i = 0; i <= M; ++i) {
ll L = (i == 0) ? LLONG_MIN/2 : f[i - 1];
ll R = (i == M) ? LLONG_MAX/2 : f[i];
if (i == 0) { pq.push(sumIn(L, R - 1)); continue; }
if (i == M) { pq.push(sumIn(L + 1, R)); continue; }
ll mid = (L + R) / 2;
ll first = sumIn(L + 1, mid - 1); // capture half the gap
ll whole = sumIn(L + 1, R - 1);
pq.push(first);
pq.push(whole - first); // second patch fills the rest
}
ll ans = 0;
for (int i = 0; i < K && !pq.empty(); ++i) { ans += pq.top(); pq.pop(); }
cout << ans << "\n";
}
Pitfalls
- Tie goes to Nhoj. The midpoint of two Nhoj patches is captured by Nhoj, so Bessie's first patch in a gap can only capture the strict interior of one half.
- Outer gaps capture everything to the side. Left of the leftmost f and right of the rightmost f, one Bessie patch can capture all cows on that side.
- K may exceed the number of useful slots. Extra patches gain 0; the heap handles it.
- Integer overflow. Tastiness sums up to N · 109 = 2 · 1014 — long long.
Problem 2 — Connecting Two Barns
Statement
A farm has N fields and M bidirectional paths. Bessie wants to go from field 1 to field N. You may add up to 2 new paths; the cost of a path between fields a and b is (a − b)². Find the minimum total cost of added paths so that field 1 and field N become connected.
Constraints
- 1 ≤ T ≤ 20 test cases.
- 1 ≤ N ≤ 105, 0 ≤ M ≤ 105.
- Time limit: 2s.
Key idea
Run DSU. Let A = component of node 1, B = component of node N. If A = B, answer 0. Otherwise we add one or two edges. With one edge: pick u ∈ A, v ∈ B minimizing (u − v)². With two edges: pick a third "bridge" component C, an edge from A to C and an edge from C to B, paying min over u ∈ A, c ∈ C of (u − c)² plus min over c' ∈ C, w ∈ B of (c' − w)². For each component C, the per-component cost to A is min over c ∈ C of squared distance to nearest node in A (use sorted A + binary search); likewise for B. Then iterate components.
Complexity target
O((N + M) α(N) + N log N) per test case.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct DSU {
vector<int> p;
DSU(int n) : p(n, -1) {}
int f(int x) { return p[x] < 0 ? x : p[x] = f(p[x]); }
void u(int a, int b) { a = f(a); b = f(b); if (a != b) p[a] = b; }
};
void solve() {
int N, M; cin >> N >> M;
DSU d(N + 1);
for (int i = 0; i < M; ++i) { int a, b; cin >> a >> b; d.u(a, b); }
// Bucket nodes by their component root.
map<int, vector<int>> comp;
for (int i = 1; i <= N; ++i) comp[d.f(i)].push_back(i);
int rA = d.f(1), rB = d.f(N);
if (rA == rB) { cout << 0 << "\n"; return; }
auto& A = comp[rA]; auto& B = comp[rB];
// Min cost to attach a single node x to component S (sorted).
auto bestTo = [](const vector<int>& S, int x) -> ll {
auto it = lower_bound(S.begin(), S.end(), x);
ll best = LLONG_MAX;
if (it != S.end()) best = min(best, (ll)(*it - x) * (*it - x));
if (it != S.begin()) { auto p = prev(it); best = min(best, (ll)(x - *p) * (x - *p)); }
return best;
};
ll ans = LLONG_MAX;
for (auto& [r, C] : comp) {
ll cA = LLONG_MAX, cB = LLONG_MAX;
for (int x : C) { cA = min(cA, bestTo(A, x)); cB = min(cB, bestTo(B, x)); }
if (cA < LLONG_MAX && cB < LLONG_MAX) ans = min(ans, cA + cB);
}
cout << ans << "\n";
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin >> T;
while (T--) solve();
}
Pitfalls
- The "one bridge" case is just C = A or C = B. Including A and B in the iteration auto-handles both 1-edge and 2-edge optima.
- Squared cost is large. (105)² = 1010; long long throughout.
- Sort each component once. DSU lookups give roots; binary search needs each component sorted by field id.
- Self-loops / multi-edges in input don't affect DSU but watch for malformed lines.
Problem 3 — Convoluted Intervals
Statement
N intervals [ai, bi] with integer endpoints in [0, M]. For every s ∈ [0, 2M], count the number of ordered pairs (i, j) (with i = j allowed) such that ai + aj ≤ s ≤ bi + bj. Output the N+1... actually 2M+1 counts.
Constraints
- 1 ≤ N ≤ 2 · 105.
- 0 ≤ ai ≤ bi ≤ M ≤ 5 · 103.
- Time limit: 4s.
Key idea
Let cntA[x] = #{i : ai = x} and cntB[x] = #{i : bi = x}. The number of pairs with ai + aj = s is (cntA ⋆ cntA)[s] (convolution), computable in O(M²). Same for b. Let f(s) = #pairs with a-sum ≤ s = prefix sum of cntA ⋆ cntA; g(s) = #pairs with b-sum < s = prefix sum of cntB ⋆ cntB up to s−1. Then answer[s] = f(s) − g(s) = #pairs with a-sum ≤ s minus #pairs with b-sum < s, which by inclusion–exclusion equals #pairs with a-sum ≤ s ≤ b-sum.
Complexity target
O(M²) for the convolution (M ≤ 5000 ⇒ 2.5 · 107) plus O(M) prefix sums.
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, M; cin >> N >> M;
vector<ll> cA(M + 1, 0), cB(M + 1, 0);
for (int i = 0; i < N; ++i) { int a, b; cin >> a >> b; ++cA[a]; ++cB[b]; }
// Convolutions: cA*cA and cB*cB. Both length 2M+1.
int S = 2 * M;
vector<ll> convA(S + 1, 0), convB(S + 1, 0);
for (int x = 0; x <= M; ++x) if (cA[x])
for (int y = 0; y <= M; ++y) if (cA[y]) convA[x + y] += cA[x] * cA[y];
for (int x = 0; x <= M; ++x) if (cB[x])
for (int y = 0; y <= M; ++y) if (cB[y]) convB[x + y] += cB[x] * cB[y];
// f[s] = #pairs with a-sum <= s, g[s] = #pairs with b-sum <= s.
// answer[s] = #pairs(a-sum <= s) - #pairs(b-sum <= s - 1)
vector<ll> f(S + 1, 0), g(S + 1, 0);
f[0] = convA[0]; g[0] = convB[0];
for (int s = 1; s <= S; ++s) {
f[s] = f[s - 1] + convA[s];
g[s] = g[s - 1] + convB[s];
}
for (int s = 0; s <= S; ++s) {
ll bLess = (s == 0 ? 0 : g[s - 1]);
cout << (f[s] - bLess) << "\n";
}
}
Pitfalls
- Pairs are ordered, with i = j allowed. That's why a plain self-convolution works without symmetry corrections.
- 64-bit counts. N² is 4 · 1010; long long always.
- Don't FFT. M ≤ 5000 makes the naïve O(M²) convolution fast enough and avoids precision headaches.
- Boundary at s = 0. Be careful with "b-sum < 0" — that count is just zero.
What Silver December 2021 was actually testing
- P1 — greedy on gaps with a priority queue. Decompose into "first patch" and "second patch" rewards, then take the K best.
- P2 — DSU + nearest-by-index search. Reduce a graph problem to "min squared distance between component label sets."
- P3 — convolution and prefix sums. Replace pairs (i, j) iteration with histograms; M ≤ 5000 is the hint.