【数据结构】笛卡尔树
把一个数组的元素,从左到右插入笛卡尔树,可以用栈O(n)地构建出来。笛卡尔树上的节点满足堆的性质(小根堆就是一个节点小于其两个子节点的权值)。所以用这个方式扫描出的笛卡尔树,一棵子树就是对应一段连续的区间,而子树的根节点就是这段区间的最值(小根堆就是最小值)。
以最大矩形面积的小根堆为例:
从根节点一直往右走,形成的链称为右链。
从下往上逐个检查右链上的节点,找到第一个比当前节点u的权值小的点x,在那之前的点都直接忽视掉。
由于点x的权值比u小,所以u就要作为x的其中一个子节点,这里就让它做x的右节点,成为新的右链的末端。而之前被忽略掉的点,就作为u的左子树。相当于就是:
- 把右链逐个弹出,把最后一个点y(如果存在)完整的整棵树取下来,直到右链的第一个点x小于u
- u接在x的右子树,这样堆的性质满足
- 取出的子树接在u的左子树,堆的性质满足
- 相当于就是u终结了被取出子树的根节点y的右延展性,被u节点掐断了,这是因为u比y小,阻断了y
static const int MAXN = 200000 + 10;
int h[MAXN];
int rt;
int lc[MAXN];
int rc[MAXN];
void build (int n) {
static int stk[MAXN];
memset (stk, 0, sizeof (lc[0]) * (n + 1));
memset (lc, 0, sizeof (lc[0]) * (n + 1));
memset (rc, 0, sizeof (lc[0]) * (n + 1));
for (int i = 1, top = 0; i <= n; i++) {
int k = top;
while (k >= 1 && h[stk[k]] > h[i]) { // 如果是大根堆,则翻转h数组的比较符号,其他的都不用改
k--;
}
if (k >= 1) {
// 节点i的权值>=栈顶节点的权值,挂在栈顶节点的右子树下
rc[stk[k]] = i;
}
if (k + 1 <= top) {
// 栈顶节点原本的右子树权值>节点i的权值,改为挂在节点i的左子树下
lc[i] = stk[k + 1];
}
stk[++k] = i;
top = k;
}
rt = stk[1]; // 笛卡尔树的根
}
如果树里面有重复的元素,会怎么样呢?上面的这个实现中,会统统挂在最先进入笛卡尔树(最左边)的重复元素下面。其实是不影响的。
构造出笛卡尔树后,可以用一次简单的深搜算出答案。树的size是否可以在构建时就算出来呢?其实是可以的,元素出栈(离开右链)之后就不会动到其中的子树了,这个区间就固定下来了。需要做的其实就是不断pop栈把size更新一下。然后对于计算最大矩形面积,也可以顺便更新答案。
但是搞得太不直观了,不利于扩展,直接用dfs就好。dfs的话就不用维护多一个size,直接return就行。
ll ans = 0;
int dfs (int u) {
if (u == 0) {
return 0;
}
int siz = dfs (lc[u]) + 1 + dfs (rc[u]);
ans = max (ans, 1LL * siz * h[u]);
return siz;
}
比起两次扫描单调栈才能确定边界,也有很多+1-1的问题,这个算法算是非常简单了。额外的空间其实也没有多用多少,反正都是O(n)。
小根笛卡尔树,带有管辖范围的版本,mostL[i]和mostR[i]就是节点i作为最小值的管辖范围,意味着[mostL, mostR]的最小值是h[i],而往左或者往右都会比它更小或者触碰到边界。
struct MinTree {
int rt;
int lc[MAXN];
int rc[MAXN];
int mostL[MAXN];
int mostR[MAXN];
void build (int n) {
static int stk[MAXN];
memset (stk, 0, sizeof (lc[0]) * (n + 1));
memset (lc, 0, sizeof (lc[0]) * (n + 1));
memset (rc, 0, sizeof (lc[0]) * (n + 1));
for (int i = 1, top = 0; i <= n; i++) {
int k = top;
while (k >= 1 && a[stk[k]] > a[i]) {
k--;
}
if (k >= 1) {
// 节点i的权值>=栈顶节点的权值,挂在栈顶节点的右子树下
rc[stk[k]] = i;
}
if (k + 1 <= top) {
// 栈顶节点原本的右子树权值>节点i的权值,改为挂在节点i的左子树下
lc[i] = stk[k + 1];
}
stk[++k] = i;
top = k;
}
rt = stk[1]; // 笛卡尔树的根
}
// 计算每个节点最左和最右能管辖到哪里
void most (int n) {
for (int i = 1; i <= n; ++i) {
mostL[i] = n + 1;
mostR[i] = 0;
}
dfs (rt);
}
void dfs (int u) {
if (lc[u]) {
// 如果有左子树,那么把左子树能管的最左边管了
dfs (lc[u]);
mostL[u] = mostL[lc[u]];
} else {
// 否则只能管自己
mostL[u] = u;
}
if (rc[u]) {
// 如果有右子树,那么把右子树能管的最左边管了
dfs (rc[u]);
mostR[u] = mostR[rc[u]];
} else {
// 否则只能管自己
mostR[u] = u;
}
}
} miTree;