洛谷P2171 Hz 吐泡泡 题解 笛卡尔树
题目链接:https://www.luogu.com.cn/problem/P2171
题目要求我们按照输入顺序依次向一棵二叉查找树(BST)中插入泡泡,并最终输出这棵树的后序遍历和深度。
树可能退化成链,直接模拟插入是 \(O(n^2)\) 的。
但是可以发现一个关键性质:
按插入顺序构建出的二叉查找树,本质上就是一棵笛卡尔树。
为什么是笛卡尔树呢?
在二叉查找树中:
- 所有节点的中序遍历一定是键值升序。
- 插入时间越早的节点,越靠近根节点,即任意祖先的插入时间一定小于后代节点的插入时间。
我们把每个泡泡看成一个节点:
- 节点的键值:泡泡的大小 \(a_i\);
- 节点的权重:插入时间 \(i\)。
那么最终形成的 BST 满足:
- 中序遍历按键值升序;
- 祖先的权重小于后代权重,也就是小根堆性质。
这恰好就是笛卡尔树的定义:
笛卡尔树是一棵二叉树,中序遍历为给定序列,且满足堆性质。
因此,我们只需要按泡泡大小排序,得到中序序列对应的插入时间编号,然后以插入时间为权重构建笛卡尔树即可。
示例程序:
#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;
}
浙公网安备 33010602011771号