浅谈伸展树(Splay)

//本文是一个暂时的小记,有不对的请大佬们指出~
真正大佬的在这http://blog.csdn.net/clove_unique/article/details/50630280

伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由丹尼尔·斯立特Daniel Sleator和罗伯特·恩卓·塔扬Robert Endre Tarjan在1985年发明的。
在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。于是想到设计一个简单方法, 在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。伸展树应运而生。伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。
它的优势在于不需要记录用于平衡树的冗余信息。

(本段来自百度)

Splay的基本操作

  • get操作
    主要用来查找x结点属于其父亲结点的左儿子还是右儿子
int get(int x)
{
    return son[fa[x]][1]==x;
}
  • update操作
    主要是更新size值
    (有的时候也会更新其他)
void update(int x)
{
     if (x)
     {
          size[x]=1;
          if (son[x][0]) size[x]+=size[son[x][0]];
          if (son[x][1]) size[x]+=size[son[x][1]];
     }
}  
  • rotate(旋转)操作
    这是Splay最常用也是最重要的操作了
    具体的操作就是找到x与父亲的关系(是他的左儿子还是右儿子),然后旋上去,直接取代其父亲的位置,然后进行一系列的“儿子认领“(具体看代码)
int rotate(int x)
{
    int k=get(x),y=fa[x];
    if(fa[y])son[fa[y]][get(y)]=x;
    if(son[x][!k])fa[son[x][!k]]=y;//x结点反方向的儿子
    fa[x]=fa[y],fa[y]=x,son[y][k]=son[x][!k],son[x][!k]=y;//认领儿子
    update(y),update(x);
}

然后来一道例题,相信用线段树做过【最大值】,此题甚好,但是又有了区间修改,变成了【最大值2】,现在用Splay实现:
题目不记得了吧~

Description

 在N(1<=N<=100000)个数A1…An组成的序列上进行M(1<=M<=100000)次操作,操作有两种:
  (1)1 L R C:表示把A[L]到A[R]增加C(C的绝对值不超过10000);
  (2)2 L R:询问A[L]到A[R]之间的最大值。

思考一下~

好的,那么我们只需要在上面讲的所有里面加入一个打标记的操作就好了。
这和线段树的差不多,打完这个标记lazy,下传~

#include<cstdio>
#include<iostream>
#define LL long long
using namespace std;
int son[100001][2];//Son
LL fmax[100001];//Ans
int fa[100001];//Father
LL add[100001];//Lazy
LL a[100001];//Data
int d[100001];//Queue
int n,m,kind,l,r,c;
int update(int x){fmax[x]=max(a[x],max(fmax[son[x][0]],fmax[son[x][1]]));}
int lazy(int x,int c){fmax[x]+=c,a[x]+=c,add[x]+=c;}
int get(int x){return son[fa[x]][1]==x;}
int clear(int x)
{
    if(son[x][0])lazy(son[x][0],add[x]);
    if(son[x][1])lazy(son[x][1],add[x]);
    add[x]=0;
}
int remove(int x,int y)
{
    d[0]=0;
    for (x=x;x-y;x=fa[x]) d[++d[0]]=x;
    while (d[0]) clear(d[d[0]--]);
}
int rotate(int x)
{
    int k=get(x),y=fa[x];
    if(fa[y])son[fa[y]][get(y)]=x;
    if(son[x][!k])fa[son[x][!k]]=y;
    fa[x]=fa[y],fa[y]=x,son[y][k]=son[x][!k],son[x][!k]=y;
    update(y),update(x);
}
int Splay(int x,int y)
{
    remove(x,y);
    while(fa[x]-y)
    {
        if(fa[fa[x]]-y)
            if(get(x)==get(fa[x])) rotate(fa[x]);
            else rotate(x);
        rotate(x);
    }
}
int main()
{
    freopen("Max2.in","r",stdin);
    scanf("%d",&n);
    int i;fmax[0]=-1e9;
    for (i=1;i<=n;++i)
        scanf("%lld",&a[i+1]),fa[i]=i+1,son[i+1][0]=i,update(i+1);
    fa[n+1]=n+2,son[n+2][0]=n+1,update(n+2);
    scanf("%d",&m);
    for (i=1;i<=m;++i)
    {
        scanf("%d%d%d",&kind,&l,&r);++l,++r;
        Splay(l-1,0),Splay(r+1,l-1);
        if(kind-2){scanf("%d",&c),lazy(son[r+1][0],c);}
        else printf("%lld\n",fmax[son[r+1][0]]);
    }
}

JZOJ【NOIP2015模拟9.12】平方和

#include<cstdio>
#include<iostream>
#define maxn 200010
#define mo 7459
#define ll long long
using namespace std;
struct Moon{
    ll tag,key,size;
    ll sum,sum2,fa;
}t[4*maxn];
ll son[maxn][2],d[maxn];
ll n,a[maxn],m,x,y,z,root,sz;
ll get(ll x)
{
    return son[t[x].fa][1]==x;
}
void update(ll x)
{
    t[x].size=t[son[x][0]].size+t[son[x][1]].size+1;
    (t[x].sum=t[son[x][0]].sum+t[son[x][1]].sum+t[x].key)%=mo;
    (t[x].sum2=t[son[x][0]].sum2+t[son[x][1]].sum2+t[x].key*t[x].key)%=mo;
}
ll findx(ll num)
{
    ll v=root;
    while(x)
    {
        if(son[v][0]&&t[son[v][0]].size>=num) v=son[v][0];
        else
        {
            ll cnt=t[son[v][0]].size+1;
            if(cnt==num) return v;
            num-=cnt;
            v=son[v][1];
        }
    }
    return v;
}
void rotate(ll x)
{
    ll k=get(x),y=t[x].fa;
    if(t[y].fa) son[t[y].fa][get(y)]=x;
    if(son[x][!k]) t[son[x][!k]].fa=y;
    t[x].fa=t[y].fa,son[y][k]=son[x][!k];
    son[x][!k]=y,t[y].fa=x;
    update(y),update(x);
}
ll Sum(ll x,ll b)
{
    ll ans=((t[x].sum2+2*b*t[x].sum)%mo+t[x].size*b*b%mo)%mo;
    return ans;
}
void clear(ll x)
{
    if(t[x].tag)
    {
        for (ll i=0;i<=1;++i)
        {
            if(!son[x][i]) continue;
            (t[son[x][i]].key+=t[x].tag)%=mo;
            (t[son[x][i]].tag+=t[x].tag)%=mo;
            t[son[x][i]].sum2=Sum(son[x][i],t[x].tag%mo);
            (t[son[x][i]].sum+=t[son[x][i]].size*t[x].tag)%=mo;
        }
        t[x].tag=0;
    }
}
void remove(ll x,ll y)
{
    d[0]=0;
    for (;x!=y;x=t[x].fa) d[++d[0]]=x;
    for (ll i=d[0];i>=1;--i) clear(d[i]);
}
void splay(ll x,ll y)
{
    remove(x,y);
    while(t[x].fa!=y)
    {
        if(t[t[x].fa].fa!=y)
            if(get(x)==get(t[x].fa)) rotate(t[x].fa);
            else rotate(x);
        rotate(x);
    }
    if(y==0) root=x;
}
void add(ll x,ll c)
{
    (t[x].key+=c)%=mo;
    (t[x].tag+=c)%=mo;
    t[x].sum2=Sum(x,c);
    (t[x].sum+=t[x].size*c%mo)%=mo;
}
int main()
{
    scanf("%lld",&n);
    ll i,j,xx,yy;char k;
    for (i=1;i<=n;++i) scanf("%lld",&a[i+1]);
    for (i=2;i<=n+2;++i) son[i][0]=i-1,t[i-1].fa=i,t[i-1].key=a[i-1],update(i-1);
    update(n+2);
    scanf("%lld\n",&m);root=sz=n+2;
    for (i=1;i<=m;++i)
    {
        scanf("%c",&k);
        if(k=='Q')
        {
            scanf("uery%lld%lld\n",&x,&y);++x,++y;
            yy=findx(y+1),xx=findx(x-1);
            splay(xx,0);
            splay(yy,xx);
            printf("%lld\n",(t[son[yy][0]].sum2+mo)%mo);
        }
        if(k=='I')
        {
            scanf("nsert%lld%lld\n",&x,&y);++x;
            xx=findx(x-1),yy=findx(x);
            splay(xx,0),splay(yy,xx);
            son[yy][0]=++sz,t[sz].fa=yy;
            t[sz].key=t[sz].sum=y,t[sz].sum2=y*y%mo,t[sz].size=1;
            update(yy),update(xx);
        }
        if(k=='A')
        {
            scanf("dd%lld%lld%lld\n",&x,&y,&z);++x,++y;
            yy=findx(y+1),xx=findx(x-1);
            splay(xx,0),splay(yy,xx);
            add(son[yy][0],z);
        }
    }
}

原文地址:https://www.cnblogs.com/Chandery/p/11332812.html

时间: 2024-11-04 21:02:40

浅谈伸展树(Splay)的相关文章

树-伸展树(Splay Tree)

伸展树概念 伸展树(Splay Tree)是一种二叉排序树,它能在O(log n)内完成插入.查找和删除操作.它由Daniel Sleator和Robert Tarjan创造. (01) 伸展树属于二叉查找树,即它具有和二叉查找树一样的性质:假设x为树中的任意一个结点,x节点包含关键字key,节点x的key值记为key[x].如果y是x的左子树中的一个结点,则key[y] <= key[x]:如果y是x的右子树的一个结点,则key[y] >= key[x]. (02) 除了拥有二叉查找树的性质

浅谈 trie树 及其实现

定义:又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构, 如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树. 核心思想:是空间换时间.利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的. 三个基本性质: 1. 根结点不包含字符,除根结点外每一个结点都只包含一个字符. 2. 从根结点到某一结点,路径上经过的字符连接起来,为该结点对应的字符串. 3. 每个结点的所有子结点包含的字符都不相同. 优点:利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比

# 伸展树 Splay

伸展树 Splay 维基百科上称为伸展树,但是国内好像一般叫平衡树,是众多平衡树中比较优秀的一种. 平衡树左旋右旋不会影响中序遍历顺序. 一棵平衡树的中序遍历顺序是值递增排序的,相当于从小到大到大排了一次序. 平衡树的作用: 平衡树其实就是一棵二叉搜索树,set和map都是平衡树实现. 一棵二叉搜索树理论深度是\(O(log(n))\),但是当二叉树退化成链表的时候,深度就变成了\(O(n)\),很多\(O(log)\)级别从操作会退化成\(O(n)\)线性级别的操作.平衡树就是在不改变二叉搜索

伸展树(splay tree)

伸展树同样是一种平衡二叉搜索树,它的优势在于,在足够长的序列中能保证分摊意义上的高效率,同时也无需记录高度或者平衡因子等信息. 伸展树的高效前提是局部性:刚刚被访问到的数据,可能在短时间内被再次访问:将被访问的下一元素,可能就在不久之前刚刚访问过的元素的附近.因此,伸展树的策略,就是把刚刚访问到的节点,及时"伸展"到树根附近. 所谓"伸展"操作,其实就是BST中的旋转操作.如果每次经过适当旋转,将访问的节点提升一层,直到上升到树根,称为逐层伸展.可以验证,这种伸展方

浅谈数据结构-树

树是一种数据结构,其中一个元素可以有两个或者多个数据元素,具有一对多的特点,用树结构来存储文件. 树的概念 结点的度:子结点的个数.例如结点1中有3个子结点,结点1的度是3. 树的度:树的度等于所有结点度中度最高的值.结点最高的度为3,树的度为3. 叶子结点:度为0的结点,即没有子结点的结点.例如:上图中3,5,6,7,9,10. 分支结点:除了叶子结点以外的结点,即度不为0的结点.例如:上面树的分支结点为1,2,4,8. 内部结点:除了根结点以及叶子结点或在分支结点的基础之上在去掉根结点.例如

【BBST 之伸展树 (Splay Tree)】

最近“hiho一下”出了平衡树专题,这周的Splay一直出现RE,应该删除操作指针没处理好,还没找出原因. 不过其他操作运行正常,尝试用它写了一道之前用set做的平衡树的题http://codeforces.com/problemset/problem/675/D,运行效果居然还挺好的,时间快了大概10%,内存少了大概30%. 1 #include <cstdio> 2 #include <cstring> 3 #include <string> 4 #include

伸展树 Splay 模板

学习Splay的时候参考了很多不同的资料,然而参考资料太杂的后果就是模板调出来一直都有问题,尤其是最后发现网上找的各种资料均有不同程度的错误. 好在啃了几天之后终于算是啃下来了. Splay也算是平衡树的一种,但是跟AVL树.SBT不同的是,Splay并不是一直保持严格的平衡,因此在速度上可能要慢一些,但是统计学上仍能保证Splay具有O(logn)的均摊复杂度. Splay原生不需要附加任何空间,它的先天优势是其特有的Splay操作可以非常灵活地改变整棵树的结构形态,完成一般线段树.平衡树做不

POJ 3580 - SuperMemo - [伸展树splay]

题目链接:http://poj.org/problem?id=3580 Your friend, Jackson is invited to a TV show called SuperMemo in which the participant is told to play a memorizing game. At first, the host tells the participant a sequence of numbers, {A1, A2, ... An}. Then the h

模板——伸展树 splay 实现快速分裂合并的序列

伸展操作:将treap中特定的结点旋转到根 //将序列中从左数第k个元素伸展到根,注意结点键值保存的是原序列id void splay(Node* &o, int k) { int s = o->ch[0] == NULL ? 0 : o->ch[0]->s; int d = k <= s ? 0 : (k == s+1 ? -1 : 1); if(d == 1) k -= s+1; if(d != -1) { splay(o->ch[d], k); rotate(o