题解 洛谷 P1168 中位数
由于这道题目比较水,所以机房中诞生了四种做法,分别是权值线段树、堆、二分以及 $\operatorname{multiset}$。
这里主要介绍本题的堆解法
通过读题我们可以轻易发现,每两次插入数据就需要进行一次访问,且期间必须维护当前访问过的所有元素组成的数列具有单调性 。
问题一:如何维护数列的单调性
于是我们很容易想到可以使用优先队列对其插入的数字进行动态排序来维护其单调性。如果你不知道什么是优先队列,那么不妨先通过下面的传送门去看一看优先队列的用法:
点我
于是你现在明白了什么是优先队列,我们只需要在维护好的单调队列里取出中位数就可以了。所以此时你发现,由于单调队列是一个队列,想要访问中间的元素,就只能把它前面的元素全部弹出来,这样弹出一遍插入一遍甚至不如每次 sort 时间效率高,于是问题来了。
问题二:如何快速地在每次访问中提取中位数
既然我们一定需要把队列分成两段,那么我们直接定义两个优先队列就可以完美地解决问题。我们将前奇数个(这里假设为前 $\mathit{K}$ 个)数列切分后扔进左队列、右队列和中位数三种位置,那么左边的队列维护第 $ 1 $ ~ $\left[\frac{K}{2}\right]$ 个元素,右边的队列维护第 $(\left[\frac{K}{2}\right]+2)$ ~ $K$ 个元素,第$(\left[\frac{K}{2}\right]+1)$ 个元素也就是剩下的那个元素就是中位数,将其定义为 mid 。左边的队列从大到小,使用大根堆,右边的队列从小到大,使用小根堆,定义如下:
1
2
3
4typedef long long LL;
priority_queue<LL,vector<LL>,greater<LL> > Right;
priority_queue<LL,vector<LL>,less<LL> > Left;
LL mid;
接下来在每个奇数次插入时,如果插入的数比 mid 小,就塞进左队列,如果比 mid 大,就塞进右队列。需要注意的是:为了确保 mid 就是中位数,必须在每次访问时对两个队列内的元素个数进行平衡! 所以这是最后一个问题。
问题三:平衡两个队列内的元素个数
我们不妨设其中一个队列元素个数为 $x$ ,另一个队列元素个数为 $y$,设 $x>y$ ,则从 $x$ 队列中转移到 $y$ 队列中的元素个数为 $(\frac{x-y}{2}-1)$ 个,最后只需要把 mid 插入到 $y$ 中,再将 $x$ 的队头元素弹出来更新为新的 mid 即可。注意:这一步操作必须先进行当前步骤的插入再进行元素个数平衡。
至此,我们已经解决了这个题目的所有小问题,下面是AC代码:
1 |
|
以上,如果有不对的地方,还请各位大佬指正!
说些什么吧!