2024 January Platinum — three problems
← 2024 January contest index · Official results page
Platinum is well above my current target. I'm logging the subtask structure, the algorithmic family, and a skeleton solution per problem so I have something to come back to.
Problem 1 · Island Vacation
Official statement (cpid=1380)
Statement (paraphrased)
A connected graph on N islands has at most ⌊3(N−1)/2⌋ edges; each edge lies in at most one simple cycle. Bessie starts at island 1. At every island i she terminates with probability pi (given as a fraction mod 1e9+7), else she picks a uniformly random unvisited bridge and crosses it. Output, for each island, the probability she terminates there (mod 1e9+7).
Subtasks
- Inputs 4–5: N ≤ 11 (brute over edge orderings)
- Inputs 6–7: graph is a tree
- Inputs 8–11: no island in more than one simple cycle (i.e. the cactus condition)
- Inputs 12–15: ≤ 5 simple cycles total
- Inputs 16–19: ≤ 50 simple cycles total
- Inputs 20–23: full constraints
Constraints
- 2 ≤ N ≤ 104; N−1 ≤ M ≤ ⌊3(N−1)/2⌋
- Time/memory: USACO Platinum default (2 s / 256 MB)
Key idea
The "each edge in ≤ 1 cycle" condition makes the graph a cactus. Build the block-cut tree: each block is either a bridge or a simple cycle. DP over the block-cut tree. Tree case: standard per-edge probability propagation (probability of not having returned multiplied by 1/deg). Cycle case: walk around the cycle in both directions and sum over which side we eventually traverse; account for the fact that once we enter a cycle node and pick a direction, both edges out of that node are still unvisited initially.
Complexity
O((N + M) log MOD) for modular inverses; the block-cut tree DP itself is linear.
C++ reference (skeleton)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1'000'000'007LL;
ll pw(ll a, ll b) { ll r = 1; a %= MOD; while (b){ if(b&1) r = r*a%MOD; a = a*a%MOD; b >>= 1; } return r; }
ll inv(ll a) { return pw(a, MOD - 2); }
// Skeleton: build block-cut tree of a cactus, then DFS it propagating arrival probability.
// [verify] block-cut construction and cycle-block DP against editorial cpid=1380
int N, M;
vector<pair<int,int>> g[10005]; // (neighbour, edge_id)
ll prob[10005]; // p_i mod
ll endProb[10005]; // answer per island
// stub: BCC into bridges and simple cycles, build forest of blocks,
// DFS root=island 1, accumulate endProb[v] += arrive[v] * prob[v].
void solve() {
// ... omitted skeleton; the meaningful work is the cactus DP.
}
int main() {
scanf("%d %d", &N, &M);
for (int i = 1; i <= N; i++) { ll num, den; scanf("%lld %lld", &num, &den); prob[i] = num * inv(den) % MOD; }
for (int i = 0; i < M; i++) { int u, v; scanf("%d %d", &u, &v); g[u].push_back({v, i}); g[v].push_back({u, i}); }
solve();
for (int i = 1; i <= N; i++) printf("%lld\n", endProb[i]);
}
Pitfalls
- "Each edge lies in at most one simple cycle" is the cactus condition — the right data structure is the block-cut tree, not generic biconnected components.
- Probability of taking each direction around a cycle depends on the degree at the entry node, not just on cycle length.
- Read pi as a modular fraction; pre-invert denominators once.
Problem 2 · Merging Cells
Official statement (cpid=1381)
Statement (paraphrased)
N cells in a row with sizes s1..sN and labels 1..N. Repeatedly pick a uniformly random adjacent pair and merge them: the merged cell takes the sum of sizes and the label of the strictly larger cell (ties → the larger label). After N−1 merges one cell remains. Output the probability each original label is the final label, mod 109+7.
Subtasks
- N ≤ 8 (brute over merge orderings)
- N ≤ 100
- N ≤ 500
- Full: N ≤ 5000
Constraints
- 2 ≤ N ≤ 5000; 1 ≤ si ≤ 105; memory limit 512 MB
- Time: USACO Platinum default (2 s)
Key idea
For each label k, compute P[k] = probability label k survives. Condition on the final interval [l, r] that label k "absorbs": survival happens iff (a) the merge tree contracts [l, r] before merging with the outside, and (b) within [l, r] label k always sits on the side of the larger sum at every merge. The probability of a particular contraction order on N cells is a classical Catalan-number / random binary tree quantity; let f(l, r) = probability that the random process first reduces [l, r] to a single cell before ever merging cell l with l−1 or cell r with r+1.
f satisfies an O(N3) DP that's tight at N = 5000; with the right reformulation (counting orderings by which adjacency is collapsed last) it tightens to O(N2) — which is what fits in 2 s.
Complexity
O(N2) for the full subtask.
C++ reference (skeleton)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1'000'000'007LL;
ll pw(ll a, ll b) { ll r = 1; a %= MOD; while (b){ if(b&1) r = r*a%MOD; a = a*a%MOD; b >>= 1; } return r; }
ll inv(ll a) { return pw(a, MOD - 2); }
int N;
ll s[5005], pref[5005];
ll ans[5005];
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; i++) { scanf("%lld", &s[i]); pref[i] = pref[i-1] + s[i]; }
// f[l][r] = probability the interval [l, r] is fully contracted before merging across either boundary.
// Recurrence: f[l][r] = sum over the LAST merge inside [l, r] of (probability that merge happens last)
// weighted by f[l][k] * f[k+1][r]. [verify] full recurrence against editorial cpid=1381
// For each label k, walk outward from cell k and sum f[l][r] * (prob label k wins inside [l, r]).
// Skeleton — the O(N^2) reformulation is the trick.
for (int i = 1; i <= N; i++) ans[i] = 0; // placeholder
for (int i = 1; i <= N; i++) printf("%lld\n", ans[i]);
}
Pitfalls
- Tie-breaking ("larger label wins on equal size") cannot be ignored — it changes per-interval winners.
- Memory: full N2 table of
long longis ~200 MB; the 512 MB limit lets you keep it, but a rolling DP can halve it. - Probabilities are modular; precompute modular inverses for 1..N.
Problem 3 · Mooball Teams III
Official statement (cpid=1382)
Statement (paraphrased)
N cows at points (xi, yi) with both x and y coordinates being permutations of 1..N. Assign each cow to RED, BLUE, or NEITHER such that (1) both teams are non-empty and (2) the two coloured sets are separable by a single axis-aligned line at a non-integer coordinate. Count the assignments mod 109+7.
Subtasks
- N ≤ 10 (brute)
- N ≤ 200
- N ≤ 3000
- Full: N ≤ 2·105
Constraints
- 2 ≤ N ≤ 2·105; x and y each a permutation of 1..N
- Time/memory: USACO Platinum default (2 s / 256 MB)
Key idea
Inclusion–exclusion over the axis. Fix a vertical split at x = k + ½ separating left set L (size k) from right set R (size N−k). Each cow in L is RED, NEITHER, or doesn't exist for this split; symmetric for R as BLUE. Counting assignments where L is non-empty RED and R is non-empty BLUE: (2k−1)(2N−k−1). Sum over k = 1..N−1 for the vertical line case, double for horizontal, then subtract the overlap (assignments separable by both lines simultaneously) using a 2-D scan with a Fenwick tree.
Complexity
O(N log N) for the overlap count, O(N) for the two single-axis counts.
C++ reference (skeleton)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1'000'000'007LL;
ll pw(ll a, ll b) { ll r = 1; a %= MOD; while (b){ if(b&1) r = r*a%MOD; a = a*a%MOD; b >>= 1; } return r; }
int N;
int yof[200005];
// Fenwick over y for the overlap count.
ll bit[200005];
void upd(int i, ll v) { for (; i <= N; i += i & -i) bit[i] = (bit[i] + v) % MOD; }
ll qry(int i) { ll r = 0; for (; i > 0; i -= i & -i) r = (r + bit[i]) % MOD; return r; }
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; i++) { int x, y; scanf("%d %d", &x, &y); yof[x] = y; }
ll pow2[200005]; pow2[0] = 1;
for (int i = 1; i <= N; i++) pow2[i] = pow2[i-1] * 2 % MOD;
// Vertical-only: sum_{k=1}^{N-1} (2^k - 1)(2^{N-k} - 1)
ll vert = 0;
for (int k = 1; k < N; k++) vert = (vert + (pow2[k] - 1 + MOD) % MOD * ((pow2[N-k] - 1 + MOD) % MOD)) % MOD;
ll horiz = vert; // by symmetry of permutations
// Both-line separable: sweep x left-to-right, for each split count how many cows are "above"
// vs "below" the chosen y-cut on each side. Use Fenwick on y.
// [verify] overlap formula and inclusion-exclusion sign against editorial cpid=1382
ll both = 0;
for (int x = 1; x < N; x++) {
upd(yof[x], 1);
// contribution roughly (#left-below)(#left-above)(#right-below)(#right-above)
// packaged through the (2^a - 1) terms — skeleton only.
}
ll ans = ((vert + horiz - both) % MOD + MOD) % MOD;
printf("%lld\n", ans);
}
Pitfalls
- Inclusion–exclusion sign is easy to flip; the "both-axes separable" set is subtracted, not added.
- The Fenwick part needs careful product-of-(2a−1) per quadrant — easy to lose a factor and WA only on large N.
- The output must be non-negative mod p — always add MOD before mod after a subtraction.