#include #include #include #include #include using namespace std; #define max(a, b) ((a) > (b) ? (a) : (b)) #define min(a, b) ((a) < (b) ? (a) : (b)) typedef unordered_map int_dict; // https://stackoverflow.com/a/53283994 struct VectorHasher { int operator()(const vector &V) const { int hash = V.size(); for(auto &i : V) { hash ^= i + 0x9e3779b9 + (hash << 6) + (hash >> 2); } return hash; } }; bool is_subseq(vector x, vector y) { auto it = y.begin(); for (int c : x) { while (*it != c) { it++; if (it == y.end()) return false; } } return true; } int main() { int n, weight_per_bucket; cin >> n >> weight_per_bucket; vector ws; for (int i = 0, w; i < n; ++i) { cin >> w; ws.push_back(w); } int hammers = 0; int_dict stones_left; for (int w : ws) { hammers += w / weight_per_bucket - (w % weight_per_bucket == 0 ? 1 : 0); if (w % weight_per_bucket != 0) stones_left[w % weight_per_bucket]++; } vector, VectorHasher> > splits_of_size = {{}, {{1}}}; for (int size = 2; size < weight_per_bucket * (weight_per_bucket - 1) + 1; size++) { unordered_set, VectorHasher> splits; for (int i = 1; i < min(weight_per_bucket, size + 1); i++) { if (size == i) splits.insert({i}); else for (auto s : splits_of_size[size - i]) { vector new_s(s); new_s.push_back(i); sort(new_s.begin(), new_s.end(), greater<>()); splits.insert(new_s); } } splits_of_size.push_back(splits); // TODO: as speedup, splits_of_size[8,16,24,...] should only contain tuples that cannot be split with lower round splits } int saved1 = 0, saved2 = 0, saved1a = 0, saved1b = 0; for (int size = weight_per_bucket; size < weight_per_bucket * weight_per_bucket; size += weight_per_bucket) { vector > splits2; copy(splits_of_size[size].begin(), splits_of_size[size].end(), back_inserter(splits2)); sort(splits2.begin(), splits2.end(), greater<>()); for (vector splits : splits2) { int_dict counter; for (int i : splits) counter[i]++; int m = 100000000; for (auto &[k, v] : counter) m = min(m, stones_left[k] / v); if (weight_per_bucket == 6 and counter == int_dict({{2, 3}}) or weight_per_bucket == 7 and splits == vector({5, 1, 1}) or weight_per_bucket == 8 and (splits == vector({4, 2, 2}) or splits == vector({3, 3, 2}) or counter == int_dict({{3, 3}, {7, 1}}))) saved1 += m; if (weight_per_bucket == 7 and splits == vector({4, 3}) or weight_per_bucket == 8 and splits == vector({4, 2, 2})) saved1a += m; if (weight_per_bucket == 8 and splits == vector({6, 1, 1})) saved1b += m; if (weight_per_bucket == 8 and counter == int_dict({{2, 4}})) saved2 += m; if (m) { for (auto &[k, v] : counter) stones_left[k] -= m * v; hammers += m * (size / weight_per_bucket - 1); if (weight_per_bucket == 6 and counter == int_dict({{5, 6}}) or weight_per_bucket == 7 and counter == int_dict({{3, 7}}) or weight_per_bucket == 8 and counter == int_dict({{7, 8}})) hammers -= min(m, saved2) * 2 + min(max(0, m - saved2), saved1); if (weight_per_bucket == 7 and counter == int_dict({{5, 7}}) or weight_per_bucket == 8 and (counter == int_dict({{3, 8}}) or counter == int_dict({{7, 5}, {5, 1}}))) hammers -= min(m, saved1a); if (weight_per_bucket == 8 and counter == int_dict({{5, 8}})) hammers -= min(m, saved1b); } } if (count_if(stones_left.begin(), stones_left.end(), [](auto entry){return entry.second == 0;}) == stones_left.size()) break; } cout << hammers << endl; }