【k叉哈弗曼树】 荷马史诗
传送门
题意
\(n\)种单词,第 \(i\) 种单词出现的总次数为 \(w_{i}\)
用\(k\)进制串\(s_{i}\)来代替第 \(i\) 种单词,使其满足以下要求,对于任意的\(1<= i , j <=n , i != j\) 都存在\(s_{i}\)不是\(s_{j}\)的前缀,
求出重新编码以后的最短长度,和保证最短长度的情况下最长字符串\(s_{i}\)的最短长度
数据范围
\(\begin{array}{l}2 \leq n \leq 100000 \\ 2 \leq k \leq 9 \\ 1 \leq w_{i} \leq 10^{12}\end{array}\)
题解
结合\(Trie\)树,因为不能存在一个\(k\)进制串为另一个的前缀,所以\(Trie\)的叶子节点即为每个单词的终点
求的是最短的带权路径之和和该哈夫曼树的高度
- 权值代表的是当前单词的频数,那么频数大的单词一定短才能保证总和最大
每次都将\(k\)个点合并成\(1\)个,即减少了\(k-1\)个,最终要将\(n\)个点合并为1个,减少了\(n-1\),如果\((n-1)\; mod\; ( k-1)\neq 0\),即最后合并的时候的点不足\(k\)个,
加入值为\(0\)的空节点,将权值大的挤到上面,这样每次合并\(k\)个最后一定是最优的。
-
至于最小的哈夫曼树高度,额外维护一个信息当前合并的次数,即在哈夫曼树中的深度,
-
在对于权值相同的点先合并深度小的,最后的根节点的最大深度一定最小,深度指的边,所以要从\(0\)开始
Code
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=a;i<n;i++)
#define fi first
#define se second
#define ll long long
typedef pair<long long,int> pli;
const int N=1e5+10;
priority_queue<pli,vector<pli>,greater<pli>>heap;
int n,k;
int main()
{
scanf("%d%d",&n,&k);
rep(i,0,n)
{
ll x;
scanf("%lld",&x);
heap.push({x,0});
}
while((n-1) % (k-1)) heap.push({1ll * 0,0}),n++;
ll ans=0;
while(heap.size() > 1)
{
ll res=0;
int h=0;
rep(i,0,k)
{
auto t=heap.top();
heap.pop();
res+=t.fi;
h=max(h,t.se);
}
ans+=res;
heap.push({res,h+1});
}
printf("%lld\n%lld\n",ans,heap.top().se);
}

浙公网安备 33010602011771号