非旋转Treap:用运行时间换调试时间的有效手段

    Hello大家好,我们今天来聊一聊非旋转Treap

  相信各位或多或少都做过些序列上的问题。如果水题我们考虑暴力;不强制在线我们可能用过莫队和待修改莫队;不更改序列上的时间戳信息的我们使用线段树或者树状数组,也有可能请出主席树。那如果更大幅度的操作,我们要用到splay或者块链。但是!我们今天介绍一种特殊的平衡树,用时间换取时间的数据结构。

  啥意思?如果写过splay和块链,前者的调试时间和后者的敲版子时间让我们头痛欲裂,但是这个平衡树,好写好调,省去了时间。但是常数相对较大,这便是我们的代价(好一个时间换时间)。

  Treap就是Tree+heap,一个基于堆的二叉搜索树,但是它的维护过程是仰仗于旋转的。而我们今天讲的非旋转Treap,顾名思义,不需要旋转的哦。

  那应该如何维护平衡呢?用两个基本操作即可:撕裂&合并

  撕裂??考虑区间最本质的:如果我们可以将要修改的区间成为一棵单独的树,我们是不是想干什么就干什么!整体操作就打标记,特殊操作就特殊处理。我们也想要这样怎么办?引入一个split操作,就是断开原来BST上的一条边,整个BST变成了两棵树,返回一个pair,pair里存的是两棵新BST的根。如果想要提取一个区间,就split两次,中间的子树就是我们要的区间子树。 附上版子(为了方便理解,我每个语句都是一行):

// split函数的pair存的是两个新BST的两个根,时间戳小的在first,时间戳大的在second
pair split(int pos,int num) //表示在以pos为根节点的BST中,取出前num个作为新的BST,剩下的作为另一棵BST
{
if(num==0)//递归退出条件,如果在一个pos的BST取出前0个,我们就返回一个(0,pos)的pair。
{
return make_pair(0,pos);
}
int lson=a[pos].ls;
int rson=a[pos].rs;
// 这里我们分类讨论
if(num==a[lson].size) // 如果恰好是左子树的大小,我们将左子树断开。
{
a[pos].ls=0;//将左子树和根节点之间的路径断开
pushup(pos);//更新当前根节点信息。
return make_pair(lson,pos);//返回两个新子树的pair,这里返回的是lson而不是a[pos].ls是因为后者已经被更新成0.
}
// 后面的同理
else if(num==a[lson].size+1)
{
a[pos].rs=0;
pushup(pos);
return make_pair(pos,rson);
}
else if(num<a[lson].size)
{
Pair t=split(lson,num);
a[pos].ls=t.second;
pushup(pos);
return make_pair(t.first,pos);
}
else
{
// 这里我们需要注意:如果num>a[lson].size+1,说明我们分开的两棵子树中有一棵包含了pos的左子树和根节点。
Pair t=split(rson,num-a[lson].size-1); // 所以这里我们直接将右子树分成num-a[lson].size-1的两个子树。
a[pos].rs=t.first; // 然后将num-a[lson].size-1大小的子树和前面的树加在一起,这样就得到了大小为num的子树。
pushup(pos);
return make_pair(pos,t.second);
}
}

  合并??如果我们将一棵单独的表示区间的BST处理完了,我们需要将两棵子树合并到一起啊!所以就有了这个merge操作。因为我们本质上维护平衡的办法就是利用随机数的权值来维护一个依据权值的堆,撕裂操作显然不会打破这个局势,但是如果我们瞎合并,就无法保证平衡,时间复杂度也就没有办法保证。所以我们想一个能维护两个子树的方法,即能保证堆的性质,也保留BST应该有的中序遍历时间戳递增。我们请出可并堆!显然,如果我们采用可并堆的合并方式,是可以达到我们预期的效果的。

  可并堆是如何合并的呢?其实也非常简单。这样:因为我们是想维护基于随机权值val的堆!所以对于两棵BST,分别以x和y为根。其中,x的整体时间戳小于y。那么,如果val[x]大于val[y],那么x一定是他们两棵BST合并之后的根。因为val[x]是x所在子树里最大的,y是y所在子树里最大的,x还比y大,所以x是根。又因为,我们要维护Tree,所以我们递归地将x的右儿子和y合并即可。这样合并时候的新BST就满足我们的要求。 附上板子!

// merge 操作返回的是两棵BST合并后的新BST的根。
int merge(int x,int y)
{
if(x==0||y==0)
{
if(x==0) return y;
else return x;
}
// 如果x的权值大于y的权值,显然x是根。我们只需要让x的右儿子和y合并即可。
if(a[x].val_heap>a[y].val_heap)
{
a[x].rs=merge(a[x].rs,y);
pushup(x);
return x;
}
// 反之,如果y的权值大于x,显然y是根。所以我们让x和y的左儿子合并即可。
else
{
a[y].ls=merge(x,a[y].ls);
pushup(y);
return y;
}
}

  说完了非旋转Treap的基本操作,相信各位对于这个神奇的平衡树有了一定的认识。它的其他操作有了split和merge后都变得非常简单,自己手玩即可。

  例题时间

  Bzoj 3223 文艺平衡树 (所有平衡树的入门题)

  这道题非常经典,我们记录一下标记,每次merge和split之前pushdown一下即可。 趁着这个题看一下其他的操作哦。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100100
#define mp make_pair
using namespace std;
typedef pair<int,int> par;
int ls[N],rs[N],size[N],key[N],val[N];
int n,m,root,tot;
bool lazy[N];
inline void update(int x)
{
size[x]=1;
if(ls[x])size[x]+=size[ls[x]];
if(rs[x])size[x]+=size[rs[x]];
}
inline void pushdown(int x)
{
if(!x||!lazy[x])return;
swap(ls[x],rs[x]);lazy[x]=0;
if(ls[x])lazy[ls[x]]^=1;
if(rs[x])lazy[rs[x]]^=1;
}
int lson,rson;
par split(int x,int k)
{
pushdown(x);
if(!k) return mp(0,x);
lson=ls[x],rson=rs[x];
par t;
if(k==size[ls[x]])
{
ls[x]=0;update(x);
return mp(lson,x);
}
else if(k==size[ls[x]]+1)
{
rs[x]=0;update(x);
return mp(x,rson);
}
else if(k<size[ls[x]])
{
t=split(lson,k);
ls[x]=t.second;update(x);
return mp(t.first,x);
}
t=split(rson,k-size[ls[x]]-1);
rs[x]=t.first;update(x);
return mp(x,t.second);
}
int merge(int x,int y)
{
if(!x||!y) return x|y;
pushdown(x);pushdown(y);
if(key[x]<key[y])
{
ls[y]=merge(x,ls[y]);update(y);
return y;
}
rs[x]=merge(rs[x],y);update(x);
return x;
}
void output(int x,int flag)
{
pushdown(x);
if(ls[x]) output(ls[x],0);
if(!rs[x]&&flag) printf("%d",val[x]);
else printf("%d ",val[x]);
if(rs[x])output(rs[x],flag);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
key[++tot]=rand();
val[tot]=i;
size[tot]=1;
root=merge(root,tot);
}
par t1,t2;
for(int x,y,i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
t2=split(root,y);t1=split(t2.first,x-1);
lazy[t1.second]^=1;
root=merge(merge(t1.first,t1.second),t2.second);
}
output(root,1);
return 0;
}

  Bzoj 1861 书架

  多了两个get_rank和get_id的操作。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 80010
#define mp make_pair
using namespace std;
typedef pair<int,int> par;
int n,m,root;
int ls[N],rs[N],size[N],key[N],fa[N];
inline void pushup(int x)
{
size[x]=size[ls[x]]+size[rs[x]]+1;
}
int merge(int x,int y)
{
if(!x||!y) return x|y;
if(key[x]>key[y])
{
rs[x]=merge(rs[x],y);fa[rs[x]]=x;
pushup(x); return x;
}
ls[y]=merge(x,ls[y]);fa[ls[y]]=y;
pushup(y); return y;
}
par split(int x,int k)
{
if(!k) return mp(0,x);
int lson=ls[x],rson=rs[x];
if(k==size[lson])
{
ls[x]=0;pushup(x);
fa[lson]=0;
return mp(lson,x);
}
if(k==size[lson]+1)
{
rs[x]=0;pushup(x);
fa[rson]=0;
return mp(x,rson);
}
if(k<size[lson])
{
par t=split(lson,k);
ls[x]=t.second;fa[ls[x]]=x;pushup(x);
return mp(t.first,x);
}
par t=split(rson,k-size[ls[x]]-1);
rs[x]=t.first;fa[rs[x]]=x;pushup(x);
return mp(x,t.second);
}
int getid(int x)
{
int flag=1,t=x,ans=0;
while(t)
{
if(flag)ans+=size[ls[t]]+1;
flag=(t==rs[fa[t]]);
t=fa[t];
}
return ans-1;
}
int find(int x)
{
x--;int t=root;
while(1)
{
if(x==size[ls[t]]) return t;
if(x<size[ls[t]]) t=ls[t];
else x-=size[ls[t]]+1,t=rs[t];
}
}
int main()
{
cin >> n >> m ;
for(int x,i=1;i<=n;i++)
{
scanf("%d",&x);
size[x]=1,key[x]=rand()*rand();
root=merge(root,x);
}
char flag[10];
for(int x,y,i=1;i<=m;i++)
{
scanf("%s",flag);
if(flag[0]=='T')
{
scanf("%d",&x);
int id=getid(x);
par t2=split(root,id+1),t1=split(t2.first,id);
root=merge(t1.second,merge(t1.first,t2.second));
}
else if(flag[0]=='B')
{
scanf("%d",&x);
int id=getid(x);
par t2=split(root,id+1),t1=split(t2.first,id);
root=merge(merge(t1.first,t2.second),t1.second);
}
else if(flag[0]=='I')
{
scanf("%d%d",&x,&y);
if(!y)continue;
if(y==-1)
{
int id=getid(x);
par t3=split(root,id+1),t2=split(t3.first,id),t1=split(t2.first,id-1);
root=merge(merge(merge(t1.first,t2.second),t1.second),t3.second);
}
else
{
int id=getid(x);
par t3=split(root,id+2),t2=split(t3.first,id+1),t1=split(t2.first,id);
root=merge(merge(merge(t1.first,t2.second),t1.second),t3.second);
}
}
else if(flag[0]=='A')
{
scanf("%d",&x);
printf("%d\n",getid(x));
}
else
{
scanf("%d",&x);
printf("%d\n",find(x));
}
}
return 0;
}

  小结:好啦,非旋转Treap到这里,基本就说完了。这个平衡树好写,不用好调。常数虽然相较其他平衡树(除了BST)较大,但是时间复杂度是没有问题的。相信用这个fhq大神发明的神奇平衡树能让各位感受到序列的友好吧! 有问题直接评论哦!

非旋转Treap:用运行时间换调试时间的有效手段的更多相关文章

  1. BZOJ1014 JSOI2008 火星人prefix 【非旋转Treap】*

    BZOJ1014 JSOI2008 火星人prefix Description 火星人最近研究了一种操作:求一个字串两个后缀的公共前缀.比方说,有这样一个字符串:madamimadam,我们将这个字符 ...

  2. [bzoj3196][Tyvj1730]二逼平衡树_树套树_位置线段树套非旋转Treap/树状数组套主席树/权值线段树套位置线段树

    二逼平衡树 bzoj-3196 Tyvj-1730 题目大意:请写出一个维护序列的数据结构支持:查询给定权值排名:查询区间k小值:单点修改:查询区间内定值前驱:查询区间内定值后继. 注释:$1\le ...

  3. [bzoj3173]最长上升子序列_非旋转Treap

    最长上升子序列 bzoj-3173 题目大意:有1-n,n个数,第i次操作是将i加入到原有序列中制定的位置,后查询当前序列中最长上升子序列长度. 注释:1<=n<=10,000,开始序列为 ...

  4. 关于非旋转treap的学习

    非旋转treap的操作基于split和merge操作,其余操作和普通平衡树一样,复杂度保证方式与旋转treap差不多,都是基于一个随机的参数,这样构出的树树高为\(logn\) split 作用:将原 ...

  5. [Codeforces702F]T-Shirts——非旋转treap+贪心

    题目链接: Codeforces702F 题目大意:有$n$种T恤,每种有一个价格$c_{i}$和品质$q_{i}$且每种数量无限.现在有$m$个人,第$i$个人有$v_{i}$元,每人每次会买他能买 ...

  6. BZOJ5063旅游——非旋转treap

    题目描述 小奇成功打开了大科学家的电脑. 大科学家打算前往n处景点旅游,他用一个序列来维护它们之间的顺序.初 始时,序列为1,2,...,n. 接着,大科学家进行m次操作来打乱顺序.每次操作有6步: ...

  7. BZOJ3223文艺平衡树——非旋转treap

    此为平衡树系列第二道:文艺平衡树您需要写一种数据结构,来维护一个有序数列,其中需要提供以下操作: 翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 ...

  8. BZOJ3224普通平衡树——非旋转treap

    题目: 此为平衡树系列第一道:普通平衡树您需要写一种数据结构,来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3. 查询x数的排名(若有多个相同的数, ...

  9. [NOIP]2017列队——旋转treap/非旋转treap

    Sylvia 是一个热爱学习的女孩子.  前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia所在的方阵中有n × m名学生,方阵的行数为 n,列数为m.  为了便 ...

随机推荐

  1. HTML5 Canvas奇幻色彩Loading加载动画

    转自   https://www.html5tricks.com/tag/loading%E5%8A%A8%E7%94%BB/

  2. ExtJs如何使用自定义插件动态保存表头配置(隐藏或显示)

    关于保存列表表头的配置,一般我们不需要与后台交互,直接保存在 localStorage 中就能满足常规使用需求(需要浏览器支持). 直接上代码,插件: Ext.define('ux.plugin.Co ...

  3. Java输入几行字符串

    查找书籍 给定n本书的名称和定价,本题要求编写程序,查找并输出其中定价最高和最低的书的名称和定价. 输入格式: 输入第一行给出正整数n(<10),随后给出n本书的信息.每本书在一行中给出书名,即 ...

  4. 老男孩Python高级全栈开发工程师【真正的全套完整无加密】

    点击了解更多Python课程>>> 老男孩Python高级全栈开发工程师[真正的全套完整无加密] 课程大纲 老男孩python全栈,Python 全栈,Python教程,Django ...

  5. Template--模板

    模板引擎的支持 配置 模板引擎配置为TEMPLATES设置.这是一个配置列表,每个引擎一个,默认值为空.这是settings.py生成的,通过startproject命令定义了一个更有用的值: TEM ...

  6. LeetCode(92) Reverse Linked List II

    题目 Reverse a linked list from position m to n. Do it in-place and in one-pass. For example: Given 1- ...

  7. mysqldump 常见报错及解决

    mysqldump失败案例及解决: 1.mysqldump: Error 2020: Got packet bigger than 'max_allowed_packet' bytes when du ...

  8. 序列化 random模块应用

    序列化 我们今天学习下序列化,什么是序列化呢? 将原本的字典.列表等内容转换成一个字符串的过程就叫做序列化. 为什么要有序列化模块: 比如,我们在python代码中计算的一个数据需要给另外一段程序使用 ...

  9. 【01】let和const命令

    let和const命令   魔芋总结: 01,let声明变量,只在代码块{}内有效. 02,不存在变量提升,只能先声明,再使用.否则报错. 03,暂时性死区 如果代码块中存在let和const声明的变 ...

  10. Codeforces Round #362 (Div. 2)

    闲来无事一套CF啊,我觉得这几个题还是有套路的,但是很明显,这个题并不难 A. Pineapple Incident time limit per test 1 second memory limit ...