堆的基本操作(手写模拟堆)
地址:https://www.acwing.com/problem/content/841/
解析:
这个题比较麻烦的一点就是k。第k个插入的数,不是树里的序号。经过一系列变换后,第k个插入的数在树里的序号会发生交换。
所以引入两个数组:ph[i]=x:表示第i次插入的数,在树中的序号为x。hp[i]=x:表示树中的序号i,是第x次插入的数。
乍一看,hp[]似乎并没有什么用,每次交换h[]和ph[]即可了。别急,请看下面:
我们来定义一个全新的交换方式:heap_swap(int u,int v)
u,v做为树的两个结点号被传了进来。
那么我们需要交换h[u],h[v],我们要交换两个ph[],但是,ph[]里的索引不知道,也就是说我们并不知道h[u],h[v]代表的数,是第几次插入的,自然就没法交换ph[]。
这个时候就可以看出hp[]的厉害了,hp[u],hp[v]不就是表示第几次插入的数吗?也就是ph的索引,至此,就可以交换ph[]了。有代码:
void heap_swap(int u,int v) { swap(h[u],h[v]); swap(ph[hp[u]],ph[hp[v]]); swap(hp[u],hp[v]); }
可以发现,顺序对结果是没有影响的~
关于堆,我有小笔记可供大家参考:https://www.cnblogs.com/liyexin/p/13942174.html
这个是本题的一个难点,其他的就是模板了,看注释吧:
#include<iostream> #include<algorithm> #include<cmath> using namespace std; #include<map> typedef long long ll; const int maxn=1e5+10; const int maxn2=3e6+10; int hp[maxn],ph[maxn],h[maxn],n,m,siz; void heap_swap(int a,int b) { swap(h[a],h[b]); swap(ph[hp[a]],ph[hp[b]]); swap(hp[a],hp[b]); } void down(int u) { int t=u; if(u*2<=siz&&h[2*u]<h[t]) t=2*u; if(2*u+1<=siz&&h[2*u+1]<h[t]) t=2*u+1; if(u!=t) { heap_swap(u,t); down(t); } } void up(int u) { while(u/2>=1&&h[u/2]>h[u]) { heap_swap(u/2,u); u=u/2; } } int main() { cin>>n; siz=0;//树的总大小 m=0;//第几次插入 while(n--) { char op[5]; cin>>op; if(op[0]=='I') { int x; cin>>x; h[++siz]=x;//按照前面所说的,hp,ph的作用,进行值的更新 ph[++m]=siz; hp[siz]=m; up(siz);//插到结尾,所以只需要up } else if(op[0]=='P') { cout<<h[1]<<endl; } else if(op[0]=='D'&&op[1]=='M')//删除最小 { heap_swap(1,siz); //结尾覆盖上去,但是ph,hp会发生响应的改变,所以需要先进行连个值的交换,再down siz--; down(1); } else if(op[0]=='D') { int k; cin>>k; int u=ph[k]; //获取第k次插入的数在树中的下标 heap_swap(u,siz);//删除,同样也可以先看成交换,再siz-- siz--; up(u); down(u); } else { int k,x; cin>>k>>x; int u=ph[k]; h[u]=x; down(u); up(u); } } }