\(\\\)
對頂堆
處理動態中位數等問題,靈活運用了堆的性質,本質是維護兩個堆。
大根堆\(Q_1\):維護集合中較小值的部分的最大值。
小根堆\(Q_2\):維護集合中較大值的部分的最小值。
注意到兩個堆中的元素各自是單調的,兩個堆間也是單調的。也就是說,\(Q_1\)中的任何一個元素都不大於\(Q_2\)中的任何一個元素。
那么假設高度為權值,兩個堆可以形象化的表示成:

如果兩個堆的大小相差不超過\(1\),較大的那個堆的堆頂必定是中位數(偶數個數時中位數是排序后中間的兩個之一)
\(UPD:\) 圖中的 \(Q1\) 和 \(Q2\) 標反了,權值是越高越大。
\(\\\)
具體操作
-
插入
先找到當前正確的集合插入,比較與堆頂的大小關系,大於小根堆的堆頂就插入大根堆,反之小根堆。
調整兩個堆的大小關系,因為兩個堆不管是總體上還是各自內都是滿足單調性的,所以每次取更大的堆的堆頂,插入另一個堆,直到兩個堆的大小相差不超過\(1\)。
代碼使用的是\(STL\)的\(priority\_queue\),默認大根堆所以\(Q_1\)需要通過相反數實現。
(!q2.size()||x>q2.top())?q1.push(-x):q2.push(x); while(q1.size()>q2.size()+1){q2.push(-q1.top());q1.pop();} while(q2.size()>q1.size()+1){q1.push(-q2.top());q2.pop();} -
查詢
直接輸出比較大的堆的堆頂就好,注意\(Q_1\)的輸出。
if(i&1) printf("%d\n",(q1.size()>q2.size())?-q1.top():q2.top()); -
擴展
動態\(K\)大值:維護思想不變,只需要讓\(Q_1\)的大小為\(K\)即可。
帶刪除\(K\)大值\(/\)中位數:手寫可刪堆\(/\)打標記懶惰刪除法
-
模板
以 \([\ POJ\ 3784\ ]\ Running\ Median\) 為例,只有插入操作,每插入到奇數個就輸出當前中位數,多組數據。
#include<cmath> #include<queue> #include<cstdio> #include<cctype> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 100010 #define R register #define gc getchar using namespace std; inline int rd(){ int x=0; bool f=0; char c=gc(); while(!isdigit(c)){if(c=='-')f=1;c=gc();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();} return f?-x:x; } int n,x,cnt; priority_queue<int> q1,q2,tmp; inline void work(){ cnt=0; x=rd(); n=rd(); q1=tmp; q2=tmp; printf("%d %d\n",x,(n+1)/2); for(R int i=1,x;i<=n;++i){ x=rd(); (!q2.size()||x>q2.top())?q1.push(-x):q2.push(x); while(q1.size()>q2.size()+1){q2.push(-q1.top());q1.pop();} while(q2.size()>q1.size()+1){q1.push(-q2.top());q2.pop();} if(i&1){ printf("%d ",(q1.size()>q2.size())?-q1.top():q2.top()); if((++cnt)==10&&i!=n) cnt=0,puts(""); } } puts(""); } int main(){ int t=rd(); while(t--) work(); return 0; }
\(\\\)
一道例題
對一個集合一共有\(N\)次操作,計數器\(cnt\)初始為\(0\):
\(ADD\ x\):把\(x\)放進該集合。
\(GET\):將\(cnt\)加\(1\),輸出集合種中第\(cnt\)小的數。
\(N\in [1,2\times 10^5]\)
\(\\\)
動態\(K\)小值,注意題目限制的\(Q_2\)大小是變化的,每次詢問后記得調整好。
\(\\\)
#include<cmath>
#include<queue>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 200010
#define R register
#define gc getchar
using namespace std;
inline int rd(){
int x=0; bool f=0; char c=gc();
while(!isdigit(c)){if(c=='-')f=1;c=gc();}
while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
return f?-x:x;
}
int n,m,cnt,x,a[N];
priority_queue<int> q1,q2;
int main(){
n=rd(); m=rd();
for(R int i=1;i<=n;++i) a[i]=rd();
x=rd();
for(R int i=1;i<=n;++i){
(!q2.size()||a[i]>q2.top())? q1.push(-a[i]):q2.push(a[i]);
while(q2.size()>cnt) q1.push(-q2.top()),q2.pop();
while(q2.size()<cnt) q2.push(-q1.top()),q1.pop();
while(x==i){
q2.push(-q1.top()); q1.pop();
printf("%d\n",q2.top());
x=((++cnt)<m)?rd():-1;
}
}
return 0;
}
