数据结构番外篇【stl应用(1)】优先队列
stl是一种重要技巧,可以极大地简化编程过程
在总结stl之前,我们先简单介绍一下迭代器。
迭代器可以简单理解为地址的等价物。
在不同数据类型中迭代器支持的操作略有不同
其中vector使用的是随机访问迭代器,其支持的操作可以参考上述表格
虽然本文用不上预备知识,但是还是先说一下吧
接下来介绍常用stl——优先队列
1.优先队列
优先队列(英文priority_queue)是一种维护集合最大最小值的数据结构(堆)
它能够在O(1)时间得到一个集合的最大(小)值,在O(logn)时间加入新的元素或者删除最大(小)的元素
常见操作如下:
优先队列默认为大根堆,即维护最大值
#include <algorithm>
#include <vector>
#include <queue>
#include <iostream>
using namespace std;
priority_queue<int> q1;//默认大根堆
priority_queue<int,vector<int>,less<int> > q2;//等价的大根堆
priority_queue<int,vector<int>,greater<int> > q3;//小根堆
int main()
{
int n;
scanf("%d",&n);
for(int i = 1; i <= n; i ++)
{
int x;scanf("%d",&x);
q1.push(x);
q2.push(x);
q3.push(x);//插入新元素
}
while(!q1.empty())//检查是否非空
{
printf("%d\n",q1.top());//返回最大(小)值
q1.pop();//删除最大(小)值
}
while(q3.size()>0)//返回元素个数
{
printf("%d\n",q3.top());
q3.pop();
}
}
优先队列的优化十分优秀,很多情况下甚至比手写heap还要快
例题
洛谷P3871 中位数
题目描述
给定一个由N个元素组成的整数序列,现在有两种操作:
1 add a
在该序列的最后添加一个整数a,组成长度为N + 1的整数序列
2 mid
输出当前序列的中位数
中位数是指将一个序列按照从小到大排序后处在中间位置的数。(若序列长度为偶数,则指处在中间位置的两个数中较小的那个)
例1:1 2 13 14 15 16 中位数为13
例2:1 3 5 7 10 11 17 中位数为7
例3:1 1 1 2 3 中位数为1
输入输出格式
输入格式:
第一行为初始序列长度N。第二行为N个整数,表示整数序列,数字之间用空格分隔。第三行为操作数M,即要进行M次操作。下面为M行,每行输入格式如题意所述。
输出格式:
对于每个mid操作输出中位数的值
https://www.luogu.org/problemnew/show/P3871
这道题有很多做法,在此给出简单且时间效率高的优先队列做法
将数组按照从小到大排序,将较小的一半放入一个大根堆,将较大的一半放入一个小根堆
每次添加数的时候与两个堆堆顶元素比较,将其放入相应的堆中
确保左堆的元素个数>=右堆的元素个数>=左堆元素个数-1
若打破了这个条件,只需将某堆中的堆顶元素换到另一个堆即可
每一次询问输出左堆堆顶
#include <iostream>
#include <algorithm>
#include <set>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
priority_queue <int,vector<int>,greater<int>> qright;
priority_queue <int,vector<int>,less<int>> qleft;
vector <int> v;
int main()
{
int n,m;
scanf("%d",&n);
v.push_back(0);
for(int i = 1; i <= n ;i++)
{
int x;
scanf("%d",&x);
v.push_back(x);
}
sort(v.begin()+1,v.end());
for(int i = 1; i <= n/2; i ++)qleft.push(v[i]);
for(int i = n/2+1; i <= n; i ++)qright.push(v[i]);
scanf("%d",&m);
while(m--)
{
char str[10];
scanf("%s",str);
if(str[0]=='m')
{
printf("%d\n",qleft.top());
}
else
{
int x;
scanf("%d",&x);
if(x<=qleft.top())
{
qleft.push(x);
}
else qright.push(x);
int a = qleft.size(),b=qright.size();
if(a-b>1)
{
qright.push(qleft.top());
qleft.pop();
}
else if(b>a)
{
qleft.push(qright.top());
qright.pop();
}
}
}
}
https://www.luogu.org/problemnew/show/P1484
洛谷P1484 种树
题目描述
cyrcyr今天在种树,他在一条直线上挖了n个坑。这n个坑都可以种树,但为了保证每一棵树都有充足的养料,cyrcyr不会在相邻的两个坑中种树。而且由于cyrcyr的树种不够,他至多会种k棵树。假设cyrcyr有某种神能力,能预知自己在某个坑种树的获利会是多少(可能为负),请你帮助他计算出他的最大获利。
输入输出格式
输入格式:
第一行,两个正整数n,k。
第二行,n个正整数,第i个数表示在直线上从左往右数第i个坑种树的获利。
输出格式:
输出1个数,表示cyrcyr种树的最大获利。
这道题采用的思想是在一棵树被种之后,要改种左右两颗树获利等于左右两树之和减去这棵树,我们把这个获利再当成之前被种的树的获利放入堆中,并且标记左右两棵树无效。
最后k次取堆顶元素求和
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
#define N 500004
int f[N],l[N],r[N],n,k,cnt,tot;
long long ans;
bool vis[N<<2];
struct num
{
int pos,val;
bool operator < (const num b)const
{
return val<b.val;
}
}buf;
priority_queue <num> heap;
int main()
{
scanf("%d%d",&n,&k);
for(int i = 1; i <= n; i ++)
{
scanf("%d",f+i);
heap.push((num){i,f[i]});
vis[i]=1;
r[i]=i+1;
l[i]=i-1;
}
for(int i = 1; i <= k ; i ++)
{
while(!heap.empty()&&!vis[heap.top().pos])heap.pop();
if(heap.empty())break;
buf=heap.top();
heap.pop();
vis[r[buf.pos]]=vis[l[buf.pos]]=0;
if(buf.val<0)break;
ans+=buf.val;
heap.push((num){buf.pos,f[buf.pos]=f[r[buf.pos]]+f[l[buf.pos]]-buf.val});
r[buf.pos]=r[r[buf.pos]];l[r[buf.pos]]=buf.pos;
l[buf.pos]=l[l[buf.pos]];r[l[buf.pos]]=buf.pos;
vis[buf.pos]=1;
}
cout<<ans;
}