传送门:点击打开链接
题意:....省略100字。。
思路:搞了两天的spay,终于明白了这个算法的优势以及用途和原理了。。。
首先是作用:splay是介于线段树和平衡树之间的,是一种平衡树,既能用来做set,也能用来做priority_queue,还能用来做线段树的一种万能树型数据结构!
与平衡树相比:与BST相比,BST的复杂度会退化,splay的复杂度平摊后是nlogn。与红黑树和SBT相比,在代码的编写上splay要简单一些,但是肯定比红黑树和BST稍微慢一点。
与线段树相比:首先基本能完成线段树的所有操作。但是,常数比线段树大。
然而也有线段树实现不了的功能,这才是我们对于一道题目为什么要选择splay这个数据结构的最重要的原因:
1.能实现区间插入。2.能实现区间删除。3.能实现区间翻转。
这3个是splay相比线段树独一无二的,这决定了splay巨大的价值!
splay网上的资料非常多,,我就说下我觉得容易混淆的地方。。
1.我这个模板的Rotate函数,c=0表示右旋,c=1表示左旋。
2.不要因为这个模板看起来比较短,,就怀疑它只是单旋,这个模板真的有双旋的。复杂度不会退化的放心。。
3.在Prepare函数里,为什么一开始要建立两个Node,这两个Node是端点,相当与第0个和第n+1个
4.为什么要在Prepare的最后Splay(3, 0),这是因为要把内容push_up到第1个节点和第2个节点上去。
5.如果想直接在Build里面读数值的时候,要注意scanf的位置,应该在两个Build的中间,因为splay是中序遍历才是原序列
6.为什么在求区间和的时候,要先Splay(l - 1, 0), 然后再Splay(r + 1, root),因为这样旋转后,son[son[root][1]][0]这棵树,就代表了[l, r]
7.splay的复杂度是平摊的,也就是说它并不是一颗完全二叉树,它可能第一次查询的时候接近O(n),但是后面查询又会非常快,这和并查集非常类似。
8.splay具有堆的性质,根节点大于等于左边树所有的节点,小于等于右边树所有的节点。因为这个性质,可以做到快速找第k大。
9.splay最重要的一个函数就是Splay函数,Splay(a, b)代表把节点a提到b的下面,如果b=0表示把a提到树根 ,所有的操作都是基于Splay函数的
10.splay有很多种旋转,这些旋转都是不会改变堆的性质的。
11.为什么网上经常有单旋和双旋的说法,单旋复杂度可能是会退化的,所以我们一般splay都是写双旋的,其实也并没有那么复杂,代码也不会特别长。
12.Select函数是用来找第k大的函数,然后有人会觉得里面判断向左和向右的那个数值好像有点不对劲,好像特意多算了1个数,那是因为我代码里在NewNode的时候,边界也算了一个size,所以那个多出来的1是为了平衡边界的那个节点的。
13.splay的功能很多基于Select,而它基于维护size,所以一般的splay都需要维护size
14.在Splay函数运行的同时,因为会改变树的结构,所以要动态的push_up和push_down,动态的维护节点的含义
15.基于区间的操作,通常就是用Select按照第6点所说的,搞出一个一个树,代表要操作的区间,然后再打懒惰标记,或者查询,或者删除之类的。
以上都是我学习完splay后的一些见解,如果有不对的地方,还请指出,谢谢拉~
#include<map> #include<set> #include<cmath> #include<ctime> #include<stack> #include<queue> #include<cstdio> #include<cctype> #include<string> #include<vector> #include<cstring> #include<iomanip> #include<iostream> #include<algorithm> #include<functional> #define fuck(x) cout<<"["<<x<<"]" #define FIN freopen("input.txt","r",stdin) #define FOUT freopen("output.txt","w+",stdout) using namespace std; typedef long long LL; typedef pair<int, int>PII; const int MX = 5e4 + 5; const int mod = 1e9 + 7; const int INF = 0x3f3f3f3f; int sum[MX], num[MX], size[MX]; int son[MX][2], fa[MX], root, sz; void Link(int x, int y, int c) { fa[x] = y; son[y][c] = x; } void push_up(int rt) { size[rt] = size[son[rt][0]] + size[son[rt][1]] + 1; sum[rt] = sum[son[rt][0]] + sum[son[rt][1]] + num[rt]; } void Rotate(int x, int c) { int y = fa[x]; Link(x, fa[y], son[fa[y]][1] == y); Link(son[x][!c], y, c); Link(y, x, !c); push_up(y); } void Splay(int x, int g) { while(fa[x] != g) { int y = fa[x], cx = son[y][1] == x, cy = son[fa[y]][1] == y; if(fa[y] == g) Rotate(x, cx); else { if(cx == cy) Rotate(y, cy); else Rotate(x, cx); Rotate(x, cy); } } push_up(x); if(!g) root = x; } void NewNode(int f, int &rt) { rt = ++sz; fa[rt] = f, size[rt] = 1; son[rt][0] = son[rt][1] = 0; num[rt] = sum[rt] = 0; } int Select(int k, int g) { int rt = root; while(size[son[rt][0]] != k) { if(size[son[rt][0]] > k) rt = son[rt][0]; else k -= size[son[rt][0]] + 1, rt = son[rt][1]; } Splay(rt, g); return rt; } void Build(int l, int r, int &rt, int f) { if(l > r) return; int m = (l + r) >> 1, t; NewNode(f, rt); Build(l, m - 1, son[rt][0], rt); scanf("%d", &t); num[rt] = sum[rt] = t; Build(m + 1, r, son[rt][1], rt); push_up(rt); } void Prepare(int n) { sz = 0; NewNode(0, root); NewNode(root, son[root][1]); Build(1, n, son[2][0], 2); Splay(3, 0); } int Query(int l, int r) { Select(l - 1, 0); Select(r + 1, root); return sum[son[son[root][1]][0]]; } int Update(int p, int s) { Select(p, 0); sum[root] += s; num[root] += s; } int main() { int T, ansk = 0, n; //FIN; scanf("%d", &T); while(T--) { scanf("%d", &n); Prepare(n); char op[10]; printf("Case %d:\n", ++ansk); while(scanf("%s", op), op[0] != 'E') { int a, b; scanf("%d%d", &a, &b); if(op[0] == 'Q') printf("%d\n", Query(a, b)); else if(op[0] == 'A') Update(a, b); else Update(a, -b); } } return 0; }