acwing839模拟堆,堆(复杂模板)(模板)
一般大根堆和小根堆可以用优先队列,但解决“把第n个插入数修改成x”类似的问题可能要用模拟堆
如果题目是先只修改,最后询问最小,可以先记录插入顺序修改插入顺序,然后输进堆中进行询问
如果询问后有修改第n插入、删除第n插入,就要模拟堆:
模拟堆板子:
#include <bits/stdc++.h> using namespace std; int n,k,x; const int N=100009; int ph[N],h[N],hp[N],size=0,m=0;//ph:普通插入位置对应的堆位置;hp:堆位置对应普通插入位置;h:堆中各位置对应数值 void heap_swap(int a,int b){//堆中交换函数(传入的是堆的位置) swap(ph[hp[a]],ph[hp[b]]); swap(hp[a],hp[b]); swap(h[a],h[b]); } void up(int x){//向上更新 while(x>>1&&h[x]<h[x>>1]) { heap_swap(x,x>>1); x>>=1; } } void down(int x){//向下更新 int p=x; if(x<<1<=size&&h[x<<1]<h[p]) p=x<<1;//如果左儿子小于父节点 if((x<<1|1)<=size&&h[x<<1|1]<h[p]) p=x<<1|1;//右儿子 if(p!=x){//如果父节点不是最小 heap_swap(p,x);//交换父节点和较小儿子 down(p);//继续更新原父节点(现在换成了儿子的位置) } } int main(){ ios::sync_with_stdio(false); cin.tie(0); cin>>n; while(n--){ string op; cin>>op; if(op=="I"){//插入x进堆 cin>>x; size++, m++; h[size]=x, hp[size]=m, ph[m]=size; up(size);//down不能增加 }else if(op=="PM") cout<<h[1]<<endl;//输出最小值 else if(op=="DM"){//删除最小值 heap_swap(1,size); size--; down(1); }else if(op=="D"){//删除第k个插入值 cin>>k; k=ph[k];//令k指向插入值所在堆的位置 heap_swap(k,size); size--; up(k); down(k); }else{//把第k个插入值改成x cin>>k>>x; k=ph[k]; h[k]=x; up(k); down(k); } } }
注意几点:
1.为什么要hp,ph;
因为题目“删除当前集合中的最小值”是从堆位置影响到普通插入位置;
而“删除第k个插入的数;”,“修改第k个插入的数,将其变为x”是从普通插入位置影响到堆位置
所以要有堆到插入顺序、插入顺序到堆的映射
2.
正确代码:
else if(op=="D"){//删除第k个插入值
cin>>k;
k=ph[k];//令k指向插入值所在堆的位置
heap_swap(k,size);
size--;
up(k);
down(k);
}
原错误代码
else if(op=="D"){
cin>>k; heap_swap(ph[k],size);
size--;
down(ph[k]);
up(ph[k]);
}
为什么错: 明显删除时如果用 heap_swap(ph[k],size);对应堆地址是最尾,那么up,down时就不能堆新插入的头更新,而是会错误把头归为原处