Cribbage On Steroids ⧉
At the time of writing, this problem has a difficulty rating of 6.7 on Kattis, which qualifies as a Hard. It's fresh from the contest — I think the difficulty will even out to around a 4.0.
This is a 3-part implementation task. Implement a cribbage scorer for n
-length hands.
There are three main subtasks: Given an array of integers...
- ... count all the ways you can sum to 15.
This is solveable inn^2
steps using dynamic programming. See the functioncount_sums_to_k
for implementation details. - ... count all pairs of identical values.
This is given byn
choose 2 (n(n-1)/2
), withn
being the frequency of each value in the array. - ... count all ways to make a maximum length contiguous runs of length 3 or more after sorting the array.
This is done in linear time by iterating over an ordered map of the{ value, frequency }
.
There are more implementation details to consider. For instance, Aces have a face value of 1, but 10s, Jacks, Queens and Kings all have a face value of 10. Keeping all the requirements in order is key.
No one task is difficult on its own, but implementing all three without writing a bug is the challenge! I overlooked the "length 3 or more" requirement for counting runs on my first try. Took a minute to catch.
_104#include <iostream>_104#include <algorithm>_104#include <numeric>_104#include <map>_104#include <functional>_104#include <vector>_104_104using ll = long long;_104_104/**_104 * Count all the ways to sum to `k` with the values in `arr`._104 * Complexity: O(|arr|^2)_104 * */_104ll count_sums_to_k(ll k, const std::vector<ll>& arr) {_104 // `sum[i]` = the number of subsets of `arr` that sum to `i`_104 std::vector<ll> sum (k+1, 0);_104 sum[0] = 1; // We can sum to 0 by adding 0 elements together :)_104 for (ll x : arr) {_104 for (int i = k; i >= 0; --i) {_104 if (sum[i] != 0 && i+x <= k) {_104 // Since `sum[i]` subsets already sum to `i`, _104 // we can add `x` to each of those subsets, _104 // summing to `i+x` in `sum[i]` different ways!_104 sum[i+x] += sum[i]; _104 }_104 }_104 }_104 return sum[k];_104}_104_104/**_104 * Count all disjoint 'runs' of length 3 or more._104 * */_104ll count_runs(const std::map<ll, ll>& counts) {_104 ll score = 0;_104 ll len = 1;_104 ll card = -1;_104 ll mul = 1;_104 for (const auto& [v, k] : counts) {_104 if (card == -1) { card = v; mul = k; continue; }_104 if (v == card+1) {_104 mul *= k;_104 len++;_104 } else {_104 score += (len > 2 ? mul * len : 0LL);_104 len = 1;_104 mul = k;_104 }_104 card = v;_104 }_104 return score + (len > 2 ? mul * len : 0LL);_104}_104_104/**_104 * AKA count the pairs that can be made from `n` identical elements._104 **/_104ll n_choose_2(ll n) {_104 return (n*n - n)/2;_104}_104_104int main () {_104 // map each card to a number that reflects the order of the ranks_104 const auto encode = [] (char card) -> ll {_104 if (card == 'A') return 1LL;_104 if (card == 'T') return 10LL;_104 if (card == 'J') return 11LL;_104 if (card == 'Q') return 12LL;_104 if (card == 'K') return 13LL;_104 return card - '0';_104 };_104_104 int n;_104 std::cin >> n;_104 std::vector<char> cards (n);_104 std::map<ll, ll> counts;_104_104 for (auto& card : cards) {_104 std::cin >> card;_104 counts[encode(card)]++;_104 }_104_104 // Ace through 9 map to 1 through 9. 10s, Jacks, Queens and Kings are worth 10._104 const auto face_value = [] (ll encoding) -> ll {_104 if (encoding >= 1 && encoding <= 9) return encoding;_104 return 10LL;_104 };_104_104 std::vector<ll> values (n);_104 std::transform(cards.begin(), cards.end(), values.begin(), _104 [&] (char card) { _104 return face_value(encode(card));_104 });_104_104 ll to15 = count_sums_to_k(15, values);_104_104 ll pairs = std::accumulate(counts.begin(), counts.end(), 0LL,_104 [] (ll acc, const auto& p) {_104 return acc + n_choose_2(p.second);_104 });_104_104 ll runs = count_runs(counts);_104_104 std::cout << 2 * (to15 + pairs) + runs << '\n';_104}
If you found this solution helpful, consider leaving a star!