[学习笔记]替罪羊树
替罪羊树——简单粗暴的数据结构 - a_forever_dream的..._CSDN博客
一种简单粗暴的数据结构(我自认为暴力“不优雅”。。。)
和其他“优雅”的splay,fhq-treap不同
替罪羊既不旋转,也不分裂合并
我看谁不顺眼,直接让其暴力重构!
思路就是这样。
基础
特点是:
1.如果一个点的左子树或者右子树的节点个数>x的节点个数*alpha(0.5~1.0之间的一个值),那么暴力重构整个子树
2.懒惰删除,因为SGT不够灵活。对删除的点打上标记,并且在祖先节点更新信息。但是不删掉节点本身(名存实亡)。
3.如果一个点子树的已经删除部分占30%左右,暴力重构子树
所以,一个点的信息是:
sz:子树节点个数
cnt:子树实际存在的节点个数
val:自己的权值
ch[2]:两个儿子
die:是否被删除
先说暴力重构:
分三步:
1.dfs(x),把x子树所有没有删除的点加入一个数组里
2.build(l,r),分治建树
3.pushup(fa[x])把x的father的信息更新一下(node中可以不记录father,临时记录即可)
操作:
(最大的坑莫过于未考虑到名存实亡的点)
insert:类比splay,while循环,找到位置之后加入节点(可以同一个值分成不同的点,x和左右子树的关系变成不严格的即可)
最后从上而下尝试rebuild
dele:找到后die=1,
注意:必须找到一个没有被删除的。所以,不用权值找,而用第k大(坑了2h)
(权值的话,如果x被删除了你不知道往哪里走)
最后从上而下尝试rebuild
rank,kth,pre,bac都差不多,kth的返回,注意当前点必须是未删除点
复杂度:alpha调好,可以保证O(nlogn)(alpha一般是0.75)
优劣
优点:
1.常数小。不知为啥
2.结构稳定,一些不能大量旋转或者分裂的时候,只能SGT
3.暴力重构的思想值得借鉴
缺点:
1.不够灵活,区间操作无能为力
2.一些题目名存实亡的点不好处理。且容易写挂。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define reg register int #define il inline #define fi first #define se second #define mk(a,b) make_pair(a,b) #define numb (ch^'0') #define pb push_back #define solid const auto & #define enter cout<<endl #define pii pair<int,int> using namespace std; typedef long long ll; template<class T>il void rd(T &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } template<class T>il void output(T x){if(x/10)output(x/10);putchar(x%10+'0');} template<class T>il void ot(T x){if(x<0) putchar('-'),x=-x;output(x);putchar('\n');} template<class T>il void prt(T a[],int st,int nd){for(reg i=st;i<=nd;++i) ot(a[i]);putchar('\n');} namespace Miracle{ const int N=1e5+5; const double alp=0.75; int n,tot,rt; struct node{ int ch[2]; int sz,cnt,val; int die; }t[N]; int nc(int v){ ++tot;t[tot].ch[0]=t[tot].ch[1]=0; t[tot].sz=t[tot].cnt=1;t[tot].die=0; t[tot].val=v;return tot; } #define ls t[x].ch[0] #define rs t[x].ch[1] void pushup(int x){ if(!x) return; t[x].sz=t[ls].sz+t[rs].sz+1; t[x].cnt=t[ls].cnt+t[rs].cnt+(!t[x].die); } int q[N],num; bool isbad(int x){ return (t[ls].sz>t[x].sz*alp||t[rs].sz>t[x].sz*alp||t[x].cnt<t[x].sz*alp); } void dfs(int x){ if(!x) return ; dfs(ls); if(!t[x].die) q[++num]=x; dfs(rs); } int build(int l,int r){ if(l>r) return 0; int mid=(l+r)>>1; int x=q[mid]; ls=build(l,mid-1); rs=build(mid+1,r); pushup(x); return x; } void rebuild(int &x){ num=0;dfs(x); x=build(1,num); pushup(x); } int sta[N],top; void che(){ reg i; for( i=1;i<=top;++i){ if(isbad(sta[i])){ if(sta[i]==rt) { rebuild(rt); }else{ rebuild(t[sta[i-1]].ch[t[sta[i-1]].ch[1]==sta[i]]);pushup(sta[i-1]); } break; } } } void ins(int v){ if(!rt){ rt=nc(v);return; } int x=rt; top=0; while(1){ int d=t[x].val<=v; ++t[x].sz; ++t[x].cnt; sta[++top]=x; if(!t[x].ch[d]){ t[x].ch[d]=nc(v); break; } x=t[x].ch[d]; } che(); } int kth(int k){ int x=rt; while(1){ int d=t[t[x].ch[0]].cnt+(!t[x].die); if(!t[x].die&&d==k) return t[x].val; if(t[t[x].ch[0]].cnt>=k) x=t[x].ch[0]; else{ k-=d;x=t[x].ch[1]; } } return -23333333; } int rk(int v){ int x=rt; int ret=1; while(x){ int d=t[x].val<v; if(d) ret+=t[t[x].ch[0]].cnt+(!t[x].die),x=t[x].ch[1]; else x=t[x].ch[0]; } return ret; } void dele(int v){ int k=rk(v); int x=rt,top=0; while(1){ int d=t[ls].cnt+(!t[x].die); sta[++top]=x; --t[x].cnt; if(!t[x].die&&d==k) { t[x].die=1;break; } if(t[ls].cnt>=k) x=ls; else { k-=d;x=rs; } } che(); } int pre(int v){ return kth(rk(v)-1); } int bac(int v){ return kth(rk(v+1)); } int main(){ rd(n); int op,x; while(n--){ rd(op);rd(x); switch(op){ case 1:ins(x);break; case 2:dele(x);break; case 3:printf("%d\n",rk(x));break; case 4:printf("%d\n",kth(x));break; case 5:printf("%d\n",pre(x));break; case 6:printf("%d\n",bac(x));break; } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* */