USACO 2019 December · Silver · all three problems
Problem 1 — MooBuzz
Statement
In FizzBuzz, the cows say "Moo" on integers not divisible by 3 or 5 (skipping the ones that would have been Fizz, Buzz, or FizzBuzz). Given N, output the N-th integer the cows say "Moo" on.
Constraints
- 1 ≤ N ≤ 109.
- Time limit: 2s.
Key idea
In every block of 15 consecutive integers exactly 8 are coprime to {3, 5}: positions 1, 2, 4, 7, 8, 11, 13, 14. So the N-th moo is 15 · ((N − 1) / 8) + base[(N − 1) % 8], where base lists those 8 offsets. Constant-time arithmetic.
Complexity target
O(1).
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);
ll N; cin >> N;
int base[8] = {1, 2, 4, 7, 8, 11, 13, 14};
ll q = (N - 1) / 8, r = (N - 1) % 8;
cout << 15 * q + base[r] << "\n";
}
Pitfalls
- Off-by-one in indexing. N is 1-based; use (N − 1) / 8 and (N − 1) mod 8.
- 64-bit math. 15 · N / 8 at N = 109 fits in 64-bit but not 32-bit comfortably.
- Don't simulate to N. 109 iterations would TLE; the period-15 structure is the whole point.
Problem 2 — Meetings
Statement
N cows stand on a number line of length L. Each cow i has weight wi, position xi, and initial direction di ∈ {−1, +1}, walking at speed 1. When two cows meet they swap directions (equivalently the labels pass through). When a cow reaches an endpoint it falls off. The contest ends at time T, the first instant when the total weight on the right end equals at least half the total weight. Output the number of unordered cow-pair meetings that occur in [0, T].
Constraints
- 1 ≤ N ≤ 5 · 104.
- 1 ≤ L ≤ 109, 1 ≤ xi < L distinct.
- 1 ≤ wi ≤ 103.
- Time limit: 2s.
Key idea
Two-step trick. (1) Since cows are indistinguishable physical points (swaps = ghost pass-throughs), the set of fall-off times equals the multiset of times {(L − x)/1 : d=+1} ∪ {x/1 : d=−1}, but the weight delivered to each end is not the same multiset — sort cows left-to-right by x and assign right-end fall-offs to the right-most "right-going-ghosts" (the K largest-positioned cows whose ghost goes right), where K = number of +1 cows. From this we get T by sorting fall-off times and prefix-summing weight on the right. (2) For the meeting count, a +1 cow at position a meets a −1 cow at position b > a within time T iff (b − a) ≤ 2T. Sort the two groups by position and two-pointer-count pairs with gap ≤ 2T.
Complexity target
O(N log N) overall — two sorts and two linear sweeps.
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; ll L; cin >> N >> L;
vector<ll> W(N), X(N), D(N);
for (int i = 0; i < N; ++i) cin >> W[i] >> X[i] >> D[i];
vector<int> ord(N); iota(ord.begin(), ord.end(), 0);
sort(ord.begin(), ord.end(), [&](int a, int b){ return X[a] < X[b]; });
// Collect ghost fall times and where each ghost exits (left = 0, right = 1).
// Ghost of cow i with d = +1 exits right at time L - x; d = -1 exits left at time x.
vector<pair<ll,int>> ev; // (time, side) side 1 = right, 0 = left
for (int i = 0; i < N; ++i)
ev.push_back({D[i] == 1 ? L - X[i] : X[i], (int)(D[i] == 1)});
// Assign weights to fall-events by matching sorted positions to sorted ghost arrivals on each side.
// Standard trick: the weight that falls off the right end at the k-th right-event is the weight of the
// k-th right-most cow.
vector<ll> rightW, leftW;
for (int idx : ord) {
// gather in left-to-right cow order; we'll pair from the back for right side, front for left side.
(void)idx;
}
// Count right-going ghosts.
int R = 0; for (int i = 0; i < N; ++i) if (D[i] == 1) ++R;
// The R rightmost cows by position keep weights for right-end fall-offs.
vector<ll> sortedRightTimes, sortedLeftTimes;
for (auto& e : ev) (e.second ? sortedRightTimes : sortedLeftTimes).push_back(e.first);
sort(sortedRightTimes.begin(), sortedRightTimes.end());
sort(sortedLeftTimes.begin(), sortedLeftTimes.end());
// Right-end weights, in time order, are the weights of the R rightmost cows in left-to-right order.
vector<ll> rightWeights, leftWeights;
for (int k = N - R; k < N; ++k) rightWeights.push_back(W[ord[k]]);
for (int k = 0; k < N - R; ++k) leftWeights.push_back(W[ord[k]]);
// Combine all fall events sorted by time; track cumulative right-end weight.
vector<tuple<ll,int,ll>> evts; // (time, side, w)
for (int k = 0; k < (int)sortedRightTimes.size(); ++k) evts.push_back({sortedRightTimes[k], 1, rightWeights[k]});
for (int k = 0; k < (int)sortedLeftTimes.size(); ++k) evts.push_back({sortedLeftTimes[k], 0, leftWeights[k]});
sort(evts.begin(), evts.end());
ll total = 0; for (auto w : W) total += w;
ll need = (total + 1) / 2; // "at least half"
ll cum = 0, T = 0;
for (auto& [t, side, w] : evts) {
if (side == 1) cum += w;
if (cum >= need) { T = t; break; }
}
// Count meetings: +1 cow at a meets -1 cow at b > a iff b - a <= 2T.
vector<ll> pos, neg;
for (int i = 0; i < N; ++i) (D[i] == 1 ? pos : neg).push_back(X[i]);
sort(pos.begin(), pos.end()); sort(neg.begin(), neg.end());
ll ans = 0; int j = 0;
// For each +1 cow at position a, count -1 cows at b in (a, a + 2T].
int lo = 0;
for (ll a : pos) {
while (lo < (int)neg.size() && neg[lo] <= a) ++lo;
while (j < (int)neg.size() && neg[j] <= a + 2 * T) ++j;
if (j > lo) ans += j - lo;
// j is non-decreasing across a's because we iterate a in sorted order.
}
cout << ans << "\n";
}
Pitfalls
- "Half" is ceiling. Use (total + 1) / 2 so a tied split also stops the clock.
- Ghosts vs cows. Meeting count uses real labels (positions of +1 vs −1 cows), but the time T is determined by ghost fall-offs paired by left-to-right rank.
- Pair gap is 2T, not T — they close at relative speed 2.
- 64-bit positions and time. L · N can overflow 32-bit.
Problem 3 — Milk Visits
Statement
A tree of N nodes; each node has a milk type 'H' or 'G'. M queries, each (a, b, c) asking whether the unique path from a to b contains at least one node of type c. Output a binary string of length M.
Constraints
- 1 ≤ N, M ≤ 105.
- Types are 'H' or 'G'.
- Time limit: 4s.
Key idea
Because there are only 2 types, build a DSU over edges connecting same-type nodes. Each connected component is monochromatic. The path from a to b passes through a node of type c iff (a's type is c) or (b's type is c) or (a and b lie in different same-type components). Simplest: a's path contains type c iff not all nodes on the path are the opposite type, which is equivalent to a and b not being in the same opposite-type component (with the further check that the component's type isn't c).
Complexity target
O((N + M) α(N)) with DSU.
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 find(int x) { return p[x] == x ? x : p[x] = find(p[x]); }
void unite(int a, int b) {
a = find(a); b = find(b); if (a == b) return;
if (r[a] < r[b]) swap(a, b);
p[b] = a; if (r[a] == r[b]) ++r[a];
}
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, M; cin >> N >> M;
string t; cin >> t; // length N, characters 'H'/'G'
DSU d(N);
for (int i = 0; i < N - 1; ++i) {
int u, v; cin >> u >> v; --u; --v;
if (t[u] == t[v]) d.unite(u, v);
}
string ans;
while (M--) {
int a, b; char c; cin >> a >> b >> c; --a; --b;
// Path a..b contains type c iff some node on it is c.
// If a or b is already c, yes.
// Else both are the opposite type 'o'. Path contains a c iff a and b are
// NOT in the same monochromatic 'o' component.
bool ok;
if (t[a] == c || t[b] == c) ok = true;
else ok = d.find(a) != d.find(b);
ans += ok ? '1' : '0';
}
cout << ans << "\n";
}
Pitfalls
- Only union edges joining same-type nodes. Cross-type edges separate components.
- Same-component check is for the "opposite" type. If a and b are both 'H' and share an 'H' component, no 'G' node lies between them.
- Endpoints count. If a itself is type c the answer is 1 regardless of structure.
- Don't LCA this. Two types collapses to DSU; reaching for LCA + Euler tour wastes time. (Gold's version with arbitrary types is where LCA / small-to-large becomes mandatory.)
What Silver December 2019 was actually testing
- P1 — closed-form on a period. Spot the period-15 structure and write 4 lines.
- P2 — "ghosts pass through" + sorting tricks. The classic line-bouncing identity is the hard part; the meeting count is a two-pointer.
- P3 — DSU on like-type edges. Recognizing the 2-color simplification beats the LCA reflex.