【BZOJ3223】文艺平衡树【Splay】
在此之前,我学会了treap,因此学起splay来很轻松
splay又叫伸展树,可以分裂和合并,从而实现区间操作。splay,和treap是不同的。treap是二叉搜索树,对节点权值满足左小右大的特性。splay是由序列信息组成的一颗树,满足一大特性——中序遍历后得到原序列。换句话说,splay中每个节点都对应序列的一个位置坐标。往左子树走,就是这个位置往左;往右子树走则相反。也可以这样理解:splay是一颗bst(二叉搜索树),不过每个节点的权值是位置坐标而已,附带着这个位置上的元素
splay有3种基本操作:
1.build(A):把数组A转化为序列
2.merge(S1,S2):把序列S1和S2连接在一起,S1在左边,S2在右边。返回新序列
3.split(S,k):把序列S分成两个连续的子序列,左子序列包含S的前k个元素,其他元素在右子树中
这三种操作可以由伸展操作splay(S,k)实现,即把子树S的第k个元素旋转到根上,详见白书,此处略过...
对于这道题,反转操作要这样实现:先截取所要的区间,交换左右子树,并打上标记,从而实现逐层交换
其实很好理解——反转区间后,位置坐标原本靠左的,现在变得靠右了嘛
注意每访问一个点,都要下传标记(类似线段树)
#include<iostream> #include<cstdio> #include<cstring> #include<vector> using namespace std; struct Node{ Node* ch[2]; int s,v; bool tg; int cmp(int k) const { int d = k - ch[0]->s; if(d == 1) return -1; return d <= 0 ? 0 : 1; } void tain() { s = ch[0]->s + ch[1]->s + 1; } void pushdown() { if(!tg)return; swap(ch[0], ch[1]); ch[0]->tg^=1; ch[1]->tg^=1; tg=0; } }; Node* null = new Node(); void rotate(Node* &o,int d) { Node* k = o->ch[d^1]; o->ch[d^1] = k->ch[d]; k->ch[d] = o; o->tain(); k->tain(); o = k; } void splay(Node* &o, int k) { o->pushdown(); int d = o->cmp(k); if(d == 1) k -= o->ch[0]->s + 1; if(d != -1) { Node* p = o->ch[d]; p->pushdown(); int d2 = p->cmp(k); int k2 = (d2 == 0 ? k : k - p->ch[0]->s - 1); if(d2 != -1) { splay(p->ch[d2], k2); if(d == d2) rotate(o, d^1); else rotate(o->ch[d], d); } rotate(o, d^1); } } Node* merge(Node* l,Node* r) { splay(l,l->s); l->ch[1] = r; l->tain(); return l; } typedef pair<Node*,Node*> D; D split(Node* o,int k) { splay(o, k); Node* l=o; Node* r=o->ch[1]; o->ch[1] = null; l->tain(); return D(l,r); } Node* build(int l,int r) { if(l>r)return null; int mid = (l+r)>>1; Node* x = new Node(); x->tg=0; x->v=mid; x->ch[0] = build(l,mid-1); x->ch[1] = build(mid+1,r); x->tain(); return x; } vector<int>ans; void travel(Node* o) { if(o == null)return; o->pushdown(); travel(o->ch[0]); ans.push_back(o->v); travel(o->ch[1]); } int main() { int n,m; scanf("%d%d",&n,&m); null->s=0; Node* root=build(0,n+1); int l,r; while(m--) { scanf("%d%d",&l,&r); D x=split(root,l); D y=split(x.second,r-l+1); y.first->tg^=1; root=merge(x.first,merge(y.first,y.second)); } travel(root); for(int i=1;i<n;i++) printf("%d ",ans[i]); printf("%d\n",ans[n]); return 0; }
还有一种数据结构叫无旋treap,和splay的概念是近乎相同的,个人感觉比splay还好写,想学习的同学可以看下这篇博文https://www.cnblogs.com/LadyLex/p/7182631.html