POJ-3253-Fence Repair(Java+哈夫曼树)

来源:互联网 发布:java打印等边三角形用 编辑:程序博客网 时间:2024/05/16 18:06
Fence Repair
Time Limit: 2000MS Memory Limit: 65536KTotal Submissions: 28971 Accepted: 9408

Description

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

3858

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).

Source

USACO 2006 November Gold


首先说一下题目的大概意思(以下翻译出自《挑战程序设计竞赛》):

Fence    Repair (POJ 3253)
农夫约翰为了修理栅栏,要将一块很长的木板切割成N块。准备切成的木板的长度为L1,L2,...,LN,未切割前木板的长度恰好为切割后木板长度的总和。每次切断木板时,需要的开销为这块木板的长度。例如长度为21的木板要切成长度为5,8,8的三块木板。长21的木板切成长为13和8的板时,开销为21。再将长度为13的板切成长度为5和8的板时,开销是13.于是合计开销是34.请求出按照目标要求将木板切割完最小的开销是多少。

限制条件
1  -  1<=N<=20000
2  -  0<=Li<=50000

输入

N=3,L={8,5,8}

输出
    
34(对应题目中的例子)

由于木板的切割顺序不确定,自由度很高,刚开始看着到题目的时候的确是很难入手。但是其实可以用贪心法求解。仔细想想题目中的一句话,"为切割前木板的长度恰好为切割后木板长度的总和。"这句话暗示我们要联系数据结构中树结构来思考!



以15为例,这里每一个叶子结点就对应了切割出的一块快木板。叶子结点的深度就对应了为了得到对应木板所需的切割次数,开销的合计就是各叶子节点的:

木板的长度*节点的深度

的总和。例如,上图示例的全部开销就可以这样计算:

  3*2+4*2+5*2+1*3+2*3=33

于是最佳切割方法首先应该具有如下性质:
  
                     最短的板与次短的板的节点应当是兄弟节点


对于最优解来讲,最短的板应当是深度最大的叶子节点之一。所以与这个叶子节点同一深度兄弟节点一定存在,并且由于是同样是最深的叶子结点,所以应该对应于次短的板。
不妨将Li按照打小顺序排列,那么最短的板应该是L1而次短的则是L2。如果它们在二叉树中是兄弟节点,就意味着它们是从一块长度为(L1+L2)的板切割得来的。由于切割顺序是自由的,不妨当作是最后被切割。这样以来,在这里切割前就有

(L1+L2),L3,L4,...,LN

这样的N-1块木板存在。与以上讨论方式相同,递归的将这N-1块木板的问题求解,就可以求出整个问题的答案。


解题思路:
利用Huffman思想,要使总费用最小,那么每次只能选取当前最小长度的两个木板相加,再把这些和累加到总费用即可。
但是这样解的复杂度是O(N^2),虽然对于本题的输入规模来说可以通过,但是本题可以用O(N*logN)的时间求解.

因为朴素的Huffman树思想是:
(1)先把输入的所有元素按照升序排列,再选取最小的两个元素,把他们的和累加到总费用。
(2)把这个两个元素出队,再让这两个元素的和进队。重新排列所有元素,重复(1)直至队列中的所有元素<=1,则累计的费用就是最小的费用.。

效率不高的主要原因是每次都要重新排序,极度浪费时间,即使是快排。


HuffmanTree:给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

本题的HuffmanTree结构:
长度为21的木板切割成5,8,8三块木板.

(1)5,8,8这三个输入的数据进行排序。


(2)找到最小的两块木板5,8。


(3)用一个类似于树杈的“树枝”连接上两个最小的数。在顶点处计算出这两个数字的和 并写在上面。然后再比较剩下的数字和这个和的大小,再取出两个最小的数字进行排列。


(4)如果两个数的和正好是下一步的两个最小数的其中的一个那么这个树直接往上生长就可以了。如果这两个数的和比较大不是下一步的两个最小数的其中一个那么,就并列生长。



(5)类似步骤(3)


(6)构造成功!




Java代码(简易HuffmanTree):

import java.io.*;import java.util.*;public class Main{public static void main(String[] args){// TODO Auto-generated method stubScanner input = new Scanner(System.in);int N = input.nextInt();int L[] = new int[N];for (int i = 0; i < N; i++){L[i] = input.nextInt();}solve(L, N);}public static void solve(int L[], int N){long ans = 0;                             //找出最小的两个木板while (N > 1){int mii1 = 0, mii2 = 1;if (L[mii1] > L[mii2]){int temp = L[mii1];L[mii1] = L[mii2];L[mii2] = temp;}for (int i = 2; i < N; i++) // 每次循环的次数,都比前一次少1,此时的N已经改变{if (L[i] < L[mii1]){mii2 = mii1;mii1 = i;} else if (L[i] < L[mii2]){mii2 = i;}}int t = L[mii1] + L[mii2];ans += t;if (mii1 == N - 1){int temp = mii1;mii1 = mii2;mii2 = temp;}L[mii1] = t;L[mii2] = L[N - 1]; // 把最后一个数给提到前面来,防止少比较一个数N--;}System.out.println(ans);}}


0 0
原创粉丝点击