USACO 2021 February · Platinum · all three problems
Problem 1 — No Time to Dry
Statement
Bessie has a 1D canvas of N segments with desired colours c1..cN (1 to N, lighter is smaller). A stroke paints a contiguous range with a single colour, but she cannot paint a lighter colour over a darker one. For each of Q queries [a, b], find the minimum number of strokes needed to paint segments a..b to their target colours (segments outside [a, b] don't matter).
Constraints
- 1 ≤ N, Q ≤ 2 · 105.
- 1 ≤ ci ≤ N.
- Subtasks: 1–2 N, Q ≤ 100; 3–5 N, Q ≤ 5000; 6–10 ci ≤ 10; 11–20 full.
- Time limit: 2s.
Key idea
The answer for [a, b] equals the number of distinct colours that appear in c[a..b] with the
twist that runs of the same colour can be painted in one stroke — but only when there is no strictly
smaller (lighter) colour strictly between two equal-colour positions. Formally, define
next[i] = smallest j > i with c[j] = c[i], provided min(c[i+1..j-1]) ≥ c[i]; else
next[i] = ∞. Then the answer = (b − a + 1) − #{i ∈ [a, b−1] : c[i] == c[i+1] or
next[i] ≤ b with all values in (i, next[i]] ≥ c[i]}. Offline sweep with a segment tree of
"active prev-pointers" and Mo-style answering, or directly: sort queries by b, scan i = 1..N
maintaining a Fenwick of indicator-at-prev[i] when prev[i] is "still mergeable", and answer each
query b by range-sum on [a, b]. Standard O((N + Q) log N).
Complexity target
O((N + Q) log N) with a Fenwick tree.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
struct BIT {
int n; vector<int> f;
BIT(int n) : n(n), f(n + 2, 0) {}
void upd(int i, int v) { for (++i; i <= n; i += i & -i) f[i] += v; }
int sum(int i) { int s = 0; for (++i; i > 0; i -= i & -i) s += f[i]; return s; }
int range(int l, int r) { return sum(r) - (l ? sum(l - 1) : 0); }
};
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, Q; cin >> N >> Q;
vector<int> c(N);
for (auto& x : c) cin >> x;
// prev[i] = largest j < i with c[j] == c[i] and min(c[j+1..i-1]) >= c[i]; else -1.
// We compute it by sweeping with a monotonic stack per colour.
vector<int> prev(N, -1);
vector<int> lastIdx(N + 1, -1);
// Stack of indices with strictly increasing colour values; while top has
// colour > c[i], pop (those can no longer reach a same-colour predecessor
// because c[i] sits between them as a smaller value).
stack<int> st;
for (int i = 0; i < N; ++i) {
while (!st.empty() && c[st.top()] > c[i]) {
lastIdx[c[st.top()]] = -1; // invalidate same-colour bookkeeping
st.pop();
}
if (lastIdx[c[i]] != -1 && (st.empty() || c[st.top()] >= c[i]))
prev[i] = lastIdx[c[i]];
lastIdx[c[i]] = i;
st.push(i);
}
// Offline: sort queries by right endpoint, answer with Fenwick on prev[].
vector<tuple<int,int,int>> qs(Q);
for (int i = 0; i < Q; ++i) { int a, b; cin >> a >> b; --a; --b; qs[i] = {b, a, i}; }
sort(qs.begin(), qs.end());
BIT bit(N);
vector<int> ans(Q);
int idx = 0;
for (auto [b, a, qi] : qs) {
while (idx <= b) {
if (prev[idx] != -1) bit.upd(prev[idx], 1);
++idx;
}
int mergeable = bit.range(a, b);
ans[qi] = (b - a + 1) - mergeable;
}
for (int v : ans) cout << v << "\n";
}
// [verify edge cases on prev[] when equal colours surround a smaller value]
// https://usaco.org/index.php?page=viewproblem2&cpid=1116
Pitfalls
- "Lighter over darker" is forbidden. So a run of colour C that has a value < C between two equal Cs cannot be merged; that's the only constraint that matters.
- Don't confuse "distinct colours in range" with the answer. Two distant equal-colour runs separated only by larger colours can share a stroke.
- Offline sweep simplifies the segment tree. Process queries in order of right endpoint; only insertions into the BIT.
- Index conversion. Statement is 1-indexed; convert once on input.
Problem 2 — Minimizing Edges
Statement
Given a connected undirected graph G on N vertices, define fG(a, b) = true iff there is a walk from vertex 1 to vertex a using exactly b edges (with repetition allowed; self-loops permitted, no parallel edges). Build a graph G' on the same vertex set with fG' ≡ fG, minimising the number of edges of G'. Output that minimum count.
Constraints
- 1 ≤ T ≤ 5 · 104 test cases.
- 2 ≤ N ≤ 105, N−1 ≤ M ≤ N(N+1)/2.
- Sum of N over tests ≤ 105; sum of M ≤ 2 · 105.
- Time limit: 5s.
Key idea
fG(a, b) depends only on (i) the BFS distance d(1, a) from vertex 1 and (ii) whether the component containing a has an odd cycle reachable on or before reaching a (parity-mixing). Hence the minimum G' is built from BFS layers L0, L1, … from vertex 1: for every vertex v ∈ Lk (k ≥ 1) add one edge to some vertex in Lk−1 — that's N−1 edges, the BFS tree. Then for each vertex a where odd-walks of length d(1, a) + 1, d(1, a) + 3, … must also be achievable, add a self-loop at some vertex in Lk (or an intra-layer edge) iff the original component had an odd cycle reachable to that layer. The bookkeeping is BFS + parity check; the count of required odd-loops is the number of distinct "odd-reachable layer regions".
Complexity target
O(N + M) per test using BFS and a single parity-flag DSU/scan.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin >> T;
while (T--) {
int N, M; cin >> N >> M;
vector<vector<int>> g(N + 1);
bool hasSelfLoop = false;
for (int i = 0; i < M; ++i) {
int u, v; cin >> u >> v;
g[u].push_back(v);
if (u != v) g[v].push_back(u);
else hasSelfLoop = true;
}
// BFS from 1 to compute layers; detect odd cycles by finding an edge
// joining two vertices in the same layer.
vector<int> dist(N + 1, -1);
dist[1] = 0;
queue<int> q; q.push(1);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : g[u]) if (dist[v] == -1) {
dist[v] = dist[u] + 1;
q.push(v);
}
}
// Count "earliest odd-mix depth": smallest layer that admits an odd walk.
int firstOddLayer = INT_MAX;
for (int u = 1; u <= N; ++u)
for (int v : g[u])
if (u == v) firstOddLayer = min(firstOddLayer, dist[u]);
else if (dist[u] == dist[v]) firstOddLayer = min(firstOddLayer, dist[u]);
// Minimum edges = (N - 1) BFS-tree edges, plus 1 self-loop if any odd
// cycle is reachable from vertex 1 at all.
long long answer = (long long)(N - 1);
if (firstOddLayer != INT_MAX) answer += 1;
cout << answer << "\n";
}
}
// [verify the "single self-loop suffices" claim for all reachable a;
// the editorial discusses cases where intra-layer edges are mandatory]
// https://usaco.org/index.php?page=viewproblem2&cpid=1117
Pitfalls
- f depends on walk parity, not simple-path length. Once any odd cycle is reachable on the walk to a, both parities of walk-length become available for all longer lengths.
- Self-loops count as edges. Adding a self-loop is the cheapest way to inject a length-1 "odd cycle" at any vertex.
- Single test case has small N but many tests share buffer. Sum-of-N matters; allocate per-test, not statically.
- This reference is a scaffold; a fully-correct Platinum solution must handle vertices that are odd-reachable only via deep layers — see editorial.
Problem 3 — Counting Graphs
Statement
With the same fG(a, b) as in Problem 2, count the number of distinct simple graphs G' on the same N vertices (self-loops permitted, no parallel edges) such that fG' ≡ fG, modulo 109+7.
Constraints
- 2 ≤ N ≤ 100 per test.
- N−1 ≤ M ≤ N(N+1)/2.
- 1 ≤ T ≤ 2.5 · 104 test cases; sum of N² over tests ≤ 105.
- Time limit: 5s.
Key idea
From Problem 2 we know G' is parameterised by: a BFS-tree (each non-root vertex picks any parent one layer higher) plus optional intra-layer / self-loop edges that don't change parity-reachability. Group vertices by (layer, parity-of-odd-reachability). For each non-root vertex of layer k, count the number of choices of "edges into layer k−1 or k" that preserve f. With layer sizes s0, s1, … and odd-reach pattern, the answer factorises as a product over layers of (combinatorial factors involving 2sk·sk-1 minus invalid subsets). Compute modulo 109+7.
Complexity target
O(N² + M) per test, dominated by layer-pair enumeration.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1'000'000'007LL;
ll powmod(ll a, ll e, ll m = MOD) {
ll r = 1 % m; a %= m;
while (e) { if (e & 1) r = r * a % m; a = a * a % m; e >>= 1; }
return r;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin >> T;
while (T--) {
int N, M; cin >> N >> M;
vector<vector<int>> g(N + 1);
for (int i = 0; i < M; ++i) {
int u, v; cin >> u >> v;
g[u].push_back(v);
if (u != v) g[v].push_back(u);
}
// BFS layers from vertex 1.
vector<int> dist(N + 1, -1);
dist[1] = 0;
queue<int> q; q.push(1);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int v : g[u]) if (dist[v] == -1) { dist[v] = dist[u] + 1; q.push(v); }
}
// Layer sizes.
int maxL = 0;
for (int i = 1; i <= N; ++i) maxL = max(maxL, dist[i]);
vector<int> sz(maxL + 1, 0);
for (int i = 1; i <= N; ++i) ++sz[dist[i]];
// Sketch product: each layer-k vertex chooses a nonempty subset of layer
// (k-1) parents — (2^{sz[k-1]} - 1)^{sz[k]} — times the free choice of
// intra-layer / odd-loop edges consistent with f. This is the structural
// skeleton; the precise factor needs the per-layer odd-parity flag.
ll ans = 1;
for (int k = 1; k <= maxL; ++k) {
ll base = (powmod(2, sz[k - 1]) - 1 + MOD) % MOD;
ans = ans * powmod(base, sz[k]) % MOD;
}
cout << ans << "\n";
}
}
// NOTE: this is a structural template. The full solution multiplies by extra
// factors that account for intra-layer edges and odd-cycle-injection points.
// See editorial. [verify] cpid=1118
Pitfalls
- Each non-root vertex must connect to at least one layer-(k−1) vertex — that's the (2sk-1 − 1) factor.
- Intra-layer edges only matter to parity. They can be freely added or removed unless they would create an odd-cycle reachability that contradicts f.
- Self-loops are counted as edges and contribute to odd-parity injection at their vertex.
- The skeleton above is illustrative. The full Platinum-grade counting requires careful case analysis of parity flags per layer — read the editorial before relying on it.
What Platinum February 2021 was actually testing
- P1 — offline sweep with a Fenwick tree on "mergeable pairs". Classic colour-painting interval reduction.
- P2 — BFS layers + walk-parity reasoning. The minimum-edge graph is a BFS tree plus at most one odd-cycle gadget.
- P3 — counting graphs by BFS-layer profile, modular arithmetic. Product over layer-pairs of inclusion–exclusion subset factors.