目录
写在前面
妈的为啥我不会这个
问题
给定 \(n\) 次操作,要求动态地维护一个可重集合,每次操作为下列三种形式之一:
- 给定参数 \(x\),向集合中插入一个权值 \(x\)。
- 给定参数 \(x\),删除集合中已存在的一个权值 \(x\)。
- 查询集合的中位数。
要求在 \(O(n\log n)\) 时间复杂度内实现。
思路
考虑对当前可重集合维护两个 multiset
,记它们分别为 \(A\) 与 \(B\),\(A\) 中存小于等于中位数的权值,\(B\) 中存大于等于中位数的权值,且钦定 \(\operatorname{size}(A) \ge \operatorname{size}(B), \left|\operatorname{size}(A) - \operatorname{size}(B)\right|\le 1\)。则在此限制下 \(A\) 中最大的数即为集合的中位数。
考虑在上述限制下如何维护 \(A, B\):
- 为了方便首先在 \(A\) 中插入极小值,\(B\) 中插入极大值。
- 对于插入操作,若给定参数 \(x\le \operatorname{max}\{A\}\),则将 \(x\) 插入 \(A\),否则插入 \(B\)。
- 对于删除操作,先查询 \(A\) 中是否存在 \(x\),若存在则直接删除,否则在 \(B\) 中查询并删除。
- 每次插入删除操作后,都进行调整操作:若 \(\operatorname{size}(A) > \operatorname{size}(B) + 1\) 则不断取出 \(A\) 中最大值插入 \(B\);若 \(\operatorname{size}(B) > \operatorname{size}(A)\) 则不断取出 \(B\) 中最小值插入 \(A\)。
- 完成调整后,查询 \(A\) 中最大的数即为整个集合的中位数。
发现在上述过程中,单次调整至多仅会调整一个元素,则仅有常数次对 set
的操作。则单次插入/删除时间复杂度 \(O(\log n)\) 级别,查询 \(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。
写在最后
妈的为啥我不会这个
标签:greater,less,中位数,笔记,插入,动态,operatorname,size From: https://www.cnblogs.com/luckyblock/p/18159496