转载请保留本博客源地址:http://blog.csdn.net/u011327397/article/details/53783700
作者Anantheparty:http://blog.csdn.net/u011327397
本来是要去学lct,然后看到要用splay我又不会,就开始看splay了,然后发现splay 写 艹起来特别爽,但是序列操作网上没看到有详讲的,理解的时候很多奇怪的问题(无限RE)再加上指针调试复杂度高,花了很多时间,就觉得来写一个讲解吧。然后小生才疏学浅,难免会出错,如果哪位神犇看见了错误,有劳在评论区提出,THX。
#基本概念
基本概念(我的理解)还是大概说一下。
##二叉查找树
一颗符合左子树结点全部小于当前结点,右子树结点全部大于当前节点的树。
可以看出二叉查找树查找等操作的复杂度是很不稳定的,树的结构是严重影响效率的(链状的树和完全二叉树)
##平衡树
使二叉查找树维持基本平衡的数据结构,如treap,splay,sbt,AVL,红黑树(后面3个我都不会)
#splay平衡树的基本操作
##Rotate(旋转)
旋转看着一张图就足够了,实际上我觉得每次旋转都把这个图画出来不容易写错。。。
旋转的意思就是让我们这棵树变一下形,但是又不改变二叉查找树的规则,基本操作就是左旋和右旋,旋转的原理可以自己写出x,y,A,B,C的大小关系然后去画
我先学习的treap,因为treap不用维护父亲指针,里面的旋转超级简单,就2句话,而这里要,旋转一下就重要了。
旋转是最重要最基本的操作,一定不能记错,快速记住旋转的方法是这样的(看着上面的图):一共有3句:
B与x,y的父子关系。
y的父亲与x,y的父子关系。
x,y的父子关系。
然后每一句话对应两个操作一个是父亲指针的改变,一个是儿子指针的改变。代码如下。
void rotate(tree2 *x,int p)//p==0 left; p==1 right
{
tree2 *y=x->fa;
y->son[p^1]=x->son[p];
if(x->son[p]!=Null)x->son[p]->fa=y;//不一定有B这个儿子,要特判
y->fa->son[y->fa->son[1]==y]=x;//这是一种判断儿子方向的技巧,可以自己画一下
x->fa=y->fa;
x->son[p]=y;
y->fa=x;
pushup(y);//后面说
}
然后就是splay的旋转了,splay的旋转可以分为4种,ZIG-ZIG(右旋-右旋),ZAG-ZAG(右旋-右旋),ZIG-ZAG(右旋-左旋),ZAG-ZIG(左旋-右旋)。
(实际上是2种,相同方向和不同方向)
为什么非要分成这几个呢!!!
为什么不能直接把x一直向上转转转呢???
因为要保证复杂度,这样会让树转完变得更平衡,有一篇叫《伸展树的基本操作与应用》的论文里面有证明,我这种蒟蒻肯定看不懂,有兴趣的神犇可以去看。
对于具体的旋转这里有两张图,根据图上面树的位置关系可以手推出来怎么转,简单的说就是两个一样的方向就先转爸爸,然后自己,然后如果方向两次不一样就把自己按着该旋转的方向转上去。
##Splay(伸展)
这个是splay里面最重要的部分了(毕竟名字都是这个)不过感觉该说的都在rotate里面说完了。。
伸展就是讲一个结点通过旋转操作转到它某一个祖先下面。
主要就是上面几种旋转,然后如果只用转一次就到了特判一下
需要注意的地方就是每一个操作后面都要有splay,splay相当于是在维护树的平衡,并且使越近访问的结点越接近根,也就是说我们一直访问一个点就会很快很快,这个在OI好像没什么用,但是实际生活感觉很有用(其实我也不知道)。
代码:
void splay(tree2 *tree,tree2 *goal)//goal==null splay to the root
{
if(tree==goal)return ;
while(tree->fa!=goal)
{
if(tree->fa->fa==goal)//特判只用一次旋转
{
rotate(tree,tree->fa->son[0]==tree);//等价于rotate(tree,tree->fa->son[1]==tree^1)
break;
}
tree2 *fa=tree->fa,*gfa=fa->fa;
int x=gfa->son[1]==fa;
//旋转具体方向最好去画,比死记要好
if((tree==fa->son[1])==x)//相同方向旋转
{
rotate(fa,x^1);
rotate(tree,x^1);
}
else //相反方向
{
rotate(tree,x);
rotate(tree,x^1);
}
}
pushup(tree);
if(goal==null)root=tree;//更新根节点,容易写掉
}
##pushup
感觉一般平衡树里面只用维护结点个数,主要是按名次查询的时候用的,更多作用后面数列维护的地方讲。
void pushup(tree2 *tree)
{
tree->siz=tree->num+tree->lson->siz+tree->rson->siz;
}
##Find查找
就是根据二叉查找树的性质找数值为一个点的结点
tree2 *find_num(int k)
{
tree2 *temp=root;
while(temp->son[temp->n<k])//向下寻找结点
{
if(temp->n==k)break;
temp=temp->son[temp->n<k];
}
if(temp->n!=k)return Null;
return temp;
}
##query查询
我这里是指按名次查询,就是查询排第几的数是什么,然后这个时候我们一直维护的size就有用了,根据左右子树size的大小我们就可以一直向下走了
int query(int k)
{
tree2 *temp=root;
int num=temp->lson->siz;
while(!k==num+1)
{
if(k>num)//k>num说明在右子树
{
k-=num+1;
temp=temp->rson;
}
else temp=temp->lson;
num=temp->lson->siz;
}
splay(temp,null);//无论如何splay
return temp->n;
}
##Insert插入
这个实际上和查询差不多,就是如果没查到就创建新的结点。
void insert(int k)
{
if(root==NULL)//空树特判
{
root=newtree(null,k);
return ;
}
tree2 *temp=root;
while(temp->son[temp->n<k]!=Null)//同find
{
if(temp->n==k)//重复不添加
{
splay(temp);//无论何时都要splay
return ;
}
temp=temp->son[temp->n<k];
}
temp->son[temp->n<k]=newtree(temp,k);
splay(temp->son[temp->n<k],null);
}
##delete删除
删除要稍微复杂一点,主要是删除节点儿子的处理问题。
假设要删除的结点没有2个儿子,那就直接删除,然后把儿子接到他的父亲上就好了,然后我们来看一下2个儿子的情况。
想象一个有序数列,中间有一个数被删去了,那么原来这个数左边的数就接到了他原来这个数右边的数的最左边(或者右边的数接到了左边的最右边)
我们模拟这个操作,就找到左子树最大的一个点,因为这个点最大,它一定没有右儿子,我们就可以直接把右儿子整个接上去(因为右儿子的所有数一定大于左儿子的所有数),找最大的数就一直沿着右儿子指针向下跑就可以了。
然后实现感觉我写得有点麻烦。。。
void delet(int k)
{
tree2 *temp=find(k);
int x=temp->fa->son[1]==temp;//temp和爸爸的关系
if(temp->lson==Null)//4种情况分类讨论
if(temp->rson==Null)//无子树
temp->fa->son[x]=Null;
else //一棵子树
{
temp->fa->son[x]=temp->rson;
temp->rson->fa=temp->fa;
}
else
if(temp->rson==Null)//一棵子树
{
temp->fa->son[x]=temp->lson;
temp->lson->fa=temp->fa;
}
else//两棵子树
{
tree2 *tree=temp->lson;
temp->fa->son[x]=temp->lson;
temp->lson->fa=temp->fa;//先将左子树接到父亲上
while(tree->rson!=Null)tree=tree->rson;//寻找左子树最大点
tree->rson=temp->rson;//接上右子树
temp->rson->fa=tree;
pushup(tree);
splay(tree,null);
return ;
}
pushup(temp->fa);
splay(temp->fa,null);
}
##per前驱
##next后驱
前驱:小于当前数的最大值
后驱:大于当前数的最小值
找到一个数,然后左子树一直向右跳就是前驱,右子树一直向左跳就是后驱,然后可以写一起。
int get_num(tree2 *tree,int x)//x==0 pre; x==1 next
{
tree2 *temp=tree->son[x];
if(temp==NULL)return INF;
while(temp->son[x^1])temp=temp->son[x^1];
return abs(tree->n-temp->n);
}
##split分裂
这个和后文的数列维护删除数列差不多,就是把要分裂的部分旋转到一棵树上,然后删除,pushup,然后回收空间
##merge合并
合并有前提,就是一棵树全部元素大于另一棵树。
然后我没用过这种东西,实际上随便乱搞一下就好了,比如把大的那棵树的最小节点旋转到根,然后直接把小的那棵树接过来什么的,感觉用不到。
#splay维护序列
刚开始看这个主要是想去看看翻转,然后感觉翻转很难,然后想先看看其他序列操作,看完后在发现翻转最简单。。。
然后再在某篇文献里面看到说splay解决线段树问题要快15倍,然后就更想去看了。实际上我写的splay之比线段树快了零点几秒,不过看网上的人说splay是比线段树慢15倍我还是很开心的(万一是线段树写丑了呢233)
##基本思想
再说具体操作之前先说一下思想,理解了思想差不多后面的操作全部可以不管自己就直接靠感觉写了。
1、splay用来排序的这个时候是序号,就是那个节点在数列是第几个,所以中序遍历就是原序列,所以翻转不会有什么打乱顺序,顺序却是改变了
2、维护序列最重要的就是要找到你要维护的区间,因为区间内的数是连续的所以可以想办法弄到一颗子树上,具体操作就是这张图
我们如果要对[a,b]进行某些操作那么将树旋转成这样以后root->rson->lson就是我们要的子树了,直接对这个子树可以进行所有操作
3、splay比线段树能多做的事和优点:可以在任意数后面加入,删除,同时还有区间旋转,然后还有节省空间(splay只用O(n))的空间
4、如果只有n个结点上面的查询有着明显的问题,[1,n]该怎么查询?因此我们要增加0和n+1结点,作为哨兵结点来保护头和尾
##pushup
##pushdown
类似线段树操作,但需要注意一点,在splay里面当前节点代表的区间由左子树,自己和右子树构成,而线段树仅由左右子树,所以splay要多考虑一个自己,下面放一个求和的好了,下面的程序lazy是区间加一个值。
void pushup(tree2 *tree)
{
tree->siz=tree->lson->siz+tree->rson->siz+1;
tree->sum=tree->key+tree->lson->sum+tree->rson->sum;
}
void pushdown(tree2 *tree)
{
if(tree->lazy)//这里和线段树类似
{
tree->lson->lazy+=tree->lazy;
tree->rson->lazy+=tree->lazy;
tree->lson->sum+=(ll)tree->lson->siz*tree->lazy;
tree->rson->sum+=(ll)tree->rson->siz*tree->lazy;
if(tree->lson!=Null)tree->lson->key+=tree->lazy;
if(tree->rson!=Null)tree->rson->key+=tree->lazy;
tree->lazy=0;
}
}
##bulid建树
建树就是将这个序列按序列号大小转换成一颗二叉搜索树,我们从中间开始这棵树一来就比较平衡,降低常数(建一条链也是二叉搜索树)
然后建树很重要,用指针建树的时候一定要想清楚(不然一直RE),子树的地址是什么,先放代码:
tree2 *bulid(tree2 *fa,int l,int r)
{
if(l>r)return Null;
int mid=(l+r)>>1;
tree2 *tree=newtree(fa,a[mid]);
tree->lson=bulid(tree,l,mid-1);
tree->rson=bulid(tree,mid+1,r);
pushup(tree);
return tree;
}
这里有几点需要注意的。第一,bulid时是mid-1和线段树不同。第二,如果要让tree做参数,tree的地址就要提前赋好。
##rotate旋转
这里旋转和前面差不多其实就增加了pushup&pushdown
void rotate(tree2 *tree,int x)//x==0 left,x==1 right
{
tree2 *fa=tree->fa;
pushdown(fa);//增加语句
pushdown(tree);//增加语句
fa->son[x^1]=tree->son[x];
if(tree->son[x])tree->son[x]->fa=fa;
fa->fa->son[fa->fa->son[1]==fa]=tree;
tree->fa=fa->fa;
fa->fa=tree;
tree->son[x]=fa;
pushup(fa);
}
然后为什么最后只pushup(fa),因为splay的时候tree在下一次操作的时候还会被调用,pushup了也会立马被破坏,所以我们只用在splay结束的地方pushup一次就可以了,这样可以减小常数
##splay伸展
splay和前面完全一样,pushdown都在rotate里面了,省略。
##splayto按序号伸展
我乱起的名字,我也不知道叫什么,中英文都不知道。。
理解成将在原序列中编号为k的旋转到它的某个祖先下面
实际上就是一个查询+splay查询和上面的query 基本一样,
void splayto(int k,tree2 *goal)
{
tree2 *tree=root;
pushdown(tree);
int num=tree->lson->siz;
while(num!=k)//这里上面是num+1
{
if(num<k)
{
tree=tree->rson;
k-=num+1;
}
else tree=tree->lson;
pushdown(tree);
num=tree->lson->siz;
}
splay(tree,goal);
}
造成这种差异是哨兵元素(在基本思想最后一个提了一句),导致多了0号结点。
##Insert插入
在pos后面插入一个数或者一段数,这个是splay特有的,线段树做不到,思想很简单,就把pos转到根,然后pos+1转到根的右儿子(新加入的数序号肯定在原pos和pos+1之间),然后把要加入的数列建一棵树加在pos+1的左儿子。
void insert(int pos,int num)//这里是加一段数,num是数的个数
{
if(num==0)return ;
for(int i=1;i<=num;i++)
a[i]=read();
splayto(pos,null);//pos转到根
splayto(pos+1,root);//pos+1到root->rson
key=bulid(root->rson,1,n);//key是root->rson->lson后同
pushup(root->rson);
pushup(root);
}
##delete删除
删除还是一样,先把要删除区间转到key(见上文代码注释),然后直接删除,回收空间就好了
void delet(int pos,int num)
{
if(num==0)return ;
splayto(pos-1,null);
splayto(pos+num,root);
rec(key);//回收空间
key=Null;//删除
pushup(root->rson);
pushup(root);
}
##addnum区间修改
这里用区间加一个数来举例子,其他区间修改类似线段树。
还是先把区间找到,然后直接修改就好了,没什么好多说的
void add(int l,int r,int num)
{
splayto(l-1,null);
splayto(r+1,root);
key->lazy+=num;
key->sum+=(ll)num*key->siz;
key->key+=num;
}
##reserve旋转
旋转实际上和上面的修改差不多,只是pushdown有点不一样。
一个区间如果旋转2次就又转回来了,所以我们直接异或1就好了
void reverse(int l,int r)
{
if(num==0)return ;
splayto(l-1,null);
splayto(r+1,root);
key->f^=1;
swap(key->lson,key->rson);
swap(key->lx,key->rx);
pushup(root->rson);
pushup(root);
}
void pushdown(tree2 *tree)
{
if(tree->f)
{
tree->lson->f^=1;
tree->rson->f^=1;
swap(tree->lson->lson,tree->lson->rson);
swap(tree->rson->lson,tree->rson->rson);
tree->f=0;
}
}
##sum求和
求和类似线段树,pushup实际上就是我上面放那个
##print输出数列
直接中序遍历输出就好了,要特判哨兵不输出(我下面没有特判)。
然后记得pushdown。
void print(tree2 *tree)
{
pushdown(tree);
if(tree->lson!=Null)print(tree->lson);
printf("%d ",tree->n);
if(tree->rson!=Null)print(tree->rson);
}
#基础训练题目
后面都是一些裸题,可以用来上手。
我写得都比较长(不习惯压码),但自认为可读性和速度还是可以
##【bzoj1588】 [HNOI2002] 营业额统计
基本平衡树操作,只有insert和查询前驱后驱
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define INF 2100000000
#define ll long long
#define clr(x) memset(x,0,sizeof(x))
#define maxclr(x) memset(x,127,sizeof(x))
using namespace std;
inline int read()
{
char c;
int ret=0,b=1;
while(!(c>='0'&&c<='9'))
{
if(c=='-')b=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
ret=(c-'0')+(ret<<1)+(ret<<3);
c=getchar();
}
return ret*b;
}
#define M 34000
struct tree2
{
tree2 *son[2],*fa;
int n;
}*root,dizhi[M],*null=NULL;
int n,t,ans;
void rotate(tree2 *tree,int x)//x==0 left; x==1 right
{
tree2 *fa=tree->fa;
fa->son[x^1]=tree->son[x];
if(tree->son[x])tree->son[x]->fa=fa;
tree->son[x]=fa;
fa->fa->son[fa->fa->son[1]==fa]=tree;
tree->fa=fa->fa;
fa->fa=tree;
}
void splay(tree2 *tree,tree2 *top)//top==null splay to the root
{
while(tree->fa!=top)
{
if(tree->fa->fa==top)
{
rotate(tree,tree->fa->son[0]==tree);
break;
}
tree2 *fa=tree->fa,*gfa=fa->fa;
int x=gfa->son[1]==fa;
if((tree==fa->son[1])==x)
{
rotate(fa,x^1);
rotate(tree,x^1);
}
else
{
rotate(tree,x);
rotate(tree,x^1);
}
}
if(top==null)root=tree;
}
tree2 *newtree(int num,tree2 *fa)
{
tree2 *newnode=&dizhi[t++];
newnode->fa=fa;
newnode->n=num;
return newnode;
}
int insert(int x)
{
tree2 *temp=root;
while(temp->son[x>temp->n])
{
if(x==temp->n)
{
splay(temp,null);
return 1;
}
temp=temp->son[x>temp->n];
}
tree2 *newnode=newtree(x,temp);
temp->son[x>temp->n]=newnode;
splay(newnode,null);
return 0;
}
int get_num(tree2 *tree,int x)//x==0 pre; x==1 next
{
tree2 *temp=tree->son[x];
if(temp==NULL)return INF;
while(temp->son[x^1])temp=temp->son[x^1];
return abs(tree->n-temp->n);
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
n=read();
null=&dizhi[t++];
root=newtree(ans+=read(),null);
for(int i=2;i<=n;i++)
{
int x=read();
if(insert(x))continue;
if(i==1)ans+=x;
else
{
int a=get_num(root,0);
int b=get_num(root,1);
ans+=min(a,b);
}
}
printf("%d",ans);
return 0;
}
##【poj3468】 A Simple Problem with Integers
这个是线段树经典题目了,区间修改,区间求和。splay做起来实际上要复杂得多,不过2797MS比我大线段树3063MS要快了一点点
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define INF 2100000000
#define ll long long
#define clr(x) memset(x,0,sizeof(x))
#define maxclr(x) memset(x,127,sizeof(x))
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
using namespace std;
inline int read()
{
char c;
int ret=0,b=1;
while(!(c>='0'&&c<='9'))
{
if(c=='-')b=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
ret=(c-'0')+(ret<<1)+(ret<<3);
c=getchar();
}
return ret*b;
}
#define M 100005
#define Key root->son[1]->son[0]
#define lson son[0]
#define rson son[1]
struct tree2
{
tree2 *son[2],*fa,*self;
int key,lazy,siz,mid;
ll sum;
}*root,dizhi[M],*null,*Null;
int t,n,m,a[M];
tree2 *newtree(tree2 *fa,int key)
{
tree2 *tree=&dizhi[t++];
tree->fa=fa;
tree->key=tree->sum=key;
tree->siz=1;
//tree->lson=tree->rson=Null;
return tree;
}
void pushup(tree2 *tree)
{
if(tree==null)return ;
tree->siz=tree->lson->siz+tree->rson->siz+1;
tree->sum=tree->key+tree->lson->sum+tree->rson->sum;
}
void pushdown(tree2 *tree)
{
if(tree->lazy)
{
tree->lson->lazy+=tree->lazy;
tree->rson->lazy+=tree->lazy;
tree->lson->sum+=(ll)tree->lson->siz*tree->lazy;
tree->rson->sum+=(ll)tree->rson->siz*tree->lazy;
if(tree->lson!=Null)tree->lson->key+=tree->lazy;
if(tree->rson!=Null)tree->rson->key+=tree->lazy;
tree->lazy=0;
}
}
tree2 *bulid(tree2 *tree,tree2 *fa,int l,int r)
{
if(l>r)return Null;
int mid=(l+r)>>1;
tree=newtree(fa,a[mid]);
//printf("233:%d\n",mid);
tree->lson=bulid(tree->lson,tree,l,mid-1);
tree->rson=bulid(tree->rson,tree,mid+1,r);
pushup(tree);
//printf("233:%d %d %d\n",l,r,tree->sum);
return tree;
}
void rotate(tree2 *tree,int x)//x==0 left,x ==1 right
{
tree2 *fa=tree->fa;
pushdown(fa);
pushdown(tree);
fa->son[x^1]=tree->son[x];
if(tree->son[x])tree->son[x]->fa=fa;
fa->fa->son[fa->fa->son[1]==fa]=tree;
tree->fa=fa->fa;
fa->fa=tree;
tree->son[x]=fa;
pushup(fa);
}
void splay(tree2 *tree,tree2 *goal)
{
if(tree==goal)return ;
pushdown(tree);
while(tree->fa!=goal)
{
tree2 *fa=tree->fa,*gfa=fa->fa;
if(gfa==goal)
{
rotate(tree,tree==fa->son[0]);
break;
}
int x=fa==gfa->son[1];
if((tree==fa->son[1])==x)
{
rotate(fa,x^1);
rotate(tree,x^1);
}
else
{
rotate(tree,x);
rotate(tree,x^1);
}
}
pushup(tree);
if(goal==null)root=tree;
}
void splayto(int k,tree2 *goal)
{
tree2 *temp=root;
pushdown(root);
int num=temp->lson->siz;
while(num!=k)
{
if(k>num)
{
k-=num+1;
temp=temp->rson;
}
else temp=temp->lson;
pushdown(temp);
num=temp->lson->siz;
}
splay(temp,goal);
}
void add(int l,int r,int num)
{
splayto(l-1,null);
splayto(r+1,root);
Key->lazy+=num;
Key->sum+=(ll)num*Key->siz;
Key->key+=num;
}
ll query(int l,int r)
{
splayto(l-1,null);
splayto(r+1,root);
return Key->sum;
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++)
a[i]=read();
Null=&dizhi[t++];
null=newtree(NULL,0);null->siz=0;
root=newtree(null,0);
root->rson=newtree(root,0);
root->lson=Null;root->rson->rson=Null;
Key=bulid(Key,root->rson,1,n);
pushup(root->rson);pushup(root);
for(int i=1;i<=m;i++)
{
char c=getchar();
while(c!='Q'&&c!='C')c=getchar();
if(c=='Q')
{
int l=read(),r=read();
printf(AUTO "\n",query(l,r));
}
else
{
int l=read(),r=read(),num=read();
add(l,r,num);
}
}
return 0;
}
##【bzoj3223】 Tyvj 1729 文艺平衡树
这个就一个旋转。。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define INF 2100000000
#define ll long long
#define clr(x) memset(x,0,sizeof(x))
#define maxclr(x) memset(x,127,sizeof(x))
using namespace std;
inline int read()
{
char c;
int ret=0;
while(!(c>='0'&&c<='9'))
c=getchar();
while(c>='0'&&c<='9')
{
ret=(c-'0')+(ret<<1)+(ret<<3);
c=getchar();
}
return ret;
}
#define M 100005
struct tree2
{
tree2 *son[2],*fa;
int n,f,siz;
}*root,dizhi[M],*null,*Null;
#define lson son[0]
#define rson son[1]
#define Key root->rson->lson
int n,m,t;
void pushdown(tree2 *tree)
{
if(tree->f)
{
tree->lson->f^=1;
tree->rson->f^=1;
swap(tree->lson,tree->rson);
tree->f=0;
}
}
void pushup(tree2 *tree)
{
tree->siz=tree->lson->siz+tree->rson->siz+1;
}
tree2 *newtree(tree2 *fa,int x)
{
tree2 *tree=&dizhi[t++];
tree->fa=fa;
tree->n=x;
return tree;
}
tree2 *bulid(tree2 *fa,int l,int r)
{
if(l>r)return Null;
int mid=(l+r)>>1;
tree2 *tree=newtree(fa,mid);
tree->lson=bulid(tree,l,mid-1);
tree->rson=bulid(tree,mid+1,r);
pushup(tree);
return tree;
}
void rotate(tree2 *tree,int x)
{
tree2 *fa=tree->fa;
pushdown(tree);
//pushdown(fa);
fa->son[x^1]=tree->son[x];
if(tree->son[x]!=Null)tree->son[x]->fa=fa;
tree->son[x]=fa;
fa->fa->son[fa->fa->son[1]==fa]=tree;
tree->fa=fa->fa;
fa->fa=tree;
pushup(fa);
}
void splay(tree2 *tree,tree2 *goal)
{
if(tree==goal)return ;
while(tree->fa!=goal)
{
tree2 *fa=tree->fa,*gfa=fa->fa;
pushdown(gfa);pushdown(fa);
if(gfa==goal)
{
rotate(tree,fa->son[0]==tree);
break;
}
int x=gfa->son[1]==fa;
if((fa->son[1]==tree)==x)
{
rotate(fa,x^1);
rotate(tree,x^1);
}
else
{
rotate(tree,x);
rotate(tree,x^1);
}
}
pushup(tree);
if(goal==null)root=tree;
}
void splayto(int k,tree2 *goal)
{
pushdown(root);
tree2 *temp=root;
int num=temp->lson->siz;
while(num!=k)
{
if(k>num)
{
temp=temp->rson;
k-=num+1;
}
else temp=temp->lson;
pushdown(temp);
num=temp->lson->siz;
}
splay(temp,goal);
}
void turn(int l,int r)
{
splayto(l-1,null);
splayto(r+1,root);
Key->f^=1;
}
void print(tree2 *tree)
{
pushdown(tree);
if(tree->lson!=Null)print(tree->lson);
if(tree->n)printf("%d ",tree->n);
if(tree->rson!=Null)print(tree->rson);
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
n=read();m=read();
Null=&dizhi[t++];
null=newtree(NULL,0);
root=newtree(null,0);
root->rson=newtree(root,0);
root->lson=root->rson->rson=Null;
Key=bulid(root->rson,1,n);
for(int i=1;i<=m;i++)
{
int l=read(),r=read();
turn(l,r);
//print(root);putchar('\n');
}
print(root);
}
##【bzoj3224】 Tyvj 1728 普通平衡树
一颗基本平衡树的各种操作,打完当板用的题
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define INF 2100000000
#define ll long long
#define clr(x) memset(x,0,sizeof(x))
#define maxclr(x) memset(x,127,sizeof(x))
using namespace std;
inline int read()
{
char c;
int ret=0,b=1;
while(!(c>='0'&&c<='9'))
{
if(c=='-')b=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
ret=(c-'0')+(ret<<1)+(ret<<3);
c=getchar();
}
return ret*b;
}
#define M 100005
struct tree2
{
tree2 *son[2],*fa;
int n,siz,num;
}*root,dizhi[M],*null,*Null;
#define lson son[0]
#define rson son[1]
int n,t;
tree2 *newtree(tree2 *fa,int x)
{
tree2 *tree=&dizhi[t++];
tree->siz=tree->num=1;
tree->lson=tree->rson=Null;
tree->n=x;
tree->fa=fa;
return tree;
}
void pushup(tree2 *tree)
{
if(tree==null)return ;
tree->siz=tree->num+tree->lson->siz+tree->rson->siz;
}
void rotate(tree2 *tree,int x)
{
tree2 *fa=tree->fa;
fa->son[x^1]=tree->son[x];
if(tree->son[x]!=Null)tree->son[x]->fa=fa;
fa->fa->son[fa->fa->son[1]==fa]=tree;
tree->fa=fa->fa;
fa->fa=tree;
tree->son[x]=fa;
pushup(fa);
}
void splay(tree2 *tree,tree2 *goal)
{
if(tree==goal)return ;
while(tree->fa!=goal)
{
tree2 *fa=tree->fa,*gfa=fa->fa;
if(gfa==goal)
{
rotate(tree,fa->son[0]==tree);
break;
}
int x=gfa->son[1]==fa;
if((fa->son[1]==tree)==x)
{
rotate(fa,x^1);
rotate(tree,x^1);
}
else
{
rotate(tree,x);
rotate(tree,x^1);
}
}
pushup(tree);
if(goal==null)root=tree;
}
void insert(int k)
{
if(root==NULL)
{
root=newtree(null,k);
return ;
}
tree2 *temp=root;
while(temp->son[temp->n<k]!=Null)
{
if(temp->n==k)break;
temp=temp->son[temp->n<k];
}
if(temp->n==k)
{
temp->siz++;
temp->num++;
splay(temp,null);
return;
}
temp->son[temp->n<k]=newtree(temp,k);
splay(temp->son[temp->n<k],null);
}
tree2 *find(int k)
{
tree2 *temp=root;
while(temp->son[temp->n<k])
{
if(temp->n==k)break;
temp=temp->son[temp->n<k];
}
return temp;
}
void dele(int k)
{
tree2 *temp=find(k);
if(temp->num!=1)
{
temp->siz--;
temp->num--;
splay(temp,null);
return ;
}
int x=temp->fa->son[1]==temp;
if(temp->lson==Null)
if(temp->rson==Null)
temp->fa->son[x]=Null;
else
{
temp->fa->son[x]=temp->rson;
temp->rson->fa=temp->fa;
}
else
if(temp->rson==Null)
{
temp->fa->son[x]=temp->lson;
temp->lson->fa=temp->fa;
}
else
{
tree2 *tree=temp->lson;
temp->fa->son[x]=temp->lson;
temp->lson->fa=temp->fa;
while(tree->rson!=Null)tree=tree->rson;
tree->rson=temp->rson;
temp->rson->fa=tree;
pushup(tree);
splay(tree,null);
return ;
}
pushup(temp->fa);
splay(temp->fa,null);
}
int query(int k)
{
tree2 *temp=root;
int num=0;
while(temp->n!=k)
{
int x=temp->n<k;
if(x==1)num+=temp->lson->siz+temp->num;
temp=temp->son[x];
}
num+=temp->lson->siz;
splay(temp,null);
return num+1;
}
int query_rank(int k)
{
tree2 *temp=root;
int num=temp->lson->siz;
while(!(k>num&&k<=num+temp->num))
{
if(k>num)
{
k-=num+temp->num;
temp=temp->rson;
}
else temp=temp->lson;
num=temp->lson->siz;
}
splay(temp,null);
return temp->n;
}
int get_pre(int k)
{
tree2 *tree=root;
int ret=0;
while(tree->lson!=Null||tree->rson!=Null)
{
if(tree->n>=k)
if(tree->lson!=Null)tree=tree->lson;
else break;
else
{
ret=max(ret,tree->n);
if(tree->rson!=Null)tree=tree->rson;
else break;
}
}
if(tree->n<k)ret=max(ret,tree->n);
splay(tree,null);
return ret;
}
int get_next(int k)
{
tree2 *tree=root;
int ret=INF;
while(tree->lson!=Null||tree->rson!=Null)
{
if(tree->n<=k)
if(tree->rson!=Null)tree=tree->rson;
else break;
else
{
ret=min(ret,tree->n);
if(tree->lson!=Null)tree=tree->lson;
else break;
}
}
if(tree->n>k)ret=min(ret,tree->n);
splay(tree,null);
return ret;
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
n=read();
Null=&dizhi[t++];null=&dizhi[t++];
for(int i=1;i<=n;i++)
{
int c=read(),x=read();
switch(c)
{
case 1:insert(x);break;
case 2:dele(x);break;
case 3:printf("%d\n",query(x));break;
case 4:printf("%d\n",query_rank(x));break;
case 5:printf("%d\n",get_pre(x));break;
case 6:printf("%d\n",get_next(x));break;
}
}
return 0;
}
##【bzoj1500】 [NOI2005]维修数列
这个题就是全部的序列维护了,vijos上有一道小白逛公园和这个有点像,就是求区间最大子序列,不过那个是线段树,没有翻转,还有一个加强版就是序列变成一棵树,变成树剖,这个就还加了一大堆操作。
然后维护的时候要维护区间和(sum),区间长度(siz),区间最大子序列(mx),从左边起区间最大子序列(lx),从右边起区间最大子序列(rx),还有翻不翻转(f)
pushup和pushdown要尤其小心
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define INF 1000000000
#define ll long long
#define clr(x) memset(x,0,sizeof(x))
#define maxclr(x) memset(x,127,sizeof(x))
using namespace std;
inline int read()
{
char c;
int ret=0,b=1;
while(!(c>='0'&&c<='9'))
{
if(c=='-')b=-1;
c=getchar();
}
while(c>='0'&&c<='9')
{
ret=(c-'0')+(ret<<1)+(ret<<3);
c=getchar();
}
return ret*b;
}
#define M 1000005
struct tree2
{
tree2 *son[2],*fa;
int n,t,f,lazy,sum,lx,rx,mx,siz;
}*root,dizhi[M],*null,*Null;
#define lson son[0]
#define rson son[1]
#define key root->rson->lson
queue<int>Q;
int m,n,t,a[M/2];
tree2 *newtree(tree2 *fa,int x)
{
tree2 *tree;
if(t<=M-5)
{
tree=&dizhi[t++];
tree->t=t-1;
}
else
{
tree=&dizhi[Q.front()];
Q.pop();
}
tree->fa=fa;
tree->n=tree->mx=tree->sum=x;
if(x>0)tree->lx=tree->rx=x;
else tree->lx=tree->rx=0;
tree->f=tree->lazy=0;
tree->siz=1;
return tree;
}
void pushup(tree2 *tree)
{
tree->siz=tree->lson->siz+tree->rson->siz+1;
tree->sum=tree->lson->sum+tree->rson->sum+tree->n;
tree->lx=max(tree->lson->lx,tree->n+tree->lson->sum+tree->rson->lx);
tree->rx=max(tree->rson->rx,tree->n+tree->rson->sum+tree->lson->rx);
tree->mx=max(tree->n+tree->lson->rx+tree->rson->lx,max(tree->lson->mx,tree->rson->mx));
}
void pushdown(tree2 *tree)
{
if(tree->lazy)
{
if(tree->lson!=Null)tree->lson->lazy=1,tree->lson->n=tree->n,tree->lson->sum=tree->lson->siz*tree->n;
if(tree->rson!=Null)tree->rson->lazy=1,tree->rson->n=tree->n,tree->rson->sum=tree->rson->siz*tree->n;
if(tree->n>0)
{
if(tree->lson!=Null)tree->lson->mx=tree->lson->lx=tree->lson->rx=tree->lson->sum;
if(tree->rson!=Null)tree->rson->mx=tree->rson->lx=tree->rson->rx=tree->rson->sum;
}
else
{
if(tree->lson!=Null)tree->lson->lx=tree->lson->rx=0,tree->lson->mx=tree->n;
if(tree->rson!=Null)tree->rson->lx=tree->rson->rx=0,tree->rson->mx=tree->n;
}
tree->lazy=tree->f=0;
}
if(tree->f)
{
tree->lson->f^=1;
tree->rson->f^=1;
swap(tree->lson->lson,tree->lson->rson);
swap(tree->rson->lson,tree->rson->rson);
swap(tree->lson->lx,tree->lson->rx);
swap(tree->rson->lx,tree->rson->rx);
tree->f=0;
//pushup(tree);
}
}
tree2 *bulid(tree2 *fa,int l,int r)
{
if(l>r)return Null;
int mid=(l+r)>>1;
tree2 *tree=newtree(fa,a[mid]);
tree->lson=bulid(tree,l,mid-1);
tree->rson=bulid(tree,mid+1,r);
pushup(tree);
return tree;
}
void rotate(tree2 *tree,int x)
{
tree2 *fa=tree->fa;
fa->son[x^1]=tree->son[x];
if(tree->son[x]!=Null)tree->son[x]->fa=fa;
tree->fa=fa->fa;
fa->fa->son[fa->fa->son[1]==fa]=tree;
tree->son[x]=fa;
fa->fa=tree;
pushup(fa);
}
void splay(tree2 *tree,tree2 *goal)
{
if(tree==goal)return ;
while(tree->fa!=goal)
{
tree2 *fa=tree->fa,*gfa=fa->fa;
//pushdown(gfa);
//pushdown(fa);
if(gfa==goal)
{
rotate(tree,fa->son[0]==tree);
break;
}
int x=gfa->son[1]==fa;
if((fa->son[1]==tree)==x)
{
rotate(fa,x^1);
rotate(tree,x^1);
}
else
{
rotate(tree,x);
rotate(tree,x^1);
}
}
pushup(tree);
if(goal==null)root=tree;
}
void splayto(int k,tree2 *goal)
{
tree2 *tree=root;
pushdown(tree);
int num=tree->lson->siz;
while(num!=k)
{
if(num<k)
{
tree=tree->rson;
k-=num+1;
}
else tree=tree->lson;
pushdown(tree);
num=tree->lson->siz;
}
splay(tree,goal);
}
void insert(int pos,int num)
{
if(num==0)return ;
n=num;
for(int i=1;i<=n;i++)
a[i]=read();
splayto(pos,null);
splayto(pos+1,root);
key=bulid(root->rson,1,n);
pushup(root->rson);
pushup(root);
}
void rec(tree2 *tree)
{
Q.push(tree->t);
if(tree->lson!=Null)rec(tree->lson);
if(tree->rson!=Null)rec(tree->rson);
}
void delet(int pos,int num)
{
if(num==0)return ;
splayto(pos-1,null);
splayto(pos+num,root);
rec(key);
key=Null;
pushup(root->rson);
pushup(root);
}
void change(int pos,int num)
{
int x=read();
if(num==0)return ;
splayto(pos-1,null);
splayto(pos+num,root);
key->lazy=1;key->n=x;
key->sum=key->siz*x;
if(x<0)key->mx=x,key->lx=key->rx=0;
else key->mx=key->lx=key->rx=key->sum;
pushup(root->rson);
pushup(root);
}
void reverse(int pos,int num)
{
if(num==0)return ;
splayto(pos-1,null);
splayto(pos+num,root);
if(!key->lazy)
{
key->f^=1;
swap(key->lson,key->rson);
swap(key->lx,key->rx);
// pushdown(key);
// pushup(key);
pushup(root->rson);
pushup(root);
}
}
int sum(int pos,int num)
{
if(num==0)return 0;
splayto(pos-1,null);
splayto(pos+num,root);
return key->sum;
}
int mxsum()
{
return root->mx;
}
void print(tree2 *tree)
{
pushdown(tree);
if(tree->lson!=Null)print(tree->lson);
printf("%d ",tree->n);
if(tree->rson!=Null)print(tree->rson);
}
int main()
{
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
n=read();m=read();
for(int i=1;i<=n;i++)
a[i]=read();
Null=&dizhi[t++];
Null->mx=-INF;
null=newtree(NULL,-INF);
root=newtree(null,-INF);
root->rson=newtree(root,-INF);
root->lson=root->rson->rson=Null;
key=bulid(root->rson,1,n);
pushup(root->rson);
pushup(root);
for(int i=1;i<=m;i++)
{
//print(root);printf("\n");
char s[10];
scanf("%s",s);
int a,b;
if(s[2]!='X')
{
a=read();b=read();
}
switch(s[2])
{
case 'S':insert(a,b);break;
case 'L':delet(a,b);break;
case 'K':change(a,b);break;
case 'V':reverse(a,b);break;
case 'T':printf("%d\n",sum(a,b));break;
case 'X':printf("%d\n",mxsum());break;
}
}
return 0;
}
#易错内容
1、题目读入大多带负数,读入优化要加负数
2、严格注意哨兵结点的修改是否会影响树
3、splay最后还要pushup(tree)
4、建树不可以把儿子指针直接放到参数上面!!!
5、再向下找第k个结点的时候num要在循环末附上新的值,并且要记得pushdown
6、序列操作完了以后要pushup,平衡树所有操作完了一定要splay,不然可能T
7、如果有多种有标记的操作,应该在给结点打标记的时候修改它,而不是pushdown的时候,pushdown应该去修改它的儿子
8、求最值哨兵结点不要直接赋0
9、修改关系要2部操作,修改son和fa指针
#总结
splay是一个很强大写起来也比较舒服的数据结构,并且内存要求也不高,相对于其他平衡树和线段树都有比较大的优势
#参考资料
《伸展树的基本操作与应用》:我实际上就用了里面三张图