原址:
題目大意
:給出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#include#include#include#define ll long long
using namespace std;
struct node
node(ll a,ll b)
bool operator < (const node &a) const
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)
point.push(node(sum,len+1));
cnt=cnt-k+1;
}printf("%i64d\n",ans);
printf("%i64d\n",point.top().dep);
}
NOI 2015 荷馬史詩 (哈夫曼樹)
問題描述 追逐影子的人,自己就是影子。荷馬 allison 最近迷上了文學。她喜歡在乙個慵懶的午後,細細地品上一杯卡布奇諾,靜靜地閱讀她愛不釋手的 荷馬史詩 但是由 奧德賽 和 伊利亞特 組成的鴻篇巨制 荷馬史詩 實在是太長了,allison 想通過一種編碼方式使得它變得短一些。一部 荷馬史詩 中有...
NOI2015 荷馬史詩
題面 追逐影子的人,自己就是影子 荷馬 allison 最近迷上了文學。她喜歡在乙個慵懶的午後,細細地品上一杯卡布奇諾,靜靜地閱讀她愛不釋手的 荷馬史詩 但是由 奧德賽 和 伊利亞特 組成的鴻篇巨制 荷馬史詩 實在是太長了,allison 想通過一種編碼方式使得它變得短一些。一部 荷馬史詩 中有n種...
NOI 2015 荷馬史詩 k叉哈夫曼樹 堆優化
題目大意 給出n個數字w,代表n個字母出現的次數,給出k。要求用k進製的數字串si替換第i個字母,且替換之後要求替換後的文章無二義性 這裡的無二義性是指對於任意的 1 i,j n i j,都有 si不是sj的字首 求替換後最短的文章的長度 長度len sigma w i strlen si 和這種情...