2023 February Platinum · All three problems
Problem 1 · Hungry Cow (Platinum)
Statement (paraphrased)
Same eating model as Bronze: Bessie eats one haybale per day if any is in stock. But now you process U updates of the form "on day d set delivery to b haybales" (overwriting any previous delivery on day d, or removing if b=0). After each update output the sum of days on which Bessie eats, mod 109+7.
Constraints
- 1 ≤ U ≤ 105.
- 1 ≤ d ≤ 1014; 0 ≤ b ≤ 109.
- Subtasks: 3: U ≤ 5000; 4–10: only-increases; 11–22: full.
- Time / memory: 6 s (3× default), 512 MB (2× default).
Key idea
Build an implicit segment tree over a compressed timeline of delivery days plus the open intervals between them. Each node stores: range length, total deliveries inside, count of "eaten days" already pinned, and the surplus (excess deliveries carried over the right boundary). Combining children l, r: surplus(l) flows into r's empty days first; eaten days = eaten(l) + eaten(r) + min(unfilled(r), surplus(l)), and the days summed are tracked via an extra field summing arithmetic progressions. Updates are O(log) on the implicit segtree. Sums of consecutive integers from a..b mod p use (a+b)(b−a+1)/2 mod p.
Complexity target
O(U log U) time, O(U log U) memory for the implicit segtree.
C++ reference solution
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1'000'000'007;
ll inv2;
// Implicit segtree on compressed delivery-day coordinates.
// Each node holds: len, eaten_days_count, eaten_days_sum (mod p),
// surplus (haybales carried past the right end).
struct Node { ll len=0, cnt=0, sum=0, sur=0; };
ll arith(ll a, ll b){ // a + (a+1) + ... + b mod p
ll k = (b - a + 1) % MOD;
ll s = ((a + b) % MOD * (k % MOD)) % MOD;
return s * inv2 % MOD;
}
Node merge(const Node& L, const Node& R, ll Lstart, ll Rstart){
Node X;
X.len = L.len + R.len;
// Surplus from L spills into the unfilled portion of R.
ll spill = min(L.sur, R.len - R.cnt);
// Days newly eaten lie in R's unfilled left-most positions (greedy fill).
// Tracking the exact day numbers requires storing R's "first-empty" position;
// editorial uses a recursive lazy-spill traversal.
X.cnt = L.cnt + R.cnt + spill;
X.sum = (L.sum + R.sum) % MOD; // R's newly-eaten day-sum patched during recursion
X.sur = L.sur - spill + R.sur;
return X;
}
// Full implementation omitted — Platinum editorial gives ~80 lines.
int main(){
int U; cin >> U;
inv2 = 500000004; // modular inverse of 2 mod 1e9+7
// ... build implicit segtree, process updates, output total day-sum.
// [verify] full impl in https://usaco.org/current/data/sol_prob3_platinum_feb23.html
cout << 0 << '\n';
}
Pitfalls
- "Sum of days eaten" not "count of days eaten" — so the segtree must track an arithmetic progression sum, not just a count.
- Surplus must propagate strictly left-to-right (lower days get filled first). Don't average.
- Modular inverse of 2 is needed for the arithmetic-series formula.
Problem 2 · Problem Setting
Statement (paraphrased)
FJ has N candidate problems and M test-solvers; each solver labels every problem "easy" (0) or "hard" (1). Each problem thus carries an M-bit signature. Choose an ordered subset (problemset) such that for no solver j does some later problem appear easy while some earlier one appears hard. Count ordered problemsets mod 109+7.
Constraints
- 1 ≤ N ≤ 105.
- 1 ≤ M ≤ 20.
- Subtasks: 3–4: M=1; 5–14: M ≤ 16; 15–22: full.
- Time / memory: 2 s, 512 MB (2× default).
Key idea
Each signature S ∈ {0,1}M defines an equivalence class. Within a class, problems are interchangeable, so a class of size c contributes Σk=0..c C(c,k) · k! permutations once you've decided to include k of them — but the ordering between classes must respect the partial order "S ⪯ T iff S OR T = T and S AND T = S" (S can come before T iff no solver j has Tj=0 while Sj=1). DP over subsets: g[mask] = number of valid orderings using exactly the classes whose signature is a subset of mask. SOS-style transition: g[mask] = Σ over S ⊆ mask, S a maximal element, g[mask − S] · (contribution of class S given how many we pick). 2M·M ≤ 2·107.
Complexity target
O(2M·M + N).
C++ reference solution
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1'000'000'007;
int main(){
int N, M; cin >> N >> M;
vector<int> cnt(1<<M, 0);
for (int i = 0; i < N; ++i){
string row; cin >> row;
int s = 0;
for (int j = 0; j < M; ++j) if (row[j] == 'H') s |= (1<<j);
cnt[s]++;
}
// f[s] = number of nonempty ordered sequences using only classes that are subsets of s,
// where every successive signature is ⊆ s and respects ⪯.
// Transition: f[s] = sum over T ⊆ s, T != 0, of (ways to pick a nonempty ordered sequence
// from class T) * f[s ^ (T's contribution)].
// The editorial uses "fix the maximal element of the ordering."
vector<ll> f(1<<M, 0); f[0] = 1;
for (int s = 1; s < (1<<M); ++s){
// For each bit j set in s, transition from s without j; details in editorial.
// Sketch: iterate over subsets T of s containing only top-level elements.
// (Real implementation is ~30 lines; this is a placeholder.) // [verify]
}
ll ans = (f[(1<<M)-1] - 1 + MOD) % MOD; // subtract empty problemset if needed
cout << ans << '\n';
}
Pitfalls
- 220·20 ≈ 2·107: fine, but skip empty signature classes early.
- "Ordered subset" counts orderings: include factorials within each class size.
- Modular factorials and inverse factorials should be precomputed up to N.
Problem 3 · Watching Cowflix
Statement (paraphrased)
On a tree of N nodes, a subset S of nodes is marked "viewing". Pay min cost so that the chosen connected-component cover of all viewing nodes consists of disjoint subtrees; each chosen component of size d costs d + k. For every k = 1..N, output the minimum total cost.
Constraints
- 2 ≤ N ≤ 2·105.
- Subtasks: 3–5: N ≤ 5000; 6–8: linear (path) tree; 9–19: N ≤ 105; 20–24: full.
- Time / memory: 3 s (1.5× default), 256 MB.
Key idea
For fixed k, do a tree DP: dp[v][0/1] = min cost to cover all viewing nodes inside v's subtree, where 0 = v is not in any chosen component, 1 = v is in some component (and we will close or extend it upward). Answer for fixed k = min(dp[root][0], dp[root][1]+k). Naively O(N²). To handle all k, observe that the answer as a function of k is piecewise-linear concave: as k grows, the optimal number of components C* decreases. Use Aliens trick (binary search on the slope penalty) or smallish- to-large merging with sqrt-decomposition on k: solve for O(√N) representative k-values in O(N) each, plus a sub-N²·√ method splits over k ≤ √N (many components) and k > √N (few components). Editorial uses √N decomposition:
- For small k (k ≤ √N): the answer has many components, brute-force tree DP each k in O(N).
- For large k: components ≤ √N. Enumerate the chosen component set via a contracted-tree DP.
Total O(N · √N).
Complexity target
O(N · √N) or O(N log² N) with Aliens.
C++ reference solution
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = 1e18;
int N;
vector<vector<int>> adj;
vector<int> want;
vector<ll> dp0, dp1; // dp[v][0]: v not in chosen comp; dp[v][1]: v in chosen comp
ll Kfixed;
void dfs(int u, int par){
// dp0[u]: u not in component — must satisfy want[u]==0
// dp1[u]: u in component — counts u's size, must add Kfixed once when closed.
ll d0 = (want[u] ? INF : 0);
ll d1 = 1; // u itself
for (int v : adj[u]) if (v != par){
dfs(v, u);
// child's best closed = min(dp0[v], dp1[v] + Kfixed)
ll closed = min(dp0[v], dp1[v] + Kfixed);
ll d0n = (d0 == INF ? INF : d0 + closed);
// u in component: child either also-in (merge: dp1[v], same component) or closed
ll d1n = d1 + min(dp1[v], closed);
d0 = d0n; d1 = d1n;
}
dp0[u] = d0; dp1[u] = d1;
}
int main(){
cin >> N;
string s; cin >> s;
want.assign(N+1, 0);
for (int i = 1; i <= N; ++i) want[i] = (s[i-1] == '1');
adj.assign(N+1, {});
for (int i = 0; i < N-1; ++i){ int a, b; cin >> a >> b; adj[a].push_back(b); adj[b].push_back(a); }
dp0.assign(N+1, 0); dp1.assign(N+1, 0);
for (int k = 1; k <= N; ++k){
Kfixed = k;
dfs(1, 0);
cout << min(dp0[1], dp1[1] + Kfixed) << '\n';
}
// O(N^2) — fine for N=5000 subtask. Full N=2e5 needs √-decomposition or Aliens trick. [verify]
}
Pitfalls
- The naive O(N²) above only passes through subtask 3–5 (N ≤ 5000). Full credit requires Aliens trick or √-split.
- "Connected component" in a tree = subtree induced by chosen nodes; ensure the DP merges children correctly when staying in the same component.
- Per-k answer is piecewise-linear concave in k — exploit it to amortise.