1. 程式人生 > >【哈弗曼樹】 WOJ2343 圍欄維修

【哈弗曼樹】 WOJ2343 圍欄維修

【描述】

農民 John 希望修復圍繞農場的一小段圍欄。他測量了一下,發現需要N (1 <= N <= 20,000) 根木頭,每根都有某一個整數長度 Li (1 <= Li <= 50,000) 單位長度。 他買了一根很長的很長的木頭,正好能夠鋸出他所需要的N根木頭。(即它的長度正好等於 Li的總和) FJ 忽略鋸口,鋸掉的木屑產生的長度損失忽略不計,你也可以忽略它。

FJ 遺憾的發現他自己沒有用於切木頭的鋸子,所以他就帶著那根很長的木頭來到了農民 Don 的農場,想問他借一個鋸子。

農民 Don是一個保守的資本家,他不願意借鋸子給 FJ ,但願意自己來切這N-1刀,每一次 都向FJ收取費用。每次的收費正好等於你要鋸的那根木頭的總長度。例如,你要鋸一根長度 為21的木頭,就花費21分錢。

農民 Don 然後讓農民 John 自己決定每次鋸木頭的順序和位置。幫助農民 John 確定鋸出 這N根木頭的最小總花費。 FJ 知道可以有很多種不同的切割方式,不同的方式可能得到 不同的總花費,這是因為木頭在鋸的過程中的長度不一。

【輸入】

  • Line 1: 一個整數 N,表示要鋸出的木頭數

  • Lines 2..N+1: 每行一個整數,表示每根木頭的長度。

【輸出】

  • Line 1: 一個整數,表示他最少需要多少分錢,鋸N-1下,鋸出所有需要的木頭。

【樣例輸入】[複製]

3
8
5
8

【樣例輸出】[複製]

34

【提示】

輸出解釋:

原本的木頭長度為 8+5+8=21。第一次鋸的花費是 21,應該切成兩段長度分別是13和8。 第二次花費是13,把長度是13的木頭鋸成8和5。總花費是21+13=34。但如果先將21鋸成 16和5,第二次將花費16,導致總花費達到37 (大於34)。

【思路】倒著貪心。我們最後得到了N個木頭。每次當前取出最小的兩個合併之後再丟回去。最後合併成一個木頭。一定是最優解。正確性大概就是哈弗曼樹的最優性證明。具體實現用一個堆維護就行了。注意統計費用開longlong。

本題可以轉化為一個哈弗曼樹的構造。具體大概就是一個有n個葉子結點的二叉樹。每個葉子結點有一個權值,每個葉子結點的貢獻就是【葉子結點到根節點的距離】乘上【點權】。現在要讓這個總費用最小。如上所述。。。。

#include<bits/stdc++.h>
using namespace std;
int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return (x*(-1));
}
int N;long long ans=0;
priority_queue<int> Q;
int main(){
	scanf("%d",&N);
	for(int i=1;i<=N;++i) Q.push(read());
	while(Q.size()>=2){
		int u=Q.top();Q.pop();
		int v=Q.top();Q.pop();
		ans-=u+v;
		Q.push(u+v);
	}
	cout<<ans<<'\n';
}