USACO 2024 December · Bronze · all three problems
← Full December 2024 set · Official results · Focused notes on Problem 1
Problem 1 — Roundabout Rounding
Statement
Bessie rounds a positive integer x to the nearest power of 10 in one step (standard rounding: digit ≥ 5 rounds up). Elsie does chain rounding: she rounds x first to the nearest 10, then that result to the nearest 100, and so on, all the way up to the smallest power of 10 that exceeds x. For each test case N, count the integers 2 ≤ x ≤ N for which the two procedures disagree.
Constraints
- 1 ≤ N ≤ 109; 1 ≤ T ≤ 105 test cases per file.
- Time limit: 2s (standard Bronze).
Key idea
Chain rounding "ratchets up": an x like 45 rounds to 50, then 50 chains to 100. Bessie's one-step rounding on 45 gives 0 (since 45 < 50 when rounding to 100). Whenever a chain-step pushes a number just over a halfway threshold and that propagates upward, the answers diverge. Enumerate divergent "bad" patterns directly: count numbers whose decimal form has a leading digit 4 followed by a digit ≥ 5 — every such number is a divergence root; multiplying by powers of 10 generates the rest.
Complexity target
O(log10 N) per test case after a tiny precomputation — well under 2s for T = 105.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// Count x in [1, N] whose decimal digits start with the pattern 4d...
// where d >= 5, then any tail. Such x cause chain-rounding to "ratchet"
// up past the 5*10^k boundary while one-shot rounding stays below it.
ll countBad(ll N) {
ll ans = 0;
// For each pivot power 10^k (k >= 1), count integers in [4*10^k + 5*10^{k-1}, 5*10^k - 1]
// and all their decimal-shifted multiples.
for (ll p = 10; p <= N; p *= 10) {
ll lo = 4 * p + p / 2; // e.g. p=10 -> 45
ll hi = 5 * p - 1; // e.g. p=10 -> 49
if (lo > N) break;
ans += min(N, hi) - lo + 1;
if (p > N / 10) break; // overflow guard
}
return ans;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin >> T;
while (T--) {
ll N; cin >> N;
cout << countBad(N) << "\n";
}
}
Pitfalls
- Off-by-one on the rounding rule. "Round half up" vs. "round half to even" — USACO uses round-half-up; digit exactly 5 rounds up.
- Overflow on p *= 10. N up to 109; using
long longfor p avoids surprises near 1010. - Brute force is too slow for T = 105, N = 109. Must close-form per test.
- The exact divergence pattern is best verified by writing a brute force for small N and diffing — Bronze problems reward this 5-minute sanity check.
Problem 2 — Farmer John's Cheese Block
Statement
Farmer John starts with a solid N×N×N cube of cheese. Q operations each remove one 1×1×1 unit cube at a given (x,y,z). After each removal, report how many distinct positions inside the cube can hold a straight 1×1×N "brick" — i.e. a full row, column, or pillar of N consecutive removed unit cubes aligned with one of the three axes.
Constraints
- 2 ≤ N ≤ 1000, 1 ≤ Q ≤ 2×105.
- Time limit: 2s, memory 256 MB.
Key idea
There are exactly 3·N² candidate brick positions (one per axis-aligned line through the cube). For each line, keep a counter of how many unit cubes along it have been removed. A line is "full" when the counter hits N. Each carve increments exactly three line-counters (the X-line, Y-line, Z-line through that cell). Maintain the answer incrementally: when a counter goes from N−1 to N, +1; never decrement (carves don't un-remove).
Complexity target
O(1) per query, O(N² + Q) total. Memory 3·N² ≈ 3·106 ints.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int N, Q;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N >> Q;
// cntX[y][z] = removed cubes along the x-line at (y,z); similarly for Y, Z.
vector<vector<int>> cntX(N, vector<int>(N, 0));
vector<vector<int>> cntY(N, vector<int>(N, 0));
vector<vector<int>> cntZ(N, vector<int>(N, 0));
// mark which cells have already been carved, to ignore duplicates [verify]
// (statement: each carve targets a still-solid cell, so we can skip the set)
long long fullLines = 0;
while (Q--) {
int x, y, z; cin >> x >> y >> z;
// 0-indexed inside [0, N)
if (++cntX[y][z] == N) ++fullLines;
if (++cntY[x][z] == N) ++fullLines;
if (++cntZ[x][y] == N) ++fullLines;
cout << fullLines << "\n";
}
}
Pitfalls
- Indexing. Statement may use 1-indexed (x,y,z); subtract 1 on read.
- Don't re-enumerate all 3N² lines every query — that's O(Q·N²) = 6×1011, far too slow.
- Memory. N=1000 gives 3·106 ints = 12 MB — fine, but don't allocate N³ anything.
- Duplicate carves. If the statement does not guarantee distinct (x,y,z) targets, track a 3D
boolgrid (1 GB at N=1000 — use a hash set instead) and skip increments on a repeat. [verify against PDF]
Problem 3 — It's Mooin' Time
Statement
Farmer John has a string s of length N over a small alphabet. A "moo" is a length-3 pattern of the form a-b-b (first character different from the next two, which are equal). Bessie tells him exactly one character of the string may have been corrupted (changed to any other letter). For each candidate pattern (a,b), count how many occurrences appear in s; report every (a,b) whose occurrence count, if we are allowed to repair one bad character, can reach at least F.
Constraints
- 3 ≤ N ≤ 2×104, 1 ≤ F ≤ N.
- Alphabet is the 26 lowercase letters.
- Time limit: 2s.
Key idea
There are only 26·25 = 650 possible (a,b) moo patterns. For each, scan s once and count exact-match triples. Then for each pattern, also check whether flipping one single character (any of N positions to any of 25 alternatives) can push the count to ≥ F: the gain from flipping s[i] is the number of new pattern-matches that include position i minus the matches it destroys. Take the max over i and over target characters. With 650 patterns × N positions × 25 letters this is about 3·108, borderline; tighten by only considering positions adjacent to a "near-miss" triple.
Complexity target
About O(26² · N) ≈ 1.4×107 with the tight enumeration; under 2s comfortably.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, F; cin >> N >> F;
string s; cin >> s;
auto countExact = [&](char a, char b) {
int c = 0;
for (int i = 0; i + 2 < N; ++i)
if (s[i] == a && s[i+1] == b && s[i+2] == b) ++c;
return c;
};
auto bestWithOneFlip = [&](char a, char b) {
int base = countExact(a, b);
int best = base;
// Try flipping each position to every possible char, compute delta locally.
for (int i = 0; i < N; ++i) {
char orig = s[i];
for (char c = 'a'; c <= 'z'; ++c) {
if (c == orig) continue;
s[i] = c;
int lo = max(0, i - 2), hi = min(N - 3, i);
int delta = 0;
for (int j = lo; j <= hi; ++j) {
bool nowMatch = (s[j]==a && s[j+1]==b && s[j+2]==b);
s[i] = orig;
bool wasMatch = (s[j]==a && s[j+1]==b && s[j+2]==b);
s[i] = c;
delta += (int)nowMatch - (int)wasMatch;
}
best = max(best, base + delta);
s[i] = orig;
}
}
return best;
};
vector<pair<char,char>> good;
for (char a = 'a'; a <= 'z'; ++a)
for (char b = 'a'; b <= 'z'; ++b) {
if (a == b) continue;
if (bestWithOneFlip(a, b) >= F) good.push_back({a, b});
}
cout << good.size() << "\n";
for (auto [a, b] : good) cout << a << b << "\n"; // [verify output format against PDF]
}
Pitfalls
- Output format is fiddly. The exact format (which patterns to print, in what order) needs to be cross-checked against the PDF. Marked
[verify]above. - "At most one corruption." Don't forget the zero-flip case — the original string itself counts.
- Don't confuse "moo" with length-3 distinct. The pattern is X-Y-Y, not X-Y-Z.
- Naive triple loop over (i, target_char, j) is fine at N=20000, but watch out: 26²·N·25·constant gets close to the limit; pull the pattern outside or precompute prefix counts.
What Bronze December 2024 was actually testing
- P1 — closed-form digit reasoning. Translate a wordy rounding rule into a clean count by powers of 10.
- P2 — incremental counters over implicit lines. See past the 3D framing; only 3N² objects matter.
- P3 — small alphabet, brute the patterns. 26² patterns make this a "search every option" Bronze problem in disguise.