笛卡尔树学习笔记

前言

笛卡尔树是一种不那么常见的 \(\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;
}

例题

咕咕咕

参考资料:

简单树论 —— Alex_wei

笛卡尔树 —— OI-Wiki

posted @ 2022-12-02 22:35  Akane_Moon  阅读(84)  评论(2)    收藏  举报

“一言(ヒトコト)