笛卡尔树学习笔记
前言
笛卡尔树是一种不那么常见的 \(\text{DS}\),但是最近也经常在一些 \(\text{CF}\) 题里看到,\(\text{NOIP2022}\) \(\text{T4}\) 的 \(\text{36pts}\) 和这玩意有关,所以来重新系统地学习一下。
性质
笛卡尔树的实质是堆+BST。
笛卡尔树是一种二叉树,每一个节点由一个键值二元组 \((k,w)\) 构成,其中 \(k\) 满足二叉搜索树的性质,而 \(w\) 满足堆的性质。 —— OI Wiki
我们通常所说的 “对一个序列建笛卡尔树”,意思就是将一个位置的下标和权值分别作为 \(k\) 和 \(w\),也就是说,对于一个节点 \(i\) 的左儿子 \(l_i\) 和右儿子 \(r_i\),一定满足 \(l_i < i < r_i\)(下标 \(k\) 满足二叉搜索树的性质)且 \(v_{l_i}\) 与 \(v_{r_i}\) 同时不大于或不小于 \(v_i\)(权值 \(w\) 满足堆的性质)。若序列的权值互不相同,则笛卡尔树形态唯一。———— Alex_wei
扔一张 OI-Wiki 的图:
从图可以看出,笛卡尔树的子树是对应序列上一段连续的区间。
构造
为了满足下标的 BST 性质,新加入的节点只会影响到树的最右链。
所以我们可以从左到右依次加入序列中的每个值,维护一个递减的单调栈表示当前笛卡尔树的最右链。
加入一个值 \(v_i\) (也就是下标为 \(i\))时,首先找到单调栈中最小的且大于 \(v\) 的值 \(w\),设其对应节点为 \(u\),那么将 \(u\) 的右儿子修改成 \(i\),将 \(i\) 的左儿子修改成 \(u\) 原来的右儿子(也就是单调栈中 \(u\) 上方的元素),再将 \(i\) 压入栈即可。
时间复杂度 \(O(n)\)
for(int i=1;i<=n;++i){
scanf("%d",&p[i]);
pos=top;
while(pos&&p[stk[pos]]>p[i]) pos--;
if(pos) rs[stk[pos]]=i;
if(pos<top) ls[i]=stk[pos+1];
stk[++pos]=i;
top=pos;
}
例题
咕咕咕
参考资料: