SPLAY,LCT学习笔记(一)
写了两周数据结构,感觉要死掉了,赶紧总结一下,要不都没学明白。
SPLAY专题:
例:NOI2005 维修数列
典型的SPLAY问题,而且综合了SPLAY常见的所有操作,特别适合新手入门学习(比如我这种蒟蒻)
题目要求很多,我们一步一步来分析
首先,区间翻转是SPLAY一个很基础的操作,我们以他为基础分析这个SPLAY
例:luogu文艺平衡树
题目:读入一个序列,进行多次区间翻转操作,请你输出操作后的序列
我们怎么处理呢?
首先要明确一点,就是SPLAY是可以当做区间树来使用的(就像线段树一样)
所以,我们首先把原来读进来的序列建起一个SPLAY,建树方法类似于线段树
void buildtree(int l,int r,int f)
{
int mid=(l+r)>>1;
if(l==r)
{
tree[l].val=a[l];
tree[l].huge=1;
tree[l].ttag=0;
tree[l].fa=f;
tree[l].lson=tree[l].rson=0;
}
if(l<mid)
{
buildtree(l,mid-1,mid);
}
if(r>mid)
{
buildtree(mid+1,r,mid);
}
tree[mid].val=a[mid];
tree[mid].fa=f;
tree[mid].ttag=0;
update(mid);
if(mid<f)
{
tree[f].lson=mid;
}else
{
tree[f].rson=mid;
}
}
注意到其中有一个ttag,这是翻转区间的标记,暂时先不用关心
这样我们就建起了一个SPLAY
然后我们把SPLAY的根置成整个区间的中点即可
还有一个要点,就是SPLAY中一个节点既是序列中的一个位置,也是树中一个根节点,换言之,如果你建立了一棵SPLAY,根节点对应的是原序列中4位置,那么他的左子树就要维护1-3位置,右子树要维护5-7位置,而4位置交给根节点自己,不能放进子树中
这也是他与线段树少数的区别。
所以如果我们有一个序列1,2,3,4,5,6,7
建起的SPLAY大概会长这个样子
接下来,我们来尝试做一下区间翻转:
假设我要翻转区间[2,4],我要怎么办呢?
现在我希望找到一棵子树,使得这棵子树正好对应了[2,4]这个区间,然后我们把这个区间整体打上标记就可以了
怎么找?
这才是SPLAY真正要解决的问题
最后我们得出了一个结论:假设我们要翻转区间[2,4],那么我们首先把1转到SPLAY的根上(别忘了,这是一棵伸展树,是可以旋转的)
那么旋转规则和treap类似,最后大概长这样吧:
至于怎么转的,暂时还不必关心,一会就提到
接下来,我们把5节点旋转到根节点的右儿子处,长这样:
(原谅本蒟蒻的画图技术)
发现什么了吗?
当5旋转到右子节点以后,你所需要的区间[2,4]自然出现在了5的左子树上!
这样我们只对这棵树打一个标记就好
总结:如果我想修改区间[l,r],那么我只需将节点l-1转至SPLAY的根节点上,将节点r+1转至根节点的右儿子上,那么这个右儿子的左子树就是你想要的区间[l,r]!
至于证明,由于SPLAY本质是一个二叉搜索树,所以要满足搜索树的性质(这也是所有基于二叉搜索树变形出的数据结构所有旋转,伸展等等诡异操作的原理)
而搜索树的性质:左子树<根<右子树
那就好办啦,把要修改的区间前驱放到根上,这样整个区间一定在根的右子树内
再把区间的后继放到根的右子树上,这样整个区间一定在这个节点的左子树内
这样不就分出来这段区间了吗。
等等,这里面是不有点问题?
是啊,如果l=1呢?
这样的话,l-1就为0了,而在SPLAY里,找的到这样一个点吗?
似乎找不到诶
或者,如果r=n的话,那r+1就等于n+1了,好像同样不存在诶
那我们怎么玩?
一句话:插入两个空节点!
为什么呢?
假如我们将整个区间由[1,n]变为[2,n+1],然后加入1和n+2两个空节点,这样不就可以实现上面所说的所有要求了吗?
这就很妙啦
于是,我们来解决一下最后这个问题:怎么旋转?
结论如下(这里使用双旋splay,常数小一些)
void rotate(int st,int &ed)
{
int ltyp;
int v1=tree[st].fa;
int v2=tree[v1].fa;
if(tree[v1].rson==st)
{
ltyp=1;
}else
{
ltyp=0;
}
if(v1==ed)
{
ed=st;
}else
{
if(tree[v2].lson==v1)
{
tree[v2].lson=st;
}else
{
tree[v2].rson=st;
}
}
if(ltyp)
{
tree[tree[st].lson].fa=v1;
tree[v1].fa=st;
tree[v1].rson=tree[st].lson;
tree[st].lson=v1;
tree[st].fa=v2;
}else
{
tree[tree[st].rson].fa=v1;
tree[v1].fa=st;
tree[v1].lson=tree[st].rson;
tree[st].rson=v1;
tree[st].fa=v2;
}
update(v1);
update(st);
}
void splay(int st,int &ed)
{
while(st!=ed)
{
int v1=tree[st].fa,v2=tree[v1].fa;
if(v1!=ed)
{
if((tree[v1].lson==st&&tree[v2].lson!=v1)||(tree[v1].rson==st&&tree[v2].rson!=v1))
{
rotate(st,ed);
}else
{
rotate(v1,ed);
}
}
rotate(st,ed);
}
}
双旋,按类似treap的方法反复旋转即可
最后,每次操作的时候都需要下传标记,然后想输出的话对这棵树进行中序遍历即可
全代码(文艺平衡树):
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ls tree[rt].lson
#define rs tree[rt].rson
using namespace std;
struct SPLAY
{
int lson;
int rson;
int val;
int fa;
bool ttag;
int huge;
}tree[100005];
int a[100005];
int rot;
int n,m;
void update(int rt)
{
tree[rt].huge=tree[ls].huge+tree[rs].huge+1;
}
void pushdown(int rt)
{
if(tree[rt].ttag)
{
tree[rt].ttag=0;
tree[ls].ttag^=1;
tree[rs].ttag^=1;
swap(tree[ls].lson,tree[ls].rson);
swap(tree[rs].lson,tree[rs].rson);
}
}
void buildtree(int l,int r,int f)
{
int mid=(l+r)>>1;
if(l==r)
{
tree[l].val=a[l];
tree[l].huge=1;
tree[l].ttag=0;
tree[l].fa=f;
tree[l].lson=tree[l].rson=0;
}
if(l<mid)
{
buildtree(l,mid-1,mid);
}
if(r>mid)
{
buildtree(mid+1,r,mid);
}
tree[mid].val=a[mid];
tree[mid].fa=f;
tree[mid].ttag=0;
update(mid);
if(mid<f)
{
tree[f].lson=mid;
}else
{
tree[f].rson=mid;
}
}
int findf(int rt,int v)
{
pushdown(rt);
if(tree[ls].huge+1==v)
{
return rt;
}else if(tree[ls].huge>=v)
{
return findf(ls,v);
}else
{
return findf(rs,v-1-tree[ls].huge);
}
}
void rotate(int st,int &ed)
{
int ltyp;
int v1=tree[st].fa;
int v2=tree[v1].fa;
if(tree[v1].rson==st)
{
ltyp=1;
}else
{
ltyp=0;
}
if(v1==ed)
{
ed=st;
}else
{
if(tree[v2].lson==v1)
{
tree[v2].lson=st;
}else
{
tree[v2].rson=st;
}
}
if(ltyp)
{
tree[tree[st].lson].fa=v1;
tree[v1].fa=st;
tree[v1].rson=tree[st].lson;
tree[st].lson=v1;
tree[st].fa=v2;
}else
{
tree[tree[st].rson].fa=v1;
tree[v1].fa=st;
tree[v1].lson=tree[st].rson;
tree[st].rson=v1;
tree[st].fa=v2;
}
update(v1);
update(st);
}
void splay(int st,int &ed)
{
while(st!=ed)
{
int v1=tree[st].fa,v2=tree[v1].fa;
if(v1!=ed)
{
if((tree[v1].lson==st&&tree[v2].lson!=v1)||(tree[v1].rson==st&&tree[v2].rson!=v1))
{
rotate(st,ed);
}else
{
rotate(v1,ed);
}
}
rotate(st,ed);
}
}
int split(int st,int ed)
{
int v1=findf(rot,st-1);
int v2=findf(rot,ed+1);
splay(v1,rot);
splay(v2,tree[v1].rson);
return tree[v2].lson;
}
void reverse(int st,int ed)
{
int v1=split(st,ed);
tree[v1].ttag^=1;
swap(tree[v1].lson,tree[v1].rson);
update(tree[v1].fa);
update(tree[tree[v1].fa].fa);
}
void print(int rt)
{
if(!rt)
{
return;
}
pushdown(rt);
print(ls);
printf("%d ",tree[rt].val);
print(rs);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
a[i+1]=i;
}
buildtree(1,n+2,0);
rot=(n+3)>>1;
for(int i=1;i<=m;i++)
{
int st,ed;
scanf("%d%d",&st,&ed);
reverse(st+1,ed+1);
}
int v1=split(2,n+1);
print(v1);
return 0;
}
这样SPLAY的基础知识就完成了
剩下的留待下一篇更新
SPLAY,LCT学习笔记(一)的更多相关文章
- LCT 学习笔记
LCT学习笔记 前言 自己定的学习计划看起来完不成了(两天没学东西,全在补题),决定赶快学点东西 于是就学LCT了 简介 Link/Cut Tree是一种数据结构,我们用它解决动态树问题 但是LCT不 ...
- BST,Splay平衡树学习笔记
BST,Splay平衡树学习笔记 1.二叉查找树BST BST是一种二叉树形结构,其特点就在于:每一个非叶子结点的值都大于他的左子树中的任意一个值,并都小于他的右子树中的任意一个值. 2.BST的用处 ...
- SPLAY,LCT学习笔记(六)
这应该暂时是个终结篇了... 最后在这里讨论LCT的一个常用操作:维护虚子树信息 这也是一个常用操作 下面我们看一下如何来维护 以下内容转自https://blog.csdn.net/neither_ ...
- SPLAY,LCT学习笔记(五)
这一篇重点探讨LCT的应用 例:bzoj 2631 tree2(国家集训队) LCT模板操作之一,利用SPLAY可以进行区间操作这一性质对维护懒惰标记,注意标记下传顺序和如何下传 #include & ...
- SPLAY,LCT学习笔记(四)
前三篇好像变成了SPLAY专题... 这一篇正式开始LCT! 其实LCT就是基于SPLAY的伸展操作维护树(森林)连通性的一个数据结构 核心操作有很多,我们以一道题为例: 例:bzoj 2049 洞穴 ...
- SPLAY,LCT学习笔记(三)
前两篇讲述了SPLAY模板操作,这一篇稍微介绍一下SPLAY的实际应用 (其实只有一道题,因为本蒟蒻就写了这一个) 例:bzoj 1014火星人prefix 由于本蒟蒻不会后缀数组,所以题目中给的提示 ...
- SPLAY,LCT学习笔记(二)
能够看到,上一篇的代码中有一段叫做find我没有提到,感觉起来也没有什么用,那么他的存在意义是什么呢? 接下来我们来填一下这个坑 回到我们的主题:NOI 2005维修数列 我们刚刚讨论了区间翻转的操作 ...
- LCT学习笔记
最近自学了一下LCT(Link-Cut-Tree),参考了Saramanda及Yang_Zhe等众多大神的论文博客,对LCT有了一个初步的认识,LCT是一种动态树,可以处理动态问题的算法.对于树分治中 ...
- [总结] LCT学习笔记
\(emmm\)学\(lct\)有几天了,大概整理一下这东西的题单吧 (部分参考flashhu的博客) 基础操作 [洛谷P1501Tree II] 题意 给定一棵树,要求支持 链加,删边加边,链乘,询 ...
随机推荐
- elasticsearch 动态模板设置
自定义动态映射 如果你想在运行时增加新的字段,你可能会启用动态映射.然而,有时候,动态映射 规则 可能不太智能.幸运的是,我们可以通过设置去自定义这些规则,以便更好的适用于你的数据. 日期检测 当 E ...
- 面向对象【day07】:新式类和经典类(八)
本节内容 1.概述 2.类的多继承 3.经典类VS新式类 4.总结 一.概述 在python还支持多继承,但是一般我们很少用,有些语言干脆就不支持多继承,有多继承,就会带来两个概念,经典类和新式类,下 ...
- Linux 命令详解(十)Shell脚本的数组详解
1.数组定义 [root@bastion-IDC ~]# a=( ) [root@bastion-IDC ~]# echo $a 一对括号表示是数组,数组元素用“空格”符号分割开. 2.数组读取与赋值 ...
- PHP7 学习笔记(四)PHP PSR-4 Autoloader 自动加载
参考文献: 1.PHP PSR-4 Autoloader 自动加载(中文版) 2.PHP编码规范(中文版)导读 3.PHP-PSR-[0-4]代码规范 基本步骤: (1)在vendor 下新建一个项目 ...
- 流媒体技术学习笔记之(二)RTMP和HLS分发服务器nginx.conmf配置文件(解决了,只能播放RTMP流而不能够播放HLS流的原因)
user www www; worker_processes ; error_log logs/error.log debug; #pid logs/nginx.pid; events { worke ...
- 旅行商问题(TSP)、最长路径问题与哈密尔顿回路之间的联系(归约)
一,旅行商问题与H回路的联系(H回路 定义为 哈密尔顿回路) 旅行商问题是希望售货员恰好访问每个城市一次,最终回到起始城市所用的费用最低,也即判断图中是否存在一个费用至多为K的回路.(K相当于图中顶点 ...
- tomcat运行时为什么不能删除war
tomcat启动时会监听webapps下的war文件,如果有新增就解压,如果有删除就删除项目
- Linux 4.10.8 根文件系统制作(二)---制作jiffs文件系统
一.制作jiffs文件系统 制作jffs2 文件系统需要用到 mkfs.jffs2工具. 执行命令: mkfs.jffs2 -n -s 0x800 -e 0x20000 --pad=0x800000 ...
- 并行动画组QParallelAnimationGroup
QParallelAnimationGroup会同时执行添加到该组的所有动画 import sys from PyQt5.QtGui import QPixmap from PyQt5.QtCore ...
- Java EE之Struts2路径访问小结
一.项目WEB视图结构 注释:struts.xml:最普通配置,任何无特殊配置 二.访问页面 1.访问root.jsp //方式1: http://localhost/demo/root.jsp // ...