【题目链接】
【思路要点】
- 如果将一个合法的分配方案放到字典树上,那么每一个关键节点均为叶子结点。
- 考虑如何使答案最小化,数据范围较大,考虑贪心。
- 我们注意到一系列具有相同父亲的叶子结点可以被看作一个大小为它们大小总和的叶子结点。
- 我们称之为一次合并,我们不断地执行合并,最后就会得到字典树上的根节点。
- 由于我们希望使最小化答案,而一个叶子结点被合并的次数越多,其大小在答案中被计算的次数也越多,所以在执行合并时,我们会尽量选取大小较小的叶子进行合并。
- 具体实现时可以用一个堆来实现这个过程,注意第一次合并的叶子的数量可能不是\(K\),而是\((N-1)\%(K-1)+1\)。
- 第二问只需要在满足以上限制的情况下选取深度尽量小的叶子即可。
- 时间复杂度\(O(NLogN)\)。
【代码】
#include<bits/stdc++.h> using namespace std; const int MAXN = 100005; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct info {long long size; int depth; }; bool operator < (info a, info b) { if (a.size == b.size) return a.depth > b.depth; else return a.size > b.size; } priority_queue <info> Heap; long long ans; int n, m; void merge(int cnt) { if (cnt == 1) return; long long now = 0; int depth = 0; for (int i = 1; i <= cnt; i++) { info tmp = Heap.top(); Heap.pop(); now += tmp.size; chkmax(depth, tmp.depth); } ans += now; Heap.push((info) {now, depth + 1}); } int main() { read(n), read(m); for (int i = 1; i <= n; i++) { long long x; read(x); Heap.push((info) {x, 0}); } merge((n - 1) % (m - 1) + 1); while (Heap.size() != 1) merge(m); writeln(ans); writeln(Heap.top().depth); return 0; }