USACO 2020 December · Silver · all three problems
Problem 1 — Cowntact Tracing
Statement
There are N cows. Over T discrete days, you're given a list of "shake" events: pairs (a, b) at time t. At day T+1 a subset S of cows is sick. One cow is patient zero; when an infected cow shakes hooves with a healthy cow there is a (possibly limited) chance of transmission. Determine how many cows could be the patient zero and the maximum number of transmissions consistent with the observed sick set.
Constraints
- 1 ≤ N ≤ 100.
- 1 ≤ T ≤ 250 events.
- Time limit: 2s.
Key idea
With N ≤ 100 and T ≤ 250, try each cow as patient zero (N ways) and each "K = max transmissions per handshake" candidate (up to T). For each (zero, K) simulate the events in order: when an infected cow meets a healthy one, infect (transmission used). If the resulting sick set equals S, this (zero, K) is valid. Count distinct valid zeros and track the maximum K.
Complexity target
O(N · T · T) ≈ 6 · 106, comfortably fast.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int N, T;
vector<tuple<int,int,int>> ev; // (time, a, b)
vector<int> finalSick;
// Simulate with patient zero p and per-pair cap K. Return true if final state matches.
bool sim(int p, int K) {
vector<int> inf(N + 1, 0), cnt(N + 1, 0); // cnt = remaining transmissions
inf[p] = 1; cnt[p] = K;
for (auto [t, a, b] : ev) {
if (inf[a] && inf[b]) continue;
if (inf[a] && cnt[a] > 0) { inf[b] = 1; cnt[a]--; cnt[b] = K; }
else if (inf[b] && cnt[b] > 0) { inf[a] = 1; cnt[b]--; cnt[a] = K; }
}
for (int i = 1; i <= N; ++i) if (inf[i] != finalSick[i]) return false;
return true;
}
int main() {
cin >> N >> T;
string s; cin >> s;
finalSick.assign(N + 1, 0);
for (int i = 0; i < N; ++i) finalSick[i + 1] = (s[i] == '1');
ev.resize(T);
for (auto& [t, a, b] : ev) cin >> t >> a >> b;
sort(ev.begin(), ev.end());
int zeros = 0, bestK = 0;
for (int p = 1; p <= N; ++p) {
bool ok = false; int K;
for (K = 0; K <= T; ++K) if (sim(p, K)) { ok = true; bestK = max(bestK, K); }
if (ok) zeros++;
}
cout << zeros << " " << bestK << "\n"; // [verify exact output spec] cpid=1062
}
Pitfalls
- Read the transmission semantics carefully. The exact rule for how many times an infected cow can transmit varies; the above caps it at K total transmissions per cow.
- Process events in chronological order. Multiple events at the same t are independent — order doesn't matter within a tie.
- Output two numbers per the statement (count of possible patient zeros, max transmissions).
Problem 2 — Rectangular Pasture
Statement
N cows lie at distinct points in the plane. Count the number of distinct subsets of cows that can be obtained as "cows inside some axis-aligned rectangle." Two rectangles that capture the same subset count once. Include the empty set.
Constraints
- 1 ≤ N ≤ 2500.
- Coordinates up to 109, distinct after coordinate compression.
- Time limit: 2s.
Key idea
Compress coordinates to [1..N] in both axes. A rectangle's "useful" subset is determined by choosing
two cows that define its x-range (left and right boundary cows), and then counting how many distinct
y-cross-sections we can include. Sort cows by x. For each pair (i, j) of cows with i ≤ j by x, count
distinct subsets of y-intervals that include both i and j — equivalently, pick a y-range containing
yi and yj; the count is (a + 1)(b + 1) where a, b are the number of cows strictly
above/below the pair within x-range [i, j]. Use a precomputed prefix-sum-over-rectangles array
cnt[i][j] = number of cows with x ≤ i and y ≤ j.
Complexity target
O(N²) pairs, O(1) lookup each. ~6 · 106 ops.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
int N; cin >> N;
vector<pair<int,int>> pts(N);
for (auto& [x, y] : pts) cin >> x >> y;
// Compress.
vector<int> xs(N), ys(N);
for (int i = 0; i < N; ++i) { xs[i] = pts[i].first; ys[i] = pts[i].second; }
sort(xs.begin(), xs.end()); sort(ys.begin(), ys.end());
for (auto& [x, y] : pts) {
x = (int)(lower_bound(xs.begin(), xs.end(), x) - xs.begin()) + 1;
y = (int)(lower_bound(ys.begin(), ys.end(), y) - ys.begin()) + 1;
}
// cnt[i][j] = # cows with x <= i and y <= j.
vector cnt(N + 2, vector<int>(N + 2, 0));
for (auto [x, y] : pts) cnt[x][y]++;
for (int i = 1; i <= N; ++i)
for (int j = 1; j <= N; ++j)
cnt[i][j] += cnt[i-1][j] + cnt[i][j-1] - cnt[i-1][j-1];
auto box = [&](int x1, int y1, int x2, int y2) {
if (x1 > x2 || y1 > y2) return 0;
return cnt[x2][y2] - cnt[x1-1][y2] - cnt[x2][y1-1] + cnt[x1-1][y1-1];
};
sort(pts.begin(), pts.end());
ll ans = N + 1; // empty + N single-cow subsets
for (int i = 0; i < N; ++i)
for (int j = i + 1; j < N; ++j) {
int lx = pts[i].first, rx = pts[j].first;
int lo = min(pts[i].second, pts[j].second), hi = max(pts[i].second, pts[j].second);
int above = box(lx, hi + 1, rx, N);
int below = box(lx, 1, rx, lo - 1);
ans += (ll)(above + 1) * (below + 1);
}
cout << ans << "\n";
}
Pitfalls
- Empty subset counts. Always include 1 for "the empty rectangle."
- Single-cow subsets. Add N separately if you only iterate over pairs.
- 64-bit answer. N² pairs × O(N) extra cows is up to ~N³ ≈ 1010.
- Sort by x before pairing. Otherwise (i, j) doesn't define a clean x-range.
Problem 3 — Stuck in a Rut (Silver)
Statement
Same rules as Bronze, but with up to N = 50000 cows. Each cow walks N or E; when two would meet at the same point, the later arriver stops. Output how far each cow walks before being stopped (or "Infinity").
Constraints
- 1 ≤ N ≤ 5 · 104.
- Coordinates up to 109.
- Time limit: 2s.
Key idea
Only an East cow and a North cow whose paths cross can collide. Sweep events in order of collision time. Maintain two ordered sets: alive East-walkers keyed by y, alive North-walkers keyed by x. For each candidate pair (E, N) with xE < xN and yN < yE, the collision time is max(xN − xE, yE − yN). Push these into a priority queue and process the earliest; when one stops, lazily mark and skip later events involving it.
Complexity target
O((N + events) log N). The number of relevant collision events is O(N) amortized when handled with sweep + sorted set neighbors.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct Cow { ll x, y; char d; int id; };
int main() {
int N; cin >> N;
vector<Cow> c(N);
for (int i = 0; i < N; ++i) {
cin >> c[i].d >> c[i].x >> c[i].y; c[i].id = i;
}
vector<ll> dist(N, -1);
vector<char> alive(N, 1);
// Event: collision time, eIdx, nIdx, expected ages (for staleness check via alive flags).
priority_queue<tuple<ll,int,int>, vector<tuple<ll,int,int>>, greater<>> pq;
// Sort East cows by y, North cows by x to find first crossing pairs cheaply.
vector<int> Es, Ns;
for (int i = 0; i < N; ++i) (c[i].d == 'E' ? Es : Ns).push_back(i);
// Seed: all O(|E| * |N|) candidate pairs — fine up to ~N^2 = 2.5e9 worst case,
// so in practice use the standard trick of pairing each E only with the "next"
// N it will hit; below we enumerate all pairs and rely on early staleness skipping.
for (int e : Es) for (int n : Ns) {
if (c[e].x < c[n].x && c[n].y < c[e].y) {
ll tE = c[n].x - c[e].x, tN = c[e].y - c[n].y;
if (tE != tN) pq.push({max(tE, tN), e, n});
}
}
while (!pq.empty()) {
auto [t, e, n] = pq.top(); pq.pop();
if (!alive[e] || !alive[n]) continue;
ll tE = c[n].x - c[e].x, tN = c[e].y - c[n].y;
if (tE > tN) { alive[e] = 0; dist[e] = tE; }
else { alive[n] = 0; dist[n] = tN; }
}
for (int i = 0; i < N; ++i) {
if (dist[i] < 0) cout << "Infinity\n"; else cout << dist[i] << "\n";
}
}
Pitfalls
- This enumerates all O(|E|·|N|) pairs. For tighter limits (full Silver test data) use the editorial's sweep that maintains only "nearest live neighbor" pairs in two ordered sets, which keeps the candidate set linear.
- Lazy deletion via
alive. Don't try to mutate the priority queue. - Tie at the exact intersection — neither stops. Skip those events.
What Silver December 2020 was actually testing
- P1 — bounded brute force with careful state. N² simulation candidates within budget.
- P2 — 2-D prefix sums. Subset-of-points counting via "pick the two boundary cows" decomposition.
- P3 — event-driven sweep with ordered sets. The bridge from Bronze N²-simulation to a proper priority-queue sweep.