USACO 2024 December · Gold · all three problems
← Full December 2024 set · Official results · Focused notes on Cowdependence
Problem 1 — Cowdependence
Statement
N cows in a line each carry a label ai. A "group" must consist of cows that all share a label and all sit within a span of x positions of each other. Every cow joins exactly one group. For each x from 1 to N, report the minimum number of groups across the whole line.
Constraints
- 1 ≤ N ≤ 105; 1 ≤ ai ≤ N.
- Subtasks: small N (≤5000), few distinct labels (≤10), few occurrences per label (≤10).
- Time limit: 2s.
Key idea
Per label independently: given the sorted positions p1 < p2 < … < pm, the minimum number of groups at window size x is the count of "breaks" where consecutive pi+1 − pi + (already-spanned width within the current group) exceeds x, plus 1. Equivalently, greedy: start a group at p1, extend while pj − group_start ≤ x. Across all x simultaneously, use the fact that the answer for label L as a function of x is non-increasing and changes only at O(occurrences²) breakpoints in the worst case — but the sum across labels can be computed by a difference-array sweep over x: each gap d = pi+1 − pi contributes +1 to the group count for all x < d.
[verify] The "greedy per label, contribute gaps to diff array" reading matches the intended O((N + max_freq) log N) editorial; check the official write-up for the exact accounting when groups overlap multiple gaps.
Complexity target
O(N · α(N)) or O(N log N) across all x.
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;
vector<int> a(N);
for (auto& v : a) cin >> v;
// positions[label] = sorted list of 1-indexed positions
unordered_map<int, vector<int>> pos;
for (int i = 0; i < N; ++i) pos[a[i]].push_back(i + 1);
// diff[x] += contribution of "another group needed" when window size = x.
// For each label with positions p_1..p_m, at window size x the greedy
// group count = 1 + (# of gaps p_{i+1} - group_start that exceed x).
// A safe upper-bound formulation: each adjacent gap d contributes +1 for
// every x in [1, d - 1].
vector<long long> diff(N + 2, 0);
long long totalLabels = pos.size();
for (auto& [lab, ps] : pos) {
// At minimum one group per label at any x >= max span.
for (int i = 0; i + 1 < (int)ps.size(); ++i) {
int d = ps[i + 1] - ps[i];
// a new group is forced when x < d
diff[1] += 1;
diff[d] -= 1;
}
}
// groups(x) = totalLabels + prefix_sum(diff[1..x])
long long running = 0;
for (int x = 1; x <= N; ++x) {
running += diff[x];
cout << (totalLabels + running) << "\n";
}
}
[verify] This counts every adjacent gap as one mandatory break; the actual greedy may collapse multiple gaps inside a single window of width x and is tighter. Use this as a baseline and refine after reading the editorial.
Pitfalls
- Groups span by start-to-end distance, not consecutive gap. Greedy must track the group anchor, not just neighbor differences.
- Per-label greedy across all x. Re-running it from scratch for each x is O(N²); needs the diff-array trick.
- Don't forget x = N. At the largest window, every label collapses to exactly one group.
Problem 2 — Interstellar Intervals
Statement
Positions 1..N each have a preference in {R, B, X} (X = either). Bessie picks a set of disjoint sub-intervals of [1, N], all of the same length; alternately paints them red and blue (starting color is her choice); positions not covered may take any color. Count the colorings of 1..N consistent with the R/B/X preferences and with some valid interval scheme, modulo a prime.
Constraints
- 2 ≤ N ≤ 5×105; the input string has only R, B, X.
- Time limit: 2s.
Key idea
Iterate over the common interval length L (1 ≤ L ≤ N). For fixed L, the coloring is determined by an integer offset (where the first interval starts), a starting color, and how many intervals you place. Validity reduces to a per-position check of whether the forced color (R if covered by an odd-indexed interval, B if even-indexed, free otherwise) matches the preference. Compute valid interval counts via prefix sums; total work is O(N / L) per L → harmonic O(N log N).
[verify] The exact "free positions between intervals" rule and what "valid coloring" must include for uncovered positions needs careful reading of the PDF; the harmonic O(N log N) framing is the typical Gold pattern but the details depend on whether uncovered positions are X-only or free.
Complexity target
O(N log N).
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1'000'000'007;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N; cin >> N;
string s; cin >> s;
// forced[i] = 'R' or 'B' if position i must be that color, else 'X'
// For an interval length L starting at p with starting color C, positions
// p..p+L-1 are color C, p+L..p+2L-1 are the opposite, etc.
// Number of free positions per coloring = number of X positions not covered.
// Each X contributes a factor of 2 (independent red/blue choice if free).
// pow2[k] precomputed
vector<ll> pow2(N + 1, 1);
for (int i = 1; i <= N; ++i) pow2[i] = pow2[i - 1] * 2 % MOD;
// prefR[i]/prefB[i]/prefX[i] = counts of R/B/X in s[0..i-1]
vector<int> pR(N+1,0), pB(N+1,0), pX(N+1,0);
for (int i = 0; i < N; ++i) {
pR[i+1] = pR[i] + (s[i]=='R');
pB[i+1] = pB[i] + (s[i]=='B');
pX[i+1] = pX[i] + (s[i]=='X');
}
ll ans = 0;
// Enumerate interval length L.
for (int L = 1; L <= N; ++L) {
// For each start position p in [0, L) and each starting color, count
// packings where stripes of length L alternate over [p, N).
// Validity: in each red stripe, count of B must be 0; in each blue
// stripe, count of R must be 0; X is always fine.
for (int p = 0; p < L && p < N; ++p) {
for (int startColor = 0; startColor < 2; ++startColor) {
bool ok = true;
int xCovered = 0;
int k = 0; // stripe index
for (int seg = p; seg < N; seg += L, ++k) {
int lo = seg, hi = min(N, seg + L);
int rcnt = pR[hi]-pR[lo], bcnt = pB[hi]-pB[lo], xcnt = pX[hi]-pX[lo];
int color = (startColor + k) & 1; // 0=R, 1=B
if (color==0 && bcnt) { ok = false; break; }
if (color==1 && rcnt) { ok = false; break; }
xCovered += xcnt;
}
if (!ok) continue;
int xTotal = pX[N];
int xFree = xTotal - xCovered;
ans = (ans + pow2[xFree]) % MOD;
}
}
}
cout << ans << "\n";
}
[verify] The above is O(N²/L) per L → O(N² H_N). For N=5·105 that's too slow; the intended solution likely fixes a single starting offset per L and uses prefix sums to jump-test stripes in O(1) each, giving harmonic O(N log N). Treat as a correctness baseline.
Pitfalls
- "Uncovered" positions. Re-read the statement on whether positions outside any chosen interval are forced to X or free — it changes the counting drastically.
- Choice of starting color. Easy to double-count: be explicit about the symmetry.
- Modular arithmetic. All counts mod a prime; precompute powers of 2.
Problem 3 — Job Completion
Statement
N jobs are given. Each job i has a release time si (earliest time at which it becomes available) and a duration ti. You work serially without pause; at any moment after a job has started you can't switch. Pick a subset and order of jobs to maximize the count of jobs completed (a job counts only if started ≥ si and finished before the next one starts).
Constraints
- 1 ≤ N ≤ 2×105; 0 ≤ si ≤ 1018; 1 ≤ ti ≤ 1018; T ≤ 10; sum N ≤ 3×105.
- Time limit: 2s.
Key idea
Classic "maximum number of deadline jobs" pattern (Earliest-Deadline-First with a swap heap). Sort jobs by release time s. Sweep; maintain a max-heap of accepted durations. Try to take the next job: advance current time to max(time, si), add ti, push ti onto the heap. If the new completion time exceeds the next job's release time — well, that's only a problem if you later regret it; instead use the variant where you keep the heap of accepted durations and, when adding a job would violate the constraint, pop the heaviest duration if it exceeds ti. Here the "deadline" is "starts before its successor's release," so we sort by s and greedily pack.
Complexity target
O(N log N) per test case.
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 T; cin >> T;
while (T--) {
int N; cin >> N;
vector<pair<ll,ll>> jobs(N); // (s_i, t_i)
for (auto& [s, t] : jobs) cin >> s >> t;
sort(jobs.begin(), jobs.end()); // by release time ascending
priority_queue<ll> heap; // max-heap of accepted durations
ll cur = 0; // current wall-clock time
ll totalDur = 0;
for (auto [s, t] : jobs) {
// We may idle until s, but only if cur < s.
ll newCur = max(cur, s) + t;
// Tentatively accept this job.
heap.push(t);
cur = newCur;
totalDur += t;
// If our current time has somehow exceeded the next job's release
// we're forced to drop the worst duration. (Captured by checking
// cur > s of *next* job in the outer loop -- here we check post-hoc.)
// The cleanest invariant: if at any point cur > s_i for the i'th
// job we are about to consider, we've over-spent; pop the biggest
// duration on the heap to recover.
}
// The above tentative-accept logic still needs the "regret" step:
// walk jobs again and enforce cur(after i) <= s(i+1).
// (Editorial-style: rebuild with regret.)
// For clarity, we redo with regret inline:
while (!heap.empty()) heap.pop();
cur = 0; ll accepted = 0;
for (int i = 0; i < N; ++i) {
auto [s, t] = jobs[i];
ll start = max(cur, s);
// Tentative accept
heap.push(t);
cur = start + t;
++accepted;
// If after accepting, we have over-committed past the NEXT job's
// release, we may need to drop the longest job so far.
if (i + 1 < N && cur > jobs[i + 1].first) {
ll worst = heap.top(); heap.pop();
cur -= worst;
--accepted;
}
}
cout << accepted << "\n";
}
}
[verify] The exact "what counts as a completed job" rule (does the last job need to finish before some global deadline, or just respect successor release times?) needs the PDF. The EDF-with-regret heap pattern is the standard tool either way.
Pitfalls
- Overflow. s, t up to 1018; sums fit in unsigned 64-bit but signed
long longcan wrap. Use checked addition or__int128for sums. - Greedy direction. Sort by release, not by duration — the heap handles "which to drop" when over-committed.
- Ties on si. Secondary sort by ti ascending usually works; verify with edge tests.
What Gold December 2024 was actually testing
- P1 — difference-array sweep across a parameter. Solve once, contribute to many x's.
- P2 — harmonic enumeration over divisors/lengths. N/L pattern with prefix sums.
- P3 — scheduling with regret heap. The "EDF + max-heap drop" pattern.