あいさか たいがblogAisaka_Taiga的博客
//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

笛卡尔树学习笔记

Toretto·2023-01-25 08:56·51 次阅读

笛卡尔树学习笔记

笛卡尔树

下文的资料多摘自OI Wiki

性质#

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

例如 OI Wiki 上的这张图:

image

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

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

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

构建#

过程#

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

image

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

实现#

Copy
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 【模板】笛卡尔树#

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

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

Copy
#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] 树的序#

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

Copy
#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 @   北烛青澜  阅读(51)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示
目录