JZOJsenior1776.经济编码

本文介绍了一种经济编码方法,通过构建哈夫曼树来为字符编码,确保编码没有前置关系。详细描述了编码过程,并提供了输入输出示例。最后,给出了求字符编码预期长度的算法思路和数据约束。

Description

  为降低资料储存的空间或增加资料传送的速度,编码是常用的方法。
  假设有一个字符集,每个字符出现的频率是已知的。现在要把每个字符编码成为一个二元字串(例如把“A”编码作101),采用的编码必须合乎以下条件:一个字符的编码不可以是另一个字符的前置(prefix)。前置的定义如下:若一个字串S1为另一个字串S2的前置,则从S2的最后一个字符开始,连续删除一定数量的字符后可以得到S1(S2本身也是S2的前置),举例而言:如果字符“A”的编码是101,而字符“B”的编码为01,则“B”的编码不为“A”编码的前置;如果字符“C”的编码为1100,而字符“D”的是11,则“D”的编码是“C”编码的前置。以下的编码方式可以在符合这个条件下给出最经济的编码,请找出使用下述方法做最经济编码时,一个字符编码的预期长度。
  
 这里写图片描述
 这里写图片描述
  编码法:
  1、 如以下所述建立一棵二元树。
  先从字符集选取两个出现频率最低的字符作合并,合并后以一个全新的虚拟字符取代这两个字符,新字符的频率等于这两个旧字符频率的总和,并令这两个旧字符为此新字符的两个子树,左右不限。重复以上操作,直至字符集剩下一个字符为止。如下图(i)到(iv)。
  2、 再依照以下所述方法将各字符作编码。
  由上一步骤所得之二元树,将每个内部节点(internal node)连往左子树的边(edge)标记为“0”,连往右子树的边标记为“1”,如下图(V)所示。一字元的编码即为从树根(root)至此字符,经过的每一个边的标记所成之字串。在此“a”编码作000,“?”编码作01。
  在按照上述的编码法完成最经济编码之后,就可以计算这个字符编码的预期长度。首先算出每个字符的预期长度=编码长度×出现频率,然后把所有字符的预期长度结合起来,就可以得到此字符编码的预期长度。下表是上述编码的计算范例。

字符 编码 编码长度 出现频率 预期长度
a 000 3 0.1 0.3
b 001 3 0.1 0.3
? 01 2 0.3 0.6
8 1 1 0.5 0.5

    字符编码的预期长度 1.7

Input

  第一行为两整数n和m,分别代表字符集的大小和文章总长度。然后每一个字符分行列出,每行列出一字符出现的次数。

Output

  预期一个字符编码的长度,保留至小数点后6位。

Sample Input

输入范例1:
4 10
1
1
3
5

输出范例1:
1.700000

输入范例2:
6 100
30
30
5
25
5
5

输出范例2:
2.250000

Sample Output

Data Constraint

Hint

【数据约定】
  对于50%的数据,1<=n<=2000;
  对于100%的数据,1<=n<=200000。
  所有字符出现次数和等于文章总长度。
【提示】

思路:
这题除了题面比较长,其实没有过多的技术含量吧……
我来简化下题意:
从一个序列里面选出最小值和次小值,把两个数的和重新塞回序列里,直到序列剩下一个数。其实就是一道裸的合并果子嘛(虽说我没有做合并果子——)
(说的好听一点,建出来的那棵树,叫哈夫曼树)

用堆维护一下序列最小值,每次取出最小和次小
注意每次合并两个数时,新开一个点,把左儿子、右儿子和两个数的和记录一下,把这个点塞回堆里去
最后全部弄完以后,dfs遍历一次,求出∑深度×节点值

代码:

var
        heap,tree:array[0..1000000,0..2]of longint;
        n,m,i,j,x,y,xx,yy,tot:longint;
        ans:extended;
procedure swap(var x,y:longint);
var
        z:longint;
begin
        z:=x;
        x:=y;
        y:=z;
end;
procedure up(x:longint);
var
        y:longint;
begin
        if n=1 then exit;
        while x>1 do
        begin
                y:=x div 2;
                if heap[y,0]>heap[x,0] then
                begin
                        swap(heap[x,0],heap[y,0]);
                        swap(heap[x,1],heap[y,1]);
                end;
                x:=y;
        end;
end;
procedure down(x:longint);
var
        y:longint;
begin
        while x<=n div 2 do
        begin
                y:=2*x;
                if (y<n)and(heap[y,0]>heap[y+1,0])then inc(y);
                if heap[x,0]>heap[y,0] then
                begin
                        swap(heap[x,0],heap[y,0]);
                        swap(heap[x,1],heap[y,1]);
                end;
                x:=y;
        end;
end;
procedure dfs(x,v:longint);
begin
        if (tree[x,1]=0)and(tree[x,2]=0)then
        begin
                ans:=ans+v*(tree[x,0]/tot);
                exit;
        end;
        if tree[x,1]<>0 then dfs(tree[x,1],v+1);
        if tree[x,2]<>0 then dfs(tree[x,2],v+1);
end;
begin
        readln(n,tot);
        for i:=1 to n do
        begin
                read(heap[i,0]);
                heap[i,1]:=i;
                tree[i,0]:=heap[i,0];
        end;
        for i:=n div 2 downto 1 do down(i);
        m:=n;
        while n>1 do
        begin
                x:=heap[1,0];
                xx:=heap[1,1];
                heap[1,0]:=heap[n,0];
                heap[1,1]:=heap[n,1];
                heap[n,0]:=0;
                heap[n,1]:=0;
                dec(n);
                down(1);
                y:=heap[1,0];
                yy:=heap[1,1];
                heap[1,0]:=heap[n,0];
                heap[1,1]:=heap[n,1];
                heap[n,0]:=0;
                heap[n,1]:=0;
                dec(n);
                down(1);
                inc(n);
                inc(m);
                heap[n,0]:=x+y;
                heap[n,1]:=m;
                up(n);
                tree[m,0]:=x+y;
                tree[m,1]:=xx;
                tree[m,2]:=yy;
        end;
        dfs(m,0);
        writeln(ans:0:6);
end.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值