学习笔记: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 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
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);elserotate(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
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);elserotate(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];}elseu=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");elseprintf("%d\n",get_k(root,tr[root].siz-x)+delta);}}printf("%d",num-tr[root].siz+2);
}
AcWing 1063
本题操作都是平衡树最基础的操作,没什么好讲的。但是需要注意的是,我们有可能会把两个平衡树合并。这里就可以用到启发式合并,即把集合内元素较少的插入到元素较大的中。这样可以把时间复杂度控制在可行的范围内。
#include
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);elserotate(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");elseprintf("%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
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);elserotate(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);}elseprintf("%d\n",tr[root].ms);}return 0;
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
