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 <cctype>
#include <queue>
#define LL long long int
#define Max(a,b) a>b?a:b
using namespace std;

LL a ,n ,k ,cnt ,Temp ,Ans ,Maxlen ;
char b;

struct T
{
	LL Data ;
	int Len ;
	T(){}
	T(const LL _data,const int _len)
	{
		Data=_data,Len=_len;
	}
	bool operator < (const T &a) const
	{
		if(Data==a.Data)
			return a.Len<Len;
		return a.Data<Data;
	}
};
priority_queue<T> Huffman;//维护最小值

inline void GET(LL &n)
{
    n = 0;
    char c;
    do c = getchar();while(!isdigit(c));
    while(isdigit(c)){n = n * 10 + c - '0'; c = getchar();}
}

int main()
{
	freopen("epic.in","r",stdin);
	freopen("epic.out","w",stdout);
	GET(n),GET(k);
	for(int i = 1;i <= n; ++i)
	{
		GET(a);
		Huffman.push(T(a,1));
	}
	cnt = n;
	if((n - 1)%(k - 1))
		cnt += k - 1 - (n - 1)%(k - 1);
	for(int i = n+1; i <= cnt; ++i)//在叶子节点添0,方便编码
		Huffman.push(T(0,1));
	while(cnt > 1)
	{
		Temp = Maxlen = 0;
		for(int i = 1; i <= k; ++i)//取k个最小的
		{
			Temp += Huffman.top().Data;
			Maxlen = Max(Maxlen,Huffman.top().Len);
			Huffman.pop();
		}
		Ans += Temp;
		Huffman.push(T(Temp,Maxlen + 1));
		cnt = cnt - k + 1;
	}
	printf("%lld\n",Ans);
	printf("%d\n",Huffman.top().Len - 1);
	return 0;
}


  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值