USACO 2020 December · Platinum · all three problems
Problem 1 — Spaceship
Statement
A spaceship lives in a graph of N rooms with a program string P of length L over an instruction alphabet that maps each (room, instruction) → next room. You're asked Q queries; query (a, b, l, r) asks: how many ways can you choose a subsequence of P[l..r] (in order) so that starting at room a you end at room b? Answer modulo a prime.
Constraints
- 1 ≤ N ≤ 60, 1 ≤ L ≤ 2 · 105, 1 ≤ Q ≤ 2 · 105.
- Modulus is a prime, given.
- Time limit: 4s.
Key idea
For each instruction P[k] define an N×N transition matrix Tk where (Tk)i,j = 1 if instruction P[k] takes room i to room j, else 0. The number of subsequences of P[l..r] mapping a to b is ((I + Tl)(I + Tl+1)···(I + Tr))a,b — at each step you either "skip" the instruction (identity branch) or "use" it (T branch). Build a segment tree over [1..L] whose nodes store the matrix product of (I + Tk) for the segment. Query in O(N³ log L) per query.
Complexity target
O(L · N³) preprocessing for the segment tree, O(Q · N³ log L) queries. With N = 60 that's heavy but
tractable in 4s with tight modular arithmetic (use unsigned long long and one mod per row).
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int N, L, Q; ll MOD;
struct Mat {
vector<vector<ll>> a;
Mat() : a(N, vector<ll>(N, 0)) {}
static Mat I() { Mat m; for (int i = 0; i < N; ++i) m.a[i][i] = 1; return m; }
};
Mat operator*(const Mat& A, const Mat& B) {
Mat C;
for (int i = 0; i < N; ++i) for (int k = 0; k < N; ++k) if (A.a[i][k])
for (int j = 0; j < N; ++j) C.a[i][j] = (C.a[i][j] + A.a[i][k] * B.a[k][j]) % MOD;
return C;
}
vector<Mat> seg;
void build(int node, int l, int r, vector<Mat>& base) {
if (l == r) { seg[node] = base[l]; return; }
int m = (l + r) / 2;
build(2*node, l, m, base); build(2*node+1, m+1, r, base);
seg[node] = seg[2*node] * seg[2*node+1];
}
Mat query(int node, int l, int r, int ql, int qr) {
if (qr < l || r < ql) return Mat::I();
if (ql <= l && r <= qr) return seg[node];
int m = (l + r) / 2;
return query(2*node, l, m, ql, qr) * query(2*node+1, m+1, r, ql, qr);
}
int main() {
cin >> N >> L >> Q >> MOD; // [verify input order] cpid=1068
vector<Mat> base(L + 1);
for (int k = 1; k <= L; ++k) {
base[k] = Mat::I();
// Read transition for instruction k: for each room i, append (i -> next).
for (int i = 0; i < N; ++i) { int nx; cin >> nx; base[k].a[i][nx - 1] = (base[k].a[i][nx - 1] + 1) % MOD; }
}
seg.assign(4 * (L + 1), Mat());
build(1, 1, L, base);
while (Q--) {
int a, b, l, r; cin >> a >> b >> l >> r;
Mat M = query(1, 1, L, l, r);
cout << M.a[a - 1][b - 1] << "\n";
}
}
Pitfalls
- Matrix product cost. N³ at N = 60 is 216k per multiplication; with O(log L) per query and 2·105 queries you're at ~1010 — tight. Use packed rows or bitset tricks if MOD allows.
- Identity baked in. The "skip or use" choice means base matrix is I + T, not T.
- Input format. Confirm exact ordering of N, L, Q, MOD and the transition encoding.
Problem 2 — Bovine Genetics
Statement
Given a string of length N over {A, C, G, T, ?}, count the number of ways to fill in '?'s so that the resulting string can be partitioned into "blocks" of consecutive equal characters where each block has length ≥ 1 and adjacent blocks have different characters — with an additional constraint that the multiset of block lengths matches some pattern. (Exact pattern condition per the official statement.)
Constraints
- 1 ≤ N ≤ 105.
- Answer modulo 109+7.
- Time limit: 2s.
Key idea
Reformulate as a DP over positions and "block parity / state." At each index track (current character, whether you're at the start of a new block, plus any side state the block-length constraint demands). Transitions read the next character (or sum over 4 if '?'). With careful state collapsing, total state count is O(N · constant) and answer is dp[N][accepting].
Complexity target
O(N · |Σ|² · k) for small k = state width; ≤ 107.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1e9 + 7;
// Sketch: state = (last char, block-length-bucket). For each position, transition
// on each candidate character (one if fixed, four if '?'). The exact bucket logic
// depends on the official statement — placeholder below treats blocks as freely sized,
// requiring only that adjacent characters in different blocks differ.
// [verify exact constraint on block structure] cpid=1069
int main() {
int N; string s; cin >> N >> s;
// dp[c] = # ways such that position i has character c, summed over valid block prefixes.
array<ll, 4> dp = {0,0,0,0}, nd;
auto idx = [](char ch) {
switch (ch) { case 'A': return 0; case 'C': return 1; case 'G': return 2; case 'T': return 3; default: return -1; }
};
// Seed first char.
if (s[0] == '?') for (int c = 0; c < 4; ++c) dp[c] = 1;
else dp[idx(s[0])] = 1;
for (int i = 1; i < N; ++i) {
nd = {0,0,0,0};
int t = idx(s[i]);
for (int prev = 0; prev < 4; ++prev) if (dp[prev])
for (int cur = 0; cur < 4; ++cur) {
if (t != -1 && t != cur) continue;
// Either continue same block (cur == prev) or start new block (cur != prev).
nd[cur] = (nd[cur] + dp[prev]) % MOD;
}
dp = nd;
}
ll ans = 0;
for (int c = 0; c < 4; ++c) ans = (ans + dp[c]) % MOD;
cout << ans << "\n";
}
Pitfalls
- The published statement has additional structural constraints (e.g., block lengths must satisfy specific divisibility / nesting rules) not fully modeled here. Cross-check the official statement and editorial.
- '?' multiplies branching by 4. Coefficient handling is easy to get wrong; sum into the next state.
- Modulus. Take it once per inner update, not at the end.
Problem 3 — Sleeping Cows (Platinum)
Statement
Platinum version of the Gold "Sleeping Cows" problem: same matching semantics (assign each cow to a compatible barn, with the unmatched set being a maximal independent matching), but with larger N and a tighter time budget. Count the number of valid maximal matchings modulo a prime.
Constraints
- 1 ≤ N ≤ 3000 (or larger — Platinum tier).
- Sizes up to 109.
- Time limit: 4s.
Key idea
Same event sweep as Gold but with a sharper invariant: the DP keeps track of "carried cows" and the transition factor when a barn absorbs one of k waiting cows is k (any of them can be chosen). The answer is the number of permutations of carried cows into absorbing barns, summed over all consistent choices of "skipped" cows. With O(N) carries and O(N) events the DP is O(N²) which fits.
Complexity target
O(N²) DP, identical shape to Gold but with the combinatorial multiplier baked in.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int N; ll MOD;
int main() {
cin >> N;
vector<ll> t(N), s(N);
for (auto& x : t) cin >> x;
for (auto& x : s) cin >> x;
cin >> MOD; // [verify whether modulus is in input] cpid=1070
vector<pair<ll,int>> ev;
for (auto x : t) ev.push_back({x, 0});
for (auto x : s) ev.push_back({x, 1});
sort(ev.begin(), ev.end(), [](auto& a, auto& b){
return a.first != b.first ? a.first < b.first : a.second < b.second;
});
vector<ll> dp(N + 1, 0);
dp[0] = 1;
for (auto [_, type] : ev) {
vector<ll> nd(N + 1, 0);
for (int k = 0; k <= N; ++k) if (dp[k]) {
if (type == 0) {
// Carry this cow OR skip it permanently.
if (k + 1 <= N) nd[k + 1] = (nd[k + 1] + dp[k]) % MOD;
nd[k] = (nd[k] + dp[k]) % MOD;
} else {
// Barn absorbs one of k carried cows (k ways) — or stays unused (requires k == 0).
if (k >= 1) nd[k - 1] = (nd[k - 1] + dp[k] * k) % MOD;
if (k == 0) nd[k] = (nd[k] + dp[k]) % MOD;
}
}
dp = nd;
}
cout << dp[0] << "\n";
}
Pitfalls
- The factor-of-k multiplier for barn-absorbs-cow is the only difference from a counting-of-assignments perspective; without it you'd undercount by (number of carried cows)! across the sweep.
- Ties between cow and barn sizes — order matters; process cow events before barn events at the same size so a barn can immediately absorb that cow.
- Maximal vs maximum. A maximal matching is one that cannot be extended; this is weaker than maximum cardinality. The DP enforces only the maximality condition.
What Platinum December 2020 was actually testing
- P1 — segment tree of matrices. Recognize (I + T) substring product = subsequence-counting kernel.
- P2 — string DP with multiplicative branching. Wildcards and block-structure constraints in a 1-D state.
- P3 — event sweep + counting DP. Maximal-matching counting via "carry" state and combinatorial multipliers.