1. 程式人生 > >NOI2015 荷馬史詩 [哈夫曼樹]

NOI2015 荷馬史詩 [哈夫曼樹]

per inpu h+ 正是 存在 space pre 節點 noi2015

Description

一部《荷馬史詩》中有n種不同的單詞,從1到n進行編號。其中第i種單 詞出現的總次數為wi。Allison 想要用k進制串si來替換第i種單詞,使得其滿足如下要求:
對於任意的 1 ≤ i, j ≤ n , i ≠ j ,都有:si不是sj的前綴。
現在 Allison 想要知道,如何選擇si,才能使替換以後得到的新的《荷馬史詩》長度最小。在確保總長度最小的情況下,Allison 還想知道最長的si的最短長度是多少?
一個字符串被稱為k進制字符串,當且僅當它的每個字符是 0 到 k ? 1 之間(包括 0 和 k ? 1 )的整數。
字符串 str1 被稱為字符串 str2 的前綴,當且僅當:存在 1 ≤ t ≤ m ,使得str1 = str2[1..t]。其中,m是字符串str2的長度,str2[1..t] 表示str2的前t個字符組成的字符串。

Input

輸入的第 1 行包含 2 個正整數 n<=100000, k<=9 ,中間用單個空格隔開,表示共有 n種單詞,需要使用k進制字符串進行替換。

接下來n行,第 i + 1 行包含 1 個非負整數wi <=10^11,表示第 i 種單詞的出現次數。

Output

輸出包括 2 行。
第 1 行輸出 1 個整數,為《荷馬史詩》經過重新編碼以後的最短長度。
第 2 行輸出 1 個整數,為保證最短總長度的情況下,最長字符串 si 的最短長度。

Example

Input:
6 3
1
1
3
3
9
9

Output:
36
3

思路

??首先介紹一下哈夫曼樹,哈夫曼樹也叫最優二叉樹,是一種帶權路徑(葉子節點權值乘該節點到根節點路徑長度)之和最短的二叉樹。通過哈夫曼樹可以構造出哈夫曼編碼,可以用於數據壓縮和數據加密。
??哈夫曼編碼的原理是,利用哈夫曼樹中權值越高(數據出現頻率越高)的節點到根節點的距離越小(對應哈夫曼編碼越短)的性質,將數據壓縮成哈夫曼編碼。正是由於每一個數據對應於樹上的每一個葉子節點,而對應的編碼為根到該葉子節點的路徑,所以一串哈夫曼編碼才能對應惟一的一串原碼(即題目中所說si不是sj的前綴。),解碼時才不會產生混淆。
??依據題意和題目背景裏面的一些暗示,思路可以明確,就是要求構造一個K進制的哈夫曼編碼。平時我們所說的哈夫曼編碼為2進制的,構造的方法也很容易(類似於NOIP2004 合並果子),本題也可以使用相同的思想。
??首先明確需要維護什麽。轉換一下題意,不難知道我們需要維護的是最短的帶權路徑之和和該哈夫曼樹的高度。然後便是如何維護,由於不需要知道哈夫曼樹的具體形態,我們便可以按照哈夫曼樹的構造方式,將當前最小的K個節點合並為1個父節點,直至只有一個父節點。看到“將最小K個節點合並”便可以明確使用優先隊列(二叉堆)進行維護。
??最後,我們需要註意一個細節。因為每次都是將k個節點合並為1個(減少k-1個),一共要將n個節點合並為1個,如果(n-1)%(k-1)!=0 則最後一次合並時不足k個。也就表明了最靠近根節點的位置反而沒有被排滿,因此我們需要加入k-1-(n-1)%(k-1)個空節點使每次合並都夠k個節點(也就是利用空節點將其余的節點擠到更優的位置上)。

代碼

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <ext/pb_ds/priority_queue.hpp> //pb_ds庫
#define LL long long 
using namespace std;
struct node{
    LL w,h;
    node(LL W, LL H){
        w=W,h=H;
    }
};
bool operator<(node a, node b){ //保證最短總長度的情況下,最長字符串的最短長度。
    if(a.w!=b.w) return a.w>b.w; 
    return a.h>b.h;   //如果長度相等,高度小的優先(保證最短總長度的情況下,最長字符串的最短長度)。
} //構造小根堆的操作。
__gnu_pbds::priority_queue <node, std::less<node>, __gnu_pbds::pairing_heap_tag> q; //優先隊列
int n,k,cnt;
LL temp,maxh,ans;

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1; i<=n; i++){
        scanf("%lld",&temp);
        q.push(node(temp,1));
    }
    if((n-1)%(k-1) != 0) cnt=k-1-(n-1)%(k-1);  //判斷是否要補空節點
    for (int i=1; i<=cnt; i++)
        q.push(node(0,1)); //補空節點
    cnt+=n;  //cnt為根節點個數(最初每個根節點都為其本身)
    while(cnt>1){
        temp=maxh=0;
        for(int i=1; i<=k; i++){
            temp+=q.top().w;
            maxh=max(maxh,q.top().h);
            q.pop();
        }
        ans+=temp; //維護帶權路徑長度之和
        q.push(node(temp, maxh+1)); //合並為父節點,高度為最高子樹高度+1
        cnt-=k-1; //減少根節點
    }
    printf("%lld\n%lld\n",ans,q.top().h-1);
    return 0;
}

NOI2015 荷馬史詩 [哈夫曼樹]