一、题目
二、分析
为什么会有伸展树?
伸展树与AVL的区别除了保持平衡的方式不同外,最重要的是在每次查找点时,让该点旋转到根结点,这里可以结合计算机里的局部性原理思考。
伸展树有什么优势?
有了伸展树,我们可以根据每次到根节点的值,根据二叉搜索树的性质,可以将整棵树划分成两个部分,左子树的值都比根结点值大,右子树的值都比根结点小。
该题除了伸展树还用到了什么?
该题还需要旋转,所以,需要像线段树的lazy标记一样标记是否需要旋转。
有什么需要注意的地方?
一定注意写法的不同,构建树的范围不同,如果没有定于null数组,则需要将建树范围从$[1,n]$扩为$[0,n]$,因为空指针的sum(表示以该结点为根的树的大小)是不清楚的。
三、AC代码
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 5 using namespace std; 6 7 struct Node 8 { 9 Node *ch[2]; 10 int sum, val; 11 int flip; 12 Node(int v = 0) 13 { 14 val = v; 15 sum = 1; 16 flip = 0; 17 ch[0] = ch[1] = NULL; 18 } 19 int cmp(int x) 20 { 21 int d = x - (ch[0] == NULL ? 0 : ch[0]->sum); 22 if(d == 1) return -1; 23 else return d <= 0 ? 0 : 1; 24 } 25 void maintain() 26 { 27 sum = 1; 28 if(ch[0] != NULL) sum += ch[0]->sum; 29 if(ch[1] != NULL) sum += ch[1]->sum; 30 } 31 void pushdown() 32 { 33 if(flip) 34 { 35 flip = 0; 36 swap(ch[0], ch[1]); //** 37 if(ch[0] != NULL) 38 ch[0]->flip = !ch[0]->flip; 39 if(ch[1] != NULL) 40 ch[1]->flip = !ch[1]->flip; 41 } 42 } 43 }; 44 45 void build(Node* &o, int l, int r) 46 { 47 if(l > r) 48 return; 49 int mid = (l + r) >> 1; 50 o = new Node(mid); 51 build(o->ch[0], l, mid - 1); 52 build(o->ch[1], mid + 1, r); 53 o->maintain(); 54 } 55 56 void rotate(Node* &o, int d) 57 { 58 Node *k = o->ch[d^1]; 59 o->ch[d^1] = k->ch[d]; 60 k->ch[d] = o; 61 o->maintain(); 62 k->maintain(); 63 o = k; 64 } 65 66 67 void splay(Node* &o, int k) 68 { 69 o->pushdown(); 70 int d = o->cmp(k); 71 if(d == 1) 72 k -= (o->ch[0] == NULL ? 0 : o->ch[0]->sum) + 1; 73 //当前结点不是第k个,则需要往上伸展 74 if(d != -1) 75 { 76 Node *p = o->ch[d]; 77 p->pushdown(); 78 79 int d2 = p->cmp(k); 80 int k2 = (d2 == 0 ? k : k - (p->ch[0] == NULL ? 0 :p->ch[0]->sum) - 1); 81 if(d2 != -1) 82 { 83 splay(p->ch[d2], k2); 84 //x,x的父节点,x祖父结点三点共线 85 if(d == d2) rotate(o, d^1); 86 //不共线 87 else rotate(o->ch[d], d); 88 } 89 rotate(o, d^1); 90 } 91 } 92 93 94 void print(Node* &o) //一定要传引用 95 { 96 o->pushdown(); 97 if(o->ch[0] != NULL) print(o->ch[0]); 98 if(o->val) 99 printf("%d ", o->val); 100 if(o->ch[1] != NULL) print(o->ch[1]); 101 102 delete o; 103 o = NULL; 104 } 105 106 Node* merge(Node* left, Node* right) 107 { 108 //left不能为NULL 109 //因为初始化了sum为1,对于空指针的sum,不清楚大小 110 //如何避免:建树从0开始,即保证sum>0 111 splay(left, left->sum); 112 left->ch[1] = right; 113 left->maintain(); 114 return left; 115 } 116 117 118 void split(Node *o, int k, Node* &left, Node* &right) 119 { 120 splay(o, k); 121 left = o; 122 right = o->ch[1]; 123 o->ch[1] = NULL; 124 left->maintain(); 125 } 126 127 128 int main() 129 { 130 //freopen("input.txt", "r", stdin); 131 //freopen("out.txt", "w", stdout); 132 int N, M, a, b; 133 while(scanf("%d %d", &N, &M)==2) 134 { 135 Node *root = NULL; 136 Node *left, *mid, *right, *o; 137 //上述模板注意不能用空指针 138 //必须从0开始,不然会RE 139 build(root, 0, N); 140 141 for(int i = 0; i < M; i++) 142 { 143 scanf("%d %d", &a, &b); 144 //树里面有0所以是a 145 split(root, a, left, o); 146 split(o, b - a + 1, mid, right); 147 mid->flip ^= 1; 148 root = merge(merge(left, right), mid); 149 } 150 print(root); 151 } 152 return 0; 153 }