模板链接:https://www.luogu.org/problemnew/show/P3371
【模板】堆
题目描述
如题,初始小根堆为空,我们需要支持以下3种操作:
操作1: 1 x 表示将x插入到堆中
操作2: 2 输出该小根堆内的最小数
操作3: 3 删除该小根堆内的最小数
输入输出格式
输入格式:
第一行包含一个整数N,表示操作的个数
接下来N行,每行包含1个或2个正整数,表示三种操作,格式如下:
操作1: 1 x
操作2: 2
操作3: 3
输出格式:
包含若干行正整数,每行依次对应一个操作2的结果。
输入输出样例
输入样例#1:
5
1 2
1 5
2
3
2
输出样例#1:
2
5
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=15
对于70%的数据:N<=10000
对于100%的数据:N<=1000000(注意是6个0。。。不过不要害怕,经过编者实测,堆是可以AC的)
样例说明:
故输出为2、5
题解
托某位毒瘤张瀚文老师的福,学习了一大堆堆知识。
配对堆不像其他娇气的堆,它对堆本身的形态没有什么要求,只要是棵树就行了,插入/合并也很任性,直接比较两个根的大小,优先级高的当爸爸,轻松 O(1) O ( 1 ) 完成插入/合并。
配对堆还可以支持改变堆中元素的值,只需要将元素原来的父边断掉,修改完权值后直接与根合并就完了,虽然看起来是 O(1) O ( 1 ) 的,但是因为会影响到 pop p o p 操作,所以均摊下来复杂度为 O(log2n) O ( l o g 2 n ) 。
因为前面的操作比较随意,所以 pop p o p 操作要收拾随意连边的烂摊子,我们要把根节点的所有儿子不停地两两合并合成一棵树,复杂度是 O(儿子个数) O ( 儿 子 个 数 ) 的,但是神奇摊还分析说,它是 O(log2n) O ( l o g 2 n ) 的,它就是 O(log2n) O ( l o g 2 n ) 的。
代码
贼好写,甚至短于左偏树。
#include<bits/stdc++.h>
using namespace std;
const int M=2e6+5;
int n,root,id,tot,top,val[M],head[M],nxt[M],to[M],dad[M];
queue<int>dui;
void add(int x,int y){nxt[++id]=head[x],head[x]=id,to[id]=y,dad[y]=x;}
int _new(int x){val[++tot]=x;return tot;}
int merge(int x,int y){if(!x||!y)return x+y;if(val[x]<val[y]){add(x,y);return x;}else {add(y,x);return y;}}
void pop()
{
int t;
for(int i=head[root];i;i=nxt[i])if(dad[to[i]]==root)dui.push(to[i]),dad[to[i]]=0;
while(dui.size()>1)
{
t=dui.front();dui.pop();
dui.push(merge(dui.front(),t));
dui.pop();
}
if(dui.size())root=dui.front(),dui.pop();
else root=0;
}
void in(){scanf("%d",&n);}
void ac()
{
int op,x;
for(int i=1;i<=n;++i)
{
scanf("%d",&op);
if(op==1)scanf("%d",&x),root=merge(_new(x),root);
else if(op==2)printf("%d\n",val[root]);
else pop();
}
}
int main(){in();ac();}