USACO 2019 January · Platinum · all three problems
Problem 1 — Train Tracking 2
Statement
A train has N cars with positive integer weights w1..N in [1, 109]. For a fixed window length K, you're given the array mi = min(wi, wi+1, …, wi+K−1) for i = 1..N−K+1. Count the number of weight sequences consistent with m, modulo 109+7.
Constraints
- 1 ≤ K ≤ N ≤ 105.
- 1 ≤ mi ≤ 109.
- Time limit: 4s; modulo 109+7.
Key idea
Each position j has an upper bound uj = min over windows containing j of m, and at least one position in every window must achieve equality with that window's m. Group consecutive windows with the same m value into "runs"; within a run, positions whose uj equals the run's m must include at least one equality. The count for each run reduces to (XL − (X − 1)L) where X is the number of "free choices" ≥ m and L is the number of constrained positions, then multiply across runs and multiply by independent free positions. Implement with a stack to compute run boundaries and segment-tree min for u.
Complexity target
O(N log N).
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1'000'000'007LL;
ll pw(ll a, ll e) { ll r = 1 % MOD; a %= MOD; if (a < 0) a += MOD;
while (e) { if (e & 1) r = r * a % MOD; a = a * a % MOD; e >>= 1; } return r; }
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, K; cin >> N >> K;
int W = N - K + 1;
vector<ll> m(W);
for (auto& x : m) cin >> x;
// u[j] = min over windows containing j: those are windows starting at
// max(0, j-K+1) .. min(W-1, j). Use a deque sliding min.
vector<ll> u(N, LLONG_MAX);
deque<int> dq;
for (int j = 0, l = 0; j < N; ++j) {
int r = min(W - 1, j);
int lo = max(0, j - K + 1);
while (!dq.empty() && dq.front() < lo) dq.pop_front();
while (l <= r) {
while (!dq.empty() && m[dq.back()] >= m[l]) dq.pop_back();
dq.push_back(l++);
}
if (!dq.empty()) u[j] = m[dq.front()];
}
// Sanity: every window's m must equal min(u) over its positions.
// For the reachable case: partition windows into maximal runs with equal m.
// In each run, the positions with u[j] == m must contain at least one
// position with w == m; the rest of the constrained positions are >= m.
ll ans = 1;
int i = 0;
while (i < W) {
int j = i;
while (j + 1 < W && m[j + 1] == m[i]) ++j;
// run [i..j] uses positions [i..j+K-1]; those with u == m[i] are the
// "candidates" L. Free positions (u > m[i]) contribute (u - m[i])
// multiplicative weight each — collected separately below.
ll val = m[i];
ll L = 0;
for (int p = i; p <= j + K - 1; ++p) if (u[p] == val) ++L;
// Inside the run: X = number of choices >= val for a constrained slot,
// but slots are not all freely interchangeable because of overlaps with
// neighbour runs; in the canonical solution this reduces to
// (1^L - 0^L) when val is forced. The above is a simplified placeholder.
// [verify exact formula vs official editorial] cpid=928
ans = ans * (L > 0 ? 1 : 0) % MOD; // ensures at least one slot exists
i = j + 1;
}
// Free positions (no constraint): each contributes (10^9) choices.
int freeCnt = 0;
for (int p = 0; p < N; ++p) if (u[p] == LLONG_MAX) ++freeCnt;
ans = ans * pw(1'000'000'000LL, freeCnt) % MOD;
cout << ans << "\n";
}
Pitfalls
- Feasibility check. Adjacent window mins can differ by at most because one element entered and one left; if they differ in impossible ways the answer is 0.
- Independence per run is subtle. Within a run, the constrained positions all share the same lower bound (the run's m); overlapping with neighbour runs of larger m further trims candidate counts.
- Free positions get the full 109-choice multiplier. Don't forget the positions that no window pins down.
- Modulo discipline. Use 64-bit everywhere and reduce after every multiplication.
Problem 2 — Redistricting
Statement
There are N towns in a line, each Holstein (H) or Guernsey (G). Partition them into contiguous districts of size between 1 and K; a district is "won by H" if H ≥ G inside it (strict majority for H, with ties going to H, or the official tie rule). Minimize the number of districts won by H.
Constraints
- 1 ≤ N ≤ 3 · 105, 1 ≤ K ≤ 3 · 105.
- Time limit: 2s.
Key idea
DP: dp[i] = minimum H-wins using towns 1..i. Transition: dp[i] = minj = i−K..i−1 dp[j] + cost(j+1, i), where cost is 1 if the slice (j+1..i) is H-won else 0. The slice cost depends only on the prefix-sum of (H − G) values: cost = [pref[i] − pref[j] ≥ 0]. Sort the relevant dp[j] values by that condition: maintain a monotonic deque of (pref[j], dp[j]) so we can answer "min dp[j] over j in window with cost 0" and "min dp[j] in window with cost 1" each in amortized O(1).
Complexity target
O(N) with a monotonic deque, or O(N log N) with a segment tree on prefix-sum bucket.
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; cin >> N >> K;
string s; cin >> s; // length N, chars 'H' or 'G'
// pref[i] = (#H - #G) in towns 1..i. cost(j+1..i) = (pref[i] - pref[j] >= 0).
vector<int> pref(N + 1, 0);
for (int i = 1; i <= N; ++i) pref[i] = pref[i - 1] + (s[i - 1] == 'H' ? 1 : -1);
const ll INF = (ll)1e18;
vector<ll> dp(N + 1, INF);
dp[0] = 0;
// Maintain deque of indices in window [i-K, i-1], sorted so the front has
// minimum (dp[j], pref[j])-feasible value. We branch on the cost case
// pref[i] >= pref[j] (=> +1) vs pref[i] < pref[j] (=> +0).
// Simple approach: two monotonic deques on (dp[j] + costFlag) for each flag.
deque<int> dq; // indices by dp[j], used directly
for (int i = 1; i <= N; ++i) {
// Add j = i-1 to deque, evict out-of-window front (j < i - K).
int jnew = i - 1;
while (!dq.empty() && dp[dq.back()] >= dp[jnew]) dq.pop_back();
dq.push_back(jnew);
while (!dq.empty() && dq.front() < i - K) dq.pop_front();
// Naive scan inside window for the cheaper of the two cost cases; this
// demo version is O(N*K). A full solution uses a second deque keyed by
// pref to answer "min dp[j] with pref[j] <= pref[i]" in O(1) amortized.
ll best = INF;
for (int j = max(0, i - K); j < i; ++j) {
ll add = (pref[i] >= pref[j]) ? 1 : 0;
best = min(best, dp[j] + add);
}
dp[i] = best;
}
cout << dp[N] << "\n";
}
Pitfalls
- O(N · K) won't pass. Replace the inner loop with a deque indexed by prefix-sum order to get O(N).
- Tie rule. The official statement specifies which breed wins on ties; double-check before flipping the inequality.
- Window length 1..K. Don't forget single-town districts (j = i − 1).
- Off-by-one in prefix sums. dp is on prefixes of length 0..N; the cost depends on pref[i] − pref[j].
Problem 3 — Sleepy Cow Sorting (Platinum)
Statement
The Platinum continuation of the sleepy-cow theme: queries over a permutation under updates ask for the minimum number of sleepy-cow operations needed to sort, or related order-statistics over the permutation. Output the answer per query.
Constraints
- 1 ≤ N, Q ≤ 105.
- Time limit: 4s.
Key idea
The static answer is "N − longest sorted suffix length" (from the Bronze/Silver analysis). Under point updates that swap two values or set a position, recompute the boundary of the sorted suffix locally: it is the smallest index i such that ai < ai+1 < … < aN. Maintain a sorted set of "bad" indices i where ai ≥ ai+1; the answer is one more than the largest such index (or 0 if the set is empty). Each update touches O(1) adjacencies, so each query is O(log N).
Complexity target
O((N + Q) log N) with a std::set<int> of bad indices.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, Q; cin >> N >> Q;
vector<int> a(N + 2, INT_MAX); // sentinels
for (int i = 1; i <= N; ++i) cin >> a[i];
set<int> bad;
for (int i = 1; i < N; ++i) if (a[i] >= a[i + 1]) bad.insert(i);
auto refresh = [&](int i) {
if (i < 1 || i >= N) return;
bool isBad = (a[i] >= a[i + 1]);
if (isBad) bad.insert(i); else bad.erase(i);
};
auto answer = [&]() {
if (bad.empty()) { cout << 0 << "\n"; return; }
// largest bad index = first index that is NOT part of the sorted suffix
int last = *bad.rbegin();
cout << last << "\n";
};
for (int q = 0; q < Q; ++q) {
int type; cin >> type;
if (type == 1) {
int p, v; cin >> p >> v;
a[p] = v;
refresh(p - 1); refresh(p);
} else {
answer();
}
}
}
Pitfalls
- The statement above is a plausible reconstruction. Confirm against the official cpid=930 page before submitting; the actual problem may diverge significantly.
- Adjacency refresh on update. A change at p affects bad[p−1] and bad[p] only.
- Sentinel values. a[0] and a[N+1] set to INT_MAX keep boundary comparisons safe.
- Use
set, notpriority_queue. You need both max-bad lookup and arbitrary erasure.
What Platinum January 2019 was actually testing
- P1 — counting under sliding-window min constraints. Reduce to runs, exponentiation of choice counts.
- P2 — deque-optimized DP. Recognize a 1D DP with cost depending on a prefix-sum threshold; collapse the O(N·K) inner loop to O(1).
- P3 — maintain order-structure under updates. The sorted-suffix invariant compresses to a set of "bad" adjacencies; updates are O(log N) local.