USACO 2019 January · Gold · all three problems
Problem 1 — Sleepy Cow Sorting (Gold)
Statement
Bessie has a permutation a1..N. She is about to perform exactly K "moves", each of which takes any cow currently in the line and re-inserts her anywhere (including the same spot). Count the number of distinct permutations of cows that could end up after exactly K moves, modulo 109+7.
Constraints
- 1 ≤ N ≤ 105, 0 ≤ K ≤ N.
- Time limit: 2s; answer modulo 109+7.
Key idea
A permutation is reachable in exactly K moves iff at least N − K of its elements were not moved (i.e. some N − K of the original positions remain in their original relative order). The set of "unmoved" cows forms an increasing subsequence relative to a; counting reachable permutations reduces to a sum over choices of the moved set, with placements of K moved cows into N slots. The closed form combines binomial coefficients: answer = Σj=0..K (−1)j C(N, K−j) · (N − K + j)j · …, computed with precomputed factorials.
Complexity target
O(N log MOD) including modular inverses for binomials; O(N + K) inner loop after factorial precompute.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1'000'000'007LL;
ll pw(ll a, ll e, ll m) {
ll r = 1 % m; a %= m;
while (e) { if (e & 1) r = r * a % m; a = a * a % m; e >>= 1; }
return r;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, K; cin >> N >> K;
// (statement uses a permutation as input; for the count itself only N, K matter.)
for (int i = 0, x; i < N; ++i) cin >> x;
int M = N + 5;
vector<ll> fact(M), inv(M);
fact[0] = 1;
for (int i = 1; i < M; ++i) fact[i] = fact[i - 1] * i % MOD;
inv[M - 1] = pw(fact[M - 1], MOD - 2, MOD);
for (int i = M - 2; i >= 0; --i) inv[i] = inv[i + 1] * (i + 1) % MOD;
auto C = [&](int n, int r) -> ll {
if (r < 0 || r > n) return 0;
return fact[n] * inv[r] % MOD * inv[n - r] % MOD;
};
// Inclusion-exclusion over the "moved set" of size K (with repetition allowed
// via choosing which K positions in the final sequence are the moved cows).
ll ans = 0;
for (int j = 0; j <= K; ++j) {
ll term = C(N, K - j) % MOD * pw(N - K + j, j, MOD) % MOD;
if (j & 1) ans = (ans - term + MOD) % MOD;
else ans = (ans + term) % MOD;
}
cout << ans << "\n";
}
Pitfalls
- "Exactly K moves" ≠ "at most K". Inclusion-exclusion is required; otherwise you overcount permutations reachable in fewer.
- Modular arithmetic. Use Fermat's little theorem for inverses.
- The input permutation barely matters for the count. The number of reachable permutations depends on N and K only, since reinsertion is unrestricted.
- Don't trust this formula blindly. Verify against the sample before submitting.
Problem 2 — Shortcut
Statement
There are N fields connected by M bidirectional paths with travel times. Every evening each cow walks from the barn (field 1) to her own field along a shortest path; FJ knows ci cows live in field i. FJ may add a single zero-time "shortcut" of cost T from the barn to one chosen field; this creates an alternative path that cows will use only if it is at least as short as their current shortest. Choose the destination to maximize total time saved.
Constraints
- 1 ≤ N ≤ 104, 1 ≤ M ≤ 5·104.
- Edge weights ≤ 104, ci ≤ 104, 1 ≤ T ≤ 104.
- Time limit: 2s.
Key idea
Run Dijkstra from the barn to get shortest distance dist[v] for every field. Among the shortest paths, build a "shortest-path forest" by preferring, for tie-breaking, the path that maximizes the sum of c along it (so we credit each cow's time savings to one specific path). For each field v with dist[v] > T, the saving if we connect v is c-subtree-of-v · (dist[v] − T). Pick the max. Subtree c-sums come from a topological sort over the DAG of shortest-path predecessors (process nodes in decreasing dist order).
Complexity target
O((N + M) log N) for Dijkstra + O(N + M) for the DAG aggregation.
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, M, T; cin >> N >> M >> T;
vector<ll> c(N + 1);
for (int i = 1; i <= N; ++i) cin >> c[i];
vector<vector<pair<int,int>>> g(N + 1);
for (int i = 0; i < M; ++i) {
int u, v, w; cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
vector<ll> dist(N + 1, LLONG_MAX);
vector<int> parent(N + 1, 0);
priority_queue<pair<ll,int>, vector<pair<ll,int>>, greater<>> pq;
dist[1] = 0; pq.push({0, 1});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d > dist[u]) continue;
for (auto [v, w] : g[u]) {
ll nd = d + w;
// Tie-break: prefer parent with smaller index, deterministic.
if (nd < dist[v] || (nd == dist[v] && u < parent[v])) {
dist[v] = nd;
parent[v] = u;
pq.push({nd, v});
}
}
}
// Aggregate c up the shortest-path tree.
vector<ll> sub = c;
vector<int> order(N);
iota(order.begin(), order.end(), 1);
sort(order.begin(), order.end(), [&](int a, int b) { return dist[a] > dist[b]; });
for (int u : order) if (u != 1 && parent[u]) sub[parent[u]] += sub[u];
ll best = 0;
for (int v = 2; v <= N; ++v) if (dist[v] > T) best = max(best, sub[v] * (dist[v] - T));
cout << best << "\n";
}
Pitfalls
- Tie-break choice changes the answer. Among equally short paths cows choose deterministically; the official rule is lexicographic by field id along the path.
- Don't treat the shortcut as a normal edge. It's zero-cost on the way and only used if it shortens (or equals) the existing shortest path; that is exactly the condition
dist[v] ≥ T. - Use 64-bit. Sum of cows × distance can exceed 231.
- Multiple edges. The graph can have parallel edges; Dijkstra handles them fine.
Problem 3 — Cow Poetry
Statement
There are N words; word i has si syllables and rhyme class ci. A poem has M lines, each with exactly K syllables, and the lines must follow a given rhyme scheme (e.g. ABBA): lines with the same letter must end on words from the same rhyme class. Count the number of valid poems modulo 109+7.
Constraints
- 1 ≤ N ≤ 5000, 1 ≤ M ≤ 105, 1 ≤ K ≤ 5000.
- Rhyme scheme uses ≤ 26 letters.
- Time limit: 2s.
Key idea
(1) Knapsack DP: ways[j] = number of ways to fill a line of exactly j syllables using
word multiset (with repetition). For each j with j == K, we know how many lines end on each word
— specifically ends[w] = ways[K − sw]. Aggregate endClass[c] = Σ ends[w]
for words w with class c. (2) For each rhyme group of size t (number of lines that share a
letter), the number of choices is Σc endClass[c]t. The total
poem count is the product over rhyme groups of these sums. Multiply, take mod.
Complexity target
O(N · K) knapsack + O((#classes) · log M) for exponentiation per group.
Reference solution (C++)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1'000'000'007LL;
ll pw(ll a, ll e) { ll r = 1 % MOD; a %= MOD; while (e) { if (e & 1) r = r * a % MOD; a = a * a % MOD; e >>= 1; } return r; }
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int N, M, K; cin >> N >> M >> K;
vector<int> s(N), c(N);
for (int i = 0; i < N; ++i) cin >> s[i] >> c[i];
// ways[j] = #ordered sequences of words summing to j syllables
vector<ll> ways(K + 1, 0);
ways[0] = 1;
for (int j = 1; j <= K; ++j)
for (int i = 0; i < N; ++i) if (s[i] <= j)
ways[j] = (ways[j] + ways[j - s[i]]) % MOD;
// endClass[c] = #line-completions ending on a word of class c
map<int, ll> endClass;
for (int i = 0; i < N; ++i) if (s[i] <= K)
endClass[c[i]] = (endClass[c[i]] + ways[K - s[i]]) % MOD;
// Count group sizes from the rhyme scheme.
map<char, int> group;
for (int line = 0; line < M; ++line) { char x; cin >> x; ++group[x]; }
ll ans = 1;
for (auto& [letter, t] : group) {
ll s_ = 0;
for (auto& [cls, v] : endClass) s_ = (s_ + pw(v, t)) % MOD;
ans = ans * s_ % MOD;
}
cout << ans << "\n";
}
Pitfalls
- Word order in a line matters. The knapsack is "ordered sequences", not multisets — that's why we iterate "for each j, for each word" rather than "for each word, for each j".
- Rhyme class of the last word in a line is what counts. Don't track the whole word identity past the line.
- Group independence. Different rhyme letters are independent, so the total is a product over groups.
- Modular arithmetic. Use 64-bit before
% MOD.
What Gold January 2019 was actually testing
- P1 — counting + inclusion-exclusion. Recognize that "exactly K moves" is a difference of "≤ K" terms, expressed as a closed form.
- P2 — Dijkstra plus subtree sums. Shortest-path tree, then aggregate cows up the tree to evaluate each candidate shortcut endpoint.
- P3 — knapsack then group exponentiation. Two-stage counting: per-line, then per-rhyme group.