学习笔记:Splay

概念

S p l a y Splay Splay,又叫伸展树,是一种平衡树,通过旋转来实现,可以实现多种一般的平衡树(如 T r e a p Treap Treap)很难或无法在时限内做到的操作。 S p l a y Splay Splay始终保证原序列就是树的中序遍历。

方法

主要是由操作 s p l a y splay splay完成的。 s p l a y splay splay操作可以将点 u u u转到点 k k k的下面。注意该操作要保证 u u u k k k的孩子,所以将 a a a挂到 b b b下面需要先把 b b b挂到根再把 a a a挂到 b b b下面。
s p l a y splay splay操作先令父节点为 x x x,二级祖先结点为 y y y。显然 ,当 x = k x=k x=k时结束操作。当 y = k y=k y=k时可以将 u u u旋转上去(不难将旋转的左旋和右旋写到一起)。接下来就要针对于父节点、二级祖先结点(即父节点的父节点)和当前结点的不同状态来分类。因为第一下旋转右旋左旋同理,所以我们假定第一次是右旋,即 x x x y y y的左儿子的情况。若 u u u x x x的左儿子,则先转 x x x,再转 u u u,否则就转两次 u u u。其实也可以一直转 u u u,但是因为 s p l a y splay splay玄学的时间复杂度证明,就会导致这样做就无法保证时间复杂度是对的,就会 T L E TLE TLE。插入就是二叉查找树的插入方法,但必须要求序列有序才行。为了保证时间复杂度是对的,就要求每次插入后要把当前点转到根(即 s p l a y ( u , 0 ) splay(u,0) splay(u,0))。

例题

AcWing 2437

首先把所有的数都插入进去。接下来考虑翻转如何实现,不难发现把需要翻转的序列提出来以后,将所有结点的左右子树调换一下即可。直接实现可能会超时,可以用懒标记实现。那如何把需要翻转的序列提出来呢?不难发现把 r + 1 r+1 r+1放到 l − 1 l-1 l1的下面后, r + 1 r+1 r+1的左子树就是要的序列了。因为 l − 1 l-1 l1的右子树都比 l − 1 l-1 l1大,而 r + 1 r+1 r+1 l − 1 l-1 l1大,所以转过去后整个 r + 1 r+1 r+1的子树都比 l − 1 l-1 l1大。又因为 r + 1 r+1 r+1的左子树比 r + 1 r+1 r+1小,所以这就是我们要的序列 l l l r r r。然而我们用了 l − 1 l-1 l1 r + 1 r+1 r+1,会出边界,所以要在左边和右边加上哨兵,则 l − 1 l-1 l1变成 l l l r + 1 r+1 r+1变成 r + 2 r+2 r+2。最后,题目输入的 l l l r r r并不是序列的值,而是编号,即第 l l l个点和第 r r r个点,所以要用平衡树里找当前排名是哪个点的函数来找到 S p l a y Splay Splay中对应的点。最后输出中序遍历即可。

#include<bits/stdc++.h>
using namespace std;
const int NN=1e5+4;
struct node
{
    int v,p,s[2],siz,flag;
}tr[NN];
int root,n,idx;
void push_up(int u)
{
    tr[u].siz=tr[tr[u].s[0]].siz+tr[tr[u].s[1]].siz+1;
}
void push_down(int u)
{
    if(tr[u].flag)
    {
        swap(tr[u].s[0],tr[u].s[1]);
        tr[tr[u].s[0]].flag^=1;
        tr[tr[u].s[1]].flag^=1;
        tr[u].flag=0;
    }
}
void rotate(int u)
{
    int x=tr[u].p,y=tr[x].p,k=tr[x].s[1]==u;
    tr[y].s[tr[y].s[1]==x]=u;
    tr[u].p=y;
    tr[x].s[k]=tr[u].s[k^1];
    tr[tr[u].s[k^1]].p=x;
    tr[u].s[k^1]=x;
    tr[x].p=u;
    push_up(x);
    push_up(u);
}
void splay(int u,int k)
{
    while(tr[u].p!=k)
    {
        int x=tr[u].p,y=tr[x].p;
        if(y!=k)
        {
            if(tr[x].s[0]==u^tr[y].s[0]==x)
                rotate(u);
            else
                rotate(x);
        }
        rotate(u);
    }
    if(!k)
        root=u;
}
void insert(int v)
{
    int u=root,p=0;
    while(u)
    {
        p=u;
        u=tr[u].s[tr[u].v<v];
    }
    u=++idx;
    if(p)
        tr[p].s[tr[p].v<v]=u;
    tr[u].v=v;
    tr[u].p=p;
    tr[u].siz=1;
    splay(u,0);
}
int get_k(int k,int u)
{
    push_down(u);
    if(k<=tr[tr[u].s[0]].siz)
        return get_k(k,tr[u].s[0]);
    if(k==tr[tr[u].s[0]].siz+1)
        return u;
    return get_k(k-tr[tr[u].s[0]].siz-1,tr[u].s[1]);
}
void get_ans(int u)
{
    push_down(u);
    if(tr[u].s[0])
        get_ans(tr[u].s[0]);
    if(tr[u].v>=1&&tr[u].v<=n)
        printf("%d ",tr[u].v);
    if(tr[u].s[1])
        get_ans(tr[u].s[1]);
}
int main()
{
    int m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n+1;i++)
        insert(i);
    while(m--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        l=get_k(l,root);
        r=get_k(r+2,root);
        splay(l,0);
        splay(r,l);
        tr[tr[r].s[0]].flag^=1;
    }
    get_ans(root);
    return 0;
}

AcWing 950

1 1 1 4 4 4操作都是平衡树的基本操作,不用在意。 2 2 2 3 3 3操作不难发现一定是群体同加或同减,所以可以用一个值 d e l t a delta delta来暂时存储,注意进来时要减去 d e l t a delta delta,因为他不受目前的工资变化的影响。要求走了几个员工可以用总量减去当前量(平衡树大小)计算。需要注意,每一次 3 3 3操作可能会删除从 a a a开始之前的所有点,所以可以用上一题提出某一个序列的方式,再把左儿子设置成一个不存在的结点就行。找 a a a是平衡树的基本操作,和二叉搜索树查询操作是基本一样的。

#include<bits/stdc++.h>
using namespace std;
const int NN=1e5+4;
struct node
{
    int v,p,siz,s[2];
}tr[NN];
int root,idx;
void push_up(int u)
{
    tr[u].siz=tr[tr[u].s[0]].siz+tr[tr[u].s[1]].siz+1;
}
void rotate(int u)
{
    int x=tr[u].p,y=tr[x].p,k=tr[x].s[1]==u;
    tr[y].s[tr[y].s[1]==x]=u;
    tr[u].p=y;
    tr[x].s[k]=tr[u].s[k^1];
    tr[tr[u].s[k^1]].p=x;
    tr[u].s[k^1]=x;
    tr[x].p=u;
    push_up(x);
    push_up(u);
}
void splay(int u,int k)
{
    while(tr[u].p!=k)
    {
        int x=tr[u].p,y=tr[x].p;
        if(y!=k)
        {
            if(tr[x].s[0]==u^tr[y].s[0]==x)
                rotate(u);
            else
                rotate(x);
        }
        rotate(u);
    }
    if(!k)
        root=u;
}
void insert(int v)
{
    int u=root,p=0;
    while(u)
    {
        p=u;
        u=tr[u].s[tr[u].v<v];
    }
    u=++idx;
    if(p)
        tr[p].s[tr[p].v<v]=u;
    tr[u].p=p;
    tr[u].v=v;
    tr[u].siz=1;
    splay(u,0);
}
int get_k(int u,int k)
{
    if(tr[tr[u].s[0]].siz>=k)
        return get_k(tr[u].s[0],k);
    if(tr[tr[u].s[0]].siz+1==k)
        return tr[u].v;
    return get_k(tr[u].s[1],k-tr[tr[u].s[0]].siz-1);
}
int get_more(int u,int v)
{
    int res=0;
    while(u)
    {
        if(tr[u].v>=v)
        {
            res=u;
            u=tr[u].s[0];
        }
        else
            u=tr[u].s[1];
    }
    return res;
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    insert(-1e9);
    insert(1e9);
    int l=1,r=2,num=0,delta=0;
    while(n--)
    {
        char opt[2];
        int x;
        scanf("%s%d",opt,&x);
        if(opt[0]=='I')
        {
            if(x<m)
                continue;
            insert(x-delta);
            num++;
        }
        else if(opt[0]=='A')
            delta+=x;
        else if(opt[0]=='S')
        {
            delta-=x;
            r=get_more(root,m-delta);
            splay(r,0);
            splay(l,r);
            tr[l].s[1]=0;
            push_up(l);
            push_up(r);
        }
        else if(opt[0]=='F')
        {
            if(tr[root].siz-2<x)
                puts("-1");
            else
                printf("%d\n",get_k(root,tr[root].siz-x)+delta);
        }
    }
    printf("%d",num-tr[root].siz+2);
}

AcWing 1063

本题操作都是平衡树最基础的操作,没什么好讲的。但是需要注意的是,我们有可能会把两个平衡树合并。这里就可以用到启发式合并,即把集合内元素较少的插入到元素较大的中。这样可以把时间复杂度控制在可行的范围内。

#include<bits/stdc++.h>
using namespace std;
const int NN=100004;
struct node
{
    int v,id,p,s[2],siz;
}tr[1800004];
int fa[NN],root[NN],idx;
int find(int x)
{
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
void pushup(int u)
{
    tr[u].siz=tr[tr[u].s[0]].siz+tr[tr[u].s[1]].siz+1;
}
void rotate(int u)
{
    int x=tr[u].p,y=tr[x].p,k=tr[x].s[1]==u;
    tr[y].s[tr[y].s[1]==x]=u;
    tr[u].p=y;
    tr[x].s[k]=tr[u].s[k^1];
    tr[tr[u].s[k^1]].p=x;
    tr[u].s[k^1]=x;
    tr[x].p=u;
    pushup(x);
    pushup(u);
}
void splay(int u,int k,int a)
{
    while(tr[u].p!=k)
    {
        int x=tr[u].p,y=tr[x].p;
        if(y!=k)
        {
            if(tr[x].s[0]==u^tr[y].s[0]==x)
                rotate(u);
            else
                rotate(x);
        }
        rotate(u);
    }
    if(!k)
        root[a]=u;
}
void insert(int v,int id,int a)
{
    int u=root[a],p=0;
    while(u)
    {
        p=u;
        u=tr[u].s[tr[u].v<v];
    }
    u=++idx;
    if(p)
        tr[p].s[tr[p].v<v]=u;
    tr[u].v=v;
    tr[u].id=id;
    tr[u].p=p;
    tr[u].siz=1;
    splay(u,0,a);
}
int get_k(int u,int k)
{
    if(tr[tr[u].s[0]].siz>=k)
        return get_k(tr[u].s[0],k);
    if(tr[tr[u].s[0]].siz+1==k)
        return tr[u].id;
    return get_k(tr[u].s[1],k-tr[tr[u].s[0]].siz-1);
}
void dfs(int u,int b)
{
    if(tr[u].s[0])
        dfs(tr[u].s[0],b);
    if(tr[u].s[1])
        dfs(tr[u].s[1],b);
    insert(tr[u].v,tr[u].id,b);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        fa[i]=i;
        int x;
        scanf("%d",&x);
        insert(x,i,i);
    }
    for(int i=1;i<=m;i++)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        a=find(a);
        b=find(b);
        if(a!=b)
        {
            if(tr[root[a]].siz>tr[root[b]].siz)
                swap(a,b);
            dfs(root[a],b);
            fa[a]=b;
        }
    }
    int q;
    scanf("%d",&q);
    while(q--)
    {
        char opt[2];
        int x,y;
        scanf("%s%d%d",opt,&x,&y);
        if(opt[0]=='B')
        {
            x=find(x);
            y=find(y);
            if(x!=y)
            {
                if(tr[root[x]].siz>tr[root[y]].siz)
                    swap(x,y);
                dfs(root[x],y);
                fa[x]=y;
            }
        }
        else
        {
            x=find(x);
            if(tr[root[x]].siz<y)
                puts("-1");
            else
                printf("%d\n",get_k(root[x],y));
        }
    }
    return 0;
}

AcWing 955

第一个操作可以单独对这个序列建一个平衡树再插入到原序列。可以把要插入的点和后一点隔开,即把插入位置后一点挂到要插入的位置编号的下面,然后把新建的树挂到左子树上。第二个操作如AcWing 950。第三个操作可以像线段树一样用一个懒标记。第四个操作同AcWing 2437。第五个操作可以在每一个点存一个当前子树的和 s u m sum sum,然后把序列挑出来(如AcWing 950)即可。最后一个操作要求任意一段的最大子列 m s ms ms可以如AcWing 950把序列挑出来,但是值不好求。我们可以用从最左开始的最大子列 l s ls ls和从最右开始的最大子列 r s rs rs两个值来辅助。每次更新这三个值,令 v v v表示点权, u u u为当前点, l l l为左子树, r r r为右子树则有: u . m s = max ⁡ ( l . m s , r . m s , l , r s + v u + r . l s ) u.ms=\max(l.ms,r.ms,l,rs+v_u+r.ls) u.ms=max(l.ms,r.ms,l,rs+vu+r.ls) u . l s = m a x ( l . l s , l . s u m + v u + r . l s ) u.ls=max(l.ls,l.sum+v_u+r.ls) u.ls=max(l.ls,l.sum+vu+r.ls) u . r s = m a x ( r . r s , r . s u m + v u + l . r s ) u.rs=max(r.rs,r.sum+v_u+l.rs) u.rs=max(r.rs,r.sum+vu+l.rs)

#include<bits/stdc++.h>
using namespace std;
const int NN=500004;
struct node
{
    int v,p,s[2],siz,sum,ms,ls,rs,rev,same;
    void init(int _v,int _p)
    {
        s[0]=s[1]=0;
        v=sum=ms=_v;
        p=_p;
        siz=1;
        ls=rs=max(v,0);
        rev=same=0;
    }
}tr[NN];
int nodes[NN],idx,w[NN],root;
void pushup(int u)
{
    node l=tr[tr[u].s[0]],r=tr[tr[u].s[1]];
    tr[u].siz=l.siz+r.siz+1;
    tr[u].sum=l.sum+r.sum+tr[u].v;
    tr[u].ls=max(l.ls,l.sum+tr[u].v+r.ls);
    tr[u].rs=max(r.rs,r.sum+tr[u].v+l.rs);
    tr[u].ms=max(max(l.ms,r.ms),l.rs+tr[u].v+r.ls);
}
void pushdown(int u)
{
    node&l=tr[tr[u].s[0]],&r=tr[tr[u].s[1]];
    if(tr[u].same)
    {
        tr[u].same=tr[u].rev=0;
        if(tr[u].s[0])
        {
            l.same=1;
            l.v=tr[u].v;
            l.sum=tr[u].v*l.siz;
        }
        if(tr[u].s[1])
        {
            r.same=1;
            r.v=tr[u].v;
            r.sum=tr[u].v*r.siz;
        }
        if(tr[u].v>=0)
        {
            if(tr[u].s[0])
                l.ls=l.rs=l.ms=l.sum;
            if(tr[u].s[1])
                r.ls=r.rs=r.ms=r.sum;
        }
        else
        {
            if(tr[u].s[0])
            {
                l.ls=l.rs=0;
                l.ms=tr[u].v;
            }
            if(tr[u].s[1])
            {
                r.ls=r.rs=0;
                r.ms=tr[u].v;
            }
        }
    }
    if(tr[u].rev)
    {
        tr[u].rev=0;
        l.rev^=1;
        r.rev^=1;
        swap(l.s[0],l.s[1]);
        swap(l.ls,l.rs);
        swap(r.s[0],r.s[1]);
        swap(r.ls,r.rs);
    }
}
void rotate(int u)
{
    int x=tr[u].p,y=tr[x].p,k=tr[x].s[1]==u;
    tr[y].s[tr[y].s[1]==x]=u;
    tr[u].p=y;
    tr[x].s[k]=tr[u].s[k^1];
    tr[tr[u].s[k^1]].p=x;
    tr[u].s[k^1]=x;
    tr[x].p=u;
    pushup(x);
    pushup(u);
}
void splay(int u,int k)
{
    while(tr[u].p!=k)
    {
        int x=tr[u].p,y=tr[x].p;
        if(y!=k)
        {
            if(tr[x].s[0]==u^tr[y].s[0]==x)
                rotate(u);
            else
                rotate(x);
        }
        rotate(u);
    }
    if(!k)
        root=u;
}
int get_k(int u,int k)
{
    pushdown(u);
    if(tr[tr[u].s[0]].siz>=k)
        return get_k(tr[u].s[0],k);
    if(tr[tr[u].s[0]].siz+1==k)
        return u;
    return get_k(tr[u].s[1],k-tr[tr[u].s[0]].siz-1);
}
int build(int l,int r,int p)
{
    if(l>r)
        return 0;
    int u=nodes[idx--],mid=l+(r-l)/2;
    tr[u].init(w[mid],p);
    tr[u].s[0]=build(l,mid-1,u);
    tr[u].s[1]=build(mid+1,r,u);
    pushup(u);
    return u;
}
void dfs(int u)
{
    if(tr[u].s[0])
        dfs(tr[u].s[0]);
    if(tr[u].s[1])
        dfs(tr[u].s[1]);
    nodes[++idx]=u;
}
int main()
{
    for(int i=1;i<NN;i++)
        nodes[++idx]=i;
    int n,m;
    scanf("%d%d",&n,&m);
    tr[0].ms=w[0]=w[n+1]=-1e9;
    for(int i=1;i<=n;i++)
        scanf("%d",&w[i]);
    root=build(0,n+1,0);
    while(m--)
    {
        char opt[20];
        scanf("%s",opt);
        if(opt[0]=='I')
        {
            int posi,tot;
            scanf("%d%d",&posi,&tot);
            for(int i=0;i<tot;i++)
                scanf("%d",&w[i]);
            int l=get_k(root,posi+1),r=get_k(root,posi+2);
            splay(l,0);
            splay(r,l);
            tr[r].s[0]=build(0,tot-1,r);
            pushup(r);
            pushup(l);
        }
        else if(opt[0]=='D')
        {
            int posi,tot;
            scanf("%d%d",&posi,&tot);
            int l=get_k(root,posi),r=get_k(root,posi+tot+1);
            splay(l,0);
            splay(r,l);
            dfs(tr[r].s[0]);
            tr[r].s[0]=0;
            pushup(r);
            pushup(l);
        }
        else if(opt[0]=='M'&&opt[2]=='K')
        {
            int posi,tot,c;
            scanf("%d%d%d",&posi,&tot,&c);
            int l=get_k(root,posi),r=get_k(root,posi+tot+1);
            splay(l,0);
            splay(r,l);
            node&work=tr[tr[r].s[0]];
            work.same=1;
            work.v=c;
            work.sum=c*work.siz;
            if(c>=0)
                work.ms=work.ls=work.rs=work.sum;
            else
            {
                work.ms=c;
                work.ls=work.rs=0;
            }
            pushup(r);
            pushup(l);
        }
        else if(opt[0]=='R')
        {
            int posi,tot;
            scanf("%d%d",&posi,&tot);
            int l=get_k(root,posi),r=get_k(root,posi+tot+1);
            splay(l,0);
            splay(r,l);
            node&work=tr[tr[r].s[0]];
            work.rev^=1;
            swap(work.s[0],work.s[1]);
            swap(work.ls,work.rs);
            pushup(r);
            pushup(l);
        }
        else if(opt[0]=='G')
        {
            int posi,tot;
            scanf("%d%d",&posi,&tot);
            int l=get_k(root,posi),r=get_k(root,posi+tot+1);
            splay(l,0);
            splay(r,l);
            printf("%d\n",tr[tr[r].s[0]].sum);
        }
        else
            printf("%d\n",tr[root].ms);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值