Splay bzoj3223文艺平衡树

9/6/2018

Splay,中文名伸展树,是由tarjan大神发明的... orz 本质上就是BST加上splay操作——把结点x旋转到指定结点的下面。 每次查询完都把查到的数旋转到根,就可以让所有查找的时间效率为均摊O(logN) (不知道为啥...大佬说是就是吧orz)

因为Splay可以通过伸展操作随意改变树的结构,只要把排名L-1的结点伸展到根,把排名R+1的结点伸展到根的右孩子,R+1结点的左子树就包含了区间[L,R]中所有的点,所以Splay做区间操作非常方便。

缺点大概就是大得惊人的常数吧。

Splay的定义:

int ch[maxn][2], fa[maxn], val[maxn], siz[maxn], lazy[maxn];
int tot,root;
#define ls ch[now][0]
#define rs ch[now][1]
1
2
3
4

跟Treap的定义基本一样的,少个优先级,多个fa数组记录该结点的父亲。lazy是题目要用的翻转标记。 宏是用来偷懒的~

为什么Treap不要fa,Splay要? 因为Splay操作要用到父亲的父亲,不记录fa不会写。

旋转rotate: 跟所有平衡树的旋转都是一样的。get函数用来判断该结点是它父亲的哪个儿子。因为要维护区间,有些地方要pushup(把x旋转到y的上面之后,y的子树大小要重新计算) 贴张丑图以供想象 这里写图片描述

bool get(int x)//返回x是它父亲的哪个儿子
{
    return x == ch[fa[x]][1];
}

void connect(int son,int f,int dir)
{
    fa[son] = f;
    ch[f][dir] = son;
}

int y,z,yson,zson,xson;
void Rotate(int x)//把x旋转到fa[x]的位置,用get来判断要左旋还是右旋
{
    y = fa[x];
    z = fa[y];
    yson = get(x), zson = get(y);//记录x和y是他们父亲的哪个儿子
    xson = ch[x][yson^1];
    connect(x,z,zson);
    connect(y,x,yson^1);
    connect(xson,y,yson);
    pushup(y);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

伸展splay——把x旋转到to的下面,成为to的儿子 三种情况: 1.fa[fa[x]] == to 往上旋一次即可 2.get(x) == get(fa[x]) 设y是x的父亲,z是y的父亲,这种情况下,xyz是共线的(自行脑补) tarjan大佬说这种情况要先把y旋上去,再把x旋上去 3.其他情况 x往上旋两次

void splay(int x,int to)//把x旋转到to的下面
{
    pushdown(x);//旋转后会改变结点关系,先下推标记!
    while(fa[x] != to)
    {
        if(fa[fa[x]] == to)
            Rotate(x);
        else if(get(x)==get(fa[x]))
            Rotate(fa[x]), Rotate(x);
        else
            Rotate(x), Rotate(x);
    }
    pushup(x);//旋转后会子树大小会变,更新
    if(to == 0)//x移到根了,更新根
        root = x;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

建树build: 跟线段树差不多,多传一个参数f用来维护fa数组

void build(int l,int r,int &now,int f)
{
    if(l>r) return ;
    int m = l + r >> 1;
    newnode(m,now,f);
    build(l,m-1,ls,now);
    build(m+1,r,rs,now);
    pushup(now);
}
1
2
3
4
5
6
7
8
9

更新update(本题的更新是翻转) 首先把 L-1 splay到root,把 R+1 splay到root下面 build前要插入两个虚结点0和N+1,不然splay操作会越界

    newnode(0,root,0);
    newnode(N+1,ch[root][1],root);
    build(1,N,ch[ch[root][1]][0],ch[root][1]);
1
2
3

但splay函数是把下标为x的结点旋转到下标的to的结点下面,这里的结点编号跟要维护的区间的下标是不一致的 区间就是BST的中序序列,写个find函数把区间下标对应的结点编号求出来

int findk(int k,int now)//找区间中第k个数字的下标(0是第一个)
{
    pushdown(now);//往下找之前要pushdown(可能左右子树需要翻转)
    if(k == siz[ls]+1) return now;
    else if(k <= siz[ls]) return findk(k,ls);
    else return findk(k-siz[ls]-1,rs);
}
1
2
3
4
5
6
7

上面也提到了,0是区间中的第一个数字,所以要splay的是L和R+2

void update(int L,int R)
{
    int &now = root;//为了用上面那个宏弄了个引用...(一开始没写引用狂T o(╥﹏╥)o)
    splay(findk(L,root),0);
    splay(findk(R+2,root),root);
    lazy[ch[rs][0]] ^= 1;
    pushup(rs);
    pushup(root);
}
1
2
3
4
5
6
7
8
9

完整代码

#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn = 1e5 + 5;
int ch[maxn][2], fa[maxn], val[maxn], siz[maxn], lazy[maxn];
int tot,root;
#define ls ch[now][0]
#define rs ch[now][1]
int N,M;
void newnode(int v,int &x,int f)
{
    x = ++tot;
    ch[x][0] = ch[x][1] = 0;
    val[x] = v;
    siz[x] = 1;
    lazy[x] = 0;
    fa[x] = f;
}

bool get(int x)//返回x是它父亲的哪个儿子
{
    return x == ch[fa[x]][1];
}

void pushup(int now)
{
    if(now)
        siz[now] = siz[ls] + siz[rs] + 1;
}

void pushdown(int now)
{
    if(lazy[now])
    {
        lazy[ls] ^= 1;
        lazy[rs] ^= 1;
        swap(ls,rs);//翻转把左右子树交换一下就好了
        lazy[now] = 0;
    }
}

void connect(int son,int f,int dir)
{
    fa[son] = f;
    ch[f][dir] = son;
}

int y,z,yson,zson,xson;
void Rotate(int x)//把x旋转到fa[x]的位置,用get来判断要左旋还是右旋
{
    y = fa[x];
    z = fa[y];
    yson = get(x), zson = get(y);//记录x和y是他们父亲的哪个儿子
    xson = ch[x][yson^1];
    connect(x,z,zson);
    connect(y,x,yson^1);
    connect(xson,y,yson);
    pushup(y);
}

void splay(int x,int to)//把x旋转到to的下面
{
    pushdown(x);
    while(fa[x] != to)
    {
        if(fa[fa[x]] == to)
            Rotate(x);
        else if(get(x)==get(fa[x]))
            Rotate(fa[x]), Rotate(x);
        else
            Rotate(x), Rotate(x);
    }
    pushup(x);
    if(to == 0)//x移到根了,更新根
        root = x;
}

void build(int l,int r,int &now,int f)
{
    if(l>r) return ;
    int m = l + r >> 1;
    newnode(m,now,f);
    build(l,m-1,ls,now);
    build(m+1,r,rs,now);
    pushup(now);
}

int findk(int k,int now)//找区间中第k个数字的下标(0是第一个)
{
    pushdown(now);
    if(k == siz[ls]+1) return now;
    else if(k <= siz[ls]) return findk(k,ls);
    else return findk(k-siz[ls]-1,rs);
}

void update(int L,int R)
{
    int &now = root;//为了用上面那个宏弄了个引用...(一开始没写引用狂T o(╥﹏╥)o)
    splay(findk(L,root),0);
    splay(findk(R+2,root),root);
    lazy[ch[rs][0]] ^= 1;
    pushup(rs);
    pushup(root);
}

void dfs(int now)
{
    pushdown(now);
    if(ls)
        dfs(ls);
    if(val[now]<=N&&val[now]>=1)
        printf("%d ",val[now]);
    if(rs)
        dfs(rs);
}

int main()
{
    int l,r;
    scanf("%d%d",&N,&M);
    newnode(0,root,0);
    newnode(N+1,ch[root][1],root);
    build(1,N,ch[ch[root][1]][0],ch[root][1]);
    while(M--)
    {
        scanf("%d%d",&l,&r);
        update(l,r);
    }
    dfs(root);
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
Last Updated: 4/3/2022, 1:11:43 AM