黑匣子_NOI导刊2010提高(06) 题解

题目大意:一开始你有一个空的数组,有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();//然后输出一下 
    } 
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值