题目描述
给出一个长度为 NN 的非负整数序列 A_iAi ,对于所有 1 ≤ k ≤ (N + 1) / 21≤k≤(N+1)/2 ,输出 A_1, A_3, …, A_{2k - 1}A1,A3,…,A2k−1 的中位数。即前 1,3,5,…1,3,5,… 个数的中位数。
输入输出格式
输入格式:
第 11 行为一个正整数 NN ,表示了序列长度。
第 22 行包含 NN 个非负整数 A_i (A_i ≤ 10^9)Ai(Ai≤109) 。
输出格式:
共 (N + 1) / 2(N+1)/2 行,第 ii 行为 A_1, A_3, …, A_{2k - 1}A1,A3,…,A2k−1 的中位数。
输入输出样例
说明
对于 20\%20% 的数据, N ≤ 100N≤100 ;
对于 40\%40% 的数据, N ≤ 3000N≤3000 ;
对于 100\%100% 的数据, N ≤ 100000N≤100000 ;
讲道理一看这题第一反应是暴力模拟,第二眼看到了数据范围,然后滚回去想别的方法,后来觉得插排还行,又觉得单调队列也不错,在一想线段树好像才是真爱,然而,最后这些想法都没用上(好吧其实想不到p.s插排不会写了,sort后遗症233)。。。最后用堆过的来着
先介绍下堆
堆实际上是一棵二叉树,这棵树满足所有的非叶子节点都大于/小于它的儿子,大于的称为大根堆,小于的称为小根堆
下图为一个大根堆
233
12 23
5 6 7 8
小根堆
1
2 3
4 5 6 7
可以发现,不论大根堆还是小根堆,堆首都是最值,加上堆是一个logn的复杂度,所以堆广泛运用于top n 问题中
回到本题,对于本题,只开一个堆的话,每次要取出前(n+1)/2-1个元素,取出并输出(n+1)/2个元素然后再把之前取出的元素压回堆中
很明显时间是不够用的,所以考虑开两个堆分别维护,假设左边为大根堆,右边为小根堆,只要大于左边堆首的,就压入右边,这样的话
左边除了堆首,其他元素都小于堆首,右边则全部大于左边的堆首,左边的堆首就是答案了,但是我们发现,要想让左边堆首成为答案,
两个堆的元素差只能为1才行,比如左边有5个元素,右边有2个,这种情况下左边的堆首并不是答案,只有把左边的堆首压入右边,变成
左边4个元素,右边3个,左边的堆首才是答案
完整代码
#include<bits/stdc++.h> using namespace std; int read() { long long ans=0;int k=1;char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-')k=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { ans=ans*10+ch-'0'; ch=getchar(); } return ans*k; }
//建议用优先队列代替手写堆,方便且不会错 priority_queue<int,vector<int> > q1;//大根堆 priority_queue<int,vector<int>,greater<int> > q2;//小根堆 int n; int main() { n=read();q1.push(read()); cout<<q1.top()<<endl;//第一个元素直接输出 for(int i=2;i<=n;i++) { int k=read(); if(k>q1.top())q2.push(k);//如果大于左边的堆首,压入右边 else q1.push(k);//否则压入左边 while(abs(q1.size()-q2.size())>1)//如果两边元素差不为1 if(q1.size()>q2.size())q2.push(q1.top()),q1.pop();//左大于右往右压入堆首 else q1.push(q2.top()),q2.pop();//否则往左压入堆首 if (i%2) cout<<(q1.size()>q2.size()?q1.top():q2.top())<<endl;//如果是奇数,输出堆首即可 } return 0; }
参考大佬@ _肖恩Sean_ 题解