并不对劲的左偏树
为了反驳隔壁很对劲的太刀流,并不对劲的片手流将与之针锋相对。
它们是可并堆的两种实现方式。
(还是假装二叉堆只包括小根堆。)
斜堆的缺点在于,每次合并的堆大小不同,无条件交换左右子树可能遇到某些坑孙子的数据,复杂度会变得玄学。
左偏树是在斜堆上有所改进的。根据斜堆的代码,可以注意到合并的时间复杂度是第一个至多一个子树的点到根的距离。
把它称为dis,那么当右子树的dis大于左子树的dis时才有必要交换。
好像没那么玄学了呢。
需要注意的是,至多一个子树的点的dis为0,而空节点的dis为-1。
#include<iostream> #include<iomanip> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #define maxn 100010 using namespace std; int read() { int f=1,x=0;char ch=getchar(); while(isdigit(ch)==0 && ch!='-')ch=getchar(); if(ch=='-')f=-1,ch=getchar(); while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } void write(int x) { int ff=0;char ch[15]; if(x<0) { x=-x; putchar('-'); } while(x)ch[++ff]=(x%10)+'0',x/=10; if(ff==0)putchar('0'); while(ff)putchar(ch[ff--]); putchar('\n'); } struct node { int key,ls,rs,dis; }xx[maxn]; int fa[maxn],n,q; int x,y,l,r,tx,ty; bool cmp(int _1,int _2)//Smaller. { return xx[_1].key==xx[_2].key?_1<_2:xx[_1].key<xx[_2].key; } int f(int x){return fa[x]<0?x:f(fa[x]);} int merge(int A,int B) { if(!A || !B)return A+B; if(!cmp(A,B))swap(A,B); xx[A].rs=merge(xx[A].rs,B); fa[xx[A].rs]=A; if(xx[xx[A].ls].dis<xx[xx[A].rs].dis) swap(xx[A].ls,xx[A].rs); xx[A].dis=xx[xx[A].rs].dis+1; return A; } void getit() { x=read(),y=read(); if(xx[x].key<0 || xx[y].key<0)return; tx=f(x),ty=f(y); if(tx==ty)return; merge(tx,ty); } void delmin(int u) { l=xx[u].ls,r=xx[u].rs; xx[u].key=fa[l]=fa[r]=-1; merge(l,r); } void ask() { // printfa(); x=read(); if(xx[x].key==-1){write(-1);return;} tx=f(x); write(xx[tx].key); delmin(tx); // printfa(); } void work() { n=read(),q=read(); xx[0].dis=-1; memset(fa,-1,sizeof(fa)); for(int i=1;i<=n;i++) xx[i].key=read(); int f; while(q--) { f=read(); if(f==1) getit(); else ask(); } } int main() { work(); return 0; } /* 5 5 1 5 4 2 3 1 1 5 1 2 5 2 2 1 4 2 2 2 */
好像写得比简明教程还简明呢。