樂師理工ACM集訓 哈夫曼樹與哈夫曼編碼

2021-10-08 05:32:04 字數 4616 閱讀 1978

hdu1053 entropy【哈夫曼】

poj3253 fence repair【貪心/哈夫曼+反向思維】

codeforces884d boxes and balls【三叉哈夫曼+反向思維】

思考:為什麼權值為字元出現次數時哈夫曼編碼長度等於帶權路徑長度

傳送門:hdu2527 safe or unsafe

題目說編碼方式是哈夫曼編碼(huffman coding),並定義乙個字母的權值等於該字母在字串**現的頻率。要求判斷哈夫曼編碼值和給定的安全值大小(此處說的編碼值即為哈夫曼的帶權路徑長度)。直接統計字母出現次數,求哈夫曼樹帶權路徑長度,並比較帶權路徑長度和安全值大小即可。

#include

#include

#include

#include

#include

using

namespace std;

const

int maxn=

6e5+5;

char s[maxn]

;int cnt[30]

;// 用於統計字母出現次數

priority_queue<

int,vector<

int>

,greater<

int>

> q;

intmain()

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));

// 清除統計

}return0;

}

傳送門:hdu1053 entropy

給定只包含大寫字母和『_'的字串(乙個一行,輸入到end結束)。問:每個字母8位,這個字串長多少位?若採用哈夫曼編碼,編碼長度多少位?壓縮率為多少(保留1位小數)?

要求哈夫曼編碼長度,直接求帶權路徑長度即可,權值為字元出現次數。(若要求實際編碼,需構建哈夫曼樹,有興趣的可以了解一下。更多時候使用的是哈夫曼的思想)

#include

#include

#include

#include

#include

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;

intmain()

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));

// 清空計數陣列

}return0;

}

傳送門: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儲存。

#include

#include

#include

#include

#include

using

namespace std;

typedef

long

long ll;

priority_queue

,greater

> q;

intmain()

if(n==1)

// 一塊木板,無需切割

ll sum=0;

while

(q.size()

>1)

printf

("%lld\n"

,sum)

;return0;

}

傳送門:codeforces884d boxes and balls

給定乙個n。然後是n個不同顏色的球的數目。第 i 種顏色的球最終要放入第 i 個盒子中。開始所有的球都在第乙個盒子裡。每次可以進行以下兩步操作:

1、把某乙個非空盒子中的球全部拿出來。

2、選個k個空盒子,將拿出來的球分為k組,分別放入k個空盒子。(k可以取值為2或3)

每次操作的代價為拿出的球的數量。

問:將n種不同顏色的球放入n個對應的盒子花費最小是多少?

開始想到的是每次貪心前兩大的數,但這不能構成最優解。這個思路對於樣例

614

4444

處理的順序是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。

#include

#include

#include

#include

#include

using

namespace std;

typedef pair<

int,

int> p;

typedef

long

long ll;

const

int maxn=

6e5+5;

priority_queue

,less

> q;

intmain()

ll ans=sum;

while

(q.size()

>3)

while

(!q.

empty()

) q.

pop();

printf

("%lld\n"

,ans)

;return0;

}

#include

#include

#include

#include

#include

using

namespace std;

typedef

long

long ll;

priority_queue

,greater

> q;

intmain()

if(n==1)

// 只有乙個盒子,無需操作if(

(n&1)==

0)// 當 n為偶數,補 0 湊成奇數使得可以每次都取三個

q.push(0

);ll sum=0;

while

(q.size()

>1)

printf

("%lld\n"

,sum)

;return0;

}

因為:

哈夫曼帶權路徑長度(wpl) = sum(葉結點的權值 × 該結點到根結點的路徑長度)

哈夫曼編碼長度 = sum(字元出現次數 × 該字元編碼長度)

又:字元編碼長度 == 字元到根結點的路徑長度

葉節點權值 == 某字元出現次數

所以:哈夫曼帶權路徑長度(wpl) == 哈夫曼編碼長度

哈夫曼樹與哈夫曼編碼

在一般的資料結構的書中,樹的那章後面,著者一般都會介紹一下哈夫曼 huffman 樹和哈夫曼編碼。哈夫曼編碼是哈夫曼樹的乙個應用。哈夫曼編碼應用廣泛,如 jpeg中就應用了哈夫曼編碼。首先介紹什麼是哈夫曼樹。哈夫曼樹又稱最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的...

哈夫曼樹與哈夫曼編碼

1.哈夫曼 huffman 編碼是哈夫曼樹的乙個應用。2.哈夫曼樹又稱為最優二叉樹,是一種帶權路徑長度最短的二叉樹。所謂樹的帶權路徑長度,就是樹中所有的葉結點的相對值乘上其到根結點的路徑和權值。3.影象jpg就是利用了哈夫曼編碼。4.哈夫曼樹是最優二叉樹,子樹有左右次序之分。5.哈夫曼樹的形態不是唯...

哈夫曼樹與哈夫曼編碼

哈夫曼樹是一種簡單的樹結構,建樹過程如下 給出一組資料,不斷選擇最小的兩個數,並用兩個數的和作為它們的parent節點,再從資料中刪除這兩個數,將兩個數的和加入資料中,直到所有的資料都被加入樹結構,形成一顆樹。這顆樹的所有非葉子節點都有兩個child,兩個child的值的和則是這個節點的值,根節點是...