【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);
}
posted @ 2020-05-23 00:54  Hyx'  阅读(133)  评论(0)    收藏  举报