伸展树是一枚二叉树,可以维护一个数列,或者可以作为二叉搜索树,因为无论怎么旋转,它的中序遍历是不变的。
1、伸展操作。
Splay(x,goal):旋转结点x,使它成为结点goal的儿子。
至于旋转,本质上只有左旋和右旋。
2、插入。
如果作为二叉搜索树,则插入与二叉搜索树一样,最后把该结点旋转到根。
如果用于维护一个数列,要求在x位置之前插入val(val可以为一个数字或一个序列),则将第x-1个结点旋转到根,将第x个结点旋转到第x-1个结点的儿子,那么根据中序遍历,val应插入在第x个结点的左儿子。
3、删除。
与插入同理。
4、Select(k)。
在二叉搜索树中,对每个结点维护一个num值,如果x左儿子的num+1=k,x就是第k小的数。
在维护序列中,显然就是第k个数。
5、翻转。
区间:将第a-1个数旋转到根,将第b+1个数旋转到a-1的儿子,那么b+1的左子树就是区间[a,b]。
在b+1的左儿子标记翻转,像线段树一样延迟标记,延迟标记往下推的时候交换左右儿子。
6、循环平移。
序列:1、2、3 --循环左移一位后-> 2、3、1。
本质上就是两个区间的交换。
坑爹的地方:
1、下标为0的结点作为边界,它的pre、next值被乱搞没关系,但是它维护的数值(比如 num,max,min...)不能影响到要维护的数列。
2、区间翻转,则左右儿子交换,可能左右儿子维护的值也需要交换。
3、不断的删除和插入一段序列时,可能会爆内存,删除的时候只好把删除的结点压到栈里,循环利用。
4、区间操作时,可以在序列的两端加入两个边界结点1和n+2。避免讨论,但它维护的数值不能影响到要维护的数列。
数据有问题,用Splay实现很裸的二叉搜索树。
依然是Splay实现二叉搜索树。
依然是Splay的操作。如果在平衡树中维护一段区间,蛋就碎了。
I命令:立刻离开不算入离开公司的员工的总数。
可以把数据分成两部分,在Splay中的部分,和待加入平衡树的部分。
把Splay中的数据同时加上val(val可能为负),相当于把待加入Splay的部分同时减去val,因为两个部分数据的相对值不变。
S命令:删除Splay中小于相对标准的子树。
Splay维护区间最值。
线段树是一个区间平分为两个子区间。
Splay根据中序遍历,把一个区间分为左、中、右。
维护上大同小异。
删除、插入、翻转,之前扯淡过了。
修改、求和、最大连续子序列和、都和线段树一样,翻转的时候左右连续的最大和要交换。
循环移动T次,T>0表示左移,T<0表示右移。取模以后都转换成左移即可。
很容易想到,5在1内可以把5作为1的儿子,那么可以得到一片森林,而且儿子可能很多。
那么可以用动态树,其实用Splay也可以做到。
那么建树完DFS一遍,可以得到一个或者多个括号序列。比如2在1内,3在1内,4在3内就是1 2 2 3 4 4 3 1。
接下来,可以用Splay维护多个序列。
询问root的时候,就是该序列的最左端的值。
移动的时候,就是把一段序列移到另一段序列中。
给出一个无向图,图中每个顶点都有一个权值:
1、删除一条边;
2、询问与顶点x连通的顶点中,第k大的值。
3、改变某个顶点的权值。
输出所有询问的平均值。
膜拜ghnjk大神的做法和他的AVL……
由于删一条边不能确定该图是否边分成两个连通分量,但是加入一条边可以判断是否把两个连通分量连通、或者还是一个连通分量(并查集)。
所以离线读入所有询问,把每个顶点的权值存入该顶点的邻接表。
初始时,每个顶点都是一个单独的Splay。
当合并两个集合时,将两枚Splay合并,由于两枚Splay的值不会总是严格大于对方,所以只好把小的Splay的每个顶点插入到大的Splay中。