目錄
- 寫在前面
- 問題
- 思路
- 程式碼
- 例題
- 寫在最後
寫在前面
媽的為啥我不會這個
問題
給定 \(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。
寫在最後
媽的為啥我不會這個