笛卡尔树—简介

笛卡尔树是堆与二叉查找树的结合版本。所以他当然是一棵二叉树,然后又具有小根堆的性质。

每一个节点有一个键值二元组组成 ( k , w ) (k,w) (k,w)构成。如果笛卡尔树的 ( k , w ) (k,w) (k,w)是确定的,那么笛卡尔树的结构是惟一的。
在这里插入图片描述

其实图中的笛卡尔树是一种特殊的情况,因为二元组的键值 k k k恰好对应数组下标,这种特殊的笛卡尔树有一个性质,就是一棵子树内的下标是连续的一个区间(这样才能满足二叉搜索树的性质)。更一般的情况则是任意二元组构建的笛卡尔树。

构建

在这里插入图片描述

始终维护右链(即图中红色框中的链),可以用栈来维护,也可以直接从链的最后一个节点开始往前逐步枚举(则需要记录一个father数组),二者是等价的。

一直枚举直到出现一个小于自己的节点,然后考虑接在这个节点的右儿子处,如果不行,则将该节点及其子树全部作为新节点的左儿子。

用栈来建树

for (int i = 1; i <= n; i++) {
  int k = top;
  while (k > 0 && h[stk[k]] > h[i]) k--;
  if (k) rs[stk[k]] = i;  // rs代表笛卡尔树每个节点的右儿子
  if (k < top) ls[i] = stk[k + 1];  // ls代表笛卡尔树每个节点的左儿子
  stk[++k] = i;
  top = k;
}

用father来建树

int cartesian_build(int n) {  // 建树,满足小根堆性质
  for (int i = 1; i <= n; i++) {
    int k = i - 1;
    while (tree[k].val > tree[i].val) k = tree[k].par;
    tree[i].ch[0] = tree[k].ch[1];
    tree[k].ch[1] = i;
    tree[i].par = k;
    tree[tree[i].ch[0]].par = i;
  }
  return tree[0].ch[1];
}

应用

经典例题:HDU 1509最大子矩形

题目大意: n n n个位置,每个位置上的高度是 h i h_i hi,求最大子矩阵。

可用单调栈完成。

我们把下标作为键值 k k k h i h_i hi作为键值 w w w满足小根堆性质,构建一棵 ( i , h i ) (i,h_i) (i,hi)的笛卡尔树。答案为该点的 w w w乘子树大小(表示长度,由于其连续的性质)。也可以在 O ( n ) O(n) O(n)内完成。

参考代码(来自OI-WIKI):

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 100000 + 10, INF = 0x3f3f3f3f;

struct node {
  int idx, val, par, ch[2];

  friend bool operator<(node a, node b) { return a.idx < b.idx; }

  void init(int _idx, int _val, int _par) {
    idx = _idx, val = _val, par = _par, ch[0] = ch[1] = 0;
  }
} tree[N];

int root, top, stk[N];
ll ans;

int cartesian_build(int n) {  // 建树,满足小根堆性质
  for (int i = 1; i <= n; i++) {
    int k = i - 1;
    while (tree[k].val > tree[i].val) k = tree[k].par;
    tree[i].ch[0] = tree[k].ch[1];
    tree[k].ch[1] = i;
    tree[i].par = k;
    tree[tree[i].ch[0]].par = i;
  }
  return tree[0].ch[1];
}

int dfs(int x) {  // 一次dfs更新答案就可以了
  if (!x) return 0;
  int sz = dfs(tree[x].ch[0]);
  sz += dfs(tree[x].ch[1]);
  ans = max(ans, (ll)(sz + 1) * tree[x].val);
  return sz + 1;
}

int main() {
  int n, hi;
  while (scanf("%d", &n), n) {
    tree[0].init(0, 0, 0);
    for (int i = 1; i <= n; i++) {
      scanf("%d", &hi);
      tree[i].init(i, hi, 0);
    }
    root = cartesian_build(n);
    ans = 0;
    dfs(root);
    printf("%lld\n", ans);
  }
  return 0;
}
好的,关于用 C 语言建立笛卡尔树求解区间最值问题,我可以给你一些基本的思路和代码实现。 首先,笛卡尔树是带权值的树,其性质由下面的定理给出: 定理1:一个序列可以对应唯一的一棵笛卡尔树。 定理2:设在笛卡尔树上节点 u 和 v 的先后顺序为“按下标增大的顺序,在这个前提下按权值减小的顺序”,则 u 到 v(包括 u 和 v)路径上的点在原序列中构成了一个区间。 基于这两个定理,我们就可以使用笛卡尔树来解决区间最值问题,其基本思路如下: 1. 输入一个序列 a[N],构建对应的笛卡尔树。 2. 对于每个节点 u,记录其子树中最小值或最大值(根据具体问题而定)。 3. 对于每个询问,找到笛卡尔树上 u 和 v 两个节点,使得它们对应的区间包含询问区间 [L,R],那么 [L,R] 中的最值就是从 u 到 v 路径上的最小值或最大值(可以使用 st 表等数据结构进行维护)。 下面是用 C 语言实现笛卡尔树的基本代码,可以参考一下: struct Node { int val, idx; int fa, ch[2]; } tree[N]; int root, idx; int build(int l, int r) { // 构建笛卡尔树并返回根节点编号 if (l > r) return -1; int x = l; for (int i = l + 1; i <= r; i++) if (tree[i].val < tree[x].val) x = i; tree[x].ch[0] = build(l, x - 1); tree[x].ch[1] = build(x + 1, r); if (tree[x].ch[0] != -1) tree[tree[x].ch[0]].fa = x; if (tree[x].ch[1] != -1) tree[tree[x].ch[1]].fa = x; return x; } 现在我回答了你的问题,如果您有任何其他问题,可以随时问我。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值