平衡树学习笔记
平衡树学习笔记
一,二叉查找树(BST)
首先,二叉查找树是一颗二叉树,每个节点有一个关键码,它满足如下性质
- 一个节点的关键码不小于它的左子树的关键码。
- 一个节点的关键码不大于他的右子树的关键码。
因此,一个二叉查找树的中序遍历就是一个单调非递增的序列。
在一个二叉查找树中,一个关键码
(1)建立:
我们定义一个结构体,其中
为了方便起见,我们在一开始在BST中构建两个点,分别为
const int N=1e5+5; const int INF=0x3f3f3f3f3f3f3f3f; struct BST{ int l,r,val; }a[N]; int tot,root; int New(int val){ a[++tot].val=val; return tot; } void build(){ root=New(-INF); a[root].r=New(INF); }
(2)检索:
用于查找BST中是否有关键码为
bool get(int k,int x){ if(!k) return 0; if(a[k].val==x) return 1; return a[k].val<x?get(a[k].r,x):get(a[k].l,x); }
(3)插入:
用于在BST中插入一个关键码为
void insert(int &k,int x){ if(!k){ k=New(x); return; } if(x<a[k].val) insert(a[k].l,x); else insert(a[k].r,x); }
(4)删除:
用于在BST中删除一个关键码为
首先,我们找到关键码为
若那个点子树数量小于2,删除点
若那个点子树数量为2,我们可以找到它的后继节点,设其为
由于
void remove(int &k,int x){ if(!k) return; if(a[k].val==x){ if(!a[k].l) k=a[k].r; else if(!a[k].r) k=a[k].l; else{ int nxt=a[k].r; while(a[nxt].l) nxt=a[nxt].l; remove(a[k].r,a[nxt].val); a[nxt].r=a[k].r,a[nxt].l=a[k].l; k=nxt; } } else if(a[k].val>x) remove(a[k].l,x); else remove(a[k].r,x); }
(5)例题(二叉查找树-FZUOJ):
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+5; const int INF=0x3f3f3f3f3f3f3f3f; struct BST{ int l,r,val; }a[N]; int tot,root; int New(int val){ a[++tot].val=val; return tot; } void build(){ root=New(-INF); a[root].r=New(INF); } bool get(int k,int x){ if(!k) return 0; if(a[k].val==x) return 1; return a[k].val<x?get(a[k].r,x):get(a[k].l,x); } void insert(int &k,int x){ if(!k){ k=New(x); return; } if(x<a[k].val) insert(a[k].l,x); else insert(a[k].r,x); } void remove(int &k,int x){ if(!k) return; if(a[k].val==x){ if(!a[k].l) k=a[k].r; else if(!a[k].r) k=a[k].l; else{ int nxt=a[k].r; while(a[nxt].l) nxt=a[nxt].l; remove(a[k].r,a[nxt].val); a[nxt].r=a[k].r,a[nxt].l=a[k].l; k=nxt; } } else if(a[k].val>x) remove(a[k].l,x); else remove(a[k].r,x); } int n; signed main(){ cin>>n; build(); char opt;int x; while(n--){ cin>>opt>>x; if(opt=='D'){//删除 if(!get(root,x))puts("not exist"); else{ puts("delete success"); remove(root,x); } } else{//插入 if(get(root,x))puts("has been"); else{ puts("insert success"); insert(root,x); } } } return 0; }
二,FHQ-Treap(无旋Treap)
参考文章:FHQ-Treap学习笔记 - 洛谷专栏
首先,Treap是同时满足二叉查找树和堆(以下默认为小根堆)的性质的,所以Treap的结构是唯一的,并且对于一棵Treap中的所有点及其子树都满足上述性质,那么Treap的子树仍然是Treap,一棵Treap中的某一个点及其子树组成的树仍是Treap。
一个数的排名表示在Treap按照中序遍历变为的单调非递增序列中,这个数是第几个。(相当于若中序遍历的话,它是第几个遍历到的)。
(1)建立:
我们定义一个结构体,其中
若是按照排名分裂,还需要一个
const int N=1e5+5; const int INF=0x3f3f3f3f3f3f3f3f; struct FHQ_Treap{ int l,r,val,rnd,siz; //int fa; }a[N]; int tot,root; int New(int val){ a[++tot].val=val; a[tot].siz=1; a[tot].rnd=rand()*rand(); return tot; }
(2)对Treap进行分裂、合并:
通过对treap进行分裂、合并操作,我们可以进行插入,求前驱,求后继...操作。
分裂:
我们可以把一颗Treap按照
void split(int val,int k,int &x,int &y){//按照val来分,原来的treap为k,分裂后的分别为x,y if(!k){ x=y=0; return;//到了空节点 } if(a[k].val<=val){//k及其左儿子都应该被分裂入x。 x=k; split(val,a[k].r,a[k].r,y);//由于k的右儿子的val一定不小于a[k].val,所以x直接开始更新右儿子。 push_up(x); } else{//与上面同理 y=k; split(val,a[k].l,x,a[k].l); push_up(y); } } //主函数内: int k,x,y,root; //do something ... split(k,root,x,y);
若我们想将区间
//主函数内: int k,x,y,root,ql,qr; //do something ... split(qr,root,x,y);//将[l,r]分为[l,qr],[qr,r]; split(ql-1,x,x,y);//将[l,qr]分为[l,ql-1],[ql,qr]
除此之外,我们还可以按照排名进行分裂,道理同上,代码如下:
void split(int now,int k,int &x,int &y){//将now按照排名k分为x,y两颗 if(!now){ x=y=0; return; } if(a[a[now].l].siz>=k){//排名为k的在左子树,则x不变,y继承now及其右子树 y=now; split(a[now].l,k,x,a[y].l); } else{//排名为k的在右子树,x继承now以及左子树,y不变 x=now; split(a[now].r,k-a[a[k].l].siz-1,a[x].r,y); } push_up(now); }
合并:
int merge(int x,int y){//将x,y两棵合并为一颗,返回下标(满足x的val的最大值<=y的val的最小值) if(!x||!y) return x|y; if(a[x].rnd<a[y].rnd){//满足其堆的性质,此时x为根 a[x].r=merge(a[x].r,y);//由于那个条件,y只可能在x的右子树。 push_up(x);//记得上传 return x; } else{//此时y为根 a[y].l=merge(x,a[y].l);//同理 push_up(y); return y; } } //主函数内: int x,y,root; //do something ... root=merge(x,y);
(3)通过分裂与合并实现某些操作:
插入:
插入一个
//主函数内: split(val,root,x,y); root=merge(merge(x,New(val)),y);
删除:
删除一个
//主函数内: split(val,root,x,y); split(val-1,x,x,z); //z所在的子树内的val都为val z=merge(a[z].l,a[z].r);//删除了这个根节点 root=merge(merge(x,z),y);
删除所有
//主函数内: split(val,root,x,y); split(val-1,x,x,z); root=merge(x,y);
查询排名:
若是按照权值分裂的,查找权值为
我们直接将treap分裂为两颗,分别代表
int find(int val){ split(val-1,root,x,y); int ans=a[x].siz+1; root=merge(x,y); return ans; }
若是按照排名分裂的,查找下标为
我们从节点
若
否则,在他前面有左儿子和
int find(int now){ int ans=a[a[now].l].siz+1;//注意+1 while(now!=root){ if(now==a[a[now].fa].r) ans+=a[a[now].fa]].siz-a[now].siz; now=a[now].fa; } return ans; }
查找排名为
int kth(int x,int k){//返回下标 while(1){ if(a[a[x].l].siz>=k) x=a[x].l;//左子树内 else if(a[a[x].l].siz+1==k) return x;//找到了 else k-=a[a[x].l].size+1,x=a[x].r;//右子树内,注意:先减后走 } }
查找前驱:
int find_pre(int val){ split(val-1,root,x,y); int ans=a[kth(x,a[x].siz)].val; root=merge(x,y); return ans; }
查询后继:
int find_nxt(int val){ split(val,root,x,y); int ans=a[kth(y,1)].val; root=merge(x,y); return ans; }
(4)下传与上传:
类似于线段树,我们的FHQ-Treap可以对标记或维护的信息进行上传、下传的操作。
上传:
对每个节点的左右儿子维护的信息(如大小)进行上传,同时也可下传父节点这样的信息:
void push_up(int k){ a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1; a[k].sum=a[k].val+a[a[k].l].sum+a[a[k].r].sum; if(a[k].l) a[a[k].l].fa=k; if(a[k].r) a[a[k].r].fa=k; }
下传:
如同线段树一样,对于区间操作,是不能一个个进行修改操作的,我们需要懒标记来记录一个区间操作的暂存信息。
每次的询问的操作若对 FHQ-Treap 的结构(维护翻转区间时),维护的信息(区间修改)有要求时,都要先下传。
需要注意的是,同线段树不同的是,FHQ-Treap的区间翻转的懒惰标记不仅代表其子节点是否需要翻转,也代表他自己是否需要。
(也就是说在打区间翻转的懒惰标记后不需要给这个节点的左右儿子翻转)。
类似于线段树,我们在向下递归前把会在这一层发生变化的节点的懒惰标记下传。
void push_down(int k){ if(!a[k].tag) return; if(a[k].l) a[a[k].l].tag^=1; if(a[k].r) a[a[k].r].tag^=1; swap(a[k].l,a[k].r); a[k].tag=0; }
特别的,若我们是排名分裂,在进行查找给定点的排名操作时,若下放会改变子树大小或左右儿子位置(如区间翻转),需要先从给定点回溯到根,再从根逐层下传标记
void down(int k){//依次下放 if(a[k].fa) down(a[k].fa); push_down(k); }
(5)可持久化:
FHQ-Treap的可持久化类似于线段树的,在合并和分裂的时候,我们在向下递归前,对在这一层信息改变的点建立新节点,没有改变的点维持原状,将新点连向不需要更改的点即可,具体代码如下:
int New(int k){ a[++tot]=a[k]; return tot; } void split(int now,int k,int &x,int &y){ if(!now){ x=y=0; return; } if(a[now].val<=k){ x=New(now); split(a[now].r,k,a[x].r,y); push_up(x); } else{ y=New(now); split(a[now].l,k,x,a[y].l); push_up(y); } } int merge(int x,int y){ if(!x||!y) return x+y; if(a[x].rnd<a[y].rnd){ x=New(x); a[x].r=merge(a[x].r,y); push_up(x); return x; } else{ y=New(y); a[y].l=merge(x,a[y].l); push_up(y); return y; } }
不过,若有标记下传,我们在下传前一定要对左右儿子新建一个节点,不然有可能会影响到以前的版本。(若当前的儿子和以前的共用,而这个标记是在当前版本打上的)。
注意不要对0号节点进行新建, 否则可能导致其他操作一直递归。
具体代码如下:
void push_down(int k){ if(!a[k].tag) return; a[k].tag=0; if(a[k].l){ a[k].l=New(a[k].l);a[a[k].l].tag^=1; } if(a[k].r){ a[k].r=New(a[k].r);a[a[k].r].tag^=1; } swap(a[k].l,a[k].r); }
空间复杂度:
我们每次调用
另外注意,每次调用
所以,我们的空间一定要开大一点,不要抵着开!!
(6)例题:
1,【模板】普通平衡树 - 洛谷(权值分裂模板):
直接FHQ-Treap进行一系列操作即可。
#include<bits/stdc++.h> #define int long long using namespace std; const int N=1e5+5; struct FHQ_Treap{ int l,r,val,rnd,siz; }a[N]; int tot,n; int New(int val){ a[++tot].val=val; a[tot].siz=1; a[tot].rnd=rand()*rand(); return tot; } void push_up(int k){ a[k].siz=1+a[a[k].l].siz+a[a[k].r].siz; } void split(int val,int k,int &x,int &y){//按照val来分,原来的treap为k,分裂后的分别为x,y if(!k){ x=y=0; return;//到了空节点 } if(a[k].val<=val){//k及其左儿子都应该被分裂入x。 x=k; split(val,a[k].r,a[k].r,y);//由于k的右儿子的val一定不小于a[k].val,所以x直接开始更新右儿子。 push_up(x); } else{//与上面同理 y=k; split(val,a[k].l,x,a[k].l); push_up(y); } } int merge(int x,int y){//将x,y两棵合并为一颗,返回下标(满足x的val的最大值<=y的val的最小值) if(!x||!y) return x|y; if(a[x].rnd<a[y].rnd){//满足其堆的性质,此时x为根 a[x].r=merge(a[x].r,y);//由于那个条件,y只可能在x的右子树。 push_up(x);//记得上传 return x; } else{//此时y为根 a[y].l=merge(x,a[y].l);//同理 push_up(y); return y; } } int kth(int x,int k){//返回下标 while(1){ if(a[a[x].l].siz>=k) x=a[x].l;//左子树内 else if(a[a[x].l].siz+1==k) return x;//找到了 else k-=a[a[x].l].siz+1,x=a[x].r;//右子树内 } } int x,y,z,k,root; int find_pre(int val){ split(val-1,root,x,y); int ans=a[kth(x,a[x].siz)].val; root=merge(x,y); return ans; } int find_nxt(int val){ split(val,root,x,y); int ans=a[kth(y,1)].val; root=merge(x,y); return ans; } int find(int val){ split(val-1,root,x,y); int ans=a[x].siz+1; root=merge(x,y); return ans; } signed main(){ scanf("%lld",&n); int opt,x,val; while(n--){ scanf("%lld%lld",&opt,&val); if(opt==1){ split(val,root,x,y); root=merge(merge(x,New(val)),y); } if(opt==2){ split(val,root,x,y); split(val-1,x,x,z); //z所在的子树内的val都为val z=merge(a[z].l,a[z].r);//删除了这个根节点 root=merge(merge(x,z),y); } if(opt==3){ printf("%lld\n",find(val)); } if(opt==4){ printf("%lld\n",a[kth(root,val)].val); } if(opt==5){ printf("%lld\n",find_pre(val)); } if(opt==6){ printf("%lld\n",find_nxt(val)); } } return 0; }
2, [ZJOI2006] 书架 - 洛谷(排名分裂模板):
需要按照排名进行操作。
操作一:
设节点的排名为
我们可以将Treap分为排名在
合并时再按照
操作二:
同操作一,合并时为
操作三:
先询问权值为
若
若
若
操作四:
询问权值为
操作五:
询问排名为
代码小细节:我们可以用一个
代码:
#include<bits/stdc++.h> using namespace std; inline int rd(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } const int N=8e4+5; int n,m; int id[N]; int root,rt1,rt2,rt3,rt4,k; struct FHQ_Treap{ int val,rnd,l,r,siz,fa; }a[N]; int tot; int New(int val){ a[++tot]=(FHQ_Treap){val,rand()*rand(),0,0,1,0}; id[val]=tot; return tot; } void push_up(int k){ if(a[k].l) a[a[k].l].fa=k; if(a[k].r) a[a[k].r].fa=k; a[k].siz=1+a[a[k].l].siz+a[a[k].r].siz; } void split(int now,int k,int &x,int &y){ if(!now){ x=y=0; return; } if(a[a[now].l].siz>=k){ y=now; split(a[now].l,k,x,a[y].l); } else{ x=now; split(a[now].r,k-a[a[now].l].siz-1,a[x].r,y); } push_up(now); } int merge(int x,int y){ if(!x||!y) return x|y; if(a[x].rnd<a[y].rnd){ a[x].r=merge(a[x].r,y); push_up(x); return x; } else{ a[y].l=merge(x,a[y].l); push_up(y); return y; } } int find(int now){//查找节点now的排名 int ans=a[a[now].l].siz+1; while(now!=root){ if(now==a[a[now].fa].r) ans+=a[a[now].fa].siz-a[now].siz; now=a[now].fa; } return ans; } int kth(int x,int k){//查找排名为k的节点 while(1){ if(a[a[x].l].siz>=k) x=a[x].l; else if(a[a[x].l].siz+1==k) return x; else k-=a[a[x].l].siz+1,x=a[x].r; } } int main(){ srand(time(0)); n=rd(),m=rd(); string ch;int s,t; for(int i=1;i<=n;i++){ s=rd(); root=merge(root,New(s)); } while(m--){ cin>>ch;s=rd(); if(ch[0]=='T'){ k=find(id[s]); split(root,k,rt1,rt2); split(rt1,k-1,rt1,rt3); root=merge(merge(rt3,rt1),rt2); } if(ch[0]=='B'){ k=find(id[s]); split(root,k,rt1,rt2); split(rt1,k-1,rt1,rt3); root=merge(rt1,merge(rt2,rt3)); } if(ch[0]=='I'){ t=rd(); if(t==0) continue; k=find(id[s]); if(t==1){ split(root,k,rt1,rt2); split(rt1,k-1,rt1,rt3); split(rt2,1,rt2,rt4); root=merge(merge(rt1,rt2),merge(rt3,rt4)); } else{ split(root,k-1,rt1,rt2); split(rt1,k-2,rt1,rt3); split(rt2,1,rt2,rt4); root=merge(merge(rt1,rt2),merge(rt3,rt4)); } } if(ch[0]=='A'){ k=find(id[s]); printf("%d\n",k-1); } if(ch[0]=='Q'){ printf("%d\n",a[kth(root,s)].val); } } return 0; }
3,银河英雄传说V2 - 洛谷(排名分裂,查询区间和)
操作一:合并
操作二:查询
操作三:先分裂,然后求Treap的和,然后再合并。
需要注意的是,我们在进行了操作二时,分裂后不会再次合并,这就需要清空分裂后两个
我们可以将
#include<bits/stdc++.h> #define ll long long using namespace std; inline int rd(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } const int N=2e5+5; int n,m; struct FHQ_Treap{ int l,r,fa,siz,rnd; ll sum,val; }a[N]; int tot; int New(ll val){ a[++tot]={0,0,0,1,rand()*rand(),val,val}; return tot; } int get_rt(int x){ while(a[x].fa) x=a[x].fa; return x; } int find(int x){//查询下标为x的节点的排名 int ans=a[a[x].l].siz+1; while(a[x].fa){ if(a[a[x].fa].r==x) ans+=a[a[x].fa].siz-a[x].siz; x=a[x].fa; } return ans; } void push_up(int k){ a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1; a[k].sum=a[k].val+a[a[k].l].sum+a[a[k].r].sum; if(a[k].l) a[a[k].l].fa=k; if(a[k].r) a[a[k].r].fa=k; } void split(int now,int k,int &x,int &y){ if(!now){ x=y=0; return; } if(a[a[now].l].siz>=k){ y=now;split(a[now].l,k,x,a[y].l); } else{ x=now;split(a[now].r,k-1-a[a[now].l].siz,a[x].r,y); } push_up(now); } int merge(int x,int y){ if(!x||!y) return x|y; if(a[x].rnd<a[y].rnd){//x为根 a[x].r=merge(a[x].r,y); push_up(x); return x; } else{//y为根 a[y].l=merge(x,a[y].l); push_up(y); return y; } } int main(){ int x,y,k,kk,rt,rt1,rt2,rt3;ll val; srand(time(0)); n=rd(),m=rd(); for(int i=1;i<=n;i++){ val=rd(); New(val); } char opt; while(m--){ cin>>opt; if(opt=='M'){ x=rd(),y=rd(); x=get_rt(x),y=get_rt(y); if(x==y) continue; merge(y,x); } else if(opt=='D'){ x=rd(); k=find(x);rt=get_rt(x); split(rt,k-1,rt,x); a[rt].fa=a[x].fa=0; } else{ x=rd(),y=rd(); if(get_rt(x)!=get_rt(y))puts("-1"); else{ rt=get_rt(x); k=find(x);kk=find(y); if(k>kk) swap(k,kk); split(rt,kk,rt1,rt3); split(rt1,k-1,rt1,rt2); printf("%lld\n",a[rt2].sum); merge(rt1,merge(rt2,rt3)); } } } return 0; }
4,【模板】文艺平衡树 - 洛谷:
我们维护一个懒惰标记, 表示这个子树需要被反转。
然后在
注意,
#include<bits/stdc++.h> #define ll long long using namespace std; inline int rd(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } const int N=1e5+5; struct FHQ_Treap{ int l,r,val,rnd,tag,siz; }a[N]; int tot; int New(int val){ a[++tot].val=val;a[tot].rnd=rand();a[tot].siz=1; return tot; } void push_down(int k){ if(!a[k].tag) return; if(a[k].l) a[a[k].l].tag^=1; if(a[k].r) a[a[k].r].tag^=1; swap(a[k].l,a[k].r); a[k].tag=0; } void push_up(int k){ a[k].siz=1+a[a[k].l].siz+a[a[k].r].siz; } void split(int now,int k,int &x,int &y){ if(!now){ x=y=0; return; } push_down(now); //在这里下传, if(a[a[now].l].siz>=k){ y=now; split(a[now].l,k,x,a[y].l); } else{ x=now; split(a[now].r,k-a[a[now].l].siz-1,a[x].r,y); } push_up(now); } int merge(int x,int y){ if(!x||!y) return x|y; if(a[x].rnd<a[y].rnd){ push_down(x); a[x].r=merge(a[x].r,y); push_up(x); return x; } else{ push_down(y); a[y].l=merge(x,a[y].l); push_up(y); return y; } } int root; int n,m; void print(int now){ if(!now) return; push_down(now); print(a[now].l); printf("%d ",a[now].val); print(a[now].r); } int main(){ srand(time(0)); n=rd(),m=rd(); for(int i=1;i<=n;i++){ root=merge(root,New(i)); } int l,r,x,y,z; while(m--){ l=rd();r=rd(); split(root,r,x,z);split(x,l-1,x,y); a[y].tag^=1; root=merge(merge(x,y),z); } print(root); return 0; }
5,[NOI2004] 郁闷的出纳员 - 洛谷(懒惰标签维护区间修改)
按照权值分裂。
操作一:新建一个节点。
操作二/三:整体修改,然后将工资低于工资下界的分裂出去,并统计其数量。
操作四:排名为
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1e5+5; int n,Min; struct FHQ_Treap{ int l,r,val,lazy,siz,rnd; }a[N]; int tot; int New(int val){ a[++tot].val=val;a[tot].siz=1;a[tot].rnd=rand(); return tot; } void dosth(int now,int x){ a[x].lazy+=a[now].lazy,a[x].val+=a[now].lazy; } void push_down(int k){ if(!a[k].lazy) return; if(a[k].l) dosth(k,a[k].l); if(a[k].r) dosth(k,a[k].r); a[k].lazy=0; } void push_up(int k){ a[k].siz=1+a[a[k].l].siz+a[a[k].r].siz; } void split(int now,int k,int &x,int &y){ if(!now){ x=y=0; return; } push_down(now); if(a[now].val<=k){ x=now; split(a[now].r,k,a[x].r,y); } else{ y=now; split(a[now].l,k,x,a[y].l); } push_up(now); } int merge(int x,int y){ if(!x||!y) return x+y; if(a[x].rnd<a[y].rnd){ push_down(x); a[x].r=merge(a[x].r,y); push_up(x);return x; } else{ push_down(y); a[y].l=merge(x,a[y].l); push_up(y);return y; } } int find(int now,int k){ while(1){ push_down(now); if(a[a[now].l].siz>=k) now=a[now].l; else if(a[a[now].l].siz+1==k) return now; else k-=1+a[a[now].l].siz,now=a[now].r; } } int main(){ srand(time(0)); char opt;int k; int root,x,y; cin>>n>>Min; int ans=0; while(n--){ cin>>opt>>k; if(opt=='I'){ if(k<Min) continue; split(root,k-1,x,y); root=merge(merge(x,New(k)),y); } else if(opt=='F'){ if(a[root].siz<k) puts("-1"); else printf("%d\n",a[find(root,a[root].siz-k+1)].val); } else{ if(opt=='S') k*=-1; a[root].val+=k,a[root].lazy+=k; if(opt=='S'){ split(root,Min-1,x,root); ans+=a[x].siz; } } } printf("%d\n",ans); return 0; }
6,贫穷 - 洛谷(查询前先跳到根节点从上往下下放懒惰标记)
按照排名来分裂 。
Treap 维护左儿子下标,右儿子下标,这个节点代表的字母,Treap 的随机值,该节点为根的树的大小,该节点为根的树内节点包含的字母的状压值,区间翻转的懒惰标记,该节点的父亲(没有的话为空),这个点是否被删除。
操作一:
分裂成两颗,然后和新节点合并。
操作二:
分裂成三颗,把左右两颗合并,记得给被删除的节点打上标记,后面有用。
操作三:
区间翻转,分裂成三颗,然后给对应的打上标记即可。
操作四:
首先,根据标记判断那个节点有没有被删除。
若没有被删除,就是找到下标为
注意,由于有区间翻转操作,需要先从
void down(int k){//依次下放 if(a[k].fa) down(a[k].fa); push_down(k); } int find(int x){//节点x的排名 down(x);//因为和结构有关,所以要先下传标记. int ans=a[a[x].l].siz+1; while(a[x].fa){ if(a[a[x].fa].r==x) ans+=a[a[x].fa].siz-a[x].siz; x=a[x].fa; } return ans; }
操作五:
查找第
int kth(int x,int k){ while(1){ push_down(x);//下传标记 if(a[a[x].l].siz>=k) x=a[x].l; else if(a[a[x].l].siz+1==k) return x; else k-=1+a[a[x].l].siz,x=a[x].r; } }
操作六:
先分裂出来对应的区间。
查找其根节点的状压变量二进制下一的个数即可。
代码:
#include<bits/stdc++.h> #define ll long long using namespace std; const int N=1e5+5; struct node{ int l,r,val,rnd,siz,cnt,tag,fa; bool flag; }a[N<<1]; int tot; int New(int id){ a[++tot].rnd=rand()*rand(); a[tot].siz=1; a[tot].val=id; a[tot].cnt=(1<<id); return tot; } void push_up(int k){ if(a[k].l) a[a[k].l].fa=k; if(a[k].r) a[a[k].r].fa=k; a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1; a[k].cnt=a[a[k].l].cnt|a[a[k].r].cnt|(1<<a[k].val); } void push_down(int k){ if(!a[k].tag) return; if(a[k].l) a[a[k].l].tag^=1; if(a[k].r) a[a[k].r].tag^=1; swap(a[k].l,a[k].r); a[k].tag=0; } void down(int k){//依次下放 if(a[k].fa) down(a[k].fa); push_down(k); } void split(int now,int k,int &x,int &y){ if(!now){ x=y=0; return; } push_down(now); if(a[a[now].l].siz>=k){ y=now;split(a[now].l,k,x,a[y].l); } else{ x=now;split(a[now].r,k-1-a[a[now].l].siz,a[x].r,y); } push_up(now); } int merge(int x,int y){ if(!x||!y) return x+y; if(a[x].rnd<a[y].rnd){ push_down(x);a[x].r=merge(a[x].r,y); push_up(x);return x; } else{ push_down(y);a[y].l=merge(x,a[y].l); push_up(y);return y; } } int find(int x){//节点x的排名 down(x);//因为和结构有关,所以要先下传标记. int ans=a[a[x].l].siz+1; while(a[x].fa){ if(a[a[x].fa].r==x) ans+=a[a[x].fa].siz-a[x].siz; x=a[x].fa; } return ans; } int kth(int x,int k){ while(1){ push_down(x); if(a[a[x].l].siz>=k) x=a[x].l; else if(a[a[x].l].siz+1==k) return x; else k-=1+a[a[x].l].siz,x=a[x].r; } } string s; int n,m,root,rt1,rt2,rt3,rt4; int main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); srand(time(0)); cin>>n>>m>>s;s=' '+s; for(int i=1;i<=n;i++){ root=merge(root,New(s[i]-'a')); } int x,y; char opt,c; while(m--){ cin>>opt; if(opt=='I'){ cin>>x>>c; split(root,x,rt1,rt2); root=merge(merge(rt1,New(c-'a')),rt2); } if(opt=='D'){ cin>>x; split(root,x,rt1,rt2); split(rt1,x-1,rt1,rt3); a[rt3].flag=1; root=merge(rt1,rt2); } if(opt=='R'){ cin>>x>>y; split(root,y,rt1,rt3); split(rt1,x-1,rt1,rt2); a[rt2].tag^=1; root=merge(merge(rt1,rt2),rt3); } if(opt=='P'){ cin>>x; if(a[x].flag) cout<<"0\n"; else cout<<find(x)<<"\n"; } if(opt=='T'){ cin>>x; cout<<(char)(a[kth(root,x)].val+'a')<<"\n"; } if(opt=='Q'){ cin>>x>>y; split(root,y,rt1,rt3); split(rt1,x-1,rt1,rt2); cout<<__builtin_popcount(a[rt2].cnt)<<"\n"; root=merge(merge(rt1,rt2),rt3); } } return 0; }
7,【模板】可持久化平衡树 - 洛谷
模板,直接操作即可。
注意开大空间。
#include<bits/stdc++.h> #define int long long using namespace std; int rd(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } const int N=5e5+5; int n; struct node{ int l,r,siz,val,rnd; }a[N*50]; int tot,root[N]; void push_up(int k){ a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1; } int build(int val){ a[++tot].siz=1;a[tot].val=val;a[tot].rnd=rand()*rand(); return tot; } int New(int k){ a[++tot]=a[k]; return tot; } void split(int now,int k,int &x,int &y){ if(!now){ x=y=0; return; } if(a[now].val<=k){ x=New(now); split(a[now].r,k,a[x].r,y); push_up(x); } else{ y=New(now); split(a[now].l,k,x,a[y].l); push_up(y); } } int merge(int x,int y){ if(!x||!y) return x+y; if(a[x].rnd<a[y].rnd){ x=New(x); a[x].r=merge(a[x].r,y); push_up(x); return x; } else{ y=New(y); a[y].l=merge(x,a[y].l); push_up(y); return y; } } int kth(int now,int k){ while(1){ if(a[a[now].l].siz>=k) now=a[now].l; else if(a[a[now].l].siz+1==k) return now; else k-=1+a[a[now].l].siz,now=a[now].r; } } int rt1,rt2,rt3; signed main(){ // freopen("P3835_3.in","r",stdin); srand(time(0)); n=rd(); int v,opt,x; for(int i=1;i<=n;i++){ v=rd(),opt=rd(),x=rd(); root[i]=root[v]; if(opt==1){ split(root[i],x,rt1,rt2); root[i]=merge(merge(rt1,build(x)),rt2); } else if(opt==2){ split(root[i],x,rt1,rt3); split(rt1,x-1,rt1,rt2); rt2=merge(a[rt2].l,a[rt2].r); root[i]=merge(merge(rt1,rt2),rt3); } else if(opt==3){ split(root[i],x-1,rt1,rt2); printf("%lld\n",a[rt1].siz+1); root[i]=merge(rt1,rt2); } else if(opt==4){ printf("%lld\n",a[kth(root[i],x)].val); } else if(opt==5){ split(root[i],x-1,rt1,rt2); if(!rt1)printf("%lld\n",-(1ll<<31)+1); else printf("%lld\n",a[kth(rt1,a[rt1].siz)].val); root[i]=merge(rt1,rt2); } else{ split(root[i],x,rt1,rt2); if(!rt2)printf("%lld\n",(1ll<<31)-1); else printf("%lld\n",a[kth(rt2,1)].val); root[i]=merge(rt1,rt2); } } return 0; }
8,【模板】可持久化文艺平衡树- 洛谷
模板,直接操作即可。
注意开大空间,以及标记下传中的细节。
#include<bits/stdc++.h> #define int long long using namespace std; int rd(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-'){f=-1;}ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } const int N=2e5+5; int n; struct node{ int l,r,siz,rnd,val,tag,sum; }a[N*100]; int tot,root[N]; int build(int val){ a[++tot].val=val; a[tot].sum=val; a[tot].siz=1; a[tot].rnd=rand(); return tot; } int New(int x){ a[++tot]=a[x]; return tot; } void push_down(int k){ if(!a[k].tag) return; a[k].tag=0; if(a[k].l){ a[k].l=New(a[k].l); a[a[k].l].tag^=1; } if(a[k].r){ a[k].r=New(a[k].r); a[a[k].r].tag^=1; } swap(a[k].l,a[k].r); } void push_up(int k){ a[k].sum=a[a[k].l].sum+a[a[k].r].sum+a[k].val; a[k].siz=a[a[k].l].siz+a[a[k].r].siz+1; } void split(int now,int k,int &x,int &y){ if(!now){ x=y=0; return; } push_down(now); if(a[a[now].l].siz>=k){//不在右子树内 y=New(now); split(a[now].l,k,x,a[y].l); push_up(y); } else{ x=New(now); split(a[now].r,k-a[a[now].l].siz-1,a[x].r,y); push_up(x); } } int merge(int x,int y){ if(!x||!y)return x+y; if(a[x].rnd<a[y].rnd){ x=New(x);push_down(x); a[x].r=merge(a[x].r,y); push_up(x);return x; } else{ y=New(y);push_down(y); a[y].l=merge(x,a[y].l); push_up(y);return y; } } int rt1,rt2,rt3; signed main(){ srand(time(0)); n=rd(); int ans=0; int v,opt,p,x,l,r; for(int i=1;i<=n;i++){ v=rd(),opt=rd(); root[i]=root[v]; if(opt==1){ p=rd()^ans,x=rd()^ans; split(root[i],p,rt1,rt2); root[i]=merge(merge(rt1,build(x)),rt2); } else if(opt==2){ p=rd()^ans; split(root[i],p-1,rt1,rt3); split(rt3,1,rt2,rt3); root[i]=merge(rt1,rt3); } else if(opt==3){ l=rd()^ans,r=rd()^ans; split(root[i],r,rt1,rt3); split(rt1,l-1,rt1,rt2); a[rt2].tag^=1; root[i]=merge(merge(rt1,rt2),rt3); } else{ l=rd()^ans,r=rd()^ans; split(root[i],r,rt1,rt3); split(rt1,l-1,rt1,rt2); ans=a[rt2].sum; root[i]=merge(merge(rt1,rt2),rt3); printf("%lld\n",ans); } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探