2025.11.3(对顶堆)

20 阅读2分钟

今天本来想补题的,但补题发现有一题曼哈顿最小生成树的题解里基本都要用到树状数组,而我不会...于是上洛谷找了一个树状数组题单,但官方题单里面又混了堆的有关题目,所以事已至此,先写写堆吧。

首先是写了一题P1801黑匣子,一看到人有点蒙:维护第 kk 小的值?这怎么做?去查了一下原来要对顶堆,下面总结一下: 如果我们要维护一个动态序列中第 kk 小的数,我们可以怎么维护?不妨考虑建立两个堆,一个大根堆,一个小根堆,分别用来存储序列中的数。

我们考虑到这两个堆的堆顶分别是堆内最大和最小的元素,如果我们能令大根堆内所有元素都小于小根堆内所有元素,同时维持大根堆的大小为 kk ,是不是此时大根堆的堆顶元素就是整个序列中第 kk 小的数了?

因此我们考虑如何动态维护两个堆,它应当支持如下操作:插入、查询、改变 kk 的值。

首先,插入:设当前插入的元素为 xx ,而当前大根堆的大小为 ss ,大根堆的堆顶元素为 tt ,有如下情况:

①当 s<ks<k 时,此时我们需要直接将 xx 插入大根堆以维护第 kk 小值。

②当 s=ks=k 时,我们比较 xx 与 tt 。若 x<tx<t ,则将 xx 插入到大根堆里,同时将 tt 压入小根堆并从大根堆中弹出以保持大根堆内元素为 kk 。

③当 s>ks>k 时,此时我们需要先将大根堆内多余的元素压入小根堆内并从大根堆内弹出,直到 s=ks=k 时再进行②的操作。

随后是查询,此时直接返回大根堆的堆顶元素即可。

当我们要改变 kk 的值时,比较新的 kk 与当前大根堆的大小,若大根堆大小大于 kk ,则不断将大根堆的堆顶元素弹出并压入小根堆;反之不断将小根堆的堆顶元素弹出并压入大根堆。

综上,我们可以在 O(logn)O(logn) 的时间内动态维护对顶堆以查询序列中第 kk 小的值。