NOI 2015 荷马史诗 k叉哈夫曼树 堆优化

原址:http://blog.csdn.net/Quack_quack/article/details/46958413

题目大意:给出n个数字w[],代表n个字母出现的次数,给出k。要求用k进制的数字串si替换第i个字母,且替换之后要求替换后的文章无二义性(这里的无二义性是指对于任意的 1≤i,j≤n ,i≠j,都有: si不是sj的前缀),求替换后最短的文章的长度(长度len=sigma(w[i]*strlen(si)))和这种情况下最大的si的最小值。 
数据范围:n<=100000,k<=9 
分析: 
题目要求的限制条件很多,既要求替换后无二义性,又要求方案的最值,还有k进制的限制= =。没学过哈夫曼树的能想到就太厉害了,学过的(NOI这个级别肯定也学过)能想起来然后套模型就有思路了。 
我很弱,因此直接套了哈夫曼树的模型。 
哈夫曼树一般是二叉树,建树的方法就是每次选择两个权值(即出现次数)最小的点,删除这2个点,加入一个权值是这两个点之和的新点进去。并且使这被删除的2个点的父亲成为那个新点。 
编码的时候左支和右支一个是1一个是0,从根节点到叶子节点经过的边的1/0序列就是叶子节点对应的编码。 
然而这个题是k叉树,方法和上面类似,然而每次选择k个权值最小的点的时候容易让最后一次合并的时候的点不足k个。假设最初有n个点,最后有1个点,每次合并删除k个点又放进1个点。那么易得:(n-1)是(k-1)的倍数。如果(n-1)%(k-1)!=0,那么就要再放入(k-1-(n-1)%(k-1))个虚拟点,并且它们的权值为0,它们也参与求最小k个点。 
然而此题还要求si的最大值最小,因此我们让点代表一个二元组(val,dep),表示这个点的权值和点在树中的深度。在求最小k个点时,把val作为第一比较条件,如果val值相等,则把dep小的放在前面,这样在每次合并的时候,深度小的点都会被优先合并,保证了根到叶子的最长链的长度尽量小。 
所以,可以得到此题的算法: 
1)处理这n个权值,加入虚拟点,这些点的val值上文已经告诉,dep值为0,ans=0; 
2)每次取出前k小的点,求它们的val之和sum,求它们的dep的最大值d,那么放入的新点应该是(sum,d+1),把它放入原来的容器里面并要求有序,且ans+=sum(画一棵哈夫曼树,想想求文章长度的过程能这么实现的原理); 
3)当容器内只有一个点时,输出ans和这个点的dep值。 
这样的话正确性可以保证,但是注意容器的选择,不能直接数组模拟,会超时,可以用堆优化,我用的优先队列,和堆差不多,这样维护点的有序性变成O(log n)。求前k大的数也不用什么高级的数据结构,考虑k不大,就优先队列一个一个弹出,弹k次就可以了。 
最终时间复杂度是O(nlogn)。 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#define ll long long
using namespace std;
struct node{
    ll num,dep;
    node(){}
    node(ll a,ll b){num=a,dep=b;}
    bool operator < (const node &a) const
    {
        if(num==a.num)
            return a.dep<dep;
        return a.num<num;
    }
};
priority_queue<node>point;
ll k,n,ans,cnt;
int main()
{
    scanf("%I64d%I64d",&n,&k);
    for(int i=1;(ll)i<=n;++i)
    {
        ll temp;
        scanf("%I64d",&temp);
        point.push(node(temp,0));
    }
    cnt=n;
    if((n-1)%(k-1))cnt+=(k-1-(n-1)%(k-1));
    for(int i=n+1;(ll)i<=cnt;++i)
        point.push(node(0,0));
    while(cnt>1)
    {
        ll sum=0,len=0;
        for(int i=1;(ll)i<=k;++i)
        {
            sum+=point.top().num;
            len=max(len,point.top().dep);
            point.pop();
        }
        point.push(node(sum,len+1));
        cnt=cnt-k+1;
    }
    printf("%I64d\n",ans);
    printf("%I64d\n",point.top().dep);
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值