文章目录
前言
发现很多简单,但比较偏的算法我都不懂,所以现在开始按随机跳的题来浅浅了解一下这些东西。
正文
例题:P2168 [NOI2015] 荷马史诗
随机跳到这一题,感觉是贪心,但没有具体思路,看了题解发现是个叫 H u f f m a n Huffman Huffman 树 的东西,去了解了一下。
算法概述
也可以说是一个数据结构。
它可以用一句话来描述:带权路径长度最短的二(多)叉树。
一棵树的带权路径长度是 每个节点的权值*这个点到根的距离之和 。
二叉Huffman树的构造
假设现在有 n n n 个有一定权值的点,那么如何构造出一棵二叉 H u f f m a n Huffman Huffman 树?
方法是这样的:
先默认每个点都是一棵权值为它自己,深度为 1 1 1 的树。
每次选择权值最小,深度较小的两棵树,将其合并,合并的方法是将这 2 2 2 棵树的根放在同一个新建根节点下,显然,该树的权值为两棵树的权值和,深度为两棵树中深度较大的再 + 1 +1 +1 。
重复执行,直到只剩一棵树了,这就是最后的 H u f f m a n Huffman Huffman 树。
使用优先队列维护。
怎么理解这个过程?可以把每棵树看成一个整体,那么显然优先合并的树,其在最终的深度会尽量的比后合并的树大一点,那么自然是权值小一点的树放在深度较大的地方比较好。
多叉Huffman树的构造
有了上面的过程,我们可以类比得到多叉树的维护方式:每次选前 k k k 个权值较小的。
不过这是错误的,原因是如果我们这样做,可能会出现这样的情况:前面合并的很开心,最后一步却发现只剩下不到 k k k 棵树了,那怎么办?强制合并吗?那显然不是最优的,因为任何一个点补充上去都会使情况更优。
所以有了一个神奇的方法:加 0 0 0 。
首先,易证不管加多少个 0 0 0 ,只要足够,对答案是没有影响的,因为多余的 0 0 0 一定会被放在叶子结点上。
那么多少个 0 0 0 就足够了呢?我们补 0 0 0 的原因是防止最后只剩不到 k k k 棵树,所以补到 ( n − 1 ) m o d ( k − 1 ) ! = 0 (n-1)\bmod(k-1)!=0 (n−1)mod(k−1)!=0 时即可。
引申:Huffman编码
初赛好像考过。
问题:给一个字符串,试给每个字符确定一个唯一的 01 01 01 串,使替换后的字符串长度最短。
其实就是将每个字符的出现次数定义为它的权值,求一个二叉 H u f f m a n Huffman Huffman 树即可,将每个点去左儿子的边设成 0 0 0 ,去右儿子的设成 1 1 1 ,最后每个字符的 01 01 01 串就是它到根的路径。
本题解答
就是求一个 k k k 进制的 H u f f m a n Huffman Huffman 编码。
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read() {
int s=0,m=0;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-')m=1;ch=getchar();}
while( isdigit(ch)) s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return m?-s:s;
}
struct QWQ{
int dep,w;
bool operator <(const QWQ &x) const{
if(w==x.w)
return dep>x.dep;
return w>x.w;
}
};
priority_queue<QWQ> pq;
int n,k,ans;
signed main() {
cin>>n>>k;
for(int i=1;i<=n;i++)
pq.push({1,read()});
while((pq.size()-1)%(k-1))
pq.push({1,0});
while(pq.size()>=k) {
QWQ t={0,0};
for(int i=1;i<=k;i++) {
QWQ f=pq.top();pq.pop();
t.dep=max(t.dep,f.dep),t.w+=f.w;
}
ans+=t.w,t.dep++;
pq.push(t);
}
printf("%lld\n%lld",ans,pq.top().dep-1);
return 0;
}