乐师理工ACM集训-哈夫曼树与哈夫曼编码

HDU2527 Safe Or Unsafe【哈夫曼】

传送门:HDU2527 Safe Or Unsafe

解题思路

  题目说编码方式是哈夫曼编码(Huffman Coding),并定义一个字母的权值等于该字母在字符串中出现的频率。要求判断哈夫曼编码值和给定的安全值大小(此处说的编码值即为哈夫曼的带权路径长度)。直接统计字母出现次数,求哈夫曼树带权路径长度,并比较带权路径长度和安全值大小即可。

AC代码

#include<stdio.h>
#include<vector>
#include<map>
#include<queue>
#include<string.h>
using namespace std;
const int MAXN=6e5+5;
char s[MAXN];
int cnt[30]; // 用于统计字母出现次数
priority_queue<int,vector<int>,greater<int> > q;
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int n;
		scanf("%d%s",&n,&s);
		int len=strlen(s);
		for(int i=0;i<len;i++)
			cnt[s[i]-'a']++;
		for(int i=0;i<26;i++)
			if(cnt[i])
				q.push(cnt[i]);
		int sum=0;
		while(q.size()>1)
		{
			int t1=q.top();
			q.pop();
			int t2=q.top();
			q.pop();
			sum+=t1+t2;
			q.push(t1+t2);
		}
		if(sum==0) // 特判只有一个字母的情况
			sum=q.top();
		if(sum<=n)
			printf("yes\n");
		else
			printf("no\n");
		while(!q.empty()) // 清空队列
			q.pop();
		memset(cnt,0,sizeof(cnt)); // 清除统计
	}
	return 0;
}

HDU1053 Entropy【哈夫曼】

传送门:HDU1053 Entropy

题目大意

  给定只包含大写字母和‘_'的字符串(一个一行,输入到END结束)。问:每个字母8位,这个字符串长多少位?若采用哈夫曼编码,编码长度多少位?压缩率为多少(保留1位小数)?

解题思路

  要求哈夫曼编码长度,直接求带权路径长度即可,权值为字符出现次数。(若要求实际编码,需构建哈夫曼树,有兴趣的可以了解一下。更多时候使用的是哈夫曼的思想)

AC代码

#include<stdio.h>
#include<vector>
#include<map>
#include<queue>
#include<string.h>
using namespace std;
typedef long long ll;
const int MAXN=6e5+5;
char s[MAXN];
int cnt[35];
priority_queue<int,vector<int>,greater<int> > q;
int main()
{
	while(scanf("%s",s)&&strcmp(s,"END"))// 读到 END为止
	{
		int len=strlen(s);
		for(int i=0;i<len;i++)
			cnt[s[i]-'A']++; // '_'ascll码为 95,'A'为 65,95-65=30
		for(int i=0;i<31;i++)
			if(cnt[i])
				q.push(cnt[i]);
		int sum=0;
		while(q.size()>1)
		{
			int t1=q.top();
			q.pop();
			int t2=q.top();
			q.pop();
			sum+=t1+t2;
			q.push(t1+t2);
		} 
		if(sum==0) // 特判只有一个字符的情况
			sum=q.top();
		printf("%d %d %.1lf\n",len*8,sum,len*8*1.0/sum);
		while(!q.empty()) //清空队列
			q.pop();
		memset(cnt,0,sizeof(cnt)); // 清空计数数组
	}
	return 0;
}

POJ3253 Fence Repair【贪心/哈夫曼+反向思维】

传送门:POJ3253 Fence Repair

题目大意

  将一块木板分割成N块,每块长度为L[i]。每切割一次,代价为被分开的两块木板长度总和。例如,将一块木板切割成8、5、8的三块木板。首先将木板切割成13、8时,代价为21,再将长度为13的木板切割成长度5、8时,代价为13。于是合计代价为34。
  问:按题目要求将木板切割出N块,最小的代价是多少?

解题思路

  反向还原,将最终被分割的每块木板还原为完整的一根木块,每次还原的代价就是两块木板拼接后的总长度。要使得还原代价最小,每次拼接的木板长度应该是最小的。每次拼接后,后面再拼接还需要计算当前木块的长度。所以可以转化为求哈夫曼带权路径长度的问题。
  因为(1 ≤ N ≤ 20000) ,(1 ≤ L[i] ≤ 50000),最多20000块木板,每块最长为50000,累积求和会超出int,所以要用long long存储。

AC代码

#include<stdio.h>
#include<vector>
#include<map>
#include<queue>
#include<string.h>
using namespace std;
typedef long long ll;
priority_queue<ll,vector<ll>,greater<ll> > q;
int main()
{
	int n;
	scanf("%d",&n);
	while(n--)
	{
		int len;
		scanf("%d",&len);
		q.push(len);
	}
    if(n==1) // 一块木板,无需切割
	{
		puts("0");
		return 0;
	}
	ll sum=0;
	while(q.size()>1)
	{
		ll t1=q.top();
		q.pop();
		ll t2=q.top();
		q.pop();
		sum+=t1+t2; // 代价
		q.push(t1+t2); // 后面拼接需再次计算	
	}
	printf("%lld\n",sum);
	return 0;
}

Codeforces884D Boxes And Balls【三叉哈夫曼+反向思维】

传送门:Codeforces884D Boxes And Balls

题目大意

  给定一个n。然后是n个不同颜色的球的数目。第 i 种颜色的球最终要放入第 i 个盒子中。开始所有的球都在第一个盒子里。每次可以进行以下两步操作:
1、把某一个非空盒子中的球全部拿出来。
2、选个k个空盒子,将拿出来的球分为k组,分别放入k个空盒子。(k可以取值为2或3)
每次操作的代价为拿出的球的数量。
问:将n种不同颜色的球放入n个对应的盒子花费最小是多少?

解题思路

  开始想到的是每次贪心前两大的数,但这不能构成最优解。这个思路对于样例

6
1 4 4 4 4 4

处理的顺序是21->13 4 4->5 4 4 4 4->1 4 4 4 4 4,ans=21+13+5=39.
但是如果我们按照21->9 8 4->9 4 4 4->1 4 4 4 4 4的顺序处理的话,ans=21+8+9=38.
  逆向思考,把n个盒子里的球放回第一个盒子,每次的操作代价是k个盒子球数量的和。那么就转化为了构造哈夫曼树。k可能为2或3,直接合并3个的代价更少,所以我们要使得k=3。当奇数时,能构成一个完整的三叉哈夫曼树,当偶数时,添一个0结点,在不影响结果的情况下强行构成三叉哈夫曼树。
  注意:(1 ≤ n ≤ 200000) , (1 ≤ ai ≤ 109),要使用long long。

WA代码

#include<stdio.h>
#include<vector>
#include<map>
#include<queue>
#include<string.h>
using namespace std;
typedef pair<int,int> P;
typedef long long ll;
const int MAXN=6e5+5;
priority_queue<ll,vector<ll>,less<ll> > q;
int main()
{
	int n;
	scanf("%d",&n);
	ll sum=0;
	for(int i=0;i<n;i++)
	{
		int num;
		scanf("%d",&num);
		q.push(num);
		sum+=num;
	}
	ll ans=sum;
	while(q.size()>3)
	{
		ll t1=q.top();
		q.pop();
		ll t2=q.top();
		q.pop();
		sum-=(t1+t2); // 每次确定两个最大的
		ans+=sum;
	}
	while(!q.empty())
		q.pop();
	printf("%lld\n",ans);
	return 0;
}

AC代码

#include<stdio.h>
#include<vector>
#include<map>
#include<queue>
#include<string.h>
using namespace std;
typedef long long ll;
priority_queue<ll,vector<ll>,greater<ll> > q;
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		int num;
		scanf("%d",&num);
		q.push(num);
	}
	if(n==1) // 只有一个盒子,无需操作
	{
		puts("0");
		return 0;
	}
	if((n&1)==0) // 当 n为偶数,补 0 凑成奇数使得可以每次都取三个
		q.push(0);
	ll sum=0;
	while(q.size()>1)
	{
		ll t1=q.top();
		q.pop();
		ll t2=q.top();
		q.pop();
		ll t3=q.top();
		q.pop();
		ll temp=t1+t2+t3;
		q.push(temp);
		sum+=temp;
	}
	printf("%lld\n",sum);
	return 0;
}

思考:为什么权值为字符出现次数时哈夫曼编码长度等于带权路径长度

解释:

因为:
哈夫曼带权路径长度(WPL) = SUM(叶结点的权值 × 该结点到根结点的路径长度)
哈夫曼编码长度 = SUM(字符出现次数 × 该字符编码长度)
又:
字符编码长度 == 字符到根结点的路径长度
叶节点权值 == 某字符出现次数
所以:
哈夫曼带权路径长度(WPL) == 哈夫曼编码长度

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值