题解 P6915 【[ICPC2015 WF]Weather Report】
Starlight237 · · 题解
Huffman 树
设一棵二叉树的叶节点权值和深度分别为
给定所有叶节点和它们的
如下算法可以构建一个序列的 Huffman 树。
- 以给定的权值构造 n 棵只含有 1 个节点的二叉树,加入二叉树集合
S 。 - 从
S 中选取权值最小的两棵二叉树T_1,T_2 合并成一个大二叉树T ,并令w(T)=w(T_1)+w(T_2) 。从S 中删去T_1,T_2 ,加入T 。 - 重复上一个步骤,直到
S 中只剩下一棵二叉树。这棵二叉树就是 Huffman 树。事实上,合并过程中所有的w(T) 之和就是 Huffman 树的 WPL。
Huffman 编码
- 定义:将
n 个不同的字符映射为互不为前缀的二进制编码,称为这组字符的前缀编码。若这组字符构成一个字符串S ,记f(S) 表示将S 中每个字符都替换成对应编码后S 的长度。使f(S) 最小的前缀编码称为 Huffman 编码。
如下算法可以构造对于一个字符集
设需要编码的字符分别为
回到本题:
(2021 集训队作业 P6915 【[ICPC2015 WF]Weather Report】)给定
4 种字符出现的概率,求所有4^n 个可能的长度为n 的字符串构成的集合的前缀编码,使得每个字符串的编码长度的期望值最小。
对于所有可能的字符串对应的概率求其 Huffman 树即可。但是
我们并不显式地建树,而只是开一个堆维护所有的
设堆顶为
若 continue,否则把状态拆成
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pi pair<double, ll>
const int N = 21;
int n, C[N][N];
double p[4][N];
priority_queue<pi, vector<pi>, greater<pi> > Q;
int main() {
scanf("%d", &n);
for (int i = 0; i < 4; ++i) {
p[i][0] = 1; double pp; scanf("%lf", &pp);
for (int j = 1; j <= n; ++j) p[i][j] = p[i][j - 1] * pp;
}
for (int i = 0; i <= n; ++i) C[i][0] = 1;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= i; ++j)
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= n - i; ++j)
for (int k = 0, l; k <= n - i - j; ++k)
l = n - i - j - k,
Q.push(make_pair(p[0][i] * p[1][j] * p[2][k] * p[3][l], (ll)C[n][i] * C[n - i][j] * C[n - i - j][k]));
double ans = 0;
while (!Q.empty()) {
pi tp = Q.top(); Q.pop();
if (Q.empty() && tp.second == 1) break;
double pp = tp.first; ll cnt = tp.second;
if (cnt > 1) {
if (cnt & 1) Q.push(make_pair(pp, 1)), --cnt;
ans += pp * cnt;
Q.push(make_pair(pp * 2., cnt >> 1));
continue;
}
tp = Q.top(), Q.pop();
double qq = tp.first; cnt = tp.second;
ans += pp + qq;
Q.push(make_pair(pp + qq, 1));
if (cnt > 1) Q.push(make_pair(qq, cnt - 1));
}
printf("%.6lf", ans);
return 0;
}