笛卡尔树学习笔记
笛卡尔树是一棵二叉树,满足:
- 每个节点的编号满足二叉搜索树的性质。
- 节点 \(i\) 的权值为 \(p_i\),每个节点的权值满足小根堆的性质。
建树
考虑一颗笛卡尔树由二元组 \((k,v)\) 构成,k为数组下标,所以天然是升序排序,\(v\) 为数组元素。
笛卡尔树=堆+基于数组下标的二叉搜索树。
大根笛卡尔树就是满足大根堆性质的笛卡尔树,小根笛卡尔树满足小根堆性质。
以下拿最小堆为例:
-
单纯实现一个最小堆,我们使用栈作为辅助空间来构造堆.当一个数组元素要构造节点时,他可以成为栈顶元素的子节点(比栈顶元素大),或者成为栈顶元素的父节点(比栈顶元素小)
-
还要将最小堆变成二叉搜索树。一个已入栈元素的下标<后入栈元素的下标,所以在第一步中,变成子节点的元素必须要成为右节点;变成父节点的元素,栈中的元素必须成为他的左子树,这样才保持了基于下标的二叉搜索树性质。
本质是不断左旋新插入的节点直到堆的性质满足,所以二叉搜索树的性质也得以保存。
int k,rt; inline void buildtree() { k=top; for(int i=1; i<=n; i++) { while(k&&a[st[k]]>a[i]) --k; if(k) rson[st[k]]=i; if(k<top) lson[i]=st[k+1];//接在左子树 st[++k]=i;//用栈维护链 top=k; } rt=st[1]; }
应用
- 构造笛卡尔树利用性质解决问题
题目大意:\(n\) 个位置,每个位置上的高度是 \(h_i\),求最大子矩阵。
考虑以 \((i,h_i)\) 建出笛卡尔树,发现对于每个节点,有且仅有这个节点的子树内所有节点的 \(h\) 值必定不小于这个节点的值。所以考虑以每个点的值作为矩形长,这个点对答案的贡献即为 \(h_i\times siz_i\),其中 \(siz\) 表示子树大小。dfs 一遍即可。
const int maxn=100010; ll res; int n; int a[maxn]; struct node { int val,fa,x,ls,rs; } t[maxn]; int dfs(int x) { if(!x) return 0; // cerr<<x<<' '<<t[x].ls<<' '<<t[x].rs<<'\n'; int siz=dfs(t[x].ls)+dfs(t[x].rs); gmax(res,(ll)(siz+1)*t[x].val); return siz+1; } void buildtree() { for(int i=1; i<=n; i++) { int k=i-1; while(t[k].val>t[i].val) k=t[k].fa; t[i].ls=t[k].rs; t[k].rs=i; t[i].fa=k; t[t[k].ls].fa=i; } rt=t[0].rs; } int main() { while(rd(n),n!=0) { t[0].ls=t[0].rs=0; for(int i=1; i<=n; i++) rd(a[i]),t[i].x=i,t[i].val=a[i],t[i].ls=t[i].rs=t[i].fa=0; buildtree(); res=0; dfs(rt); printf("%lld\n",res); } return 0; }
- 笛卡尔树上 dp
对于一类带有最大值计数的 dp,可以考虑笛卡尔树的节点对子树的贡献。
- 笛卡尔树上分治
本文作者:lgh_2009
本文链接:https://www.cnblogs.com/lgh-blog/p/18038655
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步