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...

  1. ... count all the ways you can sum to 15.
    This is solveable in n^2 steps using dynamic programming. See the function count_sums_to_k for implementation details.
  2. ... count all pairs of identical values.
    This is given by n choose 2 (n(n-1)/2), with n being the frequency of each value in the array.
  3. ... 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
_104
using 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
* */
_104
ll 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
* */
_104
ll 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
**/
_104
ll n_choose_2(ll n) {
_104
return (n*n - n)/2;
_104
}
_104
_104
int 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!