[NOI2015]荷马史诗

题意

  给定n个单词的出现次数,将这n个单词用nk进制字符串代替这n个单词,要求任意一个字符串不是另一个字符串的前缀
  求出一种方案使得替换后的总长度最小,在总长度最小的前提下,尽量使最长字符串的长度变小

题解

  如果是以前学过哈夫曼树的人,应该能够一眼看出这就是k进制哈夫曼树
  因为当(n1)%(k1)!=0时,哈夫曼树会不满,所以我们加入k1(n1)%(k1)个权为0的点以方便解题
  使用哈夫曼树能够轻易的满足第一个条件:总长度最小。为了使第二个条件也满足,我们考虑将一个点变成一个二元组:(点权,深度)(所有点初始深度为0),先按点权从小到大,点券相同就按深度从小到大
  然后使用优先队列存点,在合并时,新节点的深度就是选出的k个点的深度的最大值+1,然后将新节点丢入队列中,如此反复,知道队列中只剩下一个元素时停止
  第二问的答案很好求,最长字符串的长度就是最后剩下的点的深度;考虑怎么统计总长度:在合并的过程中(一开始将初始点兑入队列中不算),每往队列中丢入一个元素,ans+=sum(sum为该元素的点权),这样的话,第i层的点的点权会被加上i次,就相当于是出现次数*字符串长度,因此这样得到的答案就是总长度

复杂度

O(nlogn)

代码

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<queue>
#define Rint register int
#define Lint long long int
using namespace std;
const int N=100010;
struct node
{
    Lint val;
    int dep;
    bool operator < (const node &a) const
    {
        return val==a.val ? dep>a.dep:val>a.val;
    }
};
Lint w[N],ans;
int n,k,l;
priority_queue<node> q;
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)   scanf("%lld",&w[i]);
    sort( w+1,w+n+1 );
    for(int i=1;i<=n;i++)   q.push( (node){ w[i],0 } );
    while( (n-1)%(k-1) )   q.push( (node){ 0,0 } ),n++;
    while( q.size()>1 )
    {
        Lint sum=0;
        int num=1,mx=0;
        while( num<=k )
        {
            sum+=q.top().val;
            num++,mx=max( mx,q.top().dep );
            q.pop();
        }
        ans+=sum;
        q.push( (node){ sum,mx+1 } );
    }
    printf("%lld\n",ans);
    printf("%d\n",q.top().dep);
    return 0;
}
posted @ 2018-02-19 21:34  清疚  阅读(97)  评论(0)    收藏  举报