USACO 2020 January · Gold · all three problems
Problem 1 — Time is Mooney
Statement
A directed graph of N cities with edges (u, v). Visiting city v earns mv mooney. Each traversed edge costs 1 day; at the end of T days Bessie loses C · T² mooney (cost grows quadratically with time). She starts and ends at city 1. Maximize net profit (sum of m at visited cities − C · T²).
Constraints
- 2 ≤ N ≤ 1000, 1 ≤ M ≤ 2000 (edges).
- 0 ≤ mv ≤ 1000, 1 ≤ C ≤ 1000.
- Time limit: 2s.
Key idea
Time T is bounded: best gross profit per day is ≤ max m ≤ 1000, so once C · T > 1000, going further is strictly worse. T_max ≈ 1000. DP: dp[t][v] = max mooney collected after t days ending at v. Transition: dp[t+1][v] = max over (u, v) of dp[t][u] + mv. Answer = max over t of dp[t][1] − C · t².
Complexity target
O(T_max · M) ≈ 1000 · 2000 = 2 · 106.
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, M; ll C;
cin >> N >> M >> C;
vector<ll> mv(N);
for (auto& x : mv) cin >> x;
vector<pair<int,int>> edges(M);
for (auto& [u,v] : edges) { cin >> u >> v; --u; --v; }
const int T = 1001; // T > sqrt(max_m / C) * something; 1000 suffices for the stated bounds
vector<vector<ll>> dp(T + 1, vector<ll>(N, LLONG_MIN));
dp[0][0] = 0;
ll ans = 0;
for (int t = 0; t < T; ++t) {
for (auto& [u, v] : edges) {
if (dp[t][u] == LLONG_MIN) continue;
ll cand = dp[t][u] + mv[v];
if (cand > dp[t + 1][v]) dp[t + 1][v] = cand;
}
// Bessie must end at city 1 (index 0). Net profit at day t+1 if back home.
if (dp[t + 1][0] != LLONG_MIN) ans = max(ans, dp[t + 1][0] - C * (ll)(t + 1) * (t + 1));
}
cout << ans << "\n";
}
Pitfalls
- Starting city earns 0, not m1 — the "earn on visit" applies only when Bessie arrives at a city, not at the start.
- T bound must be justified. If max m = 1000 and C = 1, T_max ≈ sqrt(1000·N) ≈ 1000. Pick T = 1001 and you cover every case in the stated bounds.
- Initialize dp to −∞ except dp[0][0] = 0; otherwise a phantom 0 leaks into unreachable states.
- 64-bit. Net profit can be 103·1000 − 1·106 ≈ 0; intermediate sums stay small but use ll for safety.
Problem 2 — Farmer John Solves 3SUM
Statement
Given an array a1..N, define f(i, j) = number of unordered pairs (p, q) with i ≤ p < q ≤ j and ap + aq = 0. Answer Q queries each asking for f(l, r).
Constraints
- 1 ≤ N ≤ 5000, 1 ≤ Q ≤ 105.
- |ai| ≤ 106.
- Time limit: 2s, memory 256 MB.
Key idea
Precompute cnt[i][j] = number of valid pairs entirely inside [i, j]. Iterate j outward and for each j sweep i from j−1 down to 0, adding +1 whenever ai + aj = 0. Then build a 2D prefix sum so cnt[i][j] = cnt[i+1][j] + cnt[i][j−1] − cnt[i+1][j−1] + (ai+aj==0). Each query is O(1). Total O(N²) precomp, O(Q) answer.
Complexity target
O(N² + Q). 2.5 · 107 table cells, each a long long → ~200 MB if naive; use 32-bit pairs or store row-by-row carefully. Most ACs use a vector<vector<long long>> with N = 5000 which is 200 MB — tight but fits with int32.
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, Q; cin >> N >> Q;
vector<ll> a(N);
for (auto& x : a) cin >> x;
// cnt[i][j] = # pairs (p, q), i <= p < q <= j, with a[p] + a[q] = 0.
// Use long long for the answers (can grow to ~N^2/2 = 1.25e7, fits 32-bit
// but we'll be safe). Row 0..N-1, col 0..N-1.
vector<vector<ll>> cnt(N, vector<ll>(N, 0));
for (int i = N - 1; i >= 0; --i) {
for (int j = i + 1; j < N; ++j) {
cnt[i][j] = cnt[i + 1][j] + cnt[i][j - 1] - cnt[i + 1][j - 1];
if (a[i] + a[j] == 0) ++cnt[i][j];
}
}
while (Q--) {
int l, r; cin >> l >> r; --l; --r;
cout << cnt[l][r] << "\n";
}
}
Pitfalls
- Memory. 5000² · 8 bytes = 200 MB → over the 256 MB limit when combined with other allocations. Use
intif value safely fits (≤ 2·109; here ~107, fine). - 2D inclusion–exclusion. cnt[i+1][j] + cnt[i][j-1] − cnt[i+1][j-1] is the standard rectangle-merge identity; the +1 hits only when (ai, aj) is itself a valid pair.
- 1-indexed queries. Convert l, r to 0-based before indexing the table.
- Don't try Mo's algorithm. N is small enough that O(N²) precomp + O(1) query beats any sqrt-decomp approach.
Problem 3 — Springboards
Statement
Bessie walks from (0, 0) to (X, Y) on an integer grid using only +x or +y unit moves. There are P springboards; each instantly teleports her from (x1, y1) to (x2, y2) with x2 ≥ x1, y2 ≥ y1 (a free Manhattan jump). Minimize the total Manhattan distance she actually walks (springboard hops are free).
Constraints
- 1 ≤ P ≤ 105, 0 ≤ all coords ≤ 109.
- Time limit: 4s.
Key idea
DP: for each springboard i, let f(i) = min Manhattan distance walked to arrive at springboard i's start (x1i, y1i), then take the springboard to (x2i, y2i). The walked distance is (x1i + y1i) − (x2j + y2j) + g(j), summed over the best predecessor j with x2j ≤ x1i, y2j ≤ y1i. Coordinate-compress y, sort events by x, and maintain a Fenwick tree of "min g(j) − x2 − y2" indexed by y2. Final answer: X + Y − max savings reachable to (X, Y).
Complexity target
O(P log P) after coordinate compression.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = (ll)4e18;
struct Fenw {
int n; vector<ll> t;
Fenw(int n) : n(n), t(n + 1, INF) {}
void upd(int i, ll v) { for (++i; i <= n; i += i & -i) t[i] = min(t[i], v); }
ll qry(int i) { ll r = INF; for (++i; i > 0; i -= i & -i) r = min(r, t[i]); return r; }
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int P; ll X, Y;
cin >> P >> X; // statement order: P first, then X, Y? [verify cpid=995]
// Common format: first line "P N" where N is the grid (X=Y=N). Adjust as needed.
Y = X;
vector<array<ll, 4>> sp(P); // x1, y1, x2, y2
vector<ll> ys;
for (auto& s : sp) {
cin >> s[0] >> s[1] >> s[2] >> s[3];
ys.push_back(s[1]); ys.push_back(s[3]);
}
sort(ys.begin(), ys.end()); ys.erase(unique(ys.begin(), ys.end()), ys.end());
auto yid = [&](ll y){ return (int)(lower_bound(ys.begin(), ys.end(), y) - ys.begin()); };
// Events: process springboards by (x1, +0) "use as endpoint" then (x2, +1) "register".
vector<tuple<ll, int, int>> ev; // (x, type 0=use 1=register, idx)
for (int i = 0; i < P; ++i) {
ev.push_back({sp[i][0], 0, i});
ev.push_back({sp[i][2], 1, i});
}
sort(ev.begin(), ev.end());
Fenw fw((int)ys.size());
// dp[i] = min walked distance to reach end of springboard i (i.e., at (x2_i, y2_i))
vector<ll> dp(P, INF);
for (auto& [x, t, i] : ev) {
if (t == 0) {
// Arrive at start of springboard i. Best predecessor j has x2_j <= x1_i, y2_j <= y1_i.
ll best = fw.qry(yid(sp[i][1]));
ll arr = sp[i][0] + sp[i][1]; // Manhattan from origin via best predecessor
ll cost = (best == INF) ? arr : best + arr;
dp[i] = cost; // walked-distance to end of springboard i (springboard hop is free)
} else {
// Register springboard i's endpoint.
if (dp[i] < INF) fw.upd(yid(sp[i][3]), dp[i] - sp[i][2] - sp[i][3]);
}
}
// Answer = X + Y minus max saving to (X, Y). Equivalent formula:
// best dp[i] + (X - x2_i) + (Y - y2_i) over i (and the option of skipping all boards).
ll ans = X + Y;
for (int i = 0; i < P; ++i) if (dp[i] < INF) {
ans = min(ans, dp[i] + (X - sp[i][2]) + (Y - sp[i][3]));
}
cout << ans << "\n";
}
Pitfalls
- Springboard hop is free — only walked Manhattan distance counts; double-check this when defining your DP cost.
- Event ordering at equal x. Process "use as endpoint" (type 0) before "register" (type 1) so a board cannot use itself as predecessor.
- Coordinate compression on y — raw y is up to 109, can't index a Fenwick directly.
- Always include the no-springboard answer X + Y as a baseline.
What Gold January 2020 was actually testing
- P1 — bounded-time DP on a graph. Recognize T_max from the cost function and run a layered DP.
- P2 — 2D rectangle counting via inclusion–exclusion. A clean prefix-sum identity precomputes every interval.
- P3 — offline sweep with BIT. Reduce a 2D dominance DP to events sorted by x with a Fenwick over compressed y.