堆顶技术学习指南
前置芝士
对顶堆
对顶堆是一种简单好用的动态维护单调区间第k大数或第k小数的数据结构。由一个大顶堆和一个小顶堆构成。
小顶堆
使用c++的STL内裤的 priority_queue<int,vector<int>,greater<int>> mi_hp
,进行操作
单调区间第k大值
[problem description]
[支持的操作]
添加一个元素
询问第k大值(k会变)
不能查询特定的[l,r]区间的第小值
智能查询[1,now]的区间的第k小值
[solved]
priority_queue<int> mx_hp;//大顶堆
priority_queue<int,vector<int>,greater<int>> mi_hp;//小顶堆,存放最大的k个元素,以至于可以O(1)找到第k大的值。
int n,k,num;//n:元素个数,动态k,num:需要加入的数
void qwq(){
//调整小顶堆里的元素个数
if(mi_hp.size()<k){
mi_hp.push(mx_hp.top());
mx_hp.pop();
}
if(mi_hp.size()>k){
mx_hp.push(mi_hp.top());
mi_hp.pop();
}
}
void push(int num){
//如果新加入的元素大于mx_hp的堆顶的最大元素,可以直接加入小顶堆,
//否则需要先将mx_hp的堆顶加入到mi_hp中,将num加入到大顶堆中
//维护小顶堆一定是存放最大的k个数
if(num>=mx_hp.top()) mi_hp.push(num);
else mx_hp.push(num);
qwq();
}
void solve(){
cin>>n;
mx_hp.push(0);//避免边界判断
for(int i=1;i<=n;i++){
//k=pass
cin>>num;
push(num);
cout<<mi_hp.top()<<" ";
}
cout<<endl;
}
单调区间第k小值
[problem description]
[支持添加元素]
查找小幅度变化的k值(第k小值)
不能查询特定的[l,r]区间的第小值
智能查询[1,now]的区间的第k小值
[solved]
const int N = 200010;
int a[N];
priority_queue<int> mx_hp;//大顶堆
priority_queue<int, vector<int>, greater<int>> mi_hp; //小顶堆,存放最大的k个元素,以至于可以O(1)找到第k大的值。
int n, k=1, num; //n:元素个数,动态k,num:需要加入的数
int m;
//调整堆元素个数
void qwq() {
//调整小顶堆里的元素个数
if (mx_hp.size() < k) {
mx_hp.push(mi_hp.top());
mi_hp.pop();
}
if (mx_hp.size() > k) {
mi_hp.push(mx_hp.top());
mx_hp.pop();
}
}
//添加元素
void push(int num) {
//如果新加入的元素小于mi_hp的堆顶的最小元素,可以直接加入大顶堆,
//否则需要先将mi_hp的堆顶加入到mx_hp中,将num加入到小顶堆中
//维护大顶堆一定是存放最小的k个数
if (num <= mi_hp.top()) mx_hp.push(num);
else mi_hp.push(num);
qwq();
}
void solve() {
cin >> n;
mi_hp.push(dinf);//避免边界判断,存放不可能出现的最大值
for (int i = 1; i <= n; i++) {
//k=pass
push(a[i]);
//查询第k小值:mx_hp.top();
}
}
动态维护中位数
[problem description]
给定一个长度为 \(N\) 的非负整数序列 \(A\),对于前奇数项求中位数。
[input]
第一行一个正整数 \(N\)。
第二行 \(N\) 个正整数 \(A_{1\dots N}\)。
[output]
共 \(\lfloor \frac{N + 1}2\rfloor\) 行,第 \(i\) 行为 \(A_{1\dots 2i - 1}\) 的中位数。
[sample]
in
7
1 3 5 7 9 11 6
out
1
3
5
6
\(1 \le N ≤ 100000\),\(0 \le A_i \le 10^9\)
[solved]
const int N=100010;
int n,a[N];
priority_queue<int> mx_hp;
priority_queue<int,vector<int>,greater<int>> mi_hp;
int k,num;//动态维护第k小节点
void qwq(){
if(mx_hp.size()<k){
mx_hp.push(mi_hp.top());
mi_hp.pop();
}
if(mx_hp.size()>k){
mi_hp.push(mx_hp.top());
mx_hp.pop();
}
}
void push(int num){
if(num<=mi_hp.top()) mx_hp.push(num);
else mi_hp.push(num);
qwq();
}
void solve() {
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
mi_hp.push(inf);//避免边界判断,存放不可能出现的最大值
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
k=(1+i)/2;//动态维护k值
push(a[i]);
if(i&1){
cout<<mx_hp.top()<<endl;
}
}
}
序列合并
[problem description]
有两个长度为 \(N\) 的单调不降序列 \(A,B\),在 \(A,B\) 中各取一个数相加可以得到 \(N^2\) 个和,求这 \(N^2\) 个和中最小的 \(N\) 个。
[input]
第一行一个正整数 \(N\);
第二行 \(N\) 个整数 \(A_{1\dots N}\)。
第三行 \(N\) 个整数 \(B_{1\dots N}\)。
[output]
一行 \(N\) 个整数,从小到大表示这 \(N\) 个最小的和。
[sample]
a.in
3
2 6 6
1 4 8
a.out
3 6 7
[datas]
\(1 \le N \le 10^5\),\(1 \le a_i,b_i \le 10^9\)
[solved]
(1)首先将a[1]+b[i](1<=i<=n)全部放入堆中
(2)再取最小值val,再将val-a[i]+a[i+1]放入堆中
const int N=100010;
int n;
int a[N],b[N];
priority_queue<pii,vector<pii>,greater<pii>> mi_hp;
void solve() {
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++) mi_hp.push({a[1]+b[i],1});
for(int i=1;i<=n;i++){
auto mi=mi_hp.top();
mi_hp.pop();
int idx=mi.se;
int val=mi.fi;
if(idx+1<=n)
mi_hp.push({val-a[idx]+a[idx+1],idx+1});
cout<<val<<" ";
}
cout<<endl;
}