Black Box
Description
我们使用黑匣子的一个简单模型。它能存放一个整数序列和一个特别的变量i。在初始时刻,黑匣子为空且i等于0。这个黑匣子能执行一系列的命令。有两类命令:
ADD(x):把元素x放入黑匣子;
GET:把i加1的同时,输出黑匣子内所有整数中第i小的数。牢记第i小的数是当黑匣子中的元素已非降序排序后位于第i位的元素。
下面是一个11个命令的例子:
编号 | 命令 | i | 黑匣子内容 | 输出 |
1 | ADD(3) | 0 | 3 |
|
2 | GET | 1 | 3 | 3 |
3 | ADD(1) | 1 | 1,3 |
|
4 | GET | 2 | 1,3 | 3 |
5 | ADD(-4) | 3 | -4,1,3 |
|
6 | ADD(2) | 2 | -4,1,2,3 |
|
7 | ADD(8) | 2 | -4,1,2,3,8 |
|
8 | ADD(-1000) | 2 | -1000,-4,1,2,3,8 |
|
9 | GET | 3 | -1000,-4,1,2,3,8 | 1 |
10 | GET | 4 | -1000,-4,1,2,3,8 | 2 |
11 | ADD(-2) | 4 | -1000,-4,1,2,3,8 |
|
现需要一个有效的算法处理给定的一系列命令。ADD和GET命令的总数至多个有30000个。
定义ADD命令的个数为M个,GET命令的个数为N个。
我们用下面得两个整数序列描述命令序列:
1.A(1),A(2),……,A(M):加入黑匣子的元素序列。
所有的数均为绝对值不超过2000000的整数。例如在上例中A=(3,1,-4,2,8,-1000,2)。
2.u(1),u(2),……,u(N):
u(i)表示第i个GET命令在第u(i)个ADD命令之后,例如在上例中,u=(1,2,6,6)。
你可以假定自然数序列u(1),u(2),……,u(N)以非降序排列,
N≤M,且对于每一个p(1≤p≤N)有p≤u(p)≤M
现在要求找出对于给定的命令串的最好的处理方法。
ADD 和 GET 命令分别最多有30000个。
现在用两个整数数组来表示命令串:
1. A(1), A(2), ..., A(M): 一串将要被放进Black Box的元素。
每个数都是绝对值不超过2 000 000 000 的整数
M <= 30000。例如上面的例子就是A=(3, 1, -4, 2, 8, -1000, 2).
2. u(1), u(2), ..., u(N):
表示第u(j)个元素被放进了Black Box里后就出现一个GET命令。
例如上面的例子中u=(1, 2, 6, 6).
输入数据不用判错。
Input
题目有多组输入数据!
第一行是一个表示输入组数的整数 N
隔一空行之后是 N 组输入数据,每组输入数据之间都有一行空行隔开,
各组数据都是按上面的形式输入。
Output
输出包含 N 组输出数据,
每一组之间用一个空行隔开。
Sample Input
1
7 4
3 1 -4 2 8 -1000 2
1 2 6 6
Sample Output
3
3
1
2
题意
读入
第一行输入n,m,代表有n个数字,m个询问。
第二行输入n个数字用a[i]代表
第三行输入m个数字,设为b[i]代表当前询问的右边界,左边界为1
输出
针对第1个询问,输出从1到b[1]之间第1小的数字
针对第2个询问,输出从1到b[2]之间第2小的数字
针对第3个询问,输出从1到b[3]之间第3小的数字
......
思路
本题是一个特定范围的询问前K小数的问题。
前b[i]个小数字中最大的就是第i小的。
我们可以建立一个大根堆,一个小根堆。每当需要在前b[i]个数中寻找第i小的数字时,把这前i个数都加入小根堆,然后不断地把小根堆的堆顶元素弹出来放到大根堆去,直到大根堆里有i个元素。弹出完成后一定要检查大根堆的堆顶元素就会小于小根堆的堆顶元素,如果不满足,那么大根堆的i个元素就不是前i个小数字,此时就要把两个堆的堆顶元素交换。通过这样的调整可以保证:大根堆里的i个元素就是前i个小数字,此时这个大根堆的堆顶元素就是前i个数中第i小的数字了。
代码1(标程)
#include<cstdio>
#include<queue>
using namespace std;
int a[30001],b[30001];
int s,m,n,x,y,i,j,k;
int main()
{
scanf("%d",&s);
for(k=1;k<=s;k++)
{
j=1;
priority_queue<int>q1;//大根堆
priority_queue<int,vector<int>,greater<int> >q2;//小根堆
scanf("%d%d",&m,&n);
for(i=1;i<=m;i++)
scanf("%d",&a[i]);//数字
for(i=1;i<=n;i++)
scanf("%d",&b[i]);//询问
for(i=1;i<=n;i++)
{
for(;j<=b[i];j++)
q2.push(a[j]);//把前b[i]个数加入小根堆
while(q1.size()<i)//把小根堆的数加入大根堆,直到大根堆里有i个数
{
q1.push(q2.top());
q2.pop();
}
while(!q2.empty()&&q1.top()>q2.top())//保证大根堆中的i个数是读入的b[i]个数中前i个小的
{
x=q1.top();
y=q2.top();
q1.pop();
q2.pop();
q1.push(y);
q2.push(x);//交换2个堆顶
}
printf("%d\n",q1.top());
}
if(k!=s)printf("\n");
}
}
代码2
#include<bits/stdc++.h>
using namespace std;
int t;
void work()
{
int x,y,last=1,n,m,num[30001],ask,now1=0,now2=0;
priority_queue<int,vector<int>,greater<int> >d1;
priority_queue<int>d2;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&num[i]);
for(int i=1;i<=m;i++)
{
scanf("%d",&ask);
for(int j=last;j<=ask;j++)//使前ask个数都加入小根堆
d1.push(num[j]);
last=ask+1;
d2.push(d1.top());
d1.pop();//把小根堆堆顶元素加入大根堆 同时把它从小根堆弹出
while(d1.size()!=0&&d2.top()>d1.top())//检查 以保证大根堆里的i个数是前i个小数字
{
x=d1.top();
d1.pop();
y=d2.top();
d2.pop();
d1.push(y);
d2.push(x);
}
printf("%d\n",d2.top());
}
printf("\n");
}
int main()
{
scanf("%d",&t);
for(int s=1;s<=t;s++)//多组数据
work();
return 0;
}