无旋Treap - BZOJ1014火星人 & 可持久化版文艺平衡树
!前置技能&概念!
二叉搜索树
一棵二叉树,对于任意子树,满足左子树中的任意节点对应元素小于根的对应元素,右子树中的任意节点对应元素大于根对应元素。换言之,就是满足中序遍历为依次访问节点对应元素为升序的二叉树。
平衡树
一棵二叉搜索树,为了防止插入、查询等在朴素二叉搜索树中复杂度为$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火星人 & 可持久化版文艺平衡树的更多相关文章
- [Bzoj1014][JSOI2008]火星人prefix(无旋Treap&hash)
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1014 因为涉及到增加和修改,所以后缀数组就被pass掉了,想到的就是平衡树维护hash值 ...
- 模板 - 数据结构 - 可持久化无旋Treap/PersistentFHQTreap
有可能当树中有键值相同的节点时,貌似是要对Split和Merge均进行复制的,本人实测:只在Split的时候复制得到了一个WA,但只在Merge的时候复制还是AC,可能是恰好又躲过去了.有人说假如确保 ...
- [转载]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)
转载自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182491.html 今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和t ...
- [您有新的未分配科技点]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)
今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和treap一样简单易懂,同时还支持可持久化. 无旋treap的节点定义和treap一样,都要同时满足树性质和堆性质,我 ...
- 【算法学习】Fhq-Treap(无旋Treap)
Treap——大名鼎鼎的随机二叉查找树,以优异的性能和简单的实现在OIer们中广泛流传. 这篇blog介绍一种不需要旋转操作来维护的Treap,即无旋Treap,也称Fhq-Treap. 它的巧妙之处 ...
- 无旋treap的区间操作实现
最近真的不爽...一道维修数列就做了我1上午+下午1h+1晚上+晚上1h+上午2h... 一道不错的自虐题... 由于这一片主要讲思想,代码我放这里了 不会无旋treap的童鞋可以进这里 呵呵... ...
- 浅谈无旋treap(fhq_treap)
一.简介 无旋Treap(fhq_treap),是一种不用旋转的treap,其代码复杂度不高,应用范围广(能代替普通treap和splay的所有功能),是一种极其强大的平衡树. 无旋Treap是一个叫 ...
- [转载]无旋treap:从单点到区间(例题 BZOJ1500&NOI2005 维护数列 )
转自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182631.html 1500: [NOI2005]维修数列 Time Limit: 10 Sec Mem ...
- Luogu 3369 / BZOJ 3224 - 普通平衡树 - [无旋Treap]
题目链接: https://www.lydsy.com/JudgeOnline/problem.php?id=3224 https://www.luogu.org/problemnew/show/P3 ...
随机推荐
- pthon 基础 9.8 增加数据
#/usr/bin/python #-*- coding:utf-8 -*- #@Time :2017/11/24 2:59 #@Auther :liuzhenchuan #@File :增加 ...
- thinkphp自动验证无效的问题
新手入门thinkphp,试用自动验证表单输入数据功能,却发现怎么都不能调用自动验证,自动验证无效,原因竟是一个小细节的疏忽,学习一定要细心啊! Action方法: IndexAction下的adds ...
- 高次同余方程模板BabyStep-GiantStep
/************************************* ---高次同余方程模板BabyStep-GiantStep--- 输入:对于方程A^x=B(mod C),调用BabySt ...
- windows10系统自带输入法不能切换中文如何解决
具体如下: 1.打开计算机管理软件,右击“开始”按钮,在弹出的菜单中选择“计算机管理”: 2.或在桌面右击“此电脑”图标,在弹出的菜单中选择“管理”: 3.在打开的计算机管理软件中,选择“系统工具”- ...
- 【BZOJ2460】[BeiJing2011]元素 贪心+高斯消元求线性基
[BZOJ2460][BeiJing2011]元素 Description 相传,在远古时期,位于西方大陆的 Magic Land 上,人们已经掌握了用魔法矿石炼制法杖的技术.那时人们就认识到,一个法 ...
- 电路分析二-------基尔霍夫定律KCL和KVL
1.先了解几个名词 (1)支路----一个二端原件视为一条支路--图中6个二端原件所以有6条支路. (2)结点----两条或以上的支路连接的点. d,e可以看做一个结点. (3).回路----- (4 ...
- ubuntu下操作端口的方法
最简单的一个操作:sudo ufw status可检查防火墙的状态,我的返回的是:不活动 sudo ufw version防火墙版本: ufw 0.29-4ubuntu1 Copyright 2008 ...
- 软RAID5制作流程
说明:本实验没有用到多个磁盘,而是利用单个磁盘划分出的多个分区来仿真的,如果在实际项目中,请依情况而定. 1. 分区 我这里划分6个分区,用4个分区组成RAID 5,用1个分区作为spare disk ...
- What I learned from competing against a ConvNet on ImageNet
http://karpathy.github.io/2014/09/02/what-i-learned-from-competing-against-a-convnet-on-imagenet/
- linux c编程:Posix共享内存区
Posix共享内存区:共享内存是最快的可用IPC形式.它允许多个不相关(无亲缘关系)的进程去访问同一部分逻辑内存.如果需要在两个进程之间传输数据,共享内存将是一种效率极高的解决方案.一旦这样的内存区映 ...