USACO 2017 January · Gold · all three problems
Problem 1 — Balanced Photo
Statement
N cows stand in a line with distinct heights h1..hN. For each cow i, let Li be the number of cows on her left that are strictly taller and Ri the same on her right. Cow i is unbalanced when max(Li, Ri) > 2 · min(Li, Ri). Count the unbalanced cows.
Constraints
- 1 ≤ N ≤ 105.
- 0 ≤ hi ≤ 109, all distinct.
- Time limit: 2s (Gold default).
Key idea
Coordinate-compress heights to ranks 1..N. Compute Li with a Fenwick tree indexed by rank: sweep left-to-right and at position i, Li = (# inserted so far) − (# of inserted ranks ≤ rank(hi)) = (i) − prefix sum up to rank(hi). Then sweep right-to-left for Ri. Compare max/min with the doubled threshold (use long long).
Complexity target
O(N log N) total — two BIT sweeps plus the rank-compression sort.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
struct BIT {
int n; vector<int> t;
BIT(int n) : n(n), t(n + 1, 0) {}
void upd(int i) { for (; i <= n; i += i & -i) ++t[i]; }
int qry(int i) { int s = 0; for (; i > 0; i -= i & -i) s += t[i]; return s; }
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N; cin >> N;
vector<int> h(N);
for (auto& x : h) cin >> x;
// Rank-compress: rank[i] in 1..N, larger height -> larger rank.
vector<int> srt = h;
sort(srt.begin(), srt.end());
vector<int> r(N);
for (int i = 0; i < N; ++i)
r[i] = int(lower_bound(srt.begin(), srt.end(), h[i]) - srt.begin()) + 1;
// L[i] = # of taller cows to the left of i.
vector<int> L(N), R(N);
BIT bl(N);
for (int i = 0; i < N; ++i) {
L[i] = i - bl.qry(r[i]); // inserted-so-far minus those with rank <= r[i]
bl.upd(r[i]);
}
BIT br(N);
for (int i = N - 1; i >= 0; --i) {
R[i] = (N - 1 - i) - br.qry(r[i]);
br.upd(r[i]);
}
int ans = 0;
for (int i = 0; i < N; ++i) {
long long a = L[i], b = R[i];
long long lo = min(a, b), hi = max(a, b);
if (hi > 2 * lo) ++ans;
}
cout << ans << "\n";
}
Pitfalls
- Strictly taller. Heights are distinct, but if you accidentally use ≤ you'll count the cow itself; the formula above subtracts a strict-prefix count correctly.
- min = 0 is allowed. If min = 0 and max > 0, the cow is unbalanced (hi > 2 · 0 = 0). Don't divide by min.
- O(N²) brute force TLEs. N = 10⁵ means 10¹⁰ — far beyond 2s. BIT or merge-sort inversions is the move.
- Use long long for 2 · Li. Within int it's fine here (L ≤ 10⁵), but it's cheap insurance.
Problem 2 — Hoof, Paper, Scissors
Statement
Same setup as Silver, but Bessie may switch her gesture at most K times across N rounds (0 ≤ K ≤ 20). Maximize wins given FJ's known sequence.
Constraints
- 1 ≤ N ≤ 105.
- 0 ≤ K ≤ 20.
- FJ's gesture per round is H, P, or S.
- Time limit: 2s.
Key idea
DP. State dp[i][k][g] = best wins through the first i rounds with k switches used so far, currently playing gesture g. Transition: at round i+1, either keep g (dp[i+1][k][g] = dp[i][k][g] + win(g, fj[i+1])) or switch to a different g' (dp[i+1][k+1][g'] = dp[i][k][g] + win(g', fj[i+1])). Final answer = max over k ≤ K and g of dp[N][k][g]. Memory: O(N · K · 3); roll on i to save space if needed.
Complexity target
O(N · K · 9) ≈ 10⁵ · 20 · 9 = 1.8 · 10⁷. Comfortable in 2s.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, K; cin >> N >> K;
vector<int> fj(N);
for (int i = 0; i < N; ++i) {
char c; cin >> c;
fj[i] = (c == 'H' ? 0 : c == 'P' ? 1 : 2);
}
// Winning gesture vs fj: H(0)<-S(2), P(1)<-H(0), S(2)<-P(1).
auto wins = [](int bessie, int john) {
return (bessie == 0 && john == 2) ||
(bessie == 1 && john == 0) ||
(bessie == 2 && john == 1);
};
// Rolling DP on rounds. cur[k][g], nxt[k][g].
const int NEG = INT_MIN / 2;
vector<vector<int>> cur(K + 1, vector<int>(3, NEG));
for (int g = 0; g < 3; ++g) cur[0][g] = 0;
for (int i = 0; i < N; ++i) {
vector<vector<int>> nxt(K + 1, vector<int>(3, NEG));
for (int k = 0; k <= K; ++k)
for (int g = 0; g < 3; ++g) if (cur[k][g] > NEG / 2) {
int v = cur[k][g] + (wins(g, fj[i]) ? 1 : 0);
nxt[k][g] = max(nxt[k][g], v); // keep gesture
if (k < K)
for (int g2 = 0; g2 < 3; ++g2) if (g2 != g) {
int v2 = cur[k][g] + (wins(g2, fj[i]) ? 1 : 0);
nxt[k + 1][g2] = max(nxt[k + 1][g2], v2);
}
}
cur = move(nxt);
}
int best = 0;
for (int k = 0; k <= K; ++k) for (int g = 0; g < 3; ++g) best = max(best, cur[k][g]);
cout << best << "\n";
}
Pitfalls
- Index k counts the switches used so far, not the number of gesture segments. The first segment costs 0 switches.
- Allow k < K (strict) when switching, so the boundary case K = 0 (no switches) reduces to "play one gesture forever".
- Roll the DP to avoid 10⁵ · 21 · 3 int arrays if memory matters; the version above already uses two rows.
- O(N · K · K) is too slow if you also iterate k inside the transition unnecessarily. Keep transitions O(1) per (k, g).
Problem 3 — Cow Navigation
Statement
An N×N grid (N ≤ 20) has empty cells and haybales. Bessie starts at the lower-left cell facing either up or right (unknown to you) and must reach the upper-right cell. You give a fixed command sequence from {F, L, R}: F moves forward (no-op into a wall/haybale), L/R rotate. Once a copy of Bessie reaches the goal, she ignores future commands. Find the shortest sequence that succeeds for both starting orientations.
Constraints
- 2 ≤ N ≤ 20.
- Cells are 'E' (empty) or 'H' (haybale). Start (1,1) and goal (N,N) are empty.
- A path of empty cells from start to goal is guaranteed.
- Time limit: 2s.
Key idea
BFS on the joint state (posup, dirup, posright, dirright): one virtual Bessie for each starting orientation, advanced simultaneously by every command. State space ≤ N² · 4 · N² · 4 ≈ 2.5 · 10⁶ for N = 20, edges × 3 commands. Once one copy reaches (N,N) it "freezes" (use a goal sentinel for that copy so further commands don't change it). The answer is the shortest path to a state where both copies are at the goal.
Complexity target
O(N⁴ · 16 · 3) BFS edges ≈ 7.7 · 10⁶. Fits easily.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int N;
vector<string> g;
// Directions: 0=up, 1=right, 2=down, 3=left. dr, dc match "row 0 at top".
// We treat (1,1) at lower-left = grid row N-1, col 0; (N,N) = row 0, col N-1.
int dr[4] = {-1, 0, 1, 0};
int dc[4] = { 0, 1, 0,-1};
struct S { int r1,c1,d1, r2,c2,d2; };
bool atGoal(int r, int c) { return r == 0 && c == N - 1; }
int encode(const S& s) {
return (((s.r1 * N + s.c1) * 4 + s.d1) * (N * N * 4))
+ ((s.r2 * N + s.c2) * 4 + s.d2);
}
S step(S s, int cmd) {
auto move1 = [&](int& r, int& c, int& d) {
if (atGoal(r, c)) return; // frozen at goal
if (cmd == 1) { d = (d + 3) & 3; return; } // L
if (cmd == 2) { d = (d + 1) & 3; return; } // R
int nr = r + dr[d], nc = c + dc[d];
if (nr >= 0 && nr < N && nc >= 0 && nc < N && g[nr][nc] == 'E') { r = nr; c = nc; }
};
move1(s.r1, s.c1, s.d1);
move1(s.r2, s.c2, s.d2);
return s;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> N;
g.assign(N, "");
for (auto& r : g) cin >> r;
// Copy 1 starts facing up (dir 0), copy 2 facing right (dir 1), both at (N-1, 0).
S start{N - 1, 0, 0, N - 1, 0, 1};
unordered_map<long long, int> dist;
auto key = [&](const S& s) {
return ((long long)encode(s));
};
queue<S> q; q.push(start); dist[key(start)] = 0;
while (!q.empty()) {
S s = q.front(); q.pop();
if (atGoal(s.r1, s.c1) && atGoal(s.r2, s.c2)) {
cout << dist[key(s)] << "\n"; return 0;
}
int d0 = dist[key(s)];
for (int cmd = 0; cmd < 3; ++cmd) {
S t = step(s, cmd);
long long k = key(t);
if (!dist.count(k)) { dist[k] = d0 + 1; q.push(t); }
}
}
return 0;
}
Pitfalls
- "Once she reaches the goal she ignores further commands." Freeze each copy independently or you'll let extra commands push her off (N,N).
- Coordinate frame. The statement uses lower-left = (1,1) and upper-right = (N,N); the input lists the top row first. Make sure "up" decreases the grid-row index.
- Joint state, not separate paths. Optimizing each orientation independently and concatenating is wrong — the same command must serve both.
- Don't allow F to push into a haybale or wall. No-op silently; this is also what makes "freezing at goal" easy to express.
What Gold January 2017 was actually testing
- P1 — Fenwick / inversion counting. Two BIT sweeps for "taller to the left/right" — the canonical first-Gold ad-hoc structure.
- P2 — DP with a switch budget. Add a "k switches used" axis to a per-round state DP.
- P3 — BFS over a product state space. Two virtual agents driven by one command stream; classic "agnostic-to-state" puzzle.