hihocoder #1111 : Huffman编码

时间限制: 30000ms
单点时限: 3000ms
内存限制: 256MB

描述

给定一个大小为n的字符集Σ中每个字符出现的频数,求不同的Huffman编码的数量模109 + 7。

一个编码是一个从Σ到01字符串的函数。 一个Huffman编码是一个由以下过程生成的编码:

初始时每个字符为一个集合。初始时所有字符对应到空字符串。一个集合S的频数w(S)定义为S内所有字符的频数的和。 不断进行以下操作直到只剩下一个集合:

选择两个不同的集合A和B使得w(A) + w(B)最小。将A内所有字符对应到的字符串前端加上'0'。将B内所有字符对应到的字符串前端加上'1'。添加一个集合C,为A和B的并。删除A和B。

两个编码不同当且仅当存在一个字符对应到的01字符串不同。

输入

第一行,T,测试点个数。下面T个测试点。

对于每个测试点,第一行,n,为字符集Σ的大小。 第二行,n个整数w1, ..., wn. 其中wi为第i个字符的频数。

1 ≤ n ≤ 106. 所有n的和 ≤ 106. 1 ≤ wi ≤ 106.

输出

T行。每行为对应测试点的答案。

样例输入
5
1
1
2
1 1
3
1 1 1
4
1 1 1 1
4
1 1 2 2
样例输出
1
2
12
24
24
解题思路

首先统计相同频数出现的次数。这里用map来存储  map[频数]=频数出现次数。

然后从频数小的开始考虑。

1.如果最小频数的出现次数n是偶数。

   则类似于考虑左右字数交换的情况下,n个不同元素进行划分,每块里面都只有2个元素的划分数。比如{1,2,3,4}的不考虑分块内的顺序划分有三种{(1,2),(3,4)},{(1,3),(2,4)},{(1,4),(2,3)},在考虑分块顺序的情况下应该是3*(2^3)。

 依次考虑2个,4个,6个,8个不同元素不考虑顺序的划分,发现存在递推关系。

   比如对于6个元素,先确定(1,2)的话,剩下的4个元素的划分实际上是4个不同元素的划分,然后再依次确定(1,3)、(1,4)、(1,5)、(1,6)的四个元素划分。所以6个元素的划分实际上为(6-1)*(4个不同元素不考虑顺序划分数)。

递推公式为

所以

再考虑每个划分内可以互换位置,则在这种情况下结果是

然后,把map[最小频数]擦除,map[最小频数*2]+=n/2

2.如果最小频数的出现次数n是奇数且大于1

则从里面里面挑出一个单独的,而其他两两组队的清楚共有C(n-1,n)=C(1,n)

此时参与配对的偶数个数可用上面的方法来求。

然后把map[最小频数*2]+=(n-1)/2,map[最小频数]赋值为1。

3.如果最小频数出现次数是1而且map的size为1就结束了。

4如果最小频数出现次数是1,map的size大于1

 就要把改最小频数与第二小频数凑对。凑对共有map[第二小频数]种情况。

然后把map[最小频数+第二小频数]++,map[最小频数]擦除,map[第二小频数]--


源码

// 1111 Huffman编码.cpp : 定义控制台应用程序的入口点。
//
//递归算法

#include "stdafx.h"
#include <iostream>
#include <vector>
#include <deque>
#include <map>
#include <algorithm>
using namespace std;

const int mode = 1000000007;

int same_num_count(int a)    //a为偶数
{
	int b = a - 1;
	int res = 1;
	while (b != 1)
	{
		res = b*res % mode;
		b = b - 2;
	}
	return res;
}

vector<int> insert_new(vector<int> &w, int a)
{
	if (w.size() != 0)
	{
		int flag = 1;
		for (int i = 0; i < w.size(); i++)
		{
			if (w[i]>a)
			{
				flag = 1;
				w.insert(w.begin() + i, a);
				break;
			}
			if (w[i] < a)
				flag = 0;
		}
		if (flag==0)
			w.push_back(a);
	}
	else
		w.push_back(a);
	return w;
}

void comput(vector<int> &w, map<int, int>&count, int &ans)
{
	if (count[w[0]] % 2 == 0)
	{
		ans = ans*same_num_count(count[w[0]]) % mode;
		ans = ans*pow(2, count[w[0]] / 2);
		ans = ans%mode;
		int tmp = 2 * w[0];
		if (count.find(tmp) == count.end())
		{ 
			count.insert(pair<int, int>(tmp, count[w[0]] / 2));
			vector<int>::iterator iter = w.begin();
			w.erase(iter);
			w = insert_new(w, tmp);
		}
		else
		{
			count[tmp] += count[w[0]] / 2;
			vector<int>::iterator iter = w.begin();
			w.erase(iter);
		}
		//cout << ans << endl;
	}
	else if (count[w[0]] % 2 != 0 && count[w[0]] != 1)
	{
		ans = count[w[0]] * ans * same_num_count(count[w[0]] - 1) % mode;
		ans = ans * pow(2, (count[w[0]] - 1) / 2);
		ans = ans%mode;
		int tmp = 2 * w[0];
		
		if (count.find(tmp) == count.end())
		{
			count.insert(pair<int, int>(tmp, (count[w[0]] - 1) / 2));
			w = insert_new(w, tmp);
		}
		else
			count[tmp] += (count[w[0]] - 1) / 2;
		count[w[0]] = 1;
		//cout << ans << endl;
	}
	else
	{
		//cout << w.size() << endl;
		ans = ans * 2 * count[w[1]];
		ans = ans%mode;
		count.erase(w[0]);
		int tmp1 = w[0] + w[1];
		//cout << tmp1 << endl;
		vector<int>::iterator iter = w.begin();
		w.erase(iter);
		if (count[w[0]] == 1)
		{
			count.erase(w[0]);
			iter = w.begin();
			w.erase(iter);			
		}
		else
			count[w[0]]--;
		
		if (count.find(tmp1) == count.end())
		{
			
			count.insert(pair<int, int>(tmp1, 1));
			w = insert_new(w, tmp1);
		}
		else
			count[tmp1]++;
		//cout << w.size() << endl;
		//cout << ans << endl;
	}
}

int main()
{
	int t;
	cin >> t;
	while (t--)
	{
		int n;
		cin >> n;
		vector<int>w;
		map<int, int>count;
		for (int i = 1; i <= n; i++)
		{
			int a;
			cin >> a;
			if (count.find(a) == count.end() || count.size() == 0)
			{
				count.insert(pair<int, int>(a, 1));
				w.push_back(a);
			}
			else
				count[a]++;
		}

		sort(w.begin(), w.end());
		int ans = 1;
		while (w.size() > 1 || (w.size() == 1 && count[w[0]] != 1))
			comput(w, count, ans);
		if (w.size() == 1 && count[w[0]] != 1)
			ans *= 2;
		cout << ans << endl;

	}
	system("pause");
	return 0;
}


竟然超时了

再改了一下终于过了(scanf和printf有时还蛮有用的)

#include <iostream>
#include <vector>
#include <map>
#include <cstdio>
using namespace std;

const int mode = 1000000007;

int main()
{
	int t;
	scanf("%d", &t);
	while (t--)
	{
		int n;
		scanf("%d", &n);
		map<long long, int>count;
		int a;
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &a);
			count[a]++;
		}

		long long int ans = 1;
		while (1)
		{
			map<long long, int>::iterator iter = count.begin();
			if (iter->second % 2 == 0)
			{
				long long tmp1 = iter->second;
				while (tmp1)
				{
					ans = ans*(tmp1 - 1) * 2 % mode;
					tmp1 = tmp1 - 2;
				}
				long long tmp2 = 2 * iter->first;
				count[tmp2] += iter->second / 2;
				count.erase(iter);
			}
			else if (iter->second % 2 != 0 && iter->second != 1)
			{
				ans = iter->second * ans  % mode;
				int tmp3 = iter->second - 1;
				while (tmp3)
				{
					ans = ans*(tmp3 - 1) * 2 % mode;
					tmp3 = tmp3 - 2;
				}
				long long tmp = 2 * iter->first;
				count[tmp] += (iter->second - 1) / 2;
				iter->second = 1;
			}
			else
			{
				if (count.size() == 1)
					break;
				long long t = iter->first;
				count.erase(iter);
				iter = count.begin();
				ans = ans * 2 * iter->second%mode;
				long long tmp1 = t + iter->first;
				count[tmp1]++;
				iter->second--;
				if (iter->second == 0)
					count.erase(iter);
			}
		}
		printf("%d\n", int(ans));

	}
	system("pause");
	return 0;
}

(づ ̄ 3 ̄)づ


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值