題目的意思是。要把一塊木板切成所需要的n塊,但是每切一刀需要收費。收費的方法是(網上有一些人理解有誤):
每當在一塊木板上動刀子,這塊木板的原有長度代表了這刀的費用。
思路:
切割的過程可以用一棵二叉樹表示,每個節點有兩個或者沒有子節點,
節點存儲切割過程中每塊木板的長度。父節點的值=兩個子節點相加
根節點是原始木板長度。每塊所需要的木板都是葉子節點。
非葉子節點的值之和就是要花的總價格。
定理1:
如果要使得總價格最少,就要求高層(靠近根)的每一個節點的值 大於 低層每一個節點的值
證明:
運用反證法,假如某一棵二叉樹,總價格最低,且不滿足定理1。
假如第N層a節點大於第M層b節點,N>M a-b = c
那麼,我們可以構造出一棵二叉樹,即把第N層的a節點和M層的b節點交換(如果是非葉子節點,交換子樹),之後保證 父節點的值=兩個子節點相加 。
a值的減少c會使得自己的每個先輩節點減少c, b值增加c也會使自己每個先輩節點增加c。由於a在b的下層,導致a的先輩比b的先輩多,
每個先輩節點必定不是葉子節點,這就使得總價格降低,和原二叉樹總價最低矛盾。
定理2:
每一顆二叉樹,只要高層(靠近根)的每一個節點的值 大於 低層每一個節點的值,那麼這些二叉樹所代表的總價值相同。
證明:
原理同定理1,通過同一層的節點(或者子樹)相互交換,構造出的任意一棵滿足要求的二叉樹,
由於先輩節點值增加,減少的值相互抵消,所以總價值不變。
由於哈弗曼樹滿足我們所需要的二叉樹的性質(這也同時說明,滿足總費用最少的二叉樹不一定是哈夫曼樹),所以這道題用哈夫曼樹來解。
int find_small(int arr[], int len, int &a, int &b){//a是最小的,b是第二小的
a=b=99999;
int a_index, b_index, i;
a_index = b_index = 0;
for(i=0; i<len; ++i){
if(arr[i]!=-1&&arr[i]<a){
b_index = a_index;
a_index = i;
b = a;
a = arr[i];
} else if(arr[i]!=-1&&arr[i]<b){
b_index = i;
b = arr[i];
}
}
arr[a_index] = arr[b_index] = -1;
return a_index;
};
int main(){
int n,cnt;
int arr[100];
cin>>n;
for(int i=0; i<n; ++i){
cin>>arr[i];
}
cnt = n;
int w=0;
if(cnt==1){
w = arr[0];
}
while(cnt!=1){
--cnt;
int a,b,hole;
hole = find_small(arr,n,a,b);
w = w+a+b;
arr[hole] = a+b;
}
cout<<w;
}