USACO 2016 January · Bronze · all three problems
Problem 1 — Promotion Counting
Statement
USACO has four divisions (bronze, silver, gold, platinum). After a contest some bronze cows are promoted to silver, some silver to gold, and some gold to platinum (no demotions, no new entrants). You are given the head counts in each division before and after the contest. Output the number of promotions across each of the three boundaries (bronze→silver, silver→gold, gold→platinum).
Constraints
- Four lines of input, each with two non-negative integers ≤ 1,000,000.
- Promotions only flow upward; no demotions.
- Time limit: 2s.
Key idea
Let bi, ai be the before/after sizes of division i (i = 1..4 for B/S/G/P). Let pi be the number promoted from division i to i+1. Conservation gives: a1 = b1 − p1, a2 = b2 + p1 − p2, a3 = b3 + p2 − p3, a4 = b4 + p3. Solve in order: p1 = b1 − a1, p3 = a4 − b4, p2 = (b3 − a3) + p3 = a4 − b4 + b3 − a3.
Complexity target
O(1). Six integers in, three out.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
long long b[4], a[4];
for (int i = 0; i < 4; ++i) cin >> b[i] >> a[i];
// p[i] = # promoted from level i to level i+1.
// bronze loses p0, silver gains p0 loses p1, gold gains p1 loses p2,
// platinum gains p2. Solve top-down and bottom-up:
long long p0 = b[0] - a[0]; // bronze net loss
long long p2 = a[3] - b[3]; // platinum net gain
long long p1 = (a[3] - b[3]) + (b[2] - a[2]); // = p2 + (gold net loss)
cout << p0 << "\n" << p1 << "\n" << p2 << "\n";
}
Pitfalls
- Don't average the middle two. Silver and gold each see a "gain on the left, loss on the right" — you must derive each pi from the two boundary divisions, not the middle ones.
- Sanity check. All three pi should be ≥ 0; if your formula gives a negative, the input violates the no-demotions promise (won't happen on official data).
- Output one per line. Bronze graders are strict about newlines.
Problem 2 — Angry Cows
Statement
N hay bales lie on a number line at integer positions xi. You launch one cow that lands on a chosen bale and detonates it at time t = 1 with blast radius 1, then radius 2 at t = 2, radius 3 at t = 3, etc. When any bale explodes it has the same growing-radius behaviour starting from time 1 (i.e. the chain reaction propagates). At each time step, every previously-detonated bale's radius grows by 1. Find the maximum number of bales that can be detonated by choosing the best landing bale.
Constraints
- 1 ≤ N ≤ 100.
- 0 ≤ xi ≤ 109, all distinct integers.
- Time limit: 2s.
Key idea
With N ≤ 100, just simulate. For each starting bale s: run BFS in time steps. At time t, all bales already-exploded blast with radius t; newly engulfed bales begin at t+1 with radius 1, so they reach radius t+1 − (their start time) + 1 at the next tick. Easier: keep a set S of detonated bales and for each bale i in S, record its detonation time τi. At time t any undetonated j is added if some i ∈ S has |xj − xi| ≤ t − τi + 1. Stop when no new bale is added. Return |S|.
Complexity target
O(N³) per starting bale and O(N⁴) overall ≈ 10⁸ — fine within the limit.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int N;
vector<long long> X;
int simulate(int start) {
// tau[i] = time at which bale i was detonated (0 if not yet).
vector<int> tau(N, 0);
tau[start] = 1; // landing detonation = "time 1"
int detonated = 1;
for (int t = 2; ; ++t) {
bool any = false;
// A new bale j is hit at time t if some exploded i has |x_i - x_j| <= t - tau[i].
vector<int> new_tau = tau;
for (int j = 0; j < N; ++j) if (!tau[j]) {
for (int i = 0; i < N; ++i) if (tau[i]) {
if (llabs(X[i] - X[j]) <= (long long)(t - tau[i])) {
new_tau[j] = t; any = true; ++detonated; break;
}
}
}
if (!any) break;
tau = new_tau;
}
return detonated;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N; X.resize(N);
for (auto& x : X) cin >> x;
int best = 0;
for (int s = 0; s < N; ++s) best = max(best, simulate(s));
cout << best << "\n";
}
Pitfalls
- Radius grows monotonically. Once a bale is detonated it keeps blasting at increasing radius, so a near-miss this tick may hit next tick.
- The landing site counts. The cow always detonates the bale it lands on, so the answer is ≥ 1.
- Integer positions but radii are integers too. "Within radius r" means |Δx| ≤ r.
- Don't binary search. Bronze version is small; just simulate.
Problem 3 — Mowing the Field
Statement
Farmer John starts at (0,0) at time 0 and executes N movement statements. Each statement says "direction D, S steps" (D ∈ {N,E,S,W}, 1 ≤ S ≤ 10). He moves one cell per time unit, mowing each cell as he enters it. Grass cut at time t regrows at time t + x. FJ never re-cuts grass (he never enters a still-cut cell). Find the maximum integer x for which this is consistent, i.e. the largest x ≤ minimum gap between consecutive visits to the same cell. Output −1 if no cell is ever revisited.
Constraints
- 1 ≤ N ≤ 100, 1 ≤ S ≤ 10, so the total path has ≤ 1000 cells visited.
- Coordinates can be negative; use a map.
- Time limit: 2s.
Key idea
Walk the path step-by-step. Maintain a hash map from cell (x,y) to the last time it was visited. On each new step, if (x,y) is in the map, compute gap = t − last; update the running min-gap; overwrite last. The answer is (min-gap − 1), because grass that regrows at time t+x is cut at exactly time t+x must still be cut, i.e. gap ≥ x+1 ⇒ x ≤ gap−1 (strictly fewer time units than the gap). If no revisit occurs, print −1.
Complexity target
O(total steps) ≈ 1000, trivially fast.
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;
map<pair<int,int>, int> last;
int x = 0, y = 0, t = 0;
last[{0, 0}] = 0; // mowed at time 0
int best = INT_MAX;
for (int k = 0; k < N; ++k) {
char d; int s; cin >> d >> s;
int dx = 0, dy = 0;
if (d == 'N') dy = 1; else if (d == 'S') dy = -1;
else if (d == 'E') dx = 1; else dx = -1;
for (int i = 0; i < s; ++i) {
x += dx; y += dy; ++t;
auto it = last.find({x, y});
if (it != last.end()) best = min(best, t - it->second);
last[{x, y}] = t;
}
}
if (best == INT_MAX) cout << -1 << "\n";
else cout << best - 1 << "\n"; // grass regrows at t+x, gap > x => x <= gap-1
}
Pitfalls
- Mind the starting cell. (0,0) is mowed at t=0, so if the path returns to (0,0) at t=k, the gap is k.
- Off-by-one on x. The relation is gap ≥ x+1 (strict), so x = min-gap − 1.
- Negative coordinates. Use map / unordered_map with hashed pair, not a fixed 2D array indexed from 0.
- Update "last" on every visit, not just the first. The min-gap is over consecutive visit pairs.
What Bronze January 2016 was actually testing
- P1 — arithmetic conservation. Translate "promotions only flow up" into linear equations across four divisions; solve in three lines.
- P2 — brute-force simulation with growing radii. N ≤ 100 means O(N⁴) is fine; recognise that exhaustive simulation per starting bale beats any clever-but-buggy formula.
- P3 — coordinate tracking with a map. Standard "walk the path, hash visit times" pattern; the off-by-one on regrowth is the only trap.