2022 February Silver · All three problems
Problem 1 · Redistributing Gifts
Statement (paraphrased)
N cows each have a permutation preference list over N gifts. Cow i starts with gift i. The cows may reassign gifts so long as every cow ends up with a gift she likes at least as much as her own. For each cow output the best gift she could possibly receive under some valid reassignment.
Constraints
- 1 ≤ N ≤ 500.
- Time / memory: 2 s, 256 MB (default).
Key idea
Build a directed graph: edge i → j iff cow i prefers gift j at least as much as gift i (j appears no later than i on cow i's list). A reassignment is a permutation lying within this graph, which decomposes into vertex-disjoint cycles. Cow i can end up with gift j iff there's a directed cycle through edge (i → j). Equivalently, j is reachable from i AND i is reachable from j using only "at-least-as-good" edges. Compute the reachability matrix (N BFSes, O(N · (N + E)) = O(N3)), then for each i scan her preference list top-down and output the first j satisfying both reachabilities.
Complexity target
O(N3) ≈ 1.25 · 108 for N = 500 — fits in 2 s with bitsets.
C++ reference solution
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N; cin >> N;
vector<vector<int>> pref(N + 1, vector<int>(N));
// rank[i][g] = position of gift g in cow i's preference list (0 = best)
vector<vector<int>> rnk(N + 1, vector<int>(N + 1));
for (int i = 1; i <= N; ++i)
for (int k = 0; k < N; ++k) {
cin >> pref[i][k];
rnk[i][pref[i][k]] = k;
}
// adj[i] = set of gifts j such that cow i would accept j (rnk[i][j] <= rnk[i][i])
vector<bitset<505>> reach(N + 1);
for (int i = 1; i <= N; ++i) {
reach[i].set(i);
for (int j = 1; j <= N; ++j)
if (rnk[i][j] <= rnk[i][i]) reach[i].set(j);
}
// Transitive closure via Floyd-Warshall on bitsets
for (int k = 1; k <= N; ++k)
for (int i = 1; i <= N; ++i)
if (reach[i].test(k)) reach[i] |= reach[k];
for (int i = 1; i <= N; ++i) {
for (int k = 0; k < N; ++k) {
int g = pref[i][k];
// need both i -> g and g -> i (cycle through edge i->g)
if (reach[i].test(g) && reach[g].test(i)) { cout << g << '\n'; break; }
}
}
return 0;
}
Pitfalls
- "Cow i can be happy with j" is two-way reachability, not just one-way — j must also have a path back to i to close a cycle.
- Bitset Floyd-Warshall is the cleanest N=500 closure; plain BFS-from-each-node also works but is slower in practice.
- Include i in her own reach set: every cow is trivially willing to keep her own gift.
Problem 2 · Robot Instructions
Statement (paraphrased)
N ≤ 40 plane-vector instructions and a target (xg, yg). For each K from 1..N, count subsets of size K whose vector sum equals the target.
Constraints
- 1 ≤ N ≤ 40; coords up to 109; target ≠ origin.
- Time / memory: 4 s, 512 MB (2× default).
Key idea
Meet in the middle. Split into halves of size ≈ 20. Enumerate every subset of the left half, record (sumX, sumY, popcount) → frequency in a map. Then enumerate subsets of the right half; for each, look up (xg−sumX, yg−sumY, K−popcount) for every K and accumulate. 220 ≈ 106 subsets per half.
Complexity target
O(2N/2 · N) per side. Hash map keyed by (x, y, k).
C++ reference solution
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int N; ll gx, gy;
vector<pair<ll,ll>> v;
// Pack (x, y, k) into one 64-bit key (coords fit in ~36 bits each after offset)
struct Key { ll x, y; int k; bool operator==(const Key& o) const { return x==o.x&&y==o.y&&k==o.k; } };
struct H { size_t operator()(const Key& p) const noexcept {
return hash<ll>()(p.x) ^ (hash<ll>()(p.y) << 1) ^ (hash<int>()(p.k) << 2);
}};
int main(){
ios::sync_with_stdio(false); cin.tie(nullptr);
cin >> N >> gx >> gy;
v.resize(N);
for (auto& p : v) cin >> p.first >> p.second;
int L = N / 2, R = N - L;
unordered_map<Key, ll, H> left_map;
for (int m = 0; m < (1 << L); ++m) {
ll sx = 0, sy = 0; int k = __builtin_popcount(m);
for (int i = 0; i < L; ++i) if (m >> i & 1) { sx += v[i].first; sy += v[i].second; }
left_map[{sx, sy, k}]++;
}
vector<ll> ans(N + 1, 0);
for (int m = 0; m < (1 << R); ++m) {
ll sx = 0, sy = 0; int k = __builtin_popcount(m);
for (int i = 0; i < R; ++i) if (m >> i & 1) { sx += v[L + i].first; sy += v[L + i].second; }
// For each total size t, look up left count with size t - k
for (int t = k; t <= N; ++t) {
auto it = left_map.find({gx - sx, gy - sy, t - k});
if (it != left_map.end()) ans[t] += it->second;
}
}
for (int k = 1; k <= N; ++k) cout << ans[k] << '\n';
return 0;
}
Pitfalls
- Inner loop over all t is O(N) per right subset — total 220·40 ≈ 4·107. Replace the per-subset loop with a per-popcount aggregation if it's too slow [verify].
- Coordinates require 64-bit: 40 · 109 = 4·1010.
- Use a fast hash;
gp_hash_tablefrom__gnu_pbdsis a safer choice on adversarial sets.
Problem 3 · Email Filing
Statement (paraphrased)
N emails, M folders, screen height K. Initially folders 1..K and emails 1..K are visible. Both scroll bars only move down, except the email list can "snap back" once it shows the last K emails after a file. Each email i must be filed into folder fi. Decide whether the filing is achievable.
Constraints
- 1 ≤ M ≤ 104, 1 ≤ N ≤ 105, 1 ≤ K ≤ min(N, M).
- Multiple test cases per input.
- Time / memory: 2 s, 256 MB (default).
Key idea
Simulate. Keep two pointers: top-of-folder window F and the email pointer. Greedy: whenever the next email
visible in the window can be filed (its folder is in the current folder window), file it; else scroll folders down by one.
Two passes matter: first the "forward" pass on emails 1..N-K, then the "tail" pass on the last K which may be filed in any
order while the folder window slides forward. The textbook solution uses a stack of "pending" emails whose folders haven't
scrolled into view yet and a sorted multiset of upcoming folders; whenever the folder window advances, pop any pending
emails whose folder just entered view.
Complexity target
O((N + M) log M) per test case.
C++ reference solution
#include <bits/stdc++.h>
using namespace std;
bool solve(int N, int M, int K, vector<int>& f){
// Forward phase: emails [1..N-K], folder window starts at [1..K]
int folderTop = 1; // 1-indexed top folder visible
int emailIdx = 0; // next email to consider (0-indexed)
int tail = max(0, N - K); // emails to handle in forward phase
// Stack of unfiled emails currently in the visible email window
deque<int> window; // folder ids for emails currently in window (top..top+K-1)
auto canFile = [&](int folder){ return folder >= folderTop && folder < folderTop + K; };
for (int i = 0; i < min(K, tail); ++i) window.push_back(f[i]);
int next = min(K, tail);
while (!window.empty() || next < tail) {
bool fired = false;
// file from the back (newest) if visible — only the bottom-most slot can vanish freely
while (!window.empty() && canFile(window.back())) {
window.pop_back();
if (next < tail) { window.push_back(f[next++]); }
fired = true;
}
if (fired) continue;
// Otherwise scroll folders down
if (folderTop + K - 1 >= M) return false;
++folderTop;
}
// Tail phase: last K emails are visible and may be filed in any order
multiset<int> pending(f.begin() + tail, f.end());
while (!pending.empty()) {
auto it = pending.lower_bound(folderTop);
if (it != pending.end() && *it < folderTop + K) { pending.erase(it); }
else {
if (folderTop + K - 1 >= M) return false;
++folderTop;
}
}
return true;
}
int main(){
int T; cin >> T;
while (T--){
int N, M, K; cin >> N >> M >> K;
vector<int> f(N);
for (auto& x : f) cin >> x;
cout << (solve(N, M, K, f) ? "YES" : "NO") << '\n';
}
}
Pitfalls
- The tail-of-emails snap-back rule is the trickiest part of the statement — re-read it. The email window can stay on the last K after each file.
- Order of filing inside the visible window matters: only the bottom-most email slides up cleanly. My sketch above is a useful frame but verify the precise simulation against the editorial — this is an easy place to mis-code [verify] https://usaco.org/current/data/sol_prob3_silver_feb22.html.
- Memory: vectors over M = 104 and N = 105; nothing huge, but reset between test cases.