笛卡尔树学习笔记
笛卡尔树
下文的资料多摘自OI Wiki
性质
笛卡尔树是一种二叉树,每一个节点都由一个键值二元组 \((k,w)\) 构成。要求 \(k\) 满足二叉搜索树的性质,而 \(w\) 满足堆的性质。如果笛卡尔树的 \(k\) ,\(w\) 键值确定的话,且 \(k\) 互不相同,\(w\) 互不相同,那么这个笛卡尔树的结构是唯一的。
例如 OI Wiki 上的这张图:
上面的这棵树是按每一个点内的值为键值 \(w\),把数组下标当作键值 \(k\),来建立的。仔细观察可以发现,这棵树的 \(k\) 满足二叉搜索树的性质,而键值 \(w\) 是满足小根堆的性质的。
像上面这棵树一样键值 \(k\) 恰好对应数组下标的笛卡尔树有一个性质:一棵子树内的下标是连续的一个区间(满足二叉搜索树的性质)。
谈到笛卡尔树,很容易让人想到一种家喻户晓的结构—— Treap。没错,Treap 是笛卡尔树的一种,只不过 \(w\) 的值完全随机。Treap 也有线性的构建算法,如果提前将元素排好序,显然可以使用上述单调栈算法完成构建过程,只不过很少会这么用。
构建
过程
我们考虑将元素的键值 \(k\) 进行排序,然后一个一个地插入到当前的笛卡尔树中,那么我们每一次插入的元素必在这个树的右链(右链:从根节点一直往右子树走,经过的节点)的末端。于是我们可以执行这样一个过程,从下往上比较右链节点与当前节点的 \(u\),和\(w\),如果找到了一个右链上的节点满足 \(x_{w}<u_{w}\),就把 \(u\) 接到 \(x\) 的有儿子上,而 \(x\) 原来的右子树就变成了 \(u\) 的左子树。
显然每个数最多进出右链一次(或者说每个点在右链中存在的是一段连续的时间)。这个过程我们可以用栈维护,栈中维护当前笛卡尔树的右链上的结点。一个点不在右链上了就把它弹掉。这样每个点最多进出一次,复杂度 \(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;
}
本文来自博客园,作者:北烛青澜,转载请注明原文链接:https://www.cnblogs.com/Multitree/p/17066653.html
The heart is higher than the sky, and life is thinner than paper.