# ACSI NOI Workshop 2021 Solution ## Lesson Problems ### Problem 1: Quality-Adjusted Life-Year - https://open.kattis.com/problems/qaly ```cpp=1 #include <bits/stdc++.h> using namespace std; // Declare variables here int N; float q, y, answer; int main() { // Read integer N cin >> N; // Repeat N times: for (int i = 0; i < N; i++) { // 1. Read float q, y cin >> q >> y; // 2. Add q * y to answer answer += q * y; } // Output answer cout << answer; } ``` ### Problem 2: Poker Hand - https://open.kattis.com/problems/pokerhand ```cpp=1 #include <bits/stdc++.h> using namespace std; int ans; string S[5]; // Declare variables here unordered_map<char, int> freq; int main() { // Read 5 strings into array S[] for (int i = 0; i < 5; i++) { cin >> S[i]; } // For each hand in S[]: for (int i = 0; i < 5; i++) { // 1. Get the rank char rank = S[i][0]; // 2. Increase the rank's count by 1 freq[rank]++; } // For each rank's count: for (auto x : freq) { // 1. Update answer if count > current answer ans = max(ans, x.second); } // Output answer cout << ans; } ``` ### Problem 3: Bungee Builder - https://open.kattis.com/problems/bungeebuilder ```cpp=1 #include <bits/stdc++.h> using namespace std; #define MAXN 1000006 // Declare variables here int N, height[MAXN]; // Sliding window from left to right, returns the max distance, O(N) int sliding(int N, int height[]) { int i = 0, ans = 0, bottom = height[0]; for (int j = 1; j < N; j++) { if (height[i] <= height[j]) { // Form a bridge between i and j, calculate the max distance ans = max(ans, height[i] - bottom); // Set j as the new i i = j; bottom = height[i]; } else { // Keep searching for the lowest bottom bottom = min(bottom, height[j]); } } return ans; } int main() { // Read inputs cin >> N; for (int i = 0; i < N; i++) { cin >> height[i]; } // Get forward result int ans_forward = sliding(N, height); // Reverse the height array in-place reverse(begin(height), begin(height) + N); // Get backward result int ans_backward = sliding(N, height); // Output the max of both forward & backward result cout << max(ans_forward, ans_backward); } ``` ### Problem 4: The Monkey and the Oiled Bamboo - https://onlinejudge.org/external/120/12032.pdf ```cpp=1 #include <bits/stdc++.h> using namespace std; // Bad practice in software engineering, but ok for CP #define MAXN 100005 #define MAXR 10000007 // Declare variables here int T, N, R[MAXN], ans; // Returns true if can reach the top rung with k bool can_reach(int k, int R[]) { for (int i = 0; i < N; i++) { int gap = (i == 0) ? R[0] : R[i] - R[i - 1]; if (gap > k) { return false; } else if (gap == k) { k--; } } return true; } int main() { // Read T cin >> T; // For each case i for (int i = 0; i < T; i++) { // Read input cin >> N; for (int j = 0; j < N; j++) { cin >> R[j]; } // Initialize binary search low & high int lo = 1, hi = MAXR; // Binary search terminating condition while (lo <= hi) { // Pick the middle as k int mid = hi - (hi - lo) / 2; // Prevent integer overflow if (can_reach(mid, R)) { // Keep the answer ans = mid; // Look for smaller k hi = mid - 1; } else { // Look for bigger k lo = mid + 1; } } // Output answer printf("Case %d: %d\n", i + 1, ans); } } ``` ### Problem 5: Station Balance - https://onlinejudge.org/external/4/410.pdf ```cpp=1 #include <bits/stdc++.h> using namespace std; #define MAXS 10 // Declare variables here int C, S, M[MAXS], TC = 1, s1, s2; double imb, sum, avg; int main() { while (cin >> C >> S) { sum = imb = 0; // Read input for (int i = 0; i < S; i++) { cin >> M[i]; sum += M[i]; } avg = sum / C; // Pad M with 0 for (int i = S; i < 2 * C; i++) { M[i] = 0; } sort(M, M + 2 * C); // Output answer printf("Set #%d\n", TC++); for (int i = 0; i < C; i++) { printf(" %d:", i); s1 = M[i], s2 = M[2 * C - i - 1]; // Don't print 0 if (s1) printf(" %d", s1); if (s2) printf(" %d", s2); printf("\n"); imb += abs(avg - (s1 + s2)); } // 5 digits after decimal point printf("IMBALANCE = %.5f\n\n", imb); } } ``` ### Problem 6: Wedding Shopping - https://onlinejudge.org/external/114/11450.pdf ```cpp=1 #include <bits/stdc++.h> using namespace std; #define INF 1000000009 #define MAXC 20 #define MAXK 20 #define MAXM 200 int N, M, C, K, price[MAXC][MAXK], memo[MAXK][MAXM]; // g is garment type, b is current budget int buy(int g, int b) { if (b < 0) return -INF; if (g == C) return M - b; if (memo[g][b] != -1) return memo[g][b]; // Try each model k for garment g int ans = -1; for (int k = 1; k <= price[g][0]; k++) ans = max(ans, buy(g + 1, b - price[g][k])); return memo[g][b] = ans; } int main() { scanf("%d", &N); while (N--) { // Read input scanf("%d %d", &M, &C); for (int i = 0; i < C; i++) { // price[i][0] is number of models for garment i scanf("%d", &price[i][0]); for (int j = 1; j <= price[i][0]; j++) // price[i][j] is the price of model j for garment i scanf("%d", &price[i][j]); } // Clear memory memset(memo, -1, sizeof memo); int ans = buy(0, M); // Output answer if (ans >= 0) printf("%d\n", ans); else printf("no solution\n"); } } ``` ## Day 1 Homework ### Delimiter Soup - https://open.kattis.com/problems/delimitersoup ```cpp=1 #include <bits/stdc++.h> using namespace std; int L; string str; stack<char> expect; unordered_map<char, char> match = {{'(', ')'}, {'[', ']'}, {'{', '}'}}; int main() { // Read input, cin >> ws to consume endline token for getline to work cin >> L >> ws; getline(cin, str); for (int i = 0; i < L; i++) { char c = str[i]; // Ignore whitespace if (isspace(c)) continue; auto delim = match.find(c); if (delim != match.end()) { // c is open delimiter expect.push(delim->second); // Store the expected close delimiter in a stack } else { // c is close delimiter if (!expect.empty() && c == expect.top()) expect.pop(); else { // c is not the expected close delimiter cout << c << " " << i; // Terminate program early return 0; } } } cout << "ok so far"; } ``` ### Teque - https://open.kattis.com/problems/teque ```cpp=1 #include <bits/stdc++.h> using namespace std; int N, x; string op; deque<int> d1, d2; vector<int> ans; int main() { // Fast I/O ios_base::sync_with_stdio(false); cin.tie(NULL); cin >> N; for (int i = 0; i < N; i++) { cin >> op >> x; // O(1) time for every operation type if (op == "get") ans.push_back(x < d1.size() ? d1[x] : d2[x - d1.size()]); else if (op == "push_back") d2.push_back(x); else if (op == "push_front") d1.push_front(x); else if (op == "push_middle") // Insert at the front of d2 d2.push_front(x); // Rebalance d1 and d2 // d1 is always the same size or 1 item more than d2 if (d1.size() < d2.size()) { d1.push_back(d2.front()); d2.pop_front(); } else if (d1.size() > d2.size() + 1) { d2.push_front(d1.back()); d1.pop_back(); } } // Output all answer at one go for speed for (int x : ans) cout << x << "\n"; } ``` ### Pivot - https://open.kattis.com/problems/pivot ```cpp=1 #include <bits/stdc++.h> using namespace std; #define MAXN 100005 int N, a[MAXN], max_left[MAXN], min_right[MAXN]; int main() { // Fast I/O ios_base::sync_with_stdio(false); cin.tie(NULL); // Read input cin >> N; for (int i = 0; i < N; i++) cin >> a[i]; // min_left[i] is the minimum value from everything to the left of a[i] max_left[0] = a[0]; for (int i = 1; i < N; i++) max_left[i] = max(max_left[i - 1], a[i]); // max_right[i] is the maximum value from everything to the left of a[i] min_right[N - 1] = a[N - 1]; for (int i = N - 2; i >= 0; i--) min_right[i] = min(min_right[i + 1], a[i]); int ans = 0; for (int i = 0; i < N; i++) // Count number of max_left[i] == min_right[i] ans += (max_left[i] == min_right[i]); // Output answer cout << ans; } ``` ### Disastrous Downtime - https://open.kattis.com/problems/downtime ```cpp=1 #include <bits/stdc++.h> using namespace std; #define MAXN 100005 int n, k, request, ans, t[MAXN]; queue<int> process; int main() { // Fast I/O ios_base::sync_with_stdio(false); cin.tie(NULL); // Read input cin >> n >> k; for (int i = 0; i < n; i++) { cin >> t[i]; // Add time for request to finish processed process.push(t[i] + 1000); // Add request count by 1 request++; // Remove all processed requests before t[i] while (!process.empty() && process.front() <= t[i]) { process.pop(); request--; } // Update answer ans = max(ans, request); } // Note the float conversion for ceil to work cout << ceil((float)ans / k) << "\n"; } ``` ## Day 2 Homework ### Knigs of the Forest - https://open.kattis.com/problems/knigsoftheforest ```cpp=1 #include <bits/stdc++.h> using namespace std; #define MAXN 100005 int k, n, y, p, karl, winner, power[MAXN]; priority_queue<int> pool; int main() { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); // Read input cin >> k >> n; for (int i = 0; i <= n + k - 2; i++) { cin >> y >> p; // Store Karl's power if (i == 0) karl = p; // If year is 2011, it is an initial moose if (y == 2011) pool.push(p); // Otherwise, it is a replacement moose else power[y - 2011] = p; } // Simulate from index 0 to index n - 1 for (int i = 0; i < n; i++) { winner = pool.top(); // Karl wins, early terminate if (winner == karl) { cout << i + 2011 << "\n"; return 0; } else { // Remove winner pool.pop(); // Insert next year's replacement pool.push(power[i + 1]); } } // Karl never wins, output unknown cout << "unknown\n"; } ``` ### Candy Division - https://open.kattis.com/problems/candydivision ```cpp=1 #include <bits/stdc++.h> using namespace std; // Using 'll' as the shorthand of 'long long' typedef long long ll; // Note that we need to use long long instead of integers due to constraints (10^12) ll n, sqrt_n; // Using set because it automatically sorts items and removes duplicates set<ll> ans; int main() { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); // Read input cin >> n; sqrt_n = sqrt(n); // Find divisors from 1 to square root of n for (ll i = 1; i <= sqrt_n; i++) { // If n mod i = 0, i.e. n is divisible by i if (n % i == 0) { // Problem requires (divisors - 1) ans.insert(i - 1); ans.insert((n / i) - 1); } } // Output answer for (auto it = ans.begin(); it != ans.end(); it++) cout << *it << " "; cout << "\n"; } ``` ### Doctor Kattis - https://open.kattis.com/problems/doctorkattis ```cpp=1 #include <bits/stdc++.h> using namespace std; // A Cat is a pair of <level, time> typedef pair<int, int> Cat; #define fi first #define se second int n, op, level, inc; string name; struct CatComparator { bool operator()(const Cat& cat1, const Cat& cat2) const { // Sort Cats by level descending, then by time ascending return cat1.fi != cat2.fi ? cat1.fi > cat2.fi : cat1.se < cat2.se; } }; map<Cat, string, CatComparator> cat_to_name; // Cat -> name unordered_map<string, Cat> name_to_cat; // name -> Cat int main() { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); // Read input cin >> n; for (int time = 0; time < n; time++) { cin >> op; if (op == 0) { // Arrive at clinic cin >> name >> level; // Create a new entry in both containers Cat cat = {level, time}; // O(1) cat_to_name[cat] = name; // O(logn) name_to_cat[name] = cat; // O(1) } else if (op == 1) { // Update infection level cin >> name >> inc; // Use unordered_map to retrieve Cat by name Cat& cat = name_to_cat[name]; // O(1) // Remove the Cat entry from map cat_to_name.erase(cat); // O(logn) // Increment infection level // At line 45, Cat is a reference to name_to_cat's value, // thus name_to_cat's value is updated by line 51 cat.fi += inc; // Insert the updated cat into map // We have to delete the old Cat->name entry from cat_to_name and // insert a new one because Cat is a key in cat_to_name, not a value. cat_to_name[cat] = name; // O(logn) } else if (op == 2) { // Leave clinic cin >> name; Cat& cat = name_to_cat[name]; // O(1) // Remove Cat entry from both containers cat_to_name.erase(cat); // O(logn) name_to_cat.erase(name); // O(1), not necessary } else { // Output highest infection cat if (cat_to_name.empty()) cout << "The clinic is empty\n"; else cout << cat_to_name.begin()->se << "\n"; // O(1) } } } ``` ### CD - https://open.kattis.com/problems/cd ```cpp=1 #include <bits/stdc++.h> using namespace std; int n, m, ans, x; unordered_set<int> jack; int main() { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); while (cin >> n >> m) { // Keep looping until input is 0 0 if (n == 0 && m == 0) break; // Reset counters ans = 0; jack.clear(); // Read input while (n--) { cin >> x; jack.insert(x); } while (m--) { cin >> x; if (jack.find(x) != jack.end()) ans++; } // Output answer cout << ans << "\n"; } } ``` ### Recount - https://open.kattis.com/problems/recount ```cpp=1 #include <bits/stdc++.h> using namespace std; #define fi first #define se second int most_votes, winner_count; string name, winner; unordered_map<string, int> candidates; int main() { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); // Read input while (getline(cin, name)) { if (name == "***") break; // Create new candidate entry or increase candidate's count by 1 if (candidates.find(name) == candidates.end()) candidates[name] = 1; else candidates[name]++; } // Find most votes for (auto it = candidates.begin(); it != candidates.end(); it++) most_votes = max(most_votes, it->se); // Count candidates with most_votes, get winner's name for (auto it = candidates.begin(); it != candidates.end(); it++) { if (it->se == most_votes) { winner = it->fi; winner_count++; } } // Output answer if (winner_count > 1) cout << "Runoff!" << endl; else cout << winner << endl; } ``` ### Conversation Log - https://open.kattis.com/problems/conversationlog ```cpp=1 #include <bits/stdc++.h> using namespace std; #define fi first #define se second int m; string line, name; set<string> names; unordered_map<string, int> word_count; unordered_map<string, set<string>> word_to_names; vector<string> msg; vector<pair<int, string>> common_words; int main() { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); // Read input and a whitespace for getline to work cin >> m >> ws; for (int i = 0; i < m; i++) { // Read the whole input line getline(cin, line); // Clear previous msg first msg.clear(); // Split whole line by whitespace, store into msg size_t pos = 0, next_pos; while ((next_pos = line.find(" ", pos)) != string::npos) { msg.push_back(line.substr(pos, next_pos - pos)); pos = next_pos + 1; } msg.push_back(line.substr(pos, next_pos - pos)); name = msg[0]; // Keep track of a set of unique names names.insert(name); // For each word in the msg for (auto it = msg.begin() + 1; it != msg.end(); it++) { string word = *it; // Keep track of people who have used the word word_to_names[word].insert(name); // Update the word's count if (word_count.find(word) == word_count.end()) word_count[word] = 1; else word_count[word]++; } } // Find common words that are mentioned by every user for (auto it = word_to_names.begin(); it != word_to_names.end(); it++) { string word = it->fi; int mentions = it->se.size(); if (mentions == names.size()) { // Negate first value for descending order // Alternatively, you can define your own comparator common_words.push_back(make_pair(-word_count[word], word)); } } // Sort common words sort(common_words.begin(), common_words.end()); // Output answer if (common_words.empty()) cout << "ALL CLEAR\n"; else for (auto x : common_words) cout << x.se << "\n"; } ``` ## Day 3 Homework ### The Easiest Problem Is This One - https://open.kattis.com/problems/easiest ```cpp=1 #include <bits/stdc++.h> using namespace std; int n, target, ans; // Find the sum of digit, O(# digits) i.e. O(logn) int sum_digit(int x) { int result = 0; while (x) { result += x % 10; x /= 10; } return result; } int main() { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); while (cin >> n) { // Terminate program when input is 0 if (n == 0) break; target = sum_digit(n); ans = 10; // Keep trying new answer by increasing 1 while (ans++) { if (sum_digit(n * ans) == target) { // Found correct answer cout << ans << "\n"; break; } } } } ``` ### Watering Grass - https://open.kattis.com/problems/grass ```cpp=1 #include <bits/stdc++.h> using namespace std; #define EPS 0.000000009 // A sprinkler has a leftmost & rightmost fully covered points struct sp { double l, r; }; bool cmp(sp a, sp b) { return fabs(a.l - b.l) > EPS ? a.l < b.l : a.r > b.r; } int n, length, width, center, radius; vector<sp> sprinkler; int main() { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); while (cin >> n >> length >> width) { // Read input for (int i = 0; i < n; i++) { cin >> center >> radius; // Discard sprinklers that cannot fully cover the width if (2 * radius < width) continue; // Find the inner square of the circle using Pythagoras Theorem double dx = sqrt(pow(radius, 2) - pow((width / 2.0), 2)); // Leftmost & rightmost fully covered points of a sprinkler sprinkler.push_back(sp{center - dx, center + dx}); } // Sort sprinklers with ascending l, then descending r sort(sprinkler.begin(), sprinkler.end(), cmp); // Area covered (from left) so far double covered = 0.0; int ans = 0; bool possible = true; // Checking each sprinkler for (int i = 0; i < sprinkler.size() && possible; i++) { // Covered the whole grass strip if (covered >= length) break; // Discard sprinkler if its completely inside the covered area else if (sprinkler[i].r < covered) continue; // There is a gap that is impossible to cover with any sprinkler else if (sprinkler[i].l > covered) possible = false; // Given the currently "covered" point, look for sprinklers that overlaps "covered" // and has the largest coverage else { // Initialize the current sprinkler as the best sprinkler double max_right = sprinkler[i].r; int max_i = i; // Check all sprinklers that l <= covered while (++i < sprinkler.size() && sprinkler[i].l <= covered) // Found a sprinkler that has larger coverage if (sprinkler[i].r > max_right) { max_right = sprinkler[i].r; // Keep the max coverage sprinkler's index max_i = i; } // Set the area covered so far as the right point of the selected sprinkler covered = max_right; i = max_i; ans++; } } // Output answer if (!possible || covered < length) cout << "-1\n"; else cout << ans << "\n"; // Clear container sprinkler.clear(); } } ``` ### The Dragon of Loowater - https://open.kattis.com/problems/loowater ```cpp=1 #include <bits/stdc++.h> using namespace std; #define MAXN 20004 int n, m, dragon[MAXN], knight[MAXN]; int main() { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); // Read input while (cin >> n >> m) { // Terminate program when input is 0 0 if (n == 0 && m == 0) break; for (int i = 0; i < n; i++) cin >> dragon[i]; for (int i = 0; i < m; i++) cin >> knight[i]; // Sort both arrays sort(dragon, dragon + n); sort(knight, knight + m); // Assign each dragon to the smallest possible knight int k = 0, ans = 0; bool doomed = false; for (int d = 0; d < n && !doomed; d++) { // Finding a knight while (k < m && knight[k] < dragon[d]) k++; // Ran out of knight if (k == m) doomed = true; // Found a knight ans += knight[k++]; } if (doomed) cout << "Loowater is doomed!\n"; else cout << ans << "\n"; } } ``` ### Traveling Monk - https://open.kattis.com/problems/monk ```cpp=1 #include <bits/stdc++.h> using namespace std; #define MAXN 5003 #define MAXT 5000005 #define EPS 0.000001 // A segment has a height, time and cumulative time struct Segment { int h, t, ct; } asc[MAXN], dsc[MAXN]; // Get the height at a specific time, O(n) double get_height(const Segment sg[], int n, double time) { // Cumulative height from previous segments double cum_height = 0.0; // Find the time's current segment Segment cur; for (int i = 0; i < n; i++) { cur = sg[i]; if (time <= sg[i].ct) break; cum_height += sg[i].h; } // Relative time within the segment double rel_time = cur.t - (cur.ct - time); // Relative height within the segment double rel_height = (double)cur.h / cur.t * rel_time; // Real height at the given time return cum_height + rel_height; } int a, d, total_h; int main() { // Read input cin >> a >> d; for (int i = 0; i < a; i++) { cin >> asc[i].h >> asc[i].t; asc[i].ct = (i == 0) ? asc[0].t : asc[i - 1].ct + asc[i].t; total_h += asc[i].h; } for (int i = 0; i < d; i++) { cin >> dsc[i].h >> dsc[i].t; dsc[i].ct = (i == 0) ? dsc[0].t : dsc[i - 1].ct + dsc[i].t; } // BSTA double lo = 0, hi = MAXT; while (fabs(hi - lo) > EPS) { double mid = (lo + hi) / 2.0; double asc_height = get_height(asc, a, mid); double dsc_height = total_h - get_height(dsc, d, mid); (asc_height < dsc_height) ? lo = mid : hi = mid; } // Output answer printf("%.6f\n", hi); } ``` ### Knapsack - https://open.kattis.com/problems/knapsack ```cpp=1 #include <bits/stdc++.h> using namespace std; typedef vector<int> vi; typedef vector<vi> vvi; const int MAXN = 2003; int C, n, value[MAXN], weight[MAXN]; vvi memo; // Find the best value after considering item i with w allowed weight int top_down(int i, int w) { // Previously computed state if (memo[i][w] != -1) return memo[i][w]; // Base case: 0 item or no remaining weight if (i == 0 || w == 0) return memo[i][w] = 0; // If item is heavier than the bag's allowed weight, don't take it if (w < weight[i]) return memo[i][w] = top_down(i - 1, w); // Otherwise consider either taking or not taking the item return memo[i][w] = max(top_down(i - 1, w - weight[i]) + value[i], top_down(i - 1, w)); } // Fill the memo table using the bottom-up fashion void bottom_up() { for (int i = 0; i <= n; i++) { for (int w = 0; w <= C; w++) { if (i == 0 || w == 0) memo[i][w] = 0; else if (w < weight[i]) memo[i][w] = memo[i - 1][w]; else memo[i][w] = max(memo[i - 1][w - weight[i]] + value[i], memo[i - 1][w]); } } } // Backtrack the memo to trace which items are selected void trace(int i, int w, vi& bag) { // Traced all items, job done if (i == 0) return; if (memo[i][w] == memo[i - 1][w]) { // Not taking item i return trace(i - 1, w, bag); } else { // Taking item i bag.push_back(i); return trace(i - 1, w - weight[i], bag); } } int main() { while (cin >> C >> n) { // Fast I/O ios_base::sync_with_stdio(0); cin.tie(NULL); // Read input for (int i = 1; i <= n; i++) // Change to 1-indexed cin >> value[i] >> weight[i]; // Reset memo memo = vector<vi>(n + 1, vi(C + 1, -1)); vi bag; // Dynamic programming // You may experiment by commenting away either 1 of the following 2 lines top_down(n, C); // bottom_up(); trace(n, C, bag); // Output answer cout << bag.size() << "\n"; for (int x : bag) cout << x - 1 << " "; // Revert to 0-indexed cout << "\n"; } } ```