[2017.9.18] BZOJ3223Tyvj 1729 文艺平衡树(Splay区间翻转)
直到今天考试才发现自己忘记Splay区间翻转操作了……
1.如何建树?
每个节点除了传统Splay所要维护的左右儿子,父亲,子树大小之外,还要维护一个权值,代表在初始数列的值,还要维护一个bool标记,表示自己是否被翻转了。建树时,按照中序遍历为原序列,类似二分的思想建树。
2.什么是最终序列?
每个点的左子树的size+1即为该节点在该序列的位置(即下标),而每个节点的权值就是那个位置的值,所以最终答案就是最终的Splay的中序遍历。
3.对于翻转操作,如何翻转序列?
1)如何找到区间?
要多开两个点:0号点和n+1号点,否则会出现问题,统计答案时忽略就好了
将所求区间的左端点位置的点旋转到根,(右端点+2)位置的点旋转到根节点的右儿子,此时,根节点的右儿子的左子树包含的点就是所求区间的点,不要问我为什么,我不能理性解释,就当结论记住吧。这里注意,splay不一定要把节点旋转到根,事实上,旋转到任意父亲节点都是可以的,旋转到根就相当于旋转到0号点的儿子。
2)如何实现翻转?
其实翻转就是把左右子树翻转,因为本来位置就是通过左右儿子表现出来的,一棵树的左右左右儿子翻转,就相当于给区间左右翻转。
但是直接把子树所有节点的左右儿子翻转复杂度是不对的。可以发现,有很多区间翻转过来又翻转过去,每次翻转是极大的浪费。考虑引入“lazy”的思想,如果要修改一颗子树,就把根节点打上lazy标记,下方就是把左右儿子翻转,并把标记下方给左右儿子。上放就是统计size,即size[now]=size[ls]+size[rs]+1;
所以这就要求我们每访问一个节点都要下放和上放。别的操作都好处理,关键是splay操作,这里我们要开一个栈,把要splay的节点到目标节点路径上的所有点用栈存下来,再从上往下下放标记,对于每次rotate,都要上放,最后本身当然也要上放。
复杂度O(mlog(n))
其实就这么多,上面已经讲的比较清楚了,还有一些细节(比如旋转完root要单独上放一次,rotate上放的是f而不是x)代码可能更清楚。裸题BZOJ3223: Tyvj 1729 文艺平衡树
1 #include<bits/stdc++.h> 2 #define N 100010 3 #define RG register 4 #define inf 0x3f3f3f3f 5 #define ls dot[x].son[0] 6 #define rs dot[x].son[1] 7 using namespace std; 8 int m,n,rt,x1,x2,tmp,top,tot,sta[N]; 9 struct node{ 10 bool tag; 11 int fa,siz,val,son[2]; 12 inline void init(RG int x){siz=1;val=x;} 13 }dot[N]; 14 inline int gi(){ 15 RG int x=0;RG char c=getchar(); 16 while(c<'0'||c>'9') c=getchar(); 17 while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); 18 return x; 19 } 20 inline void push_up(RG int x){ 21 dot[x].siz=dot[ls].siz+dot[rs].siz+1; 22 } 23 inline int build(RG int l,RG int r){ 24 if(l>r) return 0; 25 RG int x=++tot,mid=(l+r)>>1; 26 dot[x].init(mid); 27 ls=build(l,mid-1); 28 rs=build(mid+1,r); 29 dot[ls].fa=dot[rs].fa=x; 30 push_up(x); 31 return x; 32 } 33 inline void push_down(RG int x){ 34 if(dot[x].tag){ 35 swap(ls,rs); 36 dot[x].tag=0; 37 dot[ls].tag^=1; 38 dot[rs].tag^=1; 39 } 40 } 41 inline int find(RG int x,RG int g){//寻找某个点 42 push_down(x); 43 if(dot[ls].siz+1<g) return find(rs,g-1-dot[ls].siz); 44 if(dot[ls].siz+1==g) return x; 45 return find(ls,g); 46 } 47 inline bool dir(RG int x){ 48 return dot[dot[x].fa].son[1]==x; 49 } 50 inline void rotate(RG int x){ 51 RG bool op=dir(x); 52 RG int f=dot[x].fa; 53 dot[x].fa=dot[f].fa; 54 dot[dot[f].fa].son[dir(f)]=x; 55 dot[dot[x].son[op^1]].fa=f; 56 dot[f].son[op]=dot[x].son[op^1]; 57 dot[f].fa=x; 58 dot[x].son[op^1]=f; 59 push_up(f); 60 } 61 inline void splay(RG int x,RG int g){//将x旋转到g操作,0表示旋转到根 62 top=0;tmp=x; 63 while(tmp!=g){ 64 sta[++top]=tmp;//用栈来维护原x到g路径上的点,这下点从上往下都要下放 65 tmp=dot[tmp].fa; 66 } 67 for (RG int i=top;i;--i) 68 push_down(sta[i]); 69 if(!g) rt=x; 70 while(dot[x].fa!=g){ 71 if(dot[dot[x].fa].fa==g) {rotate(x);break;} 72 if(dir(x)==dir(dot[x].fa)) rotate(dot[x].fa),rotate(x); 73 else rotate(x),rotate(x); 74 } 75 push_up(x); 76 } 77 inline void dfs(RG int x){//统计答案,一定要记得先下放!!! 78 push_down(x); 79 if(ls) dfs(ls); 80 if(dot[x].val&&dot[x].val<=n) 81 printf("%d ",dot[x].val); 82 if(rs) dfs(rs); 83 } 84 int main(){ 85 n=gi();m=gi(); 86 rt=build(0,n+1);//一定要记得多开0、n+1两个点 87 while(m--){ 88 RG int l=gi();RG int r=gi(); 89 x1=find(rt,l);x2=find(rt,r+2); 90 splay(x1,0);splay(x2,x1);push_up(rt);//记得rt要上放,其实所有非旋转到根的旋转,最终节点到根节点的链上都要上放 91 dot[dot[x2].son[0]].tag^=1;//改变标记 92 } 93 dfs(rt); 94 putchar('\n'); 95 return 0; 96 }