我吹爆fhq,磨了一下午无旋treap,怕遗忘,写点细节。
- fhq treap中有重复的点,它们呈一条链排列,所以del时不能完全砍掉:z树是一条相同权值点的链。下方第6行,只会将z根这一点砍掉(调用merge实际上会直接返回左子树的编号,见代码)
1 void del(int &root,int val) 2 { 3 int x=0,y=0,z=0; 4 split(root,x,y,val); 5 split(x,x,z,val-1); 6 merge(z,tr[z].lc,tr[z].rc); //重复的点 7 merge(x,x,z); 8 merge(root,x,y); 9 }
- 权值分裂和位置分裂(a kth 都<=k)对于BST能达到同样的效果,像文艺平衡树这种只保留位置的只能用位置分裂。以下两种分裂:
void split(int x,int &a,int &b,int val) //权值分裂 { if(!x){ a=b=0; return; } if(tr[x].val<=val){ a=x; //x的左子树都能放到a的左子树 split(tr[x].rc,tr[a].rc,b,val); //递归找a的右子树(rc一定大于fa[rc])和b(↓) } else{ b=x; split(tr[x].lc,a,tr[b].lc,val); } up(x); }
1 void split(int x,int &a,int &b,int k) 2 { 3 if(!x){ 4 a=b=0; 5 return; 6 } 7 down(x); 8 if(k<=tr[tr[x].lc].siz){ //a已满先找b //如果x恰好是划分点(恰好x左子树为a),则x及x的右子树为b树,递归下一层将tr[x].lc付给a 9 b=x; 10 split(tr[x].lc,a,tr[b].lc,k); 11 } 12 else{ 13 a=x; 14 split(tr[x].rc,tr[a].rc,b,k-tr[tr[x].lc].siz-1); 15 } 16 up(x); 17 }
个人感觉位置分裂比较难理解且难记呃,9行实际上可以理解为a树能被x的左子树填满,但此时x的左子树中还可能有大于rank>k的值,所以显然不能直接将x.lc直接付给a。
附:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#include<cstdio> #include<ctime> #include<cstring> #include<algorithm> #include<cstdlib> #define MAXN 200005 #define reg register #define F(i,a,b) for(i=a;i<=b;++i) using namespace std; int o,root; struct TR{ int val,rd,siz,lc,rc; }tr[MAXN]; int newnode(int val) { int x=++o; tr[x].val=val; tr[x].rd=rand(); tr[x].siz=1; return x; } void up(int x) { tr[x].siz=tr[tr[x].lc].siz+tr[tr[x].rc].siz+1; } void split(int x,int &a,int &b,int val) { if(!x){ a=b=0; return; } if(tr[x].val<=val){ a=x; //x的左子树都能放到a的左子树 split(tr[x].rc,tr[a].rc,b,val); //递归找a的右子树(rc一定大于fa[rc])和b(↓) } else{ b=x; split(tr[x].lc,a,tr[b].lc,val); } up(x); } void merge(int &x,int a,int b) { if(a*b==0){ x=a+b; return; } if(tr[a].rd<tr[b].rd){ x=a; //x及x的左子树确定,b放在x(a)的右子树上(b的每个节点都大于a) merge(tr[x].rc,tr[a].rc,b); } else{ x=b; //x及x的右子树确定,同理 merge(tr[x].lc,a,tr[b].lc); } up(x); } void insert(int &root,int val) { int x=0,y=0; split(root,x,y,val); merge(x,x,newnode(val)); merge(root,x,y); } void del(int &root,int val) { int x=0,y=0,z=0; split(root,x,y,val); split(x,x,z,val-1); merge(z,tr[z].lc,tr[z].rc); //重复的点 merge(x,x,z); merge(root,x,y); } int kth(int rt,int k) { int x=rt; while(tr[tr[x].lc].siz+1!=k) { if(k<=tr[tr[x].lc].siz) x=tr[x].lc; else k-=tr[tr[x].lc].siz+1,x=tr[x].rc; } return tr[x].val; } int rak(int &root,int val) { int x=0,y=0; split(root,x,y,val-1); int ans=tr[x].siz+1; merge(root,x,y); return ans; } int pre(int &root,int val) { int x=0,y=0; split(root,x,y,val-1); int ans=kth(x,tr[x].siz); merge(root,x,y); return ans; } int nex(int &root,int val) { int x=0,y=0; split(root,x,y,val); int ans=kth(y,1); merge(root,x,y); return ans; } void dfs(int now){ if(!now) return; dfs(tr[now].lc); printf("%d ",tr[now].val); dfs(tr[now].rc); } int main() { srand(time(0)); int n; scanf("%d",&n); reg int op,x; while(n--) { scanf("%d%d",&op,&x); switch(op){ case 1:insert(root,x);break; case 2:del(root,x);break; case 3:printf("%d\n",rak(root,x));break; case 4:printf("%d\n",kth(root,x));break; case 5:printf("%d\n",pre(root,x));break; case 6:printf("%d\n",nex(root,x));break; case 7:dfs(root);puts("");break; } } return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#include<cstdio> #include<ctime> #include<cstring> #include<algorithm> #include<cstdlib> #define MAXN 100005 #define reg register #define F(i,a,b) for(i=a;i<=b;++i) using namespace std; int o,f[MAXN],root; struct TR{ int lc,rc,siz,rd,val; }tr[MAXN]; int newnode(int val) { int x=++o; tr[x].val=val; tr[x].rd=rand(); tr[x].siz=1; return x; } void up(int k) { tr[k].siz=tr[tr[k].lc].siz+tr[tr[k].rc].siz+1; } void down(int k) { if(f[k]){ swap(tr[k].lc,tr[k].rc); f[tr[k].lc]^=1; f[tr[k].rc]^=1; f[k]=0; } } void split(int x,int &a,int &b,int k) { if(!x){ a=b=0; return; } down(x); if(k<=tr[tr[x].lc].siz){ //a已满先找b //如果x恰好是划分点(恰好x左子树为a),则x及x的右子树为b树,递归下一层将tr[x].lc付给a b=x; split(tr[x].lc,a,tr[b].lc,k); } else{ a=x; split(tr[x].rc,tr[a].rc,b,k-tr[tr[x].lc].siz-1); } up(x); //eee } void merge(int &x,int a,int b) { if(a*b==0){ x=a+b; return; } down(a); down(b); if(tr[a].rd<tr[b].rd){ x=a; merge(tr[x].rc,tr[a].rc,b); } else{ x=b; merge(tr[x].lc,a,tr[b].lc); } up(x); } void work(int l,int r) { int x=0,y=0,z=0; split(root,x,y,r); split(x,x,z,l-1); f[z]^=1; merge(x,x,z); merge(root,x,y); } void dfs(int x){ if(!x) return; down(x); dfs(tr[x].lc); printf("%d ",tr[x].val); dfs(tr[x].rc); } int main() { srand(time(0)); int n; reg int i,a,b,m; scanf("%d%d",&n,&m); F(i,1,n) merge(root,root,newnode(i)); while(m--) { scanf("%d%d",&a,&b); work(a,b); } dfs(root); return 0; }
文艺线段树实际上是对下标进行操作,于是对权值来说不满足BST性质,siz[]只是用来标记节点的区间大小以便二分,split是对位置的分离,merge()不会改变中序遍历,所以可以做到区间合并甚至在两个数间插入一个数。于是我们就可以配合懒标记进行很多区间操作,例如区间加、区间最值、区间翻转等等,但由于以上操作破坏了BST性质不能支持kth、rank。