POJ 3253 Fence Repair
阿新 • • 發佈:2021-01-12
題目如下:
Farmer John wants to repair a small length of the fence around the pasture. He measures the fence and finds that he needs N (1 ≤ N ≤ 20,000) planks of wood, each having some integer length Li (1 ≤ Li ≤ 50,000) units. He then purchases a single long board just long enough to saw into the N planks (i.e., whose length is the sum of the lengths Li). FJ is ignoring the "kerf", the extra length lost to sawdust when a sawcut is made; you should ignore it, too.
FJ sadly realizes that he doesn't own a saw with which to cut the wood, so he mosies over to Farmer Don's Farm with this long board and politely asks if he may borrow a saw.
Farmer Don, a closet capitalist, doesn't lend FJ a saw but instead offers to charge Farmer John for each of the N-1 cuts in the plank. The charge to cut a piece of wood is exactly equal to its length. Cutting a plank of length 21 costs 21 cents.
Farmer Don then lets Farmer John decide the order and locations to cut the plank. Help Farmer John determine the minimum amount of money he can spend to create the N planks. FJ knows that he can cut the board in various different orders which will result in different charges since the resulting intermediate planks are of different lengths.
Input
Line 1: One integer N, the number of planks
Lines 2..N+1: Each line contains a single integer describing the length of a needed plank
Output
Line 1: One integer: the minimum amount of money he must spend to make N-1 cuts
Sample Input
3
8
5
8
Sample Output
34
Hint
He wants to cut a board of length 21 into pieces of lengths 8, 5, and 8.
The original board measures 8+5+8=21. The first cut will cost 21, and should be used to cut the board into pieces measuring 13 and 8. The second cut will cost 13, and should be used to cut the 13 into 8 and 5. This would cost 21+13=34. If the 21 was cut into 16 and 5 instead, the second cut would cost 16 for a total of 37 (which is more than 34).
解題思路:
這道題看似沒有頭緒,實際上我們也可以用略微奇特的方法來進行求解。首先,我們可以將木板看成是二叉樹的節點,切割的次數看成是二叉樹的邊。就拿上面的例子來說,將長度為21的木板要切成 13 和 8 的木板時,開銷為21。再將長度為13的木板切成長度為5和8的木板時,開銷是13.於是合計的開銷為21+13=34。我們可以將其轉換成二叉樹。具體如下:
其中,5節點、8節點、8節點都是這顆二叉樹的葉子節點。並且,這些葉子節點都是由21這個根節點進行分割而來的。為了可以講解的更加清晰,我們將上面的這顆二叉樹分成兩個部分來講。首先請看這一部分:
上面的這顆二叉樹代表:長度為21的木板,經過了一次分割(深度為1),分割成了兩個木板,一個是13(左孩子),一個是8(右孩子)。並且根據上面所說的(將長度為21的木板要切成 13 和 8 的木板時,開銷為21。)我們也能明白,開銷值跟這部分的二叉樹根節點的值相等。
再來看第二部分:
上面的這顆二叉樹代表:長度為13的木板,又經過了一次分割(深度為2),分割成了兩個木板,一個是5(左孩子),一個是8(有孩子)。並且根據上面所說的(再將長度為13的木板切成長度為5和8的木板時,開銷是13。)我們也能明白,開銷值跟這部分的二叉樹根節點的值相等。
那麼,再將目光轉回這顆二叉樹上:
根據上述的(於是合計的開銷為21+13=34)我們可以得知,這顆二叉樹的開銷為34。那麼,我們能從其中總結出什麼規律呢?
- 首先,每一次進行分割時,開銷都會進行增加。增加的具體值就是被分割的木板長度(二叉樹的根節點)
- 如果,我們想求整道題的開銷的話,實際上就是將這顆二叉樹擁有左右孩子節點(被分割的木板)的值進行相加即可。
既然開銷的值跟這顆二叉樹擁有左右孩子節點的值有關係,且二叉樹中擁有左右孩子的節點的值都是由它左右孩子的值相加而成的。例如:上面的13節點是由它的左孩子5和右孩子8構成的。知道了這些結論之後,且根據上面題目中所給的條件,要切成的三塊木板正好對應在二叉樹的葉節點上。那麼,我們不妨從葉節點開始,逆向把這顆二叉樹進行還原即可。光這樣可能還不夠,這道題求的是最小的開銷,那麼我們不妨把葉節點進行從小到大排序處理,每次選擇兩個最小的節點進行還原為一個新的節點(木板)。並且新的節點值就是這兩個節點的值相加。(同時也是總開銷的一部分)然後將這個新的節點再次跟剩下的節點進行從小到大排序,再選兩個最小的節點還原為一個新的節點(木板)。如此反覆下去,直到節點變為一個的時候,就代表還原到了根節點(原先長度的木板)。再將這棵二叉樹中擁有左右孩子節點的值進行相加,就是我們所要求的最小開銷了。
你也可以嘗試每次選擇兩個長度最大的木板進行還原,還原之後,開銷肯定比34要大!
並且,上述的構造方法就是霍夫曼樹的構造方法。並且上面所說的樹,實際上就是一顆霍夫曼樹。
這道題的注意事項:
- 儲存開銷的變數型別要設定為long long。根據上述的題目可知,每一塊木板的長度最高為20000,並且在進行還原的時候,有兩個節點的值進行相加的過程。所以,變數型別要設定為long long。
- 這道題可以用優先順序佇列進行求解,通過把木板存放在隊列當中,每次在優先順序隊列當中都能取出最大/最小長度的木板。從而可以滿足本題的要求。且優先順序佇列是通過堆來實現的。演算法複雜度為O(nlogn)
這裡提及一下優先順序佇列的標頭檔案以及使用方法
標頭檔案為:queue
定義:priority_queue<Type, Container, Functional>
Type 就是資料型別,Container 就是容器型別(Container必須是用陣列實現的容器,比如vector,deque等等,但不能用 list。STL裡面預設用的是vector),Functional 就是比較的方式,當需要用自定義的資料型別時才需要傳入這三個引數,使用基本資料型別時,只需要傳入資料型別即可,預設是大頂堆(每次佇列取出元素時,都取的是最大值)
//升序佇列(小頂堆)
priority_queue <int,vector<int>,greater<int> > q;
//降序佇列(大頂推)
priority_queue <int,vector<int>,less<int> >q;
//greater和less是std實現的兩個仿函式(就是使一個類的使用看上去像一個函式。其實現就是類中實現一個operator(),這個類就有了類似函式的行為,就是一個仿函式類了)
程式碼如下:
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
int N;
int board; //定義所輸入的木板
long long sum = 0; //定義所需要的最小開銷
bool cmp(int a, int b) { //定義讓優先順序佇列可以每一次都取出最小值的比較函式
return a < b;
}
priority_queue<long long,vector<long long>,greater<long long> > boards; //定義優先順序佇列,可以儲存已經切割好的木板(並且優先順序佇列可以每次取出最小值/最大值(預設是最大值))
int main() {
int i;
long long minboard, minboard2; //代表定義最小的木板和第二小的木板
scanf("%d", &N);
for (i = 0; i < N; i++) {
scanf("%d", &board);
boards.push(board); //將已經切割好的木板新增到優先順序佇列中
}
while (boards.size() != 1) { //當佇列中只剩一個木板時,就退出迴圈(代表已經還原到了初始的木板長度)
minboard = boards.top(); //代表在佇列中取出長度最小的木板
boards.pop(); //刪除長度最小的木板
minboard2 = boards.top(); //取出長度第二小的木板
boards.pop(); //進行木板的刪除
sum += (minboard + minboard2); //將兩個最小的木板進行還原使其成為一個新的木板,並將其最小開銷(新的木板長度)新增到sum變數中
boards.push(minboard+minboard2); //代表將新的木板新增到佇列中,如果此時佇列中還有其他已經分割完的木板的話,就繼續還原。如果沒有,就直接退出迴圈。
}
printf("%lld", sum);
}