笛卡尔树

笛卡尔树是一种特定的二叉树,可由数列构造,在范围最值查询、范围 \(top k\) 查询 \((range top k queries)\)等问题上有广泛应用。它具有堆的有序性,中序遍历可以输出原数列。——摘自百度百科

笛卡尔树每一个结点由一个键值二元组 \(k\) 构成。要求 \(k\) 满足二叉搜索树的性质,而 \(v\) 满足堆的性质。如图:

学习笔记 【笛卡尔树】_i++

笛卡尔树有这样的性质:

对于树上的任何一个节点 \(x\) 和左右儿子 \(ls\)\(rs\) 有:

  1. \(pos[\,ls\,]<pos[\,x\,]<pos[\,rs\,]\)
  2. \(val[\,x\,]<val[\,ls\,],val[\,rs\,]\) (大根堆时相反)。

即一棵笛卡尔树的 \(pos\) 满足二叉查找树, \(val\) 满足堆。

笛卡尔树的建树过程:

我们优先满足 \(pos\) 即按顺序依次插入,并调整树的结构,使之满足堆的性质。

所以我们要让新插入的点 \(x\) 在这棵树的最右面,沿着树根向右儿子走分两种情况:

  1. 这条链上 \(val\) 都比 \(val[\,x\,]\) 小(小根堆,大根堆相反),那么就让 \(x\) 作为最后一个点的右儿子。
  2. 找到了一个点 \(y\) \(val[\,y\,]>val[\,x\,]\) 此时为了满足堆的性质,\(y\) 必须作为 \(x\) 的儿子且必须是左儿子。

我们使用单调栈来维护笛卡尔树最右儿子链 \(val\) 的递增(递减)性。

建树过程的具体实现:

学习笔记 【笛卡尔树】_中序遍历_02

学习笔记 【笛卡尔树】_i++_03

学习笔记 【笛卡尔树】_中序遍历_04

学习笔记 【笛卡尔树】_大根堆_05

学习笔记 【笛卡尔树】_中序遍历_06

学习笔记 【笛卡尔树】_大根堆_07

学习笔记 【笛卡尔树】_小根堆_08

这样一棵笛卡尔树就建好了。

代码
#include<cstdio>
using namespace std;
typedef long long LL;
const int N=1e7+5;
int n; 
int a[N];
int st[N],top;
int ls[N],rs[N];
inline void build(){
	for(int i=1;i<=n;i++){
		while(top&&a[st[top]]>a[i])
			ls[i]=st[top--];
		rs[st[top]]=i;
		st[++top]=i;
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	build();
	LL lans=0,rans=0;
	for(int i=1;i<=n;i++)
		lans^=(LL)i*(ls[i]+1),rans^=(LL)i*(rs[i]+1);
	printf("%lld %lld",lans,rans);
	return 0;
}

笛卡尔树的功能:

最简单的一个应用是求元素的左右延伸区间。根据笛卡尔树的性质, \(x\) 一定是 \(x\) 的子树中 \(val\) 最小的(小根堆)。这样我们再对树进行中序遍历,记录下 \(x\) 子树中最左最右的 \(pos\) 即可。

例题:

UVA1619 感觉不错 Feel Good
这是题解

SP1805 HISTOGRA - Largest Rectangle in a Histogram

P2659 美丽的序列 这是题解

AT2060 [AGC005B] Minimum Sum