「笔记」对顶堆动态维护中位数

写在前面

妈的为啥我不会这个

问题

给定 n 次操作,要求动态地维护一个可重集合,每次操作为下列三种形式之一:

  • 给定参数 x,向集合中插入一个权值 x
  • 给定参数 x,删除集合中已存在的一个权值 x
  • 查询集合的中位数。

要求在 O(nlogn) 时间复杂度内实现。

思路

考虑对当前可重集合维护两个 multiset,记它们分别为 ABA 中存小于等于中位数的权值,B 中存大于等于中位数的权值,且钦定 size(A)size(B),|size(A)size(B)|1。则在此限制下 A 中最大的数即为集合的中位数。

考虑在上述限制下如何维护 A,B

  • 为了方便首先在 A 中插入极小值,B 中插入极大值。
  • 对于插入操作,若给定参数 xmax{A},则将 x 插入 A,否则插入 B
  • 对于删除操作,先查询 A 中是否存在 x,若存在则直接删除,否则在 B 中查询并删除。
  • 每次插入删除操作后,都进行调整操作:若 size(A)>size(B)+1 则不断取出 A 中最大值插入 B;若 size(B)>size(A) 则不断取出 B 中最小值插入 A
  • 完成调整后,查询 A 中最大的数即为整个集合的中位数。

发现在上述过程中,单次调整至多仅会调整一个元素,则仅有常数次对 set 的操作。则单次插入/删除时间复杂度 O(logn) 级别,查询 O(1) 级别。

注意 multiset.erase 时,若传入的为值则会将所有值均删除;传入迭代器才仅会删除一个。

代码

复制复制
namespace Set {
const int kInf = 1e9 + 2077;
std::multiset<int> less, greater;
void init() {
less.clear(), greater.clear();
less.insert(-kInf), greater.insert(kInf);
}
void adjust() {
while (less.size() > greater.size() + 1) {
std::multiset<int>::iterator it = (--less.end());
greater.insert(*it);
less.erase(it);
}
while (greater.size() > less.size()) {
std::multiset<int>::iterator it = greater.begin();
less.insert(*it);
greater.erase(it);
}
}
void add(int val_) {
if (val_ <= *greater.begin()) less.insert(val_);
else greater.insert(val_);
adjust();
}
void del(int val_) {
std::multiset<int>::iterator it = less.lower_bound(val_);
if (it != less.end()) {
less.erase(it);
} else {
it = greater.lower_bound(val_);
greater.erase(it);
}
adjust();
}
int get_middle() {
return *less.rbegin();
}
}

例题

The 2023 ICPC Asia Jinan Regional Contest - K

写在最后

妈的为啥我不会这个

posted @   Luckyblock  阅读(484)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示