题目大意:一开始你有一个空的数组,有m个操作,有两种操作,一种是往数组里加一个数,另一种是先让i++,然后让你输出数组中第i小的数,i从0开始。
对于这道题,我们需要开两个堆,一个堆维护1~i内的数,维护成大根堆,一个堆维护剩下的数(i+1~n),维护成小根堆(这里的1~i和i+1~n是指将数组里的数),那这样有什么用呢?可以发现,每一次输出时,我们只需要输出第二个堆的堆顶即可,然后把它移到那个大根堆里,再维护一下两个堆,那么插入操作怎么办呢,对于比大根堆的堆顶要大的数,它是不会影响到现在前i-1小的数的,所以就把它移到小根堆里,否则,把它移到大根堆里,然后把堆顶踢回小根堆,因为此时他已经不是第i-1大的了,然后再维护一下两个堆即可,代码如下:
#include <cstdio>
#include <cstring>
#define ll long long
int n,m;
int a[200010];
int dui1[200010],dui2[200010];//堆1为大根堆,堆2为小根堆
int t1=0,t2=0;
void up1(int x)
{
while(x>1&&dui1[x/2]<dui1[x])
{
int tt=dui1[x];
dui1[x]=dui1[x/2];
dui1[x/2]=tt;
x/=2;
}
}
void down1(int x)
{
if(x>t1/2)return;
int tt;
if(dui1[x]<dui1[x*2])tt=x*2;
else tt=x;
if(x*2+1<=t1&&dui1[tt]<dui1[x*2+1])tt=x*2+1;
if(tt!=x)
{
int xx=dui1[x];
dui1[x]=dui1[tt];
dui1[tt]=xx;
down1(tt);
}
}
void add1(int x)
{
dui1[++t1]=x;
up1(t1);
}
void up2(int x)
{
while(x>1&&dui2[x/2]>dui2[x])
{
int tt=dui2[x];
dui2[x]=dui2[x/2];
dui2[x/2]=tt;
x/=2;
}
}
void down2(int x)
{
if(x>t2/2)return;
int tt;
if(dui2[x]>dui2[x*2])tt=x*2;
else tt=x;
if(x*2+1<=t2&&dui2[tt]>dui2[x*2+1])tt=x*2+1;
if(tt!=x)
{
int xx=dui2[x];
dui2[x]=dui2[tt];
dui2[tt]=xx;
down2(tt);
}
}
void add2(int x)
{
dui2[++t2]=x;
up2(t2);
}
//----------------------------------------------以上为堆的标准代码
void add(int x)
{
if(x>=dui1[1])add2(x);//比大根堆的堆顶大就加到小根堆里
else add2(dui1[1]),dui1[1]=x,down1(1);//否则加到小根堆里,并且踢掉堆顶
}
void GET()
{
printf("%d\n",dui2[1]);//输出小根堆堆顶
add1(dui2[1]);//加到大根堆里
dui2[1]=dui2[t2--];//把小根堆堆顶去掉
down2(1);//维护一下
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int j=1;
dui1[1]=-999999999;
for(int i=1;i<=m;i++)
{
int x;
scanf("%d",&x);
while(j<=x)//如果还没到x,就把x之前的全部加入堆里
{
add(a[j]);
j++;
}
GET();//然后输出一下
}
}