一直觉得平衡树水很深,有机会想好好搞一搞,这里记下我大致总结的内容吧。

 

平衡树主要有以下几种类型:

 1.严格保证深度:红黑树、AVL等。这类平衡树始终把深度维护在logn级别,并且每次也只用logn的时间维护自己的性质,因为每次操作都是严格logn,常数会比较小,但伴随的一般是树的形态限制较死,编写时需要考虑的情况可能较多,代码难度较大等。

 2.均摊复杂度:Splay、替罪羊等。每次操作可能O(1)可能O(n),但这并不妨碍它们最终复杂度只有O(nlogn),依据一般是复杂的势能分析之类,不深入研究的话只要记住它们效率可以保证就行了。此类平衡树无法可持久化,强行可持久化会导致无法均摊,复杂度退化。(替罪羊可以部分可持久化(不修改历史版本))

 3.玄学即正解:Treap、跳表(假装是平衡树)等。用上一些随机策略,最后每次期望O(logn),随机的思想不仅几乎卡不掉,而且不像其他类平衡树为保持平衡加上了很多限制,这也使得没有太多限制的随机化平衡树充满待挖掘的潜力。

 4.根本不平衡:普通BST、Spaly等。快利用Trajan大神发明的Spaly算法O(1)维护平衡树。

 

下面分别介绍这些平衡树(加粗的是在目前信息学竞赛中较为重要的)

介绍的时候顺便贴下模板,因为如果都是字有点吓人……

 

模板题->n个操作,支持插入一个数和查找k大(n<=500,000)。数据大部分随机,小部分特殊构造。给出大致的运行时间,仅供参考。

 

[基于模板题的效率对比] (可能由于我写的丑/问题特殊性/随机数据/随机种子生成器/评测机状态等原因产生一定误差,仅供参考)(还有缺的以后有机会再补)

替罪羊树(504ms) < AVL树(588ms) < 普通Treap(688ms) < 无旋Treap(1000ms) < 双旋Splay(1220ms) < 混合旋Splay(1384ms) < 随机合并树(TLE) < 单旋Spaly(TLE) < 裸BST(TLE)

可以看到替罪羊树的效率十分出色(可能是因为主要是随机数据),AVL和Treap紧随其后,无旋Treap和Splay功能强大但常数较大,后面纯属娱乐。

 

这里先贴个本题目前的Rank1 写的是权值线段树ZKW版 240ms 感觉已经接近极限了,还可以调整的就是可以把排序换成O(n)的

#include<cstdio>
#include<algorithm>
using namespace std;
#define r register int
char B[1<<26],*S=B,C;int X;
inline int read()
{
    while((C=*S++)<'0'||C>'9');
    for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
    return X;
}
#define MN 500000
#define N 524288
int x[MN+5],y[MN+5],t[N*2+5],c[MN+5],rk[MN+5];
bool cmp(int a,int b){return y[a]<y[b];}
void add(r k){for(k+=N;k;k>>=1)++t[k];}
int find(r k){for(r i=1;i<<=1;){if(t[i]<k)k-=t[i++];if(i>N)return i-N;}}
int main()
{
    fread(B,1,1<<26,stdin);
    r n=read(),i;
    for(i=1;i<=n;++i)x[i]=read(),y[i]=read(),c[i]=i;
    sort(c+1,c+n+1,cmp);
    for(i=1;i<=n;++i)rk[c[i]]=i;
    for(i=1;i<=n;++i)x[i]?(add(rk[i]),0):printf("%d\n",y[c[find(y[i])]]);
}

 

正文

 

 红黑树:好像性质比较复杂,我也不是很懂,据说效率是目前各类平衡树中最高的,代码也是最难写的。我还不是很懂,有机会再补。

 

 AVL树:我最开始学的就是AVL,因为很好理解,听说这玩意儿代码很长,但我一点都不觉得(我可能学的假的)。性质是任意节点左右子树深度差不超过1,不满足就旋一旋,要考虑各种情况保证旋完满足性质,用一些代码小技巧写起来还是挺短的。后来发现这种平衡树只能做最基本的操作,赶紧跳坑Splay。

#include<cstdio>
#include<algorithm>
using namespace std;
char B[1<<26],*S=B,C;int X;
inline int read()
{
    while((C=*S++)<'0'||C>'9');
    for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
    return X;
}
#define MN 500000
#define rtf MN+1
#define L(x) c[x][0]
#define R(x) c[x][1]
#define rt L(rtf)
int fa[MN+5],c[MN+5][2],s[MN+5],h[MN+5],z[MN+5],tn;
inline void update(int x){s[x]=s[c[x][0]]+s[c[x][1]]+1;h[x]=max(h[c[x][0]],h[c[x][1]])+1;}
void rotate(int x)
{
    int f=fa[x],ff=fa[f],l=R(f)==x,r=l^1;
    fa[f]=x;fa[x]=ff;fa[c[x][r]]=f;
    c[ff][R(ff)==f]=x;c[f][l]=c[x][r];c[x][r]=f;
    update(f);update(x);
}
void ins(int f,int t,int x)
{
    if(!c[f][t]){fa[c[f][t]=++tn]=f;s[tn]=1;z[tn]=x;return;}
    f=c[f][t];ins(f,x>z[f],x);
    if(h[L(f)]-h[R(f)]>2){if(h[R(L(f))]>h[L(L(f))])rotate(R(L(f)));rotate(L(f));}
    else if(h[R(f)]-h[L(f)]>2){if(h[L(R(f))]>h[R(R(f))])rotate(L(R(f)));rotate(R(f));}
    update(f);
}
int find(int x,int k)
{
    if(k<=s[L(x)])return find(L(x),k);
    if(k-=s[L(x)]+1)return find(R(x),k);
    return z[x];
}
int main()
{
    fread(B,1,1<<26,stdin);
    int n=read(),t,x;
    while(n--)
    {
        t=read();x=read();
        if(t)ins(rtf,0,x);
        else printf("%d\n",find(rt,x));
    }
}

 

 伸展树(Splay Tree)全国人民都在用。你啥平衡树都可以不会,但你不能不会Splay。OI中经常要弄一些区间翻转区间查询之类的,提取区间最方便最广为人知的应该就是Splay了吧。Splay基本思想就是每次访问树中节点都要把节点双旋旋到根上来(旋法百度都有比较详细的说明,这里不再赘述),即使一开始树不平衡,旋到根上树的结构就慢慢平衡了。一系列复杂度分析可以保证它是均摊nlogn的(单旋就没有任何保证),但是还是改变不了它常数巨大的缺点。其实有个隐藏的性质就是越高频访问同一些节点,Splay的效率就会越高(因为靠近根),这个性质在现实中可能有一些应用,但OI中难以用题目的形式表现出来(甚至还有人专门卡你)……Splay还是Link-Cut Tree的重要组件。Splay提取区间的方法是把左右端点旋到根附近,例如[l,r]就把l-1旋到根,r+1旋到根节点的右儿子,那么根节点右儿子的左儿子就是[l,r]区间,一般要建两个辅助点0和n+1来提取[1,n]。

#include<cstdio>
char B[1<<26],*S=B,C;int X;
inline int read()
{
    while((C=*S++)<'0'||C>'9');
    for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
    return X;
}
#define MN 500000
#define rt MN+1
int fa[MN+5],c[MN+5][2],s[MN+5],v[MN+5],tn;
inline void update(int x){s[x]=s[c[x][0]]+s[c[x][1]]+1;}
void rotate(int x)
{
    int f=fa[x],ff=fa[f],l,r;
    l=c[f][1]==x;r=l^1;
    fa[f]=x;fa[x]=ff;fa[c[x][r]]=f;
    c[ff][c[ff][1]==f]=x;c[f][l]=c[x][r];c[x][r]=f;
    update(f);
}
void splay(int x,int r)
{
    for(int f;fa[x]!=r;rotate(x))
        if(fa[f=fa[x]]!=r)rotate(c[fa[f]][0]==f^c[f][1]==x?f:x);
    update(x);
}
void ins(int f,int t,int z)
{
    if(!c[f][t]){fa[c[f][t]=++tn]=f;v[tn]=z;splay(tn,rt);return;}
    ++s[f=c[f][t]];
    ins(f,z>v[f],z);
}
int find(int x,int k)
{
    if(k<=s[c[x][0]])return find(c[x][0],k);
    if(k-=s[c[x][0]]+1)return find(c[x][1],k);
    splay(x,rt);return x;
}
int main()
{
    fread(B,1,1<<26,stdin);
    int n=read(),t,x;
    while(n--)
    {
        t=read();x=read();
        if(t)ins(rt,0,x);
        else printf("%d\n",v[find(c[rt][0],x)]);
    }
}

自己瞎写的混合旋Splay

#include<cstdio>
char B[1<<26],*S=B,C;int X;
inline int read()
{
    while((C=*S++)<'0'||C>'9');
    for(X=C-'0';(C=*S++)>='0'&&C<='9';)X=(X<<3)+(X<<1)+C-'0';
    return X;
}
#define MN 500000
#define rt MN+1
int fa[MN+5],c[MN+5][2],s[MN+5],v[MN+5],tn;
inline void update(int x){s[x]=s[c[x][0]]+s[c[x][1]]+1;}
void rotate(int x)
{
    int f=fa[x],ff=fa[f],l,r;
    l=c[f][1]==x;r=l^1;
    fa[f]=x;fa[x]=ff;fa[c[x][r]]=f;
    c[ff][c[ff][1]==f]=x;c[f][l]=c[x][r];c[x][r]=f;
    update(f);
}
void splay(int x,int r)
{
    for(int f;fa[x]!=r;rotate(x))
        if(fa[f=fa[x]]!=r&&c[fa[f]][0]==f^c[f][1]==x)rotate(f);
    update(x);
}
void ins(int f,int t,int z)
{
    if(!c[f][t]){fa[c[f][t]=++tn]=f;v[tn]=z;splay(tn,rt);return;}
    ++s[f=c[f][t]];
    ins(f,z>v[f],z);
}
int find(int x,int k)
{
    if(k<=s[c[x][0]])return find(c[x][0],k);
    if(k-=s[c[x][0]]+1)return find(c[x][1],k);
    splay(x,rt);return x;
}
int main()
{
    fread(B,1,1<<26,stdin);
    int n=read(),t,x;
    while(n--)
    {
        t=read();x=read();
        if(t)ins(rt,0,x);
        else printf("%d\n",v[find(c[rt][0],x)]);
    }
}
View Code

相关文章:

  • 2021-03-30
  • 2021-10-07
  • 2021-12-04
  • 2021-08-21
  • 2021-12-25
  • 2022-12-23
  • 2022-02-08
猜你喜欢
  • 2021-10-04
  • 2021-12-13
  • 2022-12-23
  • 2021-06-24
  • 2021-10-12
  • 2021-05-09
  • 2021-10-02
相关资源
相似解决方案