优先队列——priority queue
优先队列朴素版之简单应用
合并果子
题意:
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;
}