ybtAu「高级数据结构」第8章 笛卡尔树

笛卡尔树题最难的地方永远不是笛卡尔树本身。

A. 【例题1】棋盘

发现可以把棋盘分割成若干个矩形,把问题转化成每个矩形里的方案数,再统计答案。
考虑建出一棵小根笛卡尔树,由笛卡尔树的性质可知,每棵子树都对应序列的一个区间。
对每个节点,令它子树的区间范围为 \([L,R]\),那么该节点就代表一个左上角为 \((L,h_i)\),右下角为 \((R,h_{fa_i})\) 的矩形。
建出笛卡尔树后,可以进行树形 DP。
\(f_{i,j}\) 表示 \(i\) 子树内放 \(j\) 个棋子的方案数,\(g_{i,j}\) 表示 \(i\) 矩形不放棋子,\(i\) 子树内放 \(j\) 个棋子的方案数。有:

\[\large g_{i,j}=\sum_{k=0}^jf_{ls_i,k}\cdot f_{rs_i,j-k} \\ \large f_{i,j}=\sum_{k=0}^j{h_i-h_{fa_i}\choose k}{R-L+1-(j-k)\choose k}k!\cdot g_{i,j-k} \]

对于第一个式子,由于左右儿子互不影响,因此可以随便放。
对于第二个式子,\(g_{i,j-k}\) 表示在左右儿子里选 \(j-k\) 个,前面的组合数表示在该节点长为 \(R-L+1-(j-k)\) (子树里选 \(j-k\) 个,所以可选的列少了 \(j-k\) 个)宽为 \(h_i-h_{fa_i}\) 的矩形里放 \(k\) 个。
实际实现时,不存 \(L\)\(R\),而只存子树大小。

#include <iostream>
#define N 1005
#define int long long
#define mod 1000000007
#define M 1000005
int n,m,a[N],ls[N],rs[N],st[N],tp,f[N][N],g[N],fa[N],prd[M],inv[M],siz[N];
void build()
{
	for(int i=1;i<=n;i++)
	{
		while(tp&&a[st[tp]]>a[i]) tp--;
		ls[i]=rs[st[tp]],rs[st[tp]]=i;
		st[++tp]=i;
	}
	for(int i=1;i<=n;i++) fa[ls[i]]=fa[rs[i]]=i;
}
int qpow(int x,int y) {int r=1;for(;y;y>>=1,x=x*x%mod) if(y&1) r=r*x%mod;return r;}
int C(int x,int y)
{
	if(x<y) return 0;
	return prd[x]*inv[y]%mod*inv[x-y]%mod;
}
void F(int x)
{
	if(!x) return;
	F(ls[x]),F(rs[x]),siz[x]=siz[ls[x]]+siz[rs[x]]+1;
	for(int i=0;i<=m;i++) g[i]=0;
	for(int i=0;i<=m;i++) for(int j=0;j<=m-i;j++)
		(g[i+j]+=f[ls[x]][i]*f[rs[x]][j]%mod)%=mod;
	for(int i=0;i<=m;i++) for(int j=0;j<=i;j++)
		(f[x][i]+=g[i-j]*C(a[x]-a[fa[x]],j)%mod*C(siz[x]-(i-j),j)%mod*prd[j]%mod)%=mod;
}
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n>>m;
	prd[0]=inv[0]=inv[1]=1;
	for(int i=2;i<M;i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod;
	for(int i=1;i<M;i++) prd[i]=prd[i-1]*i%mod,inv[i]=inv[i]*inv[i-1]%mod;
	for(int i=1;i<=n;i++) std::cin>>a[i];
	build();
	int rt=0;
	f[0][0]=1;
	F(st[1]);
	std::cout<<f[st[1]][m];
}

B. 【例题2】星座

把问题转化成使没涂黑的的星星的 \(C_j\) 和最大。
发现题中的星座一定是一个由黑色空格和星星组成的矩形,于是把图倒过来,建一棵小根笛卡尔树,令每个节点维护一个矩形,\(f_i\) 表示该节点子树内 \(C_j\) 的最大和。
发现一个节点里面放不放跟它在树上的兄弟节点没有任何关系,所以对每个节点维护 \(g_i\) 表示该节点到根的链上兄弟节点的权值和,为了最大化答案,把每个节点的 \(f_i\) 加到兄弟节点的子树的 \(g_i\) 里。
考虑求解答案。由于不能有星座,因此每个节点矩形内最多选一个星星。
分为两种情况:

  • 不选,答案为左右子树答案之和,即 \(f_i=f_{ls}+f_{rs}\)
  • 选,枚举矩形内的星星,令横坐标为 \(X\),容易发现 \(X\) 节点到当前节点链上的星星都不能选,而 \(ls_X\)\(rs_X\) 子树内选不选跟它无关,于是 \(f_i=\max C_j+f_{ls_X}+f_{rs_X}+g_X\)

\(g_i\) 可以用树状数组 + DFS 序维护。
求解每个星星在哪个节点的矩形内,可以使用倍增。

#include <iostream>
#include <vector>
#define int long long
#define N 200005
int n,m,a[N],st[N],tp,ls[N],rs[N],fa[N],f[N],X[N],Y[N],c[N],pa[N][20],dfn[N],siz[N],idx;
std::vector<int> buc[N];
namespace BIT
{
	int d[N],r;
	void c(int x,int t) {for(;x<=n;x+=x&-x) d[x]+=t;}
	int q(int x) {for(r=0;x;x-=x&-x) r+=d[x];return r;}
};
void build()
{
	for(int i=1;i<=n;i++)
	{
		while(tp&&a[st[tp]]<a[i]) tp--;
		ls[i]=rs[st[tp]],rs[st[tp]]=i,st[++tp]=i;
	}
	for(int i=1;i<=n;i++) fa[ls[i]]=fa[rs[i]]=i;
}
void dfs(int x)
{
	if(!x) return;
	dfn[x]=++idx;
	pa[x][0]=fa[x];
	for(int i=1;i<20;i++) pa[x][i]=pa[pa[x][i-1]][i-1];
	dfs(ls[x]),dfs(rs[x]),siz[x]=siz[ls[x]]+siz[rs[x]]+1;
}
void F(int x)
{
	if(!x) return;
	F(ls[x]),F(rs[x]);
	if(ls[x]) BIT::c(dfn[ls[x]],f[rs[x]]),BIT::c(dfn[ls[x]]+siz[ls[x]],-f[rs[x]]);
	if(rs[x]) BIT::c(dfn[rs[x]],f[ls[x]]),BIT::c(dfn[rs[x]]+siz[rs[x]],-f[ls[x]]);
	f[x]=f[ls[x]]+f[rs[x]];
	for(int i:buc[x]) f[x]=std::max(f[x],f[ls[X[i]]]+f[rs[X[i]]]+c[i]+BIT::q(dfn[X[i]]));
}
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n;
	for(int i=1;i<=n;i++) std::cin>>a[i];
	build();
	dfs(st[1]);
	std::cin>>m;
	int sum=0;
	for(int i=1;i<=m;i++)
	{
		std::cin>>X[i]>>Y[i]>>c[i],sum+=c[i];
		int t=X[i];
		for(int j=19;j>=0;j--) if(pa[t][j]&&a[pa[t][j]]<Y[i]) t=pa[t][j];
		buc[t].push_back(i);
	}
	F(st[1]);
	std::cout<<sum-f[st[1]];
}

C. 树的序

深度低的节点一定比深度高的节点先插入,因此得到的树是一个以插入顺序为权值的小根堆。又由于它是一颗二叉搜索树,所以是一个以大小为下标、以插入顺序为权值的小根笛卡尔树。
建出来这棵笛卡尔树,从根开始中序遍历,可以证明没有比所得序列字典序更小的生成序列。

#include <iostream>
#include <algorithm>
#define N 100005
int n,a[N],b[N],ls[N],rs[N],st[N],tp;
bool cmp(int x,int y) {return a[x]<a[y];}
void build()
{
	for(int i=1;i<=n;i++)
	{
		while(tp&&b[st[tp]]>b[i]) tp--;
		ls[i]=rs[st[tp]],rs[st[tp]]=i,st[++tp]=i;
	}
}
void print(int x)
{
	if(!x) return;
	printf("%d ",x);
	print(ls[x]),print(rs[x]);
}
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n;
	for(int i=1;i<=n;i++) std::cin>>a[i],b[i]=i;
	std::sort(b+1,b+n+1,cmp);
	build(),print(st[1]);
}

D. 泳池

E. 会议

posted @ 2025-07-04 08:09  整齐的艾萨克  阅读(13)  评论(0)    收藏  举报