概念
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 l−1的下面后, r + 1 r+1 r+1的左子树就是要的序列了。因为 l − 1 l-1 l−1的右子树都比 l − 1 l-1 l−1大,而 r + 1 r+1 r+1比 l − 1 l-1 l−1大,所以转过去后整个 r + 1 r+1 r+1的子树都比 l − 1 l-1 l−1大。又因为 r + 1 r+1 r+1的左子树比 r + 1 r+1 r+1小,所以这就是我们要的序列 l l l到 r r r。然而我们用了 l − 1 l-1 l−1和 r + 1 r+1 r+1,会出边界,所以要在左边和右边加上哨兵,则 l − 1 l-1 l−1变成 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;
}