无旋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 ...
随机推荐
- android shape的用法总结
参考代码: <shape xmlns:android="http://schemas.android.com/apk/res/android" > <corner ...
- 基于python实现简单web服务器
做web开发的你,真的熟悉web服务器处理机制吗? 分析请求数据 下面是一段原始的请求数据: b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8000\r\nConnectio ...
- spring boot的对象注入
1 需求 现在我们的项目中需要引入一个java类库,我想要很方便的使用该类库中的一个类,并且我想要创建这个类的一个单例对象.然后可以很方便的在各个模块中用@AutoWired进行对象注入. 比如一个配 ...
- iOS Load方法 和 initialize方法的比较
一.load方法特点: 1. 当类被引用进程序的时候会执行这个函数 2.一个类的load方法不用写明[super load],父类就会收到调用,并且在子类之前. 3.Category的load也会收到 ...
- spring 事物管理
示例:模拟实现转账操作,"A"转给"B"1000,"A"少1000而"B"多一千. 一.转账环境搭建 1.xml配置文件 ...
- windows下安装PyQt4
第一步:确认自己电脑上的Python版本.然后下载对应的.whl文件下载 第二步:https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyqt4上下载对应版本版本的 ...
- SAP 定价
近几天做门店团购销售订单上传SAP接口程序,SO创建测试过程中, 遇到定价问题,同事在定价过程的增强过不了. VOFM 了解到定价过程是个非常复杂的环节,此处出现程序处理过程中ZMP0定价条件下的价格 ...
- urllib2下载网页的三种方法
1.最直接的方法 #-*- coding: utf-8 -*- import urllib2 #直接请求 response = urllib2.urlopen('https://www.baidu.c ...
- yii 资料
https://github.com/forecho/awesome-yii2 会随时更新 链接:http://pan.baidu.com/s/1mgCKtUK 密码:t6t1 与<YII框架& ...
- php 图片下载
php图片保存.下载 <?php //获取图片2进制内容 ,可以保存入数据库 $imgStr = file_get_contents('http://.../1.jpg'); //保存图片 $f ...