USACO 2022 December · Bronze · all three problems
Problem 1 — Cow College
Statement
Farmer John opens a university for cows. Cow i is willing to pay at most ci in tuition. FJ must set a single price p that applies to every cow; only cows with ci ≥ p enroll, and each enrolled cow pays exactly p. Maximize total revenue p · #{i : ci ≥ p}, and print that maximum together with the smallest p achieving it.
Constraints
- 1 ≤ N ≤ 105.
- 1 ≤ ci ≤ 106.
- Revenue fits in 64-bit (up to ~1011).
- Time limit: 2s (standard Bronze).
Key idea
The optimal price is always equal to some ci (raising p past a ci drops that cow, lowering past one only loses money). Sort the willingness values in descending order; if the sorted array is w1 ≥ w2 ≥ …, then choosing p = wk yields revenue k · wk. Take the maximum, breaking ties by smaller p.
Complexity target
O(N log N) for the sort, O(N) sweep. Trivially fast at N = 105.
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; cin >> N;
vector<ll> c(N);
for (auto& x : c) cin >> x;
// Sort descending: the k-th value (1-indexed) is the price that
// exactly k cows are willing to pay.
sort(c.begin(), c.end(), greater<ll>());
ll bestRev = 0, bestPrice = 0;
for (int k = 1; k <= N; ++k) {
ll p = c[k - 1];
ll rev = (ll)k * p;
// Strictly greater revenue, OR equal revenue at a smaller price.
if (rev > bestRev || (rev == bestRev && p < bestPrice)) {
bestRev = rev;
bestPrice = p;
}
}
cout << bestRev << " " << bestPrice << "\n";
}
Pitfalls
- Overflow. Revenue is up to 105 · 106 = 1011; use
long long. - Tie-break on smallest p. Multiple prices can produce the same revenue — pick the smallest.
- "At least p" is non-strict. Cows with ci = p still enroll; setting p = ck after sorting descending always admits the first k cows.
- Don't try every integer price. Searching p from 1 to 106 works but is wasteful and tempts off-by-one bugs.
Problem 2 — Feeding the Cows
Statement
N cows stand at positions 1..N along a line; cow i is either a Guernsey (G) or a Holstein (H). FJ plants grass patches at integer positions; each patch is either G-type or H-type. A cow of breed B must reach a B-type patch within distance K. Output the minimum number of patches and one valid placement (positions and breeds).
Constraints
- 1 ≤ T ≤ 10 test cases.
- 1 ≤ N ≤ 105, 0 ≤ K ≤ N − 1.
- Sum of N over tests ≤ 105.
- Time limit: 2s.
Key idea
Handle each breed independently. Sweep cows of breed B from left to right; whenever the leftmost uncovered cow at position x has no patch within K, plant a B-patch at x + K — this is the rightmost legal spot that still covers x and is the greediest choice. Skip to the first cow past (x + 2K). The answer is the sum of patches over both breeds.
Complexity target
O(N) per test case after a linear scan of positions for each breed.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
// For a sorted list of cow positions of one breed, greedily cover them with
// patches: every uncovered leftmost cow at x forces a patch at x+K, which
// then covers [x, x+2K].
int coverGreedy(const vector<int>& pos, int K, vector<int>& outPatches) {
int n = pos.size(), i = 0, placed = 0;
while (i < n) {
int patch = pos[i] + K; // rightmost legal spot covering pos[i]
outPatches.push_back(patch);
++placed;
// Advance past all cows in [patch-K, patch+K] = [pos[i], pos[i]+2K].
int limit = patch + K;
while (i < n && pos[i] <= limit) ++i;
}
return placed;
}
void solve() {
int N, K; cin >> N >> K;
string s; cin >> s;
vector<int> gp, hp;
for (int i = 0; i < N; ++i) (s[i] == 'G' ? gp : hp).push_back(i + 1);
vector<int> gPatch, hPatch;
int total = coverGreedy(gp, K, gPatch) + coverGreedy(hp, K, hPatch);
cout << total << "\n";
for (int p : gPatch) cout << p << " G\n";
for (int p : hPatch) cout << p << " H\n";
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin >> T;
while (T--) solve();
}
Pitfalls
- K = 0 edge case. Each cow needs a patch at its own position; the answer is N.
- Patches at arbitrary positions. Patches don't have to be at cow positions — placing at x + K maximizes future coverage.
- Don't share patches across breeds. A G-patch never feeds an H-cow even at distance 0.
- Output format. Re-read the PDF for the exact required output (count + list).
Problem 3 — Reverse Engineering
Statement
Elsie claims to have written a program of nested if/else statements, where each condition tests a single boolean input variable; every leaf returns 0 or 1. She shows M (input, output) examples on N variables. Decide whether some such program is consistent with all M examples (print OK) or whether her claims must contradict (print LIE).
Constraints
- 1 ≤ T ≤ 10 test cases.
- 1 ≤ N, M ≤ 100.
- Time limit: 2s.
Key idea
Such a program is exactly a decision tree on the N variables, which can represent any function as long as no two examples have identical inputs but different outputs. Stronger: it's consistent iff at every splittable subset we can find one variable that partitions it into smaller subsets whose outputs are each consistent. Equivalent shortcut: as long as no two rows have the same N-bit input and different outputs, "OK"; otherwise "LIE". (Variables aren't restricted in usage count.)
Complexity target
O(M² · N) checking pairs — trivial at N, M ≤ 100.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
// A nested if/else over single-variable tests is a decision tree; it can
// realize any function on the inputs that actually appear, PROVIDED no two
// identical input rows have conflicting outputs.
void solve() {
int N, M; cin >> N >> M;
vector<string> in(M);
vector<int> out(M);
for (int i = 0; i < M; ++i) cin >> in[i] >> out[i];
// Recursive: pick any variable that splits the current subset into two
// non-trivial groups whose outputs are each consistent.
function<bool(vector<int>&)> ok = [&](vector<int>& rows) -> bool {
if (rows.empty()) return true;
int v0 = out[rows[0]];
bool same = true;
for (int r : rows) if (out[r] != v0) { same = false; break; }
if (same) return true;
for (int j = 0; j < N; ++j) {
vector<int> A, B;
for (int r : rows) (in[r][j] == '0' ? A : B).push_back(r);
if (A.empty() || B.empty()) continue; // didn't split
if (ok(A) && ok(B)) return true;
}
return false;
};
vector<int> all(M);
iota(all.begin(), all.end(), 0);
cout << (ok(all) ? "OK" : "LIE") << "\n";
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin >> T;
while (T--) solve();
}
Pitfalls
- Exponential blow-up. Naive recursion branches into 2 every level; at M ≤ 100, N ≤ 100 it's still fine because each row appears in only one branch.
- Identical rows, different outputs are an instant LIE — handle that base case before recursing if you want a fast bail.
- Splitting on a variable must produce two non-empty sides; skip variables that all current rows agree on.
- Output exactly "OK" or "LIE" per test case, on its own line.
What Bronze December 2022 was actually testing
- P1 — recognize the optimum is at a data point. Sort then sweep; don't try every integer p.
- P2 — interval-covering greedy. Plant the rightmost-feasible patch; classic Bronze pattern.
- P3 — recursion + a property of decision trees. Either show a split exists or detect a true conflict.