1. 程式人生 > >重編碼(HUFFMAN)

重編碼(HUFFMAN)

重編碼

問題描述

有一篇文章,文章包含 n 種單詞,單詞的編號從 1 至 n,第 i 種單詞的出現次數為 w[i]。

現在,我們要用一個 2 進位制串(即只包含 0 或 1 的串) s[i] 來替換第 i 種單詞,使其滿足如下要求:對於任意的 1≤i,j≤n(i≤j),都有 s[i] 不是 s[j] 的字首。(這個要求是為了避免二義性)

你的任務是對每個單詞選擇合適的 s[i],使得替換後的文章總長度(定義為所有單詞出現次數替換它的二進位制串的長度乘積的總和)最小。求這個最小長度。

字串 S1(不妨假設長度為 n)被稱為字串 S2 的字首,當且僅當:S2 的長度不小於 n,且 S1 與 S2 前 n 個字元組組成的字串完全相同。

輸入格式

第一行一個整數 n,表示單詞種數。

第 2 行到第 n+1 行,第 i+1 行包含一個正整數 w[i],表示第 i 種單詞的出現次數。

輸出格式

輸出一行一個整數,表示整篇文章重編碼後的最短長度。

樣例輸入

4
1
1
2
2

樣例輸出

12

樣例解釋

一種最優方案是令 s[1]=000,s[2]=001,s[3]=01,s[4]=1。這樣文章總長即為 1*3+1*3+2*2+1*2=12。

另一種最優方案是令 s[1]=00,s[2]=01,s[3]=10,s[4]=11。這樣文章總長也為 12。

資料範圍

對於第 1 個測試點,保證 n=3。

對於第 2 個測試點,保證 n=5。

對於第 3 個測試點,保證 n=16,且所有 w[i] 都相等。

對於第 4 個測試點,保證 n=1,000。

對於第 5 個測試點,保證所有 w[i] 都相等。

對於所有的 7 個測試點,保證 2≤n≤100,000,w[i]≤10^11。

時間限制:2 sec

空間限制:256 MB

提示

[我們希望越長的串出現次數越少,那麼貪心地考慮,讓出現次數少的串更長。]

[於是我們先區分出出現次數最少的 2 個串,在它們的開頭分別新增 0 和 1。]

[接著,由於它們已經被區分(想一想,為什麼?),所以我們可以把它們看作是**一個**單詞,且其出現次數為它們的和,然後繼續上面的“添數”和“合併”操作。]

[這樣,我們不停地“合併單詞”,直到只剩 1 個單詞,即可結束。]

[可以證明這是最優的。]

[樸素的實現是 O(n^2) 的,可以用二叉堆或__std::priority_queue__將其優化至 O(nlogn)。]

#include <bits/stdc++.h>
using namespace std;

// ================= 程式碼實現開始 =================

typedef long long ll;

/* 請在這裡定義你需要的全域性變數 */
priority_queue<ll,vector<ll>,greater<ll> > pq;
// 這是求解整個問題的函式
// w:題目描述中的 w(所有)
// n:題目描述中的 n
// 返回值:答案
long long getAnswer(int n, vector<long long> w) {
    /* 請在這裡設計你的演算法 */
    for(int i = 0;i<n;i++)
        pq.push(w[i]);
    ll sum = 0;
    while(pq.size()>1){
        ll newEle = 0;
        for(int k = 0;k<2;k++){
            newEle = newEle+pq.top();
            pq.pop();
        }
        sum+=newEle;
        pq.push(newEle);
    }return sum;
}

// ================= 程式碼實現結束 =================

int main() {
    int n;
    scanf("%d", &n);
    vector<long long> w;
    for (int i = 0; i < n; ++i) {
        long long x;
        scanf("%lld", &x);
        w.push_back(x);
    }
    printf("%lld\n", getAnswer(n, w));
    return 0;
}

每次選擇pq中最少的兩元素相加,放入堆中