笛卡尔树
1.笛卡尔树介绍
笛卡尔树是一种二叉树,与平衡树和堆有着密切的联系。其每个节点由二元组 \((k,w)\) 组成。
2.笛卡尔树构造
笛卡尔树的第一键值 \(k\) 应满足 \(BST\) (中序遍历有序)的性质。第二键值应满足堆(小根堆/大根堆)的性质。
一般情况下会把数组下标当作第一键值,把数组的权值当作第二键值。极少数情况下会有任意给定的 \((k,w)\) 。
具体实现时,用单调栈维护从根节点开始的右链。我们先按照 \(k\) 排序,然后一个一个插入。这样的建树复杂度为 \(O(n)\) 。
for(int i=1;i<=n;i++){
int now=top;
while(now>0 && tr[i].w<tr[st[now]].w){
now--;
}
if(now){
tr[st[now]].ch[1]=i;
}
if(now<top){
tr[i].ch[0]=st[now+1];
}
st[++now]=i;
top=now;
}
3.笛卡尔树性质(小根堆为例)
- 以笛卡尔树上任意一点为根的子树是一段连续极长区间。\(w_u\) 是区间最小值,区间在保证最小值不变的情况下不能再向两边延伸。
- 区间 \([a,b]\) 的最小值为 \(w_{lca(a,b)}\) 。
- 若 \(k,w\) 都互不相同,那么笛卡尔树形态结构唯一。
- 对于一个有序的 \(k\) 和无序的 \(w\) 。笛卡尔树上的任意一点期望高度为 \(E(depth_i) = H(i)+H(n-i+1)-1\) 。其中 $H(n)=\sum\limits_{i=1}^n\dfrac{1}{i} $ (调和级数)。因此笛卡尔树期望树高为 \(log\ n\) 。
- \(Treap\) 是一种特殊的笛卡尔树,可以运用 \(Treap\) 操作维护笛卡尔树。
- 笛卡尔树主要解决最大/最小值问题,以及(通过记录时间)关于插入删除的问题
4.例题
-
Largest Rectangle in a Histogram
这道题可以单调栈,但是也可以笛卡尔树。以下标为 \(k\) ,矩阵高为 \(w\) ,建立笛卡尔树(小根堆)。
不难发现,笛卡尔树上每个点的子树大小,就是他的权值高度所能拥有的最大横向距离(最长区间就是子树大小)。故节点 \(u\) 的最大子矩阵就是 $ Size_u \times w_u$ 。
Code:
struct Cartesian{
int k,w;
int ch[2];
};
Cartesian tr[DMAX];
int siz[DMAX];
void DFS(int now){
if(!now){
return ;
}
siz[now]=1;
DFS(tr[now].ch[0]);
siz[now]+=siz[tr[now].ch[0]];
DFS(tr[now].ch[1]);
siz[now]+=siz[tr[now].ch[1]];
}
int main(){
while(scanf("%d",&n)){
if(n==0){
break;
}
for(int i=1;i<=n;i++){
read(a[i]);
tr[i].k=i,tr[i].w=a[i];
tr[i].ch[0]=tr[i].ch[1]=0;
}
top=0;
build();
mem(siz,0);
DFS(st[1]);
ll ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,1ll*siz[i]*tr[i].w);
}
printf("%lld\n", ans);
}
return 0;
}
-
笛卡尔树板子题。没有什么奇技淫巧,直接算就行了。
-
还是一个板子题,但是这次要以权值为 \(k\) 值,以下标为 \(w\) 值。注意输出时为先序遍历。
Code:
struct Cartesian{ int k,w; int ch[2]; int fa; bool operator <(const Cartesian &p) const{ return k<p.k; } }; Cartesian tr[DMAX]; void DFS(int now){ if(!now){ return ; } printf("%d ", tr[now].k); DFS(tr[now].ch[0]); DFS(tr[now].ch[1]); return ; } int main(){ read(n); for(int i=1;i<=n;i++){ read(a[i]); tr[i].k=a[i],tr[i].w=i; } sort(tr+1,tr+n+1); build(); DFS(st[1]); return 0; }
4.Periodni
笛卡尔树+树形DP
我们以下标为 \(k\) 值,高度为 \(w\) 值建立笛卡尔树。这时,我们把整个图划分为了很多个小矩形。
我们首先先考虑一个小情况:对于 \(n*m\) 的矩形,在其中选 \(k\) 个点的方案数。答案是:\(\binom{n}{k}\times\binom{m}{k}\times k!\) 。考虑其组合意义加以证明,在 \(n\) 行中选出 \(k\) 行,在 \(m\) 列中选出 \(k\) 列。然后对于每一种行的选择排列,都有 \(k!\) 种列的选择排列与其对应。
现在我们考虑DP,令 \(f_{i,j}\) 表示在 \(i\) 的子树中选出 \(j\) 个点的方案数。
我们先不考虑 \(u\) 这个节点的矩阵。先考虑其儿子。显而易见的边界条件为:\(f_{u,0}=1\) 。我们枚举他的儿子 \(v\) ,那么有转移:\(f_{u,j}=\sum\limits_{i=0}^jf_{v,i}\times f_{u,j-i}\) 。
那么现在 \(f_{u,i}\) 就是从 \(u\) 的儿子中选择 \(i\) 个点的方案数。然后把这个点也考虑上(要减去已经通过儿子选择过的列)。于是有:\(f_{u,i}=\sum\limits_{j=1}^{i}f_{u,i-j}\times \binom{siz_u-(i-j)}{j}\times\binom{a_u-a_{fa_u}}{j}\times j!\) 。
答案就是:\(f_{rt,k}\)
Code:
void DFS(int now,int fa){ siz[now]=1; f[now][0]=1; for(int i=0;i<=1;i++){ if(ch[now][i]==0){ continue; } DFS(ch[now][i],a[now]); siz[now]+=siz[ch[now][i]]; int v=ch[now][i]; for(int j=min(k,siz[now]);~j;j--){ for(int l=1;l<=min(siz[v],j);l++){ f[now][j]=(0ll+f[now][j]+1ll*f[v][l]*f[now][j-l]%MOD)%MOD; } } } for(int i=min(siz[now],k);~i;i--){ for(int j=1;j<=min(i,a[now]-fa);j++){ f[now][i]=(0ll+f[now][i]+1ll*f[now][i-j]*jc[j]%MOD*C(siz[now]-(i-j),j)%MOD*C(a[now]-fa,j)%MOD)%MOD; f[now][i]=(f[now][i]%MOD+MOD)%MOD; } } }