题面:
一部《荷马史诗》中有 $n$ 种不同的单词,从 $1$ 到 $n$ 进行编号。其中第 $i$ 种单词出现的总次数为 $w_i$。用 $k$ 进制串 $s_i$ 来替换第 $i$ 种单词,使得其满足如下要求:
1. 对于任意的 $1≤i,j≤n$,$i≠j$,都有:$s_i$ 不是 $s_j$ 的前缀。
2. 替换以后得到的新的《荷马史诗》长度最小。
3. 在确保总长度最小的情况下,求出最长的 $s_i$ 的最短长度。
AcWing 149. 荷马史诗 - Tag:哈夫曼树、Trie树、贪心、堆
本题面对的两个问题:
- 最小权值和
- 最小化深度
最小权值和
- $k$ 进制编码的情况下,使用的不再是二叉哈夫曼树,而是 $k$ 叉堆。
- $k$ 叉堆会存在的问题:只能形成完全 $k$ 叉堆的特殊情况
- 形成满 $k$ 叉堆的条件:
$n-m(k-1)=1$
⇒ $n-1=m(k-1)$
⇒ $n-1$ 为 $k-1$ 的倍数
⇒ $(n-1)\%(k-1)=0$ - 解决方法:
- 法一:特殊处理首次合并,首次合并为 $n-m(k-1)$ 堆,剩下合并为 $k$ 堆。
- 法二:为最后一层补满空结点,以让其余结点权值和最小化。
- 形成满 $k$ 叉堆的条件:
最小化深度
- 在建树时,新节点优先成为深度较小的结点的孩子。
- 如果多个点的权值相同时,优先考虑高度较低(已合并次数最少)的树。
- Q:为什么代码中使用
max(u, h.top().second)
而不是min
?
A:因为建树的过程是从下往上的。
- Q:为什么代码中使用
拓展:Huffman树与Trie树
常见应用场景
深度与权重:Huffman树
求公共前缀:Trie树
本题为何适用
本题中要求对于任意的 $1≤i,j≤n$,$i≠j$,都有 $s_i$ 不是 $s_j$ 的前缀。
应用Trie树:叶子结点上的字母必然互不相同;
应用Huffman树:每层至多产生一个父结点。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, int> PLI;
const int N = 1e5 + 10;
int n, k;
LL x;
priority_queue<PLI, vector<PLI>, greater<PLI>> h;
int main()
{
cin >> n >> k;
for (int i = 0; i < n; i++) {
cin >> x;
h.push({ x,0 });
}
while ((n - 1) % (k - 1)) {
h.push({ 0,0 }); //加入空结点
n++; //更新结点数
}
LL res = 0;
while (h.size() > 1) {
LL sum = 0;
int u = 0; //新节点的高度
for (int i = 0; i < k; i++) {
sum += h.top().first; //该层的所有结点权值和
u = max(u, h.top().second); //同权值时优先深度最小
h.pop();
}
res += sum;
h.push({ sum,u + 1 });
}
cout << res << endl;
cout << h.top().second;
}