题意:
一部《荷马史诗》中有n种不同的单词,从1到n进行编号。其中第i种单词出现的总次数为wi。Allison 想要用k进制串si来替换第i种单词,使得其满足如下要求:
对于任意的 1 ≤ i, j ≤ n , i ≠ j ,都有:si不是sj的前缀。
如何选择si,才能使替换以后得到的新的《荷马史诗》长度最小。在确保总长度最小的情况下,Allison 还想知道最长的si的最短长度是多少?
思路:si不是sj的前缀,想到编码问题,构造k叉哈夫曼树
哈夫曼树的每个叶子节点有一个权值,最小化所有叶子节点*叶子到根的距离的乘积的和,贪心:令权值大的叶子节点尽可能深度小
这样构造,从根到每一个叶子节点的路径唯一,保证si不是sj的前缀
注意,这里的叶子节点的权值即为第i种单词出现的总次数为wi
实现:贪心用优先队列,一开始每个节点作为一棵深度为1,只有一个根节点的树,我们要做的就是每次从中取前k小的节点合并成一个节点,再插入森林中,新节点深度+1
因此优先队列维护一个二元组(节点权值,深度)
注意:若n个点不能被恰好k次后就剩一个节点,则需要补0节点
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int inf=0x7fffffff;
const int maxn=500005;
int n,k;
LL cnt=0,ans=0;
struct node{
LL w,h;
bool operator < (const node& b) const{
if(w!=b.w) return w>b.w;
return h>b.h;
}
};
priority_queue<node>q;
LL read(){
LL x=0,f=1; char c=getchar();
while(c>'9'||c<'0'){
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+c-'0';
c=getchar();
}
return x*f;
}
int main(){
n=read(); k=read();
for(int i=1;i<=n;i++){
LL x=read();
q.push((node){x,1});
}
if((n-1)%(k-1)!=0) cnt=k-1-(n-1)%(k-1);
for(int i=1;i<=cnt;i++){
q.push((node){0,1});
}
cnt+=n;
while(cnt>1){
LL tot=0,mx=0;
for(int i=1;i<=k;i++){
tot+=q.top().w;
mx=max(mx,q.top().h);
q.pop();
}
ans+=tot;
q.push((node){tot,mx+1});
cnt-=k-1;
}
printf("%lld\n%lld\n",ans,q.top().h-1);
return 0;
}