Loading docs/ds/cartesian-tree.md +20 −9 Original line number Diff line number Diff line author: sshwy, zhouyuyang2002, StudyingFather, Ir1d, ouuan, Enter-tainer 本文介绍一种不太常用,但是与大家熟知的平衡树与堆密切相关的数据结构——笛卡尔树。 笛卡尔树是一种二叉树,每一个结点由一个键值二元组 $(k,w)$ 构成。要求 $k$ 满足二叉搜索树的性质,而 $w$ 满足堆的性质。一个有趣的事实是,如果笛卡尔树的 $k,w$ 键值确定,且 $k$ 互不相同, $w$ 互不相同,那么这个笛卡尔树的结构是唯一的。上图: Loading @@ -12,9 +14,7 @@ ## 构建 既然笛卡尔树有具有两种不同结构的性质,那么就有两种构建方法。其中一种的复杂度是 $O(n)$ ,另一种是 $O(n\log n)$ 。 ### 栈构建 ## 栈构建 我们考虑将元素按照键值 $k$ 排序。然后一个一个插入到当前的笛卡尔树中。那么每次我们插入的元素必然在这个树的右链(右链:即从根结点一直往右子树走,经过的结点形成的链)的末端。于是我们执行这样一个过程,从下往上比较右链结点与当前结点 $u$ 的 $w$ ,如果找到了一个右链上的结点 $x$ 满足 $x_w<u_w$ ,就把 $u$ 接到 x 的右儿子上,而 x 原本的右子树就变成 u 的左子树。 Loading @@ -22,11 +22,22 @@  显然每个数最多进出右链一次(或者说每个点在右链中存在的是一段连续的时间)。这个过程我们可以用栈维护,栈中维护当前笛卡尔树的右链上的结点。一个点不在右链上了就把它弹掉。这样每个点最多进出一次,复杂度 $O(n)$ 。 ## 另一种构建 还有一种构建方式与之对应。我们按照 $w$ 排序,始终维护堆的结构。这样插入一个结点,相当于添加一个叶子结点。于是我们像平衡树那样插入就行了。这样每一次插入的复杂度是 $O(\log n)$ 的,因此总复杂度是 $O(n\log n)$ 的。 显然每个数最多进出右链一次(或者说每个点在右链中存在的是一段连续的时间)。这个过程我们可以用栈维护,栈中维护当前笛卡尔树的右链上的结点。一个点不在右链上了就把它弹掉。这样每个点最多进出一次,复杂度 $O(n)$ 。伪代码如下: ```text 新建一个大小为n的空栈。 For i:=1 to n flag = 0 While 栈不为空 if 栈顶元素<当前元素 栈顶元素.右儿子=当前元素 break else 栈顶元素.右儿子=当前元素.左儿子。 当前元素.左儿子=栈顶元素 栈顶元素出栈 当前元素入栈。 ``` ## 笛卡尔树与 Treap Loading @@ -44,7 +55,7 @@ HDU 1506 最大子矩形 这道题你可 DP,可单调栈,但你万万没想到的是它也可以笛卡尔树!具体地,我们把下标作为键值 $k$ , $h_i$ 作为键值 $w$ 满足小根堆性质,构建一棵 $(i,h_i)$ 的笛卡尔树。 这样我们枚举每个结点 $u$ ,把 $u_w$ (即结点 u 的高度键值 $h$ )作为最大子矩阵的高度。由于我们建立的笛卡尔树满足小根堆性质,因此 $u$ 的子树内的结点的高度都大于等于 $u$ 。而我们又知道 $u$ 子树内的下标是一段连续的区间。于是我们只需要知道子树内的下标最小值和最大值即可,换言之,就是 $u$ 子树内的左链和右链末端的结点的下标键值。我们对每个点这样求,最后取面积最大值即可。显然这个可以一次 DFS 完成,因此复杂度仍是 $O(n)$ 的。 这样我们枚举每个结点 $u$ ,把 $u_w$ (即结点 u 的高度键值 $h$ )作为最大子矩阵的高度。由于我们建立的笛卡尔树满足小根堆性质,因此 $u$ 的子树内的结点的高度都大于等于 $u$ 。而我们又知道 $u$ 子树内的下标是一段连续的区间。于是我们只需要知道子树的大小,然后就可以算这个区间的最大子矩阵的面积了。用每一个点计算出来的值更新答案即可。显然这个可以一次 DFS 完成,因此复杂度仍是 $O(n)$ 的。 ```cpp #include <algorithm> Loading docs/topic/rmq.md +2 −29 Original line number Diff line number Diff line Loading @@ -51,37 +51,10 @@ Four russian 是一个由四位俄罗斯籍的计算机科学家提出来的基 这样子我们只需要在询问的时候进行至多一次 ST 表上的查询操作了。 ## 笛卡尔树以及其构造 在实现更加优秀的时间复杂度之前,我们先来介绍一个数据结构:笛卡尔树。 笛卡尔树 (Cartesian tree) 的本质是一个二叉小根/大根堆。同时笛卡尔数满足对其中序遍历的结果等于原来的数组 A。 下面是一个笛卡尔树的具体例子:  接下来笔者将讲述一个构造笛卡尔树的 $O(n)$ 算法。 ```text 新建一个大小为n的空栈。 For i:=1 to n flag = 0 While 栈不为空 if 栈顶元素<当前元素 栈顶元素.右儿子=当前元素 break else 栈顶元素.右儿子=当前元素.左儿子。 当前元素.左儿子=栈顶元素 栈顶元素出栈 当前元素入栈。 ``` 由于一个元素只会入栈一次,出栈一次,所以总复杂度为 $O(n)$ 。 ## 笛卡尔树在 RMQ 上的应用 不了解笛卡尔树的朋友请移步 [笛卡尔树](../ds/cartesian-tree.md) 。 我们发现,原序列上两个点之间的 min/max,等于笛卡尔树上两个点的 LCA 的权值。 这也说明,我们现在需要去解决的是如何 $O(n)-O(1)$ 树上两个点之间的 LCA 的。 Loading Loading
docs/ds/cartesian-tree.md +20 −9 Original line number Diff line number Diff line author: sshwy, zhouyuyang2002, StudyingFather, Ir1d, ouuan, Enter-tainer 本文介绍一种不太常用,但是与大家熟知的平衡树与堆密切相关的数据结构——笛卡尔树。 笛卡尔树是一种二叉树,每一个结点由一个键值二元组 $(k,w)$ 构成。要求 $k$ 满足二叉搜索树的性质,而 $w$ 满足堆的性质。一个有趣的事实是,如果笛卡尔树的 $k,w$ 键值确定,且 $k$ 互不相同, $w$ 互不相同,那么这个笛卡尔树的结构是唯一的。上图: Loading @@ -12,9 +14,7 @@ ## 构建 既然笛卡尔树有具有两种不同结构的性质,那么就有两种构建方法。其中一种的复杂度是 $O(n)$ ,另一种是 $O(n\log n)$ 。 ### 栈构建 ## 栈构建 我们考虑将元素按照键值 $k$ 排序。然后一个一个插入到当前的笛卡尔树中。那么每次我们插入的元素必然在这个树的右链(右链:即从根结点一直往右子树走,经过的结点形成的链)的末端。于是我们执行这样一个过程,从下往上比较右链结点与当前结点 $u$ 的 $w$ ,如果找到了一个右链上的结点 $x$ 满足 $x_w<u_w$ ,就把 $u$ 接到 x 的右儿子上,而 x 原本的右子树就变成 u 的左子树。 Loading @@ -22,11 +22,22 @@  显然每个数最多进出右链一次(或者说每个点在右链中存在的是一段连续的时间)。这个过程我们可以用栈维护,栈中维护当前笛卡尔树的右链上的结点。一个点不在右链上了就把它弹掉。这样每个点最多进出一次,复杂度 $O(n)$ 。 ## 另一种构建 还有一种构建方式与之对应。我们按照 $w$ 排序,始终维护堆的结构。这样插入一个结点,相当于添加一个叶子结点。于是我们像平衡树那样插入就行了。这样每一次插入的复杂度是 $O(\log n)$ 的,因此总复杂度是 $O(n\log n)$ 的。 显然每个数最多进出右链一次(或者说每个点在右链中存在的是一段连续的时间)。这个过程我们可以用栈维护,栈中维护当前笛卡尔树的右链上的结点。一个点不在右链上了就把它弹掉。这样每个点最多进出一次,复杂度 $O(n)$ 。伪代码如下: ```text 新建一个大小为n的空栈。 For i:=1 to n flag = 0 While 栈不为空 if 栈顶元素<当前元素 栈顶元素.右儿子=当前元素 break else 栈顶元素.右儿子=当前元素.左儿子。 当前元素.左儿子=栈顶元素 栈顶元素出栈 当前元素入栈。 ``` ## 笛卡尔树与 Treap Loading @@ -44,7 +55,7 @@ HDU 1506 最大子矩形 这道题你可 DP,可单调栈,但你万万没想到的是它也可以笛卡尔树!具体地,我们把下标作为键值 $k$ , $h_i$ 作为键值 $w$ 满足小根堆性质,构建一棵 $(i,h_i)$ 的笛卡尔树。 这样我们枚举每个结点 $u$ ,把 $u_w$ (即结点 u 的高度键值 $h$ )作为最大子矩阵的高度。由于我们建立的笛卡尔树满足小根堆性质,因此 $u$ 的子树内的结点的高度都大于等于 $u$ 。而我们又知道 $u$ 子树内的下标是一段连续的区间。于是我们只需要知道子树内的下标最小值和最大值即可,换言之,就是 $u$ 子树内的左链和右链末端的结点的下标键值。我们对每个点这样求,最后取面积最大值即可。显然这个可以一次 DFS 完成,因此复杂度仍是 $O(n)$ 的。 这样我们枚举每个结点 $u$ ,把 $u_w$ (即结点 u 的高度键值 $h$ )作为最大子矩阵的高度。由于我们建立的笛卡尔树满足小根堆性质,因此 $u$ 的子树内的结点的高度都大于等于 $u$ 。而我们又知道 $u$ 子树内的下标是一段连续的区间。于是我们只需要知道子树的大小,然后就可以算这个区间的最大子矩阵的面积了。用每一个点计算出来的值更新答案即可。显然这个可以一次 DFS 完成,因此复杂度仍是 $O(n)$ 的。 ```cpp #include <algorithm> Loading
docs/topic/rmq.md +2 −29 Original line number Diff line number Diff line Loading @@ -51,37 +51,10 @@ Four russian 是一个由四位俄罗斯籍的计算机科学家提出来的基 这样子我们只需要在询问的时候进行至多一次 ST 表上的查询操作了。 ## 笛卡尔树以及其构造 在实现更加优秀的时间复杂度之前,我们先来介绍一个数据结构:笛卡尔树。 笛卡尔树 (Cartesian tree) 的本质是一个二叉小根/大根堆。同时笛卡尔数满足对其中序遍历的结果等于原来的数组 A。 下面是一个笛卡尔树的具体例子:  接下来笔者将讲述一个构造笛卡尔树的 $O(n)$ 算法。 ```text 新建一个大小为n的空栈。 For i:=1 to n flag = 0 While 栈不为空 if 栈顶元素<当前元素 栈顶元素.右儿子=当前元素 break else 栈顶元素.右儿子=当前元素.左儿子。 当前元素.左儿子=栈顶元素 栈顶元素出栈 当前元素入栈。 ``` 由于一个元素只会入栈一次,出栈一次,所以总复杂度为 $O(n)$ 。 ## 笛卡尔树在 RMQ 上的应用 不了解笛卡尔树的朋友请移步 [笛卡尔树](../ds/cartesian-tree.md) 。 我们发现,原序列上两个点之间的 min/max,等于笛卡尔树上两个点的 LCA 的权值。 这也说明,我们现在需要去解决的是如何 $O(n)-O(1)$ 树上两个点之间的 LCA 的。 Loading