USACO 2016 February · Gold · all three problems
Problem 1 — Circular Barn
Statement
n rooms in a ring with one cow per room at the end. ci cows are queued at door i (Σci = n). Cows walk clockwise to their rooms; energy cost is (distance walked)². Minimize total energy. Same problem as Silver P1 but at scale.
Constraints
- 3 ≤ n ≤ 100,000.
- Σci = n.
- Time limit: 2s.
Key idea
Find a "starting door" s such that the running balance bk = Σj=0..k (c(s+j) mod n − 1) is non-negative for all k (this is the classic "gas station" lemma — a unique rotation gives no deficit). From s, simulate FIFO assignment with a queue or a stack of entry positions; cost is Σ (k − entry)². O(n) sweep after finding s; finding s is O(n) by tracking the minimum prefix-sum index.
Complexity target
O(n) time, O(n) memory.
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<int> c(n);
for (auto& x : c) cin >> x;
// Find the door s where prefix sums of (c_i - 1) hit their minimum -- that's the unique safe start.
int s = 0; ll cur = 0, mn = 0;
for (int i = 0; i < n; ++i) {
cur += c[i] - 1;
if (cur < mn) { mn = cur; s = (i + 1) % n; }
}
// Sweep from s with a FIFO of entry positions of in-transit cows.
queue<int> q;
ll cost = 0;
for (int k = 0; k < n; ++k) {
int door = (s + k) % n;
for (int j = 0; j < c[door]; ++j) q.push(k);
ll d = k - q.front(); q.pop();
cost += d * d;
}
cout << cost << "\n";
}
Pitfalls
- Don't loop n start positions. n = 105 makes the Silver O(n²) too slow; the prefix-min trick gives the unique safe start in O(n).
- FIFO, not LIFO. Convex (squared) cost makes "exit oldest" optimal; a stack would balloon worst-case distances.
- Worst-case cost ≈ n³. 1015 — must use long long.
Problem 2 — Circular Barn Revisited
Statement
n rooms in a ring, room i needs ri cows. Farmer John unlocks exactly k exterior doors; each cow enters through some unlocked door and walks clockwise. Choose which k doors to unlock to minimize total walking distance.
Constraints
- 3 ≤ n ≤ 100.
- 1 ≤ k ≤ 7.
- 1 ≤ ri ≤ 106.
- Time limit: 2s.
Key idea
If we fix the unlocked door positions d1 < … < dk on the ring, each room is served by the nearest unlocked door at or before it (clockwise). So the n rooms split into k clockwise arcs, each from one unlocked door up to the next. The cost of an arc starting at door d covering rooms rd, rd+1, …, rd+L−1 is Σj=0..L−1 j · rd+j. Enumerate starting-door positions and split the ring into k arcs. With n ≤ 100 and k ≤ 7, "fix the first door" (n choices), then DP over (current position, arcs used). O(n³ · k) ≈ 7·106.
Complexity target
O(n · n² · k) time, O(n · k) memory after precomputing arc costs.
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, k; cin >> n >> k;
vector<ll> r(n);
for (auto& x : r) cin >> x;
ll ans = LLONG_MAX;
// Try each starting door s as the first unlocked one.
for (int s = 0; s < n; ++s) {
// arc[i][len] = cost of arc of length len starting at offset i from s.
vector<vector<ll>> arc(n, vector<ll>(n + 1, 0));
for (int i = 0; i < n; ++i) {
ll c = 0;
for (int L = 1; i + L <= n; ++L) {
c += (ll)(L - 1) * r[(s + i + L - 1) % n];
arc[i][L] = c;
}
}
// dp[i][j] = min cost to cover first i rooms using j arcs (j-th arc ends just before room i).
vector<vector<ll>> dp(n + 1, vector<ll>(k + 1, LLONG_MAX));
dp[0][0] = 0;
for (int i = 0; i < n; ++i)
for (int j = 0; j < k; ++j) if (dp[i][j] != LLONG_MAX)
for (int L = 1; i + L <= n; ++L)
dp[i + L][j + 1] = min(dp[i + L][j + 1], dp[i][j] + arc[i][L]);
ans = min(ans, dp[n][k]);
}
cout << ans << "\n";
}
Pitfalls
- Fix the first unlocked door. Without anchoring, you'd double-count rotations of the same configuration.
- Arc cost is Σ j · r, not just Σ r. The j-th room in an arc has j cows walking j doors — easy to drop the linear weight.
- Use long long. Max cost ≈ n² · max(r) ≈ 1010.
Problem 3 — Fenced In
Statement
An A×B rectangular field has n internal vertical fences at x = a1, …, an and m internal horizontal fences at y = b1, …, bm, creating (n+1)(m+1) rectangular regions. Compute the minimum total length of fencing to remove so that all regions are connected (the regions, viewed as cells, become 4-connected after removing fence segments).
Constraints
- 1 ≤ A, B ≤ 109.
- 0 ≤ n, m ≤ 2000.
- Output may require 64-bit. Time limit: 2s.
Key idea
Model as MST: each cell is a node, each fence segment between adjacent cells is a candidate edge with weight equal to that segment's length. A vertical fence at x = ai contributes horizontal-cut edges of weight (bj − bj−1) — one per row strip. Symmetric for horizontal fences. Equivalently: sort the n+1 column widths W and the m+1 row heights H in increasing order. Kruskal greedily picks the smallest gap repeatedly; the closed form is Σ Wi · (count of row strips still un-merged at that step) + symmetric for rows. The standard trick: process all (n+m) "small gaps" in sorted order, alternating between bridging a column and a row, multiplying each by the count of remaining strips on the opposite axis.
Complexity target
O((n + m) log(n + m)) time, O(n + m) memory.
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);
ll A, B; int n, m;
cin >> A >> B >> n >> m;
vector<ll> a(n), b(m);
for (auto& x : a) cin >> x;
for (auto& x : b) cin >> x;
sort(a.begin(), a.end()); sort(b.begin(), b.end());
// Column widths W (size n+1) and row heights H (size m+1).
vector<ll> W, H;
{ ll prev = 0; for (ll x : a) { W.push_back(x - prev); prev = x; } W.push_back(A - prev); }
{ ll prev = 0; for (ll y : b) { H.push_back(y - prev); prev = y; } H.push_back(B - prev); }
sort(W.begin(), W.end()); sort(H.begin(), H.end());
// Always connect via the smallest gap of width W[0] or H[0] first (these are "free" in count terms).
// Then merge remaining: each remaining width W[i] (i >= 1) is used (m+1) times if we have not yet
// started rows, else (number of row-strips still un-merged).
ll ans = 0;
int i = 1, j = 1; // pointers into W and H past the minimums
ll colRem = n, rowRem = m; // remaining merges needed on each axis
// Anchor: spend W[0] to glue all (m+1) row strips along the thinnest column gap, and
// H[0] to glue all (n+1) col strips along the thinnest row gap.
ans += W[0] * rowRem; // merges rowRem rows once each via the cheapest column gap
ans += H[0] * colRem; // merges colRem cols once each via the cheapest row gap
// Note: above is the canonical closed-form for this problem.
while (i <= n && j <= m) {
if (W[i] < H[j]) { ans += W[i] * rowRem; --colRem; ++i; }
else { ans += H[j] * colRem; --rowRem; ++j; }
}
while (i <= n) { ans += W[i] * rowRem; ++i; }
while (j <= m) { ans += H[j] * colRem; ++j; }
cout << ans << "\n";
}
Pitfalls
- Don't materialize the (n+1)(m+1) grid. Up to ~4·106 cells with Gold limits is okay, but the cleaner counting argument avoids per-cell DSU entirely.
- The two "anchor" gaps W[0] and H[0] are special. They're used to bridge an entire row of strips / column of strips, not single cells.
- A, B up to 109. Gap widths fit in 32-bit but the cross-products do not — store and accumulate in long long.
What Gold February 2016 was actually testing
- P1 — linear-time circular assignment. Find the unique safe rotation by prefix-min, then sweep with a FIFO.
- P2 — DP on arcs of a ring. Anchor the first door, then 1D DP over (position, arcs used).
- P3 — grid MST closed-form. Recognize that sorted gap widths × remaining-strips count gives the Kruskal answer in O((n+m) log).