暑假学习日记:Splay树
从昨天开始我就想学这个伸展树了,今天花了一个上午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树的更多相关文章
- Splay树再学习
队友最近可能在学Splay,然后让我敲下HDU1754的题,其实是很裸的一个线段树,不过用下Splay也无妨,他说他双旋超时,单旋过了,所以我就敲来看下.但是之前写的那个Splay越发的觉得不能看,所 ...
- 文艺平衡Splay树学习笔记(2)
本blog会讲一些简单的Splay的应用,包括但不局限于 1. Splay 维护数组下标,支持区间reserve操作,解决区间问题 2. Splay 的启发式合并(按元素多少合并) 3. 线段树+Sp ...
- Splay树学习
首先给出一论文讲的很好: http://www.docin.com/p-63165342.html http://www.docin.com/p-62465596.html 然后给出模板胡浩大神的模板 ...
- 省选算法学习-数据结构-splay
于是乎,在丧心病狂的noip2017结束之后,我们很快就要迎来更加丧心病狂的省选了-_-|| 所以从写完上一篇博客开始到现在我一直深陷数据结构和网络流的漩涡不能自拔 今天终于想起来写博客(只是懒吧.. ...
- Linux学习日记-使用EF6 Code First(四)
一.在linux上使用EF 开发环境 VS2013+mono 3.10.0 +EF 6.1.0 先检测一下EF是不是6的 如果不是 请参阅 Linux学习日记-EF6的安装升级(三) 由于我的数据库 ...
- Splay树-Codevs 1296 营业额统计
Codevs 1296 营业额统计 题目描述 Description Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司 ...
- ZOJ3765 Lights Splay树
非常裸的一棵Splay树,需要询问的是区间gcd,但是区间上每个数分成了两种状态,做的时候分别存在val[2]的数组里就好.区间gcd的时候基本上不支持区间的操作了吧..不然你一个区间里加一个数gcd ...
- 1439. Battle with You-Know-Who(splay树)
1439 路漫漫其修远兮~ 手抄一枚splay树 长长的模版.. 关于spaly树的讲解 网上很多随手贴一篇 貌似这题可以用什么bst啦 堆啦 平衡树啦 等等 这些本质都是有共同点的 查找.删除特 ...
- android学习日记05--Activity间的跳转Intent实现
Activity间的跳转 Android中的Activity就是Android应用与用户的接口,所以了解Activity间的跳转还是必要的.在 Android 中,不同的 Activity 实例可能运 ...
随机推荐
- 判断Featureclass的类型
一个Featureclass可以是Shapefile Feature Class.Personal Geodatabase Feature Class.File Geodatabase Feature ...
- Request.IsLocal与Request.Url.IsLoopback的区别
均在服务器上访问时: http://localhost:17810 Request.IsLocal => trueRequest.Url.IsLoopback => true http:/ ...
- JS对select动态添加options操作[IE&FireFox兼容]
<select id="ddlResourceType" onchange="getvalue(this)"> </select> 动态 ...
- 把url后面的.html去掉
$url = "http://haichuang1997.bmlink.com/supply/dc_355472.html";echo rtrim($url, '.html');
- 【Qt】Qt之进程间通信(IPC)【转】
简述 进程间通信,就是在不同进程之间传播或交换信息.那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区.但是,系统空间却是“ ...
- as 和is的区别
is 1,检查对象的兼容性,并返回true或false 2,不会抛出异常 3,null永远返回false as 1,检查对象的兼容性,并返回 true或false 2,不会抛出异常 3,null将抛出 ...
- WPF学习笔记 控件篇 属性整理【1】FrameworkElement
最近在做WPF方面的内容,由于好多属性不太了解,经常想当然的设置,经常出现自己未意料的问题,所以感觉得梳理下. ps:先补下常用控件的类结构,免得乱了 .NET Framework 4.5 Using ...
- XAML 概述四
这一节我们来简单介绍一下XAML的加载和编译,它包括如下三种方式: · 只使用代码 · 使用代码和未编译的XAML · 使用代码和编译过的BAML 一. 只使用代码 我们首先创建一个简单的控制台 ...
- 拥抱ARM妹纸第二季 之 第三次 给我变个月亮,让约会更浪漫!
嗯嗯,效果不错.趁着这个热乎劲,接到俺的LED测试板上试试.呃~~~ 竟然和小LED的效果不一样啊,不一样.不但闪烁而且完全没有调光效果.郁闷内,查查原因呗.看看那里出问题.迅速在PT4115手册里翻 ...
- linux文件目录下各文件简介
/bin:存放最常用命令: /boot:启动Linux的核心文件: /dev:设备文件: /etc:存放各种配置文件: /home:用户主目录: /lib:系统最基本的动态链接共享库: /mnt:一般 ...