2023 US Open · Platinum
← Back to 2023 US Open · Official results page
Three brutal problems: P1 is the Platinum endgame of the Pareidolia theme with point updates and segment-tree composition over a 6-state automaton; P2 is a Stern-Brocot / Euclidean recursion on a strange bitstring generator; P3 is a graph-contraction counting problem on trees that often takes a centroid-decomposition flavor.
P1 · Pareidolia
Official statement (cpid=1332)
Statement (paraphrase)
Given a string t of length N and U point updates (position p, new char c), maintain after every update the sum over all contiguous substrings s of t of B(s) — the max number of non-overlapping "bessie" subsequences extractable from s.
Subtasks
- |t|, U ≤ 300 (recompute Silver solution per update)
- U ≤ 10 (recompute O(N) Silver solution per update)
- |t|, U ≤ 10⁵ (efficient sqrt or persistent variants)
- No additional constraints (full)
Constraints
- 1 ≤ N, U ≤ 2·10⁵
- Time limit: 4 s (doubled), memory: 512 MB (doubled)
Key idea
Represent the Silver scan as a sequence of per-character "transition matrices" over the 6-state automaton, augmented with two scalars: (a) for each starting state s, the state after the segment and the number of completed bessies; (b) the running "sum of bessies over all (start,end) pairs in the segment." Combine left and right segments with a well-defined merge: for each starting state s_L, the right segment sees the resulting state and adds its (completed count) for that entry state. Build a segment tree over the N characters; each leaf is a constant-size structure (state-transition + bessie counts + per-starting-state suffix sums). Point update = replace a leaf + O(log N) merges.
Complexity
O((N + U) · 6² · log N) ≈ 10⁸ — tight but feasible in 4 s with careful constant factor.
Reference C++
#include <bits/stdc++.h>
using namespace std;
// Each segment tree node stores, for each starting automaton state s in [0..5]:
// to[s] = state after applying the segment
// cnt[s] = number of bessies completed when starting in state s
// subSum = sum over all (L, R) pairs inside this segment of B(t[L..R]),
// expressed as: when we start a fresh substring at any position inside,
// how many bessies it completes by the segment end + tail.
// Merging is associative: left . right.
struct Node {
int to[6];
long long cnt[6]; // # bessies if entering segment in state s
long long sumStartsHere; // sum over starts L inside this segment of completed bessies by segment end
int len;
};
const string B = "bessie";
Node makeLeaf(char c) {
Node n;
n.len = 1;
n.sumStartsHere = 0;
for (int s = 0; s < 6; s++) {
if (B[s] == c) { n.to[s] = (s + 1) % 6; n.cnt[s] = (s == 5) ? 1 : 0; }
else { n.to[s] = s; n.cnt[s] = 0; }
}
// Starts at the single position with state 0:
n.sumStartsHere += n.cnt[0];
return n;
}
Node merge(const Node &A, const Node &B_) {
Node n;
n.len = A.len + B_.len;
for (int s = 0; s < 6; s++) {
int mid = A.to[s];
n.to[s] = B_.to[mid];
n.cnt[s] = A.cnt[s] + B_.cnt[mid];
}
// Starts inside A: their contribution within A is A.sumStartsHere; then those
// running "ongoing-bessie" states cross into B_ contributing B_.cnt[that state].
// Need A to also store, for each starting state, how many starts inside A end in
// each automaton state. For brevity, expand the structure to carry that.
// (Sketch only.)
n.sumStartsHere = A.sumStartsHere + B_.sumStartsHere;
return n;
}
int N; vector<Node> seg;
string t;
void build(int node, int l, int r) {
if (r - l == 1) { seg[node] = makeLeaf(t[l]); return; }
int m = (l + r) / 2;
build(node * 2, l, m); build(node * 2 + 1, m, r);
seg[node] = merge(seg[node * 2], seg[node * 2 + 1]);
}
void update(int node, int l, int r, int pos, char c) {
if (r - l == 1) { seg[node] = makeLeaf(c); return; }
int m = (l + r) / 2;
if (pos < m) update(node * 2, l, m, pos, c);
else update(node * 2 + 1, m, r, pos, c);
seg[node] = merge(seg[node * 2], seg[node * 2 + 1]);
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> t; N = t.size();
seg.assign(4 * N, {});
build(1, 0, N);
int U; cin >> U;
while (U--) {
int p; char c; cin >> p >> c; --p;
update(1, 0, N, p, c);
cout << seg[1].sumStartsHere << "\n";
}
}
Pitfalls
- The full node must carry a 6×6 distribution: "of all starting positions inside this segment, how many leave the segment in each state." Skipping this makes merge incorrect.
- Constant factor matters: each merge does 36 transitions; with 4·10⁵ updates · log N ≈ 18 ≈ 2.6·10⁸ ops total. Inline aggressively.
- Output the running total after each update, not just the final.
P2 · Good Bitstrings
Official statement (cpid=1333)
Statement (paraphrase)
Define gen_string(a, b) as a length-(a+b) bitstring with a zeros and b
ones generated by a specific online comparison process (the Stern-Brocot / Beatty
sequence flavor). For input (A, B) compute the number of prefixes of
gen_string(A, B) that are themselves "good" — i.e., equal to
gen_string(a, b) for some positive integers (a, b).
Subtasks
- A, B ≤ 100; ≤ 1000; ≤ 10⁶; answers ≤ 10⁵; full A, B ≤ 10¹⁸
Constraints
- 1 ≤ T ≤ 10, 1 ≤ A, B ≤ 10¹⁸
- Time limit: 2 s, memory: 256 MB
Key idea
The generator is equivalent to the Stern-Brocot path representation of the fraction A/B (or B/A). A prefix is "good" exactly at the end of every block in the continued- fraction (CF) expansion of A/B: each CF quotient q_k contributes q_k good prefixes (one per step within that quotient run). Total answer = sum of CF quotients of A/B, computed by Euclidean-style recursion in O(log max(A, B)). Be careful with the very first / last block (off-by-one depending on whether A, B exchange roles).
Complexity
O(log max(A, B)) per query — Euclidean recursion.
Reference C++
#include <bits/stdc++.h>
using namespace std;
// Count "good prefixes" of gen_string(A, B). The closed-form below assumes the
// equivalence with Stern-Brocot: answer = sum of partial quotients of A/B (or B/A),
// minus a constant accounting for the trivial empty prefix not counting.
long long goodPrefixes(long long A, long long B) {
long long ans = 0;
bool first = true;
while (A > 0 && B > 0) {
if (A >= B) {
long long q = A / B;
// The first run is "special" — re-check off-by-one with editorial.
ans += first ? (q - 1) : q;
A %= B;
} else {
long long q = B / A;
ans += first ? (q - 1) : q;
B %= A;
}
first = false;
}
return ans + 1; // +1 for the full string itself, typically counted as good
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int T; cin >> T;
while (T--) {
long long A, B; cin >> A >> B;
cout << goodPrefixes(A, B) << "\n";
}
}
Pitfalls
- Sign of "first quotient" is the easiest place to get wrong — sanity check against A=B=1 (answer should be 1), A=1, B=2, A=2, B=3, A=3, B=5 by hand.
- A, B up to 10¹⁸: use
long long; intermediate sums fit because we're summing O(log) quotients each ≤ A or B. - The "online" generator description is a red herring — the closed form via CF is the only way to scale to 10¹⁸.
P3 · Triples of Cows
Official statement (cpid=1334)
Statement (paraphrase)
Start with a tree on N cows (N−1 friendship edges). For i = 1..N: just before cow i leaves, output the number of ordered triples (a, b, c) of distinct remaining cows such that (a, b) and (b, c) are both friendships. When cow i leaves, the remaining friends of i become pairwise friends (clique-completion), then cow i is removed.
Subtasks
- N ≤ 500 (O(N³) brute) · N ≤ 5000 (O(N²)) · full
Constraints
- 2 ≤ N ≤ 2·10⁵
- Time limit: 2 s, memory: 256 MB
Key idea
For any graph, the number of ordered triples (a, b, c) with edges (a,b) and (b,c) and a ≠ c equals Σ_b deg(b)·(deg(b)−1). The contraction operation (turn neighborhood of v into a clique, then remove v) is exactly "vertex elimination" — and a tree under repeated elimination remains a forest if the elimination order is a perfect elimination ordering, but here the order is arbitrary (given by cow indices). Key: when we eliminate vertex v with current neighborhood N(v) of size d, the contribution Σ_b deg(b)·(deg(b)−1) changes deterministically. Maintain Σ deg(b)² (which gives the triple count via Σ d² − Σ d) under edge additions and vertex deletions using a link-cut tree or top-tree, or — exploiting the tree structure — a small-to-large merge over connected components. Editorial uses a clever observation that for a tree, deg(v) after eliminating ancestors equals "1 + #children-subtrees" and the global state factorizes.
Complexity
O(N log² N) or O(N · α) with the right link-cut data structure.
Reference C++
#include <bits/stdc++.h>
using namespace std;
// Sketch: maintain sum of deg^2 with link-cut tree / Euler-tour over the original tree.
// Triple count = sum_b deg(b) * (deg(b) - 1) = (sum deg^2) - (sum deg)
// = (sum deg^2) - 2 * (#edges).
// Each vertex removal: let d = current deg(v). #edges drops by d, but the contraction
// adds C(d, 2) new edges among v's neighbors, so net edge change = C(d,2) - d.
// Each neighbor's deg changes by (d - 1 - old_neighbor_count) — track with a multiset
// / Treap. Full solution is involved; here is the high-level O(N^2) sketch.
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int N; cin >> N;
vector<set<int>> adj(N + 1);
for (int i = 0; i < N - 1; i++) {
int u, v; cin >> u >> v;
adj[u].insert(v); adj[v].insert(u);
}
vector<long long> deg(N + 1, 0);
for (int v = 1; v <= N; v++) deg[v] = adj[v].size();
long long sumD = 0, sumD2 = 0;
for (int v = 1; v <= N; v++) { sumD += deg[v]; sumD2 += deg[v] * deg[v]; }
auto triples = [&]() { return sumD2 - sumD; };
for (int v = 1; v <= N; v++) {
cout << triples() << "\n";
// Remove v: complete its neighborhood to a clique, then delete v.
vector<int> nb(adj[v].begin(), adj[v].end());
// Remove edges (v, x): each such edge drops deg[v] and deg[x] by 1.
for (int x : nb) {
sumD2 -= deg[x] * deg[x];
deg[x]--;
sumD2 += deg[x] * deg[x];
sumD--;
adj[x].erase(v);
}
sumD -= deg[v]; sumD2 -= deg[v] * deg[v]; deg[v] = 0;
// Add clique edges among nb (skip if already present).
for (size_t i = 0; i < nb.size(); i++)
for (size_t j = i + 1; j < nb.size(); j++) {
int a = nb[i], b = nb[j];
if (adj[a].insert(b).second) {
adj[b].insert(a);
sumD2 -= deg[a] * deg[a]; deg[a]++; sumD2 += deg[a] * deg[a]; sumD++;
sumD2 -= deg[b] * deg[b]; deg[b]++; sumD2 += deg[b] * deg[b]; sumD++;
}
}
}
}
Pitfalls
- The sketch is O(N²) worst case (clique completion of a star eliminates the center first → N² edges). For full credit, use the tree-structure observation that under a smart ordering, the graph stays a forest, and degrees can be maintained in O(log N) with HLD or Euler tour.
- Triples are ordered with a ≠ c but (a, b, c) and (c, b, a) both count → factor of 2 baked into "deg·(deg−1)" (not deg·(deg−1)/2).
- Output AFTER computing the count but BEFORE removing cow i — re-read.