笛卡尔树

较为抽象对新手极其不友好

定义

笛卡尔树是二叉搜索树的一种,每一个节点都有一个键值对 \((k,w)\),其中 \(k\) 满足BST性质\(w\) 满足堆性质
这不就是Treap吗?
事实上Treap是笛卡尔树的一种,只不过Treap的 \(w\) 完全随机,Treap是一种动态笛卡尔树。


来自oi-wiki(图源自维基百科)

建树

建树时,要保证 \(k\) 是有序的,依次将 \((k,w)\) 插入树中。
同时,由于 \(k\) 是升序插入,所以 \(k\) 只会成为有最靠右的叶节点,这个节点没有子节点。
我们定义「右链」为从根节点开始,一直向右走的一条链,那么插入的节点一定是在右链上。
我们用单调栈维护这个「右链」,当加入一条新节点时,不断将 \(w\) 小与新节点 \(w\) 的节点出栈,后将这个新节点作为栈顶的右子节点,将出栈的那一条链作为这个新节点的左子节点。


来自oi-wiki

由于每个节点做多进栈一次,出栈一次,故时间复杂度为 \(O(n)\)

代码

for(int i=1,pos=0,top=0;i<=n;++i){
	pos=top;
	while(pos&&w[stk[pos]]>w[i]) pos--;
	if(pos) rs[stk[pos]]=i;
	if(pos<top) ls[i]=stk[pos+1];
	stk[top=++pos]=i;
}

需要好好理解。

例题

【模板】笛卡尔树 \(^{luogu P5854}\)

一道模版题,节点编号为 \(k\) ,权值为 \(w\),建树、计算、输出即可。
一定要用快读!

代码

#include<bits/stdc++.h>
using namespace std;
const int NUM=1e7+10;

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<1)+(x<<3)+(ch-'0');
		ch=getchar();
	}
	return x*f;
}
int n,w[NUM],stk[NUM],ls[NUM],rs[NUM];
long long l,r;

int main(){
	n=read();
	for(int i=1,pos=0,top=0;i<=n;++i){
		w[i]=read();
		pos=top;
		while(pos&&w[stk[pos]]>w[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;
	return 0;
}

[TJOI2011] 树的序\(^{luoguP1377}\)

与上一道正好相反,将值作为 \(k\),下标作为 \(w\),建树,前序遍历输出即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int NUM=1e6+10;

inline void write(int x){
	if(x<0)x*=-1,putchar('-');
	if(x>9)write(x/10);
	putchar(x%10+'0');
	return;
}

int n;
int ls[NUM],rs[NUM],stk[NUM],a[NUM];

void dfs(int x){
	if(x) write(x),putchar(' '),dfs(ls[x]),dfs(rs[x]); 
}

int main(){
	cin>>n;	
	for(int i=1,x;i<=n;++i){
		cin>>x;
		a[x]=i;
	}
	for(int i=1,pos=0,top=0;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;
}

Takahashi is Slime\(^{AT\_arc189\_d}\)

以下标为 \(k\) 建立大根笛卡尔树,则相邻的节点(子父节点)是相邻的,且在根这里的史莱姆可以吃掉子树里的所有史莱姆,设 \(sum_i\) 为以 \(i\) 为根的子树所有权值之和,若其父节点的大小小于当前节点的 \(sum\) 就可以将其父节点吃掉,以此类推就可以知道所有史莱姆的大小。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NUM=1e6+10;

int n,stk[NUM],w[NUM],ls[NUM],rs[NUM],sum[NUM];
int dp[NUM];

void dfs(int x){
	if(!x) return;
	sum[x]=w[x];
	dfs(ls[x]),dfs(rs[x]);
	sum[x]+=sum[ls[x]]+sum[rs[x]];
}
void dfs2(int x,int f){
	if(!x) return;
	if(f&&sum[x]>w[f]) dp[x]=dp[f];
	else dp[x]=sum[x];
	dfs2(ls[x],x),dfs2(rs[x],x);
}

signed main(){
	cin>>n;
	for(int i=1,pos=0,top=0;i<=n;++i){
		cin>>w[i];
		pos=top;
		while(pos&&w[stk[pos]]<w[i]) pos--;
		if(pos) rs[stk[pos]]=i;
		if(pos<top) ls[i]=stk[pos+1];
		stk[top=++pos]=i;
	}
	dfs(stk[1]);
	dfs2(stk[1],0);
	for(int i=1;i<=n;++i){
		if(w[rs[i]]<w[i]||ls[i]||ls[rs[i]]) cout<<dp[i]<<' ';
		else cout<<w[i]<<' ';
	}
	return 0;
}

[COCI 2008/2009 #4] PERIODNI\(^{luoguP6453}\)

一道笛卡尔树的DP题。
参考_luanyi_的题解
先考虑如果图形是个矩形该怎么办?

\[cal(n,m,k)=C_n^k\times C_m^k\times k! \]

\(n、m\)是长和宽。
横着在 \(n\) 个里面选 \(k\) 个,竖着在 \(m\) 个里面选 \(k\) 个,最后的 \(k!\)\(A_k^k\)。很好理解吧……
于是我们就可以将这个诡异的图形拆成若干个矩形,由于不相连的部分互不干扰,所以采用从下向上分的策略。
无标题
如果我们知道高度最小的位置 \(x\),那么就可以递归到 \([1,x-1]\)\([x+1,n]\)
于是我们就可以使用笛卡尔树,让下标满足BST性质,高度满足堆性质,根节点就是 \(x\),其左子树就是 \([1,x-1]\),右子树就是 \([x+1,n]\)
\(f[x][k]\) 表示到达 \(x\) 节点所对应的矩形及其上方的矩形放入 \(k\) 个数的方案数。
当到达叶节点(即上方没有矩阵)时:

\[f[x][k]=cal(n,m,k) \]

其中 \(n、m\) 时矩形的长和宽。
下面考虑合并
设左上矩阵编号为 \(ls[x]\),右上矩阵编号为 \(rs[x]\)

\[f[x][k]=\sum_{i=0}^{k}\sum_{j=0}^{k-i}{f[l][i]\times f[r][j]\times cal(n-i-j,m,k-i-j)} \]

其中 \(n、m\)\(x\) 对应的矩形的长和宽。
可以理解为左边放 \(i\) 个,右边放 \(j\) 个,下面就要放 \(k-i-j\) 个。
但这个会T。
考虑优化。
设:

\[g[x][p]=\sum_{i+j=p}{f[l][i]\times f[r][j]} \]

则原转移方程可以写为:

\[f[x][k]=\sum_{p=0}^{k}{g[x][p]\times cal(n-p,m,k-p)} \]

于是这道题就解决了。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NUM=510;
const int mod=1e9+7;

int ksm(int a,int b){
	int ans=1;
	for(;b;b>>=1,a=a*a%mod)
	if(b&1) ans=ans*a%mod;
	return ans;
}

int n,jc[(int)1e6+10]={1},njc[(int)1e6+10]={1};
int C(int n,int m){
	if(m>n) return 0;
	return jc[n]*njc[n-m]%mod*njc[m]%mod;
}
int cal(int n,int m,int k){
	return C(n,k)*C(m,k)%mod*jc[k]%mod;
}

int stk[NUM],h[NUM],ls[NUM],rs[NUM],siz[NUM];
void dfs0(int x){
	if(!x) return;
	dfs0(ls[x]),dfs0(rs[x]);
	siz[x]=siz[ls[x]]+siz[rs[x]]+1;
}

int k,f[NUM][NUM],g[NUM];
void dfs(int x,int low){
	int hu=h[x]-low;
	if(!ls[x]&&!rs[x]){
		f[x][0]=1;
		for(int i=1;i<=k;++i) f[x][i]=cal(siz[x],hu,i);
		return;
	}
	if(!ls[x]||!rs[x]){
		int y;
		if(!ls[x]) y=rs[x];
		else y=ls[x];
		dfs(y,h[x]);
		memset(g,0,sizeof(g));
		f[x][0]=g[0]=1;
		for(int i=1;i<=k;++i)
		for(int j=0;j<=i;++j)
			g[i]=(g[i]+f[0][j]*f[y][i-j])%mod;
		for(int i=1;i<=k;++i)
		for(int j=0;j<=i;++j)
			f[x][i]=(f[x][i]+g[j]*cal(hu,siz[x]-j,i-j))%mod;
		return;
	}
	dfs(ls[x],h[x]);
	dfs(rs[x],h[x]);
	memset(g,0,sizeof(g));
	f[x][0]=g[0]=1;
	for(int i=1;i<=k;++i)
	for(int j=0;j<=i;++j)
		g[i]=(g[i]+f[ls[x]][j]*f[rs[x]][i-j])%mod;
	for(int i=1;i<=k;++i)
	for(int j=0;j<=i;++j)
		f[x][i]=(f[x][i]+g[j]*cal(hu,siz[x]-j,i-j))%mod;
}

signed main(){
	cin>>n>>k;
	for(int i=1;i<=1e6;++i) jc[i]=jc[i-1]*i%mod,njc[i]=ksm(jc[i],mod-2);
	for(int i=1,top=0,pos=0;i<=n;++i){
		cin>>h[i];
		pos=top;
		while(pos&&h[stk[pos]]>h[i]) pos--;
		if(pos) rs[stk[pos]]=i;
		if(pos<top) ls[i]=stk[pos+1];
		stk[top=++pos]=i;
	}
	dfs0(stk[1]);
	f[0][0]=1;
	dfs(stk[1],0);
	cout<<f[stk[1]][k]%mod;
	return 0;
}
posted @ 2026-07-16 11:15  LZYXT  阅读(23)  评论(0)    收藏  举报