首页 > 其他分享 >笛卡尔树

笛卡尔树

时间:2023-01-16 18:56:00浏览次数:50  
标签:return sta 笛卡尔 int top ll

这篇文章有一部分是 2021 年写的,当时完全没有理解笛卡尔树的本质。

笛卡尔树就是最值分治的搜索树。所以最容易的构建笛卡尔树的方式为:

int get(int l,int r){
	// 返回 [l,r] 中最值对应的下标
}
void solve(int p,int l,int r){
	if(l>=r)return;
	solve(lc[p]=get(l,p-1),l,p-1);
	solve(rc[p]=get(p+1,r),p+1,r);
}

笛卡尔树的定义

笛卡尔树是一种二叉树,其中的每个节点的权值为 \((p,w)\)。其中,单看权值 \(p\),这是一棵二叉搜索树。单看权值 \(w\),这是一个堆。

如果在序列 \(a\) 上建立笛卡尔树,每个点的权值为 \((i,a_i)\),那么一个节点实际上代表了它子树所覆盖的区间的最值。这是笛卡尔树最常见的性质。

笛卡尔树的建立

上面的构建方法较为常见,用 ST 表维护最值。不过下面的方法更简洁,并且是线性的。

使用一个栈构建笛卡尔树。构建时,需要知道每个点的权值 \((p,w)\),以 \(p\) 为关键字将点排序,并按顺序将点插入笛卡尔树中。

很明显,一个点插入后,在下一个点插入之前,这个点在这棵树的右链末端,因为此时这个点的 \(p\) 是整棵树中最大的。其中,右链指的是从根节点出发,一直往右走形成的链。

为了维护笛卡尔树的堆性质,我们将右链上的点保存在一个栈中,通过扫描栈的方式决定新加入的点应该插入到哪一个位置。

具体地,设当前栈存储了从当前树的根节点到右链末尾的点。不断弹出栈顶,直至发现栈顶的 \(w\) 值较将要加入的节点小,则此时栈顶可以作为新加入的节点的父亲。这时,我们把栈顶的右子树接在新加入的点的左子树位置上,并把新加入的点作为栈顶的右儿子,新点入栈。

这个过程成功地维护了保存右链的栈,它的时间复杂度是 \(O(n)\),这是因为每个点至多出入栈分别一次。代码见例题中的模板代码。

笛卡尔树的练习

P5854 【模板】笛卡尔树

板子题,没什么好讲的。对于序列中第 \(i\) 个点,其权值就是 \((i,p_i)\),所以不需要排序了。

直接放核心代码:

const int N=1e7+5;
int n,a[N],sta[N],l[N],r[N],top;
ll ans1,ans2;
int main(){
	IO::rd(n);
	for(int i=1;i<=n;i++){
		IO::rd(a[i]);
		while(top&&a[sta[top]]>a[i])top--;
		l[i]=r[sta[top]];//注意这里 r[0] 代表当前的根,所以这样写没有问题。
		r[sta[top]]=i;
		sta[++top]=i;
	}
	for(ll i=1;i<=n;i++){
		ans1^=i*(l[i]+1);
		ans2^=i*(r[i]+1);
	}
	IO::wt(ans1,' ');IO::wt(ans2,'\n');
	return 0;
}

P1377 [TJOI2011]树的序

题意:给定大小为 \(n\) 的一棵 BST 的生成序列,求可以生成同样形态 BST 的字典序最小的生成序列。\(n\le 10^5\)。

思考生成序列对于 BST 形态的影响。可以想到,一个节点在生成序列中的下标总是大于它父亲在生成序列中的下标。也就是这个 BST 在生成序列下标意义下满足小根堆性质。也就是说,这个笛卡尔树中每个点的权值为 \((k_i,i)\)。我们可以通过建立笛卡尔树的方式快速建立这个 BST。

然后我们考虑如何求出最小字典序的生成序列。为了让新的生成序列仍然满足小根堆性质,我们应该在遍历到一个点时,先将这个点加入生成序列末尾。然后因为左子树的键值比较小,先遍历左子树。也就是输出这棵树的先序遍历。

核心代码:

const int N=1e5+5;
int n,val[N],a[N],sta[N],ls[N],rs[N],top;
inline bool cmpA(const int&A,const int&B){
	return val[A]<val[B];
}
void dfs(int u){
	if(!u)return;
	IO::wt(u,' ');
	dfs(ls[u]);dfs(rs[u]);
}
int main(){
	IO::rd(n);
	for(int i=1;i<=n;i++){
		IO::rd(val[i]);
		a[i]=i;
	}
	sort(a+1,a+n+1,cmpA);
	for(int i=1;i<=n;i++){
		while(top&&a[sta[top]]>a[i])top--;
		ls[i]=rs[sta[top]];
		rs[sta[top]]=i;
		sta[++top]=i;
	}
	dfs(sta[1]);
	return 0;
}

SP1805 Largest Rectangle in a Histogram

这是一个单调栈的经典例题,也可以用笛卡尔树做。

刚看到这道题很容易产生一个想法:遍历每一个矩形,把当前矩形高度当成答案矩形高度,并将答案往两边拓展。设当前矩形为 \(u\),\(L_u=\min\{i\in\mathbb Z\mid \forall j\in[i,u),h_j\ge h_i\}\),\(R_u=\max\{i\in \mathbb Z\mid \forall j\in (u,i],h_j\ge h_i\}\)。则答案为 \(\max_{u=1}^n \{h_u(R_u-L_u+1)\}\)。

考虑构建一棵节点权值为 \((i,h_i)\) 的笛卡尔树,其中 \(h_i\) 满足小根堆性质。这样一来,对于任意一个点 \(u\) 的子树内的节点 \(v\),都有 \(h_v\ge h_u\)。

设子树 \(u\) 的大小为 \(\text{siz}_u\),则答案为 \(\max_{u=1}^n\{h_u\cdot\text{siz}_u\}\)。

核心代码:

const int N=1e5+5;
int n,h[N],sta[N],ls[N],rs[N],siz[N],top;
ll ans;
void dfs(int u){
	if(!u)return;
	dfs(ls[u]);dfs(rs[u]);
	siz[u]=siz[ls[u]]+siz[rs[u]]+1;
	ans=max(ans,(ll)h[u]*siz[u]);
}
int main(){
	while(1){
		IO::rd(n);
		if(!n)break;
		ans=0;top=0;
		for(int i=0;i<=n;i++){//由于 rs[0] 代表根,所以也要清零。
			ls[i]=rs[i]=siz[i]=0;
		}
		for(int i=1;i<=n;i++){
			IO::rd(h[i]);
			while(top&&h[sta[top]]>=h[i])top--;
			ls[i]=rs[sta[top]];
			rs[sta[top]]=i;
			sta[++top]=i;
		}
		dfs(sta[1]);
		IO::wt(ans,'\n');
	}
	return 0;
}

CF1748E Yet Another Array Counting Problem

水题,直接在笛卡尔树上跑 DP 就行了。

#include<bits/stdc++.h>
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long ll;
constexpr int N=2e5+5,P=1e9+7;
int n,m,f[N][20],a[N];
inline int ask(int l,int r){
	int k=__lg(r-l+1),x=f[l][k],y=f[r-(1<<k)+1][k];
	return a[x]>=a[y]?x:y;
}
inline ll add(const ll&x,const ll&y){return (x+y)%P;}
vector<ll>dfs(int l,int r){
	if(l==r)return vector<ll>(m,1);
	int p=ask(l,r);vector<ll>ret(m),L,R;
	if(l<p){
		L=dfs(l,p-1);
		partial_sum(all(L),L.begin(),add);
	}else L=vector<ll>(m,1),ret[0]=1;
	if(r>p){
		R=dfs(p+1,r);
		partial_sum(all(R),R.begin(),add);
	}else R=vector<ll>(m,1);
	for(int i=1;i<m;i++)ret[i]=L[i-1]*R[i]%P;
	ret[0]=ret[0]*R[0]%P;
	return ret;
};
inline void ct(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];f[i][0]=i;
	}
	for(int j=1;j<=__lg(n);j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			int x=f[i][j-1],y=f[i+(1<<(j-1))][j-1];
			f[i][j]=(a[x]>=a[y]?x:y);
		}
	}
	vector ans=dfs(1,n);
	cout<<accumulate(all(ans),0ll,add)<<'\n';
}
int main(){
	int T;cin>>T;while(T--)ct();
	return 0;
}

标签:return,sta,笛卡尔,int,top,ll
From: https://www.cnblogs.com/hihihi198/p/17056128.html

相关文章

  • 连接查询-笛卡尔积
    多表查询,当查询字段来自多个表笛卡尔积现象:表一有m行,表二有n行,结果=m*n行发生:没有有效的连接条件如何避免:赋予有效的连接条件分类:年代分类:sq192标准只支持......
  • 【数据结构】笛卡尔树
    把一个数组的元素,从左到右插入笛卡尔树,可以用栈O(n)地构建出来。笛卡尔树上的节点满足堆的性质(小根堆就是一个节点小于其两个子节点的权值)。所以用这个方式扫描出的笛卡尔......
  • MySQL的多表查询(笛卡尔积原理)
    先确定数据要用到哪些表。将多个表先通过笛卡尔积变成一个表。然后去除不符合逻辑的数据(根据两个表的关系去掉)。最后当做是一个虚拟表一样来加上条件即可。 注意:列名最好......
  • 笛卡尔树讲解
    前言笛卡尔树算是比较基础的数据结构了,但需要笛卡尔树的题目较少,且很多笛卡尔树的题都可以用其他解法解决,所以这个算法并不算的上热门,然而就在前天晚上,CF1748E这道......
  • 笛卡尔树
    笛卡尔树是一种二叉搜索树,同时也满足大根堆或小根堆的性质,Treap也是一种笛卡尔树。每个节点记录信息(x,y),对于x是二叉搜索树,对于y是小根堆,在x递增的情况下,可以线性时间内......
  • 笛卡尔树
    堆+BST性质笛卡尔树的子树是对应序列上一段连续的区间考虑BST,最初\([1,n]\),考虑划分开rt,分割成左右2个区间,且两个区间对应2棵子树,分治下去得证。构造考虑对......
  • 浅谈笛卡尔树
    笛卡尔树(CartesianTree),是一种二叉树,每个节点都有两个信息\((x_i,y_i)\),其中把\(x_i\)单独拎出来看是一棵二叉搜索树(\(ls_u<u<rs_u\)),而把\(y_i\)拎出来是一个小根堆(\(......
  • P5854 【模板】笛卡尔树
    【模板】笛卡尔树题目描述给定一个\(1\simn\)的排列\(p\),构建其笛卡尔树。即构建一棵二叉树,满足:每个节点的编号满足二叉搜索树的性质。节点\(i\)的权值为\(p......
  • 【UOJ424】count(笛卡尔树,DP,生成函数,矩阵快速幂)
    首先可以发现两个序列\(A,B\)同构当且仅当它们的笛卡尔树同构。那么可以考虑枚举笛卡尔树,然后判断它能否构成满足题目条件的序列。发现一棵笛卡尔树满足条件当且仅当它......
  • 数据结构#1 笛卡尔树学习笔记
    笛卡尔树数据结构结构介绍笛卡尔树是一种树形的数据结构,它的每一个节点都有两个值key和weight,其中key满足二叉搜索树的性质,而weight满足堆的性质。在使用时,我们通常将ke......