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\) 个棋子的方案数。有:
对于第一个式子,由于左右儿子互不影响,因此可以随便放。
对于第二个式子,\(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]);
}

浙公网安备 33010602011771号