2023 US Open · Silver
← Back to 2023 US Open · Official results page
Silver here is a clean trio: a sort-and-prefix-sum query problem, a bitmask superset-DP problem on strings of length up to 18, and the Silver flavor of the recurring "Pareidolia" theme (counting bessie subsequences in every substring).
S1 · Milk Sum
Official statement (cpid=1326)
Statement (paraphrase)
Given N cows with milk values a_i, Farmer John unhooks them one at a time; the k-th cow unhooked contributes k · a_{σ(k)} to the total. He picks the order σ to maximize the total (so: sort ascending and multiply by 1,2,…,N). For each query (i, x), answer "if we temporarily set a_i := x, what's the maximum total?"
Constraints
- 1 ≤ N, Q ≤ 1.5·10⁵, 0 ≤ a_i ≤ 10⁸
- Subtasks: N, Q ≤ 1000 · full
- Time limit: 2 s, memory: 256 MB
Key idea
Sort a[] ascending; base answer = Σ (k+1) · a_sorted[k] (1-indexed multiplier). For a query that changes a_i to x, conceptually remove old value a_i (which sits at rank r_old) and insert x at rank r_new (lower_bound in the remaining sorted array). Use prefix sums of the sorted array: when x > a_i, all values strictly between a_i and x shift down one rank (their multiplier decreases by 1); when x < a_i, those between shift up one. The delta is a difference of prefix sums plus the placement of x.
Complexity
O((N + Q) log N) — sort once, then O(log N) per query via binary search + prefix sums.
Reference C++
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N; cin >> N;
vector<long long> a(N);
for (auto &x : a) cin >> x;
vector<long long> original = a;
vector<long long> s = a;
sort(s.begin(), s.end());
// prefix sum of sorted
vector<long long> pre(N + 1, 0);
for (int i = 0; i < N; i++) pre[i + 1] = pre[i] + s[i];
long long base = 0;
for (int i = 0; i < N; i++) base += (long long)(i + 1) * s[i];
int Q; cin >> Q;
while (Q--) {
int i; long long x;
cin >> i >> x;
--i;
long long old = original[i];
// Position of "old" in sorted: first occurrence found via lower_bound.
int rOld = (int)(lower_bound(s.begin(), s.end(), old) - s.begin());
// After removing old, where does x go? Compute in the "removed" array.
// Position of x in sorted assuming old still present:
int rXraw = (int)(lower_bound(s.begin(), s.end(), x) - s.begin());
// If x <= old, removing old (at rOld) doesn't shift x's position when x < old
// (rOld > rX). If x > old, x's true rank in the removed array is rXraw - 1.
int rNew = (x > old) ? rXraw - 1 : rXraw;
long long ans;
if (rNew == rOld) {
// x replaces old at the same rank: delta = (rOld+1) * (x - old)
ans = base + (long long)(rOld + 1) * (x - old);
} else if (rNew > rOld) {
// Values originally at ranks rOld+1..rNew shift down by 1 multiplier (lose 1*v each).
long long shifted = pre[rNew + 1] - pre[rOld + 1];
ans = base - (long long)(rOld + 1) * old - shifted + (long long)(rNew + 1) * x;
} else {
// rNew < rOld: values at rNew..rOld-1 shift up by 1.
long long shifted = pre[rOld] - pre[rNew];
ans = base - (long long)(rOld + 1) * old + shifted + (long long)(rNew + 1) * x;
}
cout << ans << "\n";
}
}
Pitfalls
- Duplicate values:
lower_boundfinds the first occurrence; "removing" any one of them is equivalent, but be careful thatrNewis correctly placed relative to other copies. - Answer can exceed 32-bit (N·max_a·N ≈ 1.5·10⁵ · 10⁸ · 1.5·10⁵ ≈ 2·10¹⁸). Use
long longeverywhere. - Queries are independent — restore the original value (no mutation between queries).
S2 · Field Day
Official statement (cpid=1327)
Statement (paraphrase)
N teams, each a length-C string over {G, H} (C ≤ 18). For each team T_i, output the maximum Hamming distance between T_i and any other team T_j.
Constraints
- 2 ≤ N ≤ 10⁵, 1 ≤ C ≤ 18
- Subtasks: C = 10 · all answers ≥ C−3 · full
- Time limit: 2 s (Python 15 s), memory: 256 MB
Key idea
Map each string to a bitmask in [0, 2^C). Hamming(T_i, T_j) = popcount(T_i XOR T_j).
Maximizing popcount(T_i XOR T_j) is equivalent to finding any T_j whose mask agrees
with the complement ~T_i on the maximum number of bits — i.e., we want
the smallest "distance" between ~T_i and the set of present masks. Do a
multi-source BFS from all present masks over the hypercube graph (edges = flip one
bit): dist[m] = min Hamming distance from m to any present mask. Then answer for T_i
is C − dist[ ~T_i & ((1<<C)−1) ].
Complexity
O(C · 2^C + N) — BFS over the hypercube visits 2^C nodes each with C neighbors.
Reference C++
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int C, N; cin >> C >> N;
int M = 1 << C;
vector<int> mask(N);
vector<char> present(M, 0);
for (int i = 0; i < N; i++) {
string s; cin >> s;
int m = 0;
for (int b = 0; b < C; b++) if (s[b] == 'H') m |= (1 << b);
mask[i] = m;
present[m] = 1;
}
// BFS: dist[m] = min Hamming distance from m to any present mask.
vector<int> dist(M, INT_MAX);
queue<int> q;
for (int m = 0; m < M; m++) if (present[m]) { dist[m] = 0; q.push(m); }
while (!q.empty()) {
int u = q.front(); q.pop();
for (int b = 0; b < C; b++) {
int v = u ^ (1 << b);
if (dist[v] > dist[u] + 1) { dist[v] = dist[u] + 1; q.push(v); }
}
}
// For team i, max Hamming = C - dist[~mask[i] & (M-1)],
// but we must also exclude the case where the closest mask IS itself when N=1
// for that team — handled by the multi-source containing all teams.
int full = M - 1;
for (int i = 0; i < N; i++) {
int target = mask[i] ^ full;
cout << (C - dist[target]) << "\n";
}
}
Pitfalls
- If a team's own complement is itself present (e.g., team T and its bit-flip both appear), the BFS correctly reports dist = 0 and answer = C. No special case needed.
- What if N = 1? Problem guarantees N ≥ 2, but if all N teams are identical, the BFS still works — the answer for each is 0 (closest team to its own complement is itself = some other identical team's complement = distance C from itself, hmm — verify).
- Reading input as 'G'/'H' — pick a consistent bit convention; using H=1 keeps masks readable.
S3 · Pareidolia
Official statement (cpid=1328)
Statement (paraphrase)
For a string s, define B(s) as the maximum number of non-overlapping occurrences of the word "bessie" that can be carved out of s by deleting characters (the carved copies must appear in order, each spelling b-e-s-s-i-e). Compute the sum of B(s) over every contiguous substring s of the given string.
Constraints
- 1 ≤ |t| ≤ 3·10⁵
- Subtasks: |t| ≤ 5000 · full
- Time limit: 4 s (doubled), memory: 256 MB
Key idea
Walk the string with a 6-state automaton (state s = how many letters of "bessie" we've matched modulo 6). At each position i, for every starting position L ≤ i, track the state we'd be in at position i+1 if we started a fresh "bessie" search at L. Equivalently, scan left to right and maintain, for each starting position L, the automaton state — but that's O(N²). Smart trick: when we see character c at position i, all starting positions whose current state expects c advance by 1 (and complete a bessie when state would become 6 → wrap to 0 and credit +1). Use a counter per state. On reading c, the count of "starts currently in state expecting c" all transition; add the number that completed (state 5 → 0) into a running sum, and also account for the "every substring that starts ≤ i contributes its final B value at its right endpoint."
Complexity
O(N) per character with constant-size state counter (6 states).
Reference C++
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string t; cin >> t;
int N = t.size();
const string B = "bessie";
// count[s] = number of starting positions L <= i whose current automaton state is s.
// sum_complete = total bessies completed so far summed across all starts (this is sum
// over starts L of B(t[L..i])), accumulated so we can add to global answer per i.
long long count_[6] = {0,0,0,0,0,0};
long long ans = 0;
long long bessies_so_far = 0; // sum over L of B(t[L..i]) up to current i
for (int i = 0; i < N; i++) {
// A new substring starts at L = i: it begins in state 0.
count_[0]++;
// Now process character t[i]: for every state s where B[s] == t[i], transition s -> (s+1) mod 6.
// States 0..5 expect B[s].
// Build new counts.
long long newc[6] = {0,0,0,0,0,0};
for (int s = 0; s < 6; s++) {
if (count_[s] == 0) continue;
if (B[s] == t[i]) {
int ns = (s + 1) % 6;
newc[ns] += count_[s];
if (s == 5) bessies_so_far += count_[s]; // each of these starts just completed +1 bessie
} else {
newc[s] += count_[s];
}
}
for (int s = 0; s < 6; s++) count_[s] = newc[s];
// After processing position i, every substring ending at i contributes its current B value.
ans += bessies_so_far;
}
cout << ans << "\n";
}
Pitfalls
- The "sum over substrings" requires summing B(t[L..R]) for every (L,R). Tracking
bessies_so_faras the running total over all active starts at each R is the trick. - Greedy = optimal here: for a fixed substring, scanning left-to-right and matching the next expected bessie letter does maximize the count. Convince yourself.
- Watch for overflow: N = 3·10⁵, B can be up to N/6 per substring, # substrings ≈ N², so answer can reach ~10¹⁰.
long longmandatory.