51nod 1117 聪明的木匠:哈夫曼树

题目链接:http://class.51nod.com/Challenge/Problem.html#problemId=1117

一、题目描述

一位老木匠需要将一根长的木棒切成N段。每段的长度分别为L1,L2,......,LN(1 <= L1,L2,…,LN <= 1000,且均为整数)个长度单位。
我们认为切割时仅在整数点处切且没有木材损失。
木匠发现,每一次切割花费的体力与该木棒的长度成正比,不妨设切割长度为1的木棒花费1单位体力。
例如:若N=3,L1 = 3,L2 = 4,L3 = 5,则木棒原长为12。
木匠可以有多种切法,如:
第一种切法先将12切成3+9.,花费12体力,再将9切成4+5,花费9体力,一共花费21体力;
第二种切法还可以先将12切成4+8,花费12体力,再将8切成3+5,花费8体力,一共花费20体力。
显然,后者比前者更省体力。
那么,木匠至少要花费多少体力才能完成切割任务呢?

输入描述

  第1行:1个整数N(2 <= N <= 50000)

    第2 - N + 1行:每行1个整数Li(1 <= Li <= 1000)。

输出描述

  输出最小的体力消耗。

样例输入

3
3
4
5

样例输出

  19

 

二、解题思路

这道题运用了三个主要概念:“正难则反” 和 “哈夫曼树”和“优先队列”

我们先正着来思考,发现对于最少的体力,应该是最短板被切割的次数最多,因为总的体力花费是每个木棒被切的次数。

可是我们发现很难实现,所以反过来思考,将n个木棒拼接回原来的样子并且花费体力最少,每次也都是需要将两个最短的木棒拼起来。

每次从剩余的木棒中取出最短的两个,拼好之后放回去,这就用到了哈夫曼树的性质。

我们先来复习一下哈夫曼树

给定N个权值(权值是每个节点上的数值)作为N个叶子结点,构造一棵二叉树。

哈夫曼树就相当于找到两个权值最小的节点结合在一起,叶子结点是后面没有节点的节点。

 

以本图为例,节点上的数字,是点的权值Wi。

在这幅图里,叶子结点(绿色的节点)分别为2、4、7、8、12、19、20、30。

而整个树的权值等于所有叶子结点的Wi×Li(Li为这个节点到根节点的距离)的和。

按照本图的分配方法,整棵树的权值为:(2+4)×5+ 7×4 + (8+12+19)×3+(20+30) ×2 = 275。

构造哈夫曼树,需要配合一个优先队列来实现。

在这道题里,我们可以借助哈夫曼树来实现我们的思想:

每次从剩余的木棒中取出最短的两个,拼好之后放回去,这就用到了哈夫曼树的性质。

 

  接下来我们复习一下优先队列

只要是这么写的,就说明q这个优先队列是从小到大排列的:

priority_queue <int,vector<int>,greater<int> > q;                            

现在给优先队列的基本操作(只在本题中使用的基本操作):

priority_queue <long long, vector <long long>, greater <long long> > que;

         定义一个从小到大排列的优先队列

que.push(1);            往优先队列que中存入1这个数

que.size()                 返回que里元素个数

que.top()                   这个只能在优先队列里使用,不能再普通队列里使用。

          如果是从小到大排列的,第一个肯定是最小的,最后一个是最大的。

         那么返回第一个数字(在这道题里是返回最小的)

 

三、代码

#include<queue>
#include<vector>
#include<cstdio>
#include<iostream>
using namespace std;
priority_queue <int, vector<int>, greater<int> > que;
int n, l, first_small, second_small, ans, tmp;

int main(){
    cin >> n;
    for(int i = 0;i < n;i++){
        cin >> l;
        que.push(l);
    }
    while(que.size() > 1){
        first_small = que.top();//找到在所有木板中最短的 
        que.pop();//把这个木板在所有木板里删除 
        second_small = que.top();//找到在所有木板中第二短的  
        que.pop();//把这个木板在所有木板里删除 
        tmp = first_small + second_small;//这两个木板结合起来成为一个新的木板 
        ans += tmp;//往体力值里加上这个木板的长度 
        que.push(tmp);//往所有木板里放入这个崭新的木板 
    }
    cout << ans << endl;//输出体力值 
    return 0;
}
posted @ 2020-05-17 17:03  elisa02  阅读(416)  评论(0编辑  收藏  举报