笛卡尔树学习笔记
笛卡尔树
说实在的感觉这个东西过于抽象了,只能够掌握几种比较套路的笛卡尔树题目,
要是考场上出一些非常隐含的建树或者维护,大概是用不出来的,(除非灵光一现)
先放一个建树的板子
1 namespace cartesian_tree{ 2 int stk[NN],top,son[NN][2],siz[NN]; 3 inline void build(int *a){ 4 for(int i=1;i<=n;i++){ 5 while(top&&a[stk[top]]<a[i]) son[i][0]=stk[top--]; 6 if(top) son[stk[top]][1]=i; 7 stk[++top]=i; 8 } 9 } 10 }using namespace cartesian_tree;
简单说一下思想,大多数题目都是以下标满足二叉搜索树的性质,数组元素值满足小根堆性质建树的
但是有个题比较特殊($hdu4125Moles$,他反着来)
这个数据结构一般是拿来找最大子矩形,很多的奇技淫巧都和这棵树的子树大小有关
最大子矩形就是$\max\limits_{x=1}^{n}(siz[x]*a[x])$
还有一些放在例题里面说吧。
BZOJ #2616. SPOJ PERIODNI
不难发现是一道$dp$题,然后他最烦的就是各个矩形间会相互影响,这是最不方便的地方
那么我们考虑用笛卡尔树适当的减少矩形间的相互作用
发现建一个小根堆笛卡尔树后,一个比较小的子树根节点会把两个比较大的儿子矩形分开
这样两个儿子矩形高出父亲的部分就可以随便选择
那么设一个$dp[i][j]$表示以$i$为根的子树放了$j$辆车的方案数
就可以得到子树合并时的转移$g[i+j]=\sum\limits_{i=0}^{siz[ls]} \sum\limits_{j=0}^{siz[rs]} dp[ls][i] \times dp[rs][j]$($g$是一个转移数组)
注意边界要卡紧,是两个儿子的$siz$,不能再多选择了,同样的他们的父亲最多也只能选$siz[x]$辆车
考虑如何计算和父亲有重叠的部分,在递归的过程中传一个$lim$的参,表示父亲的上一层父亲的高度
那么图大概是长这样的
就是说从两个儿子的子树中转移来的方案数存到了$g$中,然后还要从$[lim,h_x]\times siz_x$的矩形中选择一些
那么转移就是
$dp[x][j]=\sum \limits_{j=0}^{siz_x} \sum\limits_{k=0}^{j}g[j-k] \times k! \times C_{h_x-lim}^{k} \times C_{siz_x-(j-k)}^{k}$
然后注意枚举顺序和边界问题就可以了
1 #include<stdio.h> 2 #include<algorithm> 3 #include<cstring> 4 #define int long long 5 const int mod=1e9+7,NN=505,MM=1e6+5; 6 int n,K,h[NN]; 7 namespace Math{ 8 int jc[MM],ny[MM]; 9 inline int qmo(int a,int b,int ans=1){int c=mod; 10 for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod; 11 return ans; 12 } 13 inline void pre(){ 14 jc[0]=jc[1]=1; ny[0]=ny[1]=1; 15 for(int i=2;i<MM;i++) jc[i]=jc[i-1]*i%mod; 16 ny[MM-1]=qmo(jc[MM-1],mod-2); 17 for(int i=MM-2;i>=2;i--) ny[i]=ny[i+1]*(i+1)%mod; 18 } 19 inline int C(int n,int m){ 20 if(n<m||n<0||m<0) return 0; 21 return jc[n]*ny[n-m]%mod*ny[m]%mod; 22 } 23 }using namespace Math; 24 namespace cartesian_tree{ 25 int stk[NN],top,son[NN][2],dp[NN][NN],siz[NN],g[NN]; 26 inline void build(int *a){ 27 for(int i=1;i<=n;i++){ 28 while(top&&a[stk[top]]>a[i]) son[i][0]=stk[top--]; 29 if(top) son[stk[top]][1]=i; 30 stk[++top]=i; 31 } 32 } 33 inline void dfs(int x,int lim){ 34 if(!x) return; siz[x]=1; 35 dfs(son[x][0],h[x]); dfs(son[x][1],h[x]); 36 siz[x]+=siz[son[x][0]]+siz[son[x][1]]; 37 int ls=son[x][0],rs=son[x][1],hi=h[x]-lim; 38 for(int i=0;i<=siz[x];i++) g[i]=0; 39 for(int i=0;i<=siz[ls];i++) 40 for(int j=0;j<=siz[rs];j++) 41 (g[i+j]+=dp[ls][i]*dp[rs][j]%mod)%=mod; 42 for(int j=siz[x];~j;--j) 43 for(int k=0;k<=j;k++) 44 (dp[x][j]+=g[j-k]*jc[k]%mod*C(hi,k)%mod*C(siz[x]-(j-k),k)%mod)%=mod; 45 } 46 }using namespace cartesian_tree; 47 namespace WSN{ 48 inline short main(){ 49 scanf("%lld%lld",&n,&K); pre(); 50 for(int i=1;i<=n;i++) scanf("%lld",&h[i]); 51 build(h); dp[0][0]=1; 52 dfs(stk[1],0); 53 printf("%lld\n",dp[stk[1]][K]); 54 return 0; 55 } 56 } 57 signed main(){return WSN::main();}
hdu6854Kcats
利用笛卡尔树思想来做区间$dp$,就是意思是利用笛卡尔树在构建时子树下标是一段连续的区间(仅对于下标为数组下标,键值为数组元素值时成立)
我们发现他给的前缀单调栈大小实际上是在说$i$这个节点的到祖先的链上有几个祖先是在他左边的
这个是样例里面给出的关于$1,1,2,3,4,2$的合法方案,$3,1,4,5,6,2$,上面的节点写的是数组下标
那么我们$dp$的就是这样一个树形的结构,设$f[i][j][d]$表示$i$到$j$的一段区间子树的根有$d$个左祖先的建树方案数
然后需要判断可以转移的区间范围,注意在转移的时候要乘上一个组合数$C_{j-i}^{k-i}$,表示子树的构建方案
1 #include<stdio.h> 2 #include<algorithm> 3 #include<cstring> 4 #define int long long 5 const int NN=105,mod=1e9+7; 6 int T,n,a[NN],f[NN][NN][NN],C[NN][NN]; 7 namespace WSN{ 8 inline short main(){ 9 scanf("%lld",&T);C[0][0]=1; 10 for(int i=1;i<NN;i++){ 11 C[i][0]=C[i][i]=1; 12 for(int j=1;j<i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod; 13 } 14 while(T--){ 15 scanf("%lld",&n); memset(f,0,sizeof(f)); 16 for(int i=1;i<=n;i++) scanf("%lld",&a[i]); 17 for(int i=n;i;i--){ 18 for(int j=i;j<=n;j++){ 19 for(int k=i;k<=j;k++){ 20 int l,r; (a[k]==-1)?(l=1,r=n):(l=r=a[k]); 21 for(int d=l;d<=r;d++){ 22 int t1=(i==k?1:f[i][k-1][d]); 23 int t2=(j==k?1:f[k+1][j][d+1]); 24 (f[i][j][d]+=t1*t2%mod*C[j-i][k-i]%mod)%=mod; 25 } 26 } 27 } 28 } 29 printf("%lld\n",f[1][n][1]); 30 } 31 return 0; 32 } 33 } 34 signed main(){return WSN::main();}
以上是在做笛卡尔树专题里比较妙的两道题,再有就是那个$Moles$,
提一下就好了,就是反着建树,原来笛卡尔树的二元组$(k,w)$分别表示数组下标和元素值
这道题里面二元组$(k,w)$表示元素值和数组下标