洛谷P2171 Hz 吐泡泡 题解 笛卡尔树

题目链接:https://www.luogu.com.cn/problem/P2171

题目要求我们按照输入顺序依次向一棵二叉查找树(BST)中插入泡泡,并最终输出这棵树的后序遍历和深度。

树可能退化成链,直接模拟插入是 \(O(n^2)\) 的。

但是可以发现一个关键性质:

按插入顺序构建出的二叉查找树,本质上就是一棵笛卡尔树。

为什么是笛卡尔树呢?

在二叉查找树中:

  • 所有节点的中序遍历一定是键值升序。
  • 插入时间越早的节点,越靠近根节点,即任意祖先的插入时间一定小于后代节点的插入时间。

我们把每个泡泡看成一个节点:

  • 节点的键值:泡泡的大小 \(a_i\);
  • 节点的权重:插入时间 \(i\)。

那么最终形成的 BST 满足:

  1. 中序遍历按键值升序;
  2. 祖先的权重小于后代权重,也就是小根堆性质。

这恰好就是笛卡尔树的定义:

笛卡尔树是一棵二叉树,中序遍历为给定序列,且满足堆性质。

因此,我们只需要按泡泡大小排序,得到中序序列对应的插入时间编号,然后以插入时间为权重构建笛卡尔树即可。

示例程序:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 5;

int n, depth, a[maxn], p[maxn], ls[maxn], rs[maxn];

void dfs(int u, int d, bool print = false) {
    if (!u) return;
    depth = max(depth, d);
    dfs(ls[u], d+1, print);
    dfs(rs[u], d+1, print);
    if (print) printf("%d\n", a[u]);
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", a+i);
    iota(p+1, p+n+1, 1);
    sort(p+1, p+n+1, [](auto i, auto j) {
        return a[i] < a[j];
      });
    stack<int> stk;
    for (int i = 1; i <= n; i++) {
        int x = p[i], last = 0;
        while (!stk.empty() && stk.top() > x) {
            last = stk.top();
            stk.pop();
        }
        if (!stk.empty()) rs[stk.top()] = x;
        ls[x] = last;
        stk.push(x);
    }
    int rt;
    do {
        rt = stk.top();
        stk.pop();
    } while (!stk.empty());
    dfs(rt, 1);
    printf("deep=%d\n", depth);
    dfs(rt, 1, true);
    return 0;
}
posted @ 2026-09-03 10:11  quanjun  阅读(13)  评论(0)    收藏  举报