笛卡尔树学习笔记

笛卡尔树是一棵二叉树,满足:

  1. 每个节点的编号满足二叉搜索树的性质。
  2. 节点 \(i\) 的权值为 \(p_i\),每个节点的权值满足小根堆的性质。

建树

考虑一颗笛卡尔树由二元组 \((k,v)\) 构成,k为数组下标,所以天然是升序排序,\(v\) 为数组元素。

笛卡尔树=堆+基于数组下标的二叉搜索树。

大根笛卡尔树就是满足大根堆性质的笛卡尔树,小根笛卡尔树满足小根堆性质。

以下拿最小堆为例:

  1. 单纯实现一个最小堆,我们使用栈作为辅助空间来构造堆.当一个数组元素要构造节点时,他可以成为栈顶元素的子节点(比栈顶元素大),或者成为栈顶元素的父节点(比栈顶元素小)

  2. 还要将最小堆变成二叉搜索树。一个已入栈元素的下标<后入栈元素的下标,所以在第一步中,变成子节点的元素必须要成为右节点;变成父节点的元素,栈中的元素必须成为他的左子树,这样才保持了基于下标的二叉搜索树性质。

本质是不断左旋新插入的节点直到堆的性质满足,所以二叉搜索树的性质也得以保存。

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];
}

应用

  1. 构造笛卡尔树利用性质解决问题

HDU1506

题目大意:\(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;
}
  1. 笛卡尔树上 dp

对于一类带有最大值计数的 dp,可以考虑笛卡尔树的节点对子树的贡献。

  1. 笛卡尔树上分治
posted @ 2024-02-27 23:02  lgh_2009  阅读(33)  评论(0编辑  收藏  举报