一、問題引入
農夫約翰為了修理柵欄,要將一塊很長的木塊切成N塊。准備切成的長度分別是L1、L2、、、,LN,未切割前的木板長度切好為切割后木板長度的總和。每次切斷木板時的開銷是這塊木板的長度。(1 ≤ N ≤ 20000,0 ≤ Li ≤ 50000)
二、解題思路
由於N的值非常大,不可能枚舉所有情況再求解,必須用一種比較高效的算法。木板的切割循序不確定,看似自由度很高,是先選擇切出較短的,還是切較長的。如果我們把一種完全切割后的情況列舉出來,會發現可以用貪心算法
驚奇的發現,這種切法的切割費用之和 == 所有非葉子節點權值和 == 葉子節點的權值 * 深度(根節點深度為0)
即 33 = 15 + 7 + 8 + 3 = 3*2 + 4*2 + 5*2 + 1*3 + 2*3
問題轉化為,已知所有的葉子節點和根節點,求葉子節點的權值 * 深度和的最小值。
顯然,使權值大的深度小,權值小的深度大。於是,此時的最佳切割方法應該具有如下性質:
最短版和次短板應該是兄弟節點
這一性質在切割過程中始終成立,反過來,我們可以根據這種性質建立起對應的二叉樹。即每次合並最小的,合並后的值加到總費用中。(注意建立的樹不唯一,但每種的結果一樣,所以選其中一種就行)
由於是每次取最小和次小合並,維護一個優先隊列就行
三、代碼實現
1 #include<stdio.h> 2 #include<iostream> 3 #include<queue> 4 using namespace std; 5 6 typedef long long LL; 7 const int maxn = 20000 + 10; 8 int n, L[maxn]; 9 10 void slove() 11 { 12 LL ans = 0; 13 14 //聲明一個小值在前的優先隊列 15 priority_queue<int, vector<int>, greater<int>>que; 16 for (int i = 0; i < n; i++) 17 que.push(L[i]); 18 19 //循環到只剩一塊模板為止 20 while (que.size() > 1) 21 { 22 //取出最短的木板和次短的木板 23 int len1, len2; 24 len1 = que.top(); que.pop(); 25 len2 = que.top(); que.pop(); 26 27 ans += (len1 + len2); 28 que.push(len1 + len2); 29 } 30 printf("%lld\n", ans); 31 } 32 33 int main() 34 { 35 while (scanf("%d",&n) == 1) 36 { 37 for (int i = 0; i < n; i++) 38 scanf("%d", &L[i]); 39 slove(); 40 } 41 return 0; 42 }
四、總結
這個題運用了很多哈夫曼樹的思想,下一篇文章再討論吧。