前置知识:fhq-treap(无旋treap)
可以拆成 可持久化 和 平衡树 来看,所以就是可以维护历史版本的平衡树,在此,我们的无旋treap与splay相比可以很好的进行转化(主要还是因为splay的旋转操作进行历史版本回溯比较困难),其实如果会打主席树(可持久化线段树),那么可持久化平衡树还是相当简单的,与普通的平衡树相比,就多了历史版本根的记录,以及树节点的复制而已(>_<) 。
有人要问:开那么多节点,空间不会爆炸吗。
我们平衡树的深度是理论 l o g n log n logn 的,所以每次操作最多添加 l o g log log 个节点,所以空间复杂度是 n log n n \log n nlogn 的,但是还是建议开 50 50 50 倍的空间,不然会炸(主要是无旋treap的随机运气不好会炸)
如果你不会无旋 treap:无旋treap学习笔记
和普通的平衡树没什么两样:
void merge(int &rt,int x,int y){
if(!x||!y){
rt=x+y;
return;
}
if(tree[x].id>tree[y].id){
pushdown(x);//注意:先下传标记,再复制节点!!!
tot++,rt=tot;
tree[rt]=tree[x];
tree[rt].id=rd();//此处建议是再取一次随机数
merge(rs(rt),rs(x),y);
pushup(rt),pushup(x);
}
else{
pushdown(y);
tot++,rt=tot;
tree[rt]=tree[y];
tree[rt].id=rd();
merge(ls(rt),x,ls(y));
pushup(rt),pushup(y);
}
}
我们的 i d id id 为随机赋值,以保证时间及空间复杂度的正确(但是有的时候还是建议随机合成,下面有例题会解释)
注意:我们每次复制节点的时候,一定要先下传标记,不然对于一个子节点来说,此时共有两个父亲携带下传标记( r o o t root root 和 n e w r o o t newroot newroot 都有),就会造成错误。
void split(int rt,int &x,int &y,int siz){
if(rt==0){
x=y=0;
return;
}
pushdown(rt);
if(tree[ls(rt)].siz<siz){
tot++,x=tot;
tree[x]=tree[rt];
split(rs(rt),rs(x),y,siz-tree[ls(rt)].siz-1);
pushup(rt),pushup(x);
}
else{
tot++,y=tot;
tree[y]=tree[rt];
split(ls(rt),x,ls(y),siz);
pushup(rt),pushup(y);
}
}
在此处是按照大小进行分裂,也可以按照权值分裂。
也是要记得 先下传标记
另外,还有非常重要的:下传标记要复制新点,而上传不需要
可以发现,可持久化和普通其实没有什么实质的区别,还是很好学的=)
Luogu P3835 【模板】可持久化平衡树
非常经典的例题,普通的平衡树加上历史版本的修改和查询,若普通平衡树通过了,就可以加上历史版本维护随便切了。
#include
#include
#include
#include
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
using namespace std;
struct node{
int ls,rs,id,siz,val;
}tree[30000200];
int tot,n,root[500500];
unsigned int seed=127;
inline int read(){
long long num=0,f=1;
char ch=getchar();
while(!(ch>='0'&&ch<='9')){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
num=num*10+ch-'0';
ch=getchar();
}
return num*f;
}
inline int rd(){
return seed=seed*(unsigned int)100007;
}
int add(int val){
tot++,tree[tot].id=rd();
ls(tot)=rs(tot)=0,tree[tot].siz=1;
tree[tot].val=val;
return tot;
}
void pushup(int x){
tree[x].siz=tree[ls(x)].siz+tree[rs(x)].siz+1;
}
void merge(int &rt,int x,int y){
if(x==0||y==0){
if(!x&&!y){
rt=0;
return;
}
tot++,rt=tot;
if(x) tree[rt]=tree[x];
if(y) tree[rt]=tree[y];
return;
}
if(tree[x].id<tree[y].id){
tot++,rt=tot;
tree[rt]=tree[x];
merge(rs(rt),rs(x),y);
}
else{
tot++,rt=tot;
tree[rt]=tree[y];
merge(ls(rt),x,ls(y));
}
pushup(rt);
}
void split(int rt,int &x,int &y,int val){
if(rt==0){
x=y=0;
return;
}
if(tree[rt].val<=val){
tot++,x=tot;
tree[x]=tree[rt];
split(rs(rt),rs(x),y,val);
pushup(x);
}
else{
tot++,y=tot;
tree[y]=tree[rt];
split(ls(rt),x,ls(y),val);
pushup(y);
}
pushup(rt);
}
int query_kth(int rt,int x){
if(tree[ls(rt)].siz+1==x) return tree[rt].val;
if(tree[ls(rt)].siz>=x) return query_kth(ls(rt),x);
return query_kth(rs(rt),x-tree[ls(rt)].siz-1);
}
int find_max(int rt){
while(rs(rt)) rt=rs(rt);
return tree[rt].val;
}
int find_min(int rt){
while(ls(rt)) rt=ls(rt);
return tree[rt].val;
}
int main(){
n=read();
for(register int i=1;i<=n;i++){
int v=read(),opt=read(),x=read();
root[i]=root[v];
if(opt==1){
int m1=add(x),m2=0;
split(root[i],root[i],m2,x);
merge(root[i],root[i],m1);
merge(root[i],root[i],m2);
}
if(opt==2){
int m1=0,m2=0;
split(root[i],root[i],m2,x);
split(root[i],root[i],m1,x-1);
merge(m1,ls(m1),rs(m1));
merge(root[i],root[i],m1);
merge(root[i],root[i],m2);
}
if(opt==3){
int m1=0;
split(root[i],root[i],m1,x-1);
printf("%d\n",tree[root[i]].siz+1);
merge(root[i],root[i],m1);
}
if(opt==4)
printf("%d\n",query_kth(root[i],x));
if(opt==5){
int m1=0;
split(root[i],root[i],m1,x-1);
if(!root[i]) printf("-2147483647\n");
else printf("%d\n",find_max(root[i]));
merge(root[i],root[i],m1);
}
if(opt==6){
int m1=0;
split(root[i],root[i],m1,x);
if(!m1) printf("2147483647\n");
else printf("%d\n",find_min(m1));
merge(root[i],root[i],m1);
}
}
}
Luogu P5055 【模板】可持久化文艺平衡树
同样的,可以参考文艺平衡树的模板题,但是注意一点:下传标记一定要复制新点,我们当前的儿子还是上一个历史版本的儿子,所以此时要将其复制一份,不然就会让上一个历史版本打上此时版本的标记,这是我们所不希望看到的,下传还是 log \log log 的,所以不用担心空间的问题,但建议有多大开多大。
#include
#include
#include
#include
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
using namespace std;
struct node{
int ls,rs,id,siz;
long long sum,val;
bool rev;
}tree[30000300];
int n,root[200200],tot;
long long lastans=0;
unsigned int seed=127;
unsigned int rd(){
return seed=seed*(unsigned int)100007;
}
inline long long read(){
long long num=0,f=1;
char ch=getchar();
while(!(ch>='0'&&ch<='9')){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
num=num*10+ch-'0';
ch=getchar();
}
return num*f;
}
void pushdown(int x){
if(!tree[x].rev) return;
int nw_ls=0,nw_rs=0;
if(ls(x)) tot++,nw_rs=tot,tree[nw_rs]=tree[ls(x)];
if(rs(x)) tot++,nw_ls=tot,tree[nw_ls]=tree[rs(x)];
ls(x)=nw_ls,rs(x)=nw_rs;
tree[ls(x)].rev^=1,tree[rs(x)].rev^=1;
tree[x].rev=0;
return;
}
void pushup(int x){
tree[x].siz=tree[ls(x)].siz+tree[rs(x)].siz+1;
tree[x].sum=tree[ls(x)].sum+tree[rs(x)].sum+tree[x].val;
}
void merge(int &rt,int x,int y){
if(!x||!y){
rt=x+y;
return;
}
// pushdown(rt);
if(tree[x].id<tree[y].id){
pushdown(x);
tot++,rt=tot;
tree[rt]=tree[x];
merge(rs(rt),rs(x),y);
}
else{
pushdown(y);
tot++,rt=tot;
tree[rt]=tree[y];
merge(ls(rt),x,ls(y));
}
pushup(rt);
}
void split(int rt,int &x,int &y,int siz){
if(rt==0){
x=y=0;
return;
}
pushdown(rt);
if(tree[ls(rt)].siz<siz){
tot++,x=tot;
tree[x]=tree[rt];
split(rs(rt),rs(x),y,siz-tree[ls(rt)].siz-1);
pushup(x);
}
else{
tot++,y=tot;
tree[y]=tree[rt];
split(ls(rt),x,ls(y),siz);
pushup(y);
}
pushup(rt);
}
int add(long long val){
tot++,ls(tot)=rs(tot)=0;
tree[tot].siz=1,tree[tot].sum=tree[tot].val=val;
tree[tot].rev=0,tree[tot].id=rd();
return tot;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
int v=read(),opt=read();
root[i]=root[v];
if(opt==1){
int p=read(),x=read();
p^=lastans,x^=lastans;
int m1=0,m2=add(x);
split(root[i],root[i],m1,p);
merge(root[i],root[i],m2);
merge(root[i],root[i],m1);
}
if(opt==2){
int p=read();
p^=lastans;
int m1=0,m2=0;
split(root[i],root[i],m1,p);
split(root[i],root[i],m2,p-1);
merge(root[i],root[i],m1);
}
if(opt==3){
int l=read(),r=read(),m1=0,m2=0;
l^=lastans,r^=lastans;
split(root[i],root[i],m1,r);
split(root[i],root[i],m2,l-1);
tree[m2].rev^=1;
merge(root[i],root[i],m2);
merge(root[i],root[i],m1);
}
if(opt==4){
int l=read(),r=read(),m1=0,m2=0;
l^=lastans,r^=lastans;
split(root[i],root[i],m1,r);
split(root[i],root[i],m2,l-1);
lastans=tree[m2].sum;
printf("%lld\n",lastans);
merge(root[i],root[i],m2);
merge(root[i],root[i],m1);
}
}
}
不带劲?来点难的。
JZOJ 【NOI2014模拟】文本编辑器(editor)
除了第三个操作以外,我们其他的操作都可以用无旋 treap 来实现,但是看到第三个操作,我们总不能一个一个加上去吧。。。(时间空间双爆炸力,悲),所以我们考虑将此区间暂时存放在第 x x x 个字符后面,这样就不用每个字符都插入一遍了,只用插入 log \log log 个,所以时间复杂度和空间复杂度都是 O ( m log max ( L e n ) ) O(m \log \max(Len)) O(mlogmax(Len)) ,但是如果给 t r e a p treap treap 随机标号,我们会发现:
为什么?
别忘记我们无旋treap现在的长度可是 L e n Len Len 的,所以如果我们没弄好左右子树存放条件,我们可承担不起多出来的深度(毕竟是 L e n Len Len 的,悲 )
经过研究前人的代码,我发现有两种判断方法:
1. 1. 1. s i z e [ x ] > s i z e [ y ] size[x]>size[y] size[x]>size[y]
2. 2. 2. r a n d ( ) m o d ( s i z e [ x ] + s i z e [ y ] ) < s i z e [ x ] rand()\mod (size[x]+size[y])
都只和 treap 的 s i z e size size 相关,和随机数毛关系都没有,为什么?(我也不会)
感性理解一波:我们的”随机数“在此处循环节太短,所以会出现树的深度不符期望的情况。而这种真正做到了随机合并(逃
#include
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
using namespace std;
struct node{
int ls,rs,siz,id;
char c;
bool rev;
}tree[20000100];
int tot=0,n,root;
mt19937 myrand(time(0));
unsigned int seed=0;
inline int read(){
int num=0,f=1;
char ch=getchar();
while(!(ch>='0'&&ch<='9')){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
num=num*10+ch-'0';
ch=getchar();
}
return num*f;
}
unsigned int rd(){
return seed=(seed*(unsigned int)3450934+(unsigned int)484524);
}
void pushdown(int x){
if(!tree[x].rev) return;
int nw_ls=0,nw_rs=0;
if(ls(x)) tot++,nw_rs=tot,tree[tot]=tree[ls(x)];
if(rs(x)) tot++,nw_ls=tot,tree[tot]=tree[rs(x)];
ls(x)=nw_ls,rs(x)=nw_rs;
tree[ls(x)].rev^=1,tree[rs(x)].rev^=1;
tree[x].rev=0;
return;
}
void pushup(int x){
tree[x].siz=tree[ls(x)].siz+tree[rs(x)].siz+1;
}
void merge(int &rt,int x,int y){
if(!x||!y){
rt=x+y;
return;
}
if(tree[x].siz>tree[y].siz){
pushdown(x);
tot++,rt=tot;
tree[rt]=tree[x];
merge(rs(rt),rs(x),y);
pushup(rt),pushup(x);
}
else{
pushdown(y);
tot++,rt=tot;
tree[rt]=tree[y];
merge(ls(rt),x,ls(y));
pushup(rt),pushup(y);
}
}
void split(int rt,int &x,int &y,int siz){
if(rt==0){
x=y=0;
return;
}
pushdown(rt);
if(tree[ls(rt)].siz<siz){
tot++,x=tot;
tree[x]=tree[rt];
split(rs(rt),rs(x),y,siz-tree[ls(rt)].siz-1);
pushup(rt),pushup(x);
}
else{
tot++,y=tot;
tree[y]=tree[rt];
split(ls(rt),x,ls(y),siz);
pushup(rt),pushup(y);
}
}
int add(char ch){
tot++,ls(tot)=rs(tot)=0;
tree[tot].c=ch,tree[tot].rev=0,tree[tot].siz=1,tree[tot].id=rd();
return tot;
}
char query(int rt,int x){
pushdown(rt);
if(tree[ls(rt)].siz+1==x) return tree[rt].c;
if(tree[ls(rt)].siz>=x) return query(ls(rt),x);
else return query(rs(rt),x-tree[ls(rt)].siz-1);
}
int main(){
freopen("editor.in","r",stdin);
freopen("editor.out","w",stdout);
n=read();
while(n--){
char ch=getchar();
while(ch!='I'&&ch!='D'&&ch!='C'&&ch!='R'&&ch!='Q') ch=getchar();
if(ch=='I'){
int x=read();
char c=getchar();
while(!(c>='a'&&c<='z')) c=getchar();
int m1=0,m2=add(c);
split(root,root,m1,x);
merge(root,root,m2);
merge(root,root,m1);
}
if(ch=='D'){
int l=read(),r=read();
int m1=0,m2=0;
split(root,root,m2,r);
split(root,root,m1,l-1);
merge(root,root,m2);
}
if(ch=='C'){
int l=read(),r=read(),x=read();
int pos=0,m1=0,m2=0;
split(root,root,m2,r);
split(root,root,m1,l-1);
pos=m1;
merge(root,root,m1);
merge(root,root,m2);
split(root,root,m1,x);
merge(root,root,pos);
merge(root,root,m1);
}
if(ch=='R'){
int l=read(),r=read();
int m1=0,m2=0;
split(root,root,m2,r);
split(root,root,m1,l-1);
tree[m1].rev^=1;
merge(root,root,m1);
merge(root,root,m2);
}
if(ch=='Q'){
int x=read();
printf("%c",query(root,x));
}
}
}
此处是时间复杂度更优的代码。(所以为什么啊)
完结撒花 Q W Q !!!