USACO 2021 January · Gold · all three problems
Problem 1 — Uddered but not Herd (Gold)
Statement
Same setup as Bronze: Mildred recites a cowphabet (a permutation of 26 letters) repeatedly, and Farmer Nhoj records the heard string T. But here the cowphabet is NOT given — we choose it adversarially to minimize the number of complete recitations consistent with T. Output that minimum.
Constraints
- 1 ≤ |T| ≤ 105.
- Time limit: 4s (twice the default).
Key idea
Walk T left to right. At position i with letter T[i], we must start a new recitation if and only if the pair (T[i−1], T[i]) is forced to occur "in this order" within a cowphabet AND the cowphabet has already placed T[i] before T[i−1]. Equivalently: we need to count the minimum number of recitations such that the multiset of ordered adjacent pairs (T[i−1], T[i]) in T can be consistent with one global ordering. Concretely, consider directed adjacency edges T[i−1] → T[i]. We start a new recitation exactly when we encounter a pair (a, b) such that we've already committed b → a elsewhere — i.e., the directed graph of "must precede" relations would otherwise contain a cycle. Greedy answer: scan T and count positions i where we've already seen the pair (T[i], prev) in some earlier recitation. The total minimum is 1 + (number of forced restarts).
Complexity target
O(|T| · 26) using a 26×26 boolean "seen forward edge" table.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
string t; cin >> t;
int n = (int)t.size();
if (n == 0) { cout << 0 << "\n"; return 0; }
// seen[a][b] = true iff we've committed "a before b" in the current
// (still-open) cowphabet recitation. A new recitation lets us pick a
// fresh ordering, so seen resets to "the current recitation's edges so far".
// We accumulate the GLOBAL precedence graph in "forced"; if adding
// T[i-1] -> T[i] would close a cycle with already-forced edges in the
// SAME recitation, we open a new recitation.
bool forced[26][26] = {}; // forced[a][b] = a must come before b
auto reaches = [&](int a, int b) {
// BFS in the 26-node DAG to check if b is reachable from a.
bool vis[26] = {}; vis[a] = true;
queue<int> q; q.push(a);
while (!q.empty()) {
int u = q.front(); q.pop();
if (u == b) return true;
for (int v = 0; v < 26; ++v)
if (forced[u][v] && !vis[v]) { vis[v] = true; q.push(v); }
}
return false;
};
int recitations = 1;
auto resetForCurrent = [&]() {
for (int i = 0; i < 26; ++i)
for (int j = 0; j < 26; ++j)
forced[i][j] = false;
};
int prev = t[0] - 'a';
for (int i = 1; i < n; ++i) {
int c = t[i] - 'a';
if (c == prev) { // same letter twice in a row -> new recitation
++recitations; resetForCurrent();
} else if (reaches(c, prev)) {
// c must come before prev (forced), but we need prev -> c now -> new recitation.
++recitations; resetForCurrent();
} else {
forced[prev][c] = true;
}
prev = c;
}
cout << recitations << "\n";
}
Pitfalls
- Adjacent equal letters force a restart. One recitation can have each letter at most once.
- The "cycle would close" check is per-current-recitation, not global. Restart resets the constraint graph.
- Transitive closure matters. Don't just check the single edge — a chain a → … → c forces c after a.
- The 26-node BFS per step is fine: 105 · 262 ≈ 6.7 · 107 ops.
Problem 2 — Telephone
Statement
N cows stand in a line at positions 1..N; cow i has breed bi ∈ [1, K]. A K × K compatibility matrix S has Sij ∈ {0,1} indicating whether breed i can pass a message directly to breed j. Transmission from cow i to cow j (with Sbi, bj = 1) costs |i − j|. Find the minimum total cost to relay a message from cow 1 to cow N, or −1 if impossible.
Subtask structure
- Tests 1–5: N ≤ 1000 (naïve Dijkstra OK).
- Tests 6–13: full N.
Constraints
- 1 ≤ N ≤ 5 · 104.
- 1 ≤ K ≤ 50.
- Sij ∈ {0,1} and need not be symmetric.
- Time limit: 2s.
Key idea
N up to 5 · 104 means N² ≈ 2.5 · 109 edges — too many. Reduce edges by noting that for each cow i and each "destination breed" b', the best handoff is always to the nearest cow of breed b'. So build, for each position i and each breed c with Sbi, c = 1, edges i → (nearest cow of breed c to left) and i → (nearest cow of breed c to right). This gives O(N · K) edges. Run 0-1-style Dijkstra (or just plain Dijkstra) from cow 1.
Complexity target
O(N · K · log(N · K)) ≈ 5 · 104 · 50 · 22 ≈ 5.5 · 107.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = (ll)4e18;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, K; cin >> N >> K;
vector<string> S(K);
for (auto& r : S) cin >> r;
vector<int> b(N);
for (int i = 0; i < N; ++i) { int x; cin >> x; b[i] = x - 1; }
// For each breed c, list positions in order.
vector<vector<int>> byBreed(K);
for (int i = 0; i < N; ++i) byBreed[b[i]].push_back(i);
// Build adjacency lazily inside Dijkstra: for each i, for each breed c
// compatible (S[b[i]][c] == '1'), the only useful edges are to the two
// nearest cows of breed c -- left neighbor and right neighbor.
vector<ll> dist(N, INF);
priority_queue<pair<ll,int>, vector<pair<ll,int>>, greater<>> pq;
dist[0] = 0; pq.push({0, 0});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d != dist[u]) continue;
if (u == N - 1) break;
for (int c = 0; c < K; ++c) if (S[b[u]][c] == '1') {
auto& v = byBreed[c];
auto it = lower_bound(v.begin(), v.end(), u);
for (int delta : {-1, 0}) { // candidate right (it+0) and left (it-1)
auto jt = it + delta;
if (jt < v.begin() || jt >= v.end()) continue;
int w = *jt;
if (w == u) continue;
ll nd = d + abs(w - u);
if (nd < dist[w]) { dist[w] = nd; pq.push({nd, w}); }
}
}
}
cout << (dist[N - 1] >= INF ? -1 : dist[N - 1]) << "\n";
}
Pitfalls
- Don't build O(N²) edges. For each (i, target-breed) pair, only the two nearest cows of that breed matter — any farther cow's path through them is no worse.
- S is not symmetric. Use S[b[u]][c], not S[c][b[u]].
- Self-breed may be disallowed — Sii can be 0. Don't shortcut "same breed" handoffs.
- Unreachable. Output −1 when dist[N − 1] stays INF.
Problem 3 — Dance Mooves (Gold)
Statement
Same as Silver Dance Mooves but the number of full rounds is M, where M can be up to 1018. For each cow output the number of distinct positions it occupies during M rounds.
Constraints
- 2 ≤ N ≤ 105.
- 1 ≤ K ≤ 2 · 105.
- 1 ≤ M ≤ 1018.
- Time limit: 4s (twice default).
Key idea
Same machinery as Silver: simulate one round, record visited[p] (positions cow p touches in round 1), and build the end-of-round permutation π. Cows on the same π-cycle of length L will revisit each other's visited[·] sets. With M rounds, cow p touches the union of visited[q] for the first min(M, L) cows on its cycle, starting at p. If M ≥ L, that's the whole cycle's union. If M < L, use a sliding window of length M over the cyclically-ordered visited[·] lists. Answer = size of the window's union. Use offline frequency counters over a doubled cycle and add/remove events.
Complexity target
O((N + K) log N) — sliding-window union over each cycle.
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; ll M; cin >> N >> K >> M;
vector<int> pos(N + 1), invPos(N + 1);
iota(pos.begin(), pos.end(), 0);
iota(invPos.begin(), invPos.end(), 0);
vector<vector<int>> visited(N + 1);
for (int p = 1; p <= N; ++p) visited[p].push_back(p);
for (int i = 0; i < K; ++i) {
int a, b; cin >> a >> b;
int ca = invPos[a], cb = invPos[b];
swap(pos[ca], pos[cb]);
invPos[a] = cb; invPos[b] = ca;
visited[ca].push_back(b);
visited[cb].push_back(a);
}
// Permutation P[p] = cow whose "starting position" gets cow p's role next round.
// Standard trick: cow p ends at position pos[p]; the cow whose ORIGINAL position
// equals pos[p] is just (label) pos[p] -- so P[p] = pos[p] is wrong; we want
// the cow that will, next round, be in cow p's current spot -- but for counting
// we only need the cycle structure of p -> pos[p].
vector<int> ans(N + 1, 0);
vector<int> visIdx(N + 1, -1); // which cycle position
vector<int> cycId(N + 1, -1);
for (int s = 1; s <= N; ++s) if (cycId[s] == -1) {
// Collect the cycle starting at s.
vector<int> cyc;
int x = s;
while (cycId[x] == -1) { cycId[x] = s; visIdx[x] = (int)cyc.size(); cyc.push_back(x); x = pos[x]; }
int L = (int)cyc.size();
ll W = min<ll>(M, (ll)L); // sliding window length
// If window covers the cycle entirely, answer is the union of visited[*] over the cycle.
if (W == L) {
unordered_set<int> uni;
for (int q : cyc) for (int v : visited[q]) uni.insert(v);
int sz = (int)uni.size();
for (int q : cyc) ans[q] = sz;
continue;
}
// Sliding window of length W over the doubled cycle [cyc, cyc].
// freq[v] = how many times position v appears in current window.
unordered_map<int,int> freq;
int unionSize = 0;
auto add = [&](int q) {
for (int v : visited[q]) if (freq[v]++ == 0) ++unionSize;
};
auto rem = [&](int q) {
for (int v : visited[q]) if (--freq[v] == 0) --unionSize;
};
for (int i = 0; i < (int)W; ++i) add(cyc[i]);
ans[cyc[0]] = unionSize;
for (int i = 1; i < L; ++i) {
rem(cyc[i - 1]);
add(cyc[(i + (int)W - 1) % L]);
ans[cyc[i]] = unionSize;
}
}
for (int p = 1; p <= N; ++p) cout << ans[p] << "\n";
}
Pitfalls
- M can saturate the cycle. If M ≥ cycle length, all cows on the cycle share the full union — handle separately, otherwise the sliding window degenerates.
- Σ |visited[p]| = N + 2K. Each swap appends to two visit lists. Sliding window add/remove is amortized linear in that total.
- unordered_map can TLE with adversarial hashing — use a custom hash or replace with a vector indexed by position.
- Position ↔ cow mapping during simulation is the bug source: keep both pos[] and invPos[] and swap them coherently.
What Gold January 2021 was actually testing
- P1 — DAG / cycle reasoning on 26 letters. Each recitation is a DAG; we restart when adding an edge would close a cycle.
- P2 — graph reduction. Don't build O(N²) edges; nearest-of-each-breed is sufficient.
- P3 — sliding window over functional-graph cycles. Same Silver structure scaled to 1018 rounds.