菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
228
0

优先队列——priority queue

原创
05/13 14:22
阅读数 31920

优先队列朴素版之简单应用

合并果子

题意:

n个果子,数目为tr[i],进行n - 1次合并操作,每次都消耗两堆果子的重量和的体力,耗费的总体力等于每次合并所耗费的体力和,求最小值

思路1:

使用秘技STL,priority_queue来操作,但这个优先队列是从大到小的,有一个非常非常非常简便的方法来改变其顺序——加负号!!!

u1s1,STL针不戳

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 1000000 + 5
typedef  long long ll ;
priority_queue<int>q;
int main()
{
    int n, m;
    cin>>n;
    for(int i = 1; i <= n; ++i){
        cin>>m;
        q.push(-m);
    }
    ll ans = 0;
    for(int i = 1; i < n; ++i){
        int x = -q.top();
        q.pop();
        int y = -q.top();
        q.pop();
        ans += x + y;
        q.push(-x - y);
    }
    cout<<ans<<endl;
    return 0;
}

思路2:

手撕优先队列。

首先我们要知道优先队列其实是一种堆,是一棵完全二叉树,而这个二叉树又满足一个规律——任意节点都小于(或大于)其子节点

这个题我们要用到插入,弹出,取顶三个操作

插入

对于一个新的数插进来,我们不能坏了优先队列的顺序,所以就插在最后,然后和他的父节点进行比较,看看需不需要交换,一直换下去

弹出

对于弹出的数是队首,也就是这里面最小的(或者最大的数),所以可以直接把最后一个元素拿过来替代他,然后再和他的子节点比较,这里会出问题,因为上次是和父节点比较(众所周知,你只有一个爹,但你爹不一定只有你一个孩子,是不是很形象o(≧v≦)o),只有一个父节点,而现在和子节点比较,对于升序的优先队列,我们得取小的子节点与父节点交换

取顶

就直接取q[1]即可

注意一些边界条件

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 1000000 + 5
typedef  long long ll ;

inline int IntRead(){
    char ch = getchar();
    int s = 0, w = 1;
    while(ch < '0' || ch > '9'){
        if(ch == '-') w = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        s = s * 10 + ch - '0';
        ch = getchar();
    }
    return s * w;
}

int n,q[MAX],tot, ans;

void push(int x){//塞新的数进去
    q[++tot] = x;
    int i = tot, j = i / 2;//i是子节点,j是父节点
    while (j > 0 && q[j] > q[i]) {//没有出界且父亲节点的值大于子节点的值就交换
        swap(q[i], q[j]);
        i=j;j=i/2;//原来的父亲节点就成了现在的子节点,而现在的子节点是父节点除以2所得
    }
}

int top(){//取顶
    return q[1];
}

void pop(){//弹出队首
    q[1] = q[tot];
    tot--;
    int i = 1, j = 2 * i;//i是父节点,j是子节点
//可能i的另一个子节点k比j小,此时就是换i和k,所以要判断一下谁更小
    if (j + 1 <= tot && q[j] > q[j + 1]) {
        j++;
    }
    while (j <= tot && q[j] < q[i]) {
        swap(q[i], q[j]);
        i = j;
        j = i * 2;
        if(j + 1 <= tot && q[j] > q[j + 1]) j++;//不能忘
    }
}

int main()
{
    cin>>n;
    for(int i = 1; i <= n; ++i){
        int x;cin>>x;
        push(x);
    }
    for(int i = 1; i < n; ++i){
        int x = top();
        pop();
        int y = top();
        pop();
        ans += x + y;
        push(x + y);
    }
    cout<<ans<<endl;
    return 0;
}

优先队列进阶版之对顶堆

对顶堆

实质上是通过维护两个堆动态处理中位数等问题

大根堆Q1:根最大,维护的是集合中较小部分的最大值(即顶堆)

小根堆Q2:根最小,维护的是集合中较大部分的最小值(同堆顶)

堆中元素都是单调的,与此同时,两个堆之间也是单调的,大根堆中任意一个元素都小于小根堆的任意一个元素

从下往上值越大

RuningMedian

题意:

给一堆数,输出前i个数中的中位数(i 是奇数)

思路:

中位数指的是一堆数中从小到大排序后(或降序)中间的数或中间两个数的平均值

所以求中位数,需要排序,但肯定不能每求一次就拿出前i个数进行排序,所以就需要另辟蹊径。

也就是需要一个数据结构,能做到快速插入数据,并快速调整,同时可以动态获取中位数

我们知道。堆是一种极其有用的数据结构(虽然我不不会(╥﹏╥)),他能在短时间内将数据维护成单调递增或递减的序列,但是一个堆并不能简单的解决本题。而两个堆做得到,中位数将序列分成两部分数量相同的序列,我们可以用两个堆将他们存起来。

因为小堆Q2是堆顶最小,大堆Q1是堆顶最大,所以对于一个新来的数x,得不能破坏他们的单调性,就拿新来的数x和两个队顶比较,如果x小于Q1的堆顶,就扔进Q1里面,反之扔进Q2里面。为了防止出现一“边倒现象”,我们就再每次插入数之后判断一下,看看有没有哪个堆的数量比另一个堆大2及以上的,如果有,多几个就扔几个堆顶进去。如果是奇数次的话,就可以存中位数,我使用的vector(数组开10000 都能爆?!我服气,只能开vector)。

这个题的输出格式也很恶心心(╥﹏╥)

每行最多十个,一行内的数与数用空格替代,但是他最后一个数得有空格是什么鬼???题目没说,但是我把那个空格去掉就PE了,加上就AC了!!!毫无AC体验(⌒-⌒; )

而且你得判断一下最后用不用输换行,可能你没够十个,就不会换行,就会继续输出

#include<bits/stdc++.h>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 1000000 + 5
typedef  long long ll ;

priority_queue<int>q1;//大根堆
priority_queue<int, vector<int>, greater<int>>q2;//小根堆

//不知道为什么,我写这个push函数,开始写的时候,我的Xcode报错,扔牛客又说我RE,最后我怎么看都感觉没问题,最后他又自己好了???我tm

void push(int x){//塞数函数
    if(!q1.size() || x < q1.top())q1.push(x);
    else q2.push(x);
    if(q1.size() > q2.size() + 1){
        q2.push(q1.top());
        q1.pop();
    }
    if(q2.size() > q1.size() + 1){
        q1.push(q2.top());
        q2.pop();
    }
}

vector<int>v;

int main()
{
    int t, x, m , n;
    cin>>t;
    while (t--) {
        v.clear();//多组输入,清0万岁
        while (q1.size()) {
            q1.pop();
        }
        while (q2.size()) {
            q2.pop();
        }
        cin>>m>>n;
        cout<<m<<' '<<(n + 1) / 2<<endl;
        for(int i = 1; i <= n; ++i){
            cin>>x;
            push(x);
            if(i % 2 == 1){//奇数次就存
                if(q1.size() > q2.size())//找多的那个
                    v.push_back(q1.top());
                else
                    v.push_back(q2.top());
            }
        }
        for(int i = 0; i < v.size(); ++i){
            cout<<v[i];
            if((i + 1) % 10 != 0 && i + 1 != v.size())
                cout<<' ';
            else
                cout<<'\n';
        }
        if(v.size() % 10)
            cout<<'\n';
    }
    return 0;
}

黑匣子

题意:

两种操作,ADD(x):把x元素放进数组里面

GET:i + 1,然后输出数组中第i小的数

思路:

动态求第k小的数,可以用对顶堆来解决

需要的是从小到大的第i个数,所以我们保持大根堆有i - 1个元素,每次只需要输出小根堆的堆顶即可

有朋友可能会问,为什么不直接保持大根堆有i个元素呢?

那是因为,可能你最后塞进大根堆的那个元素x,使得小根堆中有元素比他还小,所以就需要把第i个扔进小根堆,进行排序,这样大根堆有i-1个,我们只需要输出小根堆的堆顶即可!

#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <stdlib.h>
#include <sstream>
#include <map>
#include <set>
using  namespace std;
#define inf 0x3f3f3f3f
#define MAX 1000000 + 5
typedef  long long ll ;

inline int IntRead(){
    char ch = getchar();
    int s = 0, w = 1;
    while(ch < '0' || ch > '9'){
        if(ch == '-') w = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9'){
        s = s * 10 + ch - '0';
        ch = getchar();
    }
    return s * w;
}

priority_queue<int>q1;//大
priority_queue<int, vector<int>, greater<int> >q2;//小
int tr[MAX];

int main()
{
    int n, m;
    cin>>m>>n;
    for(int i = 1; i <= m; ++i){
        tr[i] = IntRead();
    }
    int r = 1, q;
    for(int i = 1; i <= n; ++i){
        cin>>q;
        for(int j = r; j <= q; ++j){
            q1.push(tr[j]);//扔到大根堆中去从小到大排序
//如果大根堆的数量达到i个,我就只能把栈顶的扔到小根堆中再去排序,最后会发现,大根堆中有i-1个数,所以第i大的数就是小根堆的堆顶
            if(q1.size() == i){
                q2.push(q1.top());
                q1.pop();
            }
        }
        cout<<q2.top()<<endl;//取小根堆的堆顶
        r = q + 1;//更新r的值
        q1.push(q2.top());
        //因为下一次i会++,大根堆需要的数也会增加,但为了避免像样例一样出现两个相同的数字而导致不会进入循环,使得大根堆的数量得不到增加而导致的错误,就先将小根堆的堆顶扔到大根堆去
        q2.pop();
    }
    return 0;
}

发表评论

0/200
228 点赞
0 评论
收藏
为你推荐 换一批