hi
晚上好啊
今天讲了基本算法(基本听不懂的算法)——
~~又被学长虐了呢~~~~~
终于在下课前学长讲了一道能写得出来的题,
为了庆祝,
我们来写一篇博客。
$Huffman$树
首先,我们来考虑这样一个问题:
构造一棵包含 $n$ 个叶子节点的 $k$ 叉树,其中第 $i$ 个叶子节点带有权值 $w_i$,要求最小化 $\sum w_i * l_i$,其中 $l_i$ 表示第 $i$ 个叶子节点到根节点的距离。
该问题的解被称为 $k$ 叉 $Huffman$ 树(哈夫曼树)
为了最小化 $\sum w_i * l_i$,应该让权值大的叶子节点的深度尽量小(这应该十分显然了)。
当 $k==2$ 时,我们就可以维护一个小根堆(出门左转合并果子)
对于 $k>2 $的 $k$ 叉 $Huffman$ 树的求解,直观的想法是在贪心的基础上,改为每次取出最小的 $k$ 个权值。
然鹅,经过仔细思考,可以发现:
如果在执行最后一轮循环时,堆的大小在 $2-k$ 之间(也就是说,不足以取出 $k$ 个),那么整棵$Huffman$ 树的根的子节点数就小于 $k$。这显然不是最优解——我们取 $Huffman$ 树中任意一个深度最大的节点,把他改为树根的子节点,就会使 $\sum w_i * l_i$ 变小。
因此,我们在执行贪心步骤之前,首先要补充一些额外的权值为 $0$ 的叶节点,使得叶节点的个数 $n$ 满足$(n-1)mod(k-1)==0$.
也就是说,我们让子节点不足k的情况发生在最底层,而不是在根结点处,在 $(n-1)mod(k-1)==0$ 时,执行“每次从堆中取出最小的 $k$ 个权值”的贪心思路是正确的。
那么,我们现在来看 $p2168$ 荷马史诗
其实看了上面的,题目自然就很显然了
啊淦为什么我也这么爱说显然了
那我们把单词出现的次数作为权值,然后直接构造 $k$ 叉 $Huffman$ 树,对于每个节点的第 $k$ 个分支,分别标记为 $0-k-1$;
下面我们需要一点跟 $trie$ 有关的东西
如果把这个 $Huffman $树看作是一颗 $trie$,那么就得到了总长度最小的编码方式——单词 $i$ 的编码就是从根节点到叶节点 $i$ 上各个字符相连(如果没学过 $trie$ 建议去看一看,反正也不难,一举两得)。
那么如何保证一个单词不是另一个单词的前缀呢?
我们只需要保证,单词的结尾是叶子节点就好了啊(这是 $trie $的性质,如果一个串是另一个串的前缀的话,那么这个串的结尾一定还可以向后延伸,也就是可以经变长变为另一个更长的单词。)
如何保证 $s_i$ 最短呢?
只需要在构造 $Huffman$ 树的时候,对于权值相同的节,我们优先合并当前深度小(也就是合并次数小的)即可。
CODE:
|