无旋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;
}
}