堆以及一些用法 QWQ这是写得最认真的板子题

最近一直在学图论,然后吧,由于学的东西实在是太多太杂了,加上蒟蒻本蒻又经常颓,所以落了好多好多板子题的整理没写啊嘤嘤嘤,不过把这些东西学的差不多了,再一块写个整理,其实感觉还不错?????也算是很神奇吧,大概就是知识的积淀这一块有了一点用

好了,话不多说,我们来进入正题

P3378 【模板】堆

我们从板子题入手,慢慢的了解堆(其实是自己巩固而已QWQ)

这里我们学习的堆其实是二叉堆,算是比较狭义的一种定义吧,首先,

堆是一种特殊的二叉树,而且是完全二叉树

为什么我们接触到的比较早的数据结构是堆呢?

其实有这么几个原因:

1.因为堆是一颗完全二叉树,所以在父亲和儿子方面,就完全不需要结构体存树,而是严格按照完全二叉树的定义来就可以

一棵树上的一个节点i,他的儿子是i*2和i*2+1,他的父亲是i/2(此处为整除)

所以处理的时候就很方便啊。

2.c++自带的STL对于堆非常友好,基本上随便写几个STL就能构建一个堆,即使是手写,大约时间也就是十分钟左右,并不是太慢。

3.堆的应用不少,比如堆排序,以及了解一种数据结构,为将来学习可并堆,斐波那契堆打下坚实基础,而且能优化dijkstra(图论).



下面来讲一讲堆的一些基本操作步骤

插入(大雾

我们要对堆插入一个元素,但是并不是扔进去,让尾指针++就完事了,我们还得对堆的正确性进行维护。

来说一下思想,对于添加进来的元素,设其位置为now,那么他爹就是now/2(now>>1  位运算更快哦)

只要比较二者大小,如果新元素更小,那么就交换即可,否则意味着合法,我们直接退出循环就可以,循环的终止条件就是now!=1(因为当now==1时,它已经是堆首元素,没爹。。。。多苦的一孩子(大雾)

来看代码

inline void add(int x)
{
    Heap[++cnt] = x;//这里小小的压了一下行
    int now = cnt;
    while (now!=1)
    {
        if (Heap[now] < Heap[now >> 1])
            swap(Heap[now], Heap[now >> 1]),now>>=1;
        else
            break;
    }
}

弹出

这个也要分为两种,一种是弹出堆首元素,另一种是弹出任意位置的元素(这种一般与寻找元素相结合,考察对DFS,BFS之类的搜索方法的能力)

先看弹出堆首元素,

想要弹出的话,我们就把最小值修改为INF(我比较喜欢1e9),然后和之前相反,向下比较直到找到合适为止为止。

具体讲一讲

因为小根堆的要求是所有根节点都得比他孩子小,所以我们定义首节点的位置为root=1,因为已经置成INF了,所以我们向下开始比较;

先让两个孩子比,最小的那个再和root比,如果比root小,那么就交换二者,否则符合条件就直接退出循环,这里的循环终止条件是   root << 1 <= cnt,也就是说root的儿子已经比当前的堆的长度大了,也就是不存在儿子了

但是这种方法其实不是很好啊,因为你排到最后,最底下就一大堆INF,难看的要死还占空间,倒不如直接交换首元素和尾元素,然后直接把尾指针减一就可以,这样的话最小值就被删除了,之后进行一下动态维护就可以。

来看代码

inline void pop()
{
    Heap[1] = Heap[cnt--];
    int root = 1;
    while (root << 1 <= cnt)
    {
        int son;
        if ((root << 1) + 1 > cnt ||
                  Heap[root << 1] < Heap[(root << 1) + 1])
        {
            son = root << 1;
        }
        else
            son = (root << 1) + 1;
        if (Heap[son] > Heap[root])
            break;
        swap(Heap[root], Heap[son]);
        root = son;
    }
}

输出堆首元素

这东西其实没啥好讲的,因为堆首元素就肯定是Heap[1]嘛,知道就行,然后就可以输出了,因为不对堆中元素进行移动和修改,是不影响堆的合法性的。

这些都看的差不多了,就直接略微修改,板子题就切掉啦

#include <iostream>
#include <queue>
#include <algorithm>
#include <cstdio>
using namespace std;
const int INF = 214748367;
int n, a, b, Heap[1000001], cnt = 0;
inline void add(int x)
{
    Heap[++cnt] = x;
    int now = cnt;
    while (now!=1)
    {
        if (Heap[now] < Heap[now >> 1])
            swap(Heap[now], Heap[now >> 1]),now>>=1;
        else
            break;
    }
}
inline void print()
{
    printf("%d\n", Heap[1]);
}
inline void pop()
{
    Heap[1] = Heap[cnt--];
    int root = 1;
    while (root << 1 <= cnt)
    {
        int son;
        if ((root << 1) + 1 > cnt ||
                  Heap[root << 1] < Heap[(root << 1) + 1])
        {
            son = root << 1;
        }
        else
            son = (root << 1) + 1;
        if (Heap[son] > Heap[root])
            break;
        swap(Heap[root], Heap[son]);
        root = son;
    }
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
    {
        scanf("%d", &a);
        if (a == 1)
        {
            scanf("%d", &b);
            add(b);
        }
        if (a == 2)
            print();
        if (a == 3)
            pop();
    }
    return 0;
}

还是肥肠感谢gh神仙的热心帮忙啊,,,,QWQ一个板子题交了三四遍才过,真的是太辣鸡了

看完板子题,我们来看看合并果子这个题

P1090 合并果子

这个题在学完堆之后就好做多了,不过蒟蒻我很早以前做的时候是用dp加快排做的,那叫一个惨不忍睹啊,,,,,,,,连个样例都没过,所以干脆也就没有提交记录了

但是我们要分析分析,为什么这种算法会TLE

思考一下,我们对于,每一堆果子的个数进行排序,这样的话,先合并前两个果子,看似没什么问题,但是当你合并完了之后,你又得干啥呢???

我当时就傻乎乎的又用sort,然后就连本地编译都会超时,我们分析一下时间复杂度,其实就是O(nlongn+(n-1)log(n-1)+(n-2)log(n-2)+.....+log 1)

这样的话就肥肠慢了啊,因为每一次快排都是遍历了所有的数,但是其实很大一部分都是有序的,所以快排也没什么用,反而浪费时间。

那么用堆解决就没有任何问题了

每次合并前两个小的集合,然后把合并完的集合加到ans中,再扔回堆进行维护,这道题就做完啦

上代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std; 
int n,ans,Heap[10010],num,cnt=0,sum;
inline void add(int x)
{
    Heap[++cnt] = x;
    int now = cnt;
    while (now!=1)
    {
        if (Heap[now] < Heap[now >> 1])
            swap(Heap[now], Heap[now >> 1]),now>>=1;
        else
            break;
    }
}
inline void pop()
{
    Heap[1] = Heap[cnt--];
    int root = 1;
    while (root << 1 <= cnt)
    {
        int son;
        if ((root << 1) + 1 > cnt ||
                  Heap[root << 1] < Heap[(root << 1) + 1])
        {
            son = root << 1;
        }
        else
            son = (root << 1) + 1;
        if (Heap[son] > Heap[root])
            break;
        swap(Heap[root], Heap[son]);
        root = son;
    }
}
int main()
{
   
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d",&num);
        add(num);
    }
    while(cnt>=2)
    {
        sum+=Heap[1];
        pop();
        sum+=Heap[1];
        pop();
        ans+=sum;
        add(sum);
        sum=0;
    }
    printf("%d",ans);
    return 0;
}

想看堆排的同学看这里

 

posted @ 2019-04-29 12:57  Emiya_Shirou  阅读(348)  评论(0编辑  收藏  举报