//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

笛卡尔树学习笔记

笛卡尔树

下文的资料多摘自OI Wiki

性质

笛卡尔树是一种二叉树,每一个节点都由一个键值二元组 \((k,w)\) 构成。要求 \(k\) 满足二叉搜索树的性质,而 \(w\) 满足堆的性质。如果笛卡尔树的 \(k\)\(w\) 键值确定的话,且 \(k\) 互不相同,\(w\) 互不相同,那么这个笛卡尔树的结构是唯一的。

例如 OI Wiki 上的这张图:

image

上面的这棵树是按每一个点内的值为键值 \(w\),把数组下标当作键值 \(k\),来建立的。仔细观察可以发现,这棵树的 \(k\) 满足二叉搜索树的性质,而键值 \(w\) 是满足小根堆的性质的。

像上面这棵树一样键值 \(k\) 恰好对应数组下标的笛卡尔树有一个性质:一棵子树内的下标是连续的一个区间(满足二叉搜索树的性质)。

谈到笛卡尔树,很容易让人想到一种家喻户晓的结构—— Treap。没错,Treap 是笛卡尔树的一种,只不过 \(w\) 的值完全随机。Treap 也有线性的构建算法,如果提前将元素排好序,显然可以使用上述单调栈算法完成构建过程,只不过很少会这么用。

构建

过程

我们考虑将元素的键值 \(k\) 进行排序,然后一个一个地插入到当前的笛卡尔树中,那么我们每一次插入的元素必在这个树的右链(右链:从根节点一直往右子树走,经过的节点)的末端。于是我们可以执行这样一个过程,从下往上比较右链节点与当前节点的 \(u\),和\(w\),如果找到了一个右链上的节点满足 \(x_{w}<u_{w}\),就把 \(u\) 接到 \(x\) 的有儿子上,而 \(x\) 原来的右子树就变成了 \(u\) 的左子树。

image

显然每个数最多进出右链一次(或者说每个点在右链中存在的是一段连续的时间)。这个过程我们可以用栈维护,栈中维护当前笛卡尔树的右链上的结点。一个点不在右链上了就把它弹掉。这样每个点最多进出一次,复杂度 \(O(n)\)

实现

for (int i = 1; i <= n; i++) {
  int k = top;
  while (k > 0 && h[stk[k]] > h[i]) k--;
  if (k) rs[stk[k]] = i;  // rs代表笛卡尔树每个节点的右儿子
  if (k < top) ls[i] = stk[k + 1];  // ls代表笛卡尔树每个节点的左儿子
  stk[++k] = i;
  top = k;
}

P5854 【模板】笛卡尔树

这道题最后的输出没有什么难的,主要是建树。

我们可以用一个单调栈来维护一下,我们按照编号顺序插入,那么我们当前点肯定是往最右端插入,所以我们如果发现当前点是大于栈顶元素的话,我们就可以直接将其设为当前堆顶元素的右子树;如果不是的话,我们就要将栈内比其大的元素都弹出,然后将其设为最后一个小于他的点的右子树,然后将他设为第一个大于他的点的左子树,这样就能完美维护堆的性质了。

#include<bits/stdc++.h>
#define int long long
#define N 10000100
using namespace std;
int n,a[N],stk[N],ls[N],rs[N];
int l,r,pos,top;
inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		pos=top;
		while(pos&&a[stk[pos]]>a[i])pos--;
		if(pos)rs[stk[pos]]=i;
		if(pos<top)ls[i]=stk[pos+1];
		stk[top=++pos]=i;
	}
	for(int i=1;i<=n;i++)
	    l^=1ll*i*(ls[i]+1),r^=1ll*i*(rs[i]+1);
    cout<<l<<" "<<r<<endl;
    return 0;
}

P1377 [TJOI2011] 树的序

给你一串序列,以序列的值为下标,当前的序号为键值建笛卡尔树,最后先序遍历一遍输出即可。

#include<bits/stdc++.h>
#define int long long
#define N 100010
using namespace std;
int n,a[N],stk[N],ls[N],rs[N];
inline void dfs(int x){if(x)cout<<x<<" ",dfs(ls[x]),dfs(rs[x]);}
signed main()
{
	int x,pos=0,top=0;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>x;
		a[x]=i;
	}
	for(int i=1;i<=n;i++)
	{
		pos=top;
		while(pos&&a[stk[pos]]>a[i])pos--;
		if(pos)rs[stk[pos]]=i;
		if(pos<top)ls[i]=stk[pos+1];
		stk[top=++pos]=i;
	}
	dfs(stk[1]);
	return 0;
}
posted @ 2023-01-25 08:56  北烛青澜  阅读(37)  评论(0编辑  收藏  举报