从昨天开始我就想学这个伸展树了,今天花了一个上午2个多小时加下午2个多小时,学习了一下伸展树(Splay树),学习的时候主要是看别人博客啦~发现下面这个博客挺不错的http://zakir.is-programmer.com/posts/21871.html.在里面有连接到《运用伸展树解决数列维护问题》的文章,里面对伸展树的旋转操作讲得很仔细,而且也讲清楚了伸展树是怎么样维护一个数列的,一开始我是小白,觉得树和数列根本没什么关系,但看了之后就会明白,实际上树上的结点是维护该结点的值的,而这个值是原来数列里的哪一项呢?如果该结点对应的中序遍历数k,那么就是对应原数列a中的a[k]这一项.理解了这个之后我就豁然开朗了,要提取一个区间[a,b],实际上只需要将a-1,Splay为根,b+1Splay到根下的右儿子,则根下的右儿子的左儿子就是[a,b]这个区间,这是由平衡树,左小右大的性质决定的.

所以无论做什么,首先是将该区间提取出来,然后对对应结点做就好了.问题是有时a-1不存在,b+1也不存在,所以一开始人为的做两个头尾的结点.而且很多时候为了避免对NULL的特殊处理,我们会构造一个实的null,让它的sz=0;sum=0;这样就不会影响一些情况的处理

伸展树的特性是可以反转,注意到,一棵树,如果我们将它的每个结点的左右儿子都互换一次,它的中序遍历就刚好是原来的中序遍历倒过来,利用这个性质可以实现序列反转.而且还可以添加,假如要添加一个串{b1,b2,b3,b4..}在ak之后的位置,首先调出[ak,ak+1]这个区间,然后将{b1,b2...}建一棵伸展树,然后将结点粘在root->ch[1]的左儿子上即可.删除则是同理.我我还可以提取一个区间出来,反转,再加到我想加的地方.操作都是类似的.

伸展树的优势除了它支持上面的操作外,它还兼容线段树的add,set操作,同样也是每个结点存lazy标记就可以了,然后写一个类似线段树的pushDown,pushUp,维护好区间的信息就可以了~

下面给出的代码很大程度上(90%)是从上面网站的代码上copy下来的,将它改成自己的习惯的变量名,然后自己多写了一个add标记,原来的代码还能求最大子段和,但加了add之后再求就有点麻烦了,所以就删掉了原本维护最大子段和的代码,自己写了个驱动程序,调了一下感觉还行.

代码的参数设置可能会不同,像add()函数传的是从哪个位置(pos),加多少个(tot),大可直接写成l,r,传参数的姿势不同罢了,但注意的是,当要在l位置开始加的时候,传进去的是l+1,是因为前面的头指针占了一位,看到输出之后就大概明白为什么要加1了.对了,因为区间的标记的lazy的,所以直接中序遍历得不出实际的序列(因为有些标记没往下传),所以写了个maintain()先把所有标记下传,实际上是不需要的,随用随查就好了,这么写是为了方便debug~

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#define INF 0x3fffffff
#define maxn 500000
using namespace std; struct Node
{
Node *pre,*ch[];
bool rev,cov; // 结点翻转标记与cover标记
int add; // 结点add标记(表示加了多少)
int sz,val,sum; // 结点的size,保存的值,以及以该结点为子树的和
}*root,N[maxn],*null; // 定义了根的指针,人手写的空的指针,以及结点数组N
Node *stack[maxn]; // 用一个栈来回收用过的指针,这是学到的新姿势,这样的话在构造新的结点的时候可以不用一直idx++
int top,idx; // 栈顶指针,以及数组idx指针
int a[maxn+]; // 用来构造伸展树的数组 Node *addNode(int val) // 产生新结点
{
Node *p;
if(top) p=stack[--top]; // 首先从回收栈里取
else p=&N[idx++]; // 没有的话从N里面取
//初始化
p->rev=p->cov=false;
p->sz=;
p->sum=p->val=val;
p->ch[]=p->ch[]=p->pre=null;
return p;
} void Recycle(Node *p) // 递归回收删除掉的指针,这是用来节省空间的
{
if(p->ch[]!=null) Recycle(p->ch[]);
if(p->ch[]!=null) Recycle(p->ch[]);
stack[++top]=p;
} void pushDown(Node *p) // 核心函数,用来处理标记的
{
if(p==null||!p) return; // 遇到空指针返回
if(p->rev) // 先处理反转标记
{
swap(p->ch[],p->ch[]); // 交换子树
if(p->ch[]!=null) p->ch[]->rev^=; // 标记下传
if(p->ch[]!=null) p->ch[]->rev^=; // 标记下传
p->rev=false; // 标记取消
}
if(p->cov) //下面的cov和add标记的更新与下传与线段树相同
{
if(p->ch[]!=null){
p->ch[]->val=p->val;
p->ch[]->sum=p->val*p->ch[]->sz;
p->ch[]->cov=true;
p->ch[]->add=;
}
if(p->ch[]!=null){
p->ch[]->val=p->val;
p->ch[]->sum=p->val*p->ch[]->sz;
p->ch[]->cov=true;
p->ch[]->add=;
}
p->cov=false;
}
if(p->add)
{
if(p->ch[]!=null){
p->ch[]->val+=p->add;
p->ch[]->sum+=p->ch[]->sz*p->add;
p->ch[]->add+=p->add;
}
if(p->ch[]!=null){
p->ch[]->val+=p->add;
p->ch[]->sum+=p->ch[]->sz*p->add;
p->ch[]->add+=p->add;
}
p->add=;
}
} void pushUp(Node *p) // 核心函数,维护信息
{
if(p==null) return;
pushDown(p);
pushDown(p->ch[]);
pushDown(p->ch[]);
p->sz=p->ch[]->sz+p->ch[]->sz+;
p->sum=p->val+p->ch[]->sum+p->ch[]->sum;
} void rotate(Node *x,int c) // Splay树的旋转函数,标准姿势
{
Node *y=x->pre;
pushDown(y);pushDown(x);
y->ch[c^]=x->ch[c];
if(x->ch[c]!=null)
x->ch[c]->pre=y;
x->pre=y->pre;
if(y->pre!=null)
if(y->pre->ch[]==y)
y->pre->ch[]=x;
else
y->pre->ch[]=x;
x->ch[c]=y;y->pre=x;
if(y==root) root=x;
pushUp(y);
} void Splay(Node *x,Node *f) // 将x结点转到f下
{
pushDown(x);
while(x->pre!=f)
{
Node *y=x->pre,*z=y->pre;
if(x->pre->pre==f)
rotate(x,x->pre->ch[]==x);
else
{
if(z->ch[]==y){
if(y->ch[]==x) {rotate(y,);rotate(x,);}
else {rotate(x,);rotate(x,);}
}
else{
if(y->ch[]==x) {rotate(y,),rotate(x,);}
else {rotate(x,),rotate(x,);}
}
}
}
pushUp(x);
} Node *select(int kth) // 选出第k个点,返回对应结点
{
int tmp;
Node *t=root;
while(){
pushDown(t);
tmp=t->ch[]->sz;
if(tmp+==kth) break;
if(kth<=tmp) {t=t->ch[];}
else { kth-=tmp+;t=t->ch[];}
}
return t;
} Node *build(int L,int R) // 建树,有点像线段树
{
if(L>R) return null;
int M=(L+R)>>;
Node *p=addNode(a[M]);
p->ch[]=build(L,M-);
if(p->ch[]!=null){
p->ch[]->pre=p;
}
p->ch[]=build(M+,R);
if(p->ch[]!=null){
p->ch[]->pre=p;
}
pushUp(p);
} void remove(int pos,int tot) // 从pos位置开始,删除tot个(包括pos)
{
Splay(select(pos-),null);
Splay(select(pos+tot),root);
if(root->ch[]->ch[]!=null){
Recycle(root->ch[]->ch[]);
root->ch[]->ch[]=null;
}
pushUp(root->ch[]);pushUp(root);
Splay(root->ch[],null);
} void insert(int pos,int tot) // 添加,插的是一个数组的时候,要在数组a里面建一颗树,即a[1~N]是要插的数
{
Node *troot=build(,tot);
Splay(select(pos),null);
Splay(select(pos+),root);
root->ch[]->ch[]=troot;
troot->pre=root->ch[];
pushUp(root->ch[]);pushUp(root);
Splay(troot,null);
} void reverse(int pos,int tot) // 从pos开始翻转tot个
{
Splay(select(pos-),null);
Splay(select(pos+tot),root);
if(root->ch[]->ch[]!=null)
{
root->ch[]->ch[]->rev^=;
Splay(root->ch[]->ch[],null);
}
} void set(int pos,int tot,int c) // 从pos开始将tot个设置为c
{
Splay(select(pos-),null);
Splay(select(pos+tot),root);
root->ch[]->ch[]->val=c;
root->ch[]->ch[]->sum=root->ch[]->ch[]->sz*c;
root->ch[]->ch[]->cov=true;
Splay(root->ch[]->ch[],null);
} void add(int pos,int tot,int c) // 从pos开始将tot个加c
{
Splay(select(pos-),null);
Splay(select(pos+tot),root);
root->ch[]->ch[]->val+=c;
root->ch[]->ch[]->sum+=c*root->ch[]->ch[]->sz;
root->ch[]->ch[]->add+=c;
Splay(root->ch[]->ch[],null);
} int query(int pos,int tot) // 求pos开始tot个的和
{
Splay(select(pos-),null);
Splay(select(pos+tot),root);
return root->ch[]->ch[]->sum;
} void init() // 初始化函数
{
idx=top=; // idx,top归零
null=addNode(-INF); // 初始化空指针
null->sz=null->sum=; // 记住sz和sum一定要设为0
root=addNode(-INF); // 初始化根指针
root->sum=;
Node *p;
p=addNode(-INF); // 初始化"树尾"的指针
root->ch[]=p;
p->pre=root;
p->sum=;
pushUp(root->ch[]);
pushUp(root);
}
//下面三个函数是调试的时候用的
void maintain(Node *p) // 因为标记是lazy的,所以先将所有标记都下传好
{
pushDown(p);
if(p->ch[]!=null) maintain(p->ch[]);
if(p->ch[]!=null) maintain(p->ch[]);
}
void dfs(Node *x) // 中序遍历
{
if(x==null) return;
dfs(x->ch[]);
printf("%d ",x->val);
dfs(x->ch[]);
}
void print() // 打印
{
maintain(root);
dfs(root);
puts("");
} int main()
{
int n,m;
while(cin>>n)
{
for(int i=;i<=n;i++){
scanf("%d",&a[i]);
}
init();
Node *troot=build(,n); // 从a数组建一颗Splay树
root->ch[]->ch[]=troot; // 让它和init()里的root,p连上
troot->pre=root->ch[];
pushUp(root->ch[]); // 维护相关信息
pushUp(root->ch[]);
cin>>m;
int o,l,r,v;
//支持六种操作,区间add,区间set,区间反转,区间删除,区间添加,区间和
while(m--)
{
scanf("%d",&o);
if(o==){
scanf("%d%d%d",&l,&r,&v);add(l+,r-l+,v);print();
}
else if(o==){
scanf("%d%d%d",&l,&r,&v);set(l+,r-l+,v);print();
}
else if(o==){
scanf("%d%d",&l,&r);reverse(l+,r-l+);print();
}
else if(o==){
scanf("%d%d",&l,&r);remove(l+,r-l+);print();
}
else if(o==){
scanf("%d%d",&l,&v);
for(int i=;i<=v;i++){ scanf("%d",&a[i]);}
insert(l+,v);
print();
}
else if(o==){
scanf("%d%d",&l,&r);
cout<<query(l+,r-l+)<<endl;
}
}
}
return ;
}

暑假学习日记:Splay树的更多相关文章

  1. Splay树再学习

    队友最近可能在学Splay,然后让我敲下HDU1754的题,其实是很裸的一个线段树,不过用下Splay也无妨,他说他双旋超时,单旋过了,所以我就敲来看下.但是之前写的那个Splay越发的觉得不能看,所 ...

  2. 文艺平衡Splay树学习笔记(2)

    本blog会讲一些简单的Splay的应用,包括但不局限于 1. Splay 维护数组下标,支持区间reserve操作,解决区间问题 2. Splay 的启发式合并(按元素多少合并) 3. 线段树+Sp ...

  3. Splay树学习

    首先给出一论文讲的很好: http://www.docin.com/p-63165342.html http://www.docin.com/p-62465596.html 然后给出模板胡浩大神的模板 ...

  4. 省选算法学习-数据结构-splay

    于是乎,在丧心病狂的noip2017结束之后,我们很快就要迎来更加丧心病狂的省选了-_-|| 所以从写完上一篇博客开始到现在我一直深陷数据结构和网络流的漩涡不能自拔 今天终于想起来写博客(只是懒吧.. ...

  5. Linux学习日记-使用EF6 Code First(四)

    一.在linux上使用EF 开发环境 VS2013+mono 3.10.0 +EF 6.1.0 先检测一下EF是不是6的 如果不是  请参阅 Linux学习日记-EF6的安装升级(三) 由于我的数据库 ...

  6. Splay树-Codevs 1296 营业额统计

    Codevs 1296 营业额统计 题目描述 Description Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司 ...

  7. ZOJ3765 Lights Splay树

    非常裸的一棵Splay树,需要询问的是区间gcd,但是区间上每个数分成了两种状态,做的时候分别存在val[2]的数组里就好.区间gcd的时候基本上不支持区间的操作了吧..不然你一个区间里加一个数gcd ...

  8. 1439. Battle with You-Know-Who(splay树)

    1439 路漫漫其修远兮~ 手抄一枚splay树 长长的模版.. 关于spaly树的讲解   网上很多随手贴一篇 貌似这题可以用什么bst啦 堆啦 平衡树啦 等等 这些本质都是有共同点的 查找.删除特 ...

  9. android学习日记05--Activity间的跳转Intent实现

    Activity间的跳转 Android中的Activity就是Android应用与用户的接口,所以了解Activity间的跳转还是必要的.在 Android 中,不同的 Activity 实例可能运 ...

随机推荐

  1. Eclipse中tomcat之后,tomcat的相关配置会被Eclipse重置

    之前用MyEclipse,在tomcat的conf中修改了配置文件,启动就OK了. 现在改用Eclipse,发现改了,之后发现没有用,Eclipse重启tomcat之后,配置文件就被重置了. 众里寻他 ...

  2. hdu 2689 Sort it

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2689 题目分析:求至少交换多少次可排好序,可转换为逆序对问题. 用冒泡排序较为简单,复杂度较大~~ 也 ...

  3. 存储过程及Comm.cs类的创建

    2013-09-25 13:08:59 一.准备工作 首先创建一个数据库,如创建“试用期公务员管理”数据库:再创建一个Comm.cs类,添加代码如下: using System;using Syste ...

  4. 9.配置postfix空客户端

    将本地邮件服务器配置充当为控客户端,已将所有邮件都转发到中央服务器以进行发送 1.postconf -e "relayhost=[mail.example.com]" 邮件被路由到 ...

  5. 添加远程链接MySQL的权限

    mysql> grant 权限1,权限2,…权限n on 数据库名称.表名称 to 用户名@用户地址 identified by ‘连接口令’; 权限1,权限2,…权限n代表select,ins ...

  6. linux系统目录架构

    /bin目录:可执行的二进制文件,shell命令(就是我们说的命令:cp ls ...),所有用户都有权执行. /boot目录:引导目录,整个操作系统启动所需的所有文件都在该目录下,其中最主要的就是v ...

  7. 转换 Html 内容为纯文本内容(html,文本互转)

    转自http://www.cnblogs.com/jyshi/archive/2011/08/09/2132762.html : /// <summary> /// 转换纯文本内容为 HT ...

  8. Redis源码研究--redis.h

    ------------7月3日------------ /* The redisOp structure defines a Redis Operation, that is an instance ...

  9. 如何查看 Apache 的版本

    查看 Apache 服务器版本的命令行为: httpd -v 或者 apachectl -v 例如:用 Xshell 连接到服务器后,输入:httpd -v 或者:apachectl -v 返回: S ...

  10. php对数组排序代码

    php对数组排序,介绍了和php,有关的知识.技巧.经验,和一些php源码等. 对数组排序 usort() 函数使用用户自定义的函数对数组排序. */ function cmp($a, $b) //用 ...