算法设计——荷马史诗(K叉哈夫曼 贪心)

题目

题目链接:荷马史诗

题目描述:
追逐影子的人,自己就是影子。 ——荷马

Allison 最近迷上了文学。她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静地阅读她爱不释手的《荷马史诗》。但是由《奥德赛》和《伊利亚特》组成的鸿篇巨制《荷马史诗》实在是太长了,Allison 想通过一种编码方式使得它变得短一些。
一部《荷马史诗》中有 n 种不同的单词,从 1 到 n 进行编号。其中第 i 种单词出现的总次数为 wi。Allison 想要用 k 进制串 si 来替换第 i 种单词,使得其满足如下要求:
对于任意的 1≤i,j≤n,i≠j,都有:si 不是 sj 的前缀。
现在 Allison 想要知道,如何选择 si,才能使替换以后得到的新的《荷马史诗》长度最小。在确保总长度最小的情况下,Allison 还想知道最长的 si 的最短长度是多少?
一个字符串被称为 k 进制字符串,当且仅当它的每个字符是 0 到 k−1 之间(包括 0 和 k−1)的整数。
字符串 Str1 被称为字符串 Str2 的前缀,当且仅当:存在 1≤t≤m,使得 Str1=Str2[1…t]。其中,m 是字符串 Str2 的长度,Str2[1…t] 表示 Str2 的前 t 个字符组成的字符串。

输入
输入文件的第 1 行包含 2 个正整数 n,k,中间用单个空格隔开,表示共有 n 种单词,需要使用 k 进制字符串进行替换。

接下来 n 行,第 i+1 行包含 1 个非负整数 wi,表示第 i 种单词的出现次数。

输出
输出文件包括 2 行。

第 1 行输出 1 个整数,为《荷马史诗》经过重新编码以后的最短长度。
第 2 行输出 1 个整数,为保证最短总长度的情况下,最长字符串 si 的最短长度。

Hint
用 X(k) 表示 X 是以 k 进制表示的字符串。

一种最优方案:令 00(2) 替换第 1 种单词,01(2) 替换第 2 种单词,10(2) 替换第 3 种单词,11(2) 替换第 4 种单词。在这种方案下,编码以后的最短长度为:

1×2+1×2+2×2+2×2=12

最长字符串 si 的长度为 2。

一种非最优方案:令 000(2) 替换第 1 种单词,001(2) 替换第 2 种单词,01(2) 替换第 3 种单词,1(2) 替换第 4 种单词。在这种方案下,编码以后的最短长度为:

1×3+1×3+2×2+2×1=12

最长字符串 si 的长度为 3。与最优方案相比,文章的长度相同,但是最长字符串的长度更长一些。

对于所有数据,保证 2≤n≤100000,2≤k≤9。

选手请注意使用 64 位整数进行输入输出、存储和计算。

分析

这个题由于老师给的时候就标注了K叉哈夫曼树
所以避免了很多弯路
如果想不到哈夫曼树,可能就比较难搞
关于哈夫曼树,就不多加解释了

我们已经处理过2叉的哈夫曼树了,那么其实K叉树也是同样的处理方法。
但是这个题有一个深度要求,我们看题就会发现深度这个要求,编码短就要求深度小,所以又怎么处理呢

说实话,我不会。
不会也得会
于是去看了大佬们的解题思路
用优先队列,把深度作为第二关键字
于是C++学的不咋滴的我又开始优先队列咋用
我只用过队列模板啊亲

所以先讲下整体思路:
将每个元素放入优先队列,优先队列会根据优先级进行出队,那么我们就能保证每次出队的是权重最小的。
那么深度又怎么处理呢?
优先队列可设置第二关键字,即在权重相同的情况下判断深度。这样深度也解决了。

优先队列
我自己也不是很会,看了这个Archger的优先队列博客,我觉得还挺好的,不会优先队列的娃可以先看一下:优先队列详解

好了,假装自己会了优先队列,那么问题解决了

上代码

代码实现


#include
#include
#include
#include
using namespace std;
int main()
{
	priority_queue<pair<long long int,int> >q;//一定要用空格隔开否则会判断为右移报错
	//优先队列,pair用法是先比较第一个参数长度,相同的话比较第二个参数深度 
	int n,m,i;
	long long int x,ans=0;
	scanf("%d %d",&n,&m);//n个结点,m个叉 
	for (i=0;i<n;i++){
		scanf("%lld",&x);
		q.push(make_pair(-x,-1));//这里优先队列默认优先选大的,可以用结构体然后自定义
		//但这里使用的pair就将关键字取负值 ,就可以直接使用 
	}
	while((n-1)%(m-1)>0){//判断根节点能不能构成m叉树 
		n++;
		q.push(make_pair(0,-1));//不能构成就补0结点 
	}
	while(q.size()>1){//队列中最后剩的元素就是我们要求的值 
		long long int w=0,d=0;
		for(i=0;i<m;i++){//每次取优先值前m个 
			w-=q.top().first;//因为是负值所以减负负得正,所以这里w是正值,如果用的自定义加就行了 
			if(d<-q.top().second){//深度要么加深要么不变 
				d=-q.top().second;
			}
			q.pop();
		}
		ans+=w;// 直接加上就可以了 
		q.push(make_pair(-w,-d-1));//依旧负值进队 
	}
	printf("%lld\n%d\n",ans,-q.top().second-1);
	return 0;
}

还可以自定义写优先队,这样就不用取负值了,但我懒,大家可以自己找找。

你可能感兴趣的:(算法设计,贪心算法)