「平衡树」学习笔记
BST
又称二分查找树,
利用该性质,从而在
当每次插入的数据呈单调性时,其内部将形成一条链,从而使复杂度退化为
Treap
当数据随机时,其内部时趋于平衡的,
对于如下的几种基本操作单次复杂度均为
旋转
对于内一个点赋予其一个随机值,使这棵树是该随机值的二叉堆。
具体如何旋转操作在
旋转操作是使
旋转操作
void rotate(int &p,bool d) { int son=f.ch[d]; f.ch[d]=t[son].ch[d^1]; t[son].ch[d^1]=p; pushup(p); pushup(p=son); }
插入
找到其应插入的位置,若书中已存在该值则
插入操作
void insert(int &p,int x) { if(p==0) { p=++tot; f.cnt=f.size=1; f.val=x; f.dat=rand(); return ; } f.size++; if(f.val==x) { f.cnt++; return ; } bool d=f.val<x; insert(f.ch[d],x); if(f.dat>t[f.ch[d]].dat) rotate(p,d); }
删除
找到所要删除点所在位置,如果其
否则,如果该节点只有一个子节点,那么直接让子节点替换其位置即可;若该点为叶子节点,那么直接删掉它不会产生任何其他的影响。
因为
删除操作
void remove(int &p,int x) { if(p==0) return ; if(f.val==x) { if(f.cnt>1) { f.cnt--; f.size--; return ; } bool d=ls.dat>rs.dat; if(f.l==0||f.r==0) p=f.l+f.r; else rotate(p,d),remove(p,x); } else f.size--,remove(f.ch[f.val<x],x); }
查询排名
因为其满足
-
,即已经找到答案,返回 。 -
,继续向其左儿子方向寻找。 -
,继续向右儿子方向寻找,同时答案加上 。
在很多时候要查询的数并不在树里,但同时根据比他小的个数加一仍可以求出其排名,常见解决办法有两种,不会对复杂度产生较大影响。
-
在查询前先插入该点,查询后再将该点删除。
-
当寻找到一个不存在的点时,显然只有所查询数不在树中时才可能出现这种情况,此时令他返回
而不是 ,即比他小的个数 。
查询排名
int ask_rk(int p,int x) { if(p==0) return 1; if(f.val==x) return ls.size+1; if(f.val>x) return ask_rk(f.l,x); return ask_rk(f.r,x)+ls.size+f.cnt; }
查询第 小(大)值
以查询第
-
,继续向其左儿子方向寻找。 -
,该节点即为答案。 -
,另 减去 ,继续向其右儿子方向寻找。
查询第 k 小值
int ask_val(int p,int x) { if(p==0) return inf; if(ls.size>=x) return ask_val(f.l,x); if(ls.size+f.cnt>=x) return f.val; return ask_val(f.r,x-ls.size-f.cnt); }
查询前驱、后继
以前驱为例。
根据
查询前驱
int pre(int p,int x) { if(p==0) return -inf; if(f.val>=x) return pre(f.l,x); return max(pre(f.r,x),f.val); }
查询后继
int nxt(int p,int x) { if(p==0) return inf; if(f.val<=x) return nxt(f.r,x); return min(nxt(f.l,x),f.val); }
fhq_Treap
又名无旋
其分裂与合并分为按照权值合并与按照树的大小合并两种,当维护序列时按照大小分则更加方便,此处以按照权值分为例,另一种将在文艺平衡树中讲解。
分裂
将
-
,说明其左子树全部在 内,故 ,向左儿子方向继续分裂。 -
反之,其右子树全部在
内,则 ,继续向右儿子方向分裂。
分裂操作
void split(int p,int k,int &x,int &y) { if(!p) {x=y=0; return ;} if(f.val>k) y=p,split(f.l,k,x,t[y].l); else x=p,split(f.r,k,t[x].r,y); pushup(p); }
合并
通常是将被分开的两部分再合并起来,将
此时就可以按照其随机值合并,使其平衡。
合并操作
int merge(int x,int y) { if(!x||!y) return x+y; int p; if(t[x].dat>t[y].dat)t[x].r=merge(t[x].r,y),pushup(p=x); else t[y].l=merge(x,t[y].l),pushup(p=y); return p; }
插入
将
将
插入操作
void insert(int &p,int k) { t[++tot].val=k,t[tot].size=1,t[tot].dat=rand(); int x,y; split(p,k-1,x,y); p=merge(merge(x,tot),y); }
删除
将
依旧将
删除操作
void remove(int &p,int k) { int x,y,is; split(p,k-1,x,y),split(y,k,is,y); p=merge(merge(x,merge(t[is].l,t[is].r)),y); }
查询排名
查
将
查询排名
int ask_rk(int p,int k) { int x,y,is; split(p,k-1,x,y); is=t[x].size+1; p=merge(x,y); return is; }
其余操作与
优点
-
为所有平衡树中最好打,最容易理解的一种。
-
因为不需要旋转,所以可以可持久化。
-
允许有权值一样的不同节点,因为其是按照权值分裂的。
-
因为按照权值分裂,所以不在树中的数也可以直接查。
-
当其按照树的大小分裂时,可以实现维护序列,这是
做不到的,大多数 能做的他都能做。
splay
在每次操作后,将该点进行双旋转到根节点的位置,从而使复杂度达到均摊单次
哨兵节点
即添加
单旋操作
设
旋转后
其余不变。
单旋操作
void rotate(int x) { int y=t[x].ff,z=t[y].ff,k=fson(y,x); t[z].ch[fson(z,y)]=x; t[x].ff=z; t[y].ch[k]=t[x].ch[k^1]; t[t[x].ch[k^1]].ff=y; t[x].ch[k^1]=y; t[y].ff=x; pushup(y),pushup(x); }
splay 操作(双旋操作)
将点
定义
如图:
将
对于上面所说的情况,如果先旋转
从而达到平衡。
splay 操作
void splay(int x,int goal) { while(t[x].ff!=goal) { int y=t[x].ff,z=t[y].ff; if(z!=goal) fson(z,y)^fson(y,x)?rotate(x):rotate(y); rotate(x); } if(goal==0) root=x; }
另外为了方便,有一个
find 操作
void find(int x) { int p=root; if(!p) return ; while(f.ch[x>f.val]&&x!=f.val) p=f.ch[x>f.val]; splay(p,0); }
插入操作
找到离
插入操作
void insert(int x) { int p=root,ff=0; while(p&&f.val!=x) ff=p, p=f.ch[x>f.val]; if(p) {f.cnt++; splay(p,0); return ;} p=++tot; if(ff) t[ff].ch[x>t[ff].val]=p; f.ff=ff,f.val=x,f.size=f.cnt=1; splay(p,0); }
删除操作
删除操作需要先找到其前驱与后继对应的位置,将前驱转到根节点,再将后继转到前驱的右儿子位置,那么此时他们俩中间夹着的,即后继的左儿子,即为
删除操作
void remove(int x) { int pre=pre_nxt(x,0),nxt=pre_nxt(x,1); splay(pre,0),splay(nxt,pre); int p=t[nxt].l; if(f.cnt>1) f.cnt--,splay(p,0); else t[nxt].l=0; }
查询前驱、后继
以前驱为例。
先找到离
如果该点值不是
注意此处找到的是其前驱的位置,不是数值,为了方便删除操作。
查询前驱、后继
int pre_nxt(int x,bool d) { find(x); int p=root; if((f.val>x&&d)||(f.val<x&&!d)) return p; p=f.ch[d]; while(f.ch[d^1]) p=f.ch[d^1]; return p; }
查询排名
此时需要满足
先找到
由于添加了哨兵节点,实际还需要
查询排名
int ask_rk(int x) { find(x); int p=root; return ls.size; }
查询第 小(大)值
与
优点
因为其
例题
基本操作
前三道为最基本的查询前驱后继、第
luogu P2234 营业额统计
luogu P2286 宠物收养场
luogu P1486 郁闷的出纳员
luogu P3369 普通平衡树
Treap
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=1e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,tot,root; struct treap { int ch[2],cnt,size,val,dat; }t[N]; void pushup(int p) { f.size=ls.size+rs.size+f.cnt; } void rotate(int &p,bool d) { int son=f.ch[d]; f.ch[d]=t[son].ch[d^1]; t[son].ch[d^1]=p; pushup(p); pushup(p=son); } void insert(int &p,int x) { if(p==0) { p=++tot; f.cnt=f.size=1; f.val=x; f.dat=rand(); return ; } f.size++; if(f.val==x) { f.cnt++; return ; } bool d=f.val<x; insert(f.ch[d],x); if(f.dat>t[f.ch[d]].dat) rotate(p,d); } void remove(int &p,int x) { if(p==0) return ; if(f.val==x) { if(f.cnt>1) { f.cnt--; f.size--; return ; } bool d=ls.dat>rs.dat; if(f.l==0||f.r==0) p=f.l+f.r; else rotate(p,d),remove(p,x); } else f.size--,remove(f.ch[f.val<x],x); } int ask_rk(int p,int x) { if(p==0) return 1; if(f.val==x) return ls.size+1; if(f.val>x) return ask_rk(f.l,x); return ask_rk(f.r,x)+ls.size+f.cnt; } int ask_val(int p,int x) { if(p==0) return inf; if(ls.size>=x) return ask_val(f.l,x); if(ls.size+f.cnt>=x) return f.val; return ask_val(f.r,x-ls.size-f.cnt); } int pre(int p,int x) { if(p==0) return -inf; if(f.val>=x) return pre(f.l,x); return max(pre(f.r,x),f.val); } int nxt(int p,int x) { if(p==0) return inf; if(f.val<=x) return nxt(f.r,x); return min(nxt(f.l,x),f.val); } signed main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif read(n); while(n--) { int op,x; read(op),read(x); switch(op) { case 1: insert(root,x); break; case 2: remove(root,x); break; case 3: write(ask_rk(root,x)),puts(""); break; case 4: write(ask_val(root,x)),puts(""); break; case 5: write(pre(root,x)),puts(""); break; case 6: write(nxt(root,x)),puts(""); break; } } }
fhq_Treap
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=1e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,root,tot; struct fhq_treap { int ch[2],val,dat,size; }t[N]; void pushup(int p) {f.size=ls.size+rs.size+1;} void split(int p,int k,int &x,int &y) { if(!p) {x=y=0; return ;} if(f.val>k) y=p,split(f.l,k,x,t[y].l); else x=p,split(f.r,k,t[x].r,y); pushup(p); } int merge(int x,int y) { if(!x||!y) return x+y; int p; if(t[x].dat>t[y].dat)t[x].r=merge(t[x].r,y),pushup(p=x); else t[y].l=merge(x,t[y].l),pushup(p=y); return p; } void insert(int &p,int k) { t[++tot].val=k,t[tot].size=1,t[tot].dat=rand(); int x,y; split(p,k-1,x,y); p=merge(merge(x,tot),y); } void remove(int &p,int k) { int x,y,is; split(p,k-1,x,y),split(y,k,is,y); p=merge(merge(x,merge(t[is].l,t[is].r)),y); } int ask_rk(int p,int k) { int x,y,is; split(p,k-1,x,y); is=t[x].size+1; p=merge(x,y); return is; } int ask_val(int p,int k) { if(!p) return inf; if(ls.size>=k) return ask_val(f.l,k); if(ls.size+1>=k) return f.val; return ask_val(f.r,k-ls.size-1); } int pre(int p,int k) { if(!p) return -inf; if(f.val>=k) return pre(f.l,k); return max(pre(f.r,k),f.val); } int nxt(int p,int k) { if(!p) return inf; if(f.val<=k) return nxt(f.r,k); return min(nxt(f.l,k),f.val); } signed main() { read(n); for(int i=1,op,x;i<=n;i++) { read(op),read(x); switch(op) { case 1: insert(root,x); break; case 2: remove(root,x); break; case 3: write(ask_rk(root,x)),puts(""); break; case 4: write(ask_val(root,x)),puts(""); break; case 5: write(pre(root,x)),puts(""); break; case 6: write(nxt(root,x)),puts(""); break; } } }
splay
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=1e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,root,tot; struct splay { int ch[2],ff,size,cnt,val; }t[N]; bool fson(int x,int y) {return (t[x].ch[1]==y);} void pushup(int p) {f.size=ls.size+rs.size+f.cnt;} void rotate(int x) { int y=t[x].ff,z=t[y].ff,k=fson(y,x); t[z].ch[fson(z,y)]=x; t[x].ff=z; t[y].ch[k]=t[x].ch[k^1]; t[t[x].ch[k^1]].ff=y; t[x].ch[k^1]=y; t[y].ff=x; pushup(y),pushup(x); } void splay(int x,int goal) { while(t[x].ff!=goal) { int y=t[x].ff,z=t[y].ff; if(z!=goal) fson(z,y)^fson(y,x)?rotate(x):rotate(y); rotate(x); } if(goal==0) root=x; } void find(int x) { int p=root; if(!p) return ; while(f.ch[x>f.val]&&x!=f.val) p=f.ch[x>f.val]; splay(p,0); } void insert(int x) { int p=root,ff=0; while(p&&f.val!=x) ff=p, p=f.ch[x>f.val]; if(p) {f.cnt++; splay(p,0); return ;} p=++tot; if(ff) t[ff].ch[x>t[ff].val]=p; f.ff=ff,f.val=x,f.size=f.cnt=1; splay(p,0); } int pre_nxt(int x,bool d) { find(x); int p=root; if((f.val>x&&d)||(f.val<x&&!d)) return p; p=f.ch[d]; while(f.ch[d^1]) p=f.ch[d^1]; return p; } void remove(int x) { int pre=pre_nxt(x,0),nxt=pre_nxt(x,1); splay(pre,0),splay(nxt,pre); int p=t[nxt].l; if(f.cnt>1) f.cnt--,splay(p,0); else t[nxt].l=0; } int ask_rk(int x) { find(x); int p=root; return ls.size; } int ask_val(int p,int x) { if(!p) return inf; if(ls.size>=x) return ask_val(f.l,x); if(ls.size+f.cnt>=x) return f.val; return ask_val(f.r,x-ls.size-f.cnt); } signed main() { insert(-inf),insert(inf); read(n); for(int i=1,op,x;i<=n;i++) { read(op),read(x); switch(op) { case 1: insert(x); break; case 2: remove(x); break; case 3: insert(x); write(ask_rk(x)),puts(""); remove(x); break; case 4: write(ask_val(root,x+1)),puts(""); break; case 5: write(t[pre_nxt(x,0)].val),puts(""); break; case 6: write(t[pre_nxt(x,1)].val),puts(""); break; } } }
luogu P3380 树套树
可以有多种实现方法,此处采用思路较易的线段树维护区间,平衡树维护权值,缺点是查询第
-
建树与修改
对于线段树上每一个节点开一棵平衡树,直接暴力插入,因为线段树共有
层,每一层元素数量均为 ,每次插入复杂度为 ,所以建树的复杂度为 。修改即删除旧的,插入新的即可,修改后不要忘了另
。 -
查询排名
对于比
小的个数区间求和最后 即可,复杂度 。 -
查询第
小值考虑二分答案,比较该数的排名与
的大小,复杂度 。 -
查询前驱、后继
前驱则是线段树区间最大值,后继为区间最小值,当然都是针对线段树上每个节点对于平衡树上的前驱(后继)值,复杂度
。
树套树
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort using namespace std; const int N=8e6+10,inf=2147483647; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,m,tot,a[N],maxx=-inf,minn=inf; struct fhq_treap { #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] struct treap { int ch[2],cnt,size,val,dat; }t[N]; void pushup(int p) {f.size=ls.size+rs.size+1;} void split(int p,int k,int &x,int &y) { if(!p) {x=y=0; return ;} if(f.val>k) y=p,split(f.l,k,x,t[y].l); else x=p,split(f.r,k,t[x].r,y); pushup(p); } int merge(int x,int y) { if(!x||!y) return x+y; int p; if(t[x].dat>t[y].dat)t[x].r=merge(t[x].r,y),pushup(p=x); else t[y].l=merge(x,t[y].l),pushup(p=y); return p; } void insert(int &p,int k) { t[++tot].val=k,t[tot].size=1,t[tot].dat=rand(); int x,y; split(p,k-1,x,y); p=merge(merge(x,tot),y); } void remove(int &p,int k) { int x,y,is; split(p,k-1,x,y),split(y,k,is,y); p=merge(merge(x,merge(t[is].l,t[is].r)),y); } int leq(int &p,int k) { int x,y,is; split(p,k-1,x,y); is=t[x].size; p=merge(x,y); return is; } int pre(int p,int k) { if(!p) return -inf; if(f.val>=k) return pre(f.l,k); return max(pre(f.r,k),f.val); } int nxt(int p,int k) { if(!p) return inf; if(f.val<=k) return nxt(f.r,k); return min(nxt(f.l,k),f.val); } #undef f #undef ls #undef rs #undef l #undef r }fhq; #define f t[p] #define ls p<<1 #define rs p<<1|1 struct aa {int l,r,root;}t[N]; void build(int p,int l,int r) { f.l=l,f.r=r; for(int i=l;i<=r;i++) fhq.insert(f.root,a[i]); if(l==r) return ; int mid=(l+r)>>1; build(ls,l,mid),build(rs,mid+1,r); } void change(int p,int x,int d) { fhq.remove(f.root,a[x]); fhq.insert(f.root,d); if(f.l==f.r) return ; int mid=(f.l+f.r)>>1; if(x<=mid) change(ls,x,d); else change(rs,x,d); } int leq(int p,int l,int r,int x) { if(l<=f.l&&r>=f.r) return fhq.leq(f.root,x); int mid=(f.l+f.r)>>1,ans=0; if(l<=mid) ans+=leq(ls,l,r,x); if(r>mid) ans+=leq(rs,l,r,x); return ans; } int ask_rk(int l,int r,int x) {return leq(1,l,r,x)+1;} int ask_val(int ll,int rr,int x) { int l=minn,r=maxx,mid,ans=inf; while(l<=r) { mid=(l+r)>>1; if(ask_rk(ll,rr,mid)<=x) ans=mid,l=mid+1; else r=mid-1; } return ans; } int pre(int p,int l,int r,int x) { if(l<=f.l&&r>=f.r) return fhq.pre(f.root,x); int mid=(f.l+f.r)>>1,ans=-inf; if(l<=mid) ans=max(ans,pre(ls,l,r,x)); if(r>mid) ans=max(ans,pre(rs,l,r,x)); return ans; } int nxt(int p,int l,int r,int x) { if(l<=f.l&&r>=f.r) return fhq.nxt(f.root,x); int mid=(f.l+f.r)>>1,ans=inf; if(l<=mid) ans=min(ans,nxt(ls,l,r,x)); if(r>mid) ans=min(ans,nxt(rs,l,r,x)); return ans; } #undef f #undef ls #undef rs signed main() { read(n),read(m); for(int i=1;i<=n;i++) read(a[i]), maxx=max(maxx,a[i]),minn=min(minn,a[i]); build(1,1,n); for(int i=1,op,l,r,x,k;i<=m;i++) { read(op); switch(op) { case 1: read(l),read(r),read(k), write(ask_rk(l,r,k)),puts(""); break; case 2: read(l),read(r),read(k), write(ask_val(l,r,k)),puts(""); break; case 3: read(x),read(k), maxx=max(maxx,k),minn=min(minn,k); change(1,x,k); a[x]=k; break; case 4: read(l),read(r),read(k), write(pre(1,l,r,k)),puts(""); break; case 5: read(l),read(r),read(k), write(nxt(1,l,r,k)),puts(""); break; } } }
维护序列操作
luogu P3391 文艺平衡树
利用平衡树实现维护序列操作,可以使用
-
对于每次旋转,将其按照树的大小分裂,先按照
分裂, 再将左半部分按照 分裂,则中间一部分即为所求区间,向下打标记即可。翻转即左右儿子互换。
最后中序遍历输出。
文艺平衡树(fhq)
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=1e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,m,tot,root; struct fhq_treap { int ch[2],size,dat,val; bool add; }t[N]; void pushup(int p) {f.size=ls.size+rs.size+1;} void spread(int p) { if(!f.add||!p) return ; swap(f.l,f.r); ls.add^=1,rs.add^=1; f.add=0; } void split(int p,int k,int &x,int &y) { if(!p) {x=y=0; return ;} spread(p); if(ls.size>=k) y=p,split(f.l,k,x,t[y].l); else x=p,split(f.r,k-ls.size-1,t[x].r,y); pushup(p); } int merge(int x,int y) { if(!x||!y) return x+y; spread(x),spread(y); int p; if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x); else t[y].l=merge(x,t[y].l),pushup(p=y); return p; } void insert(int &p,int k) { t[++tot].val=k,t[tot].size=1,t[tot].dat=rand(); p=merge(p,tot); } void solve(int &p,int ll,int rr) { int x,y,is; split(p,rr,x,y),split(x,ll-1,x,is); t[is].add^=1; p=merge(merge(x,is),y); } void dfs(int p) { if(!p) return ; spread(p); dfs(f.l); write(f.val),putchar(' '); dfs(f.r); } signed main() { read(n),read(m); for(int i=1;i<=n;i++) insert(root,i); for(int i=1,ll,rr;i<=m;i++) read(ll),read(rr), solve(root,ll,rr); dfs(root); } -
找到
的位置,定义为 , 的位置,定义为 ,将 转到根节点,再将 转为 的子节点,那么根节点的右儿子的左儿子即对应所求区间,其余操作与 一致。文艺平衡树(splay)
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=1e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,m,root,tot; struct splay { int ch[2],ff,size,cnt,val; bool add; }t[N]; bool fson(int x,int y) {return t[x].ch[1]==y;} void pushup(int p) {f.size=ls.size+rs.size+f.cnt;} void spread(int p) { if(!p||!f.add) return ; swap(f.l,f.r); ls.add^=1,rs.add^=1; f.add=0; } void rotate(int x) { int y=t[x].ff,z=t[y].ff,k=fson(y,x); t[z].ch[fson(z,y)]=x; t[x].ff=z; t[y].ch[k]=t[x].ch[k^1]; t[t[x].ch[k^1]].ff=y; t[x].ch[k^1]=y; t[y].ff=x; pushup(y),pushup(x); } void splay(int x,int goal) { while(t[x].ff!=goal) { int y=t[x].ff,z=t[y].ff; if(z!=goal) fson(z,y)^fson(y,x)?rotate(x):rotate(y); rotate(x); } if(goal==0) root=x; } void insert(int x) { int p=root,ff=0; while(p&&f.val!=x) ff=p, p=f.ch[x>f.val]; if(p) {f.cnt++; splay(p,0); return ;} p=++tot; if(ff) t[ff].ch[x>t[ff].val]=p; f.ff=ff,f.val=x,f.size=f.cnt=1; splay(p,0); } int find(int p,int x) { spread(p); if(ls.size>=x) return find(f.l,x); if(ls.size+f.cnt>=x) return p; return find(f.r,x-ls.size-f.cnt); } void solve(int x,int y) { x=find(root,x),y=find(root,y); splay(x,0),splay(y,x); t[t[t[root].r].l].add^=1; } void dfs(int p) { if(!p) return ; spread(p); dfs(f.l); if(f.val!=1&&f.val!=n+2) write(f.val-1),putchar(' '); dfs(f.r); } signed main() { read(n),read(m); for(int i=1;i<=n+2;i++) insert(i); for(int i=1,ll,rr;i<=m;i++) read(ll),read(rr), solve(ll,rr+2); dfs(root); }
luogu P3165 排序机械臂
因为其不会插入新点,所以可以通过排序得知第
对于查询其所在位置,利用
区间翻转操作与文艺平衡树类似,同时向树中存的应为下标而不是权值。
注意审题与哨兵节点插入的顺序。
机械臂排序
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=1e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,root,tot,a[N]; struct aa {int h,id;}e[N]; bool cmp(aa a,aa b) {return a.h==b.h?a.id<b.id:a.h<b.h;} struct splay { int ch[2],ff,size,cnt,val; bool add; }t[N]; bool fson(int x,int y) {return t[x].ch[1]==y;} void pushup(int p) {f.size=ls.size+rs.size+f.cnt;} void spread(int p) { if(!p||!f.add) return ; swap(f.l,f.r); ls.add^=1,rs.add^=1; f.add=0; } void rotate(int x) { int y=t[x].ff,z=t[y].ff,k=fson(y,x); t[z].ch[fson(z,y)]=x; t[x].ff=z; t[y].ch[k]=t[x].ch[k^1]; t[t[x].ch[k^1]].ff=y; t[x].ch[k^1]=y; t[y].ff=x; pushup(y),pushup(x); } void splay(int x,int goal) { while(t[x].ff!=goal) { int y=t[x].ff,z=t[y].ff; spread(z),spread(y),spread(x); if(z!=goal) fson(z,y)^fson(y,x)?rotate(x):rotate(y); rotate(x); } if(goal==0) root=x; } void insert(int x) { int p=root,ff=0; while(p&&f.val!=x) ff=p, p=f.ch[x>f.val]; if(p) {f.cnt++; splay(p,0); return ;} p=++tot; if(ff) t[ff].ch[x>t[ff].val]=p; f.ff=ff,f.val=x,f.size=f.cnt=1; splay(p,0); } int find(int p,int x) { spread(p); if(ls.size>=x) return find(f.l,x); if(ls.size+f.cnt>=x) return p; return find(f.r,x-ls.size-f.cnt); } void solve(int x,int y) { x--,y++; x=find(root,x),y=find(root,y); splay(x,0),splay(y,x); t[t[t[root].r].l].add^=1; } int ask(int x) { splay(x,0); return t[t[root].l].size+1; } void solve(int i) { int x=i,y=ask(e[i].id); solve(x,y); } signed main() { srand(time(0)); insert(1); read(n); for(int i=2;i<=n+1;i++) read(e[i].h), e[i].id=i, insert(i); insert(n+2); sort(e+2,e+2+n,cmp); for(int i=2;i<=n+1;i++) write(ask(e[i].id)-1),putchar(' '), solve(i); }
luogu P4309 最长上升子序列
回想最长上升子序列的
此处由于每次插入的数一定大于之前插入的所有数,所以直接在
因为其需要动态插入,可以离线后线段树维护,也可以平衡树动态维护。
对于没一个节点定义
void pushup(int p) { f.val=max({ls.val,rs.val,f.dp}); }
插入操作可以用
最长上升子序列
#include<bits/stdc++.h> // #define int long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=1e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,root,tot; struct fhq_treap { int ch[2],size,dat,ans,len; }t[N]; void pushup(int p) { f.size=ls.size+rs.size+1; f.ans=max({ls.ans,rs.ans,f.len}); } void split(int p,int k,int &x,int &y) { if(!p) {x=y=0; return ;} if(ls.size>=k) y=p,split(f.l,k,x,t[y].l); else x=p,split(f.r,k-ls.size-1,t[x].r,y); pushup(p); } int merge(int x,int y) { if(!x||!y) return x+y; int p; if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x); else t[y].l=merge(x,t[y].l),pushup(p=y); return p; } void insert(int &p,int k) { t[++tot].size=1,t[tot].dat=rand(); int x,y; split(p,k,x,y); t[tot].len=t[tot].ans=t[x].ans+1; p=merge(merge(x,tot),y); } signed main() { srand(time(0)); read(n); for(int i=1,x;i<=n;i++) read(x), insert(root,x), write(t[root].ans),puts(""); }
树上哈希
对于每个节点,保存的是其子树的
void pushup(int p) { f.hash=ls.hash*b[rs.size+1]+f.val*b[rs.size]+rs.hash; }
luogu P4036 火星人
利用树上哈希,对于每次询问,二分答案即可。
查询一个区间的
火星人
#include<bits/stdc++.h> // #define int long long #define ull unsigned long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=3e5+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,m,tot,root; ull b[N]; char s[N]; struct fhq_treap { int ch[2],size,dat; ull val,hash; }t[N]; void pushup(int p) { f.size=ls.size+rs.size+1; f.hash=ls.hash*b[rs.size+1]+f.val*b[rs.size]+rs.hash; } void split(int p,int k,int &x,int &y) { if(!p) {x=y=0; return ;} if(ls.size>=k) y=p,split(f.l,k,x,t[y].l); else x=p,split(f.r,k-ls.size-1,t[x].r,y); pushup(p); } int merge(int x,int y) { if(!x||!y) return x+y; int p; if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x); else t[y].l=merge(x,t[y].l),pushup(p=y); return p; } void insert(int &p,int k,int d) { t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash=d; int x,y; split(p,k,x,y); p=merge(merge(x,tot),y); } void change(int &p,int k,int d) { t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash=d; int x,y,is; split(p,k,x,y),split(x,k-1,x,is); p=merge(merge(x,tot),y); } int ask(int p,int ll,int rr) { int x,y,is; ull ans; split(p,rr,x,y),split(x,ll-1,x,is); ans=t[is].hash; p=merge(merge(x,is),y); return ans; } bool check(int x,int y,int mid) { return ask(root,x,x+mid-1)==ask(root,y,y+mid-1); } int ask(int x,int y) { int ll=0,rr=min(n-x+1,n-y+1),mid,ans=0; while(ll<=rr) { mid=(ll+rr)>>1; if(check(x,y,mid)) ans=mid,ll=mid+1; else rr=mid-1; } return ans; } void pre() { b[0]=1,b[1]=233; for(int i=2;i<=N-1;i++) b[i]=b[i-1]*b[1]; } signed main() { srand(time(0)); cin>>s+1; read(m); n=strlen(s+1); pre(); for(int i=1;i<=n;i++) insert(root,i-1,s[i]-'a'+1); while(m--) { char op,d; int x,y; cin>>op; if(op=='Q') { read(x),read(y); if(x>y) swap(x,y); write(ask(x,y)),puts(""); } if(op=='R') read(x),cin>>d, change(root,x,d-'a'+1); if(op=='I') read(x),cin>>d, insert(root,x,d-'a'+1), n++; } }
AtCoder abc331_f Palindrome Query
类似的二分答案即可,需要存正串和反串,同时存在线段树解法,不过多展开。
Palindrome Query
#include<bits/stdc++.h> // #define int long long #define ull unsigned long long #define endl '\n' #define sort stable_sort #define f t[p] #define ls t[t[p].ch[0]] #define rs t[t[p].ch[1]] #define l ch[0] #define r ch[1] using namespace std; const int N=2e6+10,inf=0x7f7f7f7f; template<typename Tp> inline void read(Tp&x) { x=0;register bool z=true; register char c=getchar(); for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0; for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48); x=(z?x:~x+1); } void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');} void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);} int n,m,root,tot; ull b[N]; char s[N]; struct fhq_treap { int ch[2],val,size,dat; ull hash[2]; }t[N]; void pushup(int p) { f.size=ls.size+rs.size+1; f.hash[0]=ls.hash[0]*b[rs.size+1]+f.val*b[rs.size]+rs.hash[0]; f.hash[1]=rs.hash[1]*b[ls.size+1]+f.val*b[ls.size]+ls.hash[1]; } void split(int p,int k,int &x,int &y) { if(!p) {x=y=0; return ;} if(ls.size>=k) y=p,split(f.l,k,x,t[y].l); else x=p,split(f.r,k-ls.size-1,t[x].r,y); pushup(p); } int merge(int x,int y) { if(!x||!y) return x+y; int p; if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x); else t[y].l=merge(x,t[y].l),pushup(p=y); return p; } void insert(int &p,int k,int d) { t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash[0]=t[tot].hash[1]=d; int x,y; split(p,k,x,y); p=merge(merge(x,tot),y); } void change(int &p,int k,int d) { t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash[0]=t[tot].hash[1]=d; int x,y,is; split(p,k,x,y),split(x,k-1,x,is); p=merge(merge(x,tot),y); } int ask(int p,int ll,int rr,bool d) { int x,y,is; ull ans; split(p,rr,x,y),split(x,ll-1,x,is); ans=t[is].hash[d]; p=merge(merge(x,is),y); return ans; } void ask(int ll,int rr) { if(ll==rr) {puts("Yes"); return ;} int mid=(rr-ll+1)>>1; puts(ask(root,ll,ll+mid-1,0)==ask(root,rr-mid+1,rr,1)?"Yes":"No"); } void pre() { b[0]=1,b[1]=233; for(int i=2;i<=N-1;i++) b[i]=b[i-1]*b[1]; } signed main() { srand(time(0)); read(n),read(m); pre(); cin>>s+1; for(int i=1;i<=n;i++) insert(root,i-1,s[i]-'a'+1); while(m--) { int op,x,y; char d; read(op); if(op==1) read(x),cin>>d,change(root,x,d-'a'+1); else read(x),read(y),ask(x,y); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效