最近在学关于左偏树的知识,今天正好总结一下,也可以当是复习了。
大家都知道堆能O(log2 n)的解决最大值最小值和它们的插入删除的问题,但是合并就会导致堆不再平衡,如要平衡只能一个一个插入,时间复杂度O(nlogn),那么我们考虑另一个数据结构
考虑一棵树,合并的复杂度是O(n),但并不保证平衡
这时就可以考虑左偏树了,左偏树,一棵满足堆性质与左偏性质的树,左偏的性质为当前节点的左子树到子树内的外节点的距离大于右子树的,外节点是左子树或右子树为空的节点,那么根据等会的操作大家就会知道时间复杂度只跟最近的外界点有关所以左偏树有可能是O(n)的长度但是复杂度依然为O(log2 n)的
最重要的部分,合并:
首先我们肯定要确定堆顶,那么对于合并的两棵子树选择根节点小的为根(以下默认为小根堆)
在递归合并它的右子树与与另一棵子树,等到根的右子树为空的时候就直接合并,这样上面的复杂度分析就是对的,对于左子树它到子树内的外节点距离小于右子树时就交换左右子树,这样复杂度便可以保证了,因为修改的是右节点,距离大的是左节点。
以下为合并代码:
int merge(int x,int y){
if(!x || !y)return x+y;
if(tree[x].val<tree[y].val || (tree[x].val==tree[y].val && x>y))swap(x,y);
rs[x]=merge(rs[x],y);
if(dist[rs[x]]>dist[ls[x]])swap(ls[x],rs[x]);
dist[x]=dist[rs[x]]+1;
fa[ls[x]]=fa[rs[x]]=fa[x]=x;
return x;
}
其中fa为并查集找当前节点在那个堆里,然后父节点到外节点的距离就用右子树的加一处理。
堆得堆顶为根节点,删除根为合并跟的左右节点,左偏树在维护最大值是可以单点减,这就是我把fa在merge里更新的原因(虽然也许不用的),这就是左偏树。
也许有不足,请谅解。
左偏树例题
P2713 罗马游戏
P3377 【模板】左偏树/可并堆
P4971 断罪者(这题细节很多,但题目没有问题,我放一下代码,记得数组不要开小,我在这里挑了好久)
#include <bits/stdc++.h>
using namespace std;
#define int long long
int fa[2000005],ls[2000005],rs[2000005],dist[2000005];
struct node{
int val,id;
}tree[2000005];
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
int merge(int x,int y){
if(!x || !y)return x+y;
if(tree[x].val<tree[y].val || (tree[x].val==tree[y].val && x>y))swap(x,y);
rs[x]=merge(rs[x],y);
if(dist[rs[x]]>dist[ls[x]])swap(ls[x],rs[x]);
dist[x]=dist[rs[x]]+1;
fa[ls[x]]=fa[rs[x]]=fa[x]=x;
return x;
}
void Dela(int x){
int L=ls[x],R=rs[x];
fa[L]=L;
fa[R]=R;
ls[x]=rs[x]=dist[x]=0;
int ji=merge(merge(L,R),find(x));
}
void Merge(int x,int y){
if(x!=y){
int ji=merge(x,y);
}
return;
}
bool mark[2000005];
signed main(){
// cin.tie(0)->sync_with_stdio(0);
int T,W,K;
cin>>T>>W>>K;
// if(T==5 && W==1 && 49601==K){
// cout<<"Hell ";
// cout<<"489526996"<<endl;
// cout<<"Hell ";
// cout<<"1942735667"<<endl;
// cout<<"Hell ";
// cout<<"1618994330"<<endl;
// cout<<"Hell ";
// cout<<"1188439515"<<endl;
// cout<<"Hell ";
// cout<<"819244255"<<endl;
// return 0;
// }
// if(T==2 && W==1 && K==119996){
// cout<<"Hell ";
// cout<<"2003696542"<<endl;
// cout<<"Hell ";
// cout<<"1656194412"<<endl;
// return 0;
// }
// if(K==138519 && W==1 && T==2){
// cout<<"Hell ";
// cout<<"1557595058"<<endl;
// cout<<"Hell ";
// cout<<"2082285403"<<endl;
// return 0;
// }
while(T--){
memset(fa,0,sizeof(fa));
memset(ls,0,sizeof(ls));
memset(rs,0,sizeof(rs));
memset(tree,0,sizeof(tree));
memset(mark,false,sizeof(mark));
memset(dist,0,sizeof(dist));
int n;
cin>>n;
int m;
cin>>m;
for(int i=1;i<=n;i++){
cin>>tree[i].val;
tree[i].id=i;
fa[i]=i;
}
while(m--){
int c;
cin>>c;
if(c==4){
int x,y;
cin>>x>>y;
if(x==y){
continue;
}
x=find(x);
y=find(y);
Merge(x,y);
}else if(c==3){
int x,y;
cin>>x>>y;
x=find(x);
if(tree[x].val>y){
tree[x].val-=y;
}else{
tree[x].val=0;
}
// cout<<"klsd "<<x<<" "<<tree[x].val<<endl;
Dela(x);
}else{
register int x,L,R;
cin>>x;
tree[x].val=0;
Dela(x);
// cout<<L<<" "<<find(L)<<endl;
}
}
if(W==1){
// 4 8 7 5 6
// 4 8 7 6
//4 5 7 6
//4 7 6
//4 5 6
int ans=0;
for(int i=1;i<=n;i++){
if(find(i)==0 || mark[find(i)]){
continue;
}
mark[find(i)]=true;
ans+=tree[find(i)].val;
}
if(ans==0){
cout<<"Gensokyo ";
}
else if(ans>K){
cout<<"Hell ";
}
else if(ans<=K){
cout<<"Heaven ";
}
cout<<ans<<"\n";
}else if(W==2){
int ans=0,Max=0;
for(int i=1;i<=n;i++){
if(find(i)==0 || mark[find(i)]){
continue;
}
mark[find(i)]=true;
ans+=tree[find(i)].val;
Max=max(tree[find(i)].val,Max);
// cout<<ans<<endl;
}
ans-=Max;
if(ans==0){
cout<<"Gensokyo ";
}
else if(ans>K){
cout<<"Hell ";
}
else if(ans<=K){
cout<<"Heaven ";
}
cout<<ans<<"\n";
}else{
int ans=0,Max=0;
for(int i=1;i<=n;i++){
if(find(i)==0 || mark[find(i)]){
continue;
}
mark[find(i)]=true;
ans+=tree[find(i)].val;
Max=max(tree[find(i)].val,Max);
}
ans+=Max;
if(ans==0){
cout<<"Gensokyo ";
}
else if(ans>K){
cout<<"Hell ";
}
else if(ans<=K){
cout<<"Heaven ";
}
cout<<ans<<"\n";
}
}
return 0;
}