可并堆
可并堆顾名思义就是可以合并的堆。
这里不讲二项堆和斐波那契堆,只讲左偏树。
左偏树
左偏树顾名思义就是向左偏的树。
给每个点定义一个\(dist\),满足下面三个条件:
1、空结点的\(dist\)等于\(-1\)
2、每个结点的左儿子的\(dist\)都大于右儿子的\(dist\)
3、每个结点的\(dist\)都等于右儿子的\(dist+1\)
根据上面这些性质,我们可以推出左偏树中根结点的\(dist\)最大不超过\(logsize\)。
合并
左偏树合并非常简单,假设我要合并\(a,b\)两颗树并且\(val_a<val_b\)(为了满足小根堆性质,不满足就交换\(a,b\))
如果\(a\)或\(b\)为空返回另一个结点
否则合并\(a\)的右儿子和\(b\),如果这个时候右儿子的\(dist\)大于左儿子的\(dist\)就交换\(a\)的左右儿子,更新\(a\)的\(dist\)然后返回\(a\)。
由于每一层递归的\(dist_a+dist_b\)都会比上一层小\(1\),最小可以到\(-1\),所以时间复杂度是\(O(logsize_a+logsize_b)\)的。
模板题传送门:https://www.luogu.org/problemnew/show/P3377
时间复杂度:\(O(mlogn)\)
空间复杂度:\(O(n)\)
代码如下:
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
int n,m;
int son[maxn][2];
int v[maxn],fa[maxn],dist[maxn];
int read() {
int x=0,f=1;char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;
for(;ch>='0'&&ch<='9';ch=getchar())x=x*10+ch-'0';
return x*f;
}
int find(int x) {
while(fa[x])x=fa[x];
return x;//由于树型结构会改变所以不敢路径压缩
}
int merge(int a,int b) {
if(!a||!b)return a+b;
if(v[a]>v[b])swap(a,b);
son[a][1]=merge(son[a][1],b);
fa[son[a][1]]=a;
if(dist[son[a][1]]>dist[son[a][0]])
swap(son[a][1],son[a][0]);
dist[a]=dist[son[a][1]]+1;
return a;
}
void pop(int u) {
printf("%d\n",v[u]);v[u]=-1;
fa[son[u][0]]=fa[son[u][1]]=0;
merge(son[u][0],son[u][1]);
son[u][0]=son[u][1]=0;
}
int main() {
n=read(),m=read();
for(int i=1;i<=n;i++)
v[i]=read();
for(int i=1;i<=m;i++) {
int opt=read();
if(opt==1) {
int x=read(),y=read();
if(v[x]==-1||v[y]==-1)continue;
x=find(x),y=find(y);
if(x==y)continue;
merge(x,y);
}
else {
int u=read();
if(v[u]==-1) {puts("-1");continue;}
u=find(u),pop(u);
}
}
return 0;
}