[优先队列]Black Box POJ1442

学长写了一个SQL,这个SQL有一个特殊变量i,初值为0而且只有两种操作:

ADD (x): 把x存入SQL
GET: i=i+1,然后输出第i小的数
现在给你一个长为M的ADD操作序列A,N个时间点序列B,要求在每个B[i]时执行一次GET操作,求输出结果序列

Input

输入第一行有两个数字M, N
接下来是M个数字A(1), A(2), ..., A(M)
再下来是N个数字B(1), B(2), ..., B(N). 这N个数字已经从小到大排序过
本题数据范围均在int内
ADD和GET操作分别最多有30000次

Output

输出每个GET操作取出的值,一行一个值

Sample Input

7 4
3 1 -4 2 8 -1000 2
1 2 6 6

Sample Output

3
3
1
2

Hint

共7个数 4个GET时间点
1时刻 加入3 序列为:3 同时执行一次GET操作 i=1 输出3
2时刻 加入1 序列为:1,3 同时执行一次GET操作 i=2 输出3
3时刻 加入-4 序列为:-4,1,3
4时刻 加入2 序列为:-4,1,2,3
5时刻 加入8 序列为:-4,1,2,3,8
6时刻 加入-1000 序列为:-1000,-4,1,2,3,8 先执行一次GET操作 i=3 输出1,再执行一次GET操作 i=4 输出2

题意: 按时间顺序向数组a中存入n个数字,然后有m次询问,分别在b[i]时询问第i小的数字。

分析: 每加入一个元素都进行一遍排序是不可取的,过于暴力了。为了能高效地获得第k小的数,我们需要一个优先队列,并且这个优先队列大小为k,只存储整个序列中最小的k个数,剩下的数字存储在另外一个优先队列中,这样可以O(1)获得第k小的数。整体思想其实是用两个优先队列来拼成一个优先队列,示意图如下:

优先队列1是一个小根堆,大小为k且只保存最小的k个数,此时队头就是整个序列中第k小值,优先队列2为大根堆,保存剩余的所有元素,队头就是第k+1小值,这样设置是为了方便下面的维护操作。有了数据结构接下来看插入和查询是如何实现的,对于插入操作要考虑优先队列1中有否已满k个元素,若未满直接push进优先队列1,若满了再看待插入元素是否小于优先队列1队头元素,如果小于那就队头元素出队并放入优先队列2,然后待插入元素再插入到优先队列1中,反之push进优先队列2。整个插入操作实际上就是用两个优先队列模拟一个优先队列时的插入操作。查询操作就很简单了,只需要输出优先队列1的队头即可,不过查询过后k需要+1,这时候要维护一下两个优先队列,因为优先队列1的容量增加了1,所以如果优先队列2非空的话需要把其中的最小值拿到优先队列1中,这也是之前为什么要用大根堆作为优先队列2的原因,这样可以O(1)获得其中的最小值。

        最后需要注意的一点就是询问的时刻可能会非常大,因为它说数据都在int范围内,也就有可能超过n的大小,所以在枚举完从1~n的时刻后要把剩余的询问再处理一下。这点数据并未涉及,但完整的解题步骤时一定要考虑到这个问题的。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#include <queue>
#include <map>
using namespace std;
//数组动态变化下的第k大问题 
//用两个优先队列拼成一个优先队列 
int a[30005], cnt;//cnt记录处理完几次询问 
priority_queue<int> q1;//降序 
priority_queue<int, vector<int>, greater<int> > q2;//升序 
map<int, int> mp;

signed main()
{
	int n, m;
	cin >> n >> m;
	for(int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	for(int i = 1; i <= m; i++)
	{
		int t;
		scanf("%d", &t);
		mp[t]++;
	}
	for(int i = 1; i <= n; i++)
	{
		if(q1.size() == cnt+1)
		{
			if(a[i] >= q1.top())
				q2.push(a[i]);
			else
			{
				q2.push(q1.top());
				q1.pop();
				q1.push(a[i]);
			}
		}
		else
			q1.push(a[i]);
		for(int j = 1; j <= mp[i]; j++)
		{
			printf("%d\n", q1.top());
			cnt++;
			if(!q2.empty())
			{
				q1.push(q2.top());
				q2.pop();
			}
		}
	} 
	while(cnt < m)//如果还有未处理完的询问 
	{
		printf("%d\n", q1.top());
		cnt++;
		if(!q2.empty())
		{
			q1.push(q2.top());
			q2.pop();
		}
	}
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值