USACO 2015 February · Silver · all three problems
Problem 1 — Censoring (Silver)
Statement
Same statement as Bronze P1: given S and a single pattern T, repeatedly delete the earliest occurrence of T from S and continue until none remain. Output the final S, which is guaranteed non-empty. The only difference vs. Bronze is the size: |S| up to 106, so the naive O(|S|·|T|) stack-with-tail-compare can be too slow.
Constraints
- |S| ≤ 106.
- |T| ≤ |S|, but realistically |T| ≤ 106.
- Lowercase a–z only.
- Time limit: 2s.
Key idea
Build KMP failure function for T. Process S character by character maintaining a stack of (character, kmp_state) pairs. For each incoming character c, advance from the current top state using KMP fail-jumps until either we match T[state] = c (advance), or fall off (state = 0). Push (c, new_state) onto the stack. If new_state equals |T|, pop |T| entries — and crucially the new top of stack has the previous state, ready to continue. This handles chain-deletions in O(|S|).
Complexity target
O(|S| + |T|) time, O(|S|) memory.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
string S, T; cin >> S >> T;
int m = (int)T.size();
// KMP failure function
vector<int> fail(m, 0);
for (int i = 1, k = 0; i < m; ++i) {
while (k > 0 && T[k] != T[i]) k = fail[k - 1];
if (T[k] == T[i]) ++k;
fail[i] = k;
}
// Stack of (char, state_after_consuming_this_char)
vector<char> outC; outC.reserve(S.size());
vector<int> outS; outS.reserve(S.size());
int state = 0;
for (char c : S) {
while (state > 0 && T[state] != c) state = fail[state - 1];
if (T[state] == c) ++state;
outC.push_back(c);
outS.push_back(state);
if (state == m) {
// delete last m chars
for (int k = 0; k < m; ++k) { outC.pop_back(); outS.pop_back(); }
state = outS.empty() ? 0 : outS.back();
}
}
cout.write(outC.data(), (int)outC.size());
cout << "\n";
return 0;
}
Pitfalls
- State after deletion is the state of the new top. A common bug: resetting state to 0 after deletion, which misses cross-boundary matches like the "moo" inside "momoo".
- Don't pop from the wrong end. The stack is the output stack, not a queue.
- Bronze brute force times out. At |S| = 10⁶ and |T| ≈ 100, a stack-with-tail-compare is roughly 10⁸ ops worst case; KMP keeps it linear.
- Output is a single line. Newline at the end, no extra whitespace.
Problem 2 — Cow Hopscotch (Silver)
Statement
R × C grid; each cell holds an integer label in 1..K. Same jump rule as Bronze (strictly down, strictly right, different label). Count distinct jump-sequences from (1, 1) to (R, C) modulo 109+7.
Constraints
- 2 ≤ R, C ≤ 100.
- 1 ≤ K ≤ R · C.
- Output modulo 1 000 000 007.
- Time limit: 2s.
Key idea
Same DP as Bronze: f[r][c] = number of jump-sequences ending at (r, c), with
f[0][0] = 1, and
f[r][c] = sum_{r' < r, c' < c, label[r'][c'] != label[r][c]} f[r'][c'].
At Silver, R, C ≤ 100 ⇒ at most 10⁴ cells, each summing over up to 10⁴ predecessors ⇒ 10⁸ elementary ops with a constant-time mod, which is borderline but fits within 2s in C++. No fancy optimisation needed.
Complexity target
O(R² · C²) ≈ 10⁸ ops at the limits — fine with tight inner loops.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 1000000007LL;
int R, C, K;
int g[105][105];
long long f[105][105];
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cin >> R >> C >> K;
for (int r = 0; r < R; ++r)
for (int c = 0; c < C; ++c) cin >> g[r][c];
f[0][0] = 1;
for (int r = 0; r < R; ++r) {
for (int c = 0; c < C; ++c) {
if (r == 0 && c == 0) continue;
long long sum = 0;
int lab = g[r][c];
for (int rp = 0; rp < r; ++rp) {
for (int cp = 0; cp < c; ++cp) {
if (g[rp][cp] != lab) sum += f[rp][cp];
}
}
f[r][c] = sum % MOD;
}
}
cout << f[R-1][C-1] << "\n";
return 0;
}
Pitfalls
- Take the mod at every assignment, not just at the end. Accumulators are 64-bit but a single un-modded sweep at R = C = 100 with K = 1 (degenerate) still fits, however the answer must be in [0, MOD) for the judge.
- Avoid
sum -=tricks at Silver. The "total prefix minus same-colour prefix" trick is the Gold optimisation; at Silver the direct O(R²C²) is intended. - Different label, not different parity / different value mod K. Labels are integers; equality of integers.
Problem 3 — Superbull
Statement
Single-elimination tournament with N teams. In each match, the score is the XOR of the two teams' IDs, and FJ chooses which team is eliminated. The tournament runs N − 1 matches (one team remains). Maximise the sum of all match scores.
Constraints
- 1 ≤ N ≤ 2000.
- 1 ≤ idi ≤ 2³⁰ − 1, all distinct.
- Time limit: 2s.
Key idea
Treat the N teams as vertices of a complete graph; the edge weight between i and j is idi XOR idj. The N − 1 matches form a spanning tree of this graph (any single-elimination bracket on N nodes is a tree with N − 1 edges, and conversely any spanning tree can be realised as a bracket because we choose which team is eliminated). Maximising the sum of edge weights = maximum spanning tree. With N ≤ 2000 the dense Prim's algorithm in O(N²) is the cleanest fit.
Complexity target
O(N²) ≈ 4·10⁶ operations.
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; cin >> N;
vector<ll> id(N);
for (auto& x : id) cin >> x;
// Dense Prim for maximum spanning tree.
vector<ll> best(N, -1);
vector<char> inTree(N, 0);
best[0] = 0;
ll total = 0;
for (int it = 0; it < N; ++it) {
int u = -1;
for (int v = 0; v < N; ++v) {
if (!inTree[v] && (u == -1 || best[v] > best[u])) u = v;
}
inTree[u] = 1;
total += best[u];
for (int v = 0; v < N; ++v) {
if (!inTree[v]) {
ll w = id[u] ^ id[v];
if (w > best[v]) best[v] = w;
}
}
}
cout << total << "\n";
return 0;
}
Pitfalls
- Bracket ⇔ spanning tree. The key insight; without it people try DP over subsets (2^N blows up) or greedy XOR pairings (wrong).
- Maximum, not minimum. Standard Kruskal/Prim is min-spanning-tree by default — flip the comparator.
- Use
long long. Up to 1999 edges each up to 2³⁰ ⇒ sum can exceed 2³¹. - O(N² log N) Kruskal with N² edges ≈ 4·10⁶·22 ≈ 9·10⁷ — fine, but dense Prim is simpler and faster.
What Silver February 2015 was actually testing
- P1 — KMP with deletion. Classic stack-of-states trick; cropping up again in Gold P2 with multiple patterns (Aho–Corasick).
- P2 — 2D DP at moderate scale. Direct quartic DP; Gold version forces a 2D-prefix-sum-per-colour optimisation.
- P3 — graph theory recognition. Recognise "elimination bracket" as "spanning tree of K_N", then plug in MST.