!前置技能&概念!

二叉搜索树

一棵二叉树,对于任意子树,满足左子树中的任意节点对应元素小于根的对应元素,右子树中的任意节点对应元素大于根对应元素。换言之,就是满足中序遍历为依次访问节点对应元素为升序的二叉树。

平衡树

一棵二叉搜索树,为了防止插入、查询等在朴素二叉搜索树中复杂度为$O(Dep)$的操作在极端数据下会$TLE$,而在操作中不断通过旋转等操作使得树的形态更加平衡,并满足中序遍历不变。

$Treap$

一棵基于给每个点随机分配权值并维护堆结构以保持树结构较为平均的平衡树

无旋$Treap$

不使用旋转而不断分裂合并来代替其他操作的$Treap$

合并

两棵非旋$Treap\space A\space B$能合并,当且仅当$A$中的所有元素均小于$B$中的所有元素。

合并时,先保证堆的结构,即根为$A,B$两根中随机分配的值较小(本文以小根堆为例)

若将元素小(不妨设为$A$)的根$Root_A$作为根,考虑到$A$中元素小于$B$,则将$Root_A$的右儿子所在子树与$B$合并,并作为$A$的新右儿子。

反之,若将元素大(不妨设为$B$)的根$Root_B$作为根,考虑到$B$中元素大$A$,则将$Root_B$的左儿子所在子树与$A$合并,并作为$B$的新左儿子。

然后递归处理即可。不难发现,每一次合并都同时保证了堆和平衡树的结构。

#define ls c[x][0]
#define rs c[x][1]
#define lt c[y][0]
#define rt c[y][1]
int merge(int x,int y){
if(!x||!y) return x|y;
if(K[x]>K[y]){lt=merge(x,lt),pushup(y);return y;}
rs=merge(rs,y),pushup(x); return x;
//K值为每个点被随机分配的值
//注意:在无旋Treap中是不需要记录每个点的父节点的
}

分裂

将以$x$为根的$Treap$分裂为两个$Treap\space A\space B$,其中任意$A$中元素都小于所有$B$中的元素。

每次操作得到两个$Treap$一定需要返回两个数,我习惯用结构体...

说正经的,若我们要将以$x$为根的$Treap$前$K$个分为一组,后$N-K$个分为一组,先判断若左子树的$Size$已经不小于$K$了,那么前$K$个一定全部在左子树中产生,我们递归将左子树分为前$K$个为一组,剩下的为一组,这样$x$即$x$的右子树一定属于那$N-K$个,我们只需要把左子树中剩下的那部分作为$x$的左子树即可。若左子树的$Size$小于$K$,那么至少左子树和$x$一定属于前$K$个,设左子树的$Size=SizeL$,那么只需要递归将$x$的右子树划分为前$K-SizeL-1$个分为一组,并把这些连成$x$的新右子树即可。当$x$为空时,两个根都是空,返回即可。

#define ls c[x][0]
#define rs c[x][1]
struct Droot{int A,B;};
Droot split(int x,int rk){
Droot tmp; if(!x){tmp.A=tmp.B=0;return tmp;}
if(sz[ls]>=rk) tmp=split(ls,rk),ls=tmp.B,pushup(x),tmp.B=x;
else tmp=split(rs,rk-sz[ls]-1),rs=tmp.A,pushup(x),tmp.A=x;
return tmp;
}

无旋$Treap$的基本操作大致就是这两个,其他平衡树的常规操作都可以建立在它的基础上进行。

插入&删除

若要插入,在$pos$的位置前后分裂成两棵,再创造一个要插入的新的节点,最后依次合并即可。

若要删除,在$pos-1$的位置前后分裂成两棵,再在后一棵分裂出前一个,把这个点忽略,把剩下两棵$Treap$合并即可。

单点区间$\rightarrow$查询修改

把与当前要查询的部分无关的前缀和后缀都分裂出去,然后就可以进行操作和询问,最后再合并回去

BZOJ1014 火星人

用平衡树动态维护哈希值

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 400020
#define bas 29
#define ls c[x][0]
#define rs c[x][1]
#define lt c[y][0]
#define rt c[y][1]
using namespace std;
int read(){
int nm=0,fh=1; char cw=getchar();
for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
return nm*fh;
}
void write(int x){if(x>9) write(x/10);putchar(x%10+'0');}
char ch[M],S[20];
int sed=67109281,n,m,c[M][2],K[M],sz[M],u,v;
int tot,H[M],P[M],Root,p[M],CT;
int RAND(){return sed=(LL)sed*17%998244353;}
void pushup(int x){sz[x]=sz[ls]+sz[rs]+1,H[x]=H[ls]*P[sz[rs]+1]+p[x]*P[sz[rs]]+H[rs];}
struct Droot{int A,B;};
int nw(int x){++tot,K[tot]=RAND(),p[tot]=x,pushup(tot);return tot;}
int merge(int x,int y){
if(!x||!y) return x|y;
if(K[x]>K[y]){lt=merge(x,lt),pushup(y);return y;}
rs=merge(rs,y),pushup(x); return x;
}
Droot split(int x,int rk){
Droot tmp; if(!x){tmp.A=tmp.B=0;return tmp;}
if(sz[ls]>=rk) tmp=split(ls,rk),ls=tmp.B,pushup(x),tmp.B=x;
else tmp=split(rs,rk-sz[ls]-1),rs=tmp.A,pushup(x),tmp.A=x;
return tmp;
}
void ins(int x,int pos){
Droot tmp=split(Root,pos); x=nw(x);
Root=merge(tmp.A,x),Root=merge(Root,tmp.B);
}
void change(int x,int pos){
Droot tmp=split(Root,pos-1),ot; ot=split(tmp.B,1);
p[ot.A]=x,pushup(ot.A),Root=merge(tmp.A,ot.A),Root=merge(Root,ot.B);
}
int getnum(int l,int r){
Droot tmp=split(Root,r);
Droot ot=split(tmp.A,l-1);
int fin=H[ot.B];
Root=merge(ot.B,tmp.B),Root=merge(ot.A,Root);
return fin;
}
int getans(int t1,int t2){
if(t1==t2) return tot-t1+1;
int l=0,r=tot-max(t1,t2),md,fin=0,ans1,ans2;
while(l<=r){
md=((l+r)>>1),ans1=getnum(t1,t1+md),ans2=getnum(t2,t2+md);
if(ans1!=ans2) r=md-1; else fin=l=md+1;
} return fin;
}
int build(int l,int r){
int x=((l+r)>>1),now=++tot; CT+=10,K[now]=CT,p[now]=ch[x]-'a';
if(l<x) c[now][0]=build(l,x-1); if(x<r) c[now][1]=build(x+1,r);
pushup(now); return now;
}
int main(){
scanf("%s",ch+1),n=strlen(ch+1),P[0]=1;
for(int i=1;i<=n;i++) P[i]=P[i-1]*bas; Root=build(1,n);
for(int T=read();T;T--){
scanf("%s",S);
if(S[0]=='Q') u=read(),v=read(),m=getans(u,v),printf("%d\n",m);
else if(S[0]=='I') u=read(),scanf("%s",S),ins(S[0]-'a',u);
else u=read(),scanf("%s",S),change(S[0]-'a',u);
}
return 0;
}

  

无旋$Treap$还有一个非常强大的功能——可持久化

不难发现,没有旋转,每次修改仅是左右儿子中的一个,那么就尝试进行可持久化。

每次修改不选择直接连上新的左右儿子,而是复制新的节点作为左右儿子。

放上支持访问历史版本的文艺平衡树。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define mid ((l+r)>>1)
#define ls c[x][0]
#define rs c[x][1]
#define M 50020
using namespace std;
int read(){
int nm=0,fh=1;char cw=getchar();
for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
return nm*fh;
}
int n,m,c[M*300][2],sz[M*300],rev[M*300],rt[M*300],ti,T,tpe,at;
int sum[M*300],nd[M*300],p[M*300],cnt,sed=21854203,u,v,t[M];
struct Droot{int A,B;};
int rd(){return sed=abs(sed*547-93723893);}
void pushup(int x){sz[x]=sz[ls]+sz[rs]+1,sum[x]=sum[ls]+sum[rs]+p[x];}
int creat(int num){cnt++,sum[cnt]=p[cnt]=num,sz[cnt]=1;return cnt;}
int nw(int pre){
if(pre==0) return 0;
cnt++,sum[cnt]=sum[pre],p[cnt]=p[pre],nd[cnt]=nd[pre],sz[cnt]=sz[pre];
c[cnt][0]=c[pre][0],c[cnt][1]=c[pre][1],rev[cnt]=rev[pre];
return cnt;
}
void pushdown(int x){
if(x==0||!rev[x]) return;
int L=ls,R=rs;
L=nw(L),R=nw(R),ls=L,rs=R;
rev[ls]^=1,rev[rs]^=1;
swap(ls,rs),rev[x]=0;
}
int merge(int x,int y){
if(x*y==0) return x|y;
int now;
if(nd[x]<nd[y]) now=nw(x),pushdown(now),c[now][1]=merge(c[now][1],y);
else now=nw(y),pushdown(now),c[now][0]=merge(x,c[now][0]);
pushup(now); return now;
}
Droot split(int x,int rk){
Droot tmp;
if(x==0){tmp.A=tmp.B=0;return tmp;}
x=nw(x),pushdown(x);
if(sz[ls]>=rk) tmp=split(ls,rk),ls=tmp.B,pushup(x),tmp.B=x;
else tmp=split(rs,rk-sz[ls]-1),rs=tmp.A,pushup(x),tmp.A=x;
return tmp;
}
void reverse(int L,int R){
ti++,rt[ti]=nw(rt[at]),at=ti;
Droot t1=split(rt[ti],L-1);
Droot t2=split(t1.B,R-L+1);
rev[t2.A]^=1;
rt[ti]=merge(t1.A,t2.A);
rt[ti]=merge(rt[ti],t2.B);
}
int query(int L,int R){
Droot t1=split(rt[at],L-1);
Droot t2=split(t1.B,R-L+1);
int numb=sum[t2.A];
rt[at]=merge(t1.A,t2.A),rt[at]=merge(rt[at],t2.B);
return numb;
}
void ins(int num){int now=creat(num);rt[0]=merge(rt[0],now);}
int build(int l,int r,int hp){
if(l>r) return 0;
int x=creat(t[mid]);
nd[x]=hp;
ls=build(l,mid-1,hp+(rd()%200000)),rs=build(mid+1,r,hp+(rd()%200000));
pushup(x);return x;
}
int main(){
n=read(),T=read(),at=0;
for(int i=1;i<=n;i++) t[i]=read();
rt[0]=build(1,n,rd()%200000);
while(T--){
tpe=read();
if(tpe<3){
u=read(),v=read();
if(u>v) swap(u,v);
if(tpe==1) reverse(u,v);
else printf("%d\n",query(u,v));
}
else at=read();
if(at>ti) break;
}
}

  

无旋Treap - BZOJ1014火星人 & 可持久化版文艺平衡树的更多相关文章

  1. [Bzoj1014][JSOI2008]火星人prefix(无旋Treap&hash)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1014 因为涉及到增加和修改,所以后缀数组就被pass掉了,想到的就是平衡树维护hash值 ...

  2. 模板 - 数据结构 - 可持久化无旋Treap/PersistentFHQTreap

    有可能当树中有键值相同的节点时,貌似是要对Split和Merge均进行复制的,本人实测:只在Split的时候复制得到了一个WA,但只在Merge的时候复制还是AC,可能是恰好又躲过去了.有人说假如确保 ...

  3. [转载]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)

    转载自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182491.html 今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和t ...

  4. [您有新的未分配科技点]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)

    今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和treap一样简单易懂,同时还支持可持久化. 无旋treap的节点定义和treap一样,都要同时满足树性质和堆性质,我 ...

  5. 【算法学习】Fhq-Treap(无旋Treap)

    Treap——大名鼎鼎的随机二叉查找树,以优异的性能和简单的实现在OIer们中广泛流传. 这篇blog介绍一种不需要旋转操作来维护的Treap,即无旋Treap,也称Fhq-Treap. 它的巧妙之处 ...

  6. 无旋treap的区间操作实现

    最近真的不爽...一道维修数列就做了我1上午+下午1h+1晚上+晚上1h+上午2h... 一道不错的自虐题... 由于这一片主要讲思想,代码我放这里了 不会无旋treap的童鞋可以进这里 呵呵... ...

  7. 浅谈无旋treap(fhq_treap)

    一.简介 无旋Treap(fhq_treap),是一种不用旋转的treap,其代码复杂度不高,应用范围广(能代替普通treap和splay的所有功能),是一种极其强大的平衡树. 无旋Treap是一个叫 ...

  8. [转载]无旋treap:从单点到区间(例题 BZOJ1500&NOI2005 维护数列 )

    转自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182631.html 1500: [NOI2005]维修数列 Time Limit: 10 Sec  Mem ...

  9. Luogu 3369 / BZOJ 3224 - 普通平衡树 - [无旋Treap]

    题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=3224 https://www.luogu.org/problemnew/show/P3 ...

随机推荐

  1. 驱动程序分层分离概念_总线驱动设备模型_P

    分层概念: 驱动程序向上注册的原理: 比如:输入子程序一个input.c作为一层,下层为Dev.c和Dir.c,分别编写Dev.c和Dir.c向上Input.c注册:如图所示 分离概念: 分离概念主要 ...

  2. python函数的作用域

    python函数的作用域 以下内容参考自runoob网站,以总结python函数知识点,巩固基础知识,特此鸣谢! 原文地址:http://www.runoob.com/python3/python3- ...

  3. 2.Django命令行工具搭建项目(django-admin.py & manage.py)

    1.概念: django-admin.py是django的用于管理任务的命令行工具 manage.py是对django-admin.py的简单包装,每个project里会有一个自己的manage.py ...

  4. centos7 mysql允许远程连接设置

    Mysql为了安全性,在默认情况下用户只允许在本地登录,可是在有此情况下,还是需要使用用户进行远程连接,因此为了使其可以远程需要进行如下操作: 一.允许root用户在任何地方进行远程登录,并具有所有库 ...

  5. 蜗牛—ORACLE基础之触发器学习(三)

    版权声明:本文为大腰子原创文章,如若转载,请标明原地址. https://blog.csdn.net/u010071361/article/details/30037215 建立一个触发器, 当职工表 ...

  6. Struts详解

    1.什么是MVC? MVC是Model,View,Controller的缩写,MVC是Application开发的设计模式, 也就是大家所知道的Model2.在MVC的设计模式中,它包括三类对象:(1 ...

  7. Iptalbes练习题(三)

    场景需求: (1)员工在公司内部(192.168.124.0/24 ,192.168.122.0/24 )能访问服务器上任何服务 (2)当员工出差,通过VPN连接到公司 (3)公司门户网站允许公网访问 ...

  8. pinpoint agent线程模型

    pinpoint agent线程模型 以下分析基于pinpoint1.7.1版本 pinpoint agent主要使用到的异步线程有4个 DeadlockMonitorThread : 死锁监测线程, ...

  9. LRC歌词文件读取代码

    /**************************************************/ /*******************-main文件-******************* ...

  10. Java多线程系列 JUC线程池07 线程池原理解析(六)

     关闭“线程池” shutdown()的源码如下: public void shutdown() { final ReentrantLock mainLock = this.mainLock; // ...