author: sshwy, zhouyuyang2002, StudyingFather, Ir1d, ouuan, Enter-tainer, AtomAlpaca ## 引入 笛卡尔树是一种二叉树,每一个节点由一个键值二元组 $(k,w)$ 构成.要求 $k$ 满足二叉搜索树(BST)的性质,而 $w$ 满足堆的性质.如果笛卡尔树的 $k,w$ 键值确定,且 $k$ 互不相同,$w$ 也互不相同,那么这棵笛卡尔树的结构是唯一的.如下图: ![eg](./images/cartesian-tree-1.svg) (图源自维基百科) 上面这棵笛卡尔树相当于把数组元素值当作键值 $w$,而把数组下标当作键值 $k$.可以发现,这棵树的键值 $k$ 满足 BST 的性质,而键值 $w$ 满足小根堆的性质.同时根据二叉搜索树的性质,可以发现这种特殊的笛卡尔树满足一棵子树内的下标是一个连续区间. 竞赛中使用笛卡尔树时,常用数组下标作为二元组的键值 $k$,数组下标 $k$ 满足 BST 性质. 下文使用 $k,w$ 时,默认 $k$ 满足 BST 性质,$w$ 满足堆的性质. ## 单调栈构建笛卡尔树 ### 过程 我们考虑将元素按 $k$ 升序依次插入到当前的笛卡尔树中. 对于一棵笛卡尔树,定义「右链」为从根节点开始一直走右儿子,直到走到一个没有右儿子的节点形成的链.则插入节点后,这个节点一定在右链上.因为是按照满足 BST 性质的 $k$ 升序插入,那么这个新插入的节点必然在树的 **最右端**.这个节点不可能是一个左儿子,也没有右儿子. 于是我们执行这样一个过程,从下往上比较右链节点与当前节点 $u$ 的 $w$,如果找到了一个右链上的节点 $x$ 满足 $w_x 0 && w[stk[k]] > w[i]) k--; // 维护右链上的节点 if (k) rs[stk[k]] = i; // 栈顶元素.右儿子 := 当前元素 if (k < top) ls[i] = stk[k + 1]; // 当前元素.左儿子 := 上一个被弹出的元素 stk[++k] = i; // 当前元素入栈 top = k; } ``` ## 例题 ???+ note "[HDU 1506. Largest Rectangle in a Histogram](https://acm.hdu.edu.cn/showproblem.php?pid=1506)" $n$ 个位置,每个位置上的高度是 $h_i$,求最大子矩形.如下图: ![eg](./images/cartesian-tree-3.svg) 阴影部分就是图中的最大子矩阵. ??? note "解题思路" 具体地,我们把下标作为键值 $k$,$h_i$ 作为键值 $w$ 满足小根堆性质,构建一棵 $(i,h_i)$ 的笛卡尔树. 这样我们枚举每个节点 $u$,把 $w_u$(即节点 $u$ 的高度 $h$)作为最大子矩阵的高度.由于我们建立的笛卡尔树满足小根堆性质,因此 $u$ 的子树内的节点的高度都大于等于 $u$.而我们又知道 $u$ 子树内的下标是一段连续的区间.于是我们只需要知道子树的大小,然后就可以算这个区间的最大子矩阵的面积了.用每一个点计算出来的值更新答案即可.显然这个可以一次 DFS 完成,因此复杂度是 $O(n)$ 的. ??? note "参考实现" ```cpp --8<-- "docs/ds/code/cartesian-tree/cartesian-tree_1.cpp" ``` ## 参考资料 [笛卡尔树 - 维基百科](https://zh.wikipedia.org/wiki/%E7%AC%9B%E5%8D%A1%E5%B0%94%E6%A0%91)