动态中位数
题目描述
样例解释
比如序列1 2 3 4 5 6 7 8 9。当读入的整数个数为奇数时,输出已输入序列的中位数,为偶数时,不用输出。因为是动态输入,当输入1时,数据个数为奇数,此时中位数为1,所以输出1;当输入2时,数据个数为偶数,不需要输出;当输入3时,数据个数为奇数,此时中位数为2,所以输出2;当输入4时,数据个数为偶数,不需要输出;当输入5时,数据个数为奇数,此时中位数为3,所以输出3;当输入6时,数据个数为偶数,不需要输出;当输入7时,数据个数为奇数,此时中位数为4,所以输出4;当输入8时,数据个数为偶数,不需要输出;当输入9时,数据个数为奇数,此时中位数为5,所以输出5。
核心思路
将序列的前一半和后一半分开来存放,比如1~n这n个数(假设n为奇数),将1到 n / 2 + 1 n/2+1 n/2+1放在一起,将 n / 2 + 2 n/2+2 n/2+2和 n n n放在一起,这样中位数就是 1 1 1到 n / 2 + 1 n/2+1 n/2+1中最大的那个。比如当n=5,那么将1,2,3放到一起,将4,5放在一起,那么此时中位数就是1,2,3这一段中最大的那个,即中位数是3。因此,我们的做法是使用对顶堆,使用大根堆来存放1到 n / 2 + 1 n/2+1 n/2+1的这些数,使用小根堆来存放 n / 2 + 2 n/2+2 n/2+2到 n n n的这些数。此时大根堆的堆顶元素就是中位数。如果我们是把1到 n / 2 n/2 n/2这些数放到大根堆,把 n / 2 + 1 n/2+1 n/2+1到 n n n这些数放到小根堆,那么此时小根堆的堆顶元素就是中位数。
在这里,我们采用第一种,即大根堆的堆顶元素是中位数。设up为小根堆,down为大根堆。
- 序列中从小到大排名为1到 n / 2 + 1 n/2+1 n/2+1的整数存放到大根堆中
- 序列中从小到大排名为 n / 2 + 2 n/2+2 n/2+2到 n n n的整数存放到小根堆中。
- 也就是说小根堆中的堆顶元素 ≥ \geq ≥大根堆的堆顶元素。小根堆中的元素总体上来说是 ≥ \geq ≥大根堆中的元素。
- 大根堆的元素个数最多比小根堆的元素个数多1。即down.size() ≤ \leq ≤up.size()+1。
当新来了一个元素x,如果 d o w n . t o p ( ) ≥ x down.top()\geq x down.top()≥x,则说明 x x x应该放在大根堆 d o w n down down中;如果 d o w n . t o p ( ) < x down.top()<x down.top()<x,则说明 x x x应该放在小根堆 u p up up中。
当大根堆中的元素个数与小根堆中的元素个数相差大于1时,说明大根堆中的元素太多了,此时规定把大根堆的堆顶元素放入小根堆中。
当小根堆的元素个数比大根堆的元素个数多时(按规定应该是大根堆比小根堆多),那么此时规定把小根堆的堆顶元素放入大根堆中。
#include<iostream>
#include<queue>
using namespace std;
int main()
{
int T;
scanf("%d",&T); //输入T组测试数据
while(T--)
{
int n,m; //m代表数据集的编号,n表示数据集的个数
scanf("%d%d",&m,&n);
printf("%d %d\n",m,(n+1)/2); //输出编号和中位数的个数
priority_queue<int>down;//大根堆
priority_queue<int,vector<int>,greater<int>>up; //小根堆
int cnt=0; //记录一行中已经输出的个数
//处理这n个数据
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);//输入一个数据x
//如果大根堆为空或者是新来的数据x小于等于此时大根堆的堆顶元素,那么就放到大根堆down中
if(down.empty()||down.top()>=x)
down.push(x);
//如果小根堆为空或者是新来的数据x小于等于此时小根堆的堆顶元素但是大于大根堆的堆顶元素,那么就放到小根堆up中
else
up.push(x);
//如果大根堆的元素个数比小根堆的元素个数超过了1个以上,那么就把大根堆的堆顶元素放到小根堆中
if(down.size()>up.size()+1)
{
up.push(down.top());
down.pop();
}
//如果小根堆的元素个数比大根堆的元素个数多,那么就把小根堆的堆顶元素放到大根堆中
if(up.size()>down.size())
{
down.push(up.top());
up.pop();
}
//如果读入的整数个数为奇数时,则要输出已读入的整数构成的序列的中位数了
if(i%2)
{
printf("%d ",down.top());
if(++cnt%10==0) //如果一行中的输出个数为10个,则需要换行了
puts("");
}
}
//假设输入的n个数据不足10个,比如n=4,那么只输入了4个数,那么这四个数的输出就一定不会执行for循环中的if语句中的换行。
//但是输出这4个数据之后应该换行,因为下一组测试数据要来了。
if(cnt%10)
puts("");
}
return 0;
}