题意
动态维护中位数问题:依次读入一个整数序列,每当已经读入的整数个数为奇数时,输出已读入的整数构成的序列的中位数。
我的想法
中位数是第(n+1)/2大的数,也可以看成是排完序后中间位置的数。再确定了一个中位数后,每加入一个新数,就用一个二分插入排序将其放入中位数左或右,这样会涉及到链表上的二分查找,我不会。
其实可以用vector容器代替链表的,可以一试。
题解
对顶堆
聪明的解题人,在中位数这里把序列折成两半,关注中位数附近的数,这样两半都是递增的。1~n/2我们关注它的最大值,用大根堆;n/2+1~n关注它的最小值,用小根堆。对顶堆就是建立两个堆,保持两堆的大小。如果一堆太大,则取出堆顶放入另一堆。
插入时,如果大于中位数,则将其放在小根堆中,否则放在大根堆。小根堆的堆顶就是我们要的中位数。然后维护这个对顶堆即可。
代码
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
priority_queue<int,vector<int>,less<int> > da;int szd;
priority_queue<int,vector<int>,greater<int> > xiao;int szx;
int wc,w[20];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
wc=0;
while(!da.empty()) da.pop();szd=0;
while(!xiao.empty()) xiao.pop();szx=0;
int n;scanf("%d",&n);printf("%d ",n);
scanf("%d",&n);printf("%d\n",(n+1)/2);
int mid=-1e9;
for(int i=1;i<=n;i++)
{
int x;scanf("%d",&x);
if(x>mid) xiao.push(x),szx++;//插入
else da.push(x),szd++;
if(i&1)//是奇数
{
while(szd+1!=szx)//调整两堆大小
{
if(szd+1>szx)
{
int t=da.top();
da.pop();szd--;
xiao.push(t);szx++;
}
else
{
int t=xiao.top();
xiao.pop();szx--;
da.push(t);szd++;
}
}
mid=xiao.top();//小根堆的堆顶就是中位数
w[++wc]=mid;
if(wc==10)//10个10个地输出
{
for(int i=1;i<wc;i++) printf("%d ",w[i]);
printf("%d\n",w[wc]);
wc=0;
}
}
}
if(wc>0)
{
for(int i=1;i<wc;i++) printf("%d ",w[i]);
printf("%d\n",w[wc]);
wc=0;
}
}
return 0;
}