Loading

笛卡尔树学习笔记

笛卡尔树

说实在的感觉这个东西过于抽象了,只能够掌握几种比较套路的笛卡尔树题目,

要是考场上出一些非常隐含的建树或者维护,大概是用不出来的,(除非灵光一现)

先放一个建树的板子

 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();}
BZOJ#2616

 

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();}
View Code

 

以上是在做笛卡尔树专题里比较妙的两道题,再有就是那个$Moles$,

提一下就好了,就是反着建树,原来笛卡尔树的二元组$(k,w)$分别表示数组下标和元素值

这道题里面二元组$(k,w)$表示元素值和数组下标

posted @ 2021-10-13 21:43  雪域亡魂  阅读(111)  评论(0编辑  收藏  举报