[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('
');
95     return 0;
96 }
View Code
原文地址:https://www.cnblogs.com/Super-Nick/p/7544745.html