B - 多元Huffman編碼問題


Description

在一個操場的四周擺放着n堆石子。現要將石子有次序地合並成一堆。規定每次至少選2 堆最多選k堆石子合並成新的一堆,合並的費用為新的一堆的石子數。試設計一個算法,計算出將n堆石子合並成一堆的最大總費用和最小總費用。
對於給定n堆石子,計算合並成一堆的最大總費用和最小總費用。

Input

輸入數據的第1 行有2 個正整數n和k(n≤100000,k≤10000),表示有n堆石子,每次至少選2 堆最多選k堆石子合並。第2 行有n個數(每個數均不超過 100),分別表示每堆石子的個數。

Output

將計算出的最大總費用和最小總費用輸出,兩個整數之間用空格分開。

Sample

Input

7 3
45 13 12 16 9 5 22

Output

593 199

Hint

請注意數據范圍是否可能爆 int。

題解:

涉及哈夫曼問題,首先哈夫曼樹是每次選擇最大或最小的點搭建的,而大頂堆或小頂堆剛好能滿足這個要求,因此,可以用堆來解哈夫曼問題。
c++中優先隊列便是一個堆,所以可以用優先隊列解題。
由於數據過大,所以int會WA改用long long int。
在建立多元哈夫曼時,如果當前節點不足以建立一個K元的哈夫曼(有一個節點的子節點數量小於K),則需要補零來完善。
每次選擇最大值來建立二元哈夫曼樹,會得到最費力的值。
注:因為考試無法使用優先隊列,因此該代碼只能用於AC題目,不能用作考。后面補了一個不用優先隊列的方法,代碼異常的多,不推薦。
(原來的代碼有bug,雖然能AC,但無法解決n<k的情況,感謝熱心網友NagiRingu)

#include <queue>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define maxn 10050
#define INF 0x3f3f3f3f

using namespace std;

int main(){
    int n, k, i, x, sum;
    long long MAX, MIN;
    MAX = MIN = 0;
    cin>> n>> k;
    priority_queue<long long> q1;
    priority_queue<long long, vector<long long>, greater<long long > > q2;
    for(i=0;i<n;i++){
        cin>> x;
        q1.push(x);
        q2.push(x);
    }
    while((int)q1.size() > 1){
        sum = q1.top();
        q1.pop();
        if(!q1.empty()){
            sum += q1.top();
            q1.pop();
        }
        MAX += sum;
        q1.push(sum);
    }
    if(n>k){
        x = n;
        while(x>k){
            x -= (k - 1);
        }
        for(i=x;i<k;i++){
            q2.push(0);
        }
    }
    while((int)q2.size()>k){
        sum = 0;
        for(i=0;i<k;i++){
            sum += q2.top();
            q2.pop();
        }
        MIN += sum;
        q2.push(sum);
    }
    while(!q2.empty()){
        MIN += q2.top();
        q2.pop();
    }
    cout<<MAX<<' '<<MIN<<endl;
    return 0;
}

不使用優先隊列,只能把堆的代碼寫出來。不過看老師給的參考答案,是可以使用優先隊列的。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define maxn 200050 //多元哈夫曼樹最多會有2n-1個節點。

using namespace std;

//小頂堆建立過程。
struct heap_less{
    //數據存儲
    long long data[maxn];
    //記錄堆的大小。
    int num;
    //交換下標a,b的位置。
    void qswap(int a, int b){
        long long t;
        t = data[a];
        data[a] = data[b];
        data[b] = t;
    }
    //向下調整堆。
    void down(int i){
        int t, flag = 0;
        while(i*2 <= num && flag == 0){
            if(data[i] > data[i*2]){
                t = i * 2;
            }
            else
                t = i;
            if(i * 2 + 1 <= num){
                if(data[t] > data[i*2+1]){
                    t = i * 2 + 1;
                }
            }
            if (t != i){
                qswap(i, t);
                i = t;
            }
            else{
                flag = 1;
            }
        }
    }
    //向上調整堆。
    void up(int i){
        int t, flag = 0;
        while(i >= 1 && flag == 0){
            if(data[i] > data[i*2]){
                t = i * 2;
            }
            else
                t = i;
            if(i * 2 + 1 <= num){
                if(data[t] > data[i*2+1]){
                    t = i * 2 + 1;
                }
            }
            if (t != i){
                qswap(i, t);
                i = i / 2;
            }
            else{
                flag = 1;
            }
        }
    }
    //返回堆頂元素。
    int top(){
        return data[1];
    }
    //刪除堆頂元素。
    void pop(){
        data[1] = data[num];
        down(1);
        num--;
    }
    //插入元素。
    void push(int x){
        num += 1;
        data[num] = x;
        up(num / 2);
    }
};

//大頂堆建立過程,該過程與小頂堆差別不大。
struct heap_gteater{
    long long data[maxn];
    int num;
    void qswap(int a, int b){
        long long t;
        t = data[a];
        data[a] = data[b];
        data[b] = t;
    }
    void down(int i){
        int t, flag = 0;
        while(i*2 <= num && flag == 0){
            if(data[i] < data[i*2]){
                t = i * 2;
            }
            else
                t = i;
            if(i * 2 + 1 <= num){
                if(data[t] < data[i*2+1]){
                    t = i * 2 + 1;
                }
            }
            if (t != i){
                qswap(i, t);
                i = t;
            }
            else{
                flag = 1;
            }
        }
    }
    void up(int i){
        int t, flag = 0;
        while(i >= 1 && flag == 0){
            if(data[i] < data[i*2]){
                t = i * 2;
            }
            else
                t = i;
            if(i * 2 + 1 <= num){
                if(data[t] < data[i*2+1]){
                    t = i * 2 + 1;
                }
            }
            if (t != i){
                qswap(i, t);
                i = i / 2;
            }
            else{
                flag = 1;
            }
        }
    }
    int top(){
        return data[1];
    }
    void pop(){
        data[1] = data[num];
        down(1);
        num--;
    }
    void push(int x){
        num += 1;
        data[num] = x;
        up(num / 2);
    }
};

int main()
{
    int n, k, i, x;
    heap_less h;
    heap_gteater h2;
    h.num = 0;
    scanf("%d%d",&n, &k);
    for(i=0; i<n; i++){
        scanf("%d",&x);
        h.push(x);
        h2.push(x);
    }
    int t = n;
    //如果節點數不能夠建立一個完整的哈夫曼樹,將節點用0補全。
    if(t > k){
        while(t > k){
            t -= (k  -1);
        }
        for(i=0; i<k-t; i++){
            h.push(0);
        }
    }
    //計算最小值,最小值是取最小值建立k元的哈夫曼樹,並求值。(權值*路徑長度)
    long long MIN, sum;
    MIN = 0;
    while(h.num >= k){
        sum = 0;
        for(i=0; i<k; i++){
            sum += h.top();
            h.pop();
        }
        MIN += sum;
        h.push(sum);
    }
    //計算最大值,最大值是取最大值建立2元哈夫曼常數,並求值。
    long long MAX;
    MAX = 0;
    while(h2.num >= 2){
        sum = 0;
        for(i=0; i<2; i++){
            sum += h2.top();
            h2.pop();
        }
        MAX += sum;
        h2.push(sum);
    }
    cout<<MAX<<" "<<MIN<<endl;
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM