• 基础数据结构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;
    }
    

      

  • 相关阅读:
    Python基础之文件、目录
    Python基础知识之基本类型、循环
    Python基础知识之函数、模块
    Python基础之小知识要点
    【Android】ADB常用指令与logcat日志(转)
    Android 中的 Service 全面总结 (转)
    Android实现双进程守护 (转)
    Android adb常见问题整理(转)
    Android代码内存优化建议-OnTrimMemory优化
    优化Android应用内存的若干方法
  • 原文地址:https://www.cnblogs.com/thx2199/p/15116635.html
Copyright © 2020-2023  润新知