一道最小堆问题

全部相加B811
试题描述
 有 n 个正整数的集合 S,每次可以从 S 中删除两个数,然后把它们的和放回集合,直到剩下一个数。每次操作的开销等于删除的两个数之和,求最小总开销。所有数均小于 105
输入
第一行仅包含一个正整数 n,第二行包含 n 个正整数,两两之间用一个空格分隔。
输出
一个数,表示最小的总开销。
输入示例
5 3 6 2 7 4
输出示例
49
其他说明
数据范围:1 <= n <= 5000 。

 这道题可以说是最小堆的模板题,适合对于堆的初学者(比如我)。

堆是一种优先队列,适合插入元素,快速寻找最大最小值,排序等功能。

本题的要求就是找到两个最大元素,然后插入两数之和,然后向下调整就行了。直到队列中只剩一个数,程序结束。

下面呈上代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int n,a[1000005];
long long ans;

void down(int x)//向下调整
{
    int t,flag=0;
    while(x*2<=n && flag==0)//如果当前点有左儿子
    {
        if(a[x*2]<a[x]) t=x*2;
        else t=x;
        if(x*2+1<=n && a[x*2+1]<a[t]) t=x*2+1;//有右儿子的情况
        if(t!=x) //如果它换过了
        {
            swap(a[t],a[x]);
            x=t;
        }
        else flag=1;//两个儿子都比他小,不用换了
    }
    return ;
}

void creat()//建堆
{
    for(int i=n/2;i>=1;i--)//除了叶节点(n/2个叶节点),每个点都向下调整就行了
        down(i);
} 

int de()//删除堆顶元素
{
    int t=a[1];
    a[1]=a[n];
    n--;//元素数量-1
    down(1);
    return t;
}

void new(int b)//新数
{
    int t;
    n++;
    a[n]=b;
    t=n;
    while(t>1)//新数要向上调整
    {
        if(a[t/2]>a[t]) swap(a[t/2],a[t]);
        t/=2;
    }
}
int main()
{
    int i,num;
    cin>>n;
    num=n;
    for(i=1;i<=n;i++) 
        scanf("%d",&a[i]);
    creat();//建堆
    for(i=1;i<num;i++)
    {
        int a=de(),b=de();//删除最小的两个数
        new(a+b);//将新数插入堆中
        ans+=a+b;
    }
    printf("%d
",ans);
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/llllllpppppp/p/7531410.html