NOI 2015 荷馬史詩 k叉哈夫曼樹 堆優化

原址:http://blog.csdn.net/Quack_quack/article/details/46958413

題目大意:給出n個數字w[],代表n個字母出現的次數,給出k。要求用k進制的數字串si替換第i個字母,且替換之後要求替換後的文章無二義性(這裏的無二義性是指對於任意的 1≤i,j≤n ,i≠j,都有: si不是sj的前綴),求替換後最短的文章的長度(長度len=sigma(w[i]*strlen(si)))和這種情況下最大的si的最小值。 
數據範圍:n<=100000,k<=9 
分析: 
題目要求的限制條件很多,既要求替換後無二義性,又要求方案的最值,還有k進制的限制= =。沒學過哈夫曼樹的能想到就太厲害了,學過的(NOI這個級別肯定也學過)能想起來然後套模型就有思路了。 
我很弱,因此直接套了哈夫曼樹的模型。 
哈夫曼樹一般是二叉樹,建樹的方法就是每次選擇兩個權值(即出現次數)最小的點,刪除這2個點,加入一個權值是這兩個點之和的新點進去。並且使這被刪除的2個點的父親成爲那個新點。 
編碼的時候左支和右支一個是1一個是0,從根節點到葉子節點經過的邊的1/0序列就是葉子節點對應的編碼。 
然而這個題是k叉樹,方法和上面類似,然而每次選擇k個權值最小的點的時候容易讓最後一次合併的時候的點不足k個。假設最初有n個點,最後有1個點,每次合併刪除k個點又放進1個點。那麼易得:(n-1)是(k-1)的倍數。如果(n-1)%(k-1)!=0,那麼就要再放入(k-1-(n-1)%(k-1))個虛擬點,並且它們的權值爲0,它們也參與求最小k個點。 
然而此題還要求si的最大值最小,因此我們讓點代表一個二元組(val,dep),表示這個點的權值和點在樹中的深度。在求最小k個點時,把val作爲第一比較條件,如果val值相等,則把dep小的放在前面,這樣在每次合併的時候,深度小的點都會被優先合併,保證了根到葉子的最長鏈的長度儘量小。 
所以,可以得到此題的算法: 
1)處理這n個權值,加入虛擬點,這些點的val值上文已經告訴,dep值爲0,ans=0; 
2)每次取出前k小的點,求它們的val之和sum,求它們的dep的最大值d,那麼放入的新點應該是(sum,d+1),把它放入原來的容器裏面並要求有序,且ans+=sum(畫一棵哈夫曼樹,想想求文章長度的過程能這麼實現的原理); 
3)當容器內只有一個點時,輸出ans和這個點的dep值。 
這樣的話正確性可以保證,但是注意容器的選擇,不能直接數組模擬,會超時,可以用堆優化,我用的優先隊列,和堆差不多,這樣維護點的有序性變成O(log n)。求前k大的數也不用什麼高級的數據結構,考慮k不大,就優先隊列一個一個彈出,彈k次就可以了。 
最終時間複雜度是O(nlogn)。 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#define ll long long
using namespace std;
struct node{
    ll num,dep;
    node(){}
    node(ll a,ll b){num=a,dep=b;}
    bool operator < (const node &a) const
    {
        if(num==a.num)
            return a.dep<dep;
        return a.num<num;
    }
};
priority_queue<node>point;
ll k,n,ans,cnt;
int main()
{
    scanf("%I64d%I64d",&n,&k);
    for(int i=1;(ll)i<=n;++i)
    {
        ll temp;
        scanf("%I64d",&temp);
        point.push(node(temp,0));
    }
    cnt=n;
    if((n-1)%(k-1))cnt+=(k-1-(n-1)%(k-1));
    for(int i=n+1;(ll)i<=cnt;++i)
        point.push(node(0,0));
    while(cnt>1)
    {
        ll sum=0,len=0;
        for(int i=1;(ll)i<=k;++i)
        {
            sum+=point.top().num;
            len=max(len,point.top().dep);
            point.pop();
        }
        point.push(node(sum,len+1));
        cnt=cnt-k+1;
    }
    printf("%I64d\n",ans);
    printf("%I64d\n",point.top().dep);
}


發佈了34 篇原創文章 · 獲贊 4 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章