基础数据结构3.3——堆

题目上添加了超链接,大家点一下题目就会自动跳转到Poj原题界面~~              冲鸭冲鸭ヾ(◍°∇°◍)ノ゙。

前言:

我觉得想要理解堆,大家需要先学会堆排序,在教学过程中一般是放在树(图)后面的。如果大家理解困难可以先跳过

堆(heap)是一种特别的完全二叉树,如果完全二叉树中每个节点的值都小于等于其子节点的值,称为最小堆(min heap);反之,如果每个节点的值都大于等于其子节点的值,称为最大堆(max heap)

C++ STL中的优先队列priority_queue就是堆的实现,对于堆的应用可以直接使用优先队列,需要包含queue头文件。

优先队列应用广泛,是贪心算法的重要组成部分。例如,在图算法中,使用优先队列解决基于贪心思想的最小生成树等问题。

一般什么时候用到堆?当题目要求我们需要对一组数据多次反复操作(动态改变),并且每次操作(改变)后依旧要求单调有序。

3.3.1 Fence Repair (3253)

题意:将一个长木板进行n-1次切割,切成n个小木板,切木板的花费等于木板的长度。给出最后切割后的长度,求费用最小的切割方案。

小笔记:堆排平均复杂度O(nlogn),题目的主要操作是需要从集合中反复取出最小的两个值,每次都重新排序的话算法复杂度会很高,采用堆就能很好的解决这个问题。

#include <cstdio>
#include <queue>
using namespace std;
int main()
{
    int n;
    long long a, ans = 0;
    priority_queue<long long, vector<long long>, greater<long long> > Q;
    scanf("%d", &n);
    while (n--)
    {
        scanf("%lld", &a);
        Q.push(a);
    }
    while (Q.size() > 1)
    {
        long long x = Q.top();
        Q.pop();
        long long y = Q.top();
        Q.pop();
        ans += x + y;
        Q.push(x + y);
    }
    printf("%lld
", ans);
    return 0;
}

  

3.3.2 Argus (2051)

题意:输入格式Register n p,其中n代表查询序号,p代表周期,每隔p输出一个n,给出1~k间隔,依次打印输出结果,如果同一时间有多个输出,则按n值从小到大输出。

小笔记:这道题坑挺多的,建议大家自己做一下试试。

#include <cstdio>
#include <queue>
using namespace std;
struct Data
{
    int n; //查询序号
    int p; //周期
    int t; //访问时间
    Data(int a, int b) : n(a), p(b), t(b) {}
    bool operator<(const Data &a) const
    {
        return (t > a.t) || (t == a.t && n > a.n);
    }
};
int main()
{
    priority_queue<Data> Q;
    char s[10];
    while (scanf("%s", s) && s[0] != '#')
    {
        int n, p;
        scanf("%d%d", &n, &p);
        Q.push(Data(n, p));
    }
    int k;
    scanf("%d", &k);
    while (k--)
    {
        Data a = Q.top();
        Q.pop();
        printf("%d
", a.n);
        a.t += a.p;
        Q.push(a);
    }
    return 0;
}

  

3.3.3 Black Box (1442)

题意:有一个黑盒子处理一些命令,命令有两种

ADD(x):将x放入黑盒子;

GET:从1开始,第i次GET操作是从把黑盒子里面第i小的数字输出。

a[1…m]表示要放入黑盒子的m个数,u表示第i次GET操作时黑盒子里的数字数量。

小笔记:这道题上了难度。堆能保证每加入一个元素,集合里面的元素都保持有序,但是堆只能取堆顶元素,无法取到中间元素。这里就需要将序列分为两个堆,一个最大堆一个最小堆。

#include <cstdio>
#include <queue>
using namespace std;
int main()
{
    priority_queue<int> R;                         //最大堆
    priority_queue<int, vector<int>, greater<int>> L; 	// 最小堆
    int m, n;
    scanf("%d%d", &m, &n);
    int a[30005];
    for (int i = 0; i < m; i++)
        scanf("%d", &a[i]);
    int i = 0;
    while (n--)
    {
        int u;
        scanf("%d", &u);
        //先将小于u数量的数放入L
        while (i < u)
            L.push(a[i++]);
        //随时调整,保证R中元素数量为i-1
        while (!R.empty() && R.top() > L.top())
        {
            int t = R.top();
            R.pop();
            R.push(L.top());
            L.pop();
            L.push(t);
        }
        printf("%d
", L.top());
        R.push(L.top());
        L.pop();
    }
    return 0;
}

  

3.3.4 Sequence (2442)

题意:m个序列,每个序列包含n个非负整数,从每个序列中各取一个数加到一起,这样的和有n^m个,要求从小到大输出前n个和。

小笔记:最直观的做法是枚举所有的和之后排序,但如此做必然一发TEL啊。用贪心可以解决这个问题,优化第一步,题目是在nm个和中求前n个,所以在计算过程中维持一个数量为n的集合就可以了,我们只需要不断计算得到一个n的最大堆即可。

#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        priority_queue<int> Q;
        int m, n;
        scanf("%d%d", &m, &n);
        int a[105][2005];
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
                scanf("%d", &a[i][j]);
            sort(a[i], a[i] + n);
            for (int j = 0; i && j < n; j++)
                for (int k = 0; k < n; k++)
                {
                    if (j)
                    {
                        if (Q.top() > a[i][j] + a[0][k])
                            Q.pop();
                        else
                            break;
                    }
                    Q.push(a[i][j] + a[0][k]);
                }
            for (int k = n - 1; i && k >= 0; k--)
            {
                a[0][k] = Q.top();
                Q.pop();
            }
        }
        for (int i = 0; i < n; i++)
            printf("%d ", a[0][i]);
        printf("
");
    }
    return 0;
}

  

3.3.5 Moo University - Financial Aid (2010)

题意:c头小牛申请补助,每个小牛有分数和需求两个属性,要求按分数选出n头小牛,n为奇数,它们的总需求不超过f,且位于中位数的分数尽可能小。输出符合条件的分数,如果找不到,输出-1。

小笔记:这题好像数据比较弱,二分答案就能AC。实际挺劝退的。

题解:将小牛的信息保存到二元组a中,两个属性分别是分数和需求,将a按分数递减排序,排序后对a的每个位置进行枚举,分别计算前n/2个元素的需求的和lsum与后n/2个元素的需求的和rsum。lsum和rsum要保证值尽量小,求解过程可以通过两个最大堆来实现。

#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 100005;
bool cmp(pair<int, int> a, pair<int, int> b)
{
    return a.first > b.first;
}
int main()
{
    int n, c, f;
    scanf("%d%d%d", &n, &c, &f);
    n /= 2; //为了方便,将n/2赋给n
    pair<int, int> a[N];
    for (int i = 0; i < c; i++)
        scanf("%d%d", &a[i].first, &a[i].second);
    sort(a, a + c, cmp);
    //从前往后将a中前n/2个元素d值插入L中并计算lsum值
    int lsum[N]; //保存a中某位置前n/2个元素的d的和
    int rsum[N]; //保存a中某位置后n/2个元素的d的和
    priority_queue<int> R, L;
    for (int i = 0; i < n; i++)
    {
        lsum[n] += a[i].second;
        L.push(a[i].second);
    }
    //对于接下来的元素,和堆顶值进行比较,小于堆顶值则将堆和lsum更新
    for (int i = n; i < c - n; i++)
    {
        lsum[i + 1] = lsum[i];
        if (a[i].second < L.top())
        {
            lsum[i + 1] = lsum[i] + a[i].second - L.top();
            L.pop();
            L.push(a[i].second);
        }
    }
    //从后往前将a中后n/2个元素d值插入R中并计算rsum值
    for (int i = c - 1; i > c - n - 1; i--)
    {
        rsum[c - n - 1] += a[i].second;
        R.push(a[i].second);
    }
    //对于接下来的元素,和堆顶值进行比较,小于堆顶值则将堆和rsum更新
    for (int i = c - n - 1; i > n - 1; i--)
    {
        rsum[i - 1] = rsum[i];
        if (a[i].second < R.top())
        {
            rsum[i - 1] = rsum[i] + a[i].second - R.top();
            R.pop();
            R.push(a[i].second);
        }
    }
    //在满足a[i].second+ lsum [i]+ rsum [i]<f条件情况中取最小的i就是题解,如果所有i都不满足条件,则输出-1
    int i;
    for (i = n; i < c - n && lsum[i] + a[i].second + rsum[i] > f; i++)
        ;
    printf("%d
", (i == c - n) ? -1 : a[i].first);
    return 0;
}

  

原文地址:https://www.cnblogs.com/thx2199/p/15116635.html