
一、题目
二、分析
为什么会有伸展树?
伸展树与AVL的区别除了保持平衡的方式不同外,最重要的是在每次查找点时,让该点旋转到根结点,这里可以结合计算机里的局部性原理思考。
伸展树有什么优势?
有了伸展树,我们可以根据每次到根节点的值,根据二叉搜索树的性质,可以将整棵树划分成两个部分,左子树的值都比根结点值大,右子树的值都比根结点小。
该题除了伸展树还用到了什么?
该题还需要旋转,所以,需要像线段树的lazy标记一样标记是否需要旋转。
有什么需要注意的地方?
一定注意写法的不同,构建树的范围不同,如果没有定于null数组,则需要将建树范围从$[1,n]$扩为$[0,n]$,因为空指针的sum(表示以该结点为根的树的大小)是不清楚的。
三、AC代码
#include <cstdio>
#include <cstring>
#include <algorithm> using namespace std; struct Node
{
Node *ch[];
int sum, val;
int flip;
Node(int v = )
{
val = v;
sum = ;
flip = ;
ch[] = ch[] = NULL;
}
int cmp(int x)
{
int d = x - (ch[] == NULL ? : ch[]->sum);
if(d == ) return -;
else return d <= ? : ;
}
void maintain()
{
sum = ;
if(ch[] != NULL) sum += ch[]->sum;
if(ch[] != NULL) sum += ch[]->sum;
}
void pushdown()
{
if(flip)
{
flip = ;
swap(ch[], ch[]); //**
if(ch[] != NULL)
ch[]->flip = !ch[]->flip;
if(ch[] != NULL)
ch[]->flip = !ch[]->flip;
}
}
}; void build(Node* &o, int l, int r)
{
if(l > r)
return;
int mid = (l + r) >> ;
o = new Node(mid);
build(o->ch[], l, mid - );
build(o->ch[], mid + , r);
o->maintain();
} void rotate(Node* &o, int d)
{
Node *k = o->ch[d^];
o->ch[d^] = k->ch[d];
k->ch[d] = o;
o->maintain();
k->maintain();
o = k;
} void splay(Node* &o, int k)
{
o->pushdown();
int d = o->cmp(k);
if(d == )
k -= (o->ch[] == NULL ? : o->ch[]->sum) + ;
//当前结点不是第k个,则需要往上伸展
if(d != -)
{
Node *p = o->ch[d];
p->pushdown(); int d2 = p->cmp(k);
int k2 = (d2 == ? k : k - (p->ch[] == NULL ? :p->ch[]->sum) - );
if(d2 != -)
{
splay(p->ch[d2], k2);
//x,x的父节点,x祖父结点三点共线
if(d == d2) rotate(o, d^);
//不共线
else rotate(o->ch[d], d);
}
rotate(o, d^);
}
} void print(Node* &o) //一定要传引用
{
o->pushdown();
if(o->ch[] != NULL) print(o->ch[]);
if(o->val)
printf("%d\n", o->val);
if(o->ch[] != NULL) print(o->ch[]); delete o;
o = NULL;
} Node* merge(Node* left, Node* right)
{
//left不能为NULL
//因为初始化了sum为1,对于空指针的sum,不清楚大小
//如何避免:建树从0开始,即保证sum>0
splay(left, left->sum);
left->ch[] = right;
left->maintain();
return left;
} void split(Node *o, int k, Node* &left, Node* &right)
{
splay(o, k);
left = o;
right = o->ch[];
o->ch[] = NULL;
left->maintain();
} int main()
{
//freopen("input.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int N, M, a, b;
while(scanf("%d %d", &N, &M)==)
{
Node *root = NULL;
Node *left, *mid, *right, *o;
//上述模板注意不能用空指针
//必须从0开始,不然会RE
build(root, , N); for(int i = ; i < M; i++)
{
scanf("%d %d", &a, &b);
//树里面有0所以是a
split(root, a, left, o);
split(o, b - a + , mid, right);
mid->flip ^= ;
root = merge(merge(left, right), mid);
}
print(root);
}
return ;
}