3303 翻转区间 伸展树的解法

题目描述 Description

您需要写一种数据结构(可参考题目标题),来维护一个有序数列,序列初始化为1、2、3、4……,

操作如下:翻转一个区间,例如原有序序列是1 2 3 4 5,翻转区间是[2,4]的话,结果是1 4 2 3 5,如果在此基础上(1 4 2 3 5)再次翻转的区间为[3 5],那么结果为:1 4 5 3 2

输入描述 Input Description

第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2……n-1,n)
 m表示翻转操作次数,接下来m行每行两个数[l,r] 数据保证 1 < =l < = r < =n

输出描述 Output Description

输出一行n个数字,表示原始序列经过m次变换后的结果

样例输入 Sample Input

5 3

1 3

1 3

1 4

 

样例输出 Sample Output

4 3 2 1 5

数据范围及提示 Data Size & Hint

(n,m < =100000)

题目分析:

题目给定一个递增序列,以1 2 3 4 5 为例,要求对某个子区间旋转。 

例如 1-3, 1-4 。 先将1号位置和3位置翻转后,得 32145 然后对1号对4号位置区间旋转,得到41235。  翻转不管怎么翻,是与位置相关,而位置的1...n 顺序是不会变的。 

将这个连续的位置(1,2,3,4,5,6,7 其中 1号,7号位置是边界 2,3,4,5,6 分别表示原来的1号位到5号位)构建成一颗树。得到下图一所示的树。

翻转的位置对应中序遍历过程中第i个结点的位置。


3303 翻转区间 伸展树的解法_第1张图片

          图1

若1-3位置进行翻转,即图中2-4号结点翻转即可,那么只要将2,3,4放到某颗子树中,然后对这颗子树通过递归地交换左右子枝,最后中序遍历即可得到翻转的结果。

因此将 1号边界调整为根,5号(2-4的右边界)调整为1号的右子树,而将剩余的结点“卡”在这两个结点之间,随后对这两个(1,5)结点

间的子树递归的交换其左右子树即可。 而调整1,5通过伸展操作,进行调整,调整完毕结果如下图2。

3303 翻转区间 伸展树的解法_第2张图片

图2

递归交换4这颗子树的左右枝得到图3所示的树。

3303 翻转区间 伸展树的解法_第3张图片

图3

中序遍历(边界点不打印)这颗树得到序列43256 (打印出来的值为32145),那么

原位置与值对应关系

      1    2  3  4  5  6  7

      INF 1  2  3  4  5  INF

变为

     1    2  3  4  5  6  7       

     INF 3  2  1  4  5  INF


需要注意的是,当给定多个要翻转的区间时,边界结点位置是中序遍历过程中第 i 个结点的位置,

例如给定 1-3 , 2-4,第一次splay操作后将4,3,2 卡在了1,5之间,其边界是1,和5,而1是图一中,中序遍历得到的第一个结点,即1,而5是中序遍历得到的第5个结点,即5。而第二次,反转2-4时,边界是2,6(1号是INF,2记录1号位置...n+1号位置记录n),那么图3中中序遍历访问的第2个点是4,中序遍历的第6个点是6号。

如上所述一次反转操作涉及到两个点的“伸展”操作,而伸展操作完毕后,对子树的根需要加上翻转标记,当被打印,或下一次伸展操作时,更新标记,并向下传递,更新子树的标记,即所说的延迟更新。

伸展树的基本内容可参考 [1]。

最后再说一下翻转时的两次伸展操作,其中以l,r 表示序列的左右边界。

第一次将左边界(中序遍历中第l个位置上的点)调整到根root的位置

splay(root,l)   

当调整完后l成为新的根。

经过第一次调整后,l之后的点都在根的右侧,因此第2次将第中序遍历中第r+1(r的边界)个位置上的点调整到新根的右子树根位置, 

splay(right[l],r+1-right[l].len); 

主要需要了解的就这些了,代码可以参考[2]中的代码。

参考资料:

[1] 伸展树wikiepedia讲解,http://en.wikipedia.org/wiki/Splay_tree

[2] 代码参考 https://code.csdn.net/snippets/608231

你可能感兴趣的:(伸展树,翻转区间)